@nx/webpack 24.0.0-pr.37405.acdaf31 → 24.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.js +0 -1
  3. package/dist/internal.d.ts +3 -0
  4. package/dist/internal.js +7 -1
  5. package/dist/ready-check.d.ts +1 -0
  6. package/dist/ready-check.js +9 -0
  7. package/dist/ready-signal.d.ts +1 -0
  8. package/dist/ready-signal.js +5 -0
  9. package/dist/src/executors/dev-server/dev-server.impl.js +38 -30
  10. package/dist/src/executors/ssr-dev-server/schema.json +2 -1
  11. package/dist/src/executors/ssr-dev-server/ssr-dev-server.impl.js +2 -0
  12. package/dist/src/generators/configuration/configuration.js +14 -132
  13. package/dist/src/generators/configuration/schema.d.ts +0 -1
  14. package/dist/src/generators/convert-config-to-webpack-plugin/convert-config-to-webpack-plugin.js +97 -57
  15. package/dist/src/generators/convert-config-to-webpack-plugin/lib/extract-webpack-options.d.ts +2 -0
  16. package/dist/src/generators/convert-config-to-webpack-plugin/lib/extract-webpack-options.js +46 -2
  17. package/dist/src/generators/convert-config-to-webpack-plugin/schema.json +2 -2
  18. package/dist/src/generators/convert-to-inferred/convert-to-inferred.js +5 -4
  19. package/dist/src/generators/convert-to-inferred/utils/ast.d.ts +2 -0
  20. package/dist/src/generators/convert-to-inferred/utils/ast.js +13 -0
  21. package/dist/src/generators/convert-to-inferred/utils/build-post-target-transformer.js +25 -5
  22. package/dist/src/generators/convert-to-inferred/utils/serve-post-target-transformer.js +13 -14
  23. package/dist/src/generators/init/init.js +45 -54
  24. package/dist/src/generators/init/schema.d.ts +0 -1
  25. package/dist/src/migrations/update-24-0-0/finish-inferred-conversion.md +11 -0
  26. package/dist/src/migrations/update-24-0-0/inventory.d.ts +10 -0
  27. package/dist/src/migrations/update-24-0-0/inventory.js +55 -0
  28. package/dist/src/migrations/update-24-0-0/migrate-compose-plugins.d.ts +10 -0
  29. package/dist/src/migrations/update-24-0-0/migrate-compose-plugins.js +68 -0
  30. package/dist/src/migrations/update-24-0-0/migrate-compose-plugins.md +98 -0
  31. package/dist/src/migrations/update-24-0-0/remove-deprecated-executors.d.ts +6 -0
  32. package/dist/src/migrations/update-24-0-0/remove-deprecated-executors.js +39 -0
  33. package/dist/src/migrations/update-24-0-0/remove-deprecated-executors.md +92 -0
  34. package/dist/src/plugins/nx-webpack-plugin/lib/normalize-options.d.ts +6 -0
  35. package/dist/src/plugins/nx-webpack-plugin/lib/normalize-options.js +29 -24
  36. package/dist/src/plugins/nx-webpack-plugin/nx-app-webpack-plugin.d.ts +2 -0
  37. package/dist/src/plugins/nx-webpack-plugin/nx-app-webpack-plugin.js +18 -1
  38. package/dist/src/plugins/plugin.js +18 -0
  39. package/dist/src/plugins/use-legacy-nx-plugin/use-legacy-nx-plugin.js +3 -0
  40. package/dist/src/utils/config.d.ts +8 -12
  41. package/dist/src/utils/config.js +15 -68
  42. package/dist/src/utils/deprecation.d.ts +3 -6
  43. package/dist/src/utils/deprecation.js +10 -27
  44. package/dist/src/utils/dev-server-ready-check.d.ts +36 -0
  45. package/dist/src/utils/dev-server-ready-check.js +255 -0
  46. package/dist/src/utils/dev-server-ready-signal.d.ts +29 -0
  47. package/dist/src/utils/dev-server-ready-signal.js +51 -0
  48. package/dist/src/utils/e2e-web-server-info-utils.d.ts +1 -1
  49. package/dist/src/utils/e2e-web-server-info-utils.js +2 -2
  50. package/dist/src/utils/webpack/read-webpack-options.d.ts +1 -1
  51. package/dist/src/utils/webpack/read-webpack-options.js +4 -4
  52. package/dist/src/utils/with-nx.d.ts +5 -9
  53. package/dist/src/utils/with-nx.js +5 -32
  54. package/dist/src/utils/with-web.d.ts +5 -9
  55. package/dist/src/utils/with-web.js +5 -24
  56. package/executors.json +0 -10
  57. package/migrations.json +13 -0
  58. package/package.json +20 -4
  59. package/dist/src/executors/webpack/lib/run-webpack.d.ts +0 -3
  60. package/dist/src/executors/webpack/lib/run-webpack.js +0 -36
  61. package/dist/src/executors/webpack/schema.json +0 -459
  62. package/dist/src/executors/webpack/webpack.impl.d.ts +0 -13
  63. package/dist/src/executors/webpack/webpack.impl.js +0 -117
  64. package/dist/src/utils/has-plugin.d.ts +0 -2
  65. package/dist/src/utils/has-plugin.js +0 -10
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/schema",
3
3
  "$id": "NxWebpackConvertConfigToWebpackPlugin",
