diff --git a/.eslintignore b/.eslintignore index 05bb8944870..f6c8e2019eb 100644 --- a/.eslintignore +++ b/.eslintignore @@ -9,4 +9,5 @@ test/**/null.js test/**/main.js test/**/cliEntry.js test/**/foo.js -test/binCases/config-location/webpack-babel-config/bin/es6.js \ No newline at end of file +test/binCases/config-location/webpack-babel-config/bin/es6.js +packages/utils/validate-identifier.ts \ No newline at end of file diff --git a/INIT.md b/INIT.md index 112ca86eb77..4a1e9f57668 100644 --- a/INIT.md +++ b/INIT.md @@ -1,6 +1,6 @@ # webpack-cli init -`webpack-cli init` is used to initialize `webpack` projects quickly by scaffolding configuration and installing modules required for the project as per user preferences. +`webpack-cli init` is used to initialize `webpack` projects quickly by scaffolding configuration and creating a runnable project with all the dependencies based on the user preferences. ## Initial Setup @@ -10,38 +10,38 @@ These are the steps necessary to setup `webpack-cli init` locally: 1. Create `package.json` through npm - ```shell - npm init - ``` + ```shell + npm init + ``` 2. Install `webpack` and `webpack-cli` as devDependencies - ```shell + ```shell npm install --save-dev webpack webpack-cli ``` 3. Install `@webpack-cli/init` package to add the init scaffold - ```shell + ```shell npm install --save-dev @webpack-cli/init ``` - + ### b. Global Setup These are the steps necessary to setup `webpack-cli init` globally: 1. Install `webpack` and `webpack-cli` globally - ```shell - npm install -g webpack webpack-cli - ``` - + ```shell + npm install -g webpack webpack-cli + ``` + 2. Install `@webpack-cli/init` package to add the init scaffold - ```shell - npm install -g @webpack-cli/init - ``` - + ```shell + npm install -g @webpack-cli/init + ``` + ## Usage ### a. Running locally @@ -60,36 +60,35 @@ webpack-cli init 1. `Will your application have multiple bundles? (y/N)` -> *Property/key resolved: [entry](https://webpack.js.org/configuration/entry-context/#entry)* +> _Property/key resolved: [entry](https://webpack.js.org/configuration/entry-context/#entry)_ This is used to determine if your app will have multiple [entry points](https://webpack.js.org/configuration/entry-context/#entry). If you want to have multiple entry points, answer yes. If you want to have only one, answer no. 2. `Which will be your application entry point? (src/index)` -> *Property/key resolved: [entry](https://webpack.js.org/configuration/entry-context/#entry)* +> _Property/key resolved: [entry](https://webpack.js.org/configuration/entry-context/#entry)_ -This tells webpack from which file to start bundling your application. The default answer `src/index` will tell webpack to look for a file called `index` inside a folder named `src`. +This tells webpack from which file to start bundling your application. The default answer `src/index` will tell webpack to look for a file called `index` inside a folder named `src`. 3. `In which folder do you want to store your generated bundles? (dist)` -> *Property/key resolved: [output.path](https://webpack.js.org/configuration/output/#output-path)* +> _Property/key resolved: [output.path](https://webpack.js.org/configuration/output/#output-path)_ The output directory is where your bundled application will be. Your `index.html` will read the generated files from this folder, that is usually named `dist`. 4. `Will you be using ES2015? (Y/n)` -> *Property/key resolved: [module.rules](https://webpack.js.org/configuration/module/#module-rules) (for .js files)* +> _Property/key resolved: [module.rules](https://webpack.js.org/configuration/module/#module-rules) (for .js files)_ -This enables webpack to parse [`ES2015`](https://babeljs.io/learn-es2015/) code. Answer `Yes` if you want to use modern JavaScript in your project. +This enables webpack to parse [`ES2015`](https://babeljs.io/learn-es2015/) code. Answer `Yes` if you want to use modern JavaScript in your project. 5. `Will you use one of the below CSS solutions?` -> *Property/key resolved: [module.rules](https://webpack.js.org/configuration/module/#module-rules) (for .scss,.less,.css,.postCSS files)* +> _Property/key resolved: [module.rules](https://webpack.js.org/configuration/module/#module-rules) (for .scss,.less,.css,.postCSS files)_ -If you use any sort of style in your project, such as [`.less`](http://lesscss.org/), [`.scss`](http://sass-lang.com/), [`.css`](https://developer.mozilla.org/en-US/docs/Web/CSS) or [`postCSS`](http://postcss.org/) you will need to declare this here. If you don't use CSS, answer no. +If you use any sort of style in your project, such as [`.less`](http://lesscss.org/), [`.scss`](http://sass-lang.com/), [`.css`](https://developer.mozilla.org/en-US/docs/Web/CSS) or [`postCSS`](http://postcss.org/) you will need to declare this here. If you don't use CSS, answer no. -6. `If you want to bundle your CSS files, what will you name the bundle? (press -enter to skip)` +6. `If you want to bundle your CSS files, what will you name the bundle? (press enter to skip)` If you indicate based on previous questions that you are using production, this will be enabled. The default value for your generated CSS file is `style.[contentHash].css`, which will collect all your `.less`, `.scss` or `.css` into one file. This will make your build faster in production. diff --git a/SECURITY.md b/SECURITY.md index c54aedcf910..92c3d610560 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,8 +8,8 @@ webpack CLI is currently supporting webpack v4 and webpack v5. Security fixes ar | webpack version | webpack-cli version | Supported | | --------------- | ----------------------------- | ------------------ | -| >= 4.20.x | ^3.1.2 | :white_check_mark: | -| <= 4.19.x | ^3.1.1 | :white_check_mark: | +| >= 4.20.x | ^3.1.2 | :white_check_mark: | +| <= 4.19.x | ^3.1.1 | :white_check_mark: | | 5.x.0 | ^3.1.2 | :white_check_mark: | | 5.0.x | ^3.1.2 | :white_check_mark: | | < 4.x.x | (CLI included in webpack < 4) | :x: | diff --git a/bin/opencollective.js b/bin/opencollective.js index c8e81b3da6c..044ea80206c 100644 --- a/bin/opencollective.js +++ b/bin/opencollective.js @@ -26,7 +26,11 @@ function printBadge() { print(`Please consider donating to our ${chalk.bold.blue("Open Collective")}`); print("to help us maintain this package."); console.log("\n\n"); - print(`${emoji("👉")} ${chalk.bold.yellow(" Donate:")} ${chalk.reset.underline.yellow("https://opencollective.com/webpack/donate")}`); + print( + `${emoji("👉")} ${chalk.bold.yellow(" Donate:")} ${chalk.reset.underline.yellow( + "https://opencollective.com/webpack/donate" + )}` + ); console.log("\n"); } diff --git a/bin/utils/convert-argv.js b/bin/utils/convert-argv.js index d1c8d8d2f43..dc15726850d 100644 --- a/bin/utils/convert-argv.js +++ b/bin/utils/convert-argv.js @@ -110,9 +110,9 @@ module.exports = function(...args) { argv.configRegister.forEach(dep => { require(dep); }); - return require(configPath); + return require(path.resolve(process.cwd(), configPath)); } else { - return require(configPath); + return require(path.resolve(process.cwd(), configPath)); } })(); options = prepareOptions(options, argv); diff --git a/package-lock.json b/package-lock.json index 7b47fe3b74d..6677cfe9e17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2686,40 +2686,69 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.7.0.tgz", - "integrity": "sha512-NUSz1aTlIzzTjFFVFyzrbo8oFjHg3K/M9MzYByqbMCxeFdErhLAcGITVfXzSz+Yvp5OOpMu3HkIttB0NyKl54Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.9.0.tgz", + "integrity": "sha512-FOgfBorxjlBGpDIw+0LaZIXRX6GEEUfzj8LXwaQIUCp+gDOvkI+1WgugJ7SmWiISqK9Vj5r8S7NDKO/LB+6X9A==", "dev": true, "requires": { - "@typescript-eslint/parser": "1.7.0", - "@typescript-eslint/typescript-estree": "1.7.0", + "@typescript-eslint/experimental-utils": "1.9.0", + "@typescript-eslint/parser": "1.9.0", "eslint-utils": "^1.3.1", + "functional-red-black-tree": "^1.0.1", "regexpp": "^2.0.1", "requireindex": "^1.2.0", "tsutils": "^3.7.0" } }, - "@typescript-eslint/parser": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.7.0.tgz", - "integrity": "sha512-1QFKxs2V940372srm12ovSE683afqc1jB6zF/f8iKhgLz1yoSjYeGHipasao33VXKI+0a/ob9okeogGdKGvvlg==", + "@typescript-eslint/experimental-utils": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.9.0.tgz", + "integrity": "sha512-1s2dY9XxBwtS9IlSnRIlzqILPyeMly5tz1bfAmQ84Ul687xBBve5YsH5A5EKeIcGurYYqY2w6RkHETXIwnwV0A==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "1.7.0", - "eslint-scope": "^4.0.0", - "eslint-visitor-keys": "^1.0.0" + "@typescript-eslint/typescript-estree": "1.9.0" + }, + "dependencies": { + "@typescript-eslint/typescript-estree": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.9.0.tgz", + "integrity": "sha512-7Eg0TEQpCkTsEwsl1lIzd6i7L3pJLQFWesV08dS87bNz0NeSjbL78gNAP1xCKaCejkds4PhpLnZkaAjx9SU8OA==", + "dev": true, + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + } } }, - "@typescript-eslint/typescript-estree": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.7.0.tgz", - "integrity": "sha512-K5uedUxVmlYrVkFbyV3htDipvLqTE3QMOUQEHYJaKtgzxj6r7c5Ca/DG1tGgFxX+fsbi9nDIrf4arq7Ib7H/Yw==", + "@typescript-eslint/parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.9.0.tgz", + "integrity": "sha512-CWgC1XrQ34H/+LwAU7vY5xteZDkNqeAkeidEpJnJgkKu0yqQ3ZhQ7S+dI6MX4vmmM1TKRbOrKuXc6W0fIHhdbA==", "dev": true, "requires": { - "lodash.unescape": "4.0.1", - "semver": "5.5.0" + "@typescript-eslint/experimental-utils": "1.9.0", + "@typescript-eslint/typescript-estree": "1.9.0", + "eslint-scope": "^4.0.0", + "eslint-visitor-keys": "^1.0.0" }, "dependencies": { + "@typescript-eslint/typescript-estree": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.9.0.tgz", + "integrity": "sha512-7Eg0TEQpCkTsEwsl1lIzd6i7L3pJLQFWesV08dS87bNz0NeSjbL78gNAP1xCKaCejkds4PhpLnZkaAjx9SU8OA==", + "dev": true, + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + } + }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", @@ -12418,7 +12447,7 @@ }, "resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "resolved": false, "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, @@ -13179,8 +13208,7 @@ "prettier": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.0.tgz", - "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==", - "dev": true + "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==" }, "prettier-eslint": { "version": "8.8.2", @@ -16503,9 +16531,9 @@ "dev": true }, "tsutils": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.10.0.tgz", - "integrity": "sha512-q20XSMq7jutbGB8luhKKsQldRKWvyBO2BGqni3p4yq8Ys9bEP/xQw3KepKmMRt9gJ4lvQSScrihJrcKdKoSU7Q==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.12.0.tgz", + "integrity": "sha512-64KxDOb3+5ZVbz6NDZlCtOHstLk9+W96Y7d5Z/s5ge92gLaunxDeXYahvB7Rhl1dbaa3ifyq/W53o4mshIV1Tw==", "dev": true, "requires": { "tslib": "^1.8.1" diff --git a/package.json b/package.json index 5135a191ba4..9b3a166d93d 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,10 @@ "clean:all": "rimraf node_modules packages/*/{node_modules}", "commit": "git-cz", "docs": "typedoc", - "format": "prettier-eslint ./bin/*.js ./test/**/*.js ./packages/**/*.js --write", - "lint:codeOnly": "eslint \"{bin}/**/!(__testfixtures__)/*.js\" \"{bin}/**.js\"", - "lint": "eslint \"./bin/*.js\" \"./test/**/*.js\" \"packages/**/!(node_modules)/*.ts\"", + "format": "npm run format:js && npm run format:ts", + "format:ts": "prettier-eslint ./bin/*.js ./bin/**/*.js ./test/**/*.js ./packages/**/**/*.js ./packages/**/*.js --write", + "format:js": "prettier-eslint ./packages/**/**/*.ts ./packages/**/*.ts ./packages/**/**/**/*.ts --write", + "lint": "eslint \"./bin/*.js\" \"./bin/**/*.js\" \"./test/**/*.js\" \"packages/**/!(node_modules)/*.ts\" \"packages/**/!(node_modules)/**/*.ts\"", "postinstall": "node ./bin/opencollective.js", "pretest": "npm run build && npm run lint", "reportCoverage": "nyc report --reporter=json && codecov -f coverage/coverage-final.json --disable=gcov", @@ -145,8 +146,8 @@ "@commitlint/travis-cli": "^7.2.1", "@types/jest": "^23.3.14", "@types/node": "^10.12.9", - "@typescript-eslint/eslint-plugin": "^1.6.0", - "@typescript-eslint/parser": "^1.6.0", + "@typescript-eslint/eslint-plugin": "^1.9.0", + "@typescript-eslint/parser": "^1.9.0", "babel-preset-env": "^1.7.0", "babel-preset-jest": "^24.3.0", "bundlesize": "^0.17.2", diff --git a/packages/generators/.gitignore b/packages/generators/.gitignore index 74dcaf3ce86..b3cdf36cae7 100644 --- a/packages/generators/.gitignore +++ b/packages/generators/.gitignore @@ -2,3 +2,4 @@ **/*.js !*.test.js !/**/*.test.js +!/templates/*.js diff --git a/packages/generators/__tests__/add-generator.test.ts b/packages/generators/__tests__/add-generator.test.ts new file mode 100644 index 00000000000..76588aa87df --- /dev/null +++ b/packages/generators/__tests__/add-generator.test.ts @@ -0,0 +1,13 @@ +import { generatePluginName } from "../utils/plugins"; + +describe("generatePluginName", () => { + it("should return webpack Standard Plugin Name for Name : extract-text-webpack-plugin", () => { + const pluginName = generatePluginName("extract-text-webpack-plugin"); + expect(pluginName).toEqual("ExtractTextWebpackPlugin"); + }); + + it("should return webpack Standard Plugin Name for Name : webpack.DefinePlugin", () => { + const pluginName = generatePluginName("webpack.DefinePlugin"); + expect(pluginName).toEqual("webpack.DefinePlugin"); + }); +}); diff --git a/packages/generators/add-generator.ts b/packages/generators/add-generator.ts index bef3c32afd3..0bdcd282498 100644 --- a/packages/generators/add-generator.ts +++ b/packages/generators/add-generator.ts @@ -11,27 +11,11 @@ import { AutoComplete, Confirm, Input, List } from "@webpack-cli/webpack-scaffol import { SchemaProperties, WebpackOptions } from "./types"; import entryQuestions from "./utils/entry"; - -import webpackDevServerSchema from "webpack-dev-server/lib/options.json"; -import webpackSchema from "./utils/optionsSchema.json"; +import { generatePluginName } from "./utils/plugins"; +import * as webpackDevServerSchema from "webpack-dev-server/lib/options.json"; +import * as webpackSchema from "./utils/optionsSchema.json"; const PROPS: string[] = Array.from(PROP_TYPES.keys()); -/** - * - * Replaces the string with a substring at the given index - * https://gist.github.com/efenacigiray/9367920 - * - * @param {String} str - string to be modified - * @param {Number} index - index to replace from - * @param {String} replace - string to replace starting from index - * - * @returns {String} string - The newly mutated string - * - */ -function replaceAt(str: string, index: number, replace: string): string { - return str.substring(0, index) + replace + str.substring(index + 1); -} - /** * * Checks if the given array has a given property @@ -106,8 +90,9 @@ export default class AddGenerator extends Generator { const done: () => {} = this.async(); let action: string; const self: this = this; - const manualOrListInput: (promptAction: string) => Generator.Question = (promptAction: string): Generator.Question => - Input("actionAnswer", `What do you want to add to ${promptAction}?`); + const manualOrListInput: (promptAction: string) => Generator.Question = ( + promptAction: string + ): Generator.Question => Input("actionAnswer", `What do you want to add to ${promptAction}?`); let inputPrompt: Generator.Question; // first index indicates if it has a deep prop, 2nd indicates what kind of @@ -140,7 +125,7 @@ export default class AddGenerator extends Generator { .then( (entryTypeAnswer: { entryType: boolean }): Promise => { // Ask different questions for entry points - return entryQuestions(self, entryTypeAnswer); + return entryQuestions(self, entryTypeAnswer.entryType); } ) .then( @@ -395,15 +380,7 @@ export default class AddGenerator extends Generator { (p: boolean): void => { if (p) { this.dependencies.push(answerToAction.actionAnswer); - const normalizePluginName = answerToAction.actionAnswer.replace( - "-webpack-plugin", - "Plugin" - ); - const pluginName = replaceAt( - normalizePluginName, - 0, - normalizePluginName.charAt(0).toUpperCase() - ); + const pluginName = generatePluginName(answerToAction.actionAnswer); this.configuration.config.topScope.push( `const ${pluginName} = require("${answerToAction.actionAnswer}")` ); diff --git a/packages/generators/addon-generator.ts b/packages/generators/addon-generator.ts index fbbcf7fc8e5..0e61f7748d8 100644 --- a/packages/generators/addon-generator.ts +++ b/packages/generators/addon-generator.ts @@ -31,7 +31,8 @@ const addonGenerator = ( copyFiles: string[], copyTemplateFiles: string[], templateFn: Function -): typeof Generator => class AddonGenerator extends Generator { +): typeof Generator => + class AddonGenerator extends Generator { public props: Generator.Question; public copy: (value: string, index: number, array: string[]) => void; public copyTpl: (value: string, index: number, array: string[]) => void; diff --git a/packages/generators/index.ts b/packages/generators/index.ts index dd4bd030f27..b537adb02f6 100644 --- a/packages/generators/index.ts +++ b/packages/generators/index.ts @@ -13,5 +13,5 @@ export { loaderGenerator, pluginGenerator, removeGenerator, - updateGenerator, + updateGenerator }; diff --git a/packages/generators/init-generator.ts b/packages/generators/init-generator.ts index 715b44e8797..8ba18e4ca65 100644 --- a/packages/generators/init-generator.ts +++ b/packages/generators/init-generator.ts @@ -1,16 +1,18 @@ import chalk from "chalk"; import * as logSymbols from "log-symbols"; import * as Generator from "yeoman-generator"; -import * as Inquirer from "inquirer"; +import * as path from "path"; import { getPackageManager } from "@webpack-cli/utils/package-manager"; import { Confirm, Input, List } from "@webpack-cli/webpack-scaffold"; +import { getDefaultOptimization } from "./utils/webpackConfig"; import { WebpackOptions } from "./types"; import entryQuestions from "./utils/entry"; -import getBabelPlugin from "./utils/module"; -import getDefaultPlugins from "./utils/plugins"; +import langQuestionHandler, { LangType } from "./utils/languageSupport"; +import styleQuestionHandler, { Loader, StylingType } from "./utils/styleSupport"; import tooltip from "./utils/tooltip"; +import { generatePluginName } from "./utils/plugins"; /** * @@ -32,390 +34,222 @@ export default class InitGenerator extends Generator { webpackOptions?: WebpackOptions; }; }; + private langType: string; public constructor(args, opts) { super(args, opts); - this.isProd = false; - (this.usingDefaults = false), - (this.dependencies = [ - "webpack", - "webpack-cli", - "terser-webpack-plugin", - "babel-plugin-syntax-dynamic-import" - ]); + (this.usingDefaults = false), (this.isProd = this.usingDefaults ? true : false); + + this.dependencies = ["webpack", "webpack-cli", "babel-plugin-syntax-dynamic-import"]; + if (this.isProd) { + this.dependencies.push("terser-webpack-plugin"); + } else { + this.dependencies.push("webpack-dev-server"); + } + this.configuration = { config: { + configName: this.isProd ? "prod" : "config", topScope: [], - webpackOptions: {} + webpackOptions: { + mode: this.isProd ? "'production'" : "'development'", + entry: undefined, + output: undefined, + plugins: [], + module: { + rules: [] + } + } } }; + + // add splitChunks options for transparency + // defaults coming from: https://webpack.js.org/plugins/split-chunks-plugin/#optimization-splitchunks + this.configuration.config.topScope.push( + "const path = require('path');", + "const webpack = require('webpack');", + "\n", + tooltip.splitChunks() + ); + + if (this.isProd) { + this.configuration.config.topScope.push( + tooltip.terser(), + "const TerserPlugin = require('terser-webpack-plugin');", + "\n" + ); + } + + (this.configuration.config.webpackOptions.plugins as string[]).push("new webpack.ProgressPlugin()"); + + let optimizationConfig = getDefaultOptimization(this.isProd); + this.configuration.config.webpackOptions.optimization = optimizationConfig; + + if (!this.isProd) { + this.configuration.config.webpackOptions.devServer = { + open: true + }; + } } - // eslint-disable-next-line - public prompting(): any { + public async prompting() { const done: () => {} = this.async(); const self: this = this; let regExpForStyles: string; - let ExtractUseProps: object[]; + let ExtractUseProps: Loader[]; process.stdout.write( - "\n" + - logSymbols.info + - chalk.blue(" INFO ") + - "For more information and a detailed description of each question, have a look at " + - chalk.bold.green("https://github.com/webpack/webpack-cli/blob/master/INIT.md") + - "\n" + `\n${logSymbols.info}${chalk.blue(" INFO ")} ` + + `For more information and a detailed description of each question, have a look at: ` + + `${chalk.bold.green("https://github.com/webpack/webpack-cli/blob/master/INIT.md")}\n` ); process.stdout.write( - logSymbols.info + - chalk.blue(" INFO ") + - "Alternatively, run `webpack(-cli) --help` for usage info." + - "\n\n" + `${logSymbols.info}${chalk.blue(" INFO ")} ` + + `Alternatively, run "webpack(-cli) --help" for usage info\n\n` ); - this.configuration.config.webpackOptions.module = { - rules: [] - }; - this.configuration.config.topScope.push( - "const webpack = require('webpack')", - "const path = require('path')", - "\n" - ); + const { multiEntries } = await this.prompt([ + Confirm("multiEntries", "Will your application have multiple bundles?", false) + ]); - return this.prompt([Confirm("entryType", "Will your application have multiple bundles?", false)]) - .then( - (entryTypeAnswer: { entryType: boolean }): Promise => { - // Ask different questions for entry points - return entryQuestions(self, entryTypeAnswer); - } - ) - .then( - (entryOptions: object | string): Promise<{}> => { - if (typeof entryOptions === "string" && entryOptions.length > 0) { - if (entryOptions !== "\"\"" && entryOptions !== "\'\'") { - this.configuration.config.webpackOptions.entry = entryOptions; - } - } + // TODO string | object + const entryOption: void | {} = await entryQuestions(self, multiEntries); - return this.prompt([ - Input("outputType", "In which folder do you want to store your generated bundles? (dist):") - ]); - } - ) - .then( - (outputTypeAnswer: { outputType: string }): void => { - // As entry is not required anymore and we dont set it to be an empty string or """"" - // it can be undefined so falsy check is enough (vs entry.length); - if (!this.configuration.config.webpackOptions.entry && !this.usingDefaults) { - this.configuration.config.webpackOptions.output = { - chunkFilename: "'[name].[chunkhash].js'", - filename: "'[name].[chunkhash].js'" - }; - } else if (!this.usingDefaults) { - this.configuration.config.webpackOptions.output = { - filename: "'[name].[chunkhash].js'" - }; - } - if (!this.usingDefaults && outputTypeAnswer.outputType.length) { - this.configuration.config.webpackOptions.output.path = `path.resolve(__dirname, '${ - outputTypeAnswer.outputType - }')`; - } - } - ) - .then( - (): Promise => { - this.isProd = this.usingDefaults ? true : false; - this.configuration.config.configName = this.isProd ? "prod" : "config"; - if (!this.isProd) { - this.configuration.config.webpackOptions.mode = "'development'"; - } - this.configuration.config.webpackOptions.plugins = this.isProd ? [] : getDefaultPlugins(); - return this.prompt([Confirm("babelConfirm", "Will you be using ES2015?")]); - } - ) - .then( - (babelConfirmAnswer: { babelConfirm: boolean }): void => { - if (babelConfirmAnswer.babelConfirm) { - this.configuration.config.webpackOptions.module.rules.push(getBabelPlugin()); - this.dependencies.push("babel-loader", "@babel/core", "@babel/preset-env"); - } - } - ) - .then( - (): Promise => { - return this.prompt([ - List("stylingType", "Will you use one of the below CSS solutions?", [ - "No", - "CSS", - "SASS", - "LESS", - "PostCSS" - ]) - ]); - } - ) - .then( - (stylingTypeAnswer: { stylingType: string }): void => { - ExtractUseProps = []; - switch (stylingTypeAnswer.stylingType) { - case "SASS": - this.dependencies.push("sass-loader", "node-sass", "style-loader", "css-loader"); - regExpForStyles = `${new RegExp(/\.(scss|css)$/)}`; - if (this.isProd) { - ExtractUseProps.push( - { - loader: "'css-loader'", - options: { - sourceMap: true - } - }, - { - loader: "'sass-loader'", - options: { - sourceMap: true - } - } - ); - } else { - ExtractUseProps.push( - { - loader: "'style-loader'" - }, - { - loader: "'css-loader'" - }, - { - loader: "'sass-loader'" - } - ); - } - break; - case "LESS": - regExpForStyles = `${new RegExp(/\.(less|css)$/)}`; - this.dependencies.push("less-loader", "less", "style-loader", "css-loader"); - if (this.isProd) { - ExtractUseProps.push( - { - loader: "'css-loader'", - options: { - sourceMap: true - } - }, - { - loader: "'less-loader'", - options: { - sourceMap: true - } - } - ); - } else { - ExtractUseProps.push( - { - loader: "'css-loader'", - options: { - sourceMap: true - } - }, - { - loader: "'less-loader'", - options: { - sourceMap: true - } - } - ); - } - break; - case "PostCSS": - this.configuration.config.topScope.push( - tooltip.postcss(), - "const autoprefixer = require('autoprefixer');", - "const precss = require('precss');", - "\n" - ); - this.dependencies.push( - "style-loader", - "css-loader", - "postcss-loader", - "precss", - "autoprefixer" - ); - regExpForStyles = `${new RegExp(/\.css$/)}`; - if (this.isProd) { - ExtractUseProps.push( - { - loader: "'css-loader'", - options: { - importLoaders: 1, - sourceMap: true - } - }, - { - loader: "'postcss-loader'", - options: { - plugins: `function () { - return [ - precss, - autoprefixer - ]; - }` - } - } - ); - } else { - ExtractUseProps.push( - { - loader: "'style-loader'" - }, - { - loader: "'css-loader'", - options: { - importLoaders: 1, - sourceMap: true - } - }, - { - loader: "'postcss-loader'", - options: { - plugins: `function () { - return [ - precss, - autoprefixer - ]; - }` - } - } - ); - } - break; - case "CSS": - this.dependencies.push("style-loader", "css-loader"); - regExpForStyles = `${new RegExp(/\.css$/)}`; - if (this.isProd) { - ExtractUseProps.push({ - loader: "'css-loader'", - options: { - sourceMap: true - } - }); - } else { - ExtractUseProps.push( - { - loader: "'style-loader'", - options: { - sourceMap: true - } - }, - { - loader: "'css-loader'" - } - ); - } - break; - default: - regExpForStyles = null; - } - } - ) - .then( - (): Promise => { - if (this.isProd) { - // Ask if the user wants to use extractPlugin - return this.prompt([ - Input( - "extractPlugin", - "If you want to bundle your CSS files, what will you name the bundle? (press enter to skip)" - ) - ]); - } - } - ) - .then( - (extractPluginAnswer: { extractPlugin: string }): void => { - if (regExpForStyles) { - if (this.isProd) { - const cssBundleName: string = extractPluginAnswer.extractPlugin; - this.configuration.config.topScope.push(tooltip.cssPlugin()); - this.dependencies.push("mini-css-extract-plugin"); - - if (cssBundleName.length !== 0) { - (this.configuration.config.webpackOptions.plugins as string[]).push( - // TODO: use [contenthash] after it is supported - `new MiniCssExtractPlugin({ filename:'${cssBundleName}.[chunkhash].css' })` - ); - } else { - (this.configuration.config.webpackOptions.plugins as string[]).push( - "new MiniCssExtractPlugin({ filename:'style.css' })" - ); - } - - ExtractUseProps.unshift({ - loader: "MiniCssExtractPlugin.loader" - }); - - const moduleRulesObj = { - test: regExpForStyles, - use: ExtractUseProps - }; - - this.configuration.config.webpackOptions.module.rules.push(moduleRulesObj); - this.configuration.config.topScope.push( - "const MiniCssExtractPlugin = require('mini-css-extract-plugin');", - "\n" - ); - } else { - const moduleRulesObj: { - test: string; - use: object[]; - } = { - test: regExpForStyles, - use: ExtractUseProps - }; - - this.configuration.config.webpackOptions.module.rules.push(moduleRulesObj); - } + if (typeof entryOption === "string" && entryOption.length > 0) { + this.configuration.config.webpackOptions.entry = `${entryOption}`; + } else if (typeof entryOption === "object") { + this.configuration.config.webpackOptions.entry = entryOption; + } + + const { outputDir } = await this.prompt([ + Input("outputDir", "In which folder do you want to store your generated bundles?", "dist") + ]); + + // As entry is not required anymore and we dont set it to be an empty string or """"" + // it can be undefined so falsy check is enough (vs entry.length); + if (!this.configuration.config.webpackOptions.entry && !this.usingDefaults) { + this.configuration.config.webpackOptions.output = { + chunkFilename: "'[name].[chunkhash].js'", + filename: "'[name].[chunkhash].js'" + }; + } else if (!this.usingDefaults) { + this.configuration.config.webpackOptions.output = { + filename: "'[name].[chunkhash].js'" + }; + } + if (!this.usingDefaults && outputDir.length) { + this.configuration.config.webpackOptions.output.path = `path.resolve(__dirname, '${outputDir}')`; + } + + const { langType } = await this.prompt([ + List("langType", "Will you use one of the below JS solutions?", [LangType.ES6, LangType.Typescript, "No"]) + ]); + + langQuestionHandler(this, langType); + this.langType = langType; + + const { stylingType } = await this.prompt([ + List("stylingType", "Will you use one of the below CSS solutions?", [ + "No", + StylingType.CSS, + StylingType.SASS, + StylingType.LESS, + StylingType.PostCSS + ]) + ]); + + ({ ExtractUseProps, regExpForStyles } = styleQuestionHandler(self, stylingType)); + + if (this.isProd) { + // Ask if the user wants to use extractPlugin + const { useExtractPlugin } = await this.prompt([ + Input( + "useExtractPlugin", + "If you want to bundle your CSS files, what will you name the bundle? (press enter to skip)" + ) + ]); + + if (regExpForStyles) { + if (this.isProd) { + const cssBundleName: string = useExtractPlugin; + this.dependencies.push("mini-css-extract-plugin"); + this.configuration.config.topScope.push( + tooltip.cssPlugin(), + "const MiniCssExtractPlugin = require('mini-css-extract-plugin');", + "\n" + ); + if (cssBundleName.length !== 0) { + (this.configuration.config.webpackOptions.plugins as string[]).push( + // TODO: use [contenthash] after it is supported + `new MiniCssExtractPlugin({ filename:'${cssBundleName}.[chunkhash].css' })` + ); + } else { + (this.configuration.config.webpackOptions.plugins as string[]).push( + "new MiniCssExtractPlugin({ filename:'style.css' })" + ); } - // add splitChunks options for transparency - // defaults coming from: https://webpack.js.org/plugins/split-chunks-plugin/#optimization-splitchunks - this.configuration.config.topScope.push(tooltip.splitChunks()); - this.configuration.config.webpackOptions.optimization = { - splitChunks: { - cacheGroups: { - vendors: { - priority: -10, - test: "/[\\\\/]node_modules[\\\\/]/" - } - }, - chunks: "'async'", - minChunks: 1, - minSize: 30000, - // for production name is recommended to be off - name: !this.isProd - } - }; - done(); + + ExtractUseProps.unshift({ + loader: "MiniCssExtractPlugin.loader" + }); } + + this.configuration.config.webpackOptions.module.rules.push({ + test: regExpForStyles, + use: ExtractUseProps + }); + } + } + if (!this.isProd) { + this.dependencies.push("html-webpack-plugin"); + const htmlWebpackDependency = "html-webpack-plugin"; + const htmlwebpackPlugin = generatePluginName(htmlWebpackDependency); + (this.configuration.config.topScope as string[]).push( + `const ${htmlwebpackPlugin} = require('${htmlWebpackDependency}')`, + "\n", + tooltip.html() ); + (this.configuration.config.webpackOptions.plugins as string[]).push(`new ${htmlwebpackPlugin}()`); + } + done(); } + public installPlugins(): void { - if (this.isProd) { - this.dependencies = this.dependencies.filter((p: string): boolean => p !== "terser-webpack-plugin"); - } else { - this.configuration.config.topScope.push( - tooltip.terser(), - "const TerserPlugin = require('terser-webpack-plugin');", - "\n" - ); - } const packager = getPackageManager(); const opts: { dev?: boolean; "save-dev"?: boolean; } = packager === "yarn" ? { dev: true } : { "save-dev": true }; + this.scheduleInstallTask(packager, this.dependencies, opts); } public writing(): void { this.config.set("configuration", this.configuration); + + const packageJsonTemplatePath = "./templates/package.json.js"; + this.fs.extendJSON(this.destinationPath("package.json"), require(packageJsonTemplatePath)(this.isProd)); + + const generateEntryFile = (entryPath: string, name: string): void => { + entryPath = entryPath.replace(/'/g, ""); + this.fs.copyTpl(path.resolve(__dirname, "./templates/index.js"), this.destinationPath(entryPath), { name }); + }; + + // Generate entry file/files + const entry = this.configuration.config.webpackOptions.entry; + if (typeof entry === "string") { + generateEntryFile(entry, "your main file!"); + } else if (typeof entry === "object") { + Object.keys(entry).forEach((name: string): void => generateEntryFile(entry[name], `${name} main file!`)); + } + + // Generate README + this.fs.copyTpl(path.resolve(__dirname, "./templates/README.md"), this.destinationPath("README.md"), {}); + + // Genrate tsconfig + if (this.langType === LangType.Typescript) { + const tsConfigTemplatePath = "./templates/tsconfig.json.js"; + this.fs.extendJSON(this.destinationPath("tsconfig.json"), require(tsConfigTemplatePath)); + } } } diff --git a/packages/generators/loader-generator.ts b/packages/generators/loader-generator.ts index aaec1666ae2..e36dab18f4d 100644 --- a/packages/generators/loader-generator.ts +++ b/packages/generators/loader-generator.ts @@ -35,8 +35,8 @@ const LoaderGenerator = addonGenerator( message: "Loader name", name: "name", type: "input", - validate: (str: string): boolean => str.length > 0, - }, + validate: (str: string): boolean => str.length > 0 + } ], path.resolve(__dirname, "..", "generate-loader"), [ @@ -48,10 +48,10 @@ const LoaderGenerator = addonGenerator( "examples/simple/webpack.config.js.tpl", "examples/simple/src/index.js.tpl", "examples/simple/src/lazy-module.js.tpl", - "examples/simple/src/static-esm-module.js.tpl", + "examples/simple/src/static-esm-module.js.tpl" ], ["src/_index.js.tpl"], - (gen): object => ({ name: gen.props.name }), + (gen): object => ({ name: gen.props.name }) ); export default LoaderGenerator; diff --git a/packages/generators/plugin-generator.ts b/packages/generators/plugin-generator.ts index 82550f520a2..22a0295332e 100644 --- a/packages/generators/plugin-generator.ts +++ b/packages/generators/plugin-generator.ts @@ -19,8 +19,8 @@ const PluginGenerator = addonGenerator( message: "Plugin name", name: "name", type: "input", - validate: (str: string): boolean => str.length > 0, - }, + validate: (str: string): boolean => str.length > 0 + } ], path.resolve(__dirname, "..", "generate-plugin"), [ @@ -29,10 +29,10 @@ const PluginGenerator = addonGenerator( "test/functional.test.js.tpl", "examples/simple/src/index.js.tpl", "examples/simple/src/lazy-module.js.tpl", - "examples/simple/src/static-esm-module.js.tpl", + "examples/simple/src/static-esm-module.js.tpl" ], ["src/_index.js.tpl", "examples/simple/_webpack.config.js.tpl"], - (gen): object => ({ name: _.upperFirst(_.camelCase(gen.props.name)) }), + (gen): object => ({ name: _.upperFirst(_.camelCase(gen.props.name)) }) ); export default PluginGenerator; diff --git a/packages/generators/templates/README.md b/packages/generators/templates/README.md new file mode 100644 index 00000000000..500b88d8caf --- /dev/null +++ b/packages/generators/templates/README.md @@ -0,0 +1,15 @@ +# 🚀 Welcome to your new awesome project! + +This project has been created using **webpack scaffold**, you can now run + +``` +npm run build +``` + +or + +``` +yarn build +``` + +to bundle your application diff --git a/packages/generators/templates/index.js b/packages/generators/templates/index.js new file mode 100644 index 00000000000..0eec583fb7e --- /dev/null +++ b/packages/generators/templates/index.js @@ -0,0 +1 @@ +console.log("Hello World from <%= name %>"); diff --git a/packages/generators/templates/package.json.js b/packages/generators/templates/package.json.js new file mode 100644 index 00000000000..caa852fbc6c --- /dev/null +++ b/packages/generators/templates/package.json.js @@ -0,0 +1,14 @@ +module.exports = isProd => { + let scripts = { + build: "webpack" + }; + if (!isProd) { + scripts.start = "webpack-dev-server"; + } + + return { + version: "1.0.0", + description: "My webpack project", + scripts + }; +}; diff --git a/packages/generators/templates/tsconfig.json.js b/packages/generators/templates/tsconfig.json.js new file mode 100644 index 00000000000..0eae50cb36f --- /dev/null +++ b/packages/generators/templates/tsconfig.json.js @@ -0,0 +1,9 @@ +module.exports = { + compilerOptions: { + allowSyntheticDefaultImports: true, + noImplicitAny: true, + module: "es6", + target: "es5", + allowJs: true + } +}; diff --git a/packages/generators/types/index.ts b/packages/generators/types/index.ts index 449dc0cc477..32ed902ab23 100644 --- a/packages/generators/types/index.ts +++ b/packages/generators/types/index.ts @@ -214,7 +214,7 @@ export interface WebpackOptions { }; } -interface Rule { +export interface Rule { enforce?: "pre" | "post"; exclude?: IRuleSetCondition; include?: IRuleSetCondition; diff --git a/packages/generators/utils/entry.ts b/packages/generators/utils/entry.ts index 47762928337..3146d8b61de 100644 --- a/packages/generators/utils/entry.ts +++ b/packages/generators/utils/entry.ts @@ -1,5 +1,5 @@ import * as Generator from "yeoman-generator"; -import { InputValidate } from "@webpack-cli/webpack-scaffold"; +import { Input, InputValidate } from "@webpack-cli/webpack-scaffold"; import validate from "./validate"; @@ -16,21 +16,17 @@ interface CustomGenerator extends Generator { * @returns {Object} An Object that holds the answers given by the user, later used to scaffold */ -export default function entry( - self: CustomGenerator, - answer: { - entryType: boolean; - } -): Promise { +export default function entry(self: CustomGenerator, multiEntries: boolean): Promise { let entryIdentifiers: string[]; let result: Promise; - if (answer.entryType) { + if (multiEntries) { result = self .prompt([ InputValidate( "multipleEntries", - "Type the names you want for your modules (entry files), separated by comma [example: app,vendor]", - validate + "What do you want to name your bundles? (separated by comma)", + validate, + "pageOne, pageTwo" ) ]) .then( @@ -57,7 +53,7 @@ export default function entry( !n[val].includes("path") && !n[val].includes("process") ) { - n[val] = `\'${n[val].replace(/"|'/g, "").concat(".js")}\'`; + n[val] = `\'./${n[val].replace(/"|'/g, "").concat(".js")}\'`; } webpackEntryPoint[val] = n[val]; } @@ -70,14 +66,16 @@ export default function entry( ); }, Promise.resolve()); } + return forEachPromise( entryIdentifiers, (entryProp: string): Promise => self.prompt([ InputValidate( `${entryProp}`, - `What is the location of "${entryProp}"? [example: ./src/${entryProp}]`, - validate + `What is the location of "${entryProp}"?`, + validate, + `src/${entryProp}` ) ]) ).then( @@ -91,7 +89,9 @@ export default function entry( !entryPropAnswer[val].includes("path") && !entryPropAnswer[val].includes("process") ) { - entryPropAnswer[val] = `\'${entryPropAnswer[val].replace(/"|'/g, "")}\'`; + entryPropAnswer[val] = `\'./${entryPropAnswer[val] + .replace(/"|'/g, "") + .concat(".js")}\'`; } webpackEntryPoint[val] = entryPropAnswer[val]; } @@ -102,18 +102,16 @@ export default function entry( } ); } else { - result = self - .prompt([InputValidate("singularEntry", "Which will be your application entry point? (src/index)")]) - .then( - (singularEntryAnswer: { singularEntry: string }): string => { - let { singularEntry } = singularEntryAnswer; - singularEntry = `\'${singularEntry.replace(/"|'/g, "")}\'`; - if (singularEntry.length <= 0) { - self.usingDefaults = true; - } - return singularEntry; + result = self.prompt([Input("singularEntry", "Which will be your application entry point?", "src/index")]).then( + (singularEntryAnswer: { singularEntry: string }): string => { + let { singularEntry } = singularEntryAnswer; + singularEntry = `\'./${singularEntry.replace(/"|'/g, "").concat(".js")}\'`; + if (singularEntry.length <= 0) { + self.usingDefaults = true; } - ); + return singularEntry; + } + ); } return result; } diff --git a/packages/generators/utils/languageSupport.ts b/packages/generators/utils/languageSupport.ts new file mode 100644 index 00000000000..b564d8a9dc1 --- /dev/null +++ b/packages/generators/utils/languageSupport.ts @@ -0,0 +1,109 @@ +import { Rule } from "../types"; + +export enum LangType { + ES6 = "ES6", + Typescript = "Typescript" +} + +const replaceExt = (path: string, ext: string): string => path.substr(0, path.lastIndexOf(".")) + `${ext}'`; + +function updateEntryExt(self, newExt: string): void { + const jsEntryOption = self.configuration.config.webpackOptions.entry; + let tsEntryOption = {}; + if (typeof jsEntryOption === "string") { + tsEntryOption = replaceExt(jsEntryOption, newExt); + } else if (typeof jsEntryOption === "object") { + Object.keys(jsEntryOption).forEach( + (entry: string): void => { + tsEntryOption[entry] = replaceExt(jsEntryOption[entry], newExt); + } + ); + } + self.configuration.config.webpackOptions.entry = tsEntryOption; +} + +const getFolder = (path: string): string => + path + .replace("'./", "") + .split("/") + .slice(0, -1) + .join("/"); + +function getEntryFolders(self): string[] { + const entryOption = self.configuration.config.webpackOptions.entry; + let entryFolders = {}; + if (typeof entryOption === "string") { + const folder = getFolder(entryOption); + if (folder.length > 0) entryFolders[folder] = true; + } else if (typeof entryOption === "object") { + Object.keys(entryOption).forEach( + (entry: string): void => { + const folder = getFolder(entryOption[entry]); + if (folder.length > 0) entryFolders[folder] = true; + } + ); + } + return Object.keys(entryFolders); +} + +/** + * + * Returns an module.rule object for the babel loader + * @param {string[]} includeFolders An array of folders to include + * @returns {Rule} A configuration containing the babel-loader with env preset + */ +export function getBabelLoader(includeFolders: string[]): Rule { + const include = includeFolders.map((folder: string): string => `path.resolve(__dirname, '${folder}')`); + return { + test: "/.(js|jsx)$/", + include, + loader: "'babel-loader'", + options: { + plugins: ["'syntax-dynamic-import'"], + presets: [ + [ + "'@babel/preset-env'", + { + "'modules'": false + } + ] + ] + } + }; +} + +/** + * + * Returns an module.rule object for the typescript loader + * @param {string[]} includeFolders An array of folders to include + * @returns {Rule} A configuration containing the ts-loader + */ +export function getTypescriptLoader(includeFolders: string[]): Rule { + const include = includeFolders.map((folder: string): string => `path.resolve(__dirname, '${folder}')`); + return { + test: "/.(ts|tsx)?$/", + loader: "'ts-loader'", + include, + exclude: ["/node_modules/"] + }; +} + +export default function language(self, langType: string): void { + const entryFolders = getEntryFolders(self); + switch (langType) { + case LangType.ES6: + self.dependencies.push("babel-loader", "@babel/core", "@babel/preset-env"); + self.configuration.config.webpackOptions.module.rules.push(getBabelLoader(entryFolders)); + break; + + case LangType.Typescript: + self.dependencies.push("typescript", "ts-loader"); + self.configuration.config.webpackOptions.module.rules.push(getTypescriptLoader(entryFolders)); + self.configuration.config.webpackOptions.resolve = { + extensions: ["'.tsx'", "'.ts'", "'.js'"] + }; + + updateEntryExt(self, ".ts"); + break; + } +} diff --git a/packages/generators/utils/module.ts b/packages/generators/utils/module.ts deleted file mode 100644 index 25b846723f1..00000000000 --- a/packages/generators/utils/module.ts +++ /dev/null @@ -1,36 +0,0 @@ -interface Module extends Object { - include: string[]; - loader: string; - options: { - plugins: string[]; - presets: Preset[][]; - }; - test: string; -} - -type Preset = string | object; - -/** - * - * Returns an module.rule object that has the babel loader if invoked - * - * @returns {Function} A callable function that adds the babel-loader with env preset - */ -export default function(): Module { - return { - include: ["path.resolve(__dirname, 'src')"], - loader: "'babel-loader'", - options: { - plugins: ["'syntax-dynamic-import'"], - presets: [ - [ - "'@babel/preset-env'", - { - "'modules'": false - } - ] - ] - }, - test: `${new RegExp(/\.js$/)}` - }; -} diff --git a/packages/generators/utils/plugins.ts b/packages/generators/utils/plugins.ts index 6dedb1fab9e..ebc57bc0f79 100644 --- a/packages/generators/utils/plugins.ts +++ b/packages/generators/utils/plugins.ts @@ -9,3 +9,40 @@ export default function(): string[] { return ["new TerserPlugin()"]; } + +/** + * + * Replaces the string with a substring at the given index + * https://gist.github.com/efenacigiray/9367920 + * + * @param {String} str - string to be modified + * @param {Number} index - index to replace from + * @param {String} replace - string to replace starting from index + * + * @returns {String} string - The newly mutated string + * + */ + +export const replaceAt = (str: string, index: number, replace: string): string => { + return str.substring(0, index) + replace + str.substring(index + 1); +}; + +/** + * + * Generate a webpack standard webpack plugin name from the plugin name from the Answer + * + * @param {String} rawPluginName - plugin name from answer + * + * @returns {String} string - the webpack standard plugin name + * + */ + +export const generatePluginName = (rawPluginName: string): string => { + let myPluginNameArray: string[]; + myPluginNameArray = rawPluginName.split("-"); + const pluginArrLength: number = myPluginNameArray.length; + for (let i = 0; i < pluginArrLength && pluginArrLength > 1; i++) { + myPluginNameArray[i] = replaceAt(myPluginNameArray[i], 0, myPluginNameArray[i].charAt(0).toUpperCase()); + } + return myPluginNameArray.join(""); +}; diff --git a/packages/generators/utils/styleSupport.ts b/packages/generators/utils/styleSupport.ts new file mode 100644 index 00000000000..8a875626fa7 --- /dev/null +++ b/packages/generators/utils/styleSupport.ts @@ -0,0 +1,155 @@ +import tooltip from "./tooltip"; + +export enum StylingType { + CSS = "CSS", + SASS = "SASS", + LESS = "LESS", + PostCSS = "PostCSS" +} + +export enum LoaderName { + CSS = "css-loader", + SASS = "sass-loader", + STYLE = "style-loader", + LESS = "less-loader", + POSTCSS = "postcss-loader" +} + +export enum StyleRegex { + CSS = "/.css$/", + SASS = "/.(scss|css)$/", + LESS = "/.(less|css)$/", + PostCSS = "/.css$/" +} + +export interface Loader { + loader: string; + options?: { + importLoaders?: number; + sourceMap?: boolean; + plugins?: string; + }; +} + +export default function style( + self, + stylingType: string +): { + ExtractUseProps: Loader[]; + regExpForStyles: StyleRegex; +} { + const ExtractUseProps: Loader[] = []; + let regExpForStyles: StyleRegex = null; + + switch (stylingType) { + case StylingType.CSS: + regExpForStyles = StyleRegex.CSS; + + self.dependencies.push(LoaderName.CSS); + if (!self.isProd) { + self.dependencies.push(LoaderName.STYLE); + ExtractUseProps.push({ + loader: `"${LoaderName.STYLE}"` + }); + } + ExtractUseProps.push({ + loader: `"${LoaderName.CSS}"`, + options: { + sourceMap: true + } + }); + break; + + case StylingType.SASS: + regExpForStyles = StyleRegex.SASS; + + self.dependencies.push("node-sass", LoaderName.SASS, LoaderName.CSS); + if (!self.isProd) { + self.dependencies.push(LoaderName.STYLE); + ExtractUseProps.push({ + loader: `"${LoaderName.STYLE}"` + }); + } + ExtractUseProps.push( + { + loader: `"${LoaderName.CSS}"`, + options: { + sourceMap: true + } + }, + { + loader: `"${LoaderName.SASS}"`, + options: { + sourceMap: true + } + } + ); + break; + + case StylingType.LESS: + regExpForStyles = StyleRegex.LESS; + + self.dependencies.push("less", LoaderName.LESS, LoaderName.CSS); + if (!self.isProd) { + self.dependencies.push(LoaderName.STYLE); + ExtractUseProps.push({ + loader: `"${LoaderName.STYLE}"` + }); + } + ExtractUseProps.push( + { + loader: `"${LoaderName.CSS}"`, + options: { + sourceMap: true + } + }, + { + loader: `"${LoaderName.LESS}"`, + options: { + sourceMap: true + } + } + ); + break; + + case StylingType.PostCSS: + regExpForStyles = StyleRegex.PostCSS; + + self.configuration.config.topScope.push( + tooltip.postcss(), + "const autoprefixer = require('autoprefixer');", + "const precss = require('precss');", + "\n" + ); + + self.dependencies.push("precss", "autoprefixer", LoaderName.CSS, LoaderName.POSTCSS); + if (!self.isProd) { + self.dependencies.push(LoaderName.STYLE); + ExtractUseProps.push({ + loader: `"${LoaderName.STYLE}"` + }); + } + ExtractUseProps.push( + { + loader: `"${LoaderName.CSS}"`, + options: { + importLoaders: 1, + sourceMap: true + } + }, + { + loader: `"${LoaderName.POSTCSS}"`, + options: { + plugins: `function () { + return [ + precss, + autoprefixer + ]; + }` + } + } + ); + break; + } + return { ExtractUseProps, regExpForStyles }; +} diff --git a/packages/generators/utils/tooltip.ts b/packages/generators/utils/tooltip.ts index cd9dba4e7ec..c58c7fba922 100644 --- a/packages/generators/utils/tooltip.ts +++ b/packages/generators/utils/tooltip.ts @@ -57,6 +57,17 @@ export default { * * https://github.com/webpack-contrib/terser-webpack-plugin * + */`; + }, + + html: (): string => { + return `/* + * We've enabled HtmlWebpackPlugin for you! This generates a html + * page for you when you compile webpack, which will make you start + * developing and prototyping faster. + * + * https://github.com/jantimon/html-webpack-plugin + * */`; } }; diff --git a/packages/generators/utils/webpackConfig.ts b/packages/generators/utils/webpackConfig.ts new file mode 100644 index 00000000000..16ef191970c --- /dev/null +++ b/packages/generators/utils/webpackConfig.ts @@ -0,0 +1,29 @@ +import { WebpackOptions } from "../types"; + +export function getDefaultOptimization(isProd: boolean): WebpackOptions["optimization"] { + let optimizationOptions; + if (isProd) { + optimizationOptions = { + minimizer: ["new TerserPlugin()"], + splitChunks: { + chunks: "'all'" + } + }; + } else { + optimizationOptions = { + splitChunks: { + cacheGroups: { + vendors: { + priority: -10, + test: "/[\\\\/]node_modules[\\\\/]/" + } + }, + chunks: "'async'", + minChunks: 1, + minSize: 30000, + name: !this.isProd + } + }; + } + return optimizationOptions; +} diff --git a/packages/init/init.ts b/packages/init/init.ts index b4063cd15e9..2cd0bef21e3 100644 --- a/packages/init/init.ts +++ b/packages/init/init.ts @@ -81,7 +81,7 @@ export default function runTransform(webpackProperties: WebpackProperties, actio } ); - let successMessage: string = `Congratulations! Your new webpack configuration file has been created!`; + let successMessage = `Congratulations! Your new webpack configuration file has been created!`; if (initActionNotDefined && webpackProperties.config.item) { successMessage = `Congratulations! ${webpackProperties.config.item} has been ${action}ed!`; } diff --git a/packages/migrate/__tests__/migrate.test.ts b/packages/migrate/__tests__/migrate.test.ts index 4369f0f86b7..f367e19ee34 100644 --- a/packages/migrate/__tests__/migrate.test.ts +++ b/packages/migrate/__tests__/migrate.test.ts @@ -30,34 +30,34 @@ module.exports = { `; describe("transform", () => { - it("should not transform if no transformations defined", (done) => { - transform(input, []).then((output) => { - expect(output).toMatchSnapshot(input); - done(); + it("should not transform if no transformations defined", done => { + transform(input, []).then(output => { + expect(output).toMatchSnapshot(input); + done(); + }); }); - }); - it("should transform using all transformations", (done) => { - transform(input).then((output) => { - expect(output).toMatchSnapshot(); - done(); + it("should transform using all transformations", done => { + transform(input).then(output => { + expect(output).toMatchSnapshot(); + done(); + }); }); - }); - it("should transform only using specified transformations", (done) => { - transform(input, [transformations.loadersTransform]).then((output) => { - expect(output).toMatchSnapshot(); - done(); + it("should transform only using specified transformations", done => { + transform(input, [transformations.loadersTransform]).then(output => { + expect(output).toMatchSnapshot(); + done(); + }); }); - }); - it("should respect recast options", (done) => { - transform(input, undefined, { - quote: "double", - trailingComma: true, - }).then((output) => { - expect(output).toMatchSnapshot(); - done(); + it("should respect recast options", done => { + transform(input, undefined, { + quote: "double", + trailingComma: true + }).then(output => { + expect(output).toMatchSnapshot(); + done(); + }); }); - }); }); diff --git a/packages/migrate/commonsChunkPlugin/__tests__/commonsChunkPlugin.test.ts b/packages/migrate/commonsChunkPlugin/__tests__/commonsChunkPlugin.test.ts index 5c2e28e1f40..80ec6de5f7e 100644 --- a/packages/migrate/commonsChunkPlugin/__tests__/commonsChunkPlugin.test.ts +++ b/packages/migrate/commonsChunkPlugin/__tests__/commonsChunkPlugin.test.ts @@ -9,24 +9,8 @@ defineTest(dirName, "commonsChunkPlugin", "commonsChunkPlugin-2"); defineTest(dirName, "commonsChunkPlugin", "commonsChunkPlugin-3"); defineTest(dirName, "commonsChunkPlugin", "commonsChunkPlugin-4"); defineTest(dirName, "commonsChunkPlugin", "commonsChunkPlugin-5"); -defineTest( - dirName, - "commonsChunkPlugin", - "commonsChunkPlugin-6a", -); -defineTest( - dirName, - "commonsChunkPlugin", - "commonsChunkPlugin-6b", -); -defineTest( - dirName, - "commonsChunkPlugin", - "commonsChunkPlugin-6c", -); -defineTest( - dirName, - "commonsChunkPlugin", - "commonsChunkPlugin-6d", -); +defineTest(dirName, "commonsChunkPlugin", "commonsChunkPlugin-6a"); +defineTest(dirName, "commonsChunkPlugin", "commonsChunkPlugin-6b"); +defineTest(dirName, "commonsChunkPlugin", "commonsChunkPlugin-6c"); +defineTest(dirName, "commonsChunkPlugin", "commonsChunkPlugin-6d"); defineTest(dirName, "commonsChunkPlugin", "commonsChunkPlugin-7"); diff --git a/packages/migrate/extractTextPlugin/extractTextPlugin.ts b/packages/migrate/extractTextPlugin/extractTextPlugin.ts index fcc3931a394..565ff11c3ce 100644 --- a/packages/migrate/extractTextPlugin/extractTextPlugin.ts +++ b/packages/migrate/extractTextPlugin/extractTextPlugin.ts @@ -39,7 +39,8 @@ export default function(j: JSCodeshift, ast: Node): void | Node { if (literalArgs && literalArgs.length > 1) { const newArgs: object = j.objectExpression( literalArgs.map( - (p: Node, index: number): Node => utils.createProperty(j, index === 0 ? "fallback" : "use", p.value as Node) + (p: Node, index: number): Node => + utils.createProperty(j, index === 0 ? "fallback" : "use", p.value as Node) ) ); (path.value as Node).arguments = [newArgs]; diff --git a/packages/migrate/loaderOptionsPlugin/__tests__/loaderOptionsPlugin.test.ts b/packages/migrate/loaderOptionsPlugin/__tests__/loaderOptionsPlugin.test.ts index 0126a11b8cd..d827273f340 100644 --- a/packages/migrate/loaderOptionsPlugin/__tests__/loaderOptionsPlugin.test.ts +++ b/packages/migrate/loaderOptionsPlugin/__tests__/loaderOptionsPlugin.test.ts @@ -3,23 +3,7 @@ import { join } from "path"; const dirName: string = join(__dirname, ".."); -defineTest( - dirName, - "loaderOptionsPlugin", - "loaderOptionsPlugin-0", -); -defineTest( - dirName, - "loaderOptionsPlugin", - "loaderOptionsPlugin-1", -); -defineTest( - dirName, - "loaderOptionsPlugin", - "loaderOptionsPlugin-2", -); -defineTest( - dirName, - "loaderOptionsPlugin", - "loaderOptionsPlugin-3", -); +defineTest(dirName, "loaderOptionsPlugin", "loaderOptionsPlugin-0"); +defineTest(dirName, "loaderOptionsPlugin", "loaderOptionsPlugin-1"); +defineTest(dirName, "loaderOptionsPlugin", "loaderOptionsPlugin-2"); +defineTest(dirName, "loaderOptionsPlugin", "loaderOptionsPlugin-3"); diff --git a/packages/migrate/loaders/loaders.ts b/packages/migrate/loaders/loaders.ts index 0f5452c2ac1..54f75dac271 100644 --- a/packages/migrate/loaders/loaders.ts +++ b/packages/migrate/loaders/loaders.ts @@ -55,7 +55,7 @@ export default function(j: JSCodeshift, ast: Node): Node { */ const createArrayExpressionFromArray = (path: Node): Node => { - const value: Node = (path.value as Node); + const value: Node = path.value as Node; // Find paths with `loaders` keys in the given Object const paths: Node[] = value.properties.filter((prop: Node): boolean => prop.key.name.startsWith("loader")); // For each pair of key and value @@ -73,7 +73,9 @@ export default function(j: JSCodeshift, ast: Node): Node { // If items of the array are Strings if (arrElement.type === j.Literal.name) { // Replace with `{ loader: LOADER }` Object - return j.objectExpression([utils.createProperty(j, "loader", (arrElement.value as Node))]); + return j.objectExpression([ + utils.createProperty(j, "loader", arrElement.value as Node) + ]); } // otherwise keep the existing element return arrElement; @@ -108,7 +110,8 @@ export default function(j: JSCodeshift, ast: Node): Node { const createLoaderWithQuery = (p: Node): Node => { const properties: Node[] = (p.value as Node).properties; const loaderValue: string = properties.reduce( - (val: string, prop: Node): string => (prop.key.name === "loader" ? (prop.value as Node).value as string: val), + (val: string, prop: Node): string => + prop.key.name === "loader" ? ((prop.value as Node).value as string) : val, "" ); const loader: string = loaderValue.split("?")[0]; @@ -137,7 +140,8 @@ export default function(j: JSCodeshift, ast: Node): Node { const findLoaderWithQueryString = (p: Node): boolean => { return (p.value as Node).properties.reduce((predicate: boolean, prop: Node): boolean => { return ( - (utils.safeTraverse(prop, ["value", "value", "indexOf"]) && ((prop.value as Node).value as string).indexOf("?") > -1) || + (utils.safeTraverse(prop, ["value", "value", "indexOf"]) && + ((prop.value as Node).value as string).indexOf("?") > -1) || predicate ); }, false); @@ -192,7 +196,9 @@ export default function(j: JSCodeshift, ast: Node): Node { } ); if (loaders) { - (p.value as Node).properties = (p.value as Node).properties.filter((prop: Node): boolean => prop.key.name === "loaders"); + (p.value as Node).properties = (p.value as Node).properties.filter( + (prop: Node): boolean => prop.key.name === "loaders" + ); } return p; }; @@ -306,7 +312,7 @@ export default function(j: JSCodeshift, ast: Node): Node { utils.safeTraverse(prop, ["value", "value"]) && !((prop.value as Node).value as string).endsWith("-loader") ) { - prop.value = j.literal((prop.value as Node).value as string + "-loader"); + prop.value = j.literal(((prop.value as Node).value as string) + "-loader"); } } ); @@ -333,7 +339,9 @@ export default function(j: JSCodeshift, ast: Node): Node { ); if (options) { - (p.value as Node).properties = (p.value as Node).properties.filter((prop: Node): boolean => prop.key.name !== "options"); + (p.value as Node).properties = (p.value as Node).properties.filter( + (prop: Node): boolean => prop.key.name !== "options" + ); (p.value as Node).properties.forEach( (prop: Node): void => { diff --git a/packages/migrate/moduleConcatenationPlugin/__tests__/moduleConcatenationPlugin.test.ts b/packages/migrate/moduleConcatenationPlugin/__tests__/moduleConcatenationPlugin.test.ts index 840dee420a2..69fcf1c07a0 100644 --- a/packages/migrate/moduleConcatenationPlugin/__tests__/moduleConcatenationPlugin.test.ts +++ b/packages/migrate/moduleConcatenationPlugin/__tests__/moduleConcatenationPlugin.test.ts @@ -3,18 +3,6 @@ import { join } from "path"; const dirName: string = join(__dirname, ".."); -defineTest( - dirName, - "moduleConcatenationPlugin", - "moduleConcatenationPlugin-0", -); -defineTest( - dirName, - "moduleConcatenationPlugin", - "moduleConcatenationPlugin-1", -); -defineTest( - dirName, - "moduleConcatenationPlugin", - "moduleConcatenationPlugin-2", -); +defineTest(dirName, "moduleConcatenationPlugin", "moduleConcatenationPlugin-0"); +defineTest(dirName, "moduleConcatenationPlugin", "moduleConcatenationPlugin-1"); +defineTest(dirName, "moduleConcatenationPlugin", "moduleConcatenationPlugin-2"); diff --git a/packages/migrate/noEmitOnErrorsPlugin/__tests__/noEmitOnErrorsPlugin.test.ts b/packages/migrate/noEmitOnErrorsPlugin/__tests__/noEmitOnErrorsPlugin.test.ts index 760a5a1aa75..a1e43ea1021 100644 --- a/packages/migrate/noEmitOnErrorsPlugin/__tests__/noEmitOnErrorsPlugin.test.ts +++ b/packages/migrate/noEmitOnErrorsPlugin/__tests__/noEmitOnErrorsPlugin.test.ts @@ -3,18 +3,6 @@ import { join } from "path"; const dirName: string = join(__dirname, ".."); -defineTest( - dirName, - "noEmitOnErrorsPlugin", - "noEmitOnErrorsPlugin-0", -); -defineTest( - dirName, - "noEmitOnErrorsPlugin", - "noEmitOnErrorsPlugin-1", -); -defineTest( - dirName, - "noEmitOnErrorsPlugin", - "noEmitOnErrorsPlugin-2", -); +defineTest(dirName, "noEmitOnErrorsPlugin", "noEmitOnErrorsPlugin-0"); +defineTest(dirName, "noEmitOnErrorsPlugin", "noEmitOnErrorsPlugin-1"); +defineTest(dirName, "noEmitOnErrorsPlugin", "noEmitOnErrorsPlugin-2"); diff --git a/packages/migrate/outputPath/outputPath.ts b/packages/migrate/outputPath/outputPath.ts index e3d1c0bd087..90ea98f5ff6 100644 --- a/packages/migrate/outputPath/outputPath.ts +++ b/packages/migrate/outputPath/outputPath.ts @@ -2,18 +2,13 @@ import * as utils from "@webpack-cli/utils/ast-utils"; import { JSCodeshift, Node } from "../types/NodePath"; +function replaceWithPath(j: JSCodeshift, p: Node, pathVarName: string): Node { + const convertedPath: Node = j.callExpression( + j.memberExpression(j.identifier(pathVarName), j.identifier("join"), false), + [j.identifier("__dirname"), p.value as Node] + ); -function replaceWithPath( - j: JSCodeshift, - p: Node, - pathVarName: string, -): Node { - const convertedPath: Node = j.callExpression( - j.memberExpression(j.identifier(pathVarName), j.identifier("join"), false), - [j.identifier("__dirname"), p.value as Node], - ); - - return convertedPath; + return convertedPath; } /** @@ -25,67 +20,50 @@ function replaceWithPath( * @returns {Node} ast - jscodeshift ast */ export default function(j: JSCodeshift, ast: Node): Node | void { - const literalOutputPath: Node = ast - .find(j.ObjectExpression) - .filter( - (p: Node): boolean => - utils.safeTraverse(p, ["parentPath", "value", "key", "name"]) === - "output", - ) - .find(j.Property) - .filter( - (p: Node): boolean => - utils.safeTraverse(p, ["value", "key", "name"]) === "path" && - utils.safeTraverse(p, ["value", "value", "type"]) === "Literal", - ); - - if (literalOutputPath) { - let pathVarName = "path"; - let isPathPresent = false; - const pathDeclaration: Node = ast - .find(j.VariableDeclarator) - .filter( - (p: Node): boolean => - utils.safeTraverse(p, ["value", "init", "callee", "name"]) === - "require", - ) + const literalOutputPath: Node = ast + .find(j.ObjectExpression) + .filter((p: Node): boolean => utils.safeTraverse(p, ["parentPath", "value", "key", "name"]) === "output") + .find(j.Property) .filter( - (p: Node): boolean => - utils.safeTraverse(p, ["value", "init", "arguments"]) && - // TODO: to fix when we have proper typing (@types/jscodeshift) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (p.value as any).init.arguments.reduce( - (isPresent: boolean, a: Node): boolean => { - return (a.type === "Literal" && a.value === "path") || isPresent; - }, - false, - ), + (p: Node): boolean => + utils.safeTraverse(p, ["value", "key", "name"]) === "path" && + utils.safeTraverse(p, ["value", "value", "type"]) === "Literal" ); - if (pathDeclaration) { - isPathPresent = true; - pathDeclaration.forEach( - (p: Node): void => { - pathVarName = utils.safeTraverse(p, ["value", "id", "name"]) as string; - }, - ); - } - const finalPathName = pathVarName; - literalOutputPath - .find(j.Literal) - .replaceWith((p: Node): Node => replaceWithPath(j, p, finalPathName)); + if (literalOutputPath) { + let pathVarName = "path"; + let isPathPresent = false; + const pathDeclaration: Node = ast + .find(j.VariableDeclarator) + .filter((p: Node): boolean => utils.safeTraverse(p, ["value", "init", "callee", "name"]) === "require") + .filter( + (p: Node): boolean => + utils.safeTraverse(p, ["value", "init", "arguments"]) && + // TODO: to fix when we have proper typing (@types/jscodeshift) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (p.value as any).init.arguments.reduce((isPresent: boolean, a: Node): boolean => { + return (a.type === "Literal" && a.value === "path") || isPresent; + }, false) + ); - if (!isPathPresent) { - const pathRequire: Node = utils.getRequire(j, "path", "path"); - return ast - .find(j.Program) - .replaceWith( - (p: Node): Node => - j.program([].concat(pathRequire).concat((p.value as Node).body)), - ); + if (pathDeclaration) { + isPathPresent = true; + pathDeclaration.forEach( + (p: Node): void => { + pathVarName = utils.safeTraverse(p, ["value", "id", "name"]) as string; + } + ); + } + const finalPathName = pathVarName; + literalOutputPath.find(j.Literal).replaceWith((p: Node): Node => replaceWithPath(j, p, finalPathName)); + + if (!isPathPresent) { + const pathRequire: Node = utils.getRequire(j, "path", "path"); + return ast + .find(j.Program) + .replaceWith((p: Node): Node => j.program([].concat(pathRequire).concat((p.value as Node).body))); + } } - } - return ast; + return ast; } - diff --git a/packages/migrate/removeDeprecatedPlugins/__tests__/removeDeprecatedPlugins.test.ts b/packages/migrate/removeDeprecatedPlugins/__tests__/removeDeprecatedPlugins.test.ts index 922ca71424c..2e4285f1b77 100644 --- a/packages/migrate/removeDeprecatedPlugins/__tests__/removeDeprecatedPlugins.test.ts +++ b/packages/migrate/removeDeprecatedPlugins/__tests__/removeDeprecatedPlugins.test.ts @@ -3,28 +3,8 @@ import { join } from "path"; const dirName: string = join(__dirname, ".."); -defineTest( - dirName, - "removeDeprecatedPlugins", - "removeDeprecatedPlugins-0", -); -defineTest( - dirName, - "removeDeprecatedPlugins", - "removeDeprecatedPlugins-1", -); -defineTest( - dirName, - "removeDeprecatedPlugins", - "removeDeprecatedPlugins-2", -); -defineTest( - dirName, - "removeDeprecatedPlugins", - "removeDeprecatedPlugins-3", -); -defineTest( - dirName, - "removeDeprecatedPlugins", - "removeDeprecatedPlugins-4", -); +defineTest(dirName, "removeDeprecatedPlugins", "removeDeprecatedPlugins-0"); +defineTest(dirName, "removeDeprecatedPlugins", "removeDeprecatedPlugins-1"); +defineTest(dirName, "removeDeprecatedPlugins", "removeDeprecatedPlugins-2"); +defineTest(dirName, "removeDeprecatedPlugins", "removeDeprecatedPlugins-3"); +defineTest(dirName, "removeDeprecatedPlugins", "removeDeprecatedPlugins-4"); diff --git a/packages/migrate/removeJsonLoader/__tests__/removeJsonLoader.test.ts b/packages/migrate/removeJsonLoader/__tests__/removeJsonLoader.test.ts index 6d705023056..33274324889 100644 --- a/packages/migrate/removeJsonLoader/__tests__/removeJsonLoader.test.ts +++ b/packages/migrate/removeJsonLoader/__tests__/removeJsonLoader.test.ts @@ -3,23 +3,7 @@ import { join } from "path"; const dirName: string = join(__dirname, ".."); -defineTest( - dirName, - "removeJsonLoader", - "removeJsonLoader-0", -); -defineTest( - dirName, - "removeJsonLoader", - "removeJsonLoader-1", -); -defineTest( - dirName, - "removeJsonLoader", - "removeJsonLoader-2", -); -defineTest( - dirName, - "removeJsonLoader", - "removeJsonLoader-3", -); +defineTest(dirName, "removeJsonLoader", "removeJsonLoader-0"); +defineTest(dirName, "removeJsonLoader", "removeJsonLoader-1"); +defineTest(dirName, "removeJsonLoader", "removeJsonLoader-2"); +defineTest(dirName, "removeJsonLoader", "removeJsonLoader-3"); diff --git a/packages/migrate/resolve/resolve.ts b/packages/migrate/resolve/resolve.ts index be60e4e69b0..a9022039b1e 100644 --- a/packages/migrate/resolve/resolve.ts +++ b/packages/migrate/resolve/resolve.ts @@ -21,7 +21,9 @@ export default function transformer(j: JSCodeshift, ast: Node): Node { }; const isModulePresent = (p: Node): Node | false => { - const modules: Node[] = (p.node.value as Node).properties.filter((prop: Node): boolean => prop.key.name === "modules"); + const modules: Node[] = (p.node.value as Node).properties.filter( + (prop: Node): boolean => prop.key.name === "modules" + ); return modules.length > 0 && modules[0]; }; @@ -68,7 +70,8 @@ export default function transformer(j: JSCodeshift, ast: Node): Node { (p: Node): boolean => { return ( p.node.key.name === "resolve" && - (p.node.value as Node).properties.filter((prop: Node): boolean => prop.key.name === "root").length === 1 + (p.node.value as Node).properties.filter((prop: Node): boolean => prop.key.name === "root") + .length === 1 ); } ) diff --git a/packages/migrate/types/NodePath.ts b/packages/migrate/types/NodePath.ts index 1b828378ddb..66098c06894 100644 --- a/packages/migrate/types/NodePath.ts +++ b/packages/migrate/types/NodePath.ts @@ -39,11 +39,7 @@ export interface Node extends Object { size?: () => number; type?: string; value?: Node | string | Node[]; - toSource?: ( - object: { - quote?: string; - } - ) => string; + toSource?: (object: { quote?: string }) => string; source?: string; ast?: Node; rules?: ModuleRule[]; diff --git a/packages/migrate/uglifyJsPlugin/uglifyJsPlugin.ts b/packages/migrate/uglifyJsPlugin/uglifyJsPlugin.ts index 71980715a60..172d6f2f1d7 100644 --- a/packages/migrate/uglifyJsPlugin/uglifyJsPlugin.ts +++ b/packages/migrate/uglifyJsPlugin/uglifyJsPlugin.ts @@ -81,7 +81,11 @@ export default function(j: JSCodeshift, ast: Node): Node { j.arrayExpression([j.newExpression(j.identifier("TerserPlugin"), [pluginOptions[0]])]) ); } else { - expressionContent = j.property("init", j.identifier("minimizer"), j.arrayExpression([node.value as Node])); + expressionContent = j.property( + "init", + j.identifier("minimizer"), + j.arrayExpression([node.value as Node]) + ); } } else { searchForRequirePlugin.forEach((n: Node): void => j(n).remove()); diff --git a/packages/utils/__tests__/is-local-path.test.ts b/packages/utils/__tests__/is-local-path.test.ts index ca34c0e7b66..654884b9e1a 100644 --- a/packages/utils/__tests__/is-local-path.test.ts +++ b/packages/utils/__tests__/is-local-path.test.ts @@ -1,7 +1,7 @@ "use strict"; import * as path from "path"; -import isLocalPath from "../is-local-path"; +import { isLocalPath } from "../path-utils"; describe("is-local-path", () => { it("returns true for paths beginning in the current directory", () => { diff --git a/packages/utils/__tests__/npm-exists.test.ts b/packages/utils/__tests__/npm-exists.test.ts index 2b773259af1..a5a98f168f0 100644 --- a/packages/utils/__tests__/npm-exists.test.ts +++ b/packages/utils/__tests__/npm-exists.test.ts @@ -3,13 +3,13 @@ import exists from "../npm-exists"; describe("npm-exists", () => { it("should successfully existence of a published module", () => { - exists("webpack-scaffold-demo").then((status) => { + exists("webpack-scaffold-demo").then(status => { expect(status).toBe(true); }); }); it("should return false for the existence of a fake module", () => { - exists("webpack-scaffold-noop").then((status) => { + exists("webpack-scaffold-noop").then(status => { expect(status).toBe(false); }); }); diff --git a/packages/utils/__tests__/npm-packages-exists.test.ts b/packages/utils/__tests__/npm-packages-exists.test.ts index 151eb82f863..4c75f72699d 100644 --- a/packages/utils/__tests__/npm-packages-exists.test.ts +++ b/packages/utils/__tests__/npm-packages-exists.test.ts @@ -1,5 +1,5 @@ import npmPackagesExists from "../npm-packages-exists"; -import {resolvePackages} from "../resolve-packages"; +import { resolvePackages } from "../resolve-packages"; jest.mock("../npm-exists"); jest.mock("../resolve-packages"); diff --git a/packages/utils/__tests__/package-manager.test.ts b/packages/utils/__tests__/package-manager.test.ts index baa4204822a..c21a0e45aa8 100644 --- a/packages/utils/__tests__/package-manager.test.ts +++ b/packages/utils/__tests__/package-manager.test.ts @@ -19,15 +19,15 @@ describe("package-manager", () => { signal: null, status: 1, stderr: null, - stdout: null, + stdout: null }; function mockSpawnErrorOnce() { spawn.sync.mockReturnValueOnce( Object.assign({}, defaultSyncResult, { error: new Error(), - status: null, - }), + status: null + }) ); } @@ -103,8 +103,8 @@ describe("package-manager", () => { // Mock stdout of `yarn global dir` spawn.sync.mockReturnValueOnce({ stdout: { - toString: () => `${yarnDir}\n`, - }, + toString: () => `${yarnDir}\n` + } }); const globalPath = packageManager.getPathToGlobalPackages(); const expected = path.join(yarnDir, "node_modules"); diff --git a/packages/utils/__tests__/recursive-parser.test.ts b/packages/utils/__tests__/recursive-parser.test.ts index 87778124f13..20de356e54e 100644 --- a/packages/utils/__tests__/recursive-parser.test.ts +++ b/packages/utils/__tests__/recursive-parser.test.ts @@ -15,11 +15,11 @@ defineTest(join(__dirname, ".."), "init", "fixture-1", "entry", { include: ["customObj", "'Stringy'"], loader: "'eslint-loader'", options: { - formatter: "'someOption'", + formatter: "'someOption'" }, - test: new RegExp(/\.(js|vue)$/), - }, - ], + test: new RegExp(/\.(js|vue)$/) + } + ] }); defineTest(join(__dirname, ".."), "add", "fixture-2", "entry", { @@ -35,31 +35,31 @@ defineTest(join(__dirname, ".."), "add", "fixture-2", "entry", { include: ["asd", "'Stringy'"], loader: "'pia-loader'", options: { - formatter: "'nao'", + formatter: "'nao'" }, - test: new RegExp(/\.(wasm|c)$/), - }, - ], + test: new RegExp(/\.(wasm|c)$/) + } + ] }); defineTest(join(__dirname, ".."), "remove", "fixture-3", "resolve", { - alias: null, + alias: null }); defineTest(join(__dirname, ".."), "remove", "fixture-3", "plugins", ["plugin2"]); defineTest(join(__dirname, ".."), "remove", "fixture-3", "module", { - noParse: null, + noParse: null }); defineTest(join(__dirname, ".."), "remove", "fixture-3", "entry", { - a: null, + a: null }); defineTest(join(__dirname, ".."), "remove", "fixture-3", "module", { rules: [ { - loader: "eslint-loader", - }, - ], + loader: "eslint-loader" + } + ] }); diff --git a/packages/utils/ast-utils.ts b/packages/utils/ast-utils.ts index 7a6fed5a657..5ff459b10b1 100644 --- a/packages/utils/ast-utils.ts +++ b/packages/utils/ast-utils.ts @@ -570,7 +570,7 @@ function removeProperty(j: JSCodeshift, ast: Node, key: string, value: valueType * @returns ast - jscodeshift API */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars function parseTopScope(j: JSCodeshift, ast: Node, value: string[], action: string): boolean | Node { function createTopScopeProperty(p: Node): boolean { value.forEach( diff --git a/packages/utils/find-root.ts b/packages/utils/find-root.ts deleted file mode 100644 index 0345da2db62..00000000000 --- a/packages/utils/find-root.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as findup from "findup-sync"; -import * as path from "path"; - -/** - * Returns the absolute path of the project directory - * Finds the package.json, by using findup-sync - * @returns {String} path of project directory - */ - -export function findProjectRoot(): string { - const rootFilePath = findup(`package.json`); - const projectRoot = path.dirname(rootFilePath); - return projectRoot; -} diff --git a/packages/utils/modify-config-helper.ts b/packages/utils/modify-config-helper.ts index 5d25385c8c0..d27b37bd608 100644 --- a/packages/utils/modify-config-helper.ts +++ b/packages/utils/modify-config-helper.ts @@ -5,7 +5,6 @@ import * as path from "path"; import * as yeoman from "yeoman-environment"; import * as Generator from "yeoman-generator"; - import runTransform from "./scaffold"; export interface Config extends Object { @@ -87,11 +86,12 @@ export default function modifyHelperUtil( } }; } - + env.registerStub(generator, generatorName); env.run(generatorName, { configFile - }).then( + }) + .then( (): void => { let configModule: object; try { diff --git a/packages/utils/npm-packages-exists.ts b/packages/utils/npm-packages-exists.ts index 529da51ad2f..7c0d441589e 100644 --- a/packages/utils/npm-packages-exists.ts +++ b/packages/utils/npm-packages-exists.ts @@ -1,7 +1,7 @@ import chalk from "chalk"; -import isLocalPath from "./is-local-path"; import npmExists from "./npm-exists"; +import { isLocalPath } from "./path-utils"; import { resolvePackages } from "./resolve-packages"; const WEBPACK_SCAFFOLD_PREFIX = "webpack-scaffold"; diff --git a/packages/utils/package-lock.json b/packages/utils/package-lock.json index 30e688d29c6..efe1c450fdd 100644 --- a/packages/utils/package-lock.json +++ b/packages/utils/package-lock.json @@ -121,9 +121,9 @@ "dev": true }, "@types/prettier": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.15.2.tgz", - "integrity": "sha512-XIB0ZCaFZmWUHAa9dBqP5UKXXHwuukmVlP+XcyU94dui2k+l2lG+CHAbt2ffenHPUqoIs5Beh8Pdf2YEq/CZ7A==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.16.1.tgz", + "integrity": "sha512-db6pZL5QY3JrlCHBhYQzYDci0xnoDuxfseUuguLRr3JNk+bnCfpkK6p8quiUDyO8A0vbpBKkk59Fw125etrNeA==", "dev": true }, "@types/through": { @@ -5665,9 +5665,9 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" }, "prettier": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.15.3.tgz", - "integrity": "sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg==" + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.0.tgz", + "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==" }, "pretty-bytes": { "version": "5.1.0", diff --git a/packages/utils/package-manager.ts b/packages/utils/package-manager.ts index 888b974ef18..87bf62e59bb 100644 --- a/packages/utils/package-manager.ts +++ b/packages/utils/package-manager.ts @@ -102,7 +102,7 @@ export function spawnChild(pkg: string): SpawnSyncReturns { const rootPath: string = getPathToGlobalPackages(); const pkgPath: string = path.resolve(rootPath, pkg); const packageManager: string = getPackageManager(); - const isNew: boolean = !fs.existsSync(pkgPath); + const isNew = !fs.existsSync(pkgPath); return SPAWN_FUNCTIONS[packageManager](pkg, isNew); } diff --git a/packages/utils/package.json b/packages/utils/package.json index 3a243bbb1ad..f234ab8f8d1 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -19,7 +19,7 @@ "jscodeshift": "^0.5.1", "log-symbols": "^2.2.0", "p-each-series": "^1.0.0", - "prettier": "^1.15.2", + "prettier": "^1.17.0", "yeoman-environment": "^2.3.4", "yeoman-generator": "^3.1.1" }, @@ -31,7 +31,7 @@ "@types/log-symbols": "^2.0.0", "@types/node": "^10.12.9", "@types/p-each-series": "^1.0.0", - "@types/prettier": "^1.15.0", + "@types/prettier": "^1.16.1", "@types/yeoman-generator": "^3.1.2", "typescript": "^3.1.6" }, diff --git a/packages/utils/is-local-path.ts b/packages/utils/path-utils.ts similarity index 63% rename from packages/utils/is-local-path.ts rename to packages/utils/path-utils.ts index 0efb8c6fc2c..29def6d2257 100644 --- a/packages/utils/is-local-path.ts +++ b/packages/utils/path-utils.ts @@ -1,3 +1,4 @@ +import * as findup from "findup-sync"; import * as fs from "fs"; import * as path from "path"; @@ -12,6 +13,18 @@ import * as path from "path"; * @returns {Boolean} whether the string could be a path to a local file or directory */ -export default function(str: string): boolean { +export function isLocalPath(str: string): boolean { return path.isAbsolute(str) || /^\./.test(str) || fs.existsSync(str); } + +/** + * Find the root directory path of a project. + * + * @returns {String} Absolute path of the project root. + */ + +export function findProjectRoot(): string { + const rootFilePath = findup("package.json"); + const projectRoot = path.dirname(rootFilePath); + return projectRoot; +} diff --git a/packages/utils/prop-types.ts b/packages/utils/prop-types.ts index 894ec0e3c47..cccf77a1079 100644 --- a/packages/utils/prop-types.ts +++ b/packages/utils/prop-types.ts @@ -34,7 +34,7 @@ const PROP_TYPES: Set = new Set([ "target", "topScope", "watch", - "watchOptions", + "watchOptions" ]); export default PROP_TYPES; diff --git a/packages/utils/resolve-packages.ts b/packages/utils/resolve-packages.ts index c5dabe60474..9f7b2918f33 100644 --- a/packages/utils/resolve-packages.ts +++ b/packages/utils/resolve-packages.ts @@ -1,10 +1,10 @@ import chalk from "chalk"; import * as path from "path"; -import isLocalPath from "./is-local-path"; import modifyConfigHelper from "./modify-config-helper"; import { getPathToGlobalPackages } from "./package-manager"; import { spawnChild } from "./package-manager"; +import { isLocalPath } from "./path-utils"; interface ChildProcess { status: number; diff --git a/packages/utils/run-prettier.ts b/packages/utils/run-prettier.ts index 794f86923b8..5f0537f82ef 100644 --- a/packages/utils/run-prettier.ts +++ b/packages/utils/run-prettier.ts @@ -19,18 +19,17 @@ export default function runPrettier(outputPath: string, source: string, cb?: Fun try { prettySource = prettier.format(source, { filepath: outputPath, - parser: "babylon", + parser: "babel", singleQuote: true, tabWidth: 1, - useTabs: true, + useTabs: true }); } catch (err) { process.stdout.write( - "\n" + - chalk.yellow( - `WARNING: Could not apply prettier to ${outputPath}` + - " due validation error, but the file has been created\n", - ), + `\n${chalk.yellow( + `WARNING: Could not apply prettier to ${outputPath}` + + " due validation error, but the file has been created\n" + )}` ); prettySource = source; error = err; diff --git a/packages/utils/scaffold.ts b/packages/utils/scaffold.ts index 2d2ece8a390..f842d3a777e 100644 --- a/packages/utils/scaffold.ts +++ b/packages/utils/scaffold.ts @@ -2,7 +2,7 @@ import chalk from "chalk"; import * as j from "jscodeshift"; import pEachSeries = require("p-each-series"); import * as path from "path"; -import { findProjectRoot } from "./find-root"; +import { findProjectRoot } from "./path-utils"; import { Error } from "../init/types"; import { Config, TransformConfig } from "./modify-config-helper"; @@ -80,7 +80,6 @@ export default function runTransform(transformConfig: TransformConfig, action: s } else { configurationName = "webpack." + config.configName + ".js"; } - const projectRoot = findProjectRoot(); const outputPath: string = initActionNotDefined ? transformConfig.configPath @@ -98,9 +97,12 @@ export default function runTransform(transformConfig: TransformConfig, action: s ); } ); - let successMessage: string = `Congratulations! Your new webpack configuration file has been created!\n`; + let successMessage: string = + chalk.green(`Congratulations! Your new webpack configuration file has been created!\n\n`) + + `You can now run ${chalk.green("npm run start")} to run your project!\n\n`; + if (initActionNotDefined && transformConfig.config.item) { - successMessage = `Congratulations! ${transformConfig.config.item} has been ${action}ed!\n`; + successMessage = chalk.green(`Congratulations! ${transformConfig.config.item} has been ${action}ed!\n`); } - process.stdout.write("\n" + chalk.green(successMessage)); + process.stdout.write(`\n${successMessage}`); } diff --git a/packages/utils/types/NodePath.ts b/packages/utils/types/NodePath.ts index 1b828378ddb..66098c06894 100644 --- a/packages/utils/types/NodePath.ts +++ b/packages/utils/types/NodePath.ts @@ -39,11 +39,7 @@ export interface Node extends Object { size?: () => number; type?: string; value?: Node | string | Node[]; - toSource?: ( - object: { - quote?: string; - } - ) => string; + toSource?: (object: { quote?: string }) => string; source?: string; ast?: Node; rules?: ModuleRule[]; diff --git a/packages/webpack-scaffold/README.md b/packages/webpack-scaffold/README.md index a0a92441b40..413c223a51a 100755 --- a/packages/webpack-scaffold/README.md +++ b/packages/webpack-scaffold/README.md @@ -12,14 +12,23 @@ npm i -D webpack-cli @webpack-cli/webpack-scaffold # API -- [parseValue](#parsevalue) -- [createArrowFunction](#createarrowfunction) -- [createRegularFunction](#createregularfunction) -- [createDynamicPromise](#createdynamicpromise) -- [createAssetFilterFunction](#createassetfilterfunction) -- [createExternalFunction](#createexternalfunction) -- [createRequire](#createrequire) -- [Inquirer](#inquirer) - [List](#list) - [RawList](#rawlist) - [CheckList](#checklist) - [Input](#input) - [InputValidate](#inputvalidate) - [Confirm](#confirm) +- [webpack-scaffold](#webpack-scaffold) +- [Installation](#installation) +- [API](#api) + - [parseValue](#parsevalue) + - [createArrowFunction](#createarrowfunction) + - [createRegularFunction](#createregularfunction) + - [createDynamicPromise](#createdynamicpromise) + - [createAssetFilterFunction](#createassetfilterfunction) + - [createExternalFunction](#createexternalfunction) + - [createRequire](#createrequire) + - [Inquirer](#inquirer) + - [List](#list) + - [RawList](#rawlist) + - [CheckList](#checklist) + - [Input](#input) + - [InputValidate](#inputvalidate) + - [Confirm](#confirm) ## parseValue @@ -163,19 +172,19 @@ CheckList("entry", "what kind of entry do you want?", ["Array", "Function"]); ### Input -Param: `name, message` +Param: `name, message, [default]` Creates an Input from Inquirer ```js const Input = require("@webpack-cli/webpack-scaffold").Input; -Input("entry", "what is your entry point?"); +Input("entry", "what is your entry point?", "src/index"); ``` ### InputValidate -Param: `name, message, validate` +Param: `name, message, [validate, default]` Creates an Input from Inquirer @@ -186,15 +195,15 @@ const validation = value => { if (value.length > 4) { return true; } else { - return "Wow, that was short!"; + return "Your answer must be longer than 4 characters, try again"; } }; -InputValidate("entry", "what is your entry point?", validation); +InputValidate("entry", "what is your entry point?", validation, "src/index"); ``` ### Confirm -Param: `name, message, default` +Param: `name, message, [default]` Creates an Input from Inquirer diff --git a/packages/webpack-scaffold/__tests__/__snapshots__/index.test.ts.snap b/packages/webpack-scaffold/__tests__/__snapshots__/index.test.ts.snap index 0304203ef64..2bb1ed2117f 100755 --- a/packages/webpack-scaffold/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/webpack-scaffold/__tests__/__snapshots__/index.test.ts.snap @@ -9,6 +9,16 @@ Object { } `; +exports[`utils Inquirer should make an Input object with validation and default value 1`] = ` +Object { + "default": "my-plugin", + "message": "what is your plugin?", + "name": "plugins", + "type": "input", + "validate": [Function], +} +`; + exports[`utils createArrowFunction should stringify an arrow function 1`] = `"() => 'app.js'"`; exports[`utils createAssetFilterFunction should stringify an assetFilterFunction 1`] = ` diff --git a/packages/webpack-scaffold/__tests__/index.test.ts b/packages/webpack-scaffold/__tests__/index.test.ts index 0db475e179a..f6b4d238c8d 100755 --- a/packages/webpack-scaffold/__tests__/index.test.ts +++ b/packages/webpack-scaffold/__tests__/index.test.ts @@ -17,9 +17,7 @@ describe("utils", () => { expect(utils.createDynamicPromise("app.js")).toMatchSnapshot(); }); it("should stringify an array", () => { - expect( - utils.createDynamicPromise(["app.js", "index.js"]), - ).toMatchSnapshot(); + expect(utils.createDynamicPromise(["app.js", "index.js"])).toMatchSnapshot(); }); }); describe("createAssetFilterFunction", () => { @@ -51,34 +49,38 @@ describe("utils", () => { choices: ["Yes", "Maybe"], message: "does it work?", name: "entry", - type: "list", + type: "list" }); }); it("should make a RawList object", () => { - expect( - utils.RawList("output", "does it work?", ["Yes", "Maybe"]), - ).toEqual({ + expect(utils.RawList("output", "does it work?", ["Yes", "Maybe"])).toEqual({ choices: ["Yes", "Maybe"], message: "does it work?", name: "output", - type: "rawlist", + type: "rawlist" }); }); it("should make a CheckList object", () => { - expect( - utils.CheckList("context", "does it work?", ["Yes", "Maybe"]), - ).toEqual({ + expect(utils.CheckList("context", "does it work?", ["Yes", "Maybe"])).toEqual({ choices: ["Yes", "Maybe"], message: "does it work?", name: "context", - type: "checkbox", + type: "checkbox" }); }); it("should make an Input object", () => { expect(utils.Input("plugins", "what is your plugin?")).toEqual({ message: "what is your plugin?", name: "plugins", - type: "input", + type: "input" + }); + }); + it("should make an Input object", () => { + expect(utils.Input("plugins", "what is your plugin?", "my-plugin")).toEqual({ + default: "my-plugin", + message: "what is your plugin?", + name: "plugins", + type: "input" }); }); it("should make a Confirm object", () => { @@ -86,7 +88,7 @@ describe("utils", () => { default: true, message: "what is your context?", name: "context", - type: "confirm", + type: "confirm" }); }); it("should make a Confirm object with No as default", () => { @@ -94,13 +96,14 @@ describe("utils", () => { default: false, message: "what is your context?", name: "context", - type: "confirm", + type: "confirm" }); }); it("should make an Input object with validation", () => { - expect( - utils.InputValidate("plugins", "what is your plugin?", () => true), - ).toMatchSnapshot(); + expect(utils.InputValidate("plugins", "what is your plugin?", () => true)).toMatchSnapshot(); + }); + it("should make an Input object with validation and default value", () => { + expect(utils.InputValidate("plugins", "what is your plugin?", () => true, "my-plugin")).toMatchSnapshot(); }); }); }); diff --git a/packages/webpack-scaffold/index.ts b/packages/webpack-scaffold/index.ts index 0a4ceb38d5c..77a0a7bcf6f 100755 --- a/packages/webpack-scaffold/index.ts +++ b/packages/webpack-scaffold/index.ts @@ -76,21 +76,29 @@ export function CheckList(name: string, message: string, choices: string[]): Gen }; } -export function Input(name: string, message: string): Generator.Question { +export function Input(name: string, message: string, defaultChoice?: string): Generator.Question { return { + default: defaultChoice, message, name, type: "input" }; } -export function InputValidate(name: string, message: string, cb?: (input: string) => string | boolean): Generator.Question { - return { +export function InputValidate( + name: string, + message: string, + cb?: (input: string) => string | boolean, + defaultChoice?: string +): Generator.Question { + const input: Generator.Question = { message, name, type: "input", validate: cb }; + if (defaultChoice) input.default = defaultChoice; + return input; } export function Confirm(name: string, message: string, defaultChoice: boolean = true): Generator.Question { diff --git a/tsconfig.base.json b/tsconfig.base.json index d92fae8bb6a..dc260d82933 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -4,7 +4,8 @@ "module": "commonjs", "moduleResolution": "node", "allowSyntheticDefaultImports": true, - "skipLibCheck": true + "skipLibCheck": true, + "resolveJsonModule": true, }, "include": ["packages/**/*.ts"], "exclude": [