@nx/web 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.
package/dist/internal.js CHANGED
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fileServerExecutor = exports.waitForPortOpen = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  // Curated internal API surface for other first-party Nx plugins (e.g. @nx/react,
6
- // @nx/angular, @nx/rspack, @nx/next, @nx/module-federation). Prefer
7
- // this over deep `@nx/web/src/*` imports — those are not part of the public API.
6
+ // @nx/angular, @nx/rspack, @nx/next). Prefer this over deep `@nx/web/src/*`
7
+ // imports — those are not part of the public API.
8
8
  var wait_for_port_open_1 = require("./src/utils/wait-for-port-open");
9
9
  Object.defineProperty(exports, "waitForPortOpen", { enumerable: true, get: function () { return wait_for_port_open_1.waitForPortOpen; } });
10
10
  var file_server_impl_1 = require("./src/executors/file-server/file-server.impl");
@@ -7,9 +7,11 @@ const child_process_1 = require("child_process");
7
7
  const pc = tslib_1.__importStar(require("picocolors"));
8
8
  const devkit_1 = require("@nx/devkit");
9
9
  const fs_1 = require("fs");
10
+ const net_1 = require("net");
10
11
  const path_1 = require("path");
11
12
  const internal_2 = require("@nx/js/internal");
12
13
  const detect_port_1 = tslib_1.__importDefault(require("detect-port"));
14
+ const wait_for_port_open_1 = require("../../utils/wait-for-port-open");
13
15
  function getHttpServerArgs(options) {
14
16
  const { buildTarget, parallel, host, proxyUrl, ssl, sslCert, sslKey, proxyOptions, watch, spa, cacheSeconds, ...rest } = options;
15
17
  const args = [`-c${options.cacheSeconds}`];
@@ -92,6 +94,27 @@ function getBuildTargetOutputPath(options, context) {
92
94
  }
93
95
  return outputPath;
94
96
  }
