@angular-devkit/build-angular 17.0.0-rc.3 → 17.0.0-rc.5

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 (28) hide show
  1. package/package.json +6 -6
  2. package/src/builders/application/execute-build.js +37 -28
  3. package/src/builders/dev-server/builder.d.ts +12 -1
  4. package/src/builders/dev-server/builder.js +13 -4
  5. package/src/builders/dev-server/vite-server.d.ts +9 -6
  6. package/src/builders/dev-server/vite-server.js +141 -63
  7. package/src/builders/karma/find-tests-plugin.js +17 -7
  8. package/src/builders/ssr-dev-server/index.d.ts +1 -1
  9. package/src/index.d.ts +1 -0
  10. package/src/index.js +3 -1
  11. package/src/tools/esbuild/angular/compiler-plugin.js +103 -56
  12. package/src/tools/esbuild/application-code-bundle.js +1 -16
  13. package/src/tools/esbuild/bundler-context.d.ts +4 -1
  14. package/src/tools/esbuild/bundler-context.js +13 -8
  15. package/src/tools/esbuild/bundler-execution-result.d.ts +14 -14
  16. package/src/tools/esbuild/bundler-execution-result.js +4 -3
  17. package/src/tools/esbuild/commonjs-checker.js +9 -4
  18. package/src/tools/esbuild/global-scripts.d.ts +1 -1
  19. package/src/tools/esbuild/global-scripts.js +2 -1
  20. package/src/tools/esbuild/javascript-transformer.d.ts +1 -1
  21. package/src/tools/esbuild/javascript-transformer.js +20 -8
  22. package/src/utils/bundle-calculator.js +1 -1
  23. package/src/utils/environment-options.js +4 -1
  24. package/src/utils/load-esm.js +6 -1
  25. package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.d.ts +0 -4
  26. package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.js +77 -28
  27. package/src/utils/server-rendering/render-worker.js +3 -1
  28. package/src/utils/server-rendering/routes-extractor-worker.js +2 -2
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@angular-devkit/build-angular",
3
- "version": "17.0.0-rc.3",
3
+ "version": "17.0.0-rc.5",
4
4
  "description": "Angular Webpack Build Facade",
5
5
  "main": "src/index.js",
6
6
  "typings": "src/index.d.ts",
7
7
  "builders": "builders.json",
8
8
  "dependencies": {
9
9
  "@ampproject/remapping": "2.2.1",
10
- "@angular-devkit/architect": "0.1700.0-rc.3",
11
- "@angular-devkit/build-webpack": "0.1700.0-rc.3",
12
- "@angular-devkit/core": "17.0.0-rc.3",
10
+ "@angular-devkit/architect": "0.1700.0-rc.5",
11
+ "@angular-devkit/build-webpack": "0.1700.0-rc.5",
12
+ "@angular-devkit/core": "17.0.0-rc.5",
13
13
  "@babel/core": "7.23.2",
14
14
  "@babel/generator": "7.23.0",
15
15
  "@babel/helper-annotate-as-pure": "7.22.5",
@@ -20,7 +20,7 @@
20
20
  "@babel/preset-env": "7.23.2",
21
21
  "@babel/runtime": "7.23.2",
22
22
  "@discoveryjs/json-ext": "0.5.7",
23
- "@ngtools/webpack": "17.0.0-rc.3",
23
+ "@ngtools/webpack": "17.0.0-rc.5",
24
24
  "@vitejs/plugin-basic-ssl": "1.0.1",
25
25
  "ansi-colors": "4.1.3",
26
26
  "autoprefixer": "10.4.16",
@@ -64,7 +64,7 @@
64
64
  "text-table": "0.2.0",
65
65
  "tree-kill": "1.2.2",
66
66
  "tslib": "2.6.2",
67
- "undici": "5.27.0",
67
+ "undici": "5.27.2",
68
68
  "vite": "4.5.0",
69
69
  "webpack": "5.89.0",
70
70
  "webpack-dev-middleware": "6.1.1",
@@ -59,7 +59,7 @@ async function executeBuild(options, context, rebuildState) {
59
59
  // Global Scripts
60
60
  if (options.globalScripts.length > 0) {
61
61
  for (const initial of [true, false]) {
62
- const bundleOptions = (0, global_scripts_1.createGlobalScriptsBundleOptions)(options, initial);
62
+ const bundleOptions = (0, global_scripts_1.createGlobalScriptsBundleOptions)(options, target, initial);
63
63
  if (bundleOptions) {
64
64
  bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, !!options.watch, bundleOptions, () => initial));
65
65
  }
@@ -69,7 +69,12 @@ async function executeBuild(options, context, rebuildState) {
69
69
  if (serverEntryPoint && (prerenderOptions || appShellOptions || ssrOptions)) {
70
70
  const nodeTargets = [...target, ...(0, utils_1.getSupportedNodeTargets)()];
71
71
  // Server application code
72
- bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, !!options.watch, (0, application_code_bundle_1.createServerCodeBundleOptions)(options, nodeTargets, codeBundleCache), () => false));
72
+ bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, !!options.watch, (0, application_code_bundle_1.createServerCodeBundleOptions)({
73
+ ...options,
74
+ // Disable external deps for server bundles.
75
+ // This is because it breaks Vite 'optimizeDeps' for SSR.
76
+ externalPackages: false,
77
+ }, nodeTargets, codeBundleCache), () => false));
73
78
  // Server polyfills code
