From cadc4d1c664611ce35b6aaa2d1313f6b6a1fd55c Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Fri, 9 Sep 2022 08:56:40 -0400 Subject: [PATCH] feat(webpack): add webpack plugin --- .cz-config.js | 1 + docs/generated/packages/web.json | 12 + docs/generated/packages/webpack.json | 780 ++++++++++++++++++ docs/packages.json | 9 + e2e/js/src/js.test.ts | 15 - e2e/node/src/node.test.ts | 14 +- e2e/react/src/react.test.ts | 16 +- e2e/utils/index.ts | 2 + e2e/web/src/web.test.ts | 18 +- e2e/webpack/jest.config.ts | 11 + e2e/webpack/project.json | 34 + e2e/webpack/src/webpack.test.ts | 58 ++ e2e/webpack/tsconfig.json | 13 + e2e/webpack/tsconfig.spec.json | 20 + .../src/integration/packages.spec.ts | 2 +- nx-dev/nx-dev/public/images/icons/webpack.svg | 4 + nx-dev/ui-references/src/lib/icons-map.ts | 1 + package.json | 9 +- packages/angular/package.json | 3 +- .../webpack-server/webpack-server.impl.ts | 2 +- .../utils/insert-ngmodule-import.ts | 70 +- packages/js/src/index.ts | 1 + packages/next/package.json | 2 +- packages/next/src/utils/config.spec.ts | 2 +- packages/next/src/utils/config.ts | 12 +- packages/node/migrations.json | 6 + packages/node/package.json | 14 +- packages/node/src/executors/node/node.impl.ts | 2 +- .../executors/webpack/webpack.impl.spec.ts | 162 ---- .../src/executors/webpack/webpack.impl.ts | 112 +-- .../application/application.spec.ts | 8 +- .../src/generators/application/application.ts | 6 +- .../update-webpack-executor.spec.ts | 52 ++ .../update-14-7-5/update-webpack-executor.ts | 21 + packages/node/src/utils/__mocks__/plugin-a.ts | 1 - packages/node/src/utils/__mocks__/plugin-b.ts | 1 - packages/node/src/utils/config.ts | 222 ----- .../src/utils/load-ts-transformers.spec.ts | 40 - .../node/src/utils/load-ts-transformers.ts | 86 -- packages/node/src/utils/node.config.spec.ts | 137 --- packages/node/src/utils/node.config.ts | 74 -- packages/node/src/utils/normalize.spec.ts | 167 ---- packages/node/src/utils/types.ts | 114 --- packages/react/package.json | 1 + .../react/plugins/component-testing/index.ts | 45 +- .../component-testing/webpack-fallback.ts | 2 +- .../react/plugins/storybook/index.spec.ts | 2 +- packages/react/plugins/storybook/index.ts | 13 +- .../module-federation-dev-server.impl.ts | 5 +- .../application/application.spec.ts | 4 +- .../src/generators/application/application.ts | 2 +- .../generators/application/lib/add-project.ts | 4 +- .../lib/update-configs.ts | 2 +- .../setup-tailwind/lib/update-project.ts | 2 +- .../setup-tailwind/setup-tailwind.spec.ts | 2 +- .../update-external-emotion-jsx-runtime.ts | 5 +- packages/web/migrations.json | 6 + packages/web/package.json | 36 +- .../executors/dev-server/dev-server.impl.ts | 142 +--- .../rollup/lib/delete-output-dir.ts} | 0 .../web/src/executors/rollup/lib/normalize.ts | 4 +- .../web/src/executors/rollup/rollup.impl.ts | 2 +- packages/web/src/executors/webpack/compat.ts | 4 +- .../web/src/executors/webpack/webpack.impl.ts | 256 +----- .../application/application.spec.ts | 4 +- .../src/generators/application/application.ts | 165 ++-- .../src/generators/application/schema.d.ts | 1 + .../src/generators/application/schema.json | 6 + packages/web/src/generators/init/init.spec.ts | 54 -- packages/web/src/generators/init/init.ts | 18 +- packages/web/src/generators/init/schema.d.ts | 1 + packages/web/src/generators/init/schema.json | 6 + .../migrations/update-11-5-2/utils.spec.ts | 2 +- .../update-webpack-executor.spec.ts | 92 +++ .../update-14-7-5/update-webpack-executor.ts | 27 + packages/web/src/utils/config.ts | 305 ------- .../web/src/utils/devserver.config.spec.ts | 84 -- packages/web/src/utils/fs.ts | 68 +- packages/web/src/utils/normalize.spec.ts | 115 --- packages/web/src/utils/normalize.ts | 174 +--- packages/web/src/utils/run-webpack.ts | 120 --- packages/web/src/utils/shared-models.ts | 121 --- packages/webpack/.eslintrc.json | 25 + packages/webpack/README.md | 13 + packages/webpack/executors.json | 26 + packages/webpack/generators.json | 33 + packages/webpack/index.ts | 7 + packages/webpack/jest.config.ts | 11 + packages/webpack/package.json | 79 ++ packages/webpack/project.json | 87 ++ .../src/executors/dev-server/compat.ts | 5 + .../executors/dev-server/dev-server.impl.ts | 119 +++ .../dev-server/lib/get-dev-server-config.ts} | 42 +- .../executors/dev-server/lib}/serve-path.ts | 6 +- .../src/executors/dev-server/schema.d.ts | 18 + .../src/executors/dev-server/schema.json | 75 ++ .../webpack/src/executors/webpack/compat.ts | 5 + .../webpack/lib/get-emitted-files.ts | 38 + .../webpack/lib/get-webpack-config.ts} | 114 ++- .../webpack/lib/normalize-options.ts} | 116 ++- .../src/executors/webpack/lib}/run-webpack.ts | 1 + .../webpack/src/executors/webpack/schema.d.ts | 92 +++ .../webpack/src/executors/webpack/schema.json | 452 ++++++++++ .../src/executors/webpack/webpack.impl.ts | 210 +++++ .../webpack/src/generators/init/init.spec.ts | 57 ++ packages/webpack/src/generators/init/init.ts | 70 ++ .../webpack/src/generators/init/schema.d.ts | 4 + .../webpack/src/generators/init/schema.json | 22 + .../generators/webpack-project/schema.d.ts | 11 + .../generators/webpack-project/schema.json | 60 ++ .../webpack-project/webpack-project.spec.ts | 103 +++ .../webpack-project/webpack-project.ts | 106 +++ .../webpack-nx-build-coordination-plugin.ts | 84 ++ packages/webpack/src/utils/config.ts | 402 +++++++++ packages/webpack/src/utils/fs.ts | 67 ++ .../generate-package-json-webpack-plugin.ts | 19 +- .../{web => webpack}/src/utils/hash-format.ts | 0 packages/webpack/src/utils/models.ts | 29 + packages/webpack/src/utils/run-webpack.ts | 46 ++ packages/webpack/src/utils/versions.ts | 5 + .../src/utils/web-babel-loader.ts | 0 .../webpack/build-browser-features.spec.ts | 0 .../utils/webpack/build-browser-features.ts | 0 .../src/utils/webpack/custom-webpack.ts | 0 ...interpolate-env-variables-to-index.spec.ts | 0 .../interpolate-env-variables-to-index.ts | 0 .../src/utils/webpack/normalize-entry.ts | 41 + .../src/utils/webpack/package-chunk-sort.ts | 4 +- .../src/utils/webpack/partials/browser.ts | 29 +- .../src/utils/webpack/partials/common.ts | 30 +- .../src/utils/webpack/partials/styles.ts | 14 +- .../plugins/index-file/augment-index-html.ts | 0 .../index-file/html-rewriting-stream.ts | 0 .../index-file/index-html-generator.ts | 2 +- .../plugins/index-html-webpack-plugin.ts | 0 .../webpack/plugins/postcss-cli-resources.ts | 7 - .../utils/webpack/plugins/raw-css-loader.ts | 0 .../plugins/remove-empty-scripts-plugin.ts | 0 .../webpack/plugins/remove-hash-plugin.ts | 3 +- .../webpack/plugins/scripts-webpack-plugin.ts | 0 .../src/utils/webpack/safari-nomodule.js | 0 .../src/utils/webpack/write-index-html.ts | 6 +- packages/webpack/tsconfig.json | 16 + packages/webpack/tsconfig.lib.json | 10 + packages/webpack/tsconfig.spec.json | 9 + scripts/depcheck/missing.ts | 1 + tsconfig.base.json | 2 + workspace.json | 2 + yarn.lock | 59 +- 149 files changed, 4144 insertions(+), 2988 deletions(-) create mode 100644 docs/generated/packages/webpack.json create mode 100644 e2e/webpack/jest.config.ts create mode 100644 e2e/webpack/project.json create mode 100644 e2e/webpack/src/webpack.test.ts create mode 100644 e2e/webpack/tsconfig.json create mode 100644 e2e/webpack/tsconfig.spec.json create mode 100644 nx-dev/nx-dev/public/images/icons/webpack.svg delete mode 100644 packages/node/src/executors/webpack/webpack.impl.spec.ts create mode 100644 packages/node/src/migrations/update-14-7-5/update-webpack-executor.spec.ts create mode 100644 packages/node/src/migrations/update-14-7-5/update-webpack-executor.ts delete mode 100644 packages/node/src/utils/__mocks__/plugin-a.ts delete mode 100644 packages/node/src/utils/__mocks__/plugin-b.ts delete mode 100644 packages/node/src/utils/config.ts delete mode 100644 packages/node/src/utils/load-ts-transformers.spec.ts delete mode 100644 packages/node/src/utils/load-ts-transformers.ts delete mode 100644 packages/node/src/utils/node.config.spec.ts delete mode 100644 packages/node/src/utils/node.config.ts delete mode 100644 packages/node/src/utils/normalize.spec.ts delete mode 100644 packages/node/src/utils/types.ts rename packages/{node/src/utils/fs.ts => web/src/executors/rollup/lib/delete-output-dir.ts} (100%) create mode 100644 packages/web/src/migrations/update-14-7-5/update-webpack-executor.spec.ts create mode 100644 packages/web/src/migrations/update-14-7-5/update-webpack-executor.ts delete mode 100644 packages/web/src/utils/config.ts delete mode 100644 packages/web/src/utils/devserver.config.spec.ts delete mode 100644 packages/web/src/utils/normalize.spec.ts delete mode 100644 packages/web/src/utils/run-webpack.ts delete mode 100644 packages/web/src/utils/shared-models.ts create mode 100644 packages/webpack/.eslintrc.json create mode 100644 packages/webpack/README.md create mode 100644 packages/webpack/executors.json create mode 100644 packages/webpack/generators.json create mode 100644 packages/webpack/index.ts create mode 100644 packages/webpack/jest.config.ts create mode 100644 packages/webpack/package.json create mode 100644 packages/webpack/project.json create mode 100644 packages/webpack/src/executors/dev-server/compat.ts create mode 100644 packages/webpack/src/executors/dev-server/dev-server.impl.ts rename packages/{web/src/utils/devserver.config.ts => webpack/src/executors/dev-server/lib/get-dev-server-config.ts} (73%) rename packages/{web/src/utils => webpack/src/executors/dev-server/lib}/serve-path.ts (89%) create mode 100644 packages/webpack/src/executors/dev-server/schema.d.ts create mode 100644 packages/webpack/src/executors/dev-server/schema.json create mode 100644 packages/webpack/src/executors/webpack/compat.ts create mode 100644 packages/webpack/src/executors/webpack/lib/get-emitted-files.ts rename packages/{web/src/utils/web.config.ts => webpack/src/executors/webpack/lib/get-webpack-config.ts} (76%) rename packages/{node/src/utils/normalize.ts => webpack/src/executors/webpack/lib/normalize-options.ts} (56%) rename packages/{node/src/utils => webpack/src/executors/webpack/lib}/run-webpack.ts (96%) create mode 100644 packages/webpack/src/executors/webpack/schema.d.ts create mode 100644 packages/webpack/src/executors/webpack/schema.json create mode 100644 packages/webpack/src/executors/webpack/webpack.impl.ts create mode 100644 packages/webpack/src/generators/init/init.spec.ts create mode 100644 packages/webpack/src/generators/init/init.ts create mode 100644 packages/webpack/src/generators/init/schema.d.ts create mode 100644 packages/webpack/src/generators/init/schema.json create mode 100644 packages/webpack/src/generators/webpack-project/schema.d.ts create mode 100644 packages/webpack/src/generators/webpack-project/schema.json create mode 100644 packages/webpack/src/generators/webpack-project/webpack-project.spec.ts create mode 100644 packages/webpack/src/generators/webpack-project/webpack-project.ts create mode 100644 packages/webpack/src/plugins/webpack-nx-build-coordination-plugin.ts create mode 100644 packages/webpack/src/utils/config.ts create mode 100644 packages/webpack/src/utils/fs.ts rename packages/{node => webpack}/src/utils/generate-package-json-webpack-plugin.ts (83%) rename packages/{web => webpack}/src/utils/hash-format.ts (100%) create mode 100644 packages/webpack/src/utils/models.ts create mode 100644 packages/webpack/src/utils/run-webpack.ts create mode 100644 packages/webpack/src/utils/versions.ts rename packages/{web => webpack}/src/utils/web-babel-loader.ts (100%) rename packages/{web => webpack}/src/utils/webpack/build-browser-features.spec.ts (100%) rename packages/{web => webpack}/src/utils/webpack/build-browser-features.ts (100%) rename packages/{web => webpack}/src/utils/webpack/custom-webpack.ts (100%) rename packages/{web/src/utils => webpack/src/utils/webpack}/interpolate-env-variables-to-index.spec.ts (100%) rename packages/{web/src/utils => webpack/src/utils/webpack}/interpolate-env-variables-to-index.ts (100%) create mode 100644 packages/webpack/src/utils/webpack/normalize-entry.ts rename packages/{web => webpack}/src/utils/webpack/package-chunk-sort.ts (91%) rename packages/{web => webpack}/src/utils/webpack/partials/browser.ts (69%) rename packages/{web => webpack}/src/utils/webpack/partials/common.ts (90%) rename packages/{web => webpack}/src/utils/webpack/partials/styles.ts (95%) rename packages/{web => webpack}/src/utils/webpack/plugins/index-file/augment-index-html.ts (100%) rename packages/{web => webpack}/src/utils/webpack/plugins/index-file/html-rewriting-stream.ts (100%) rename packages/{web => webpack}/src/utils/webpack/plugins/index-file/index-html-generator.ts (96%) rename packages/{web => webpack}/src/utils/webpack/plugins/index-html-webpack-plugin.ts (100%) rename packages/{web => webpack}/src/utils/webpack/plugins/postcss-cli-resources.ts (96%) rename packages/{web => webpack}/src/utils/webpack/plugins/raw-css-loader.ts (100%) rename packages/{web => webpack}/src/utils/webpack/plugins/remove-empty-scripts-plugin.ts (100%) rename packages/{web => webpack}/src/utils/webpack/plugins/remove-hash-plugin.ts (88%) rename packages/{web => webpack}/src/utils/webpack/plugins/scripts-webpack-plugin.ts (100%) rename packages/{web => webpack}/src/utils/webpack/safari-nomodule.js (100%) rename packages/{web => webpack}/src/utils/webpack/write-index-html.ts (98%) create mode 100644 packages/webpack/tsconfig.json create mode 100644 packages/webpack/tsconfig.lib.json create mode 100644 packages/webpack/tsconfig.spec.json diff --git a/.cz-config.js b/.cz-config.js index f81b81c6ceaad..329891c1a3937 100644 --- a/.cz-config.js +++ b/.cz-config.js @@ -41,6 +41,7 @@ module.exports = { description: 'anything testing specific (e.g., jest or cypress)', }, { name: 'web', description: 'anything Web specific' }, + { name: 'webpack', description: 'anything Webpack specific' }, ], allowTicketNumber: true, diff --git a/docs/generated/packages/web.json b/docs/generated/packages/web.json index b479b11b31d9b..db7db628dcc11 100644 --- a/docs/generated/packages/web.json +++ b/docs/generated/packages/web.json @@ -26,6 +26,12 @@ "description": "Init Web Plugin.", "type": "object", "properties": { + "bundler": { + "type": "string", + "description": "The bundler to use.", + "enum": ["webpack", "none"], + "default": "webpack" + }, "unitTestRunner": { "description": "Adds the specified unit test runner", "type": "string", @@ -105,6 +111,12 @@ "enum": ["babel", "swc"], "default": "babel" }, + "bundler": { + "type": "string", + "description": "The bundler to use.", + "enum": ["webpack", "none"], + "default": "webpack" + }, "linter": { "description": "The tool to use for running lint checks.", "type": "string", diff --git a/docs/generated/packages/webpack.json b/docs/generated/packages/webpack.json new file mode 100644 index 0000000000000..ccc25e58addb5 --- /dev/null +++ b/docs/generated/packages/webpack.json @@ -0,0 +1,780 @@ +{ + "githubRoot": "https://github.com/nrwl/nx/blob/master", + "name": "webpack", + "packageName": "@nrwl/webpack", + "description": "The Nx Plugin for Webpack contains executors and generators that support building applications using Webpack", + "root": "/packages/webpack", + "source": "/packages/webpack/src", + "documentation": [], + "generators": [ + { + "name": "init", + "factory": "./src/generators/init/init#webpackInitGenerator", + "schema": { + "$schema": "http://json-schema.org/schema", + "$id": "NxWebpackInit", + "cli": "nx", + "title": "Init Webpack Plugin", + "description": "Init Webpack Plugin.", + "type": "object", + "properties": { + "compiler": { + "type": "string", + "enum": ["babel", "swc", "tsc"], + "description": "The compiler to initialize for.", + "default": "babel" + }, + "skipFormat": { + "description": "Skip formatting files.", + "type": "boolean", + "default": false + } + }, + "required": [], + "presets": [] + }, + "description": "Initialize the `@nrwl/webpack` plugin.", + "aliases": ["ng-add"], + "hidden": true, + "implementation": "/packages/webpack/src/generators/init/init#webpackInitGenerator.ts", + "path": "/packages/webpack/src/generators/init/schema.json" + }, + { + "name": "webpack-project", + "factory": "./src/generators/webpack-project/webpack-project#webpackProjectGenerator", + "schema": { + "$schema": "http://json-schema.org/schema", + "$id": "NxWebpackProject", + "cli": "nx", + "title": "Add Webpack Configuration to a project", + "description": "Add Webpack Configuration to a project.", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The name of the project.", + "$default": { "$source": "argv", "index": 0 }, + "x-dropdown": "project", + "x-prompt": "What is the name of the project to set up a webpack for?" + }, + "compiler": { + "type": "string", + "enum": ["babel", "swc", "tsc"], + "description": "The compiler to use to build source.", + "default": "babel" + }, + "main": { + "type": "string", + "description": "Path relative to the workspace root for the main entry file. Defaults to '/src/main.ts'." + }, + "tsConfig": { + "type": "string", + "description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '/tsconfig.app.json'." + }, + "target": { + "type": "string", + "description": "Target platform for the build, same as the Webpack config option.", + "enum": ["node", "web"], + "default": "web" + }, + "skipFormat": { + "description": "Skip formatting files.", + "type": "boolean", + "default": false + }, + "skipPackageJson": { + "type": "boolean", + "default": false, + "description": "Do not add dependencies to `package.json`." + }, + "devServer": { + "type": "boolean", + "description": "Add a serve target to run a local webpack dev-server", + "default": false + }, + "webpackConfig": { + "type": "string", + "description": "Path relative to workspace root to a custom webpack file that takes a config object and returns an updated config." + } + }, + "required": [], + "presets": [] + }, + "description": "Add webpack configuration to a project.", + "hidden": true, + "implementation": "/packages/webpack/src/generators/webpack-project/webpack-project#webpackProjectGenerator.ts", + "aliases": [], + "path": "/packages/webpack/src/generators/webpack-project/schema.json" + } + ], + "executors": [ + { + "name": "webpack", + "implementation": "/packages/webpack/src/executors/webpack/webpack.impl.ts", + "schema": { + "title": "Webpack Executor", + "description": "Builds web applications using webpack.", + "cli": "nx", + "type": "object", + "properties": { + "crossOrigin": { + "type": "string", + "description": "The `crossorigin` attribute to use for generated javascript script tags. One of 'none' | 'anonymous' | 'use-credentials'." + }, + "main": { + "type": "string", + "description": "The name of the main entry-point file.", + "x-completion-type": "file", + "x-completion-glob": "**/*@(.js|.ts|.tsx)" + }, + "tsConfig": { + "type": "string", + "description": "The name of the Typescript configuration file.", + "x-completion-type": "file", + "x-completion-glob": "tsconfig.*.json" + }, + "compiler": { + "type": "string", + "description": "The compiler to use.", + "enum": ["babel", "swc", "tsc"], + "default": "babel" + }, + "outputPath": { + "type": "string", + "description": "The output path of the generated files.", + "x-completion-type": "directory" + }, + "target": { + "type": "string", + "description": "Target platform for the build, same as the Webpack config option.", + "enum": ["node", "web"], + "default": "web" + }, + "deleteOutputPath": { + "type": "boolean", + "description": "Delete the output path before building.", + "default": true + }, + "watch": { + "type": "boolean", + "description": "Enable re-building when files change.", + "default": false + }, + "baseHref": { + "type": "string", + "description": "Base url for the application being built." + }, + "deployUrl": { + "type": "string", + "description": "URL where the application will be deployed." + }, + "vendorChunk": { + "type": "boolean", + "description": "Use a separate bundle containing only vendor libraries.", + "default": true + }, + "commonChunk": { + "type": "boolean", + "description": "Use a separate bundle containing code used across multiple bundles.", + "default": true + }, + "runtimeChunk": { + "type": "boolean", + "description": "Use a separate bundle containing the runtime.", + "default": true + }, + "sourceMap": { + "description": "Output sourcemaps. Use 'hidden' for use with error reporting tools without generating sourcemap comment.", + "default": true, + "oneOf": [{ "type": "boolean" }, { "type": "string" }] + }, + "progress": { + "type": "boolean", + "description": "Log progress to the console while building.", + "default": false + }, + "assets": { + "type": "array", + "description": "List of static application assets.", + "default": [], + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "glob": { + "type": "string", + "description": "The pattern to match." + }, + "input": { + "type": "string", + "description": "The input directory path in which to apply 'glob'. Defaults to the project root." + }, + "ignore": { + "description": "An array of globs to ignore.", + "type": "array", + "items": { "type": "string" } + }, + "output": { + "type": "string", + "description": "Absolute path within the output." + } + }, + "additionalProperties": false, + "required": ["glob", "input", "output"] + }, + { "type": "string" } + ] + } + }, + "index": { + "type": "string", + "description": "HTML File which will be contain the application.", + "x-completion-type": "file", + "x-completion-glob": "**/*@(.html|.htm)" + }, + "scripts": { + "type": "array", + "description": "External Scripts which will be included before the main application entry.", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "x-completion-type": "file", + "x-completion-glob": "**/*@(.css|.scss|.less|.sass|.styl|.stylus)" + }, + "bundleName": { + "type": "string", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "x-completion-type": "file", + "x-completion-glob": "**/*@(.css|.scss|.less|.sass|.styl|.stylus)" + } + ] + }, + "default": [] + }, + "styles": { + "type": "array", + "description": "External Styles which will be included with the application", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "x-completion-type": "file", + "x-completion-glob": "**/*@(.css|.scss|.less|.sass|.styl|.stylus)" + }, + "bundleName": { + "type": "string", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "x-completion-type": "file", + "x-completion-glob": "**/*@(.css|.scss|.less|.sass|.styl|.stylus)" + } + ] + }, + "default": [] + }, + "budgets": { + "description": "Budget thresholds to ensure parts of your application stay within boundaries which you set.", + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of budget.", + "enum": [ + "all", + "allScript", + "any", + "anyScript", + "bundle", + "initial" + ] + }, + "name": { + "type": "string", + "description": "The name of the bundle." + }, + "baseline": { + "type": "string", + "description": "The baseline size for comparison." + }, + "maximumWarning": { + "type": "string", + "description": "The maximum threshold for warning relative to the baseline." + }, + "maximumError": { + "type": "string", + "description": "The maximum threshold for error relative to the baseline." + }, + "minimumWarning": { + "type": "string", + "description": "The minimum threshold for warning relative to the baseline." + }, + "minimumError": { + "type": "string", + "description": "The minimum threshold for error relative to the baseline." + }, + "warning": { + "type": "string", + "description": "The threshold for warning relative to the baseline (min & max)." + }, + "error": { + "type": "string", + "description": "The threshold for error relative to the baseline (min & max)." + } + }, + "additionalProperties": false, + "required": ["type"] + }, + "default": [] + }, + "namedChunks": { + "type": "boolean", + "description": "Names the produced bundles according to their entry file.", + "default": true + }, + "outputHashing": { + "type": "string", + "description": "Define the output filename cache-busting hashing mode.", + "default": "none", + "enum": ["none", "all", "media", "bundles"] + }, + "stylePreprocessorOptions": { + "description": "Options to pass to style preprocessors.", + "type": "object", + "properties": { + "includePaths": { + "description": "Paths to include. Paths will be resolved to project root.", + "type": "array", + "items": { "type": "string" }, + "default": [] + } + }, + "additionalProperties": false + }, + "optimization": { + "description": "Enables optimization of the build output.", + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Enables optimization of the scripts output.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Enables optimization of the styles output.", + "default": true + } + }, + "additionalProperties": false + }, + { "type": "boolean" } + ] + }, + "generatePackageJson": { + "type": "boolean", + "description": "Generates a `package.json` file with the project's `node_module` dependencies populated for installing in a container. If a `package.json` exists in the project's directory, it will be reused with dependencies populated.", + "default": false + }, + "transformers": { + "type": "array", + "description": "List of TypeScript Compiler Transfomers Plugins.", + "default": [], + "aliases": ["tsPlugins"], + "items": { + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { + "name": { "type": "string" }, + "options": { + "type": "object", + "additionalProperties": true + } + }, + "additionalProperties": false, + "required": ["name"] + } + ] + } + }, + "additionalEntryPoints": { + "type": "array", + "items": { + "type": "object", + "properties": { + "entryName": { + "type": "string", + "description": "Name of the additional entry file." + }, + "entryPath": { + "type": "string", + "description": "Path to the additional entry file.", + "x-completion-type": "file", + "x-completion-glob": "**/*@(.js|.ts)" + } + } + } + }, + "outputFileName": { + "type": "string", + "description": "Name of the main output file.", + "default": "main.js" + }, + "externalDependencies": { + "oneOf": [ + { "type": "string", "enum": ["none", "all"] }, + { "type": "array", "items": { "type": "string" } } + ], + "description": "Dependencies to keep external to the bundle. (`all` (default), `none`, or an array of module names)", + "default": "all" + }, + "extractCss": { + "type": "boolean", + "description": "Extract CSS into a `.css` file.", + "default": true + }, + "es2015Polyfills": { + "description": "Conditional polyfills loaded in browsers which do not support `ES2015`.", + "type": "string" + }, + "subresourceIntegrity": { + "type": "boolean", + "description": "Enables the use of subresource integrity validation.", + "default": false + }, + "polyfills": { + "type": "string", + "description": "Polyfills to load before application", + "x-completion-type": "file", + "x-completion-glob": "**/*@(.js|.ts|.tsx)" + }, + "verbose": { + "type": "boolean", + "description": "Emits verbose output", + "default": false + }, + "statsJson": { + "type": "boolean", + "description": "Generates a 'stats.json' file which can be analyzed using tools such as: 'webpack-bundle-analyzer' or ``.", + "default": false + }, + "extractLicenses": { + "type": "boolean", + "description": "Extract all licenses in a separate file, in the case of production builds only.", + "default": false + }, + "memoryLimit": { + "type": "number", + "description": "Memory limit for type checking service process in `MB`.", + "default": 2048 + }, + "maxWorkers": { + "type": "number", + "description": "Number of workers to use for type checking.", + "default": 2 + }, + "fileReplacements": { + "description": "Replace files with other files in the build.", + "type": "array", + "items": { + "type": "object", + "properties": { + "replace": { + "type": "string", + "description": "The file to be replaced.", + "x-completion-type": "file" + }, + "with": { + "type": "string", + "description": "The file to replace with.", + "x-completion-type": "file" + } + }, + "additionalProperties": false, + "required": ["replace", "with"] + }, + "default": [] + }, + "buildLibsFromSource": { + "type": "boolean", + "description": "Read buildable libraries from source instead of building them separately.", + "default": true + }, + "generateIndexHtml": { + "type": "boolean", + "description": "Generates `index.html` file to the output path. This can be turned off if using a webpack plugin to generate HTML such as `html-webpack-plugin`.", + "default": true + }, + "postcssConfig": { + "type": "string", + "description": "Set a path to PostCSS config that applies to the app and all libs. Defaults to `undefined`, which auto-detects postcss.config.js files in each `app`/`lib` directory." + }, + "webpackConfig": { + "type": "string", + "description": "Path to a function which takes a webpack config, some context and returns the resulting webpack config. See https://nx.dev/guides/customize-webpack", + "x-completion-type": "file", + "x-completion-glob": "webpack?(*)@(.js|.ts)" + } + }, + "required": ["tsConfig", "main"], + "definitions": { + "assetPattern": { + "oneOf": [ + { + "type": "object", + "properties": { + "glob": { + "type": "string", + "description": "The pattern to match." + }, + "input": { + "type": "string", + "description": "The input directory path in which to apply 'glob'. Defaults to the project root." + }, + "ignore": { + "description": "An array of globs to ignore.", + "type": "array", + "items": { "type": "string" } + }, + "output": { + "type": "string", + "description": "Absolute path within the output." + } + }, + "additionalProperties": false, + "required": ["glob", "input", "output"] + }, + { "type": "string" } + ] + }, + "budget": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of budget.", + "enum": [ + "all", + "allScript", + "any", + "anyScript", + "bundle", + "initial" + ] + }, + "name": { + "type": "string", + "description": "The name of the bundle." + }, + "baseline": { + "type": "string", + "description": "The baseline size for comparison." + }, + "maximumWarning": { + "type": "string", + "description": "The maximum threshold for warning relative to the baseline." + }, + "maximumError": { + "type": "string", + "description": "The maximum threshold for error relative to the baseline." + }, + "minimumWarning": { + "type": "string", + "description": "The minimum threshold for warning relative to the baseline." + }, + "minimumError": { + "type": "string", + "description": "The minimum threshold for error relative to the baseline." + }, + "warning": { + "type": "string", + "description": "The threshold for warning relative to the baseline (min & max)." + }, + "error": { + "type": "string", + "description": "The threshold for error relative to the baseline (min & max)." + } + }, + "additionalProperties": false, + "required": ["type"] + }, + "extraEntryPoint": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "x-completion-type": "file", + "x-completion-glob": "**/*@(.css|.scss|.less|.sass|.styl|.stylus)" + }, + "bundleName": { + "type": "string", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "x-completion-type": "file", + "x-completion-glob": "**/*@(.css|.scss|.less|.sass|.styl|.stylus)" + } + ] + }, + "transformerPattern": { + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { + "name": { "type": "string" }, + "options": { "type": "object", "additionalProperties": true } + }, + "additionalProperties": false, + "required": ["name"] + } + ] + } + }, + "presets": [] + }, + "description": "Run webpack build.", + "aliases": [], + "hidden": false, + "path": "/packages/webpack/src/executors/webpack/schema.json" + }, + { + "name": "dev-server", + "implementation": "/packages/webpack/src/executors/dev-server/dev-server.impl.ts", + "schema": { + "title": "Web Dev Server", + "description": "Serve a web application.", + "cli": "nx", + "type": "object", + "properties": { + "buildTarget": { + "type": "string", + "description": "Target which builds the application." + }, + "port": { + "type": "number", + "description": "Port to listen on.", + "default": 4200 + }, + "host": { + "type": "string", + "description": "Host to listen on.", + "default": "localhost" + }, + "ssl": { + "type": "boolean", + "description": "Serve using `HTTPS`.", + "default": false + }, + "sslKey": { + "type": "string", + "description": "SSL key to use for serving `HTTPS`." + }, + "sslCert": { + "type": "string", + "description": "SSL certificate to use for serving `HTTPS`." + }, + "watch": { + "type": "boolean", + "description": "Watches for changes and rebuilds application.", + "default": true + }, + "liveReload": { + "type": "boolean", + "description": "Whether to reload the page on change, using live-reload.", + "default": true + }, + "hmr": { + "type": "boolean", + "description": "Enable hot module replacement.", + "default": false + }, + "publicHost": { + "type": "string", + "description": "Public URL where the application will be served." + }, + "open": { + "type": "boolean", + "description": "Open the application in the browser.", + "default": false + }, + "allowedHosts": { + "type": "string", + "description": "This option allows you to whitelist services that are allowed to access the dev server." + }, + "memoryLimit": { + "type": "number", + "description": "Memory limit for type checking service process in `MB`." + }, + "maxWorkers": { + "type": "number", + "description": "Number of workers to use for type checking." + }, + "baseHref": { + "type": "string", + "description": "Base url for the application being built." + } + }, + "presets": [] + }, + "description": "Serve a web application.", + "aliases": [], + "hidden": false, + "path": "/packages/webpack/src/executors/dev-server/schema.json" + } + ] +} diff --git a/docs/packages.json b/docs/packages.json index 3b478c4c55a2c..b2fddd8fc316b 100644 --- a/docs/packages.json +++ b/docs/packages.json @@ -303,6 +303,15 @@ "generators": ["init", "application"] } }, + { + "name": "webpack", + "packageName": "webpack", + "path": "generated/packages/webpack.json", + "schemas": { + "executors": ["webpack", "dev-server"], + "generators": ["init", "webpack-project"] + } + }, { "name": "workspace", "packageName": "workspace", diff --git a/e2e/js/src/js.test.ts b/e2e/js/src/js.test.ts index 4033fb969ebc3..5514b077d20b7 100644 --- a/e2e/js/src/js.test.ts +++ b/e2e/js/src/js.test.ts @@ -29,9 +29,6 @@ describe('js e2e', () => { expect(libPackageJson.scripts.test).toBeDefined(); expect(libPackageJson.scripts.build).toBeDefined(); expect(runCLI(`test ${npmScriptsLib}`)).toContain('implement test'); - expect(runCLI(`test ${npmScriptsLib}`)).toContain( - 'existing outputs match the cache, left as is' - ); const tsconfig = readJson(`tsconfig.base.json`); expect(tsconfig.compilerOptions.paths).toEqual({ @@ -47,9 +44,6 @@ describe('js e2e', () => { expect((await runCLIAsync(`test ${lib}`)).combinedOutput).toContain( 'Ran all test suites' ); - expect((await runCLIAsync(`test ${lib}`)).combinedOutput).toContain( - 'local cache' - ); const packageJson = readJson('package.json'); const devPackageNames = Object.keys(packageJson.devDependencies); @@ -115,9 +109,6 @@ describe('js e2e', () => { expect((await runCLIAsync(`test ${parentLib}`)).combinedOutput).toContain( 'Ran all test suites' ); - expect((await runCLIAsync(`test ${parentLib}`)).combinedOutput).toContain( - 'local cache' - ); expect(runCLI(`build ${parentLib}`)).toContain( 'Done compiling TypeScript files' @@ -181,9 +172,6 @@ describe('js e2e', () => { expect((await runCLIAsync(`test ${lib}`)).combinedOutput).toContain( 'Ran all test suites' ); - expect((await runCLIAsync(`test ${lib}`)).combinedOutput).toContain( - 'local cache' - ); expect(runCLI(`build ${lib}`)).toContain( 'Successfully compiled: 2 files with swc' @@ -205,9 +193,6 @@ describe('js e2e', () => { expect((await runCLIAsync(`test ${parentLib}`)).combinedOutput).toContain( 'Ran all test suites' ); - expect((await runCLIAsync(`test ${parentLib}`)).combinedOutput).toContain( - 'local cache' - ); expect(runCLI(`build ${parentLib}`)).toContain( 'Successfully compiled: 2 files with swc' diff --git a/e2e/node/src/node.test.ts b/e2e/node/src/node.test.ts index 60afad6fdfdf0..3dc9fb74b1ceb 100644 --- a/e2e/node/src/node.test.ts +++ b/e2e/node/src/node.test.ts @@ -352,7 +352,7 @@ ${jslib}(); const nestapp = uniq('nestapp'); runCLI(`generate @nrwl/nest:app ${nestapp} --linter=eslint`); - packageInstall('@nestjs/swagger', undefined, '~5.0.0'); + packageInstall('@nestjs/swagger', undefined, '^6.0.0'); updateProjectConfig(nestapp, (config) => { config.targets.build.options.tsPlugins = ['@nestjs/swagger/plugin']; @@ -396,16 +396,8 @@ ${jslib}(); await runCLIAsync(`build ${nestapp}`); const mainJs = readFile(`dist/apps/${nestapp}/main.js`); - expect(stripIndents`${mainJs}`).toContain( - stripIndents` - class FooDto { - static _OPENAPI_METADATA_FACTORY() { - return { foo: { required: true, type: () => String }, bar: { required: true, type: () => Number } }; - } - } - exports.FooDto = FooDto; - ` - ); + expect(mainJs).toContain('FooDto'); + expect(mainJs).toContain('_OPENAPI_METADATA_FACTORY'); }, 300000); }); }); diff --git a/e2e/react/src/react.test.ts b/e2e/react/src/react.test.ts index 6446213faec1c..a6dee9d873329 100644 --- a/e2e/react/src/react.test.ts +++ b/e2e/react/src/react.test.ts @@ -80,7 +80,7 @@ describe('React Applications', () => { checkFilesExist(...filesToCheck); expect(readFile(`dist/apps/${appName}/index.html`)).toContain( - `` + `` ); }, 250_000); @@ -125,13 +125,13 @@ describe('React Applications', () => { ); const filesToCheck = [ `dist/apps/${appName}/index.html`, - `dist/apps/${appName}/runtime.esm.js`, - `dist/apps/${appName}/polyfills.esm.js`, - `dist/apps/${appName}/main.esm.js`, + `dist/apps/${appName}/runtime.js`, + `dist/apps/${appName}/polyfills.js`, + `dist/apps/${appName}/main.js`, ]; if (opts.checkSourceMap) { - filesToCheck.push(`dist/apps/${appName}/main.esm.js.map`); + filesToCheck.push(`dist/apps/${appName}/main.js.map`); } if (opts.checkStyles) { @@ -214,9 +214,9 @@ describe('React Applications: additional packages', () => { checkFilesExist( `dist/apps/${appName}/index.html`, - `dist/apps/${appName}/runtime.esm.js`, - `dist/apps/${appName}/polyfills.esm.js`, - `dist/apps/${appName}/main.esm.js` + `dist/apps/${appName}/runtime.js`, + `dist/apps/${appName}/polyfills.js`, + `dist/apps/${appName}/main.js` ); }, 250_000); diff --git a/e2e/utils/index.ts b/e2e/utils/index.ts index 842a6ff0dd568..fe06f7885a116 100644 --- a/e2e/utils/index.ts +++ b/e2e/utils/index.ts @@ -301,6 +301,7 @@ export function newProject({ ); } + // TODO(jack): we should tag the projects (e.g. tags: ['package']) and filter from that rather than hard-code packages. const packages = [ `@nrwl/angular`, `@nrwl/eslint-plugin-nx`, @@ -315,6 +316,7 @@ export function newProject({ `@nrwl/react`, `@nrwl/storybook`, `@nrwl/web`, + `@nrwl/webpack`, `@nrwl/react-native`, ]; packageInstall(packages.join(` `), projScope); diff --git a/e2e/web/src/web.test.ts b/e2e/web/src/web.test.ts index 93f987794d46a..cd66dffd9a0e5 100644 --- a/e2e/web/src/web.test.ts +++ b/e2e/web/src/web.test.ts @@ -31,9 +31,9 @@ describe('Web Components Applications', () => { runCLI(`build ${appName} --outputHashing none --compiler babel`); checkFilesExist( `dist/apps/${appName}/index.html`, - `dist/apps/${appName}/runtime.esm.js`, - `dist/apps/${appName}/polyfills.esm.js`, - `dist/apps/${appName}/main.esm.js`, + `dist/apps/${appName}/runtime.js`, + `dist/apps/${appName}/polyfills.js`, + `dist/apps/${appName}/main.js`, `dist/apps/${appName}/styles.css` ); @@ -119,7 +119,7 @@ describe('Web Components Applications', () => { runCLI(`build ${appName} --outputHashing=none`); checkFilesExist( - `dist/apps/${appName}/main.esm.js`, + `dist/apps/${appName}/main.js`, `dist/apps/${appName}/main.es5.js` ); }, 120000); @@ -159,7 +159,7 @@ describe('Web Components Applications', () => { }); runCLI(`build ${appName} --outputHashing none`); - expect(readFile(`dist/apps/${appName}/main.esm.js`)).toMatch( + expect(readFile(`dist/apps/${appName}/main.js`)).toMatch( /Reflect\.metadata/ ); @@ -172,7 +172,7 @@ describe('Web Components Applications', () => { runCLI(`build ${appName} --outputHashing none`); - expect(readFile(`dist/apps/${appName}/main.esm.js`)).not.toMatch( + expect(readFile(`dist/apps/${appName}/main.js`)).not.toMatch( /Reflect\.metadata/ ); }, 120000); @@ -207,7 +207,7 @@ describe('Web Components Applications', () => { ` ); runCLI(`build ${appName} --outputHashing none`); - checkFilesExist(`dist/apps/${appName}/main.esm.js`); + checkFilesExist(`dist/apps/${appName}/main.js`); rmDist(); @@ -221,7 +221,7 @@ describe('Web Components Applications', () => { ` ); runCLI(`build ${appName} --outputHashing none`); - checkFilesExist(`dist/apps/${appName}/main.esm.js`); + checkFilesExist(`dist/apps/${appName}/main.js`); rmDist(); @@ -235,7 +235,7 @@ describe('Web Components Applications', () => { ` ); runCLI(`build ${appName} --outputHashing none`); - checkFilesExist(`dist/apps/${appName}/main.esm.js`); + checkFilesExist(`dist/apps/${appName}/main.js`); }, 100000); }); diff --git a/e2e/webpack/jest.config.ts b/e2e/webpack/jest.config.ts new file mode 100644 index 0000000000000..ba6ef13d6ecb2 --- /dev/null +++ b/e2e/webpack/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], + maxWorkers: 1, + globals: { 'ts-jest': { tsconfig: '/tsconfig.spec.json' } }, + displayName: 'e2e-webpack', + preset: '../../jest.preset.js', +}; diff --git a/e2e/webpack/project.json b/e2e/webpack/project.json new file mode 100644 index 0000000000000..da8d040a6e9d5 --- /dev/null +++ b/e2e/webpack/project.json @@ -0,0 +1,34 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "e2e/webpack", + "projectType": "application", + "targets": { + "e2e": { + "executor": "nx:run-commands", + "options": { + "commands": [ + { + "command": "yarn e2e-start-local-registry" + }, + { + "command": "yarn e2e-build-package-publish" + }, + { + "command": "nx run-e2e-tests e2e-webpack" + } + ], + "parallel": false + } + }, + "run-e2e-tests": { + "executor": "@nrwl/jest:jest", + "options": { + "jestConfig": "e2e/webpack/jest.config.ts", + "passWithNoTests": true, + "runInBand": true + }, + "outputs": ["coverage/e2e/webpack"] + } + }, + "implicitDependencies": ["webpack"] +} diff --git a/e2e/webpack/src/webpack.test.ts b/e2e/webpack/src/webpack.test.ts new file mode 100644 index 0000000000000..d3d0154b4edc8 --- /dev/null +++ b/e2e/webpack/src/webpack.test.ts @@ -0,0 +1,58 @@ +import { + cleanupProject, + newProject, + rmDist, + runCLI, + runCommand, + uniq, + updateFile, + updateProjectConfig, +} from '@nrwl/e2e/utils'; + +describe('Webpack Plugin', () => { + beforeEach(() => newProject()); + afterEach(() => cleanupProject()); + + it('should be able to setup project to build node programs with webpack and different compilers', async () => { + const myPkg = uniq('my-pkg'); + runCLI(`generate @nrwl/js:lib ${myPkg} --buildable=false`); + updateFile(`libs/${myPkg}/src/index.ts`, `console.log('Hello');\n`); + + // babel (default) + runCLI( + `generate @nrwl/webpack:webpack-project ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts` + ); + rmDist(); + runCLI(`build ${myPkg}`); + let output = runCommand(`node dist/libs/${myPkg}/main.js`); + expect(output).toMatch(/Hello/); + + updateProjectConfig(myPkg, (config) => { + delete config.targets.build; + return config; + }); + + // swc + runCLI( + `generate @nrwl/webpack:webpack-project ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts --compiler=swc` + ); + rmDist(); + runCLI(`build ${myPkg}`); + output = runCommand(`node dist/libs/${myPkg}/main.js`); + expect(output).toMatch(/Hello/); + + updateProjectConfig(myPkg, (config) => { + delete config.targets.build; + return config; + }); + + // tsc + runCLI( + `generate @nrwl/webpack:webpack-project ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts --compiler=tsc` + ); + rmDist(); + runCLI(`build ${myPkg}`); + output = runCommand(`node dist/libs/${myPkg}/main.js`); + expect(output).toMatch(/Hello/); + }, 500000); +}); diff --git a/e2e/webpack/tsconfig.json b/e2e/webpack/tsconfig.json new file mode 100644 index 0000000000000..6d5abf8483200 --- /dev/null +++ b/e2e/webpack/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": [], + "files": [], + "references": [ + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/e2e/webpack/tsconfig.spec.json b/e2e/webpack/tsconfig.spec.json new file mode 100644 index 0000000000000..1a24bfb0a1353 --- /dev/null +++ b/e2e/webpack/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx", + "**/*.d.ts", + "jest.config.ts" + ] +} diff --git a/nx-dev/nx-dev-e2e/src/integration/packages.spec.ts b/nx-dev/nx-dev-e2e/src/integration/packages.spec.ts index 667764f7a99e3..9c3a4faefaf83 100644 --- a/nx-dev/nx-dev-e2e/src/integration/packages.spec.ts +++ b/nx-dev/nx-dev-e2e/src/integration/packages.spec.ts @@ -6,7 +6,7 @@ import { assertTextOnPage } from './helpers'; */ describe('nx-dev: Packages Section', () => { (<{ title: string; path: string }[]>[ - { title: '@nrwl/', path: '/packages/angular' }, + { title: '@nrwl/angular', path: '/packages/angular' }, { title: '@nrwl/angular:add-linting', path: '/packages/angular/generators/add-linting', diff --git a/nx-dev/nx-dev/public/images/icons/webpack.svg b/nx-dev/nx-dev/public/images/icons/webpack.svg new file mode 100644 index 0000000000000..711c33c353096 --- /dev/null +++ b/nx-dev/nx-dev/public/images/icons/webpack.svg @@ -0,0 +1,4 @@ + +webpack + + diff --git a/nx-dev/ui-references/src/lib/icons-map.ts b/nx-dev/ui-references/src/lib/icons-map.ts index 61637cb371363..51f286e3d8cd5 100644 --- a/nx-dev/ui-references/src/lib/icons-map.ts +++ b/nx-dev/ui-references/src/lib/icons-map.ts @@ -18,5 +18,6 @@ export const iconsMap: Record = { 'react-native': '/images/icons/react.svg', storybook: '/images/icons/storybook.svg', web: '/images/icons/html5.svg', + webpack: '/images/icons/webpack.svg', workspace: '/images/icons/nx.svg', }; diff --git a/package.json b/package.json index 91ea9e09d6a48..e8273d3dbc4ab 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "@storybook/react": "~6.5.9", "@svgr/webpack": "^6.1.2", "@swc-node/register": "^1.4.2", + "@swc/cli": "~0.1.55", "@swc/core": "^1.2.173", "@swc/jest": "^0.2.20", "@testing-library/react": "13.3.0", @@ -122,7 +123,7 @@ "@xstate/react": "^1.6.3", "ajv": "^8.11.0", "angular": "1.8.0", - "autoprefixer": "10.4.8", + "autoprefixer": "^10.4.9", "babel-jest": "28.1.3", "chalk": "4.1.0", "chokidar": "^3.5.1", @@ -279,9 +280,11 @@ "@markdoc/markdoc": "0.1.6", "@monaco-editor/react": "^4.3.1", "@napi-rs/canvas": "^0.1.19", + "@swc/helpers": "~0.4.11", "@tailwindcss/aspect-ratio": "^0.4.0", "@tailwindcss/forms": "^0.4.0", "@tailwindcss/typography": "^0.5.0", + "axios": "0.21.1", "classnames": "^2.3.1", "cliui": "^7.0.2", "core-js": "^3.6.5", @@ -305,12 +308,10 @@ "string-width": "^4.2.3", "tailwindcss": "3.1.8", "tslib": "^2.3.0", - "weak-napi": "^2.0.2", - "axios": "0.21.1" + "weak-napi": "^2.0.2" }, "resolutions": { "**/xmlhttprequest-ssl": "~1.6.2", "minimist": "^1.2.6" } } - diff --git a/packages/angular/package.json b/packages/angular/package.json index ca7eafda6fba5..33f465d28615c 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -44,11 +44,10 @@ "@nrwl/jest": "file:../jest", "@nrwl/linter": "file:../linter", "@nrwl/storybook": "file:../storybook", - "@nrwl/web": "file:../web", + "@nrwl/webpack": "file:../webpack", "@nrwl/workspace": "file:../workspace", "@phenomnomnominal/tsquery": "4.1.1", "@schematics/angular": "~14.2.0", - "@typescript-eslint/type-utils": "^5.36.1", "chalk": "4.1.0", "chokidar": "^3.5.1", "http-server": "^14.1.0", diff --git a/packages/angular/src/builders/webpack-server/webpack-server.impl.ts b/packages/angular/src/builders/webpack-server/webpack-server.impl.ts index 4978d36039cd7..3e03cfe529763 100644 --- a/packages/angular/src/builders/webpack-server/webpack-server.impl.ts +++ b/packages/angular/src/builders/webpack-server/webpack-server.impl.ts @@ -9,7 +9,7 @@ import { parseTargetString, readCachedProjectGraph, } from '@nrwl/devkit'; -import { WebpackNxBuildCoordinationPlugin } from '@nrwl/web/src/plugins/webpack-nx-build-coordination-plugin'; +import { WebpackNxBuildCoordinationPlugin } from '@nrwl/webpack/src/plugins/webpack-nx-build-coordination-plugin'; import { calculateProjectDependencies, createTmpTsConfig, diff --git a/packages/angular/src/generators/utils/insert-ngmodule-import.ts b/packages/angular/src/generators/utils/insert-ngmodule-import.ts index 8b76599a63205..cab89d309656b 100644 --- a/packages/angular/src/generators/utils/insert-ngmodule-import.ts +++ b/packages/angular/src/generators/utils/insert-ngmodule-import.ts @@ -2,7 +2,10 @@ import { applyChangesToString, ChangeType, Tree } from '@nrwl/devkit'; import { __String, CallExpression, + ClassDeclaration, createSourceFile, + Decorator, + getDecorators, ImportDeclaration, isArrayLiteralExpression, isCallExpression, @@ -18,13 +21,6 @@ import { SourceFile, } from 'typescript'; -/** - * Importing this helper from @typescript-eslint/type-utils to ensure - * compatibility with TS < 4.8 due to the API change in TS4.8. - * This helper allows for support of TS <= 4.8. - */ -import { getDecorators } from '@typescript-eslint/type-utils'; - type ngModuleDecoratorProperty = | 'imports' | 'providers' @@ -57,14 +53,36 @@ export function insertNgModuleProperty( const ngModuleName = ngModuleNamedImport.name.escapedText; - const ngModuleClassDeclaration = findDecoratedClass(sourceFile, ngModuleName); - - const ngModuleDecorator = getDecorators(ngModuleClassDeclaration).find( - (decorator) => - isCallExpression(decorator.expression) && - isIdentifier(decorator.expression.expression) && - decorator.expression.expression.escapedText === ngModuleName - ); + /** + * Ensure backwards compatibility with TS < 4.8 due to the API change in TS4.8. + * The getDecorators util is only in TS 4.8, so we need the previous logic to handle TS < 4.8. + * + * TODO: clean this up by removing the requirement to eslint (can we use typescript instead)? + */ + let ngModuleClassDeclaration: ClassDeclaration; + let ngModuleDecorator: Decorator; + try { + ngModuleClassDeclaration = findDecoratedClass(sourceFile, ngModuleName); + ngModuleDecorator = getDecorators(ngModuleClassDeclaration).find( + (decorator) => + isCallExpression(decorator.expression) && + isIdentifier(decorator.expression.expression) && + decorator.expression.expression.escapedText === ngModuleName + ); + } catch { + // Support for TS < 4.8 + ngModuleClassDeclaration = findDecoratedClassLegacy( + sourceFile, + ngModuleName + ); + // @ts-ignore + ngModuleDecorator = ngModuleClassDeclaration.decorators.find( + (decorator) => + isCallExpression(decorator.expression) && + isIdentifier(decorator.expression.expression) && + decorator.expression.expression.escapedText === ngModuleName + ); + } const ngModuleCall = ngModuleDecorator.expression as CallExpression; @@ -165,7 +183,10 @@ function getNamedImport(coreImport: ImportDeclaration, importName: string) { ); } -function findDecoratedClass(sourceFile: SourceFile, ngModuleName: __String) { +function findDecoratedClass( + sourceFile: SourceFile, + ngModuleName: __String +): ClassDeclaration | undefined { const classDeclarations = sourceFile.statements.filter(isClassDeclaration); return classDeclarations.find((declaration) => { const decorators = getDecorators(declaration); @@ -181,6 +202,23 @@ function findDecoratedClass(sourceFile: SourceFile, ngModuleName: __String) { }); } +function findDecoratedClassLegacy( + sourceFile: SourceFile, + ngModuleName: __String +) { + const classDeclarations = sourceFile.statements.filter(isClassDeclaration); + return classDeclarations.find( + (declaration) => + declaration.decorators && + (declaration.decorators as any[]).some( + (decorator) => + isCallExpression(decorator.expression) && + isIdentifier(decorator.expression.expression) && + decorator.expression.expression.escapedText === ngModuleName + ) + ); +} + function findPropertyAssignment( ngModuleOptions: ObjectLiteralExpression, propertyName: ngModuleDecoratorProperty diff --git a/packages/js/src/index.ts b/packages/js/src/index.ts index a766533e9987f..ffba925bd9bb0 100644 --- a/packages/js/src/index.ts +++ b/packages/js/src/index.ts @@ -1,3 +1,4 @@ +export * from './utils/typescript/load-ts-transformers'; export * from './utils/typescript/print-diagnostics'; export * from './utils/typescript/run-type-check'; export { libraryGenerator } from './generators/library/library'; diff --git a/packages/next/package.json b/packages/next/package.json index c062fc9e8dc40..dfe22f77ceac9 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -39,7 +39,7 @@ "@nrwl/jest": "file:../jest", "@nrwl/linter": "file:../linter", "@nrwl/react": "file:../react", - "@nrwl/web": "file:../web", + "@nrwl/webpack": "file:../webpack", "@nrwl/workspace": "file:../workspace", "@svgr/webpack": "^6.1.2", "chalk": "4.1.0", diff --git a/packages/next/src/utils/config.spec.ts b/packages/next/src/utils/config.spec.ts index 1fdde75b3da02..6f509a5c8701f 100644 --- a/packages/next/src/utils/config.spec.ts +++ b/packages/next/src/utils/config.spec.ts @@ -5,7 +5,7 @@ import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin'; import { PHASE_PRODUCTION_BUILD } from './constants'; -jest.mock('@nrwl/web/src/utils/config', () => ({ +jest.mock('@nrwl/webpack', () => ({ createCopyPlugin: () => {}, })); jest.mock('tsconfig-paths-webpack-plugin'); diff --git a/packages/next/src/utils/config.ts b/packages/next/src/utils/config.ts index 2dfcae77a5bc5..7c80f52c619d8 100644 --- a/packages/next/src/utils/config.ts +++ b/packages/next/src/utils/config.ts @@ -1,7 +1,7 @@ import { ExecutorContext, - offsetFromRoot, joinPathFragments, + offsetFromRoot, } from '@nrwl/devkit'; // ignoring while we support both Next 11.1.0 and versions before it // @ts-ignore @@ -16,18 +16,14 @@ import type { import { join, resolve } from 'path'; import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin'; import { Configuration } from 'webpack'; -import { - FileReplacement, - NextBuildBuilderOptions, - WebpackConfigOptions, -} from './types'; -import { normalizeAssets } from '@nrwl/web/src/utils/normalize'; -import { createCopyPlugin } from '@nrwl/web/src/utils/config'; +import { FileReplacement, NextBuildBuilderOptions } from './types'; +import { createCopyPlugin, normalizeAssets } from '@nrwl/webpack'; import { WithNxOptions } from '../../plugins/with-nx'; import { createTmpTsConfig, DependentBuildableProjectNode, } from '@nrwl/workspace/src/utilities/buildable-libs-utils'; + const loadConfig = require('next/dist/server/config').default; export function createWebpackConfig( diff --git a/packages/node/migrations.json b/packages/node/migrations.json index b2dbd2ea0005d..cb18b0dce9531 100644 --- a/packages/node/migrations.json +++ b/packages/node/migrations.json @@ -33,6 +33,12 @@ "version": "13.8.5-beta.1", "description": "Renames @nrwl/node:package to @nrwl/js:tsc", "factory": "./src/migrations/update-13-8-5/update-package-to-tsc" + }, + "update-webpack-executor": { + "cli": "nx", + "version": "14.7.5-beta.1", + "description": "Update usages of webpack executors to @nrwl/webpack", + "factory": "./src/migrations/update-14-7-5/update-webpack-executor" } }, "packageJsonUpdates": { diff --git a/packages/node/package.json b/packages/node/package.json index 47c9d19eec1e3..eb608f55dae5e 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -34,26 +34,16 @@ "@nrwl/jest": "file:../jest", "@nrwl/js": "file:../js", "@nrwl/linter": "file:../linter", + "@nrwl/webpack": "file:../webpack", "@nrwl/workspace": "file:../workspace", "chalk": "4.1.0", - "copy-webpack-plugin": "^10.2.4", "dotenv": "~10.0.0", - "enhanced-resolve": "^5.8.3", - "fork-ts-checker-webpack-plugin": "7.2.13", "fs-extra": "^10.1.0", "glob": "7.1.4", - "license-webpack-plugin": "^4.0.2", "rxjs": "^6.5.4", - "source-map-support": "0.5.19", - "terser-webpack-plugin": "^5.3.3", "tree-kill": "1.2.2", - "ts-loader": "^9.3.1", "ts-node": "10.9.1", "tsconfig-paths": "^3.9.0", - "tsconfig-paths-webpack-plugin": "3.5.2", - "tslib": "^2.3.0", - "webpack": "^5.58.1", - "webpack-merge": "^5.8.0", - "webpack-node-externals": "^3.0.0" + "tslib": "^2.3.0" } } diff --git a/packages/node/src/executors/node/node.impl.ts b/packages/node/src/executors/node/node.impl.ts index e276961b4443f..0fd9a881de304 100644 --- a/packages/node/src/executors/node/node.impl.ts +++ b/packages/node/src/executors/node/node.impl.ts @@ -2,7 +2,7 @@ import { ExecutorContext } from '@nrwl/devkit'; import type { NodeExecutorOptions } from '@nrwl/js/src/executors/node/schema'; import { nodeExecutor as jsNodeExecutor } from '@nrwl/js/src/executors/node/node.impl'; -// TODO(jack): Remove for Nx 15 +// TODO(jack): Remove for Nx 16 export async function* nodeExecutor( options: NodeExecutorOptions, context: ExecutorContext diff --git a/packages/node/src/executors/webpack/webpack.impl.spec.ts b/packages/node/src/executors/webpack/webpack.impl.spec.ts deleted file mode 100644 index 581570ec5569f..0000000000000 --- a/packages/node/src/executors/webpack/webpack.impl.spec.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { ExecutorContext } from '@nrwl/devkit'; -import { of } from 'rxjs'; -import * as projectGraph from '@nrwl/devkit'; -import type { ProjectGraph } from '@nrwl/devkit'; -import webpackExecutor from './webpack.impl'; -import { BuildNodeBuilderOptions } from '../../utils/types'; - -jest.mock('tsconfig-paths-webpack-plugin'); -jest.mock('../../utils/run-webpack', () => ({ - runWebpack: jest.fn(), -})); -import { runWebpack } from '../../utils/run-webpack'; - -describe('Node Build Executor', () => { - let context: ExecutorContext; - let options: BuildNodeBuilderOptions; - - beforeEach(async () => { - (runWebpack).mockReturnValue(of({ hasErrors: () => false })); - context = { - root: '/root', - cwd: '/root', - projectName: 'my-app', - targetName: 'build', - workspace: { - version: 2, - projects: { - 'my-app': { - root: 'apps/wibble', - sourceRoot: 'apps/wibble', - }, - }, - npmScope: 'test', - }, - projectGraph: {} as ProjectGraph, - isVerbose: false, - }; - options = { - outputPath: 'dist/apps/wibble', - externalDependencies: 'all', - main: 'apps/wibble/src/main.ts', - tsConfig: 'apps/wibble/tsconfig.ts', - buildLibsFromSource: true, - fileReplacements: [], - }; - }); - - afterEach(() => jest.clearAllMocks()); - - it('should call webpack', async () => { - await webpackExecutor(options, context).next(); - - expect(runWebpack).toHaveBeenCalledWith( - expect.objectContaining({ - output: expect.objectContaining({ - filename: 'main.js', - libraryTarget: 'commonjs', - path: '/root/dist/apps/wibble', - }), - }) - ); - }); - - it('should use outputFileName if passed in', async () => { - await webpackExecutor( - { ...options, outputFileName: 'index.js' }, - context - ).next(); - - expect(runWebpack).toHaveBeenCalledWith( - expect.objectContaining({ - entry: expect.objectContaining({ - index: ['/root/apps/wibble/src/main.ts'], - }), - output: expect.objectContaining({ - filename: 'index.js', - libraryTarget: 'commonjs', - path: '/root/dist/apps/wibble', - }), - }) - ); - }); - - it('should use watchOptions if passed in', async () => { - await webpackExecutor( - { - ...options, - watchOptions: { - ignored: ['path1'], - }, - }, - context - ).next(); - - expect(runWebpack).toHaveBeenCalledWith( - expect.objectContaining({ - watchOptions: { - ignored: ['path1'], - aggregateTimeout: 200, - }, - }) - ); - }); - - describe('webpackConfig', () => { - it('should handle custom path', async () => { - jest.mock( - '/root/config.js', - () => (options) => ({ ...options, prop: 'my-val' }), - { virtual: true } - ); - await webpackExecutor( - { ...options, webpackConfig: 'config.js' }, - context - ).next(); - - expect(runWebpack).toHaveBeenCalledWith( - expect.objectContaining({ - output: expect.objectContaining({ - filename: 'main.js', - libraryTarget: 'commonjs', - path: '/root/dist/apps/wibble', - }), - prop: 'my-val', - }) - ); - }); - - it('should handle multiple custom paths in order', async () => { - jest.mock( - '/root/config1.js', - () => (o) => ({ ...o, prop1: 'my-val-1' }), - { virtual: true } - ); - jest.mock( - '/root/config2.js', - () => (o) => ({ - ...o, - prop1: o.prop1 + '-my-val-2', - prop2: 'my-val-2', - }), - { virtual: true } - ); - await webpackExecutor( - { ...options, webpackConfig: ['config1.js', 'config2.js'] }, - context - ).next(); - - expect(runWebpack).toHaveBeenCalledWith( - expect.objectContaining({ - output: expect.objectContaining({ - filename: 'main.js', - libraryTarget: 'commonjs', - path: '/root/dist/apps/wibble', - }), - prop1: 'my-val-1-my-val-2', - prop2: 'my-val-2', - }) - ); - }); - }); -}); diff --git a/packages/node/src/executors/webpack/webpack.impl.ts b/packages/node/src/executors/webpack/webpack.impl.ts index e5fcfec7f7b22..40105f7db812d 100644 --- a/packages/node/src/executors/webpack/webpack.impl.ts +++ b/packages/node/src/executors/webpack/webpack.impl.ts @@ -1,109 +1,23 @@ -import 'dotenv/config'; +/** + * For backwards compat. + * TODO(jack): Remove in Nx 16. + */ import { ExecutorContext } from '@nrwl/devkit'; -import { eachValueFrom } from '@nrwl/devkit/src/utils/rxjs-for-await'; -import { - calculateProjectDependencies, - createTmpTsConfig, -} from '@nrwl/workspace/src/utilities/buildable-libs-utils'; -import { getRootTsConfigPath } from '@nrwl/workspace/src/utilities/typescript'; - -import { map, tap } from 'rxjs/operators'; -import { resolve } from 'path'; -import { register } from 'ts-node'; - -import { getNodeWebpackConfig } from '../../utils/node.config'; -import { BuildNodeBuilderOptions } from '../../utils/types'; -import { normalizeBuildOptions } from '../../utils/normalize'; -import { deleteOutputDir } from '../../utils/fs'; -import { runWebpack } from '../../utils/run-webpack'; - -export type NodeBuildEvent = { - outfile: string; - success: boolean; -}; +import type { WebpackExecutorOptions } from '@nrwl/webpack'; +import { webpackExecutor as baseWebpackExecutor } from '@nrwl/webpack'; export async function* webpackExecutor( - rawOptions: BuildNodeBuilderOptions, + options: WebpackExecutorOptions, context: ExecutorContext ) { - const { sourceRoot, root } = context.workspace.projects[context.projectName]; - - if (!sourceRoot) { - throw new Error(`${context.projectName} does not have a sourceRoot.`); - } - - if (!root) { - throw new Error(`${context.projectName} does not have a root.`); - } - - const options = normalizeBuildOptions( - rawOptions, - context.root, - sourceRoot, - root - ); - - if (options.webpackConfig.some((x) => x.endsWith('.ts'))) { - registerTsNode(); - } - - if (!options.buildLibsFromSource) { - const { target, dependencies } = calculateProjectDependencies( - context.projectGraph, - context.root, - context.projectName, - context.targetName, - context.configurationName - ); - options.tsConfig = createTmpTsConfig( - options.tsConfig, - context.root, - target.data.root, - dependencies - ); - } - - // Delete output path before bundling - if (options.deleteOutputPath) { - deleteOutputDir(context.root, options.outputPath); - } - - const config = await options.webpackConfig.reduce( - async (currentConfig, plugin) => { - return require(plugin)(await currentConfig, { - options, - configuration: context.configurationName, - }); + yield* baseWebpackExecutor( + { + ...options, + target: 'node', + compiler: 'tsc', }, - Promise.resolve( - getNodeWebpackConfig(context, context.projectGraph, options) - ) + context ); - - return yield* eachValueFrom( - runWebpack(config).pipe( - tap((stats) => { - console.info(stats.toString(config.stats)); - }), - map((stats) => { - return { - success: !stats.hasErrors(), - outfile: resolve( - context.root, - options.outputPath, - options.outputFileName - ), - } as NodeBuildEvent; - }) - ) - ); -} - -function registerTsNode() { - const rootTsConfig = getRootTsConfigPath(); - register({ - ...(rootTsConfig ? { project: rootTsConfig } : null), - }); } export default webpackExecutor; diff --git a/packages/node/src/generators/application/application.spec.ts b/packages/node/src/generators/application/application.spec.ts index 2eb3797382bb3..76247e27cb78b 100644 --- a/packages/node/src/generators/application/application.spec.ts +++ b/packages/node/src/generators/application/application.spec.ts @@ -1,5 +1,5 @@ -import { NxJsonConfiguration, readJson, Tree, getProjects } from '@nrwl/devkit'; import * as devkit from '@nrwl/devkit'; +import { getProjects, NxJsonConfiguration, readJson, Tree } from '@nrwl/devkit'; import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing'; // nx-ignore-next-line @@ -44,9 +44,11 @@ describe('app', () => { expect(project.architect).toEqual( expect.objectContaining({ build: { - builder: '@nrwl/node:webpack', + builder: '@nrwl/webpack:webpack', outputs: ['{options.outputPath}'], options: { + target: 'node', + compiler: 'tsc', outputPath: 'dist/apps/my-node-app', main: 'apps/my-node-app/src/main.ts', tsConfig: 'apps/my-node-app/tsconfig.app.json', @@ -67,7 +69,7 @@ describe('app', () => { }, }, serve: { - builder: '@nrwl/node:node', + builder: '@nrwl/js:node', options: { buildTarget: 'my-node-app:build', }, diff --git a/packages/node/src/generators/application/application.ts b/packages/node/src/generators/application/application.ts index ab21b852fb06a..0b398a6ea2528 100644 --- a/packages/node/src/generators/application/application.ts +++ b/packages/node/src/generators/application/application.ts @@ -40,9 +40,11 @@ function getBuildConfig( options: NormalizedSchema ): TargetConfiguration { return { - executor: '@nrwl/node:webpack', + executor: '@nrwl/webpack:webpack', outputs: ['{options.outputPath}'], options: { + target: 'node', + compiler: 'tsc', outputPath: joinPathFragments('dist', options.appProjectRoot), main: joinPathFragments( project.sourceRoot, @@ -75,7 +77,7 @@ function getBuildConfig( function getServeConfig(options: NormalizedSchema): TargetConfiguration { return { - executor: '@nrwl/node:node', + executor: '@nrwl/js:node', options: { buildTarget: `${options.name}:build`, }, diff --git a/packages/node/src/migrations/update-14-7-5/update-webpack-executor.spec.ts b/packages/node/src/migrations/update-14-7-5/update-webpack-executor.spec.ts new file mode 100644 index 0000000000000..eb6fcfd2f2b2b --- /dev/null +++ b/packages/node/src/migrations/update-14-7-5/update-webpack-executor.spec.ts @@ -0,0 +1,52 @@ +import { readJson } from '@nrwl/devkit'; +import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing'; + +import update from './update-webpack-executor'; + +describe('Migration: @nrwl/webpack', () => { + it(`should update usage of webpack executor`, async () => { + let tree = createTreeWithEmptyV1Workspace(); + + tree.write( + 'workspace.json', + JSON.stringify({ + version: 2, + projects: { + myapp: { + root: 'apps/myapp', + sourceRoot: 'apps/myapp/src', + projectType: 'application', + targets: { + build: { + executor: '@nrwl/node:webpack', + options: {}, + }, + }, + }, + }, + }) + ); + + await update(tree); + + expect(readJson(tree, 'workspace.json')).toEqual({ + version: 2, + projects: { + myapp: { + root: 'apps/myapp', + sourceRoot: 'apps/myapp/src', + projectType: 'application', + targets: { + build: { + executor: '@nrwl/webpack:webpack', + options: { + compiler: 'tsc', + target: 'node', + }, + }, + }, + }, + }, + }); + }); +}); diff --git a/packages/node/src/migrations/update-14-7-5/update-webpack-executor.ts b/packages/node/src/migrations/update-14-7-5/update-webpack-executor.ts new file mode 100644 index 0000000000000..1c7eb2162ea32 --- /dev/null +++ b/packages/node/src/migrations/update-14-7-5/update-webpack-executor.ts @@ -0,0 +1,21 @@ +import { + formatFiles, + getProjects, + Tree, + updateProjectConfiguration, +} from '@nrwl/devkit'; + +export default async function update(host: Tree) { + const projects = getProjects(host); + + for (const [name, config] of projects.entries()) { + if (config?.targets?.build?.executor === '@nrwl/node:webpack') { + config.targets.build.executor = '@nrwl/webpack:webpack'; + config.targets.build.options.target = 'node'; + config.targets.build.options.compiler = 'tsc'; + updateProjectConfiguration(host, name, config); + } + } + + await formatFiles(host); +} diff --git a/packages/node/src/utils/__mocks__/plugin-a.ts b/packages/node/src/utils/__mocks__/plugin-a.ts deleted file mode 100644 index b5131cd5858ee..0000000000000 --- a/packages/node/src/utils/__mocks__/plugin-a.ts +++ /dev/null @@ -1 +0,0 @@ -export const before = () => {}; diff --git a/packages/node/src/utils/__mocks__/plugin-b.ts b/packages/node/src/utils/__mocks__/plugin-b.ts deleted file mode 100644 index df5ae8932752c..0000000000000 --- a/packages/node/src/utils/__mocks__/plugin-b.ts +++ /dev/null @@ -1 +0,0 @@ -export const after = () => {}; diff --git a/packages/node/src/utils/config.ts b/packages/node/src/utils/config.ts deleted file mode 100644 index 71a428beba100..0000000000000 --- a/packages/node/src/utils/config.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript'; -import { LicenseWebpackPlugin } from 'license-webpack-plugin'; -import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin'; -import * as ts from 'typescript'; -import type { Configuration, WebpackPluginInstance } from 'webpack'; -import * as webpack from 'webpack'; -import { loadTsTransformers } from './load-ts-transformers'; -import { BuildBuilderOptions } from './types'; -import CopyWebpackPlugin = require('copy-webpack-plugin'); -import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); -import { removeExt } from '@nrwl/workspace/src/utils/runtime-lint-utils'; - -export const OUT_FILENAME_TEMPLATE = '[name].js'; - -export function getBaseWebpackPartial( - options: BuildBuilderOptions -): Configuration { - const { options: compilerOptions } = readTsConfig(options.tsConfig); - const supportsEs2015 = - compilerOptions.target !== ts.ScriptTarget.ES3 && - compilerOptions.target !== ts.ScriptTarget.ES5; - const mainFields = [...(supportsEs2015 ? ['es2015'] : []), 'module', 'main']; - const extensions = ['.ts', '.tsx', '.mjs', '.js', '.jsx']; - - const { compilerPluginHooks, hasPlugin } = loadTsTransformers( - options.transformers - ); - - const additionalEntryPoints = - options.additionalEntryPoints?.reduce( - (obj, current) => ({ - ...obj, - [current.entryName]: current.entryPath, - }), - {} as { [entryName: string]: string } - ) ?? {}; - const mainEntry = options.outputFileName - ? removeExt(options.outputFileName) - : 'main'; - const webpackConfig: Configuration = { - entry: { - [mainEntry]: [options.main], - ...additionalEntryPoints, - }, - devtool: options.sourceMap ? 'source-map' : false, - mode: options.optimization ? 'production' : 'development', - output: { - path: options.outputPath, - filename: - options.additionalEntryPoints?.length > 0 - ? OUT_FILENAME_TEMPLATE - : options.outputFileName, - hashFunction: 'xxhash64', - // Disabled for performance - pathinfo: false, - }, - module: { - // Enabled for performance - unsafeCache: true, - rules: [ - { - test: /\.([jt])sx?$/, - loader: require.resolve(`ts-loader`), - exclude: /node_modules/, - options: { - configFile: options.tsConfig, - transpileOnly: !hasPlugin, - // https://github.com/TypeStrong/ts-loader/pull/685 - experimentalWatchApi: true, - getCustomTransformers: (program) => ({ - before: compilerPluginHooks.beforeHooks.map((hook) => - hook(program) - ), - after: compilerPluginHooks.afterHooks.map((hook) => - hook(program) - ), - afterDeclarations: compilerPluginHooks.afterDeclarationsHooks.map( - (hook) => hook(program) - ), - }), - }, - }, - ], - }, - resolve: { - extensions, - alias: getAliases(options), - plugins: [ - new TsconfigPathsPlugin({ - configFile: options.tsConfig, - extensions, - mainFields, - }) as never, // TODO: Remove never type when 'tsconfig-paths-webpack-plugin' types fixed - ], - mainFields, - }, - performance: { - hints: false, - }, - plugins: [ - new ForkTsCheckerWebpackPlugin({ - // For watch mode, type errors should result in failure. - async: false, - typescript: { - configFile: options.tsConfig, - memoryLimit: options.memoryLimit || 2018, - }, - }), - ], - watch: options.watch, - watchOptions: { - // Delay the next rebuild from first file change, otherwise can lead to - // two builds on a single file change. - aggregateTimeout: 200, - poll: options.poll, - ...options.watchOptions, - }, - stats: getStatsConfig(options), - experiments: { - cacheUnaffected: true, - }, - }; - - const extraPlugins: WebpackPluginInstance[] = []; - - if (options.progress) { - extraPlugins.push(new webpack.ProgressPlugin()); - } - - if (options.extractLicenses) { - extraPlugins.push( - new LicenseWebpackPlugin({ - stats: { - errors: false, - }, - perChunkOutput: false, - outputFilename: `3rdpartylicenses.txt`, - }) as unknown as WebpackPluginInstance - ); - } - - // process asset entries - if (Array.isArray(options.assets) && options.assets.length > 0) { - const copyWebpackPluginInstance = new CopyWebpackPlugin({ - patterns: options.assets.map((asset) => { - return { - context: asset.input, - // Now we remove starting slash to make Webpack place it from the output root. - to: asset.output, - from: asset.glob, - globOptions: { - ignore: [ - '.gitkeep', - '**/.DS_Store', - '**/Thumbs.db', - ...(asset.ignore ?? []), - ], - dot: true, - }, - }; - }), - }); - - new CopyWebpackPlugin({ - patterns: options.assets.map((asset: any) => { - return { - context: asset.input, - // Now we remove starting slash to make Webpack place it from the output root. - to: asset.output, - from: asset.glob, - globOptions: { - ignore: [ - '.gitkeep', - '**/.DS_Store', - '**/Thumbs.db', - ...(asset.ignore ?? []), - ], - dot: true, - }, - }; - }), - }); - extraPlugins.push(copyWebpackPluginInstance); - } - - webpackConfig.plugins = [...webpackConfig.plugins, ...extraPlugins]; - - return webpackConfig; -} - -function getAliases(options: BuildBuilderOptions): { [key: string]: string } { - return options.fileReplacements.reduce( - (aliases, replacement) => ({ - ...aliases, - [replacement.replace]: replacement.with, - }), - {} - ); -} - -function getStatsConfig(options: BuildBuilderOptions) { - return { - hash: true, - timings: false, - cached: false, - cachedAssets: false, - modules: false, - warnings: true, - errors: true, - colors: !options.verbose && !options.statsJson, - chunks: !options.verbose, - assets: !!options.verbose, - chunkOrigins: !!options.verbose, - chunkModules: !!options.verbose, - children: !!options.verbose, - reasons: !!options.verbose, - version: !!options.verbose, - errorDetails: !!options.verbose, - moduleTrace: !!options.verbose, - usedExports: !!options.verbose, - }; -} diff --git a/packages/node/src/utils/load-ts-transformers.spec.ts b/packages/node/src/utils/load-ts-transformers.spec.ts deleted file mode 100644 index 69610f2b0c136..0000000000000 --- a/packages/node/src/utils/load-ts-transformers.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { loadTsTransformers } from './load-ts-transformers'; - -jest.mock('plugin-a'); -jest.mock('plugin-b'); -const mockRequireResolve = jest.fn((path) => path); - -describe('loadTsTransformers', () => { - it('should return empty hooks if plugins is falsy', () => { - const result = loadTsTransformers(undefined); - assertEmptyResult(result); - }); - - it('should return empty hooks if plugins is []', () => { - const result = loadTsTransformers([]); - assertEmptyResult(result); - }); - - it('should return correct compiler hooks', () => { - const result = loadTsTransformers( - ['plugin-a', 'plugin-b'], - mockRequireResolve as any - ); - - expect(result.hasPlugin).toEqual(true); - expect(result.compilerPluginHooks).toEqual({ - beforeHooks: [expect.any(Function)], - afterHooks: [expect.any(Function)], - afterDeclarationsHooks: [], - }); - }); - - function assertEmptyResult(result: ReturnType) { - expect(result.hasPlugin).toEqual(false); - expect(result.compilerPluginHooks).toEqual({ - beforeHooks: [], - afterHooks: [], - afterDeclarationsHooks: [], - }); - } -}); diff --git a/packages/node/src/utils/load-ts-transformers.ts b/packages/node/src/utils/load-ts-transformers.ts deleted file mode 100644 index fc847c54b4632..0000000000000 --- a/packages/node/src/utils/load-ts-transformers.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { logger } from '@nrwl/devkit'; -import { join } from 'path'; -import { - CompilerPlugin, - CompilerPluginHooks, - TransformerEntry, - TransformerPlugin, -} from './types'; - -export function loadTsTransformers( - plugins: TransformerEntry[], - moduleResolver: typeof require.resolve = require.resolve -): { - compilerPluginHooks: CompilerPluginHooks; - hasPlugin: boolean; -} { - const beforeHooks: CompilerPluginHooks['beforeHooks'] = []; - const afterHooks: CompilerPluginHooks['afterHooks'] = []; - const afterDeclarationsHooks: CompilerPluginHooks['afterDeclarationsHooks'] = - []; - - if (!plugins || !plugins.length) - return { - compilerPluginHooks: { - beforeHooks, - afterHooks, - afterDeclarationsHooks, - }, - hasPlugin: false, - }; - - const normalizedPlugins: TransformerPlugin[] = plugins.map((plugin) => - typeof plugin === 'string' ? { name: plugin, options: {} } : plugin - ); - - const nodeModulePaths = [ - join(process.cwd(), 'node_modules'), - ...module.paths, - ]; - - const pluginRefs: CompilerPlugin[] = normalizedPlugins.map(({ name }) => { - try { - const binaryPath = moduleResolver(name, { - paths: nodeModulePaths, - }); - return require(binaryPath); - } catch (e) { - logger.warn(`"${name}" plugin could not be found!`); - return {}; - } - }); - - for (let i = 0; i < pluginRefs.length; i++) { - const { name: pluginName, options: pluginOptions } = normalizedPlugins[i]; - const { before, after, afterDeclarations } = pluginRefs[i]; - if (!before && !after && !afterDeclarations) { - logger.warn( - `${pluginName} is not a Transformer Plugin. It does not provide neither before(), after(), nor afterDeclarations()` - ); - continue; - } - - if (before) { - beforeHooks.push(before.bind(before, pluginOptions)); - } - - if (after) { - afterHooks.push(after.bind(after, pluginOptions)); - } - - if (afterDeclarations) { - afterDeclarationsHooks.push( - afterDeclarations.bind(afterDeclarations, pluginOptions) - ); - } - } - - return { - compilerPluginHooks: { - beforeHooks, - afterHooks, - afterDeclarationsHooks, - }, - hasPlugin: true, - }; -} diff --git a/packages/node/src/utils/node.config.spec.ts b/packages/node/src/utils/node.config.spec.ts deleted file mode 100644 index a071543b40e07..0000000000000 --- a/packages/node/src/utils/node.config.spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { ProjectGraph, ExecutorContext } from '@nrwl/devkit'; -import { join } from 'path'; -import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin'; - -import { GeneratePackageJsonWebpackPlugin } from './generate-package-json-webpack-plugin'; -import { getNodeWebpackConfig } from './node.config'; -import { BuildNodeBuilderOptions } from './types'; - -jest.mock('tsconfig-paths-webpack-plugin'); -jest.mock('@nrwl/devkit', () => ({ - get workspaceRoot() { - return join(__dirname, '../../../..'); - }, -})); - -describe('getNodePartial', () => { - let context: ExecutorContext; - let projectGraph: ProjectGraph; - let input: BuildNodeBuilderOptions; - beforeEach(() => { - context = { - projectName: 'sample-project', - root: '/root', - cwd: '', - target: {} as any, - isVerbose: false, - workspace: {} as any, - }; - projectGraph = {} as any; - input = { - main: 'main.ts', - outputPath: 'dist', - tsConfig: 'tsconfig.json', - externalDependencies: 'all', - fileReplacements: [], - statsJson: false, - }; - (TsconfigPathsPlugin).mockImplementation( - function MockPathsPlugin() {} - ); - }); - - describe('unconditionally', () => { - it('should target commonjs', () => { - const result = getNodeWebpackConfig(context, projectGraph, input); - expect(result.output.libraryTarget).toEqual('commonjs'); - }); - - it('should target node', () => { - const result = getNodeWebpackConfig(context, projectGraph, input); - - expect(result.target).toEqual('node'); - }); - - it('should not polyfill node apis', () => { - const result = getNodeWebpackConfig(context, projectGraph, input); - - expect(result.node).toEqual(false); - }); - }); - - describe('the optimization option when true', () => { - it('should minify', () => { - const result = getNodeWebpackConfig(context, projectGraph, { - ...input, - optimization: true, - }); - - expect(result.optimization.minimize).toEqual(true); - expect(result.optimization.minimizer).toBeDefined(); - }); - - it('should concatenate modules', () => { - const result = getNodeWebpackConfig(context, projectGraph, { - ...input, - optimization: true, - }); - - expect(result.optimization.concatenateModules).toEqual(true); - }); - }); - - describe('the externalDependencies option', () => { - it('should change all node_modules to commonjs imports', () => { - const result = getNodeWebpackConfig(context, projectGraph, input); - const callback = jest.fn(); - result.externals[0](null, '@nestjs/core', callback); - expect(callback).toHaveBeenCalledWith(null, 'commonjs @nestjs/core'); - }); - - it('should change given module names to commonjs imports but not others', () => { - const result = getNodeWebpackConfig(context, projectGraph, { - ...input, - externalDependencies: ['module1'], - }); - const callback = jest.fn(); - result.externals[0]({ request: 'module1' }, callback); - expect(callback).toHaveBeenCalledWith(null, 'commonjs module1'); - result.externals[0]({ request: '@nestjs/core' }, callback); - expect(callback).toHaveBeenCalledWith(); - }); - - it('should not change any modules to commonjs imports', () => { - const result = getNodeWebpackConfig(context, projectGraph, { - ...input, - externalDependencies: 'none', - }); - - expect(result.externals).not.toBeDefined(); - }); - }); - - describe('the generatePackageJson option', () => { - it('should add the GeneratePackageJsonWebpackPlugin plugin', () => { - const result = getNodeWebpackConfig(context, projectGraph, { - ...input, - generatePackageJson: true, - }); - expect( - result.plugins.find( - (plugin) => plugin instanceof GeneratePackageJsonWebpackPlugin - ) - ).toBeTruthy(); - }); - it('should not add the GeneratePackageJsonWebpackPlugin plugin', () => { - const result = getNodeWebpackConfig(context, projectGraph, { - ...input, - generatePackageJson: false, - }); - expect( - result.plugins.find( - (plugin) => plugin instanceof GeneratePackageJsonWebpackPlugin - ) - ).toBeFalsy(); - }); - }); -}); diff --git a/packages/node/src/utils/node.config.ts b/packages/node/src/utils/node.config.ts deleted file mode 100644 index 5682aae7592ff..0000000000000 --- a/packages/node/src/utils/node.config.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { ExecutorContext, ProjectGraph, workspaceRoot } from '@nrwl/devkit'; -import { Configuration } from 'webpack'; -import { merge } from 'webpack-merge'; - -import { getBaseWebpackPartial } from './config'; -import { GeneratePackageJsonWebpackPlugin } from './generate-package-json-webpack-plugin'; -import { BuildNodeBuilderOptions } from './types'; -import nodeExternals = require('webpack-node-externals'); -import TerserPlugin = require('terser-webpack-plugin'); - -function getNodePartial( - context: ExecutorContext, - projectGraph: ProjectGraph, - options: BuildNodeBuilderOptions -) { - const webpackConfig: Configuration = { - output: { - libraryTarget: 'commonjs', - }, - target: 'node', - node: false, - }; - - if (options.optimization) { - webpackConfig.optimization = { - minimize: true, - minimizer: [ - new TerserPlugin({ - terserOptions: { - mangle: false, - keep_classnames: true, - }, - }), - ], - concatenateModules: true, - }; - } - - if (options.externalDependencies === 'all') { - const modulesDir = `${workspaceRoot}/node_modules`; - webpackConfig.externals = [nodeExternals({ modulesDir })]; - } else if (Array.isArray(options.externalDependencies)) { - webpackConfig.externals = [ - function (context, callback: Function) { - if (options.externalDependencies.includes(context.request)) { - // not bundled - return callback(null, `commonjs ${context.request}`); - } - // bundled - callback(); - }, - ]; - } - - if (options.generatePackageJson) { - webpackConfig.plugins ??= []; - webpackConfig.plugins.push( - new GeneratePackageJsonWebpackPlugin(context, projectGraph, options) - ); - } - - return webpackConfig; -} - -export function getNodeWebpackConfig( - context: ExecutorContext, - projectGraph: ProjectGraph, - options: BuildNodeBuilderOptions -) { - return merge([ - getBaseWebpackPartial(options), - getNodePartial(context, projectGraph, options), - ]); -} diff --git a/packages/node/src/utils/normalize.spec.ts b/packages/node/src/utils/normalize.spec.ts deleted file mode 100644 index 996f7b08e1fac..0000000000000 --- a/packages/node/src/utils/normalize.spec.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { normalizeBuildOptions } from './normalize'; -import { BuildNodeBuilderOptions } from './types'; - -import * as fs from 'fs'; - -describe('normalizeBuildOptions', () => { - let testOptions: BuildNodeBuilderOptions; - let root: string; - let sourceRoot: string; - let projectRoot: string; - - beforeEach(() => { - testOptions = { - main: 'apps/nodeapp/src/main.ts', - tsConfig: 'apps/nodeapp/tsconfig.app.json', - outputPath: 'dist/apps/nodeapp', - fileReplacements: [ - { - replace: 'apps/environment/environment.ts', - with: 'apps/environment/environment.prod.ts', - }, - { - replace: 'module1.ts', - with: 'module2.ts', - }, - ], - assets: [], - statsJson: false, - externalDependencies: 'all', - }; - root = '/root'; - sourceRoot = 'apps/nodeapp/src'; - projectRoot = 'apps/nodeapp'; - }); - it('should add the root', () => { - const result = normalizeBuildOptions( - testOptions, - root, - sourceRoot, - projectRoot - ); - expect(result.root).toEqual('/root'); - }); - - it('should resolve main from root', () => { - const result = normalizeBuildOptions( - testOptions, - root, - sourceRoot, - projectRoot - ); - expect(result.main).toEqual('/root/apps/nodeapp/src/main.ts'); - }); - - it('should resolve additional entries from root', () => { - const result = normalizeBuildOptions( - { - ...testOptions, - additionalEntryPoints: [ - { entryName: 'test', entryPath: 'some/path.ts' }, - ], - }, - root, - sourceRoot, - projectRoot - ); - expect(result.additionalEntryPoints[0].entryPath).toEqual( - '/root/some/path.ts' - ); - }); - - it('should resolve the output path', () => { - const result = normalizeBuildOptions( - testOptions, - root, - sourceRoot, - projectRoot - ); - expect(result.outputPath).toEqual('/root/dist/apps/nodeapp'); - }); - - it('should resolve the tsConfig path', () => { - const result = normalizeBuildOptions( - testOptions, - root, - sourceRoot, - projectRoot - ); - expect(result.tsConfig).toEqual('/root/apps/nodeapp/tsconfig.app.json'); - }); - - it('should normalize asset patterns', () => { - jest.spyOn(fs, 'statSync').mockReturnValue({ - isDirectory: () => true, - } as any); - const result = normalizeBuildOptions( - { - ...testOptions, - root, - assets: [ - 'apps/nodeapp/src/assets', - { - input: 'outsideproj', - output: 'output', - glob: '**/*', - ignore: ['**/*.json'], - }, - ], - }, - root, - sourceRoot, - projectRoot - ); - expect(result.assets).toEqual([ - { - input: '/root/apps/nodeapp/src/assets', - output: 'assets', - glob: '**/*', - }, - { - input: '/root/outsideproj', - output: 'output', - glob: '**/*', - ignore: ['**/*.json'], - }, - ]); - }); - - it('should resolve the file replacement paths', () => { - const result = normalizeBuildOptions( - testOptions, - root, - sourceRoot, - projectRoot - ); - expect(result.fileReplacements).toEqual([ - { - replace: '/root/apps/environment/environment.ts', - with: '/root/apps/environment/environment.prod.ts', - }, - { - replace: '/root/module1.ts', - with: '/root/module2.ts', - }, - ]); - }); - - it('should resolve outputFileName correctly', () => { - const result = normalizeBuildOptions( - testOptions, - root, - sourceRoot, - projectRoot - ); - expect(result.outputFileName).toEqual('main.js'); - }); - - it('should resolve outputFileName to "main.js" if not passed in', () => { - const result = normalizeBuildOptions( - { ...testOptions, outputFileName: 'index.js' }, - root, - sourceRoot, - projectRoot - ); - expect(result.outputFileName).toEqual('index.js'); - }); -}); diff --git a/packages/node/src/utils/types.ts b/packages/node/src/utils/types.ts deleted file mode 100644 index a7c98b3a60e09..0000000000000 --- a/packages/node/src/utils/types.ts +++ /dev/null @@ -1,114 +0,0 @@ -import type { - CustomTransformerFactory, - Node, - Program, - TransformerFactory as TypescriptTransformerFactory, -} from 'typescript'; - -export interface FileReplacement { - replace: string; - with: string; -} - -export interface OptimizationOptions { - scripts: boolean; - styles: boolean; -} - -export interface SourceMapOptions { - scripts: boolean; - styles: boolean; - vendors: boolean; - hidden: boolean; -} - -type TransformerFactory = - | TypescriptTransformerFactory - | CustomTransformerFactory; - -export interface TransformerPlugin { - name: string; - options: Record; -} - -export type TransformerEntry = string | TransformerPlugin; - -export interface CompilerPlugin { - before?: ( - options?: Record, - program?: Program - ) => TransformerFactory; - after?: ( - options?: Record, - program?: Program - ) => TransformerFactory; - afterDeclarations?: ( - options?: Record, - program?: Program - ) => TransformerFactory; -} - -export interface CompilerPluginHooks { - beforeHooks: Array<(program?: Program) => TransformerFactory>; - afterHooks: Array<(program?: Program) => TransformerFactory>; - afterDeclarationsHooks: Array<(program?: Program) => TransformerFactory>; -} - -export interface AdditionalEntryPoint { - entryName: string; - entryPath: string; -} - -export interface WebpackWatchOptions { - aggregateTimeout?: number; - ignored?: Array | string; - poll?: number; - followSymlinks?: boolean; - stdin?: boolean; -} - -export interface BuildBuilderOptions { - main: string; - outputPath: string; - tsConfig: string; - watch?: boolean; - watchOptions?: WebpackWatchOptions; - sourceMap?: boolean | SourceMapOptions; - optimization?: boolean | OptimizationOptions; - maxWorkers?: number; - memoryLimit?: number; - poll?: number; - - fileReplacements: FileReplacement[]; - assets?: any[]; - - progress?: boolean; - statsJson?: boolean; - extractLicenses?: boolean; - verbose?: boolean; - - webpackConfig?: string | string[]; - - root?: string; - sourceRoot?: string; - projectRoot?: string; - - transformers?: TransformerEntry[]; - - additionalEntryPoints?: AdditionalEntryPoint[]; - outputFileName?: string; -} - -export interface BuildNodeBuilderOptions extends BuildBuilderOptions { - optimization?: boolean; - sourceMap?: boolean; - externalDependencies: 'all' | 'none' | string[]; - buildLibsFromSource?: boolean; - generatePackageJson?: boolean; - deleteOutputPath?: boolean; -} - -export interface NormalizedBuildNodeBuilderOptions - extends BuildNodeBuilderOptions { - webpackConfig: string[]; -} diff --git a/packages/react/package.json b/packages/react/package.json index fc816a40b924a..ed508d012d088 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -39,6 +39,7 @@ "@nrwl/linter": "file:../linter", "@nrwl/storybook": "file:../storybook", "@nrwl/web": "file:../web", + "@nrwl/webpack": "file:../webpack", "@nrwl/workspace": "file:../workspace", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", "@svgr/webpack": "^6.1.2", diff --git a/packages/react/plugins/component-testing/index.ts b/packages/react/plugins/component-testing/index.ts index 4b6f1d4c025ab..23b2518d40162 100644 --- a/packages/react/plugins/component-testing/index.ts +++ b/packages/react/plugins/component-testing/index.ts @@ -16,9 +16,9 @@ import { Target, workspaceRoot, } from '@nrwl/devkit'; -import type { WebWebpackExecutorOptions } from '@nrwl/web/src/executors/webpack/webpack.impl'; -import { normalizeWebBuildOptions } from '@nrwl/web/src/utils/normalize'; -import { getWebConfig } from '@nrwl/web/src/utils/web.config'; +import type { WebpackExecutorOptions } from '@nrwl/webpack/src/executors/webpack/schema'; +import { normalizeOptions } from '@nrwl/webpack/src/executors/webpack/lib/normalize-options'; +import { getWebpackConfig } from '@nrwl/webpack/src/executors/webpack/lib/get-webpack-config'; import { buildBaseWebpackConfig } from './webpack-fallback'; /** @@ -108,8 +108,8 @@ export function nxComponentTestingPreset( function withSchemaDefaults( target: Target, context: ExecutorContext -): WebWebpackExecutorOptions { - const options = readTargetOptions(target, context); +): WebpackExecutorOptions { + const options = readTargetOptions(target, context); options.compiler ??= 'babel'; options.deleteOutputPath ??= true; @@ -149,18 +149,16 @@ function buildTargetWebpack( Has component config? ${!!ctProjectConfig} `); } + const context = createExecutorContext( + graph, + buildableProjectConfig.targets, + parsed.project, + parsed.target, + parsed.target + ); - const options = normalizeWebBuildOptions( - withSchemaDefaults( - parsed, - createExecutorContext( - graph, - buildableProjectConfig.targets, - parsed.project, - parsed.target, - parsed.target - ) - ), + const options = normalizeOptions( + withSchemaDefaults(parsed, context), workspaceRoot, buildableProjectConfig.sourceRoot! ); @@ -171,13 +169,10 @@ function buildTargetWebpack( : options.optimization && options.optimization.scripts ? options.optimization.scripts : false; - return getWebConfig( - workspaceRoot, - ctProjectConfig.root, - ctProjectConfig.sourceRoot, - options, - true, - isScriptOptimizeOn, - parsed.configuration - ); + + return getWebpackConfig(context, options, true, isScriptOptimizeOn, { + root: ctProjectConfig.root, + sourceRoot: ctProjectConfig.sourceRoot, + configuration: parsed.configuration, + }); } diff --git a/packages/react/plugins/component-testing/webpack-fallback.ts b/packages/react/plugins/component-testing/webpack-fallback.ts index adba6fd7a1386..69e0c84ec3d67 100644 --- a/packages/react/plugins/component-testing/webpack-fallback.ts +++ b/packages/react/plugins/component-testing/webpack-fallback.ts @@ -1,4 +1,4 @@ -import { getCSSModuleLocalIdent } from '@nrwl/web/src/utils/web.config'; +import { getCSSModuleLocalIdent } from '@nrwl/webpack/src/executors/webpack/lib/get-webpack-config'; import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin'; import { Configuration } from 'webpack'; diff --git a/packages/react/plugins/storybook/index.spec.ts b/packages/react/plugins/storybook/index.spec.ts index e008ef4c1412a..03afb45727e6e 100644 --- a/packages/react/plugins/storybook/index.spec.ts +++ b/packages/react/plugins/storybook/index.spec.ts @@ -1,7 +1,7 @@ import { webpack } from './index'; import { join } from 'path'; -jest.mock('@nrwl/web/src/utils/web.config', () => { +jest.mock('@nrwl/webpack/src/executors/webpack/lib/get-webpack-config', () => { return { getStylesPartial: () => ({}), }; diff --git a/packages/react/plugins/storybook/index.ts b/packages/react/plugins/storybook/index.ts index 51820a9c43e4d..7aebbe6961a2e 100644 --- a/packages/react/plugins/storybook/index.ts +++ b/packages/react/plugins/storybook/index.ts @@ -1,17 +1,22 @@ import { + ExecutorContext, joinPathFragments, - readJsonFile, logger, + ProjectGraph, + readJsonFile, + readNxJson, + TargetConfiguration, workspaceRoot, } from '@nrwl/devkit'; -import { getBaseWebpackPartial } from '@nrwl/web/src/utils/config'; -import { getStylesPartial } from '@nrwl/web/src/utils/web.config'; +import { getBaseWebpackPartial } from '@nrwl/webpack/src/utils/config'; +import { getStylesPartial } from '@nrwl/webpack/src/executors/webpack/lib/get-webpack-config'; import { checkAndCleanWithSemver } from '@nrwl/workspace/src/utilities/version-utils'; import { join } from 'path'; import { gte } from 'semver'; -import { Configuration, WebpackPluginInstance, DefinePlugin } from 'webpack'; +import { Configuration, DefinePlugin, WebpackPluginInstance } from 'webpack'; import * as mergeWebpack from 'webpack-merge'; import { mergePlugins } from './merge-plugins'; +import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph'; const reactWebpackConfig = require('../webpack'); diff --git a/packages/react/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts b/packages/react/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts index 68766a62d671e..5d953a1776cd6 100644 --- a/packages/react/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts +++ b/packages/react/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts @@ -1,7 +1,6 @@ import { ExecutorContext, logger, runExecutor } from '@nrwl/devkit'; -import devServerExecutor, { - WebDevServerOptions, -} from '@nrwl/web/src/executors/dev-server/dev-server.impl'; +import devServerExecutor from '@nrwl/webpack/src/executors/dev-server/dev-server.impl'; +import { WebDevServerOptions } from '@nrwl/webpack/src/executors/dev-server/schema'; import { join } from 'path'; import { combineAsyncIterators, diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index c71e312e30e11..b7b70a5bf2223 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -324,7 +324,7 @@ describe('app', () => { const workspaceJson = getProjects(appTree); const targetConfig = workspaceJson.get('my-app').targets; - expect(targetConfig.build.executor).toEqual('@nrwl/web:webpack'); + expect(targetConfig.build.executor).toEqual('@nrwl/webpack:webpack'); expect(targetConfig.build.outputs).toEqual(['{options.outputPath}']); expect(targetConfig.build.options).toEqual({ compiler: 'babel', @@ -360,7 +360,7 @@ describe('app', () => { const workspaceJson = getProjects(appTree); const targetConfig = workspaceJson.get('my-app').targets; - expect(targetConfig.serve.executor).toEqual('@nrwl/web:dev-server'); + expect(targetConfig.serve.executor).toEqual('@nrwl/webpack:dev-server'); expect(targetConfig.serve.options).toEqual({ buildTarget: 'my-app:build', hmr: true, diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts index 33aa01a8ad771..cac103cf85ec7 100644 --- a/packages/react/src/generators/application/application.ts +++ b/packages/react/src/generators/application/application.ts @@ -25,7 +25,7 @@ import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-ser import reactInitGenerator from '../init/init'; import { lintProjectGenerator } from '@nrwl/linter'; import { swcCoreVersion } from '@nrwl/js/src/utils/versions'; -import { swcLoaderVersion } from '@nrwl/web/src/utils/versions'; +import { swcLoaderVersion } from '@nrwl/webpack/src/utils/versions'; async function addLinting(host: Tree, options: NormalizedSchema) { const tasks: GeneratorCallback[] = []; diff --git a/packages/react/src/generators/application/lib/add-project.ts b/packages/react/src/generators/application/lib/add-project.ts index 1cca6fe0d7c1a..c93658f01f624 100644 --- a/packages/react/src/generators/application/lib/add-project.ts +++ b/packages/react/src/generators/application/lib/add-project.ts @@ -36,7 +36,7 @@ function maybeJs(options: NormalizedSchema, path: string): string { function createBuildTarget(options: NormalizedSchema): TargetConfiguration { return { - executor: '@nrwl/web:webpack', + executor: '@nrwl/webpack:webpack', outputs: ['{options.outputPath}'], defaultConfiguration: 'production', options: { @@ -102,7 +102,7 @@ function createBuildTarget(options: NormalizedSchema): TargetConfiguration { function createServeTarget(options: NormalizedSchema): TargetConfiguration { return { - executor: '@nrwl/web:dev-server', + executor: '@nrwl/webpack:dev-server', defaultConfiguration: 'development', options: { buildTarget: `${options.projectName}:build`, diff --git a/packages/react/src/generators/cypress-component-configuration/lib/update-configs.ts b/packages/react/src/generators/cypress-component-configuration/lib/update-configs.ts index 6e6c56bfb4458..f1484aec62341 100644 --- a/packages/react/src/generators/cypress-component-configuration/lib/update-configs.ts +++ b/packages/react/src/generators/cypress-component-configuration/lib/update-configs.ts @@ -16,7 +16,7 @@ export async function updateProjectConfig( const found = await findBuildConfig(tree, { project: options.project, buildTarget: options.buildTarget, - validExecutorNames: new Set(['@nrwl/web:webpack']), + validExecutorNames: new Set(['@nrwl/webpack:webpack']), }); assetValidConfig(found.config); diff --git a/packages/react/src/generators/setup-tailwind/lib/update-project.ts b/packages/react/src/generators/setup-tailwind/lib/update-project.ts index aaddbc3171f8e..e1032f5909bef 100644 --- a/packages/react/src/generators/setup-tailwind/lib/update-project.ts +++ b/packages/react/src/generators/setup-tailwind/lib/update-project.ts @@ -8,7 +8,7 @@ export function updateProject( config: ProjectConfiguration, options: SetupTailwindOptions ) { - if (config?.targets?.build?.executor === '@nrwl/web:webpack') { + if (config?.targets?.build?.executor === '@nrwl/webpack:webpack') { config.targets.build.options ??= {}; config.targets.build.options.postcssConfig = joinPathFragments( config.root, diff --git a/packages/react/src/generators/setup-tailwind/setup-tailwind.spec.ts b/packages/react/src/generators/setup-tailwind/setup-tailwind.spec.ts index 6ef4d15867282..370d52cb61cbf 100644 --- a/packages/react/src/generators/setup-tailwind/setup-tailwind.spec.ts +++ b/packages/react/src/generators/setup-tailwind/setup-tailwind.spec.ts @@ -49,7 +49,7 @@ describe('setup-tailwind', () => { sourceRoot: 'apps/example/src', targets: { build: { - executor: '@nrwl/web:webpack', + executor: '@nrwl/webpack:webpack', options: {}, }, }, diff --git a/packages/react/src/migrations/update-14-1-0/update-external-emotion-jsx-runtime.ts b/packages/react/src/migrations/update-14-1-0/update-external-emotion-jsx-runtime.ts index cdb281943340f..b50a02c432e3a 100644 --- a/packages/react/src/migrations/update-14-1-0/update-external-emotion-jsx-runtime.ts +++ b/packages/react/src/migrations/update-14-1-0/update-external-emotion-jsx-runtime.ts @@ -1,13 +1,12 @@ import { - Tree, readProjectConfiguration, + Tree, updateProjectConfiguration, } from '@nrwl/devkit'; -import { WebRollupOptions } from '@nrwl/web/src/executors/rollup/schema'; import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils'; export async function updateExternalEmotionJsxRuntime(tree: Tree) { - forEachExecutorOptions( + forEachExecutorOptions( tree, '@nrwl/web:rollup', (options: any, projectName, targetName, configurationName) => { diff --git a/packages/web/migrations.json b/packages/web/migrations.json index ea90bd99ac8cc..a821933a09ba4 100644 --- a/packages/web/migrations.json +++ b/packages/web/migrations.json @@ -62,6 +62,12 @@ "version": "13.8.0-beta.1", "description": "Add a postcss config option to apps to load a single config file for all libs", "factory": "./src/migrations/update-13-8-0/add-postcss-config-option" + }, + "update-webpack-executor": { + "cli": "nx", + "version": "14.7.5-beta.1", + "description": "Update usages of webpack executors to @nrwl/webpack", + "factory": "./src/migrations/update-14-7-5/update-webpack-executor" } }, "packageJsonUpdates": { diff --git a/packages/web/package.json b/packages/web/package.json index b25e7269f721a..c6fdf7b619eac 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -42,46 +42,28 @@ "@nrwl/jest": "file:../jest", "@nrwl/js": "file:../js", "@nrwl/linter": "file:../linter", + "@nrwl/webpack": "file:../webpack", "@nrwl/workspace": "file:../workspace", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-commonjs": "^20.0.0", "@rollup/plugin-image": "^2.1.0", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^13.0.4", - "autoprefixer": "^10.4.7", - "babel-loader": "^8.2.2", + "autoprefixer": "^10.4.9", "babel-plugin-const-enum": "^1.0.1", "babel-plugin-macros": "^2.8.0", "babel-plugin-transform-async-to-promises": "^0.8.15", "babel-plugin-transform-typescript-metadata": "^0.3.1", - "browserslist": "^4.16.6", "bytes": "^3.1.0", - "caniuse-lite": "^1.0.30001251", "chalk": "4.1.0", "chokidar": "^3.5.1", - "copy-webpack-plugin": "^10.2.4", "core-js": "^3.6.5", - "css-loader": "^6.4.0", - "css-minimizer-webpack-plugin": "^3.4.1", - "enhanced-resolve": "^5.8.3", - "file-loader": "^6.2.0", - "fork-ts-checker-webpack-plugin": "7.2.13", "fs-extra": "^10.1.0", "http-server": "14.1.0", - "identity-obj-proxy": "3.0.0", "ignore": "^5.0.4", "less": "3.12.2", - "less-loader": "^10.1.0", - "license-webpack-plugin": "^4.0.2", - "loader-utils": "1.2.3", - "mini-css-extract-plugin": "~2.4.7", - "parse5": "4.0.0", - "parse5-html-rewriting-stream": "6.0.1", "postcss": "^8.4.14", "postcss-import": "~14.1.0", - "postcss-loader": "^6.1.1", - "raw-loader": "^4.0.2", "react-refresh": "^0.10.0", "rollup": "^2.56.2", "rollup-plugin-copy": "^3.4.0", @@ -90,23 +72,11 @@ "rollup-plugin-typescript2": "^0.31.1", "rxjs": "^6.5.4", "sass": "^1.42.1", - "sass-loader": "^12.2.0", "semver": "7.3.4", "source-map": "0.7.3", - "source-map-loader": "^3.0.0", - "style-loader": "^3.3.0", "stylus": "^0.55.0", - "stylus-loader": "^6.2.0", - "terser-webpack-plugin": "^5.3.3", - "ts-loader": "^9.3.1", "ts-node": "10.9.1", "tsconfig-paths": "^3.9.0", - "tsconfig-paths-webpack-plugin": "3.5.2", - "tslib": "^2.3.0", - "webpack": "^5.58.1", - "webpack-dev-server": "^4.9.3", - "webpack-merge": "^5.8.0", - "webpack-sources": "^3.2.3", - "webpack-subresource-integrity": "^5.1.0" + "tslib": "^2.3.0" } } diff --git a/packages/web/src/executors/dev-server/dev-server.impl.ts b/packages/web/src/executors/dev-server/dev-server.impl.ts index 0af5e399260cc..37e404ae7e061 100644 --- a/packages/web/src/executors/dev-server/dev-server.impl.ts +++ b/packages/web/src/executors/dev-server/dev-server.impl.ts @@ -1,134 +1,8 @@ -import * as webpack from 'webpack'; -import { - ExecutorContext, - parseTargetString, - readTargetOptions, -} from '@nrwl/devkit'; - -import { eachValueFrom } from '@nrwl/devkit/src/utils/rxjs-for-await'; -import { map, tap } from 'rxjs/operators'; -import * as WebpackDevServer from 'webpack-dev-server'; - -import { normalizeWebBuildOptions } from '../../utils/normalize'; -import { WebWebpackExecutorOptions } from '../webpack/webpack.impl'; -import { getDevServerConfig } from '../../utils/devserver.config'; -import { - calculateProjectDependencies, - createTmpTsConfig, -} from '@nrwl/workspace/src/utilities/buildable-libs-utils'; -import { getEmittedFiles, runWebpackDevServer } from '../../utils/run-webpack'; -import { resolveCustomWebpackConfig } from '../../utils/webpack/custom-webpack'; - -export interface WebDevServerOptions { - host: string; - port: number; - publicHost?: string; - ssl: boolean; - sslKey?: string; - sslCert?: string; - proxyConfig?: string; - buildTarget: string; - open: boolean; - liveReload: boolean; - hmr: boolean; - watch: boolean; - allowedHosts: string; - maxWorkers?: number; - memoryLimit?: number; - baseHref?: string; -} - -export default async function* devServerExecutor( - serveOptions: WebDevServerOptions, - context: ExecutorContext -) { - const { root: projectRoot, sourceRoot } = - context.workspace.projects[context.projectName]; - const buildOptions = normalizeWebBuildOptions( - getBuildOptions(serveOptions, context), - context.root, - sourceRoot - ); - - if (!buildOptions.buildLibsFromSource) { - const { target, dependencies } = calculateProjectDependencies( - context.projectGraph, - context.root, - context.projectName, - 'build', // should be generalized - context.configurationName - ); - buildOptions.tsConfig = createTmpTsConfig( - buildOptions.tsConfig, - context.root, - target.data.root, - dependencies - ); - } - - let webpackConfig = getDevServerConfig( - context.root, - projectRoot, - sourceRoot, - buildOptions, - serveOptions - ); - - if (buildOptions.webpackConfig) { - let customWebpack = resolveCustomWebpackConfig( - buildOptions.webpackConfig, - buildOptions.tsConfig - ); - - if (typeof customWebpack.then === 'function') { - customWebpack = await customWebpack; - } - - webpackConfig = await customWebpack(webpackConfig, { - buildOptions, - configuration: serveOptions.buildTarget.split(':')[2], - }); - } - - return yield* eachValueFrom( - runWebpackDevServer(webpackConfig, webpack, WebpackDevServer).pipe( - tap(({ stats }) => { - console.info(stats.toString((webpackConfig as any).stats)); - }), - map(({ baseUrl, stats }) => { - return { - baseUrl, - emittedFiles: getEmittedFiles(stats), - success: !stats.hasErrors(), - }; - }) - ) - ); -} - -function getBuildOptions( - options: WebDevServerOptions, - context: ExecutorContext -): WebWebpackExecutorOptions { - const target = parseTargetString(options.buildTarget); - - const overrides: Partial = { - watch: false, - }; - if (options.maxWorkers) { - overrides.maxWorkers = options.maxWorkers; - } - if (options.memoryLimit) { - overrides.memoryLimit = options.memoryLimit; - } - if (options.baseHref) { - overrides.baseHref = options.baseHref; - } - - const buildOptions = readTargetOptions(target, context); - - return { - ...buildOptions, - ...overrides, - }; -} +/** + * This is here for backwards-compat. + * TODO(jack): remove in Nx 16. + */ +import { devServerExecutor, WebDevServerOptions } from '@nrwl/webpack'; + +export { devServerExecutor, WebDevServerOptions }; +export default devServerExecutor; diff --git a/packages/node/src/utils/fs.ts b/packages/web/src/executors/rollup/lib/delete-output-dir.ts similarity index 100% rename from packages/node/src/utils/fs.ts rename to packages/web/src/executors/rollup/lib/delete-output-dir.ts diff --git a/packages/web/src/executors/rollup/lib/normalize.ts b/packages/web/src/executors/rollup/lib/normalize.ts index ec003057a2865..e4cfb067d9a52 100644 --- a/packages/web/src/executors/rollup/lib/normalize.ts +++ b/packages/web/src/executors/rollup/lib/normalize.ts @@ -1,7 +1,7 @@ import { dirname } from 'path'; -import { AssetGlobPattern } from '../../../utils/shared-models'; -import { normalizeAssets, normalizePluginPath } from '../../../utils/normalize'; +import { AssetGlobPattern } from '@nrwl/webpack'; +import { normalizeAssets, normalizePluginPath } from '@nrwl/webpack'; import { WebRollupOptions } from '../schema'; export interface NormalizedWebRollupOptions extends WebRollupOptions { diff --git a/packages/web/src/executors/rollup/rollup.impl.ts b/packages/web/src/executors/rollup/rollup.impl.ts index d86c080de8615..6a5ba2366e0d3 100644 --- a/packages/web/src/executors/rollup/rollup.impl.ts +++ b/packages/web/src/executors/rollup/rollup.impl.ts @@ -15,7 +15,7 @@ import { } from '@nrwl/workspace/src/utilities/buildable-libs-utils'; import resolve from '@rollup/plugin-node-resolve'; -import { AssetGlobPattern } from '../../utils/shared-models'; +import { AssetGlobPattern } from '@nrwl/webpack'; import { WebRollupOptions } from './schema'; import { runRollup } from './lib/run-rollup'; import { diff --git a/packages/web/src/executors/webpack/compat.ts b/packages/web/src/executors/webpack/compat.ts index 177aa84d07095..919858b9b3dd7 100644 --- a/packages/web/src/executors/webpack/compat.ts +++ b/packages/web/src/executors/webpack/compat.ts @@ -1,5 +1,5 @@ import { convertNxExecutor } from '@nrwl/devkit'; -import { run } from './webpack.impl'; +import { webpackExecutor } from './webpack.impl'; -export default convertNxExecutor(run); +export default convertNxExecutor(webpackExecutor); diff --git a/packages/web/src/executors/webpack/webpack.impl.ts b/packages/web/src/executors/webpack/webpack.impl.ts index a3878183d5c6c..34c886454e329 100644 --- a/packages/web/src/executors/webpack/webpack.impl.ts +++ b/packages/web/src/executors/webpack/webpack.impl.ts @@ -1,249 +1,11 @@ -import { ExecutorContext, logger } from '@nrwl/devkit'; -import { eachValueFrom } from '@nrwl/devkit/src/utils/rxjs-for-await'; -import type { Configuration, Stats } from 'webpack'; -import { from, of } from 'rxjs'; +/** + * This is here for backwards-compat. + * TODO(jack): remove in Nx 16. + */ import { - bufferCount, - mergeMap, - mergeScan, - switchMap, - tap, -} from 'rxjs/operators'; -import { execSync } from 'child_process'; -import { Range, satisfies } from 'semver'; -import { basename, join } from 'path'; -import { - calculateProjectDependencies, - createTmpTsConfig, -} from '@nrwl/workspace/src/utilities/buildable-libs-utils'; -import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript'; - -import { normalizeWebBuildOptions } from '../../utils/normalize'; -import { getWebConfig } from '../../utils/web.config'; -import type { BuildBuilderOptions } from '../../utils/shared-models'; -import { ExtraEntryPoint } from '../../utils/shared-models'; -import { getEmittedFiles, runWebpack } from '../../utils/run-webpack'; -import { BuildBrowserFeatures } from '../../utils/webpack/build-browser-features'; -import { deleteOutputDir } from '../../utils/fs'; -import { - CrossOriginValue, - writeIndexHtml, -} from '../../utils/webpack/write-index-html'; -import { resolveCustomWebpackConfig } from '../../utils/webpack/custom-webpack'; - -export interface WebWebpackExecutorOptions extends BuildBuilderOptions { - index: string; - budgets?: any[]; - baseHref?: string; - deployUrl?: string; - - crossOrigin?: CrossOriginValue; - - polyfills?: string; - es2015Polyfills?: string; - - scripts: ExtraEntryPoint[]; - styles: ExtraEntryPoint[]; - - vendorChunk?: boolean; - commonChunk?: boolean; - runtimeChunk?: boolean; - - namedChunks?: boolean; - - stylePreprocessorOptions?: any; - subresourceIntegrity?: boolean; - - verbose?: boolean; - buildLibsFromSource?: boolean; - - deleteOutputPath?: boolean; - - generateIndexHtml?: boolean; - - postcssConfig?: string; - - extractCss?: boolean; -} - -async function getWebpackConfigs( - options: WebWebpackExecutorOptions, - context: ExecutorContext -): Promise { - const metadata = context.workspace.projects[context.projectName]; - const sourceRoot = metadata.sourceRoot; - const projectRoot = metadata.root; - options = normalizeWebBuildOptions(options, context.root, sourceRoot); - const isScriptOptimizeOn = - typeof options.optimization === 'boolean' - ? options.optimization - : options.optimization && options.optimization.scripts - ? options.optimization.scripts - : false; - const tsConfig = readTsConfig(options.tsConfig); - const scriptTarget = tsConfig.options.target; - - const buildBrowserFeatures = new BuildBrowserFeatures( - projectRoot, - scriptTarget - ); - - let customWebpack = null; - - if (options.webpackConfig) { - customWebpack = resolveCustomWebpackConfig( - options.webpackConfig, - options.tsConfig - ); - - if (typeof customWebpack.then === 'function') { - customWebpack = await customWebpack; - } - } - - return await Promise.all( - [ - // ESM build for modern browsers. - getWebConfig( - context.root, - projectRoot, - sourceRoot, - options, - true, - isScriptOptimizeOn, - context.configurationName - ), - // ES5 build for legacy browsers. - isScriptOptimizeOn && buildBrowserFeatures.isDifferentialLoadingNeeded() - ? getWebConfig( - context.root, - projectRoot, - sourceRoot, - options, - false, - isScriptOptimizeOn, - context.configurationName - ) - : undefined, - ] - .filter(Boolean) - .map(async (config) => { - if (customWebpack) { - return await customWebpack(config, { - options, - configuration: context.configurationName, - }); - } else { - return config; - } - }) - ); -} - -export async function* run( - options: WebWebpackExecutorOptions, - context: ExecutorContext -) { - // Node versions 12.2-12.8 has a bug where prod builds will hang for 2-3 minutes - // after the program exits. - const nodeVersion = execSync(`node --version`).toString('utf-8').trim(); - const supportedRange = new Range('10 || >=12.9'); - if (!satisfies(nodeVersion, supportedRange)) { - throw new Error( - `Node version ${nodeVersion} is not supported. Supported range is "${supportedRange.raw}".` - ); - } - - const isScriptOptimizeOn = - typeof options.optimization === 'boolean' - ? options.optimization - : options.optimization && options.optimization.scripts - ? options.optimization.scripts - : false; - - process.env.NODE_ENV ||= isScriptOptimizeOn ? 'production' : 'development'; - - const metadata = context.workspace.projects[context.projectName]; - - if (options.compiler === 'swc') { - try { - require.resolve('swc-loader'); - require.resolve('@swc/core'); - } catch { - logger.error( - `Missing SWC dependencies: @swc/core, swc-loader. Make sure you install them first.` - ); - return { success: false }; - } - } - - if (!options.buildLibsFromSource && context.targetName) { - const { dependencies } = calculateProjectDependencies( - context.projectGraph, - context.root, - context.projectName, - context.targetName, - context.configurationName - ); - options.tsConfig = createTmpTsConfig( - join(context.root, options.tsConfig), - context.root, - metadata.root, - dependencies - ); - } - - // Delete output path before bundling - if (options.deleteOutputPath) { - deleteOutputDir(context.root, options.outputPath); - } - - const configs = await getWebpackConfigs(options, context); - return yield* eachValueFrom( - from(configs).pipe( - mergeMap((config) => (Array.isArray(config) ? from(config) : of(config))), - // Run build sequentially and bail when first one fails. - mergeScan( - (acc, config) => { - if (!acc.hasErrors()) { - return runWebpack(config).pipe( - tap((stats) => { - console.info(stats.toString(config.stats)); - }) - ); - } else { - return of(); - } - }, - { hasErrors: () => false } as Stats, - 1 - ), - // Collect build results as an array. - bufferCount(configs.length), - switchMap(async ([result1, result2]) => { - const success = - result1 && !result1.hasErrors() && (!result2 || !result2.hasErrors()); - const emittedFiles1 = getEmittedFiles(result1); - const emittedFiles2 = result2 ? getEmittedFiles(result2) : []; - if (options.generateIndexHtml) { - await writeIndexHtml({ - crossOrigin: options.crossOrigin, - sri: options.subresourceIntegrity, - outputPath: join(options.outputPath, basename(options.index)), - indexPath: join(context.root, options.index), - files: emittedFiles1.filter((x) => x.extension === '.css'), - noModuleFiles: emittedFiles2, - moduleFiles: emittedFiles1, - baseHref: options.baseHref, - deployUrl: options.deployUrl, - scripts: options.scripts, - styles: options.styles, - }); - } - return { success, emittedFiles: [...emittedFiles1, ...emittedFiles2] }; - }) - ) - ); -} + webpackExecutor, + WebpackExecutorOptions as WebWebpackExecutorOptions, +} from '@nrwl/webpack'; -export default run; +export { webpackExecutor, WebWebpackExecutorOptions }; +export default webpackExecutor; diff --git a/packages/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts index 5666829fc54bd..ec74bd782f183 100644 --- a/packages/web/src/generators/application/application.spec.ts +++ b/packages/web/src/generators/application/application.spec.ts @@ -299,7 +299,7 @@ describe('app', () => { }); const workspaceJson = readJson(tree, 'workspace.json'); const architectConfig = workspaceJson.projects['my-app'].architect; - expect(architectConfig.build.builder).toEqual('@nrwl/web:webpack'); + expect(architectConfig.build.builder).toEqual('@nrwl/webpack:webpack'); expect(architectConfig.build.outputs).toEqual(['{options.outputPath}']); expect(architectConfig.build.options).toEqual({ compiler: 'babel', @@ -336,7 +336,7 @@ describe('app', () => { }); const workspaceJson = readJson(tree, 'workspace.json'); const architectConfig = workspaceJson.projects['my-app'].architect; - expect(architectConfig.serve.builder).toEqual('@nrwl/web:dev-server'); + expect(architectConfig.serve.builder).toEqual('@nrwl/webpack:dev-server'); expect(architectConfig.serve.options).toEqual({ buildTarget: 'my-app:build', }); diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index f2f7b3fe3a07b..96d8318a4ef9d 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -1,3 +1,5 @@ +import { join } from 'path'; +import { webpackProjectGenerator } from '@nrwl/webpack'; import { cypressProjectGenerator } from '@nrwl/cypress'; import { addDependenciesToPackageJson, @@ -10,10 +12,11 @@ import { joinPathFragments, names, offsetFromRoot, - ProjectConfiguration, + readProjectConfiguration, readWorkspaceConfiguration, TargetConfiguration, Tree, + updateProjectConfiguration, updateWorkspaceConfiguration, } from '@nrwl/devkit'; import { jestProjectGenerator } from '@nrwl/jest'; @@ -22,11 +25,7 @@ import { Linter, lintProjectGenerator } from '@nrwl/linter'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript'; -import { join } from 'path'; - -import { WebWebpackExecutorOptions } from '../../executors/webpack/webpack.impl'; import { swcLoaderVersion } from '../../utils/versions'; - import { webInitGenerator } from '../init/init'; import { Schema } from './schema'; @@ -54,29 +53,43 @@ function createApplicationFiles(tree: Tree, options: NormalizedSchema) { } } -function addBuildTarget( - project: ProjectConfiguration, - options: NormalizedSchema -): ProjectConfiguration { - const buildOptions: WebWebpackExecutorOptions = { - outputPath: joinPathFragments('dist', options.appProjectRoot), - compiler: options.compiler ?? 'babel', - index: joinPathFragments(options.appProjectRoot, 'src/index.html'), - baseHref: '/', - main: joinPathFragments(options.appProjectRoot, 'src/main.ts'), - polyfills: joinPathFragments(options.appProjectRoot, 'src/polyfills.ts'), - tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), - assets: [ - joinPathFragments(options.appProjectRoot, 'src/favicon.ico'), - joinPathFragments(options.appProjectRoot, 'src/assets'), - ], - styles: [ +async function setupBundler(tree: Tree, options: NormalizedSchema) { + const main = joinPathFragments(options.appProjectRoot, 'src/main.ts'); + const tsConfig = joinPathFragments( + options.appProjectRoot, + 'tsconfig.app.json' + ); + const assets = [ + joinPathFragments(options.appProjectRoot, 'src/favicon.ico'), + joinPathFragments(options.appProjectRoot, 'src/assets'), + ]; + + if (options.bundler === 'webpack') { + await webpackProjectGenerator(tree, { + project: options.projectName, + main, + tsConfig, + compiler: options.compiler ?? 'babel', + devServer: true, + }); + const project = readProjectConfiguration(tree, options.projectName); + const prodConfig = project.targets.build.configurations.production; + const buildOptions = project.targets.build.options; + buildOptions.assets = assets; + buildOptions.index = joinPathFragments( + options.appProjectRoot, + 'src/index.html' + ); + buildOptions.baseHref = '/'; + buildOptions.polyfills = joinPathFragments( + options.appProjectRoot, + 'src/polyfills.ts' + ); + buildOptions.styles = [ joinPathFragments(options.appProjectRoot, `src/styles.${options.style}`), - ], - scripts: [], - }; - const productionBuildOptions: Partial = { - fileReplacements: [ + ]; + buildOptions.scripts = []; + prodConfig.fileReplacements = [ { replace: joinPathFragments( options.appProjectRoot, @@ -87,77 +100,51 @@ function addBuildTarget( `src/environments/environment.prod.ts` ), }, - ], - optimization: true, - outputHashing: 'all', - sourceMap: false, - namedChunks: false, - extractLicenses: true, - vendorChunk: false, - }; - - return { - ...project, - targets: { - ...project.targets, - build: { - executor: '@nrwl/web:webpack', - outputs: ['{options.outputPath}'], - defaultConfiguration: 'production', - options: buildOptions, - configurations: { - production: productionBuildOptions, - }, - }, - }, - }; -} - -function addServeTarget( - project: ProjectConfiguration, - options: NormalizedSchema -) { - const serveTarget: TargetConfiguration = { - executor: '@nrwl/web:dev-server', - options: { - buildTarget: `${options.projectName}:build`, - }, - configurations: { - production: { - buildTarget: `${options.projectName}:build:production`, + ]; + prodConfig.optimization = true; + prodConfig.outputHashing = 'all'; + prodConfig.sourceMap = false; + prodConfig.namedChunks = false; + prodConfig.extractLicenses = true; + prodConfig.vendorChunk = false; + updateProjectConfiguration(tree, options.projectName, project); + } else if (options.bundler === 'none') { + // TODO(jack): Flush this out... no bundler should be possible for web but the experience isn't holistic due to missing features (e.g. writing index.html). + const project = readProjectConfiguration(tree, options.projectName); + project.targets.build = { + executor: `@nrwl/js:${options.compiler}`, + outputs: ['{options.outputPath}'], + options: { + main, + outputPath: joinPathFragments('dist', options.appProjectRoot), + tsConfig, + assets, }, - }, - }; - - return { - ...project, - targets: { - ...project.targets, - serve: serveTarget, - }, - }; + }; + updateProjectConfiguration(tree, options.projectName, project); + } else { + throw new Error('Unsupported bundler type'); + } } -function addProject(tree: Tree, options: NormalizedSchema) { +async function addProject(tree: Tree, options: NormalizedSchema) { const targets: Record = {}; - let project: ProjectConfiguration = { - projectType: 'application', - root: options.appProjectRoot, - sourceRoot: joinPathFragments(options.appProjectRoot, 'src'), - tags: options.parsedTags, - targets, - }; - - project = addBuildTarget(project, options); - project = addServeTarget(project, options); addProjectConfiguration( tree, options.projectName, - project, + { + projectType: 'application', + root: options.appProjectRoot, + sourceRoot: joinPathFragments(options.appProjectRoot, 'src'), + tags: options.parsedTags, + targets, + }, options.standaloneConfig ); + await setupBundler(tree, options); + const workspace = readWorkspaceConfiguration(tree); if (!workspace.defaultProject) { @@ -198,7 +185,7 @@ export async function applicationGenerator(host: Tree, schema: Schema) { tasks.push(webTask); createApplicationFiles(host, options); - addProject(host, options); + await addProject(host, options); const lintTask = await lintProjectGenerator(host, { linter: options.linter, @@ -275,6 +262,8 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { ...options, prefix: options.prefix ?? npmScope, name: names(options.name).fileName, + compiler: options.compiler ?? 'babel', + bundler: options.bundler ?? 'webpack', projectName: appProjectName, appProjectRoot, e2eProjectRoot, diff --git a/packages/web/src/generators/application/schema.d.ts b/packages/web/src/generators/application/schema.d.ts index 9e94dd72264ca..762f042689cc2 100644 --- a/packages/web/src/generators/application/schema.d.ts +++ b/packages/web/src/generators/application/schema.d.ts @@ -4,6 +4,7 @@ export interface Schema { name: string; prefix?: string; style?: string; + bundler?: 'webpack' | 'none'; compiler?: 'babel' | 'swc'; skipFormat?: boolean; directory?: string; diff --git a/packages/web/src/generators/application/schema.json b/packages/web/src/generators/application/schema.json index 26349026b2119..ebea192298117 100644 --- a/packages/web/src/generators/application/schema.json +++ b/packages/web/src/generators/application/schema.json @@ -50,6 +50,12 @@ "enum": ["babel", "swc"], "default": "babel" }, + "bundler": { + "type": "string", + "description": "The bundler to use.", + "enum": ["webpack", "none"], + "default": "webpack" + }, "linter": { "description": "The tool to use for running lint checks.", "type": "string", diff --git a/packages/web/src/generators/init/init.spec.ts b/packages/web/src/generators/init/init.spec.ts index 787ec81e30b92..941fd184e63dd 100644 --- a/packages/web/src/generators/init/init.spec.ts +++ b/packages/web/src/generators/init/init.spec.ts @@ -45,58 +45,4 @@ describe('init', () => { }); expect(tree.exists('jest.config.js')).toBe(false); }); - - describe('babel config', () => { - it('should create babel config if not present', async () => { - updateJson(tree, 'nx.json', (json) => { - json.namedInputs = { - sharedGlobals: ['{workspaceRoot}/exiting-file.json'], - }; - return json; - }); - - await webInitGenerator(tree, { - unitTestRunner: 'none', - }); - - expect(tree.exists('babel.config.json')).toBe(true); - const sharedGloabls = readJson(tree, 'nx.json') - .namedInputs.sharedGlobals; - expect(sharedGloabls).toContain('{workspaceRoot}/exiting-file.json'); - expect(sharedGloabls).toContain('{workspaceRoot}/babel.config.json'); - }); - - it('should not overwrite existing babel config', async () => { - tree.write('babel.config.json', '{ "preset": ["preset-awesome"] }'); - - await webInitGenerator(tree, { - unitTestRunner: 'none', - }); - - const existing = readJson(tree, 'babel.config.json'); - expect(existing).toEqual({ preset: ['preset-awesome'] }); - }); - - it('should not overwrite existing babel config (.js)', async () => { - tree.write('/babel.config.js', 'module.exports = () => {};'); - await webInitGenerator(tree, { - unitTestRunner: 'none', - }); - expect(tree.exists('babel.config.json')).toBe(false); - }); - - it('should not fail when dependencies is missing from package.json and no other init generators are invoked', async () => { - updateJson(tree, 'package.json', (json) => { - delete json.dependencies; - return json; - }); - - expect( - webInitGenerator(tree, { - e2eTestRunner: 'none', - unitTestRunner: 'none', - }) - ).resolves.toBeTruthy(); - }); - }); }); diff --git a/packages/web/src/generators/init/init.ts b/packages/web/src/generators/init/init.ts index 8c03ac7aefa31..d86173df9b0ca 100644 --- a/packages/web/src/generators/init/init.ts +++ b/packages/web/src/generators/init/init.ts @@ -19,9 +19,18 @@ import { } from '../../utils/versions'; import { Schema } from './schema'; -function updateDependencies(tree: Tree) { +function updateDependencies(tree: Tree, schema: Schema) { removeDependenciesFromPackageJson(tree, ['@nrwl/web'], []); + const devDependencies = { + '@nrwl/web': nxVersion, + '@types/node': typesNodeVersion, + }; + + if (schema.bundler === 'webpack') { + devDependencies['@nrwl/webpack'] = nxVersion; + } + return addDependenciesToPackageJson( tree, { @@ -29,10 +38,7 @@ function updateDependencies(tree: Tree) { 'regenerator-runtime': '0.13.7', tslib: tsLibVersion, }, - { - '@nrwl/web': nxVersion, - '@types/node': typesNodeVersion, - } + devDependencies ); } @@ -66,7 +72,7 @@ export async function webInitGenerator(tree: Tree, schema: Schema) { const cypressTask = cypressInitGenerator(tree, {}); tasks.push(cypressTask); } - const installTask = updateDependencies(tree); + const installTask = updateDependencies(tree, schema); tasks.push(installTask); initRootBabelConfig(tree); if (!schema.skipFormat) { diff --git a/packages/web/src/generators/init/schema.d.ts b/packages/web/src/generators/init/schema.d.ts index 7ab6757712763..b5a870ac1fca4 100644 --- a/packages/web/src/generators/init/schema.d.ts +++ b/packages/web/src/generators/init/schema.d.ts @@ -1,4 +1,5 @@ export interface Schema { + bundler?: 'webpack' | 'none'; unitTestRunner?: 'jest' | 'none'; e2eTestRunner?: 'cypress' | 'none'; skipFormat?: boolean; diff --git a/packages/web/src/generators/init/schema.json b/packages/web/src/generators/init/schema.json index 373aa9aecffba..8b0f7d4c41b7f 100644 --- a/packages/web/src/generators/init/schema.json +++ b/packages/web/src/generators/init/schema.json @@ -6,6 +6,12 @@ "description": "Init Web Plugin.", "type": "object", "properties": { + "bundler": { + "type": "string", + "description": "The bundler to use.", + "enum": ["webpack", "none"], + "default": "webpack" + }, "unitTestRunner": { "description": "Adds the specified unit test runner", "type": "string", diff --git a/packages/web/src/migrations/update-11-5-2/utils.spec.ts b/packages/web/src/migrations/update-11-5-2/utils.spec.ts index 95ae311fbf649..5640d45cc06a4 100644 --- a/packages/web/src/migrations/update-11-5-2/utils.spec.ts +++ b/packages/web/src/migrations/update-11-5-2/utils.spec.ts @@ -1,6 +1,6 @@ import { getProjects, ProjectGraph, DependencyType } from '@nrwl/devkit'; import { reverse } from '@nrwl/devkit'; -import { hasDependentAppUsingWebBuild } from '@nrwl/web/src/migrations/update-11-5-2/utils'; +import { hasDependentAppUsingWebBuild } from './utils'; describe('hasDependentAppUsingWebBuild', () => { const graph: ProjectGraph = reverse({ diff --git a/packages/web/src/migrations/update-14-7-5/update-webpack-executor.spec.ts b/packages/web/src/migrations/update-14-7-5/update-webpack-executor.spec.ts new file mode 100644 index 0000000000000..92840720ff4ac --- /dev/null +++ b/packages/web/src/migrations/update-14-7-5/update-webpack-executor.spec.ts @@ -0,0 +1,92 @@ +import { readJson } from '@nrwl/devkit'; +import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing'; + +import update from './update-webpack-executor'; + +describe('Migration: @nrwl/webpack', () => { + it(`should update usage of webpack executor`, async () => { + let tree = createTreeWithEmptyV1Workspace(); + + tree.write( + 'workspace.json', + JSON.stringify({ + version: 2, + projects: { + myapp: { + root: 'apps/myapp', + sourceRoot: 'apps/myapp/src', + projectType: 'application', + targets: { + build: { + executor: '@nrwl/web:webpack', + options: {}, + }, + }, + }, + }, + }) + ); + + await update(tree); + + expect(readJson(tree, 'workspace.json')).toEqual({ + version: 2, + projects: { + myapp: { + root: 'apps/myapp', + sourceRoot: 'apps/myapp/src', + projectType: 'application', + targets: { + build: { + executor: '@nrwl/webpack:webpack', + options: {}, + }, + }, + }, + }, + }); + }); + + it(`should update usage of dev-server executor`, async () => { + let tree = createTreeWithEmptyV1Workspace(); + + tree.write( + 'workspace.json', + JSON.stringify({ + version: 2, + projects: { + myapp: { + root: 'apps/myapp', + sourceRoot: 'apps/myapp/src', + projectType: 'application', + targets: { + serve: { + executor: '@nrwl/web:dev-server', + options: {}, + }, + }, + }, + }, + }) + ); + + await update(tree); + + expect(readJson(tree, 'workspace.json')).toEqual({ + version: 2, + projects: { + myapp: { + root: 'apps/myapp', + sourceRoot: 'apps/myapp/src', + projectType: 'application', + targets: { + serve: { + executor: '@nrwl/webpack:dev-server', + options: {}, + }, + }, + }, + }, + }); + }); +}); diff --git a/packages/web/src/migrations/update-14-7-5/update-webpack-executor.ts b/packages/web/src/migrations/update-14-7-5/update-webpack-executor.ts new file mode 100644 index 0000000000000..c85ca88867711 --- /dev/null +++ b/packages/web/src/migrations/update-14-7-5/update-webpack-executor.ts @@ -0,0 +1,27 @@ +import { + formatFiles, + getProjects, + Tree, + updateProjectConfiguration, +} from '@nrwl/devkit'; + +export default async function update(host: Tree) { + const projects = getProjects(host); + + for (const [name, config] of projects.entries()) { + let updated = false; + if (config?.targets?.build?.executor === '@nrwl/web:webpack') { + config.targets.build.executor = '@nrwl/webpack:webpack'; + updated = true; + } + if (config?.targets?.serve?.executor === '@nrwl/web:dev-server') { + config.targets.serve.executor = '@nrwl/webpack:dev-server'; + updated = true; + } + if (updated) { + updateProjectConfiguration(host, name, config); + } + } + + await formatFiles(host); +} diff --git a/packages/web/src/utils/config.ts b/packages/web/src/utils/config.ts deleted file mode 100644 index a662a233f1fae..0000000000000 --- a/packages/web/src/utils/config.ts +++ /dev/null @@ -1,305 +0,0 @@ -import { join } from 'path'; -import * as webpack from 'webpack'; -import { Configuration, WebpackPluginInstance } from 'webpack'; -import { LicenseWebpackPlugin } from 'license-webpack-plugin'; -import * as CopyWebpackPlugin from 'copy-webpack-plugin'; -import { AssetGlobPattern, BuildBuilderOptions } from './shared-models'; -import { getOutputHashFormat } from './hash-format'; -import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin'; -import TerserPlugin = require('terser-webpack-plugin'); -import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); - -const IGNORED_WEBPACK_WARNINGS = [ - /The comment file/i, - /could not find any license/i, -]; - -export function getBaseWebpackPartial( - builderOptions: BuildBuilderOptions, - extraOptions: { - esm?: boolean; - isScriptOptimizeOn?: boolean; - emitDecoratorMetadata?: boolean; - configuration?: string; - skipTypeCheck?: boolean; - } -): Configuration { - const extensions = ['.ts', '.tsx', '.mjs', '.js', '.jsx']; - const mainFields = [ - ...(extraOptions.esm ? ['es2015'] : []), - 'module', - 'main', - ]; - const hashFormat = getOutputHashFormat(builderOptions.outputHashing); - const suffixFormat = extraOptions.esm ? '.esm' : '.es5'; - const filename = extraOptions.isScriptOptimizeOn - ? `[name]${hashFormat.script}${suffixFormat}.js` - : '[name].js'; - const chunkFilename = extraOptions.isScriptOptimizeOn - ? `[name]${hashFormat.chunk}${suffixFormat}.js` - : '[name].js'; - const mode = extraOptions.isScriptOptimizeOn ? 'production' : 'development'; - - const webpackConfig: Configuration = { - target: 'web', // webpack defaults to 'browserslist' which breaks Fast Refresh - - entry: { - main: [builderOptions.main], - }, - devtool: - builderOptions.sourceMap === 'hidden' - ? 'hidden-source-map' - : builderOptions.sourceMap - ? 'source-map' - : false, - mode, - output: { - path: builderOptions.outputPath, - filename, - chunkFilename, - hashFunction: 'xxhash64', - // Disabled for performance - pathinfo: false, - }, - module: { - // Enabled for performance - unsafeCache: true, - rules: [ - { - test: /\.(bmp|png|jpe?g|gif|webp|avif)$/, - type: 'asset', - parser: { - dataUrlCondition: { - maxSize: 10_000, // 10 kB - }, - }, - }, - { - // There's an issue resolving paths without fully specified extensions - // See: https://github.com/graphql/graphql-js/issues/2721 - // TODO(jack): Add a flag to turn this option on like Next.js does via experimental flag. - // See: https://github.com/vercel/next.js/pull/29880 - test: /\.m?jsx?$/, - resolve: { - fullySpecified: false, - }, - }, - builderOptions.compiler === 'babel' && { - test: /\.([jt])sx?$/, - loader: join(__dirname, 'web-babel-loader'), - exclude: /node_modules/, - options: { - rootMode: 'upward', - cwd: join(builderOptions.root, builderOptions.sourceRoot), - emitDecoratorMetadata: extraOptions.emitDecoratorMetadata, - isModern: extraOptions.esm, - envName: extraOptions.isScriptOptimizeOn - ? 'production' - : extraOptions.configuration, - babelrc: true, - cacheDirectory: true, - cacheCompression: false, - }, - }, - builderOptions.compiler === 'swc' && { - test: /\.([jt])sx?$/, - loader: require.resolve('swc-loader'), - exclude: /node_modules/, - options: { - jsc: { - parser: { - syntax: 'typescript', - decorators: true, - tsx: true, - }, - transform: { - react: { - runtime: 'automatic', - }, - }, - loose: true, - }, - }, - }, - ].filter(Boolean), - }, - resolve: { - extensions, - alias: getAliases(builderOptions), - plugins: [ - new TsconfigPathsPlugin({ - configFile: builderOptions.tsConfig, - extensions, - mainFields, - }) as never, // TODO: Remove never type when 'tsconfig-paths-webpack-plugin' types fixed - ], - mainFields, - }, - performance: { - hints: false, - }, - plugins: [new webpack.DefinePlugin(getClientEnvironment(mode).stringified)], - watch: builderOptions.watch, - watchOptions: { - poll: builderOptions.poll, - }, - stats: getStatsConfig(builderOptions), - ignoreWarnings: [ - (x) => - IGNORED_WEBPACK_WARNINGS.some((r) => - typeof x === 'string' ? r.test(x) : r.test(x.message) - ), - ], - experiments: { - cacheUnaffected: true, - }, - }; - - if (builderOptions.compiler !== 'swc' && extraOptions.isScriptOptimizeOn) { - webpackConfig.optimization = { - sideEffects: true, - minimizer: [ - new TerserPlugin({ - parallel: true, - terserOptions: { - ecma: (extraOptions.esm ? 2016 : 5) as TerserPlugin.TerserECMA, - safari10: true, - output: { - ascii_only: true, - comments: false, - webkit: true, - }, - }, - }), - ], - runtimeChunk: true, - }; - } - - const extraPlugins: WebpackPluginInstance[] = []; - - if (!extraOptions.skipTypeCheck && extraOptions.esm) { - extraPlugins.push( - new ForkTsCheckerWebpackPlugin({ - typescript: { - configFile: builderOptions.tsConfig, - memoryLimit: builderOptions.memoryLimit || 2018, - }, - }) - ); - } - - if (builderOptions.progress) { - extraPlugins.push(new webpack.ProgressPlugin()); - } - - // TODO LicenseWebpackPlugin needs a PR for proper typing - if (builderOptions.extractLicenses) { - extraPlugins.push( - new LicenseWebpackPlugin({ - stats: { - errors: false, - }, - perChunkOutput: false, - outputFilename: `3rdpartylicenses.txt`, - }) as unknown as WebpackPluginInstance - ); - } - - if ( - Array.isArray(builderOptions.assets) && - builderOptions.assets.length > 0 - ) { - extraPlugins.push(createCopyPlugin(builderOptions.assets)); - } - - webpackConfig.plugins = [...webpackConfig.plugins, ...extraPlugins]; - - return webpackConfig; -} - -function getAliases(options: BuildBuilderOptions): { [key: string]: string } { - return options.fileReplacements.reduce( - (aliases, replacement) => ({ - ...aliases, - [replacement.replace]: replacement.with, - }), - {} - ); -} - -function getStatsConfig(options: BuildBuilderOptions) { - return { - hash: true, - timings: false, - cached: false, - cachedAssets: false, - modules: false, - warnings: true, - errors: true, - colors: !options.verbose && !options.statsJson, - chunks: !options.verbose, - assets: !!options.verbose, - chunkOrigins: !!options.verbose, - chunkModules: !!options.verbose, - children: !!options.verbose, - reasons: !!options.verbose, - version: !!options.verbose, - errorDetails: !!options.verbose, - moduleTrace: !!options.verbose, - usedExports: !!options.verbose, - }; -} - -// This is shamelessly taken from CRA and modified for NX use -// https://github.com/facebook/create-react-app/blob/4784997f0682e75eb32a897b4ffe34d735912e6c/packages/react-scripts/config/env.js#L71 -function getClientEnvironment(mode) { - // Grab NODE_ENV and NX_* environment variables and prepare them to be - // injected into the application via DefinePlugin in webpack configuration. - const NX_APP = /^NX_/i; - - const raw = Object.keys(process.env) - .filter((key) => NX_APP.test(key)) - .reduce( - (env, key) => { - env[key] = process.env[key]; - return env; - }, - { - // Useful for determining whether we’re running in production mode. - NODE_ENV: process.env.NODE_ENV || mode, - } - ); - - // Stringify all values so we can feed into webpack DefinePlugin - const stringified = { - 'process.env': Object.keys(raw).reduce((env, key) => { - env[key] = JSON.stringify(raw[key]); - return env; - }, {}), - }; - - return { stringified }; -} - -export function createCopyPlugin(assets: AssetGlobPattern[]) { - return new CopyWebpackPlugin({ - patterns: assets.map((asset) => { - return { - context: asset.input, - // Now we remove starting slash to make Webpack place it from the output root. - to: asset.output, - from: asset.glob, - globOptions: { - ignore: [ - '.gitkeep', - '**/.DS_Store', - '**/Thumbs.db', - ...(asset.ignore ?? []), - ], - dot: true, - }, - }; - }), - }); -} diff --git a/packages/web/src/utils/devserver.config.spec.ts b/packages/web/src/utils/devserver.config.spec.ts deleted file mode 100644 index 7d659ee9ec01b..0000000000000 --- a/packages/web/src/utils/devserver.config.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { getDevServerConfig } from '@nrwl/web/src/utils/devserver.config'; - -jest.mock('./webpack/partials/common', () => ({ - getCommonConfig: () => ({ - output: {}, - resolve: {}, - }), - getCommonPartial: () => ({}), -})); - -jest.mock('tsconfig-paths-webpack-plugin', () => ({ - TsconfigPathsPlugin: class TsconfigPathsPlugin {}, -})); - -describe('getDevServerConfig', () => { - it('should set mode to production when optimization is on', () => { - const result = getDevServerConfig( - '/', - '/app', - '/app/src', - { - optimization: true, - root: '/app', - sourceRoot: '/app/src', - main: '/app/src/main.ts', - outputPath: '/dist/app', - tsConfig: '/app/tsconfig.app.json', - compiler: 'babel', - assets: [], - fileReplacements: [], - index: '/app/src/index.html', - scripts: [], - styles: [], - }, - { - buildTarget: 'app:build:production', - allowedHosts: '', - liveReload: false, - hmr: false, - ssl: false, - watch: false, - open: false, - host: 'localhost', - port: 4200, - } - ); - - expect(result.mode).toEqual('production'); - }); - - it('should set mode to development when optimization is off', () => { - const result = getDevServerConfig( - '/', - '/app', - '/app/src', - { - root: '/app', - sourceRoot: '/app/src', - main: '/app/src/main.ts', - outputPath: '/dist/app', - tsConfig: '/app/tsconfig.app.json', - compiler: 'babel', - assets: [], - fileReplacements: [], - index: '/app/src/index.html', - scripts: [], - styles: [], - }, - { - buildTarget: 'app:build:development', - allowedHosts: '', - liveReload: false, - hmr: false, - ssl: false, - watch: false, - open: false, - host: 'localhost', - port: 4200, - } - ); - - expect(result.mode).toEqual('development'); - }); -}); diff --git a/packages/web/src/utils/fs.ts b/packages/web/src/utils/fs.ts index c9b512bb29f97..062d8b7e19ef4 100644 --- a/packages/web/src/utils/fs.ts +++ b/packages/web/src/utils/fs.ts @@ -1,67 +1 @@ -import * as path from 'path'; -import { existsSync, removeSync } from 'fs-extra'; -import { directoryExists } from 'nx/src/utils/fileutils'; - -export function findUp( - names: string | string[], - from: string, - stopOnNodeModules = false -) { - if (!Array.isArray(names)) { - names = [names]; - } - const root = path.parse(from).root; - - let currentDir = from; - while (currentDir && currentDir !== root) { - for (const name of names) { - const p = path.join(currentDir, name); - if (existsSync(p)) { - return p; - } - } - - if (stopOnNodeModules) { - const nodeModuleP = path.join(currentDir, 'node_modules'); - if (existsSync(nodeModuleP)) { - return null; - } - } - - currentDir = path.dirname(currentDir); - } - - return null; -} - -export function findAllNodeModules(from: string, root?: string) { - const nodeModules: string[] = []; - - let current = from; - while (current && current !== root) { - const potential = path.join(current, 'node_modules'); - if (directoryExists(potential)) { - nodeModules.push(potential); - } - - const next = path.dirname(current); - if (next === current) { - break; - } - current = next; - } - - return nodeModules; -} - -/** - * Delete an output directory, but error out if it's the root of the project. - */ -export function deleteOutputDir(root: string, outputPath: string) { - const resolvedOutputPath = path.resolve(root, outputPath); - if (resolvedOutputPath === root) { - throw new Error('Output path MUST not be project root directory!'); - } - - removeSync(resolvedOutputPath); -} +export * from '@nrwl/webpack/src/utils/fs'; diff --git a/packages/web/src/utils/normalize.spec.ts b/packages/web/src/utils/normalize.spec.ts deleted file mode 100644 index 851b8393626aa..0000000000000 --- a/packages/web/src/utils/normalize.spec.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { normalizeBuildOptions } from './normalize'; -import { BuildBuilderOptions } from './shared-models'; - -import * as fs from 'fs'; - -describe('normalizeBuildOptions', () => { - let testOptions: BuildBuilderOptions; - let root: string; - let sourceRoot: string; - - beforeEach(() => { - testOptions = { - compiler: 'babel', - main: 'apps/nodeapp/src/main.ts', - tsConfig: 'apps/nodeapp/tsconfig.app.json', - outputPath: 'dist/apps/nodeapp', - fileReplacements: [ - { - replace: 'apps/environment/environment.ts', - with: 'apps/environment/environment.prod.ts', - }, - { - replace: 'module1.ts', - with: 'module2.ts', - }, - ], - assets: [], - statsJson: false, - webpackConfig: 'apps/nodeapp/webpack.config', - }; - root = '/root'; - sourceRoot = 'apps/nodeapp/src'; - }); - - it('should resolve main from root', () => { - const result = normalizeBuildOptions(testOptions, root, sourceRoot); - expect(result.main).toEqual('/root/apps/nodeapp/src/main.ts'); - }); - - it('should resolve the output path', () => { - const result = normalizeBuildOptions(testOptions, root, sourceRoot); - expect(result.outputPath).toEqual('/root/dist/apps/nodeapp'); - }); - - it('should resolve the tsConfig path', () => { - const result = normalizeBuildOptions(testOptions, root, sourceRoot); - expect(result.tsConfig).toEqual('/root/apps/nodeapp/tsconfig.app.json'); - }); - - it('should normalize asset patterns', () => { - jest.spyOn(fs, 'statSync').mockReturnValue({ - isDirectory: () => true, - } as any); - const result = normalizeBuildOptions( - { - ...testOptions, - root, - assets: [ - 'apps/nodeapp/src/assets', - { - input: 'outsideproj', - output: 'output', - glob: '**/*', - ignore: ['**/*.json'], - }, - ], - }, - root, - sourceRoot - ); - expect(result.assets).toEqual([ - { - input: '/root/apps/nodeapp/src/assets', - output: 'assets', - glob: '**/*', - }, - { - input: '/root/outsideproj', - output: 'output', - glob: '**/*', - ignore: ['**/*.json'], - }, - ]); - }); - - it('should resolve the file replacement paths', () => { - const result = normalizeBuildOptions(testOptions, root, sourceRoot); - expect(result.fileReplacements).toEqual([ - { - replace: '/root/apps/environment/environment.ts', - with: '/root/apps/environment/environment.prod.ts', - }, - { - replace: '/root/module1.ts', - with: '/root/module2.ts', - }, - ]); - }); - - it('should resolve both node modules and relative path for webpackConfig', () => { - let result = normalizeBuildOptions(testOptions, root, sourceRoot); - expect(result.webpackConfig).toEqual('/root/apps/nodeapp/webpack.config'); - - result = normalizeBuildOptions( - { - ...testOptions, - webpackConfig: 'react', // something that exists in node_modules - }, - root, - sourceRoot - ); - expect(result.webpackConfig).toMatch('react'); - expect(result.webpackConfig).not.toMatch(root); - }); -}); diff --git a/packages/web/src/utils/normalize.ts b/packages/web/src/utils/normalize.ts index 5dc96c3f197a5..db1481fe6cdad 100644 --- a/packages/web/src/utils/normalize.ts +++ b/packages/web/src/utils/normalize.ts @@ -1,173 +1 @@ -import { WebWebpackExecutorOptions } from '../executors/webpack/webpack.impl'; -import { normalizePath } from '@nrwl/devkit'; -import { basename, dirname, relative, resolve } from 'path'; -import { - AssetGlobPattern, - BuildBuilderOptions, - ExtraEntryPoint, - ExtraEntryPointClass, -} from './shared-models'; -import { statSync } from 'fs'; - -export interface FileReplacement { - replace: string; - with: string; -} - -export function normalizeBuildOptions( - options: T, - root: string, - sourceRoot: string -): T { - return { - ...options, - root, - sourceRoot, - main: resolve(root, options.main), - outputPath: resolve(root, options.outputPath), - tsConfig: resolve(root, options.tsConfig), - fileReplacements: normalizeFileReplacements(root, options.fileReplacements), - assets: normalizeAssets(options.assets, root, sourceRoot), - webpackConfig: normalizePluginPath(options.webpackConfig, root), - }; -} - -export function normalizePluginPath(pluginPath: void | string, root: string) { - if (!pluginPath) { - return ''; - } - try { - return require.resolve(pluginPath); - } catch { - return resolve(root, pluginPath); - } -} - -export function normalizeAssets( - assets: any[], - root: string, - sourceRoot: string -): AssetGlobPattern[] { - return assets.map((asset) => { - if (typeof asset === 'string') { - const assetPath = normalizePath(asset); - const resolvedAssetPath = resolve(root, assetPath); - const resolvedSourceRoot = resolve(root, sourceRoot); - - if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) { - throw new Error( - `The ${resolvedAssetPath} asset path must start with the project source root: ${sourceRoot}` - ); - } - - const isDirectory = statSync(resolvedAssetPath).isDirectory(); - const input = isDirectory - ? resolvedAssetPath - : dirname(resolvedAssetPath); - const output = relative(resolvedSourceRoot, resolve(root, input)); - const glob = isDirectory ? '**/*' : basename(resolvedAssetPath); - return { - input, - output, - glob, - }; - } else { - if (asset.output.startsWith('..')) { - throw new Error( - 'An asset cannot be written to a location outside of the output path.' - ); - } - - const assetPath = normalizePath(asset.input); - const resolvedAssetPath = resolve(root, assetPath); - return { - ...asset, - input: resolvedAssetPath, - // Now we remove starting slash to make Webpack place it from the output root. - output: asset.output.replace(/^\//, ''), - }; - } - }); -} - -function normalizeFileReplacements( - root: string, - fileReplacements: FileReplacement[] -): FileReplacement[] { - return fileReplacements.map((fileReplacement) => ({ - replace: resolve(root, fileReplacement.replace), - with: resolve(root, fileReplacement.with), - })); -} - -export function normalizeWebBuildOptions( - options: WebWebpackExecutorOptions, - root: string, - sourceRoot: string -): WebWebpackExecutorOptions { - return { - ...normalizeBuildOptions(options, root, sourceRoot), - optimization: - typeof options.optimization !== 'object' - ? { - scripts: options.optimization, - styles: options.optimization, - } - : options.optimization, - polyfills: options.polyfills ? resolve(root, options.polyfills) : undefined, - es2015Polyfills: options.es2015Polyfills - ? resolve(root, options.es2015Polyfills) - : undefined, - }; -} - -export function convertBuildOptions( - buildOptions: WebWebpackExecutorOptions -): any { - const options = buildOptions as any; - return { - ...options, - buildOptimizer: options.optimization, - forkTypeChecker: false, - lazyModules: [] as string[], - }; -} - -export type NormalizedEntryPoint = Required>; - -export function normalizeExtraEntryPoints( - extraEntryPoints: ExtraEntryPoint[], - defaultBundleName: string -): NormalizedEntryPoint[] { - return extraEntryPoints.map((entry) => { - let normalizedEntry; - if (typeof entry === 'string') { - normalizedEntry = { - input: entry, - inject: true, - bundleName: defaultBundleName, - }; - } else { - const { lazy, inject = true, ...newEntry } = entry; - const injectNormalized = entry.lazy !== undefined ? !entry.lazy : inject; - let bundleName; - - if (entry.bundleName) { - bundleName = entry.bundleName; - } else if (!injectNormalized) { - // Lazy entry points use the file name as bundle name. - bundleName = basename( - normalizePath( - entry.input.replace(/\.(js|css|scss|sass|less|styl)$/i, '') - ) - ); - } else { - bundleName = defaultBundleName; - } - - normalizedEntry = { ...newEntry, inject: injectNormalized, bundleName }; - } - - return normalizedEntry; - }); -} +export * from '@nrwl/webpack/src/executors/webpack/lib/normalize-options'; diff --git a/packages/web/src/utils/run-webpack.ts b/packages/web/src/utils/run-webpack.ts deleted file mode 100644 index c9298c8ef746a..0000000000000 --- a/packages/web/src/utils/run-webpack.ts +++ /dev/null @@ -1,120 +0,0 @@ -import * as webpack from 'webpack'; -import type { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server'; -import { Observable } from 'rxjs'; -import { extname } from 'path'; - -export function runWebpack(config: webpack.Configuration): Observable { - return new Observable((subscriber) => { - // Passing `watch` option here will result in a warning due to missing callback. - // We manually call `.watch` or `.run` later so this option isn't needed here. - const { watch, ...normalizedConfig } = config; - const webpackCompiler = webpack(normalizedConfig); - - const callback = (err: Error, stats: webpack.Stats) => { - if (err) { - subscriber.error(err); - } - subscriber.next(stats); - }; - - if (config.watch) { - const watchOptions = config.watchOptions || {}; - const watching = webpackCompiler.watch(watchOptions, callback); - - return () => watching.close(() => subscriber.complete()); - } else { - webpackCompiler.run((err, stats) => { - callback(err, stats); - webpackCompiler.close((closeErr) => { - if (closeErr) subscriber.error(closeErr); - subscriber.complete(); - }); - }); - } - }); -} - -export function runWebpackDevServer( - config: any, - webpack: typeof import('webpack'), - WebpackDevServer: typeof import('webpack-dev-server') -): Observable<{ stats: any; baseUrl: string }> { - return new Observable((subscriber) => { - const webpackCompiler: any = webpack(config); - - let baseUrl: string; - - webpackCompiler.hooks.done.tap('build-webpack', (stats) => { - subscriber.next({ stats, baseUrl }); - }); - - const devServerConfig = (config as any).devServer || {}; - - const originalOnListen = devServerConfig.onListening; - - devServerConfig.onListening = function (server: any) { - originalOnListen(server); - - const devServerOptions: WebpackDevServerConfiguration = server.options; - - baseUrl = `${server.options.https ? 'https' : 'http'}://${ - server.options.host - }:${server.options.port}${devServerOptions.devMiddleware.publicPath}`; - }; - - const webpackServer = new WebpackDevServer( - devServerConfig, - webpackCompiler as any - ); - - try { - webpackServer.start().catch((err) => subscriber.error(err)); - return () => webpackServer.stop(); - } catch (e) { - throw new Error('Could not start start dev server'); - } - }); -} - -export interface EmittedFile { - id?: string; - name?: string; - file: string; - extension: string; - initial: boolean; - asset?: boolean; -} - -export function getEmittedFiles(stats: webpack.Stats) { - const { compilation } = stats; - const files: EmittedFile[] = []; - // adds all chunks to the list of emitted files such as lazy loaded modules - for (const chunk of compilation.chunks) { - for (const file of chunk.files) { - files.push({ - // The id is guaranteed to exist at this point in the compilation process - // tslint:disable-next-line: no-non-null-assertion - id: chunk.id.toString(), - name: chunk.name, - file, - extension: extname(file), - initial: chunk.isOnlyInitial(), - }); - } - } - // other all files - for (const file of Object.keys(compilation.assets)) { - files.push({ - file, - extension: extname(file), - initial: false, - asset: true, - }); - } - // dedupe - return files.filter( - ({ file, name }, index) => - files.findIndex((f) => f.file === file && (!name || name === f.name)) === - index - ); -} diff --git a/packages/web/src/utils/shared-models.ts b/packages/web/src/utils/shared-models.ts deleted file mode 100644 index 335e685badba0..0000000000000 --- a/packages/web/src/utils/shared-models.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { WebWebpackExecutorOptions } from '../executors/webpack/webpack.impl'; -import { FileReplacement } from './normalize'; - -export interface OptimizationOptions { - scripts: boolean; - styles: boolean; -} - -export interface BuildBuilderOptions { - main: string; - outputPath: string; - compiler: 'babel' | 'swc'; - tsConfig: string; - watch?: boolean; - sourceMap?: boolean | 'hidden'; - optimization?: boolean | OptimizationOptions; - memoryLimit?: number; - maxWorkers?: number; - poll?: number; - - fileReplacements?: FileReplacement[]; - assets?: any[]; - - progress?: boolean; - statsJson?: boolean; - extractLicenses?: boolean; - verbose?: boolean; - - outputHashing?: any; - webpackConfig?: string; - - root?: string; - sourceRoot?: string; -} - -export interface AssetGlobPattern { - glob: string; - input: string; - output: string; - ignore?: string[]; -} - -export type AssetPattern = AssetPatternClass | string; - -export interface AssetPatternClass { - glob: string; - ignore?: string[]; - input: string; - output: string; -} - -export enum Type { - All = 'all', - AllScript = 'allScript', - Any = 'any', - AnyComponentStyle = 'anyComponentStyle', - AnyScript = 'anyScript', - Bundle = 'bundle', - Initial = 'initial', -} - -export enum CrossOrigin { - Anonymous = 'anonymous', - None = 'none', - UseCredentials = 'use-credentials', -} - -export type IndexUnion = IndexObject | string; - -export interface IndexObject { - input: string; - output?: string; -} - -export type Localize = string[] | boolean; - -export type OptimizationUnion = boolean | OptimizationClass; - -export interface OptimizationClass { - scripts?: boolean; - styles?: boolean; -} - -export enum OutputHashing { - All = 'all', - Bundles = 'bundles', - Media = 'media', - None = 'none', -} - -export type ExtraEntryPoint = ExtraEntryPointClass | string; - -export interface ExtraEntryPointClass { - bundleName?: string; - inject?: boolean; - input: string; - lazy?: boolean; -} - -export type SourceMapUnion = boolean | SourceMapClass; - -export interface SourceMapClass { - hidden?: boolean; - scripts?: boolean; - styles?: boolean; - vendor?: boolean; -} - -export interface StylePreprocessorOptions { - includePaths?: string[]; -} - -export interface WebpackConfigOptions { - root: string; - projectRoot: string; - sourceRoot?: string; - buildOptions: T; - tsConfig: any; - tsConfigPath: string; - supportES2015: boolean; -} diff --git a/packages/webpack/.eslintrc.json b/packages/webpack/.eslintrc.json new file mode 100644 index 0000000000000..556554a7956a5 --- /dev/null +++ b/packages/webpack/.eslintrc.json @@ -0,0 +1,25 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["./package.json", "./generators.json", "./executors.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nrwl/nx/nx-plugin-checks": "error" + } + } + ] +} diff --git a/packages/webpack/README.md b/packages/webpack/README.md new file mode 100644 index 0000000000000..90cb23e4167c2 --- /dev/null +++ b/packages/webpack/README.md @@ -0,0 +1,13 @@ +

Nx - Smart, Fast and Extensible Build System

+ +{{links}} + +
+ +# Nx: Smart, Fast and Extensible Build System + +Nx is a next generation build system with first class monorepo support and powerful integrations. + +This package is a [Webpack plugin for Nx](https://nx.dev/packages/webpack). + +{{content}} diff --git a/packages/webpack/executors.json b/packages/webpack/executors.json new file mode 100644 index 0000000000000..506ddcab74afd --- /dev/null +++ b/packages/webpack/executors.json @@ -0,0 +1,26 @@ +{ + "builders": { + "webpack": { + "implementation": "./src/executors/webpack/compat", + "schema": "./src/executors/webpack/schema.json", + "description": "Run webpack build." + }, + "dev-server": { + "implementation": "./src/executors/dev-server/compat", + "schema": "./src/executors/dev-server/schema.json", + "description": "Serve a web application." + } + }, + "executors": { + "webpack": { + "implementation": "./src/executors/webpack/webpack.impl", + "schema": "./src/executors/webpack/schema.json", + "description": "Run webpack build." + }, + "dev-server": { + "implementation": "./src/executors/dev-server/dev-server.impl", + "schema": "./src/executors/dev-server/schema.json", + "description": "Serve a web application." + } + } +} diff --git a/packages/webpack/generators.json b/packages/webpack/generators.json new file mode 100644 index 0000000000000..43913018f4c7d --- /dev/null +++ b/packages/webpack/generators.json @@ -0,0 +1,33 @@ +{ + "name": "Nx Webpack", + "version": "0.1", + "schematics": { + "init": { + "factory": "./src/generators/init/init#webpackInitSchematic", + "schema": "./src/generators/init/schema.json", + "description": "Initialize the `@nrwl/webpack` plugin.", + "hidden": true + }, + "webpack-project": { + "factory": "./src/generators/webpack-project/webpack-project#webpackProjectSchematic", + "schema": "./src/generators/webpack-project/schema.json", + "description": "Add webpack configuration to a project.", + "hidden": true + } + }, + "generators": { + "init": { + "factory": "./src/generators/init/init#webpackInitGenerator", + "schema": "./src/generators/init/schema.json", + "description": "Initialize the `@nrwl/webpack` plugin.", + "aliases": ["ng-add"], + "hidden": true + }, + "webpack-project": { + "factory": "./src/generators/webpack-project/webpack-project#webpackProjectGenerator", + "schema": "./src/generators/webpack-project/schema.json", + "description": "Add webpack configuration to a project.", + "hidden": true + } + } +} diff --git a/packages/webpack/index.ts b/packages/webpack/index.ts new file mode 100644 index 0000000000000..18725091ce920 --- /dev/null +++ b/packages/webpack/index.ts @@ -0,0 +1,7 @@ +export * from './src/utils/config'; +export * from './src/generators/webpack-project/webpack-project'; +export * from './src/executors/dev-server/schema'; +export * from './src/executors/dev-server/dev-server.impl'; +export * from './src/executors/webpack/lib/normalize-options'; +export * from './src/executors/webpack/schema'; +export * from './src/executors/webpack/webpack.impl'; diff --git a/packages/webpack/jest.config.ts b/packages/webpack/jest.config.ts new file mode 100644 index 0000000000000..3103ed3352503 --- /dev/null +++ b/packages/webpack/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], + globals: { 'ts-jest': { tsconfig: '/tsconfig.spec.json' } }, + displayName: 'webpack', + testEnvironment: 'node', + preset: '../../jest.preset.js', +}; diff --git a/packages/webpack/package.json b/packages/webpack/package.json new file mode 100644 index 0000000000000..932252a6a7141 --- /dev/null +++ b/packages/webpack/package.json @@ -0,0 +1,79 @@ +{ + "name": "@nrwl/webpack", + "version": "0.0.1", + "description": "The Nx Plugin for Webpack contains executors and generators that support building applications using Webpack", + "repository": { + "type": "git", + "url": "https://github.com/nrwl/nx.git", + "directory": "packages/webpack" + }, + "keywords": [ + "Monorepo", + "Webpack", + "Web", + "CLI" + ], + "main": "./index.js", + "typings": "./index.d.ts", + "author": "Victor Savkin", + "license": "MIT", + "bugs": { + "url": "https://github.com/nrwl/nx/issues" + }, + "homepage": "https://nx.dev", + "schematics": "./generators.json", + "builders": "./executors.json", + "ng-update": { + "requirements": {}, + "migrations": "./migrations.json" + }, + "dependencies": { + "@nrwl/devkit": "file:../devkit", + "@nrwl/js": "file:../js", + "@nrwl/workspace": "file:../workspace", + "autoprefixer": "^10.4.9", + "babel-loader": "^8.2.2", + "browserslist": "^4.16.6", + "caniuse-lite": "^1.0.30001394", + "chalk": "4.1.0", + "chokidar": "^3.5.1", + "copy-webpack-plugin": "^10.2.4", + "css-loader": "^6.4.0", + "css-minimizer-webpack-plugin": "^3.4.1", + "file-loader": "^6.2.0", + "fork-ts-checker-webpack-plugin": "7.2.13", + "fs-extra": "^10.1.0", + "ignore": "^5.0.4", + "less": "3.12.2", + "less-loader": "^10.1.0", + "license-webpack-plugin": "^4.0.2", + "loader-utils": "1.2.3", + "mini-css-extract-plugin": "~2.4.7", + "parse5": "4.0.0", + "parse5-html-rewriting-stream": "6.0.1", + "postcss": "^8.4.14", + "postcss-import": "~14.1.0", + "postcss-loader": "^6.1.1", + "raw-loader": "^4.0.2", + "rxjs": "^6.5.4", + "sass": "^1.42.1", + "sass-loader": "^12.2.0", + "source-map": "0.7.3", + "source-map-loader": "^3.0.0", + "style-loader": "^3.3.0", + "stylus": "^0.55.0", + "stylus-loader": "^6.2.0", + "terser-webpack-plugin": "^5.3.3", + "ts-loader": "^9.3.1", + "ts-node": "10.9.1", + "tsconfig-paths": "^3.9.0", + "tsconfig-paths-webpack-plugin": "3.5.2", + "tslib": "^2.3.0", + "webpack": "^5.58.1", + "webpack-dev-server": "^4.9.3", + "webpack-merge": "^5.8.0", + "webpack-node-externals": "^3.0.0", + "webpack-sources": "^3.2.3", + "webpack-subresource-integrity": "^5.1.0" + } +} diff --git a/packages/webpack/project.json b/packages/webpack/project.json new file mode 100644 index 0000000000000..1cea96a2a721b --- /dev/null +++ b/packages/webpack/project.json @@ -0,0 +1,87 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/webpack", + "projectType": "library", + "targets": { + "test": { + "executor": "@nrwl/jest:jest", + "options": { + "jestConfig": "packages/webpack/jest.config.ts", + "passWithNoTests": true + }, + "outputs": ["coverage/packages/webpack"] + }, + "build-base": { + "executor": "@nrwl/js:tsc", + "options": { + "outputPath": "build/packages/webpack", + "tsConfig": "packages/webpack/tsconfig.lib.json", + "main": "packages/webpack/index.ts", + "updateBuildableProjectDepsInPackageJson": false, + "assets": [ + { + "input": "packages/webpack", + "glob": "**/files/**", + "output": "/" + }, + { + "input": "packages/webpack", + "glob": "**/files/**/.gitkeep", + "output": "/" + }, + { + "input": "packages/webpack", + "glob": "**/*.json", + "ignore": ["**/tsconfig*.json", "project.json", ".eslintrc.json"], + "output": "/" + }, + { + "input": "packages/webpack", + "glob": "**/*.js", + "ignore": ["**/jest.config.js"], + "output": "/" + }, + { + "input": "packages/webpack", + "glob": "**/*.d.ts", + "output": "/" + }, + { + "input": "", + "glob": "LICENSE", + "output": "/" + } + ] + }, + "outputs": ["{options.outputPath}"] + }, + "build": { + "executor": "nx:run-commands", + "outputs": ["build/packages/webpack"], + "options": { + "command": "node ./scripts/copy-readme.js webpack" + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "options": { + "lintFilePatterns": [ + "packages/webpack/**/*.ts", + "packages/webpack/**/*.spec.ts", + "packages/webpack/**/*_spec.ts", + "packages/webpack/**/*.spec.tsx", + "packages/webpack/**/*.spec.js", + "packages/webpack/**/*.spec.jsx", + "packages/webpack/**/*.d.ts", + "packages/webpack/**/executors/**/schema.json", + "packages/webpack/**/generators/**/schema.json", + "packages/webpack/generators.json", + "packages/webpack/executors.json", + "packages/webpack/package.json", + "packages/webpack/migrations.json" + ] + }, + "outputs": ["{options.outputFile}"] + } + } +} diff --git a/packages/webpack/src/executors/dev-server/compat.ts b/packages/webpack/src/executors/dev-server/compat.ts new file mode 100644 index 0000000000000..a587ba361b107 --- /dev/null +++ b/packages/webpack/src/executors/dev-server/compat.ts @@ -0,0 +1,5 @@ +import { convertNxExecutor } from '@nrwl/devkit'; + +import devServerExecutor from './dev-server.impl'; + +export default convertNxExecutor(devServerExecutor); diff --git a/packages/webpack/src/executors/dev-server/dev-server.impl.ts b/packages/webpack/src/executors/dev-server/dev-server.impl.ts new file mode 100644 index 0000000000000..085615b7228d0 --- /dev/null +++ b/packages/webpack/src/executors/dev-server/dev-server.impl.ts @@ -0,0 +1,119 @@ +import * as webpack from 'webpack'; +import { + ExecutorContext, + parseTargetString, + readTargetOptions, +} from '@nrwl/devkit'; + +import { eachValueFrom } from '@nrwl/devkit/src/utils/rxjs-for-await'; +import { map, tap } from 'rxjs/operators'; +import * as WebpackDevServer from 'webpack-dev-server'; + +import { getDevServerConfig } from './lib/get-dev-server-config'; +import { + calculateProjectDependencies, + createTmpTsConfig, +} from '@nrwl/workspace/src/utilities/buildable-libs-utils'; +import { runWebpackDevServer } from '../../utils/run-webpack'; +import { resolveCustomWebpackConfig } from '../../utils/webpack/custom-webpack'; +import { normalizeOptions } from '../webpack/lib/normalize-options'; +import { getEmittedFiles } from '../webpack/lib/get-emitted-files'; +import { WebpackExecutorOptions } from '../webpack/schema'; +import { WebDevServerOptions } from './schema'; + +export async function* devServerExecutor( + serveOptions: WebDevServerOptions, + context: ExecutorContext +) { + const { root: projectRoot, sourceRoot } = + context.workspace.projects[context.projectName]; + const buildOptions = normalizeOptions( + getBuildOptions(serveOptions, context), + context.root, + sourceRoot + ); + + if (!buildOptions.index) { + throw new Error( + `Cannot run dev-server without "index" option. Check the build options for ${context.projectName}.` + ); + } + + if (!buildOptions.buildLibsFromSource) { + const { target, dependencies } = calculateProjectDependencies( + context.projectGraph, + context.root, + context.projectName, + 'build', // should be generalized + context.configurationName + ); + buildOptions.tsConfig = createTmpTsConfig( + buildOptions.tsConfig, + context.root, + target.data.root, + dependencies + ); + } + + let webpackConfig = getDevServerConfig(context, buildOptions, serveOptions); + + if (buildOptions.webpackConfig) { + let customWebpack = resolveCustomWebpackConfig( + buildOptions.webpackConfig, + buildOptions.tsConfig + ); + + if (typeof customWebpack.then === 'function') { + customWebpack = await customWebpack; + } + + webpackConfig = await customWebpack(webpackConfig, { + buildOptions, + configuration: serveOptions.buildTarget.split(':')[2], + }); + } + + return yield* eachValueFrom( + runWebpackDevServer(webpackConfig, webpack, WebpackDevServer).pipe( + tap(({ stats }) => { + console.info(stats.toString((webpackConfig as any).stats)); + }), + map(({ baseUrl, stats }) => { + return { + baseUrl, + emittedFiles: getEmittedFiles(stats), + success: !stats.hasErrors(), + }; + }) + ) + ); +} + +function getBuildOptions( + options: WebDevServerOptions, + context: ExecutorContext +): WebpackExecutorOptions { + const target = parseTargetString(options.buildTarget); + + const overrides: Partial = { + watch: false, + }; + if (options.maxWorkers) { + overrides.maxWorkers = options.maxWorkers; + } + if (options.memoryLimit) { + overrides.memoryLimit = options.memoryLimit; + } + if (options.baseHref) { + overrides.baseHref = options.baseHref; + } + + const buildOptions = readTargetOptions(target, context); + + return { + ...buildOptions, + ...overrides, + }; +} + +export default devServerExecutor; diff --git a/packages/web/src/utils/devserver.config.ts b/packages/webpack/src/executors/dev-server/lib/get-dev-server-config.ts similarity index 73% rename from packages/web/src/utils/devserver.config.ts rename to packages/webpack/src/executors/dev-server/lib/get-dev-server-config.ts index 3e4705a8ea090..665a14f0e13e1 100644 --- a/packages/web/src/utils/devserver.config.ts +++ b/packages/webpack/src/executors/dev-server/lib/get-dev-server-config.ts @@ -1,29 +1,27 @@ -import { logger } from '@nrwl/devkit'; +import { ExecutorContext, logger } from '@nrwl/devkit'; import type { Configuration as WebpackConfiguration } from 'webpack'; import type { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server'; import * as path from 'path'; import { basename, resolve } from 'path'; -import { getWebConfig } from './web.config'; -import { WebWebpackExecutorOptions } from '../executors/webpack/webpack.impl'; -import { WebDevServerOptions } from '../executors/dev-server/dev-server.impl'; +import { getWebpackConfig } from '../../webpack/lib/get-webpack-config'; +import { WebDevServerOptions } from '../schema'; import { buildServePath } from './serve-path'; -import { OptimizationOptions } from './shared-models'; import { readFileSync } from 'fs-extra'; -import { IndexHtmlWebpackPlugin } from './/webpack/plugins/index-html-webpack-plugin'; -import { generateEntryPoints } from './webpack/package-chunk-sort'; +import { generateEntryPoints } from '../../../utils//webpack/package-chunk-sort'; +import { IndexHtmlWebpackPlugin } from '../../../utils/webpack/plugins/index-html-webpack-plugin'; +import { NormalizedWebpackExecutorOptions } from '../../webpack/schema'; export function getDevServerConfig( - workspaceRoot: string, - projectRoot: string, - sourceRoot: string, - buildOptions: WebWebpackExecutorOptions, + context: ExecutorContext, + buildOptions: NormalizedWebpackExecutorOptions, serveOptions: WebDevServerOptions ): Partial { - const webpackConfig = getWebConfig( - workspaceRoot, - projectRoot, - sourceRoot, + const workspaceRoot = context.root; + const { root: projectRoot, sourceRoot } = + context.workspace.projects[context.projectName]; + const webpackConfig = getWebpackConfig( + context, buildOptions, true, typeof buildOptions.optimization === 'boolean' @@ -65,12 +63,20 @@ export function getDevServerConfig( function getDevServerPartial( root: string, options: WebDevServerOptions, - buildOptions: WebWebpackExecutorOptions + buildOptions: NormalizedWebpackExecutorOptions ): WebpackDevServerConfiguration { const servePath = buildServePath(buildOptions); - const { scripts: scriptsOptimization, styles: stylesOptimization } = - (buildOptions.optimization || {}) as OptimizationOptions; + let scriptsOptimization: boolean; + let stylesOptimization: boolean; + if (typeof buildOptions.optimization === 'boolean') { + scriptsOptimization = stylesOptimization = buildOptions.optimization; + } else if (buildOptions.optimization) { + scriptsOptimization = buildOptions.optimization.scripts; + stylesOptimization = buildOptions.optimization.styles; + } else { + scriptsOptimization = stylesOptimization = false; + } const config: WebpackDevServerConfiguration = { host: options.host, diff --git a/packages/web/src/utils/serve-path.ts b/packages/webpack/src/executors/dev-server/lib/serve-path.ts similarity index 89% rename from packages/web/src/utils/serve-path.ts rename to packages/webpack/src/executors/dev-server/lib/serve-path.ts index 8757646b07e6c..3e6cadb9b8762 100644 --- a/packages/web/src/utils/serve-path.ts +++ b/packages/webpack/src/executors/dev-server/lib/serve-path.ts @@ -1,6 +1,8 @@ -import { WebWebpackExecutorOptions } from '../executors/webpack/webpack.impl'; +import type { NormalizedWebpackExecutorOptions } from '../../webpack/schema'; -export function buildServePath(browserOptions: WebWebpackExecutorOptions) { +export function buildServePath( + browserOptions: NormalizedWebpackExecutorOptions +) { let servePath = _findDefaultServePath(browserOptions.baseHref, browserOptions.deployUrl) || '/'; diff --git a/packages/webpack/src/executors/dev-server/schema.d.ts b/packages/webpack/src/executors/dev-server/schema.d.ts new file mode 100644 index 0000000000000..99d513c6cd5b7 --- /dev/null +++ b/packages/webpack/src/executors/dev-server/schema.d.ts @@ -0,0 +1,18 @@ +export interface WebDevServerOptions { + host: string; + port: number; + publicHost?: string; + ssl: boolean; + sslKey?: string; + sslCert?: string; + proxyConfig?: string; + buildTarget: string; + open: boolean; + liveReload: boolean; + hmr: boolean; + watch: boolean; + allowedHosts: string; + maxWorkers?: number; + memoryLimit?: number; + baseHref?: string; +} diff --git a/packages/webpack/src/executors/dev-server/schema.json b/packages/webpack/src/executors/dev-server/schema.json new file mode 100644 index 0000000000000..a9480d9578fdd --- /dev/null +++ b/packages/webpack/src/executors/dev-server/schema.json @@ -0,0 +1,75 @@ +{ + "title": "Web Dev Server", + "description": "Serve a web application.", + "cli": "nx", + "type": "object", + "properties": { + "buildTarget": { + "type": "string", + "description": "Target which builds the application." + }, + "port": { + "type": "number", + "description": "Port to listen on.", + "default": 4200 + }, + "host": { + "type": "string", + "description": "Host to listen on.", + "default": "localhost" + }, + "ssl": { + "type": "boolean", + "description": "Serve using `HTTPS`.", + "default": false + }, + "sslKey": { + "type": "string", + "description": "SSL key to use for serving `HTTPS`." + }, + "sslCert": { + "type": "string", + "description": "SSL certificate to use for serving `HTTPS`." + }, + "watch": { + "type": "boolean", + "description": "Watches for changes and rebuilds application.", + "default": true + }, + "liveReload": { + "type": "boolean", + "description": "Whether to reload the page on change, using live-reload.", + "default": true + }, + "hmr": { + "type": "boolean", + "description": "Enable hot module replacement.", + "default": false + }, + "publicHost": { + "type": "string", + "description": "Public URL where the application will be served." + }, + "open": { + "type": "boolean", + "description": "Open the application in the browser.", + "default": false + }, + "allowedHosts": { + "type": "string", + "description": "This option allows you to whitelist services that are allowed to access the dev server." + }, + "memoryLimit": { + "type": "number", + "description": "Memory limit for type checking service process in `MB`." + }, + "maxWorkers": { + "type": "number", + "description": "Number of workers to use for type checking." + }, + "baseHref": { + "type": "string", + "description": "Base url for the application being built." + } + } +} diff --git a/packages/webpack/src/executors/webpack/compat.ts b/packages/webpack/src/executors/webpack/compat.ts new file mode 100644 index 0000000000000..919858b9b3dd7 --- /dev/null +++ b/packages/webpack/src/executors/webpack/compat.ts @@ -0,0 +1,5 @@ +import { convertNxExecutor } from '@nrwl/devkit'; + +import { webpackExecutor } from './webpack.impl'; + +export default convertNxExecutor(webpackExecutor); diff --git a/packages/webpack/src/executors/webpack/lib/get-emitted-files.ts b/packages/webpack/src/executors/webpack/lib/get-emitted-files.ts new file mode 100644 index 0000000000000..fb2554041ed33 --- /dev/null +++ b/packages/webpack/src/executors/webpack/lib/get-emitted-files.ts @@ -0,0 +1,38 @@ +import type { Stats } from 'webpack'; +import { extname } from 'path'; + +import { EmittedFile } from '../../../utils/models'; + +export function getEmittedFiles(stats: Stats): EmittedFile[] { + const { compilation } = stats; + const files: EmittedFile[] = []; + // adds all chunks to the list of emitted files such as lazy loaded modules + for (const chunk of compilation.chunks) { + for (const file of chunk.files) { + files.push({ + // The id is guaranteed to exist at this point in the compilation process + // tslint:disable-next-line: no-non-null-assertion + id: chunk.id.toString(), + name: chunk.name, + file, + extension: extname(file), + initial: chunk.isOnlyInitial(), + }); + } + } + // other all files + for (const file of Object.keys(compilation.assets)) { + files.push({ + file, + extension: extname(file), + initial: false, + asset: true, + }); + } + // dedupe + return files.filter( + ({ file, name }, index) => + files.findIndex((f) => f.file === file && (!name || name === f.name)) === + index + ); +} diff --git a/packages/web/src/utils/web.config.ts b/packages/webpack/src/executors/webpack/lib/get-webpack-config.ts similarity index 76% rename from packages/web/src/utils/web.config.ts rename to packages/webpack/src/executors/webpack/lib/get-webpack-config.ts index 84b645a821f53..c04f968d60d0a 100644 --- a/packages/web/src/utils/web.config.ts +++ b/packages/webpack/src/executors/webpack/lib/get-webpack-config.ts @@ -3,16 +3,16 @@ import { posix, resolve } from 'path'; import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { ScriptTarget } from 'typescript'; import { getHashDigest, interpolateName } from 'loader-utils'; -import { Configuration } from 'webpack'; +import type { Configuration } from 'webpack'; -import { WebWebpackExecutorOptions } from '../executors/webpack/webpack.impl'; -import { convertBuildOptions } from './normalize'; +import { NormalizedWebpackExecutorOptions } from '../schema'; // TODO(jack): These should be inlined in a single function so it is easier to understand -import { getBaseWebpackPartial } from './config'; -import { getBrowserConfig } from './webpack/partials/browser'; -import { getCommonConfig } from './webpack/partials/common'; -import { getStylesConfig } from './webpack/partials/styles'; +import { getBaseWebpackPartial } from '../../../utils/config'; +import { getBrowserConfig } from '../../../utils/webpack/partials/browser'; +import { getCommonConfig } from '../../../utils/webpack/partials/common'; +import { getStylesConfig } from '../../../utils/webpack/partials/styles'; +import { ExecutorContext } from '@nrwl/devkit'; import MiniCssExtractPlugin = require('mini-css-extract-plugin'); import webpackMerge = require('webpack-merge'); import postcssImports = require('postcss-import'); @@ -25,16 +25,32 @@ interface PostcssOptions { config?: string; } -export function getWebConfig( - workspaceRoot, - projectRoot, - sourceRoot, - options: WebWebpackExecutorOptions, +interface GetWebpackConfigOverrides { + root: string; + sourceRoot: string; + configuration?: string; +} + +export function getWebpackConfig( + context: ExecutorContext, + options: NormalizedWebpackExecutorOptions, esm?: boolean, isScriptOptimizeOn?: boolean, - configuration?: string -) { + overrides?: GetWebpackConfigOverrides +): Configuration { const tsConfig = readTsConfig(options.tsConfig); + const workspaceRoot = context.root; + + let sourceRoot: string; + let projectRoot: string; + if (overrides) { + projectRoot = overrides.root; + sourceRoot = overrides.sourceRoot; + } else { + const project = context.workspace.projects[context.projectName]; + projectRoot = project.root; + sourceRoot = project.sourceRoot; + } if (isScriptOptimizeOn) { // Angular CLI uses an environment variable (NG_BUILD_DIFFERENTIAL_FULL) @@ -56,43 +72,53 @@ export function getWebConfig( // TODO(jack): Replace merge behavior with an inlined config so it is easier to understand. return webpackMerge.merge([ _getBaseWebpackPartial( + context, options, esm, isScriptOptimizeOn, tsConfig.options.emitDecoratorMetadata, - configuration - ), - getPolyfillsPartial( - options.polyfills, - options.es2015Polyfills, - esm, - isScriptOptimizeOn - ), - getStylesPartial( - wco.root, - wco.projectRoot, - wco.buildOptions, - options.extractCss, - options.postcssConfig + overrides ), + options.target === 'web' + ? getPolyfillsPartial( + options.polyfills, + options.es2015Polyfills, + esm, + isScriptOptimizeOn + ) + : {}, + options.target === 'web' + ? getStylesPartial( + wco.root, + wco.projectRoot, + wco.buildOptions, + options.extractCss, + options.postcssConfig + ) + : {}, getCommonPartial(wco), - getBrowserConfig(wco), + options.target === 'web' ? getBrowserConfig(wco) : {}, ]); } function _getBaseWebpackPartial( - options: WebWebpackExecutorOptions, + context: ExecutorContext, + options: NormalizedWebpackExecutorOptions, esm: boolean, isScriptOptimizeOn: boolean, emitDecoratorMetadata: boolean, - configuration?: string + overrides?: GetWebpackConfigOverrides ) { - let partial = getBaseWebpackPartial(options, { - esm, - isScriptOptimizeOn, - emitDecoratorMetadata, - configuration, - }); + let partial = getBaseWebpackPartial( + options, + { + esm, + isScriptOptimizeOn, + emitDecoratorMetadata, + configuration: overrides?.configuration ?? context.configurationName, + }, + context + ); delete partial.resolve.mainFields; return partial; } @@ -256,7 +282,7 @@ export function getPolyfillsPartial( // Safari 10.1 supports