feat(webpack): add webpack plugin (#11966)
This commit is contained in:
parent
a914f78701
commit
f49769a34a
@ -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,
|
||||
|
||||
@ -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",
|
||||
|
||||
780
docs/generated/packages/webpack.json
Normal file
780
docs/generated/packages/webpack.json
Normal file
@ -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 '<projectRoot>/src/main.ts'."
|
||||
},
|
||||
"tsConfig": {
|
||||
"type": "string",
|
||||
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/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 `<https://webpack.github.io/analyse>`.",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -80,7 +80,7 @@ describe('React Applications', () => {
|
||||
checkFilesExist(...filesToCheck);
|
||||
|
||||
expect(readFile(`dist/apps/${appName}/index.html`)).toContain(
|
||||
`<script src="main.esm.js" type="module"></script><script src="main.es5.js" nomodule defer></script>`
|
||||
`<script src="main.js" type="module"></script><script src="main.es5.js" nomodule defer></script>`
|
||||
);
|
||||
}, 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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
11
e2e/webpack/jest.config.ts
Normal file
11
e2e/webpack/jest.config.ts
Normal file
@ -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: '<rootDir>/tsconfig.spec.json' } },
|
||||
displayName: 'e2e-webpack',
|
||||
preset: '../../jest.preset.js',
|
||||
};
|
||||
34
e2e/webpack/project.json
Normal file
34
e2e/webpack/project.json
Normal file
@ -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"]
|
||||
}
|
||||
58
e2e/webpack/src/webpack.test.ts
Normal file
58
e2e/webpack/src/webpack.test.ts
Normal file
@ -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);
|
||||
});
|
||||
13
e2e/webpack/tsconfig.json
Normal file
13
e2e/webpack/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node", "jest"]
|
||||
},
|
||||
"include": [],
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
20
e2e/webpack/tsconfig.spec.json
Normal file
20
e2e/webpack/tsconfig.spec.json
Normal file
@ -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"
|
||||
]
|
||||
}
|
||||
@ -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',
|
||||
|
||||
4
nx-dev/nx-dev/public/images/icons/webpack.svg
Normal file
4
nx-dev/nx-dev/public/images/icons/webpack.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<title>webpack</title>
|
||||
<path d="M28.021 24.161l-11.552 6.505v-5.068l7.198-3.943zM28.813 23.448v-13.609l-4.229 2.432v8.745zM3.901 24.161l11.552 6.505v-5.068l-7.198-3.943zM3.109 23.448v-13.609l4.229 2.432v8.745zM3.604 8.958l11.849-6.672v4.901l-7.646 4.188zM28.318 8.958l-11.849-6.672v4.901l7.646 4.188zM15.453 24.448l-7.099-3.891v-7.703l7.099 4.083zM16.469 24.448l7.099-3.891v-7.703l-7.099 4.083zM8.833 11.964l7.13-3.901 7.13 3.901-7.13 4.099z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 556 B |
@ -18,5 +18,6 @@ export const iconsMap: Record<string, string> = {
|
||||
'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',
|
||||
};
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 () => {
|
||||
(<any>runWebpack).mockReturnValue(of({ hasErrors: () => false }));
|
||||
context = {
|
||||
root: '/root',
|
||||
cwd: '/root',
|
||||
projectName: 'my-app',
|
||||
targetName: 'build',
|
||||
workspace: {
|
||||
version: 2,
|
||||
projects: {
|
||||
'my-app': <any>{
|
||||
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',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
|
||||
@ -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',
|
||||
},
|
||||
|
||||
@ -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`,
|
||||
},
|
||||
|
||||
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export const before = () => {};
|
||||
@ -1 +0,0 @@
|
||||
export const after = () => {};
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
@ -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<typeof loadTsTransformers>) {
|
||||
expect(result.hasPlugin).toEqual(false);
|
||||
expect(result.compilerPluginHooks).toEqual({
|
||||
beforeHooks: [],
|
||||
afterHooks: [],
|
||||
afterDeclarationsHooks: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
(<any>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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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),
|
||||
]);
|
||||
}
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
@ -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<Node>
|
||||
| CustomTransformerFactory;
|
||||
|
||||
export interface TransformerPlugin {
|
||||
name: string;
|
||||
options: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type TransformerEntry = string | TransformerPlugin;
|
||||
|
||||
export interface CompilerPlugin {
|
||||
before?: (
|
||||
options?: Record<string, unknown>,
|
||||
program?: Program
|
||||
) => TransformerFactory;
|
||||
after?: (
|
||||
options?: Record<string, unknown>,
|
||||
program?: Program
|
||||
) => TransformerFactory;
|
||||
afterDeclarations?: (
|
||||
options?: Record<string, unknown>,
|
||||
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> | 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[];
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -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<WebWebpackExecutorOptions>(target, context);
|
||||
): WebpackExecutorOptions {
|
||||
const options = readTargetOptions<WebpackExecutorOptions>(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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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: () => ({}),
|
||||
};
|
||||
|
||||
@ -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');
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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[] = [];
|
||||
|
||||
@ -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`,
|
||||
|
||||
@ -16,7 +16,7 @@ export async function updateProjectConfig(
|
||||
const found = await findBuildConfig(tree, {
|
||||
project: options.project,
|
||||
buildTarget: options.buildTarget,
|
||||
validExecutorNames: new Set<string>(['@nrwl/web:webpack']),
|
||||
validExecutorNames: new Set<string>(['@nrwl/webpack:webpack']),
|
||||
});
|
||||
|
||||
assetValidConfig(found.config);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -49,7 +49,7 @@ describe('setup-tailwind', () => {
|
||||
sourceRoot: 'apps/example/src',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@nrwl/web:webpack',
|
||||
executor: '@nrwl/webpack:webpack',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
|
||||
@ -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<WebRollupOptions>(
|
||||
forEachExecutorOptions<any>(
|
||||
tree,
|
||||
'@nrwl/web:rollup',
|
||||
(options: any, projectName, targetName, configurationName) => {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,134 +1,8 @@
|
||||
import * as webpack from 'webpack';
|
||||
import {
|
||||
ExecutorContext,
|
||||
parseTargetString,
|
||||
readTargetOptions,
|
||||
} from '@nrwl/devkit';
|
||||
/**
|
||||
* This is here for backwards-compat.
|
||||
* TODO(jack): remove in Nx 16.
|
||||
*/
|
||||
import { devServerExecutor, WebDevServerOptions } from '@nrwl/webpack';
|
||||
|
||||
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<WebWebpackExecutorOptions> = {
|
||||
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 { devServerExecutor, WebDevServerOptions };
|
||||
export default devServerExecutor;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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';
|
||||
webpackExecutor,
|
||||
WebpackExecutorOptions as WebWebpackExecutorOptions,
|
||||
} from '@nrwl/webpack';
|
||||
|
||||
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<Configuration[]> {
|
||||
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] };
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default run;
|
||||
export { webpackExecutor, WebWebpackExecutorOptions };
|
||||
export default webpackExecutor;
|
||||
|
||||
@ -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',
|
||||
});
|
||||
|
||||
@ -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<WebWebpackExecutorOptions> = {
|
||||
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,
|
||||
},
|
||||
];
|
||||
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,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
updateProjectConfiguration(tree, options.projectName, project);
|
||||
} else {
|
||||
throw new Error('Unsupported bundler type');
|
||||
}
|
||||
}
|
||||
|
||||
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`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
...project,
|
||||
targets: {
|
||||
...project.targets,
|
||||
serve: serveTarget,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function addProject(tree: Tree, options: NormalizedSchema) {
|
||||
async function addProject(tree: Tree, options: NormalizedSchema) {
|
||||
const targets: Record<string, TargetConfiguration> = {};
|
||||
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,
|
||||
|
||||
@ -4,6 +4,7 @@ export interface Schema {
|
||||
name: string;
|
||||
prefix?: string;
|
||||
style?: string;
|
||||
bundler?: 'webpack' | 'none';
|
||||
compiler?: 'babel' | 'swc';
|
||||
skipFormat?: boolean;
|
||||
directory?: string;
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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<NxJsonConfiguration>(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<NxJsonConfiguration>(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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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) {
|
||||
|
||||
1
packages/web/src/generators/init/schema.d.ts
vendored
1
packages/web/src/generators/init/schema.d.ts
vendored
@ -1,4 +1,5 @@
|
||||
export interface Schema {
|
||||
bundler?: 'webpack' | 'none';
|
||||
unitTestRunner?: 'jest' | 'none';
|
||||
e2eTestRunner?: 'cypress' | 'none';
|
||||
skipFormat?: boolean;
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
@ -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,
|
||||
},
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
@ -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';
|
||||
|
||||
@ -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(
|
||||
<BuildBuilderOptions>{
|
||||
...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);
|
||||
});
|
||||
});
|
||||
@ -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<T extends BuildBuilderOptions>(
|
||||
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 <any>{
|
||||
...options,
|
||||
buildOptimizer: options.optimization,
|
||||
forkTypeChecker: false,
|
||||
lazyModules: [] as string[],
|
||||
};
|
||||
}
|
||||
|
||||
export type NormalizedEntryPoint = Required<Omit<ExtraEntryPointClass, 'lazy'>>;
|
||||
|
||||
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';
|
||||
|
||||
@ -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<any> {
|
||||
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
|
||||
);
|
||||
}
|
||||
@ -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<T = WebWebpackExecutorOptions> {
|
||||
root: string;
|
||||
projectRoot: string;
|
||||
sourceRoot?: string;
|
||||
buildOptions: T;
|
||||
tsConfig: any;
|
||||
tsConfigPath: string;
|
||||
supportES2015: boolean;
|
||||
}
|
||||
25
packages/webpack/.eslintrc.json
Normal file
25
packages/webpack/.eslintrc.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
13
packages/webpack/README.md
Normal file
13
packages/webpack/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
<p style="text-align: center;"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx.png" width="600" alt="Nx - Smart, Fast and Extensible Build System"></p>
|
||||
|
||||
{{links}}
|
||||
|
||||
<hr>
|
||||
|
||||
# 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}}
|
||||
26
packages/webpack/executors.json
Normal file
26
packages/webpack/executors.json
Normal file
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
33
packages/webpack/generators.json
Normal file
33
packages/webpack/generators.json
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
7
packages/webpack/index.ts
Normal file
7
packages/webpack/index.ts
Normal file
@ -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';
|
||||
11
packages/webpack/jest.config.ts
Normal file
11
packages/webpack/jest.config.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': 'ts-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
|
||||
globals: { 'ts-jest': { tsconfig: '<rootDir>/tsconfig.spec.json' } },
|
||||
displayName: 'webpack',
|
||||
testEnvironment: 'node',
|
||||
preset: '../../jest.preset.js',
|
||||
};
|
||||
79
packages/webpack/package.json
Normal file
79
packages/webpack/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
87
packages/webpack/project.json
Normal file
87
packages/webpack/project.json
Normal file
@ -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}"]
|
||||
}
|
||||
}
|
||||
}
|
||||
5
packages/webpack/src/executors/dev-server/compat.ts
Normal file
5
packages/webpack/src/executors/dev-server/compat.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { convertNxExecutor } from '@nrwl/devkit';
|
||||
|
||||
import devServerExecutor from './dev-server.impl';
|
||||
|
||||
export default convertNxExecutor(devServerExecutor);
|
||||
119
packages/webpack/src/executors/dev-server/dev-server.impl.ts
Normal file
119
packages/webpack/src/executors/dev-server/dev-server.impl.ts
Normal file
@ -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<WebpackExecutorOptions> = {
|
||||
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;
|
||||
@ -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<WebpackConfiguration> {
|
||||
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,
|
||||
@ -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) ||
|
||||
'/';
|
||||
18
packages/webpack/src/executors/dev-server/schema.d.ts
vendored
Normal file
18
packages/webpack/src/executors/dev-server/schema.d.ts
vendored
Normal file
@ -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;
|
||||
}
|
||||
75
packages/webpack/src/executors/dev-server/schema.json
Normal file
75
packages/webpack/src/executors/dev-server/schema.json
Normal file
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
5
packages/webpack/src/executors/webpack/compat.ts
Normal file
5
packages/webpack/src/executors/webpack/compat.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { convertNxExecutor } from '@nrwl/devkit';
|
||||
|
||||
import { webpackExecutor } from './webpack.impl';
|
||||
|
||||
export default convertNxExecutor(webpackExecutor);
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
@ -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 <script type="module"> but not <script nomodule>.
|
||||
// Need to patch it up so the browser doesn't load both sets.
|
||||
config.entry.polyfills = [
|
||||
require.resolve('@nrwl/web/src/utils/webpack/safari-nomodule.js'),
|
||||
require.resolve('@nrwl/webpack/src/utils/webpack/safari-nomodule.js'),
|
||||
...(polyfills ? [polyfills] : []),
|
||||
];
|
||||
} else if (es2015Polyfills && !esm && isScriptOptimizeOn) {
|
||||
@ -304,3 +330,15 @@ export function getCSSModuleLocalIdent(
|
||||
// Remove the .module that appears in every classname when based on the file and replace all "." with "_".
|
||||
return className.replace('.module_', '_').replace(/\./g, '_');
|
||||
}
|
||||
|
||||
export function convertBuildOptions(
|
||||
buildOptions: NormalizedWebpackExecutorOptions
|
||||
): any {
|
||||
const options = buildOptions as any;
|
||||
return {
|
||||
...options,
|
||||
buildOptimizer: options.optimization,
|
||||
forkTypeChecker: false,
|
||||
lazyModules: [] as string[],
|
||||
};
|
||||
}
|
||||
@ -1,57 +1,73 @@
|
||||
import { resolve, dirname, relative, basename } from 'path';
|
||||
import {
|
||||
AdditionalEntryPoint,
|
||||
BuildNodeBuilderOptions,
|
||||
NormalizedBuildNodeBuilderOptions,
|
||||
} from './types';
|
||||
import { basename, dirname, relative, resolve } from 'path';
|
||||
import { statSync } from 'fs';
|
||||
import { normalizePath } from '@nrwl/devkit';
|
||||
|
||||
export interface FileReplacement {
|
||||
replace: string;
|
||||
with: string;
|
||||
}
|
||||
import type {
|
||||
AssetGlobPattern,
|
||||
FileReplacement,
|
||||
WebpackExecutorOptions,
|
||||
NormalizedWebpackExecutorOptions,
|
||||
} from '../schema';
|
||||
|
||||
export function normalizeBuildOptions(
|
||||
options: BuildNodeBuilderOptions,
|
||||
export function normalizeOptions(
|
||||
options: WebpackExecutorOptions,
|
||||
root: string,
|
||||
sourceRoot: string,
|
||||
projectRoot: string
|
||||
): NormalizedBuildNodeBuilderOptions {
|
||||
sourceRoot: string
|
||||
): NormalizedWebpackExecutorOptions {
|
||||
return {
|
||||
...options,
|
||||
root,
|
||||
sourceRoot,
|
||||
projectRoot,
|
||||
target: options.target ?? 'web',
|
||||
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: options.webpackConfig
|
||||
? []
|
||||
.concat(options.webpackConfig)
|
||||
.map((path) => normalizePluginPath(path, root))
|
||||
: [],
|
||||
additionalEntryPoints: normalizeAdditionalEntries(
|
||||
root,
|
||||
options.additionalEntryPoints ?? []
|
||||
),
|
||||
outputFileName: options.outputFileName ?? 'main.js',
|
||||
deleteOutputPath: options.deleteOutputPath ?? true,
|
||||
webpackConfig: normalizePluginPath(options.webpackConfig, root),
|
||||
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,
|
||||
};
|
||||
}
|
||||
function normalizeFileReplacements(
|
||||
root: string,
|
||||
fileReplacements: FileReplacement[]
|
||||
): FileReplacement[] {
|
||||
return fileReplacements.map((fileReplacement) => ({
|
||||
replace: resolve(root, fileReplacement.replace),
|
||||
with: resolve(root, fileReplacement.with),
|
||||
}));
|
||||
}
|
||||
|
||||
function normalizeAssets(
|
||||
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
|
||||
): any[] {
|
||||
if (!Array.isArray(assets)) {
|
||||
return [];
|
||||
}
|
||||
): AssetGlobPattern[] {
|
||||
return assets.map((asset) => {
|
||||
if (typeof asset === 'string') {
|
||||
const resolvedAssetPath = resolve(root, asset);
|
||||
const assetPath = normalizePath(asset);
|
||||
const resolvedAssetPath = resolve(root, assetPath);
|
||||
const resolvedSourceRoot = resolve(root, sourceRoot);
|
||||
|
||||
if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) {
|
||||
@ -78,7 +94,8 @@ function normalizeAssets(
|
||||
);
|
||||
}
|
||||
|
||||
const resolvedAssetPath = resolve(root, asset.input);
|
||||
const assetPath = normalizePath(asset.input);
|
||||
const resolvedAssetPath = resolve(root, assetPath);
|
||||
return {
|
||||
...asset,
|
||||
input: resolvedAssetPath,
|
||||
@ -88,34 +105,3 @@ function normalizeAssets(
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeFileReplacements(
|
||||
root: string,
|
||||
fileReplacements: FileReplacement[]
|
||||
): FileReplacement[] {
|
||||
return fileReplacements.map((fileReplacement) => ({
|
||||
replace: resolve(root, fileReplacement.replace),
|
||||
with: resolve(root, fileReplacement.with),
|
||||
}));
|
||||
}
|
||||
|
||||
function normalizePluginPath(path: string, root: string) {
|
||||
try {
|
||||
return require.resolve(path);
|
||||
} catch {
|
||||
return resolve(root, path);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeAdditionalEntries(
|
||||
root: string,
|
||||
additionalEntries: AdditionalEntryPoint[]
|
||||
) {
|
||||
return additionalEntries.map(
|
||||
({ entryName, entryPath }) =>
|
||||
({
|
||||
entryName,
|
||||
entryPath: resolve(root, entryPath),
|
||||
} as AdditionalEntryPoint)
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user