74
79
  const serverPolyfillBundleOptions = (0, application_code_bundle_1.createServerPolyfillBundleOptions)(options, nodeTargets, codeBundleCache);
75
80
  if (serverPolyfillBundleOptions) {
@@ -88,11 +93,33 @@ async function executeBuild(options, context, rebuildState) {
88
93
  }
89
94
  // Analyze external imports if external options are enabled
90
95
  if (options.externalPackages || options.externalDependencies?.length) {
96
+ const { browser = new Set(), server = new Set() } = bundlingResult.externalImports;
91
97
  // TODO: Filter externalImports to generate second argument to support wildcard externalDependency values
92
- executionResult.setExternalMetadata([...bundlingResult.externalImports], options.externalDependencies);
98
+ executionResult.setExternalMetadata([...browser], [...server], options.externalDependencies);
93
99
  }
94
100
  const { metafile, initialFiles, outputFiles } = bundlingResult;
95
101
  executionResult.outputFiles.push(...outputFiles);
102
+ const changedFiles = rebuildState && executionResult.findChangedFiles(rebuildState.previousOutputHashes);
103
+ // Analyze files for bundle budget failures if present
104
+ let budgetFailures;
105
+ if (options.budgets) {
106
+ const compatStats = (0, budget_stats_1.generateBudgetStats)(metafile, initialFiles);
107
+ budgetFailures = [...(0, bundle_calculator_1.checkBudgets)(options.budgets, compatStats, true)];
108
+ if (budgetFailures.length > 0) {
109
+ const errors = budgetFailures
110
+ .filter((failure) => failure.severity === 'error')
111
+ .map(({ message }) => message);
112
+ const warnings = budgetFailures
113
+ .filter((failure) => failure.severity !== 'error')
114
+ .map(({ message }) => message);
115
+ await printWarningsAndErrorsToConsoleAndAddToResult(context, executionResult, warnings, errors);
116
+ }
117
+ }
118
+ // Calculate estimated transfer size if scripts are optimized
119
+ let estimatedTransferSizes;
120
+ if (optimizationOptions.scripts || optimizationOptions.styles.minify) {
121
+ estimatedTransferSizes = await (0, utils_1.calculateEstimatedTransferSizes)(executionResult.outputFiles);
122
+ }
96
123
  // Check metafile for CommonJS module usage if optimizing scripts
97
124
  if (optimizationOptions.scripts) {
98
125
  const messages = (0, commonjs_checker_1.checkCommonJSModules)(metafile, options.allowedCommonJsDependencies);
@@ -108,27 +135,6 @@ async function executeBuild(options, context, rebuildState) {
108
135
  if (options.extractLicenses) {
109
136
  executionResult.addOutputFile('3rdpartylicenses.txt', await (0, license_extractor_1.extractLicenses)(metafile, workspaceRoot), bundler_context_1.BuildOutputFileType.Root);
110
137
  }
111
- // Analyze files for bundle budget failures if present
112
- let budgetFailures;
113
- if (options.budgets) {
114
- const compatStats = (0, budget_stats_1.generateBudgetStats)(metafile, initialFiles);
115
- budgetFailures = [...(0, bundle_calculator_1.checkBudgets)(options.budgets, compatStats, true)];
116
- if (budgetFailures.length > 0) {
117
- await (0, utils_1.logMessages)(context, {
118
- errors: budgetFailures
119
- .filter((failure) => failure.severity === 'error')
120
- .map((failure) => ({ text: failure.message, location: null })),
121
- warnings: budgetFailures
122
- .filter((failure) => failure.severity !== 'error')
123
- .map((failure) => ({ text: failure.message, location: null })),
124
- });
125
- }
126
- }
127
- // Calculate estimated transfer size if scripts are optimized
128
- let estimatedTransferSizes;
129
- if (optimizationOptions.scripts || optimizationOptions.styles.minify) {
130
- estimatedTransferSizes = await (0, utils_1.calculateEstimatedTransferSizes)(executionResult.outputFiles);
131
- }
132
138
  // Perform i18n translation inlining if enabled
133
139
  let prerenderedRoutes;
134
140
  let errors;
@@ -149,7 +155,7 @@ async function executeBuild(options, context, rebuildState) {
149
155
  executionResult.outputFiles.push(...result.additionalOutputFiles);
150
156
  executionResult.assetFiles.push(...result.additionalAssets);
151
157
  }
152
- await printWarningsAndErrorsToConsole(context, warnings, errors);
158
+ await printWarningsAndErrorsToConsoleAndAddToResult(context, executionResult, warnings, errors);
153
159
  if (prerenderOptions) {
154
160
  executionResult.addOutputFile('prerendered-routes.json', JSON.stringify({ routes: prerenderedRoutes.sort((a, b) => a.localeCompare(b)) }, null, 2), bundler_context_1.BuildOutputFileType.Root);
155
161
  let prerenderMsg = `Prerendered ${prerenderedRoutes.length} static route`;
@@ -161,7 +167,6 @@ async function executeBuild(options, context, rebuildState) {
161
167
  }
162
168
  context.logger.info(color_1.colors.magenta(prerenderMsg) + '\n');
163
169
  }
164
- const changedFiles = rebuildState && executionResult.findChangedFiles(rebuildState.previousOutputHashes);
165
170
  (0, utils_1.logBuildStats)(context, metafile, initialFiles, budgetFailures, changedFiles, estimatedTransferSizes);
166
171
  // Write metafile if stats option is enabled
167
172
  if (options.stats) {
@@ -170,9 +175,13 @@ async function executeBuild(options, context, rebuildState) {
170
175
  return executionResult;
171
176
  }
172
177
  exports.executeBuild = executeBuild;
173
- async function printWarningsAndErrorsToConsole(context, warnings, errors) {
178
+ async function printWarningsAndErrorsToConsoleAndAddToResult(context, executionResult, warnings, errors) {
179
+ const errorMessages = errors.map((text) => ({ text, location: null }));
180
+ if (errorMessages.length) {
181
+ executionResult.addErrors(errorMessages);
182
+ }
174
183
  await (0, utils_1.logMessages)(context, {
175
- errors: errors.map((text) => ({ text, location: null })),
184
+ errors: errorMessages,
176
185
  warnings: warnings.map((text) => ({ text, location: null })),
177
186
  });
178
187
  }
@@ -5,8 +5,10 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
+ /// <reference types="node" />
8
9
  import type { BuilderContext } from '@angular-devkit/architect';
9
10
  import type { Plugin } from 'esbuild';
11
+ import type http from 'node:http';
10
12
  import { Observable } from 'rxjs';
11
13
  import type { ExecutionTransformer } from '../../transforms';
12
14
  import type { IndexHtmlTransform } from '../../utils/index-file/index-html-generator';
@@ -14,10 +16,16 @@ import type { Schema as DevServerBuilderOptions } from './schema';
14
16
  import type { DevServerBuilderOutput } from './webpack-server';
15
17
  /**
16
18
  * A Builder that executes a development server based on the provided browser target option.
19
+ *
20
+ * Usage of the `transforms` and/or `extensions` parameters is NOT supported and may cause
21
+ * unexpected build output or build failures.
22
+ *
17
23
  * @param options Dev Server options.
18
24
  * @param context The build context.
19
25
  * @param transforms A map of transforms that can be used to hook into some logic (such as
20
26
  * transforming webpack configuration before passing it to webpack).
27
+ * @param extensions An optional object containing an array of build plugins (esbuild-based)
28
+ * and/or HTTP request middleware.
21
29
  *
22
30
  * @experimental Direct usage of this function is considered experimental.
23
31
  */
@@ -25,4 +33,7 @@ export declare function execute(options: DevServerBuilderOptions, context: Build
25
33
  webpackConfiguration?: ExecutionTransformer<import('webpack').Configuration>;
26
34
  logging?: import('@angular-devkit/build-webpack').WebpackLoggingCallback;
27
35
  indexHtml?: IndexHtmlTransform;
28
- }, plugins?: Plugin[]): Observable<DevServerBuilderOutput>;
36
+ }, extensions?: {
37
+ buildPlugins?: Plugin[];
38
+ middleware?: ((req: http.IncomingMessage, res: http.ServerResponse, next: (err?: unknown) => void) => void)[];
39
+ }): Observable<DevServerBuilderOutput>;
@@ -37,14 +37,20 @@ const purge_cache_1 = require("../../utils/purge-cache");
37
37
  const options_1 = require("./options");
38
38
  /**
39
39
  * A Builder that executes a development server based on the provided browser target option.
40
+ *
41
+ * Usage of the `transforms` and/or `extensions` parameters is NOT supported and may cause
42
+ * unexpected build output or build failures.
43
+ *
40
44
  * @param options Dev Server options.
41
45
  * @param context The build context.
42
46
  * @param transforms A map of transforms that can be used to hook into some logic (such as
43
47
  * transforming webpack configuration before passing it to webpack).
48
+ * @param extensions An optional object containing an array of build plugins (esbuild-based)
49
+ * and/or HTTP request middleware.
44
50
  *
45
51
  * @experimental Direct usage of this function is considered experimental.
46
52
  */
47
- function execute(options, context, transforms = {}, plugins) {
53
+ function execute(options, context, transforms = {}, extensions) {
48
54
  // Determine project name from builder context target
49
55
  const projectName = context.target?.project;
50
56
  if (!projectName) {
@@ -56,14 +62,17 @@ function execute(options, context, transforms = {}, plugins) {
56
62
  if (builderName === '@angular-devkit/build-angular:application' ||
57
63
  builderName === '@angular-devkit/build-angular:browser-esbuild' ||
58
64
  normalizedOptions.forceEsbuild) {
59
- if (Object.keys(transforms).length > 0) {
65
+ if (transforms?.logging || transforms?.webpackConfiguration) {
60
66
  throw new Error('The `application` and `browser-esbuild` builders do not support Webpack transforms.');
61
67
  }
62
- return (0, rxjs_1.defer)(() => Promise.resolve().then(() => __importStar(require('./vite-server')))).pipe((0, rxjs_1.switchMap)(({ serveWithVite }) => serveWithVite(normalizedOptions, builderName, context, plugins)));
68
+ return (0, rxjs_1.defer)(() => Promise.resolve().then(() => __importStar(require('./vite-server')))).pipe((0, rxjs_1.switchMap)(({ serveWithVite }) => serveWithVite(normalizedOptions, builderName, context, transforms, extensions)));
63
69
  }
64
- if (plugins?.length) {
70
+ if (extensions?.buildPlugins?.length) {
65
71
  throw new Error('Only the `application` and `browser-esbuild` builders support plugins.');
66
72
  }
73
+ if (extensions?.middleware?.length) {
74
+ throw new Error('Only the `application` and `browser-esbuild` builders support middleware.');
75
+ }
67
76
  // Use Webpack for all other browser targets
68
77
  return (0, rxjs_1.defer)(() => Promise.resolve().then(() => __importStar(require('./webpack-server')))).pipe((0, rxjs_1.switchMap)(({ serveWebpackBrowser }) => serveWebpackBrowser(normalizedOptions, builderName, context, transforms)));
69
78
  }));
@@ -7,7 +7,8 @@
7
7
  */
8
8
  import type { BuilderContext } from '@angular-devkit/architect';
9
9
  import type { Plugin } from 'esbuild';
10
- import type { InlineConfig } from 'vite';
10
+ import type { Connect, InlineConfig } from 'vite';
11
+ import { ExternalResultMetadata } from '../../tools/esbuild/bundler-execution-result';
11
12
  import { JavaScriptTransformer } from '../../tools/esbuild/javascript-transformer';
12
13
  import type { NormalizedDevServerOptions } from './options';
13
14
  import type { DevServerBuilderOutput } from './webpack-server';
@@ -18,9 +19,11 @@ interface OutputFileRecord {
18
19
  updated: boolean;
19
20
  servable: boolean;
20
21
  }
21
- export declare function serveWithVite(serverOptions: NormalizedDevServerOptions, builderName: string, context: BuilderContext, plugins?: Plugin[]): AsyncIterableIterator<DevServerBuilderOutput>;
22
- export declare function setupServer(serverOptions: NormalizedDevServerOptions, outputFiles: Map<string, OutputFileRecord>, assets: Map<string, string>, preserveSymlinks: boolean | undefined, externalMetadata: {
23
- implicit: string[];
24
- explicit: string[];
25
- }, ssr: boolean, prebundleTransformer: JavaScriptTransformer, target: string[]): Promise<InlineConfig>;
22
+ export declare function serveWithVite(serverOptions: NormalizedDevServerOptions, builderName: string, context: BuilderContext, transformers?: {
23
+ indexHtml?: (content: string) => Promise<string>;
24
+ }, extensions?: {
25
+ middleware?: Connect.NextHandleFunction[];
26
+ buildPlugins?: Plugin[];
27
+ }): AsyncIterableIterator<DevServerBuilderOutput>;
28
+ export declare function setupServer(serverOptions: NormalizedDevServerOptions, outputFiles: Map<string, OutputFileRecord>, assets: Map<string, string>, preserveSymlinks: boolean | undefined, externalMetadata: ExternalResultMetadata, ssr: boolean, prebundleTransformer: JavaScriptTransformer, target: string[], extensionMiddleware?: Connect.NextHandleFunction[], indexHtmlTransformer?: (content: string) => Promise<string>): Promise<InlineConfig>;
26
29
  export {};
@@ -37,11 +37,11 @@ exports.setupServer = exports.serveWithVite = void 0;
37
37
  const remapping_1 = __importDefault(require("@ampproject/remapping"));
38
38
  const mrmime_1 = require("mrmime");
39
39
  const node_assert_1 = __importDefault(require("node:assert"));
40
- const node_crypto_1 = require("node:crypto");
41
40
  const promises_1 = require("node:fs/promises");
42
- const node_path_1 = __importDefault(require("node:path"));
41
+ const node_path_1 = require("node:path");
43
42
  const bundler_context_1 = require("../../tools/esbuild/bundler-context");
44
43
  const javascript_transformer_1 = require("../../tools/esbuild/javascript-transformer");
44
+ const rxjs_esm_resolution_plugin_1 = require("../../tools/esbuild/rxjs-esm-resolution-plugin");
45
45
  const utils_1 = require("../../tools/esbuild/utils");
46
46
  const i18n_locale_plugin_1 = require("../../tools/vite/i18n-locale-plugin");
47
47
  const render_page_1 = require("../../utils/server-rendering/render-page");
@@ -50,7 +50,8 @@ const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
50
50
  const application_1 = require("../application");
51
51
  const browser_esbuild_1 = require("../browser-esbuild");
52
52
  const load_proxy_config_1 = require("./load-proxy-config");
53
- async function* serveWithVite(serverOptions, builderName, context, plugins) {
53
+ // eslint-disable-next-line max-lines-per-function
54
+ async function* serveWithVite(serverOptions, builderName, context, transformers, extensions) {
54
55
  // Get the browser configuration from the target name.
55
56
  const rawBrowserOptions = (await context.getTargetOptions(serverOptions.buildTarget));
56
57
  const browserOptions = (await context.validateOptions({
@@ -89,7 +90,7 @@ async function* serveWithVite(serverOptions, builderName, context, plugins) {
89
90
  // Always enable JIT linking to support applications built with and without AOT.
90
91
  // In a development environment the additional scope information does not
91
92
  // have a negative effect unlike production where final output size is relevant.
92
- { sourcemap: true, jit: true }, 1);
93
+ { sourcemap: true, jit: true }, 1, true);
93
94
  // Extract output index from options
94
95
  // TODO: Provide this info from the build results
95
96
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -97,12 +98,13 @@ async function* serveWithVite(serverOptions, builderName, context, plugins) {
97
98
  // dynamically import Vite for ESM compatibility
98
99
  const { createServer, normalizePath } = await Promise.resolve().then(() => __importStar(require('vite')));
99
100
  let server;
100
- let listeningAddress;
101
+ let serverUrl;
101
102
  let hadError = false;
102
103
  const generatedFiles = new Map();
103
104
  const assetFiles = new Map();
104
105
  const externalMetadata = {
105
- implicit: [],
106
+ implicitBrowser: [],
107
+ implicitServer: [],
106
108
  explicit: [],
107
109
  };
108
110
  const build = builderName === '@angular-devkit/build-angular:application'
@@ -113,7 +115,7 @@ async function* serveWithVite(serverOptions, builderName, context, plugins) {
113
115
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
116
  browserOptions, context, {
115
117
  write: false,
116
- }, plugins)) {
118
+ }, extensions?.buildPlugins)) {
117
119
  (0, node_assert_1.default)(result.outputFiles, 'Builder did not provide result files.');
118
120
  // If build failed, nothing to serve
119
121
  if (!result.success) {
@@ -147,15 +149,21 @@ async function* serveWithVite(serverOptions, builderName, context, plugins) {
147
149
  assetFiles.set('/' + normalizePath(asset.destination), asset.source);
148
150
  }
149
151
  }
150
- // To avoid disconnecting the array objects from the option, these arrays need to be mutated
151
- // instead of replaced.
152
+ // To avoid disconnecting the array objects from the option, these arrays need to be mutated instead of replaced.
152
153
  if (result.externalMetadata) {
153
- if (result.externalMetadata.explicit) {
154
- externalMetadata.explicit.push(...result.externalMetadata.explicit);
155
- }
156
- if (result.externalMetadata.implicit) {
157
- externalMetadata.implicit.push(...result.externalMetadata.implicit);
158
- }
154
+ const { implicitBrowser, implicitServer, explicit } = result.externalMetadata;
155
+ // Empty Arrays to avoid growing unlimited with every re-build.
156
+ externalMetadata.explicit.length = 0;
157
+ externalMetadata.implicitServer.length = 0;
158
+ externalMetadata.implicitBrowser.length = 0;
159
+ externalMetadata.explicit.push(...explicit);
160
+ externalMetadata.implicitServer.push(...implicitServer);
161
+ externalMetadata.implicitBrowser.push(...implicitBrowser);
162
+ // The below needs to be sorted as Vite uses these options are part of the hashing invalidation algorithm.
163
+ // See: https://github.com/vitejs/vite/blob/0873bae0cfe0f0718ad2f5743dd34a17e4ab563d/packages/vite/src/node/optimizer/index.ts#L1203-L1239
164
+ externalMetadata.explicit.sort();
165
+ externalMetadata.implicitServer.sort();
166
+ externalMetadata.implicitBrowser.sort();
159
167
  }
160
168
  if (server) {
161
169
  handleUpdate(normalizePath, generatedFiles, server, serverOptions, context.logger);
@@ -166,19 +174,37 @@ async function* serveWithVite(serverOptions, builderName, context, plugins) {
166
174
  throw new Error('The builder requires a target.');
167
175
  }
168
176
  const { root = '' } = await context.getProjectMetadata(projectName);
169
- const projectRoot = node_path_1.default.join(context.workspaceRoot, root);
177
+ const projectRoot = (0, node_path_1.join)(context.workspaceRoot, root);
170
178
  const browsers = (0, supported_browsers_1.getSupportedBrowsers)(projectRoot, context.logger);
171
179
  const target = (0, utils_1.transformSupportedBrowsersToTargets)(browsers);
172
180
  // Setup server and start listening
173
- const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, !!browserOptions.ssr, prebundleTransformer, target);
181
+ const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, !!browserOptions.ssr, prebundleTransformer, target, extensions?.middleware, transformers?.indexHtml);
174
182
  server = await createServer(serverConfiguration);
175
183
  await server.listen();
176
- listeningAddress = server.httpServer?.address();
184
+ if (serverConfiguration.ssr?.optimizeDeps?.disabled === false) {
185
+ /**
186
+ * Vite will only start dependency optimization of SSR modules when the first request comes in.
187
+ * In some cases, this causes a long waiting time. To mitigate this, we call `ssrLoadModule` to
188
+ * initiate this process before the first request.
189
+ *
190
+ * NOTE: This will intentionally fail from the unknown module, but currently there is no other way
191
+ * to initiate the SSR dep optimizer.
192
+ */
193
+ void server.ssrLoadModule('<deps-caller>').catch(() => { });
194
+ }
195
+ const urls = server.resolvedUrls;
196
+ if (urls && (urls.local.length || urls.network.length)) {
197
+ serverUrl = new URL(urls.local[0] ?? urls.network[0]);
198
+ }
177
199
  // log connection information
178
200
  server.printUrls();
179
201
  }
180
202
  // TODO: adjust output typings to reflect both development servers
181
- yield { success: true, port: listeningAddress?.port };
203
+ yield {
204
+ success: true,
205
+ port: serverUrl?.port,
206
+ baseUrl: serverUrl?.href,
207
+ };
182
208
  }
183
209
  // Add cleanup logic via a builder teardown
184
210
  let deferred;
@@ -196,7 +222,7 @@ function handleUpdate(normalizePath, generatedFiles, server, serverOptions, logg
196
222
  for (const [file, record] of generatedFiles) {
197
223
  if (record.updated) {
198
224
  updatedFiles.push(file);
199
- const updatedModules = server.moduleGraph.getModulesByFile(normalizePath(node_path_1.default.join(server.config.root, file)));
225
+ const updatedModules = server.moduleGraph.getModulesByFile(normalizePath((0, node_path_1.join)(server.config.root, file)));
200
226
  updatedModules?.forEach((m) => server?.moduleGraph.invalidateModule(m));
201
227
  }
202
228
  }
@@ -240,7 +266,7 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
240
266
  filePath = '/index.html';
241
267
  }
242
268
  else {
243
- filePath = normalizePath(file.path);
269
+ filePath = '/' + normalizePath(file.path);
244
270
  }
245
271
  seen.add(filePath);
246
272
  // Skip analysis of sourcemaps
@@ -278,16 +304,20 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
278
304
  }
279
305
  }
280
306
  // eslint-disable-next-line max-lines-per-function
281
- async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssr, prebundleTransformer, target) {
307
+ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssr, prebundleTransformer, target, extensionMiddleware, indexHtmlTransformer) {
282
308
  const proxy = await (0, load_proxy_config_1.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig, true);
283
309
  // dynamically import Vite for ESM compatibility
284
310
  const { normalizePath } = await Promise.resolve().then(() => __importStar(require('vite')));
285
311
  // Path will not exist on disk and only used to provide separate path for Vite requests
286
- const virtualProjectRoot = normalizePath(node_path_1.default.join(serverOptions.workspaceRoot, `.angular/vite-root/${(0, node_crypto_1.randomUUID)()}/`));
312
+ const virtualProjectRoot = normalizePath((0, node_path_1.join)(serverOptions.workspaceRoot, `.angular/vite-root`, serverOptions.buildTarget.project));
313
+ const serverExplicitExternal = [
314
+ ...(await Promise.resolve().then(() => __importStar(require('node:module')))).builtinModules,
315
+ ...externalMetadata.explicit,
316
+ ];
287
317
  const configuration = {
288
318
  configFile: false,
289
319
  envFile: false,
290
- cacheDir: node_path_1.default.join(serverOptions.cacheOptions.path, 'vite'),
320
+ cacheDir: (0, node_path_1.join)(serverOptions.cacheOptions.path, 'vite'),
291
321
  root: virtualProjectRoot,
292
322
  publicDir: false,
293
323
  esbuild: false,
@@ -317,8 +347,31 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
317
347
  preTransformRequests: externalMetadata.explicit.length === 0,
318
348
  },
319
349
  ssr: {
320
- // Exclude any provided dependencies (currently build defined externals)
321
- external: externalMetadata.explicit,
350
+ // Note: `true` and `/.*/` have different sematics. When true, the `external` option is ignored.
351
+ noExternal: /.*/,
352
+ // Exclude any Node.js built in module and provided dependencies (currently build defined externals)
353
+ external: serverExplicitExternal,
354
+ optimizeDeps: getDepOptimizationConfig({
355
+ /**
356
+ * *********************************************
357
+ * NOTE: Temporary disable 'optimizeDeps' for SSR.
358
+ * *********************************************
359
+ *
360
+ * Currently this causes a number of issues.
361
+ * - Deps are re-optimized everytime the server is started.
362
+ * - Added deps after a rebuild are not optimized.
363
+ * - Breaks RxJs (Unless it is added as external). See: https://github.com/angular/angular-cli/issues/26235
364
+ */
365
+ // Only enable with caching since it causes prebundle dependencies to be cached
366
+ disabled: true,
367
+ // Exclude any explicitly defined dependencies (currently build defined externals and node.js built-ins)
368
+ exclude: serverExplicitExternal,
369
+ // Include all implict dependencies from the external packages internal option
370
+ include: externalMetadata.implicitServer,
371
+ ssr: true,
372
+ prebundleTransformer,
373
+ target,
374
+ }),
322
375
  },
323
376
  plugins: [
324
377
  (0, i18n_locale_plugin_1.createAngularLocaleDataPlugin)(),
@@ -336,19 +389,18 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
336
389
  if (importer && source[0] === '.' && importer.startsWith(virtualProjectRoot)) {
337
390
  // Remove query if present
338
391
  const [importerFile] = importer.split('?', 1);
339
- source = normalizePath(node_path_1.default.join(node_path_1.default.dirname(node_path_1.default.relative(virtualProjectRoot, importerFile)), source));
340
- }
341
- if (source[0] === '/') {
342
- source = source.slice(1);
392
+ source =
393
+ '/' +
394
+ normalizePath((0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.relative)(virtualProjectRoot, importerFile)), source));
343
395
  }
344
396
  const [file] = source.split('?', 1);
345
397
  if (outputFiles.has(file)) {
346
- return node_path_1.default.join(virtualProjectRoot, source);
398
+ return (0, node_path_1.join)(virtualProjectRoot, source);
347
399
  }
348
400
  },
349
401
  load(id) {
350
402
  const [file] = id.split('?', 1);
351
- const relativeFile = normalizePath(node_path_1.default.relative(virtualProjectRoot, file));
403
+ const relativeFile = '/' + normalizePath((0, node_path_1.relative)(virtualProjectRoot, file));
352
404
  const codeContents = outputFiles.get(relativeFile)?.contents;
353
405
  if (codeContents === undefined) {
354
406
  if (relativeFile.endsWith('/node_modules/vite/dist/client/client.mjs')) {
@@ -392,7 +444,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
392
444
  // Parse the incoming request.
393
445
  // The base of the URL is unused but required to parse the URL.
394
446
  const pathname = pathnameWithoutServePath(req.url, serverOptions);
395
- const extension = node_path_1.default.extname(pathname);
447
+ const extension = (0, node_path_1.extname)(pathname);
396
448
  // Rewrite all build assets to a vite raw fs URL
397
449
  const assetSourcePath = assets.get(pathname);
398
450
  if (assetSourcePath !== undefined) {
@@ -422,16 +474,24 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
422
474
  }
423
475
  next();
424
476
  });
477
+ if (extensionMiddleware?.length) {
478
+ extensionMiddleware.forEach((middleware) => server.middlewares.use(middleware));
479
+ }
425
480
  // Returning a function, installs middleware after the main transform middleware but
426
481
  // before the built-in HTML middleware
427
482
  return () => {
428
483
  function angularSSRMiddleware(req, res, next) {
429
484
  const url = req.originalUrl;
430
- if (!url || url.endsWith('.html')) {
485
+ if (
486
+ // Skip if path is not defined.
487
+ !url ||
488
+ // Skip if path is like a file.
489
+ // NOTE: We use a regexp to mitigate against matching requests like: /browse/pl.0ef59752c0cd457dbf1391f08cbd936f
490
+ /^\.[a-z]{2,4}$/i.test((0, node_path_1.extname)(url.split('?')[0]))) {
431
491
  next();
432
492
  return;
433
493
  }
434
- const rawHtml = outputFiles.get('index.server.html')?.contents;
494
+ const rawHtml = outputFiles.get('/index.server.html')?.contents;
435
495
  if (!rawHtml) {
436
496
  next();
437
497
  return;
@@ -443,15 +503,17 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
443
503
  document: html,
444
504
  route,
445
505
  serverContext: 'ssr',
446
- loadBundle: (path) =>
506
+ loadBundle: (uri) =>
447
507
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
448
- server.ssrLoadModule(path.slice(1)),
508
+ server.ssrLoadModule(uri.slice(1)),
449
509
  // Files here are only needed for critical CSS inlining.
450
510
  outputFiles: {},
451
511
  // TODO: add support for critical css inlining.
452
512
  inlineCriticalCss: false,
453
513
  });
454
- return content;
514
+ return indexHtmlTransformer && content
515
+ ? await indexHtmlTransformer(content)
516
+ : content;
455
517
  });
456
518
  }
457
519
  if (ssr) {
@@ -468,7 +530,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
468
530
  if (pathname === '/' || pathname === `/index.html`) {
469
531
  const rawHtml = outputFiles.get('/index.html')?.contents;
470
532
  if (rawHtml) {
471
- transformIndexHtmlAndAddHeaders(req.url, rawHtml, res, next);
533
+ transformIndexHtmlAndAddHeaders(req.url, rawHtml, res, next, indexHtmlTransformer);
472
534
  return;
473
535
  }
474
536
  }
@@ -499,35 +561,18 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
499
561
  },
500
562
  },