97
+ /** Whether the server accepts connections before `exited` settles. */
98
+ async function waitUntilListening(port, host, exited) {
99
+ let alive = true;
100
+ exited.then(() => (alive = false));
101
+ // A wildcard address accepts connections on the loopback of its family. `::`
102
+ // also accepts IPv4 ones, and a host may have no ::1, so it gets both probes.
103
+ const probeHosts = host === '0.0.0.0'
104
+ ? ['127.0.0.1']
105
+ : host === '::'
106
+ ? ['::1', '127.0.0.1']
107
+ : [host];
108
+ while (alive) {
109
+ const connected = await Promise.any(probeHosts.map((probeHost) => (0, wait_for_port_open_1.waitForPortOpen)(port, { host: probeHost, retries: 0 }))).then(() => true, () => false);
110
+ if (connected) {
111
+ // A child that failed to bind the port leaves it to whoever holds it
112
+ return alive;
113
+ }
114
+ await new Promise((r) => setTimeout(r, 100));
115
+ }
116
+ return false;
117
+ }
95
118
  function createFileWatcher(project, changeHandler) {
96
119
  return internal_1.daemonClient.registerFileWatcher({
97
120
  watchProjects: project ? [project] : 'all',
@@ -193,6 +216,13 @@ async function* fileServerExecutor(options, context) {
193
216
  ...process.env,
194
217
  },
195
218
  });
219
+ const exited = new Promise((res) => {
220
+ serve.on('exit', (code, signal) => {
221
+ if (code === null)
222
+ code = (0, internal_1.signalToCode)(signal);
223
+ res({ success: code == 0 });
224
+ });
225
+ });
196
226
  const processExitListener = () => {
197
227
  serve.kill();
198
228
  if (disposeWatch) {
@@ -212,20 +242,12 @@ async function* fileServerExecutor(options, context) {
212
242
  serve.stderr.on('data', (chunk) => {
213
243
  process.stderr.write(chunk);
214
244
  });
215
- yield {
216
- success: true,
217
- baseUrl: `${options.ssl ? 'https' : 'http'}://${options.host}:${port}`,
218
- };
219
- return new Promise((res) => {
220
- serve.on('exit', (code, signal) => {
221
- if (code === null)
222
- code = (0, internal_1.signalToCode)(signal);
223
- if (code == 0) {
224
- res({ success: true });
225
- }
226
- else {
227
- res({ success: false });
228
- }
229
- });
230
- });
245
+ // http-server's own default when no `-a` is passed
246
+ const host = options.host || '0.0.0.0';
247
+ if (!(await waitUntilListening(port, host, exited))) {
248
+ return exited;
249
+ }
250
+ const baseUrl = (0, internal_1.getListeningUrl)({ address: host, family: (0, net_1.isIPv6)(host) ? 'IPv6' : 'IPv4', port }, !!options.ssl);
251
+ yield { success: true, baseUrl };
252
+ return exited;
231
253
  }
@@ -79,10 +79,6 @@ function createApplicationFiles(tree, options) {
79
79
  async function setupBundler(tree, options) {
80
80
  const main = (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/main.ts');
81
81
  const tsConfig = (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.app.json');
82
- const assets = [
83
- (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/favicon.ico'),
84
- (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/assets'),
85
- ];
86
82
  if (options.bundler === 'webpack') {
87
83
  const { configurationGenerator } = (0, devkit_1.ensurePackage)('@nx/webpack', versions_1.nxVersion);
88
84
  await configurationGenerator(tree, {
@@ -94,43 +90,7 @@ async function setupBundler(tree, options) {
94
90
  devServer: true,
95
91
  webpackConfig: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'webpack.config.js'),
96
92
  skipFormat: true,
97
- addPlugin: options.addPlugin,
98
93
  });
99
- const project = (0, devkit_1.readProjectConfiguration)(tree, options.projectName);
100
- // Only when asked: @nx/webpack:dev-server already defaults to 4200.
101
- if (options.port != null && project.targets?.serve) {
102
- project.targets.serve.options ??= {};
103
- project.targets.serve.options.port = options.port;
104
- (0, devkit_1.updateProjectConfiguration)(tree, options.projectName, project);
105
- }
106
- if (project.targets?.build) {
107
- const prodConfig = project.targets.build.configurations.production;
108
- const buildOptions = project.targets.build.options;
109
- buildOptions.assets = assets;
110
- buildOptions.index = (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/index.html');
111
- buildOptions.baseHref = '/';
112
- buildOptions.styles = [
113
- (0, devkit_1.joinPathFragments)(options.appProjectRoot, `src/styles.${options.style}`),
114
- ];
115
- // We can delete that, because this projest is an application
116
- // and applications have a .babelrc file in their root dir.
117
- // So Nx will find it and use it
118
- delete buildOptions.babelUpwardRootMode;
119
- buildOptions.scripts = [];
120
- prodConfig.fileReplacements = [
121
- {
122
- replace: (0, devkit_1.joinPathFragments)(options.appProjectRoot, `src/environments/environment.ts`),
123
- with: (0, devkit_1.joinPathFragments)(options.appProjectRoot, `src/environments/environment.prod.ts`),
124
- },
125
- ];
126
- prodConfig.optimization = true;
127
- prodConfig.outputHashing = 'all';
128
- prodConfig.sourceMap = false;
129
- prodConfig.namedChunks = false;
130
- prodConfig.extractLicenses = true;
131
- prodConfig.vendorChunk = false;
132
- (0, devkit_1.updateProjectConfiguration)(tree, options.projectName, project);
133
- }
134
94
  // 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).
135
95
  }
136
96
  else if (options.bundler === 'none') {
@@ -193,10 +153,7 @@ function setDefaults(tree, options) {
193
153
  (0, devkit_1.updateNxJson)(tree, nxJson);
194
154
  }
195
155
  async function applicationGenerator(host, schema) {
196
- return await applicationGeneratorInternal(host, {
197
- addPlugin: false,
198
- ...schema,
199
- });
156
+ return await applicationGeneratorInternal(host, schema);
200
157
  }
201
158
  async function applicationGeneratorInternal(host, schema) {
202
159
  const options = await normalizeOptions(host, schema);
@@ -227,15 +184,14 @@ async function applicationGeneratorInternal(host, schema) {
227
184
  linter: options.linter,
228
185
  project: options.projectName,
229
186
  unitTestRunner: options.unitTestRunner,
230
- addPlugin: options.addPlugin,
231
187
  }));
232
188
  }
233
189
  if (options.linter === 'eslint') {
234
190
  const { lintProjectGenerator } = (0, devkit_1.ensurePackage)('@nx/eslint', versions_1.nxVersion);
235
191
  // CommonJS `require` instead of dynamic ESM `import`: `ensurePackage`
236
192
  // exposes the temp install via `Module._initPaths`, which ESM ignores.
237
- const { isTypedLintingEnabled, addIgnoresToLintConfig, } = require('@nx/eslint/internal');
238
- enableTypedLinting = isTypedLintingEnabled(options);
193
+ const { addIgnoresToLintConfig, } = require('@nx/eslint/internal');
194
+ enableTypedLinting = options.enableTypedLinting;
239
195
  const lintTask = await lintProjectGenerator(host, {
240
196
  linter: options.linter,
241
197
  project: options.projectName,
@@ -245,7 +201,6 @@ async function applicationGeneratorInternal(host, schema) {
245
201
  unitTestRunner: options.unitTestRunner,
246
202
  skipFormat: true,
247
203
  enableTypedLinting,
248
- addPlugin: options.addPlugin,
249
204
  });
250
205
  tasks.push(lintTask);
251
206
  // Add out-tsc ignore pattern when using TS solution setup
@@ -267,7 +222,6 @@ async function applicationGeneratorInternal(host, schema) {
267
222
  includeVitest: options.unitTestRunner === 'vitest',
268
223
  inSourceTests: options.inSourceTests,
269
224
  skipFormat: true,
270
- addPlugin: options.addPlugin,
271
225
  port: options.port,
272
226
  });
273
227
  tasks.push(viteTask);
@@ -293,7 +247,6 @@ async function applicationGeneratorInternal(host, schema) {
293
247
  coverageProvider: 'v8',
294
248
  inSourceTests: options.inSourceTests,
295
249
  skipFormat: true,
296
- addPlugin: options.addPlugin,
297
250
  compiler: options.compiler,
298
251
  });
299
252
  tasks.push(vitestTask);
@@ -326,22 +279,24 @@ async function applicationGeneratorInternal(host, schema) {
326
279
  port: options.port,
327
280
  });
328
281
  }
282
+ // Only a --bundler=none app keeps these defaults. It has no `serve` target,
283
+ // so `serve-static` serves local runs too.
329
284
  let e2eWebServerInfo = {
330
285
  e2eWebServerAddress: `http://localhost:${options.port ?? 4200}`,
331
- e2eWebServerCommand: `${(0, devkit_1.getPackageManagerCommand)().exec} nx run ${options.projectName}:serve`,
286
+ e2eWebServerCommand: `${(0, devkit_1.getPackageManagerCommand)().exec} nx run ${options.projectName}:serve-static`,
332
287
  e2eCiWebServerCommand: `${(0, devkit_1.getPackageManagerCommand)().exec} nx run ${options.projectName}:serve-static`,
333
288
  e2eCiBaseUrl: `http://localhost:${options.port ?? 4200}`,
334
- e2eDevServerTarget: `${options.projectName}:serve`,
289
+ e2eDevServerTarget: `${options.projectName}:serve-static`,
335
290
  };
336
291
  if (options.bundler === 'webpack') {
337
292
  const { getWebpackE2EWebServerInfo } = (0, devkit_1.ensurePackage)('@nx/webpack', versions_1.nxVersion);
338
- e2eWebServerInfo = await getWebpackE2EWebServerInfo(host, options.projectName, (0, devkit_1.joinPathFragments)(options.appProjectRoot, `webpack.config.js`), options.addPlugin,
293
+ e2eWebServerInfo = await getWebpackE2EWebServerInfo(host, options.projectName, (0, devkit_1.joinPathFragments)(options.appProjectRoot, `webpack.config.js`),
339
294
  // Undefined unless --port was passed, so targetDefaults still apply.
340
295
  options.port);
341
296
  }
342
297
  else if (options.bundler === 'vite') {
343
298
  const { getViteE2EWebServerInfo } = (0, devkit_1.ensurePackage)('@nx/vite', versions_1.nxVersion);
344
- e2eWebServerInfo = await getViteE2EWebServerInfo(host, options.projectName, (0, devkit_1.joinPathFragments)(options.appProjectRoot, `vite.config.ts`), options.addPlugin, options.port ?? 4200,
299
+ e2eWebServerInfo = await getViteE2EWebServerInfo(host, options.projectName, (0, devkit_1.joinPathFragments)(options.appProjectRoot, `vite.config.ts`), options.port ?? 4200,
345
300
  // An explicit port serves both dev and preview, as in @nx/react:app.
346
301
  options.port);
347
302
  }
@@ -420,9 +375,10 @@ async function applicationGeneratorInternal(host, schema) {
420
375
  js: false,
421
376
  linter: options.linter,
422
377
  enableTypedLinting,
423
- webServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
424
- webServerAddress: e2eWebServerInfo.e2eCiBaseUrl,
425
- addPlugin: options.addPlugin,
378
+ webServerCommand: e2eWebServerInfo.e2eWebServerCommand,
379
+ webServerAddress: e2eWebServerInfo.e2eWebServerAddress,
380
+ ciWebServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
381
+ ciWebServerAddress: e2eWebServerInfo.e2eCiBaseUrl,
426
382
  });
427
383
  tasks.push(playwrightTask);
428
384
  }
@@ -436,7 +392,6 @@ async function applicationGeneratorInternal(host, schema) {
436
392
  setupFile: 'none',
437
393
  compiler: options.compiler,
438
394
  skipFormat: true,
439
- addPlugin: options.addPlugin,
440
395
  });
441
396
  tasks.push(jestTask);
442
397
  }
@@ -485,10 +440,6 @@ async function normalizeOptions(host, options) {
485
440
  projectType: 'application',
486
441
  directory: options.directory,
487
442
  });
488
- const nxJson = (0, devkit_1.readNxJson)(host);
489
- const addPluginDefault = process.env.NX_ADD_PLUGINS !== 'false' &&
490
- nxJson.useInferencePlugins !== false;
491
- options.addPlugin ??= addPluginDefault;
492
443
  const isUsingTsSolutionConfig = (0, internal_2.isUsingTsSolutionSetup)(host);
493
444
  const appProjectName = !isUsingTsSolutionConfig || options.name ? projectName : importPath;
494
445
  const e2eProjectName = `${appProjectName}-e2e`;
@@ -25,17 +25,10 @@ module.exports = {
25
25
  ],
26
26
  };
27
27
  <% } else { %>
28
- const { composePlugins, withNx, withWeb } = require('@nx/webpack');
28
+ const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
29
29
 
30
- // Nx plugins for webpack.
31
- module.exports = composePlugins(
32
- withNx(),
33
- withWeb(),
34
- (config) => {
35
- // Update the webpack config as needed here.
36
- // e.g. `config.plugins.push(new MyPlugin())`
37
- config.output.clean = true;
38
- return config;
39
- }
40
- );
30
+ module.exports = {
31
+ output: { clean: true },
32
+ plugins: [new NxAppWebpackPlugin()],
33
+ };
41
34
  <% } %>
@@ -15,12 +15,7 @@ export interface Schema {
15
15
  linter?: LinterType;
16
16
  formatter?: 'none' | 'prettier' | 'oxfmt';
17
17
  enableTypedLinting?: boolean;
18
- /**
19
- * @deprecated Use `enableTypedLinting` instead. This option will be removed in Nx v24.
20
- */
21
- setParserOptionsProject?: boolean;
22
18
  strict?: boolean;
23
- addPlugin?: boolean;
24
19
  useProjectJson?: boolean;
25
20
  port?: number;
26
21
  }
@@ -100,12 +100,6 @@
100
100
  "description": "Whether to enable typed linting. For flat configs, this configures the recommended `parserOptions.projectService` and `tsconfigRootDir`. For legacy `.eslintrc` configs, this configures `parserOptions.project`. We do not enable this by default for lint performance reasons.",
101
101
  "default": false
102
102
  },
103
- "setParserOptionsProject": {
104
- "type": "boolean",
105
- "description": "Deprecated alias for `enableTypedLinting`.",
106
- "default": false,
107
- "x-deprecated": "Use `enableTypedLinting` instead. This option will be removed in Nx v24."
108
- },
109
103
  "strict": {
110
104
  "type": "boolean",
111
105
  "description": "Creates an application with strict mode and strict type checking.",
@@ -71,6 +71,7 @@ function addStaticConfig(tree, opts) {
71
71
  const staticServeOptions = {
72
72
  executor: '@nx/web:file-server',
73
73
  dependsOn: [opts.parsedBuildTarget],
74
+ readyWhen: { signal: true },
74
75
  options: {
75
76
  buildTarget: opts.buildTarget,
76
77
  staticFilePath: opts.outputPath,
@@ -0,0 +1,4 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export default function update(tree: Tree): Promise<void | {
3
+ skipAgentic: true;
4
+ }>;
@@ -0,0 +1,82 @@
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
+ async function update(tree) {
7
+ return (0, internal_1.addReadyWhenSignalToTargets)(tree, ['@nx/web:file-server'], (target, projectName, defaults) => addBuildDependency(tree, target, projectName, defaults));
8
+ }
9
+ // The file server runs its build target before it listens, which would count
10
+ // against the readiness timeout unless the build already ran as a dependency.
11
+ // Returns false when `dependsOn` cannot express that build.
12
+ function addBuildDependency(tree, target, projectName, defaults) {
13
+ const effective = (0, internal_1.mergeTargetConfigurations)(target, defaults ?? undefined, undefined, undefined, undefined, false);
14
+ // `dependsOn` is per target, so a configuration that swaps the build target
15
+ // cannot be covered
16
+ if (Object.values(effective.configurations ?? {}).some((config) => config?.buildTarget !== undefined)) {
17
+ return false;
18
+ }
19
+ if (typeof effective.options?.buildTarget !== 'string') {
20
+ return true;
21
+ }
22
+ const projects = (0, devkit_1.getProjects)(tree);
23
+ const nodes = Object.fromEntries([...projects].map(([name, data]) => [
24
+ name,
25
+ { name, type: 'lib', data },
26
+ ]));
27
+ let parsed;
28
+ try {
29
+ const { buildTarget } = (0, internal_1.resolveNxTokensInOptions)({ buildTarget: effective.options.buildTarget }, { ...projects.get(projectName), name: projectName }, 'buildTarget');
30
+ parsed = (0, devkit_1.parseTargetString)(buildTarget, {
31
+ projectName,
32
+ projectGraph: { nodes, dependencies: {} },
33
+ });
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ const { project, target: name, configuration } = parsed;
39
+ // `dependsOn` can't name a configuration, so only the build target's default
40
+ // one is covered
41
+ const buildTargetConfig = projects.get(project)?.targets?.[name];
42
+ // Nx merges defaults into a run-commands or run-script target based on its
43
+ // command or script, so only targets with other executors read them
44
+ const buildTargetDefaults = buildTargetConfig?.executor &&
45
+ buildTargetConfig.executor !== 'nx:run-commands' &&
46
+ buildTargetConfig.executor !== 'nx:run-script'
47
+ ? (0, internal_1.readApplicableTargetDefaults)(name, buildTargetConfig.executor, (0, devkit_1.readNxJson)(tree)?.targetDefaults, project, nodes[project])
48
+ : null;
49
+ const defaultConfiguration = buildTargetConfig?.defaultConfiguration ??
50
+ buildTargetDefaults?.defaultConfiguration;
51
+ if (configuration !== undefined && configuration !== defaultConfiguration) {
52
+ return false;
53
+ }
54
+ // Nx runs the `dependsOn` build in the requested configuration when the build
55
+ // target has it. An inferred build target's configurations are unknown.
56
+ const buildConfigurations = {
57
+ ...buildTargetDefaults?.configurations,
58
+ ...buildTargetConfig?.configurations,
59
+ };
60
+ const inferredBuildTarget = !buildTargetConfig?.executor && !buildTargetConfig?.command;
61
+ if (Object.keys(effective.configurations ?? {}).some((config) => config !== defaultConfiguration &&
62
+ (inferredBuildTarget || config in buildConfigurations))) {
63
+ return false;
64
+ }
65
+ if ((effective.dependsOn ?? []).some((entry) => project === projectName
66
+ ? typeof entry === 'string'
67
+ ? entry === name
68
+ : entry.target === name && !entry.projects && !entry.dependencies
69
+ : typeof entry === 'string'
70
+ ? entry === `${project}:${name}`
71
+ : entry.target === name && [entry.projects].flat().includes(project))) {
72
+ return true;
73
+ }
74
+ const buildDependency = project === projectName ? name : { projects: [project], target: name };
75
+ // A `dependsOn` in the project replaces the inherited one unless it spreads it
76
+ target.dependsOn = target.dependsOn
77
+ ? [...target.dependsOn, buildDependency]
78
+ : effective.dependsOn
79
+ ? ['...', buildDependency]
80
+ : [buildDependency];
81
+ return true;
82
+ }
@@ -0,0 +1,71 @@
1
+ #### Report when file server targets are ready
2
+
3
+ The `@nx/web:file-server` executor now tells Nx when its server is listening.
4
+ Nx listens for that report only on targets that set `readyWhen: { signal: true }`.
5
+ Tasks that depend on such a target with `waitFor: 'ready'` then start once the server is listening, instead of as soon as the target starts.
6
+
7
+ This migration sets `readyWhen: { signal: true }` on the `@nx/web:file-server` targets in your projects.
8
+ A target without its own `executor` is updated too when a `targetDefaults` entry in `nx.json` gives it `@nx/web:file-server`.
9
+
10
+ The file server runs its `buildTarget` before it listens, and that time counts against the 10-minute readiness timeout.
11
+ So the migration also adds the build target to the target's `dependsOn`, which runs the build first.
12
+ The migration reads `buildTarget`, `configurations`, and `dependsOn` from the target and from the `targetDefaults` entry that applies to it.
13
+ It leaves `dependsOn` unchanged when an entry there already names the build target.
14
+ When the target inherits `dependsOn` and sets none of its own, the migration writes `"..."` before the build target, so the inherited entries still apply.
15
+
16
+ The migration leaves a target unchanged in these cases:
17
+
18
+ - The target already sets `readyWhen`, including `null`.
19
+ - The target sets `continuous: false`, or sets no `continuous` and the `targetDefaults` entry that applies to it sets `continuous: false`. Nx only checks readiness for continuous targets.
20
+ - The `targetDefaults` entry that applies to the target sets `readyWhen`.
21
+
22
+ The migration also leaves a target unchanged when `dependsOn` can't run its build:
23
+
24
+ - A configuration of the target sets `buildTarget`. `dependsOn` applies to every configuration.
25
+ - The `buildTarget` names a configuration of the build target other than its `defaultConfiguration`. `dependsOn` can't name a configuration.
26
+ The migration reads `defaultConfiguration` from the build target in `project.json`, and from `targetDefaults` when that build target runs an executor other than `nx:run-commands` or `nx:run-script`.
27
+ - The target has a configuration that the build target also has, other than the build target's `defaultConfiguration`. When you run the target in that configuration, `dependsOn` builds that configuration, but the file server still builds the `defaultConfiguration`.
28
+ The migration reads the build target's configurations the same way as its `defaultConfiguration`. When the build target isn't in `project.json`, or `project.json` sets neither its `executor` nor its `command`, any configuration of the target leaves it unchanged.
29
+ - Nx can't parse the `buildTarget`, such as an empty string.
30
+
31
+ Set `readyWhen: { signal: true }` on those targets yourself, and add a `dependsOn` entry that builds the app first.
32
+
33
+ #### Sample code changes
34
+
35
+ ##### Before
36
+
37
+ ```json title="apps/app1/project.json"
38
+ {
39
+ // ...
40
+ "targets": {
41
+ "serve-static": {
42
+ "executor": "@nx/web:file-server",
43
+ "options": {
44
+ "buildTarget": "app1:build",
45
+ "port": 4200
46
+ }
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ ##### After
53
+
54
+ ```json title="apps/app1/project.json" {10-13}
55
+ {
56
+ // ...
57
+ "targets": {
58
+ "serve-static": {
59
+ "executor": "@nx/web:file-server",
60
+ "options": {
61
+ "buildTarget": "app1:build",
62
+ "port": 4200
63
+ },
64
+ "readyWhen": {
65
+ "signal": true
66
+ },
67
+ "dependsOn": ["build"]
68
+ }
69
+ }
70
+ }
71
+ ```
package/migrations.json CHANGED
@@ -4,6 +4,12 @@
4
4
  "version": "23.0.0-beta.25",
5
5
  "description": "Rewrites `@nx/web/src/*` subpath imports now that the `./src/*` subpath is no longer exposed by `@nx/web`'s exports map. Named imports/exports of public symbols are routed to `@nx/web` and the rest to the new `@nx/web/internal` entry; `require`, dynamic `import` and `jest.mock` calls reference the whole module and are routed to `@nx/web/internal`.",
6
6
  "implementation": "./dist/src/migrations/update-23-0-0/rewrite-internal-subpath-imports"
7
+ },
8
+ "update-24-0-0-add-ready-when-signal": {
9
+ "version": "24.0.0-beta.5",
10
+ "description": "Sets `readyWhen: { signal: true }` on `@nx/web:file-server` targets that get no `readyWhen` from their own configuration or `targetDefaults`, so tasks that depend on them with `waitFor: 'ready'` start once the server is listening. Those targets also get their build target as a dependency.",
11
+ "implementation": "./dist/src/migrations/update-24-0-0/add-ready-when-signal",
12
+ "documentation": "./dist/src/migrations/update-24-0-0/add-ready-when-signal.md"
7
13
  }
8
14
  },
9
15
  "packageJsonUpdates": {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/web",
3
- "version": "24.0.0-pr.37405.acdaf31",
3
+ "version": "24.0.0-rc.0",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "files": [
@@ -79,20 +79,20 @@
79
79
  "http-server": "^14.1.0",
80
80
  "picocolors": "^1.1.0",
81
81
  "tslib": "^2.3.0",
82
- "@nx/devkit": "24.0.0-pr.37405.acdaf31",
83
- "@nx/js": "24.0.0-pr.37405.acdaf31"
82
+ "@nx/devkit": "24.0.0-rc.0",
83
+ "@nx/js": "24.0.0-rc.0"
84
84
  },
85
85
  "devDependencies": {
86
- "nx": "24.0.0-pr.37405.acdaf31",
87
- "@nx/vitest": "24.0.0-pr.37405.acdaf31"
86
+ "nx": "24.0.0-rc.0",
87
+ "@nx/vitest": "24.0.0-rc.0"
88
88
  },
89
89
  "peerDependencies": {
90
- "@nx/cypress": "24.0.0-pr.37405.acdaf31",
91
- "@nx/eslint": "24.0.0-pr.37405.acdaf31",
92
- "@nx/jest": "24.0.0-pr.37405.acdaf31",
93
- "@nx/playwright": "24.0.0-pr.37405.acdaf31",
94
- "@nx/vite": "24.0.0-pr.37405.acdaf31",
95
- "@nx/webpack": "24.0.0-pr.37405.acdaf31"
90
+ "@nx/cypress": "24.0.0-rc.0",
91
+ "@nx/eslint": "24.0.0-rc.0",
92
+ "@nx/jest": "24.0.0-rc.0",
93
+ "@nx/playwright": "24.0.0-rc.0",
94
+ "@nx/vite": "24.0.0-rc.0",
95
+ "@nx/webpack": "24.0.0-rc.0"
96
96
  },
97
97
  "peerDependenciesMeta": {
98
98
  "@nx/cypress": {