4
- "description": "Convert existing Webpack project(s) using `@nx/webpack:webpack` executor that uses `withNx` to use `NxAppWebpackPlugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
4
+ "description": "Convert existing Webpack config(s) that use `withNx` to use `NxAppWebpackPlugin`, for projects on the `@nx/webpack:webpack` executor or the `@nx/webpack/plugin` inference plugin. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
5
5
  "title": "Convert Webpack project using withNx to NxAppWebpackPlugin",
6
6
  "type": "object",
7
7
  "properties": {
8
8
  "project": {
9
9
  "type": "string",
10
- "description": "The project to convert from using the `@nx/webpack:webpack` executor and `withNx` plugin to use `NxAppWebpackPlugin`.",
10
+ "description": "The project whose webpack config to convert from the `withNx` plugin to `NxAppWebpackPlugin`.",
11
11
  "x-priority": "important"
12
12
  },
13
13
  "skipFormat": {
@@ -40,7 +40,7 @@ async function convertToInferred(tree, options) {
40
40
  const convertMessage = [...logger.loggedMessages.values()]
41
41
  .flat()
42
42
  .find((v) => v.includes('@nx/webpack:convert-config-to-webpack-plugin'));
43
- if (convertMessage.length > 0) {
43
+ if (convertMessage?.length > 0) {
44
44
  logger.flushLogs((message) => !convertMessage.includes(message));
45
45
  throw new Error(convertMessage);
46
46
  }
@@ -56,9 +56,10 @@ async function convertToInferred(tree, options) {
56
56
  if (!options.skipFormat) {
57
57
  await (0, devkit_1.formatFiles)(tree);
58
58
  }
59
- return (0, devkit_1.runTasksInSerial)(installCallback, () => {
60
- migrationContext.logger.flushLogs();
61
- });
59
+ // Flushed now rather than in the callback so remove-deprecated-executors, which
60
+ // skips the install callback, still prints these.
61
+ migrationContext.logger.flushLogs();
62
+ return installCallback;
62
63
  }
63
64
  function skipProjectFilterFactory(tree) {
64
65
  return function skipProjectFilter(projectConfiguration) {
@@ -1,3 +1,5 @@
1
1
  import * as ts from 'typescript';
2
2
  export declare function toPropertyAssignment(key: string, value: unknown): ts.PropertyAssignment;
3
3
  export declare function toExpression(value: unknown): ts.Expression;
4
+ /** Whether a top-level `configValues` object written by convert-to-inferred has `key`. */
5
+ export declare function hasMigratedConfigValues(sourceFile: ts.SourceFile, key: string): boolean;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toPropertyAssignment = toPropertyAssignment;
4
4
  exports.toExpression = toExpression;
5
+ exports.hasMigratedConfigValues = hasMigratedConfigValues;
5
6
  const tslib_1 = require("tslib");
6
7
  const ts = tslib_1.__importStar(require("typescript"));
7
8
  function toPropertyAssignment(key, value) {
@@ -38,3 +39,15 @@ function toExpression(value) {
38
39
  return ts.factory.createObjectLiteralExpression(Object.entries(value).map(([key, value]) => toPropertyAssignment(key, value)));
39
40
  }
40
41
  }
42
+ /** Whether a top-level `configValues` object written by convert-to-inferred has `key`. */
43
+ function hasMigratedConfigValues(sourceFile, key) {
44
+ return sourceFile.statements.some((statement) => ts.isVariableStatement(statement) &&
45
+ statement.declarationList.declarations.some((declaration) => ts.isIdentifier(declaration.name) &&
46
+ declaration.name.text === 'configValues' &&
47
+ declaration.initializer !== undefined &&
48
+ ts.isObjectLiteralExpression(declaration.initializer) &&
49
+ declaration.initializer.properties.some((property) => ts.isPropertyAssignment(property) &&
50
+ (ts.isIdentifier(property.name) ||
51
+ ts.isStringLiteral(property.name)) &&
52
+ property.name.text === key)));
53
+ }
@@ -13,6 +13,13 @@ function buildPostTargetTransformerFactory(migrationContext) {
13
13
  projectName: projectDetails.projectName,
14
14
  projectRoot: projectDetails.root,
15
15
  };
16
+ // outputPath moves into the config, where `{options.outputPath}` can't resolve.
17
+ const outputPath = target.options?.outputPath;
18
+ if (target.outputs && typeof outputPath === 'string') {
19
+ target.outputs = target.outputs.map((output) => output.startsWith('{options.outputPath}')
20
+ ? output.replace('{options.outputPath}', `{workspaceRoot}/${outputPath}`)
21
+ : output.replace('{options.outputPath}', outputPath));
22
+ }
16
23
  const { pluginOptions, webpackConfigPath } = processOptions(target, context);
17
24
  updateWebpackConfig(tree, webpackConfigPath, pluginOptions);
18
25
  if (target.outputs) {
@@ -109,6 +116,11 @@ function updateWebpackConfig(tree, webpackConfig, pluginOptions) {
109
116
  sourceFile = (0, tsquery_1.ast)(webpackConfigText);
110
117
  };
111
118
  updateSources();
119
+ // A target kept on its executor is converted again on rerun, and its options
120
+ // are already in the config.
121
+ if ((0, ast_1.hasMigratedConfigValues)(sourceFile, 'build')) {
122
+ return;
123
+ }
112
124
  setOptionsInWebpackConfig(tree, webpackConfigText, sourceFile, webpackConfig, pluginOptions);
113
125
  updateSources();
114
126
  setOptionsInNxWebpackPlugin(tree, webpackConfigText, sourceFile, webpackConfig);
@@ -118,9 +130,17 @@ function updateWebpackConfig(tree, webpackConfig, pluginOptions) {
118
130
  function setOptionsInWebpackConfig(tree, text, sourceFile, webpackConfig, pluginOptions) {
119
131
  const { default: defaultOptions, ...configurationOptions } = pluginOptions;
120
132
  const optionsSelector = 'VariableStatement:has(VariableDeclaration:has(Identifier[name=options]))';
121
- const optionsVariable = (0, tsquery_1.query)(sourceFile, optionsSelector)[0];
122
- // This is assuming the `options` variable will be available since it's what the
123
- // `convert-config-to-webpack-plugin` generates
133
+ let optionsVariable = (0, tsquery_1.query)(sourceFile, optionsSelector)[0];
134
+ if (!optionsVariable) {
135
+ const plugin = (0, tsquery_1.query)(sourceFile, 'NewExpression:has(Identifier[name=NxAppWebpackPlugin])')[0];
136
+ const argument = plugin.arguments?.[0];
137
+ if (argument && !ts.isObjectLiteralExpression(argument)) {
138
+ throw new Error('Move NxAppWebpackPlugin options into an object literal before converting this config.');
139
+ }
140
+ text = `const options = ${argument?.getText(sourceFile) ?? '{}'};\n${text}`;
141
+ sourceFile = (0, tsquery_1.ast)(text);
142
+ optionsVariable = (0, tsquery_1.query)(sourceFile, optionsSelector)[0];
143
+ }
124
144
  let defaultOptionsObject;
125
145
  const optionsObject = (0, tsquery_1.query)(optionsVariable, 'ObjectLiteralExpression')[0];
126
146
  if (optionsObject.properties.length === 0) {
@@ -188,8 +208,8 @@ function setOptionsInNxWebpackPlugin(tree, text, sourceFile, webpackConfig) {
188
208
  function setOptionsInLegacyNxPlugin(tree, text, sourceFile, webpackConfig) {
189
209
  const legacyNxPluginSelector = 'AwaitExpression CallExpression:has(Identifier[name=useLegacyNxPlugin])';
190
210
  const legacyNxPlugin = (0, tsquery_1.query)(sourceFile, legacyNxPluginSelector)[0];
191
- // we're assuming the `useLegacyNxPlugin` function is being called since it's what the `convert-config-to-webpack-plugin` generates
192
- // we've already "ensured" that the `convert-config-to-webpack-plugin` was run by checking for the `NxAppWebpackPlugin` in the project validation
211
+ if (!legacyNxPlugin)
212
+ return;
193
213
  const updatedLegacyNxPlugin = ts.factory.updateCallExpression(legacyNxPlugin, legacyNxPlugin.expression, undefined, [legacyNxPlugin.arguments[0], ts.factory.createIdentifier('buildOptions')]);
194
214
  text = `${text.slice(0, legacyNxPlugin.getStart())}${ts
195
215
  .createPrinter()
@@ -16,7 +16,7 @@ function servePostTargetTransformerFactory(migrationContext) {
16
16
  projectName: projectDetails.projectName,
17
17
  projectRoot: projectDetails.root,
18
18
  };
19
- const { devServerOptions, webpackConfigPath } = await processOptions(tree, target, context);
19
+ const { devServerOptions, webpackConfigPath } = await processOptions(target, context);
20
20
  updateWebpackConfig(tree, webpackConfigPath, devServerOptions, context);
21
21
  if (target.outputs) {
22
22
  (0, internal_1.processTargetOutputs)(target, [], inferredTarget, {
@@ -27,20 +27,14 @@ function servePostTargetTransformerFactory(migrationContext) {
27
27
  return target;
28
28
  };
29
29
  }
30
- async function processOptions(tree, target, context) {
31
- const executorContext = {
32
- cwd: process.cwd(),
33
- nxJsonConfiguration: (0, devkit_1.readJson)(tree, 'nx.json'),
34
- projectGraph: context.projectGraph,
35
- projectName: context.projectName,
36
- projectsConfigurations: Object.entries(context.projectGraph.nodes).reduce((acc, [projectName, project]) => {
37
- acc.projects[projectName] = project.data;
38
- return acc;
39
- }, { version: 1, projects: {} }),
40
- root: context.workspaceRoot,
30
+ async function processOptions(target, context) {
31
+ const buildTarget = (0, devkit_1.parseTargetString)(target.options.buildTarget, context.projectGraph);
32
+ const buildConfiguration = context.projectGraph.nodes[buildTarget.project].data.targets[buildTarget.target];
33
+ // The build executor is removed in v24; do not resolve its schema.
34
+ const buildOptions = {
35
+ ...buildConfiguration.options,
36
+ ...buildConfiguration.configurations?.[buildTarget.configuration ?? buildConfiguration.defaultConfiguration],
41
37
  };
42
- const buildTarget = (0, devkit_1.parseTargetString)(target.options.buildTarget, executorContext);
43
- const buildOptions = (0, devkit_1.readTargetOptions)(buildTarget, executorContext);
44
38
  // it must exist, we validated it in the project filter
45
39
  const webpackConfigPath = buildOptions.webpackConfig;
46
40
  const defaultOptions = extractDevServerOptions(target.options, context);
@@ -183,6 +177,11 @@ function updateWebpackConfig(tree, webpackConfigPath, devServerOptions, context)
183
177
  sourceFile = (0, tsquery_1.ast)(webpackConfigText);
184
178
  };
185
179
  updateSources();
180
+ // A target kept on its executor is converted again on rerun, and its options
181
+ // are already in the config.
182
+ if ((0, ast_1.hasMigratedConfigValues)(sourceFile, 'serve')) {
183
+ return;
184
+ }
186
185
  setOptionsInWebpackConfig(tree, webpackConfigText, sourceFile, webpackConfigPath, devServerOptions);
187
186
  updateSources();
188
187
  setDevServerOptionsInWebpackConfig(tree, webpackConfigText, sourceFile, webpackConfigPath, context);
@@ -7,71 +7,62 @@ const devkit_1 = require("@nx/devkit");
7
7
  const plugin_1 = require("../../plugins/plugin");
8
8
  const versions_1 = require("../../utils/versions");
9
9
  function webpackInitGenerator(tree, schema) {
10
- return webpackInitGeneratorInternal(tree, { addPlugin: false, ...schema });
10
+ return webpackInitGeneratorInternal(tree, schema);
11
11
  }
12
12
  async function webpackInitGeneratorInternal(tree, schema) {
13
13
  (0, versions_1.assertSupportedWebpackVersion)(tree);
14
- const nxJson = (0, devkit_1.readNxJson)(tree);
15
- const addPluginDefault = process.env.NX_ADD_PLUGINS !== 'false' &&
16
- nxJson.useInferencePlugins !== false;
17
- schema.addPlugin ??= addPluginDefault;
18
- if (schema.addPlugin) {
19
- await (0, internal_1.addPlugin)(tree, await (0, devkit_1.createProjectGraphAsync)(), '@nx/webpack/plugin', plugin_1.createNodesV2, {
20
- buildTargetName: [
21
- 'build',
22
- 'webpack:build',
23
- 'build:webpack',
24
- 'webpack-build',
25
- 'build-webpack',
26
- ],
27
- serveTargetName: [
28
- 'serve',
29
- 'webpack:serve',
30
- 'serve:webpack',
31
- 'webpack-serve',
32
- 'serve-webpack',
33
- ],
34
- previewTargetName: [
35
- 'preview',
36
- 'webpack:preview',
37
- 'preview:webpack',
38
- 'webpack-preview',
39
- 'preview-webpack',
40
- ],
41
- buildDepsTargetName: [
42
- 'build-deps',
43
- 'webpack:build-deps',
44
- 'webpack-build-deps',
45
- ],
46
- watchDepsTargetName: [
47
- 'watch-deps',
48
- 'webpack:watch-deps',
49
- 'webpack-watch-deps',
50
- ],
51
- serveStaticTargetName: [
52
- 'serve-static',
53
- 'webpack:serve-static',
54
- 'serve-static:webpack',
55
- 'webpack-serve-static',
56
- 'serve-static-webpack',
57
- ],
58
- }, schema.updatePackageScripts);
59
- }
14
+ await (0, internal_1.addPlugin)(tree, await (0, devkit_1.createProjectGraphAsync)(), '@nx/webpack/plugin', plugin_1.createNodesV2, {
15
+ buildTargetName: [
16
+ 'build',
17
+ 'webpack:build',
18
+ 'build:webpack',
19
+ 'webpack-build',
20
+ 'build-webpack',
21
+ ],
22
+ serveTargetName: [
23
+ 'serve',
24
+ 'webpack:serve',
25
+ 'serve:webpack',
26
+ 'webpack-serve',
27
+ 'serve-webpack',
28
+ ],
29
+ previewTargetName: [
30
+ 'preview',
31
+ 'webpack:preview',
32
+ 'preview:webpack',
33
+ 'webpack-preview',
34
+ 'preview-webpack',
35
+ ],
36
+ buildDepsTargetName: [
37
+ 'build-deps',
38
+ 'webpack:build-deps',
39
+ 'webpack-build-deps',
40
+ ],
41
+ watchDepsTargetName: [
42
+ 'watch-deps',
43
+ 'webpack:watch-deps',
44
+ 'webpack-watch-deps',
45
+ ],
46
+ serveStaticTargetName: [
47
+ 'serve-static',
48
+ 'webpack:serve-static',
49
+ 'serve-static:webpack',
50
+ 'webpack-serve-static',
51
+ 'serve-static-webpack',
52
+ ],
53
+ }, schema.updatePackageScripts);
60
54
  let installTask = () => { };
61
55
  if (!schema.skipPackageJson) {
62
- // webpack and webpack-dev-server are peer dependencies, so ensure they are
63
- // installed (both the executor and inferred-plugin paths need them at the
64
- // user's runtime).
56
+ // webpack and webpack-dev-server are peer dependencies needed at the
57
+ // user's runtime.
65
58
  const devDependencies = {
66
59
  '@nx/webpack': versions_1.nxVersion,
67
60
  '@nx/web': versions_1.nxVersion,
68
61
  webpack: versions_1.webpackVersion,
69
62
  'webpack-dev-server': versions_1.webpackDevServerVersion,
70
- };
71
- if (schema.addPlugin) {
72
63
  // The inferred plugin runs the `webpack-cli` binary.
73
- devDependencies['webpack-cli'] = versions_1.webpackCliVersion;
74
- }
64
+ 'webpack-cli': versions_1.webpackCliVersion,
65
+ };
75
66
  // @nx/webpack depends on sass, which pulls in @parcel/watcher. Its install
76
67
  // script only builds from source when npm_config_build_from_source is set,
77
68
  // so skip it.
@@ -3,5 +3,4 @@ export interface Schema {
3
3
  skipPackageJson?: boolean;
4
4
  keepExistingVersions?: boolean;
5
5
  updatePackageScripts?: boolean;
6
- addPlugin?: boolean;
7
6
  }
@@ -0,0 +1,11 @@
1
+ # Replace removed @nx/webpack executors with inferred tasks
2
+
3
+ Nx 24 removed `@nx/webpack:webpack`, `@nx/webpack:dev-server` (including old `@nrwl` aliases).
4
+
5
+ Before changing anything, check for leftover work: a project target, a package.json target or a `targetDefaults` entry still uses a removed executor, or the advisory context reports a conversion error. If none applies, make no changes and stop. Treat each advisory-context item as pending work.
6
+
7
+ 1. Inspect all affected projects and workspace defaults. Repair config-loading errors first. Do not restore executor registrations or install older Nx packages.
8
+ 2. Register `@nx/webpack/plugin` with options/includes that preserve custom target names and mixed inferred/explicit projects. Replace remaining removed-executor targets, including those in package.json and filtered targetDefaults. Transfer their behavior before removing obsolete defaults. Preserve configurations, dependencies, cache inputs/outputs and environment behavior. Do not silently delete unsupported options or unrelated targets. Migrate old @nrwl aliases too.
9
+ 3. Run project graph creation with the daemon and graph cache disabled (`NX_DAEMON=false NX_CACHE_PROJECT_GRAPH=false`), using the workspace package manager. Inspect resolved project targets and verify each migrated entrypoint exists and is inferred (a thin alias to an inferred target is acceptable). Resolve graph errors before completing this migration, even if task validation was skipped. Configs must load for subsequent migrations.
10
+ 4. By default run relevant real builds/tests with task cache disabled, including alternate configurations. Honor an existing preference to skip expensive task validation; otherwise offer that option when validation is slow. Skipping expensive tasks never skips conversion or the graph check. Report each skipped task as unverified. Never weaken assertions or change application behavior to make checks pass.
11
+ 5. Report converted projects, graph validation, task results and any skipped checks. Unconverted targets or a broken graph mean the migration is incomplete. Investigate task failures and distinguish missing environment prerequisites from conversion regressions; do not claim a working workspace with unresolved regressions.
@@ -0,0 +1,10 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export interface HelperUsage {
3
+ name: string;
4
+ root: string;
5
+ configPaths: string[];
6
+ usesHelpers: boolean;
7
+ usesModuleFederation: boolean;
8
+ usesExecutor: boolean;
9
+ }
10
+ export declare function inventoryComposePlugins(tree: Tree): HelperUsage[];
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inventoryComposePlugins = inventoryComposePlugins;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const CONFIG_EXTENSIONS = ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts'];
6
+ const REMOVED_HELPERS = /\b(composePlugins|composePluginsSync|withNx|withWeb|withReact)\b/;
7
+ // Left by an earlier conversion; the wrapped `.old` config still calls the helpers.
8
+ const LEGACY_WRAPPER = /\buseLegacyNxPlugin\b/;
9
+ const MODULE_FEDERATION_HELPERS = /\bwithModuleFederation(ForSSR)?\b/;
10
+ function inventoryComposePlugins(tree) {
11
+ const usages = [];
12
+ for (const [name, project] of (0, devkit_1.getProjects)(tree)) {
13
+ const configs = [...collectConfigPaths(tree, project)].map((path) => ({
14
+ path,
15
+ source: tree.read(path, 'utf-8') ?? '',
16
+ }));
17
+ const configPaths = configs
18
+ .filter(({ source }) => REMOVED_HELPERS.test(source) || LEGACY_WRAPPER.test(source))
19
+ .map(({ path }) => path);
20
+ if (!configPaths.length)
21
+ continue;
22
+ usages.push({
23
+ name,
24
+ root: project.root,
25
+ configPaths: configPaths.sort(),
26
+ usesHelpers: configs.some(({ source }) => REMOVED_HELPERS.test(source)),
27
+ usesModuleFederation: configs.some(({ source }) => MODULE_FEDERATION_HELPERS.test(source)),
28
+ usesExecutor: Object.values(project.targets ?? {}).some((target) => ['@nx/webpack:webpack', '@nrwl/webpack:webpack'].includes(target.executor)),
29
+ });
30
+ }
31
+ return usages;
32
+ }
33
+ function collectConfigPaths(tree, project) {
34
+ const paths = new Set();
35
+ for (const target of Object.values(project.targets ?? {})) {
36
+ for (const options of [
37
+ target.options,
38
+ ...Object.values(target.configurations ?? {}),
39
+ ]) {
40
+ const configured = options?.webpackConfig;
41
+ // A package path such as `@nx/react/plugins/webpack` resolves to the stubs
42
+ // rather than a workspace file; the prompt gives those projects a config.
43
+ if (typeof configured === 'string' && tree.exists(configured)) {
44
+ paths.add(configured);
45
+ }
46
+ }
47
+ }
48
+ for (const extension of CONFIG_EXTENSIONS) {
49
+ const conventional = `${project.root}/webpack.config.${extension}`;
50
+ if (tree.exists(conventional)) {
51
+ paths.add(conventional);
52
+ }
53
+ }
54
+ return paths;
55
+ }
@@ -0,0 +1,10 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export default function migrateComposePlugins(tree: Tree): Promise<{
3
+ skipAgentic: boolean;
4
+ nextSteps?: undefined;
5
+ agentContext?: undefined;
6
+ } | {
7
+ skipAgentic?: undefined;
8
+ nextSteps: string[];
9
+ agentContext: string[];
10
+ }>;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = migrateComposePlugins;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const convert_config_to_webpack_plugin_1 = require("../../generators/convert-config-to-webpack-plugin/convert-config-to-webpack-plugin");
6
+ const inventory_1 = require("./inventory");
7
+ const CONVERTER = '@nx/webpack:convert-config-to-webpack-plugin';
8
+ async function migrateComposePlugins(tree) {
9
+ const usages = (0, inventory_1.inventoryComposePlugins)(tree);
10
+ if (!usages.length) {
11
+ return { skipAgentic: true };
12
+ }
13
+ const converted = [];
14
+ const remaining = [];
15
+ const moduleFederation = [];
16
+ for (const usage of usages) {
17
+ if (!usage.usesHelpers) {
18
+ converted.push(usage);
19
+ continue;
20
+ }
21
+ if (usage.usesModuleFederation) {
22
+ moduleFederation.push(usage);
23
+ continue;
24
+ }
25
+ try {
26
+ await (0, convert_config_to_webpack_plugin_1.convertConfigToWebpackPluginGenerator)(tree, {
27
+ project: usage.name,
28
+ skipFormat: true,
29
+ });
30
+ converted.push(usage);
31
+ }
32
+ catch (e) {
33
+ remaining.push({ usage, reason: e.message });
34
+ }
35
+ }
36
+ if (converted.length) {
37
+ await (0, devkit_1.formatFiles)(tree);
38
+ }
39
+ return {
40
+ nextSteps: nextSteps(converted, remaining),
41
+ agentContext: agentContext(converted, remaining, moduleFederation),
42
+ };
43
+ }
44
+ function nextSteps(converted, remaining) {
45
+ const steps = [];
46
+ if (converted.length) {
47
+ steps.push(`Inline the \`webpack.config.old.*\` files that ${CONVERTER} left behind in ${converted.length} project(s), then drop \`useLegacyNxPlugin\`.`);
48
+ }
49
+ if (remaining.length) {
50
+ steps.push(`Migrate ${remaining.length} project(s) off the removed webpack config helpers by hand: ${remaining
51
+ .map(({ usage }) => usage.name)
52
+ .join(', ')}.`);
53
+ }
54
+ return steps;
55
+ }
56
+ function agentContext(converted, remaining, moduleFederation) {
57
+ const context = [];
58
+ if (converted.length) {
59
+ context.push(`${CONVERTER} converted these projects. Each one now has a standard config whose \`useLegacyNxPlugin\` call still loads the renamed \`.old\` config, so the helper callbacks it holds have to move into the new config before the \`.old\` file can go:`, ...converted.map((usage) => `- ${usage.name} (${usage.root}): ${usage.configPaths.join(', ')}`));
60
+ }
61
+ if (remaining.length) {
62
+ context.push(`${CONVERTER} could not convert these projects, so their configs are untouched and still call the removed helpers. Rewrite each one by hand:`, ...remaining.map(({ usage, reason }) => `- ${usage.name} (${usage.root}): ${usage.configPaths.join(', ')} - ${reason}`));
63
+ }
64
+ if (moduleFederation.length) {
65
+ context.push(`These projects compose Module Federation helpers, so their configs are untouched. The @nx/module-federation migration handles them; do not convert them here:`, ...moduleFederation.map((usage) => `- ${usage.name} (${usage.root}): ${usage.configPaths.join(', ')}`));
66
+ }
67
+ return context;
68
+ }
@@ -0,0 +1,98 @@
1
+ # Migrate webpack compose helpers
2
+
3
+ Nx v24 removed `composePlugins`, `composePluginsSync`, `withNx`, `withWeb`, and webpack
4
+ `withReact`. Their exports remain as inert stubs so existing configs still load while Nx
5
+ builds the project graph. They do not configure builds, and a config that loads is not a
6
+ working build.
7
+
8
+ `@nx/webpack:convert-config-to-webpack-plugin` already ran. The agent context lists the
9
+ projects it converted, including configs an earlier conversion left wrapped in
10
+ `useLegacyNxPlugin`, and, separately, the projects it could not convert with the reason
11
+ for each. Work only on those projects and the files they import. Do not re-inventory the
12
+ workspace, and do not run the converter again.
13
+
14
+ Preserve the installed webpack version, bundler, targets, configurations, and commands.
15
+ Nx 24 also removed the `@nx/webpack:webpack` executor. Its targets are converted to inferred
16
+ `@nx/webpack/plugin` tasks by a separate migration, so do not add it back or keep a target on it.
17
+
18
+ ## Baseline
19
+
20
+ This workspace is already on v24, so the old helpers no longer build here. To compare
21
+ against the previous behavior, check out the commit before the upgrade in a separate git
22
+ worktree, install with its lockfile, and build there. Do not downgrade this workspace. If
23
+ you cannot build a baseline, say so in your report rather than calling the comparison a
24
+ pass. Record build commands, output locations, assets, entry points, and any observable
25
+ custom behavior.
26
+
27
+ ## Projects the converter handled
28
+
29
+ Each one now has a standard config built around `NxAppWebpackPlugin`, plus a
30
+ `useLegacyNxPlugin(require('./webpack.config.old.*'), options)` entry that still loads the
31
+ renamed old config. The old config composes its remaining callbacks through a local
32
+ `composeLegacyPlugins` helper the converter inlined. Two things are left:
33
+
34
+ - Move what the old config still does into the new config, then delete the `.old` file and
35
+ drop `useLegacyNxPlugin`.
36
+ - `useLegacyNxPlugin` runs at `beforeCompile`. That is too late to register rules, entries,
37
+ or plugins, so anything the old config set through those is already not applying the way
38
+ it used to. Check each one against the baseline instead of trusting the converted build.
39
+
40
+ Also check the options the converter copied into the new config. It reads them out of the
41
+ old `withNx` call as written, so values that depended on the executor or on environment
42
+ variables are not represented, and workspace-relative paths may need adjusting: native
43
+ plugin options resolve against the project and config directory, while executor options
44
+ were workspace-relative.
45
+
46
+ ## Projects the converter skipped
47
+
48
+ The agent context gives a reason per project. Those configs are untouched and still call
49
+ the removed helpers, so rewrite each one.
50
+
51
+ Use a standard webpack configuration with `NxAppWebpackPlugin` from
52
+ `@nx/webpack/app-plugin`. For React apps also use `NxReactWebpackPlugin` from
53
+ `@nx/react/webpack-plugin`. Do not add React configuration to Node or plain web apps, and
54
+ do not change frameworks or upgrade dependencies to simplify the rewrite.
55
+
56
+ Map the effective old options onto those plugins and standard webpack properties, reading
57
+ the installed plugin types rather than assuming. Preserve precedence between target
58
+ options, named configurations, helper options, and custom callbacks. Preserve compiler
59
+ choice, aliases, CSS, SVG rules, assets, externals, file replacements, source maps,
60
+ optimization, public paths, output filenames, and dev-server behavior. Set output hashing
61
+ explicitly where the old helper defaults differ from the plugin defaults; a build that
62
+ succeeds with renamed entry files is not output parity.
63
+
64
+ A `webpackConfig` target option naming a package path rather than a local file, such as
65
+ `@nx/react/plugins/webpack`, resolves to the stubs. Give that project its own config file.
66
+
67
+ Inferred targets run the webpack CLI, which calls a function export with `(env, argv)`.
68
+ Represent the options the executor used to supply in the config itself. Use config
69
+ functions or separate configs where values vary by configuration.
70
+
71
+ For custom composable functions, move the function into user-owned code and supply the
72
+ options and context it needs explicitly. A local adapter exposing `apply(compiler)` can
73
+ work, but an `apply` object belongs in `config.plugins`; exporting it as the whole config
74
+ is invalid, and a top-level config function must return a config object or supported array.
75
+ Preserve execution order and whether each callback mutates its input or returns a
76
+ replacement. Await asynchronous transforms and propagate failures rather than leaving a
77
+ pending promise.
78
+
79
+ ## Module Federation
80
+
81
+ Projects using Nx Module Federation are out of scope here. `@nx/module-federation` ships
82
+ its own v24 migration; follow that one. Do not remove federation, disable the project, or
83
+ report success for it from this migration.
84
+
85
+ ## Verify and finish
86
+
87
+ 1. Rebuild the project graph without its disk cache and confirm the same projects and
88
+ targets are still discoverable.
89
+ 2. Run each affected build, development and production and any alternate configurations,
90
+ without reusing cached task outputs. Exercise Node startup where it applies, plus tests
91
+ and dev-server or HMR behavior.
92
+ 3. Compare emitted files against the baseline: custom assets and markers, entry points,
93
+ aliases, CSS and SVG handling, externals, and output paths. Compare content where hashes
94
+ differ. A zero exit code is not enough.
95
+ 4. Search the active configs and their imports for the removed helpers again. An already
96
+ migrated config needs no further change.
97
+ 5. Report the commands you ran, the output you checked, and anything still unresolved. Do
98
+ not delete tests, custom behavior, or targets to get a green result.
@@ -0,0 +1,6 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export default function update(tree: Tree): Promise<{
3
+ nextSteps?: string[];
4
+ agentContext?: string[];
5
+ skipAgentic?: boolean;
6
+ }>;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = update;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const internal_1 = require("@nx/devkit/internal");
6
+ const convert_config_to_webpack_plugin_1 = require("../../generators/convert-config-to-webpack-plugin/convert-config-to-webpack-plugin");
7
+ const inventory_1 = require("./inventory");
8
+ function update(tree) {
9
+ return (0, internal_1.migrateRemovedExecutors)(tree, [
10
+ '@nx/webpack:webpack',
11
+ '@nx/webpack:dev-server',
12
+ '@nrwl/webpack:webpack',
13
+ '@nrwl/webpack:dev-server',
14
+ ], async (tree, options) => {
15
+ await convertHelperConfigs(tree);
16
+ const { convertToInferred } = require('../../generators/convert-to-inferred/convert-to-inferred');
17
+ return convertToInferred(tree, options);
18
+ });
19
+ }
20
+ // convert-to-inferred skips configs built on the removed helpers, so convert
21
+ // those first. migrate-compose-plugins reports the `.old` configs left behind.
22
+ async function convertHelperConfigs(tree) {
23
+ for (const usage of (0, inventory_1.inventoryComposePlugins)(tree)) {
24
+ if (!usage.usesExecutor || !usage.usesHelpers)
25
+ continue;
26
+ // Left for the Module Federation migration, like migrate-compose-plugins.
27
+ if (usage.usesModuleFederation)
28
+ continue;
29
+ try {
30
+ await (0, convert_config_to_webpack_plugin_1.convertConfigToWebpackPluginGenerator)(tree, {
31
+ project: usage.name,
32
+ skipFormat: true,
33
+ });
34
+ }
35
+ catch (e) {
36
+ devkit_1.logger.warn(`Could not convert the webpack config helpers in ${usage.name}: ${e.message}`);
37
+ }
38
+ }
39
+ }