501
563
  ],
502
- optimizeDeps: {
564
+ // Browser only optimizeDeps. (This does not run for SSR dependencies).
565
+ optimizeDeps: getDepOptimizationConfig({
503
566
  // Only enable with caching since it causes prebundle dependencies to be cached
504
567
  disabled: !serverOptions.cacheOptions.enabled,
505
568
  // Exclude any explicitly defined dependencies (currently build defined externals)
506
569
  exclude: externalMetadata.explicit,
507
570
  // Include all implict dependencies from the external packages internal option
508
- include: externalMetadata.implicit,
509
- // Skip automatic file-based entry point discovery
510
- entries: [],
511
- // Add an esbuild plugin to run the Angular linker on dependencies
512
- esbuildOptions: {
513
- // Set esbuild supported targets.
514
- target,
515
- supported: (0, utils_1.getFeatureSupport)(target),
516
- plugins: [
517
- {
518
- name: 'angular-vite-optimize-deps',
519
- setup(build) {
520
- build.onLoad({ filter: /\.[cm]?js$/ }, async (args) => {
521
- return {
522
- contents: await prebundleTransformer.transformFile(args.path),
523
- loader: 'js',
524
- };
525
- });
526
- },
527
- },
528
- ],
529
- },
530
- },
571
+ include: externalMetadata.implicitBrowser,
572
+ ssr: false,
573
+ prebundleTransformer,
574
+ target,
575
+ }),
531
576
  };
532
577
  if (serverOptions.ssl) {
533
578
  if (serverOptions.sslCert && serverOptions.sslKey) {
@@ -575,3 +620,36 @@ function pathnameWithoutServePath(url, serverOptions) {
575
620
  }
576
621
  return pathname;
577
622
  }
623
+ function getDepOptimizationConfig({ disabled, exclude, include, target, prebundleTransformer, ssr, }) {
624
+ const plugins = [
625
+ {
626
+ name: `angular-vite-optimize-deps${ssr ? '-ssr' : ''}`,
627
+ setup(build) {
628
+ build.onLoad({ filter: /\.[cm]?js$/ }, async (args) => {
629
+ return {
630
+ contents: await prebundleTransformer.transformFile(args.path),
631
+ loader: 'js',
632
+ };
633
+ });
634
+ },
635
+ },
636
+ ];
637
+ if (ssr) {
638
+ plugins.unshift((0, rxjs_esm_resolution_plugin_1.createRxjsEsmResolutionPlugin)());
639
+ }
640
+ return {
641
+ // Only enable with caching since it causes prebundle dependencies to be cached
642
+ disabled,
643
+ // Exclude any explicitly defined dependencies (currently build defined externals)
644
+ exclude,
645
+ // Include all implict dependencies from the external packages internal option
646
+ include,
647
+ // Add an esbuild plugin to run the Angular linker on dependencies
648
+ esbuildOptions: {
649
+ // Set esbuild supported targets.
650
+ target,
651
+ supported: (0, utils_1.getFeatureSupport)(target),
652
+ plugins,
653
+ },
654
+ };
655
+ }