@angular/build 19.1.0-next.0 → 19.1.0-next.2

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 (42) hide show
  1. package/package.json +13 -11
  2. package/src/builders/application/execute-build.js +37 -4
  3. package/src/builders/application/execute-post-bundle.d.ts +3 -1
  4. package/src/builders/application/execute-post-bundle.js +6 -4
  5. package/src/builders/application/i18n.d.ts +3 -1
  6. package/src/builders/application/i18n.js +15 -13
  7. package/src/builders/application/options.js +7 -5
  8. package/src/builders/dev-server/vite-server.d.ts +2 -2
  9. package/src/builders/dev-server/vite-server.js +31 -47
  10. package/src/builders/extract-i18n/options.js +1 -1
  11. package/src/tools/angular/angular-host.d.ts +1 -1
  12. package/src/tools/angular/angular-host.js +4 -4
  13. package/src/tools/angular/compilation/aot-compilation.d.ts +2 -0
  14. package/src/tools/angular/compilation/aot-compilation.js +30 -9
  15. package/src/tools/angular/compilation/factory.d.ts +2 -1
  16. package/src/tools/angular/compilation/factory.js +5 -4
  17. package/src/tools/angular/compilation/jit-compilation.d.ts +2 -0
  18. package/src/tools/angular/compilation/jit-compilation.js +12 -2
  19. package/src/tools/angular/compilation/parallel-compilation.d.ts +3 -2
  20. package/src/tools/angular/compilation/parallel-compilation.js +4 -1
  21. package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
  22. package/src/tools/angular/compilation/parallel-worker.js +3 -1
  23. package/src/tools/angular/transformers/lazy-routes-transformer.d.ts +39 -0
  24. package/src/tools/angular/transformers/lazy-routes-transformer.js +163 -0
  25. package/src/tools/esbuild/angular/compiler-plugin.d.ts +1 -0
  26. package/src/tools/esbuild/angular/compiler-plugin.js +1 -1
  27. package/src/tools/esbuild/application-code-bundle.js +26 -20
  28. package/src/tools/esbuild/compiler-plugin-options.js +1 -0
  29. package/src/tools/vite/plugins/angular-memory-plugin.d.ts +1 -0
  30. package/src/tools/vite/plugins/angular-memory-plugin.js +25 -2
  31. package/src/tools/vite/utils.d.ts +14 -0
  32. package/src/tools/vite/utils.js +37 -0
  33. package/src/utils/environment-options.js +1 -1
  34. package/src/utils/i18n-options.d.ts +4 -1
  35. package/src/utils/i18n-options.js +50 -7
  36. package/src/utils/index-file/auto-csp.js +5 -5
  37. package/src/utils/normalize-cache.js +1 -1
  38. package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.js +9 -1
  39. package/src/utils/server-rendering/manifest.d.ts +5 -1
  40. package/src/utils/server-rendering/manifest.js +60 -11
  41. package/src/utils/server-rendering/prerender.js +1 -1
  42. package/tsconfig-build.json +6 -0
@@ -17,6 +17,7 @@ const typescript_1 = __importDefault(require("typescript"));
17
17
  const profiling_1 = require("../../esbuild/profiling");
18
18
  const angular_host_1 = require("../angular-host");
19
19
  const jit_bootstrap_transformer_1 = require("../transformers/jit-bootstrap-transformer");
20
+ const lazy_routes_transformer_1 = require("../transformers/lazy-routes-transformer");
20
21
  const web_worker_transformer_1 = require("../transformers/web-worker-transformer");
21
22
  const angular_compilation_1 = require("./angular-compilation");
22
23
  const hmr_candidates_1 = require("./hmr-candidates");
@@ -48,7 +49,12 @@ class AngularCompilationState {
48
49
  }
49
50
  }
50
51
  class AotCompilation extends angular_compilation_1.AngularCompilation {
52
+ browserOnlyBuild;
51
53
  #state;
54
+ constructor(browserOnlyBuild) {
55
+ super();
56
+ this.browserOnlyBuild = browserOnlyBuild;
57
+ }
52
58
  async initialize(tsconfig, hostOptions, compilerOptionsTransformer) {
53
59
  // Dynamically load the Angular compiler CLI package
54
60
  const { NgtscProgram, OptimizeFor } = await angular_compilation_1.AngularCompilation.loadCompilerCli();
@@ -58,22 +64,34 @@ class AotCompilation extends angular_compilation_1.AngularCompilation {
58
64
  if (compilerOptions.externalRuntimeStyles) {
59
65
  hostOptions.externalStylesheets ??= new Map();
60
66
  }
67
+ // Reuse the package.json cache from the previous compilation
68
+ const packageJsonCache = this.#state?.compilerHost
69
+ .getModuleResolutionCache?.()
70
+ ?.getPackageJsonInfoCache();
61
71
  const useHmr = compilerOptions['_enableHmr'] &&
62
72
  hostOptions.modifiedFiles &&
63
73
  hostOptions.modifiedFiles.size <= HMR_MODIFIED_FILE_LIMIT;
64
- // Collect stale source files for HMR analysis of inline component resources
65
74
  let staleSourceFiles;
66
- if (useHmr && hostOptions.modifiedFiles && this.#state) {
75
+ let clearPackageJsonCache = false;
76
+ if (hostOptions.modifiedFiles && this.#state) {
67
77
  for (const modifiedFile of hostOptions.modifiedFiles) {
68
- const sourceFile = this.#state.typeScriptProgram.getSourceFile(modifiedFile);
69
- if (sourceFile) {
70
- staleSourceFiles ??= new Map();
71
- staleSourceFiles.set(modifiedFile, sourceFile);
78
+ // Clear package.json cache if a node modules file was modified
79
+ if (!clearPackageJsonCache && modifiedFile.includes('node_modules')) {
80
+ clearPackageJsonCache = true;
81
+ packageJsonCache?.clear();
82
+ }
83
+ // Collect stale source files for HMR analysis of inline component resources
84
+ if (useHmr) {
85
+ const sourceFile = this.#state.typeScriptProgram.getSourceFile(modifiedFile);
86
+ if (sourceFile) {
87
+ staleSourceFiles ??= new Map();
88
+ staleSourceFiles.set(modifiedFile, sourceFile);
89
+ }
72
90
  }
73
91
  }
74
92
  }
75
93
  // Create Angular compiler host
76
- const host = (0, angular_host_1.createAngularCompilerHost)(typescript_1.default, compilerOptions, hostOptions);
94
+ const host = (0, angular_host_1.createAngularCompilerHost)(typescript_1.default, compilerOptions, hostOptions, packageJsonCache);
77
95
  // Create the Angular specific program that contains the Angular compiler
78
96
  const angularProgram = (0, profiling_1.profileSync)('NG_CREATE_PROGRAM', () => new NgtscProgram(rootNames, compilerOptions, host, this.#state?.angularProgram));
79
97
  const angularCompiler = angularProgram.compiler;
@@ -99,6 +117,7 @@ class AotCompilation extends angular_compilation_1.AngularCompilation {
99
117
  if (relativePath.startsWith('..')) {
100
118
  relativePath = componentFilename;
101
119
  }
120
+ relativePath = relativePath.replaceAll('\\', '/');
102
121
  const updateId = encodeURIComponent(`${host.getCanonicalFileName(relativePath)}@${node.name?.text}`);
103
122
  const updateText = angularCompiler.emitHmrUpdateModule(node);
104
123
  if (updateText === null) {
@@ -210,8 +229,10 @@ class AotCompilation extends angular_compilation_1.AngularCompilation {
210
229
  };
211
230
  const transformers = angularCompiler.prepareEmit().transformers;
212
231
  transformers.before ??= [];
213
- transformers.before.push((0, jit_bootstrap_transformer_1.replaceBootstrap)(() => typeScriptProgram.getProgram().getTypeChecker()));
214
- transformers.before.push(webWorkerTransform);
232
+ transformers.before.push((0, jit_bootstrap_transformer_1.replaceBootstrap)(() => typeScriptProgram.getProgram().getTypeChecker()), webWorkerTransform);
233
+ if (!this.browserOnlyBuild) {
234
+ transformers.before.push((0, lazy_routes_transformer_1.lazyRoutesTransformer)(compilerOptions, compilerHost));
235
+ }
215
236
  // Emit is handled in write file callback when using TypeScript
216
237
  if (useTypeScriptTranspilation) {
217
238
  // TypeScript will loop until there are no more affected files in the program
@@ -11,6 +11,7 @@ import type { AngularCompilation } from './angular-compilation';
11
11
  * compilation either for AOT or JIT mode. By default a parallel compilation is created
12
12
  * that uses a Node.js worker thread.
13
13
  * @param jit True, for Angular JIT compilation; False, for Angular AOT compilation.
14
+ * @param browserOnlyBuild True, for browser only builds; False, for browser and server builds.
14
15
  * @returns An instance of an Angular compilation object.
15
16
  */
16
- export declare function createAngularCompilation(jit: boolean): Promise<AngularCompilation>;
17
+ export declare function createAngularCompilation(jit: boolean, browserOnlyBuild: boolean): Promise<AngularCompilation>;
@@ -47,19 +47,20 @@ const environment_options_1 = require("../../../utils/environment-options");
47
47
  * compilation either for AOT or JIT mode. By default a parallel compilation is created
48
48
  * that uses a Node.js worker thread.
49
49
  * @param jit True, for Angular JIT compilation; False, for Angular AOT compilation.
50
+ * @param browserOnlyBuild True, for browser only builds; False, for browser and server builds.
50
51
  * @returns An instance of an Angular compilation object.
51
52
  */
52
- async function createAngularCompilation(jit) {
53
+ async function createAngularCompilation(jit, browserOnlyBuild) {
53
54
  if (environment_options_1.useParallelTs) {
54
55
  const { ParallelCompilation } = await Promise.resolve().then(() => __importStar(require('./parallel-compilation')));
55
- return new ParallelCompilation(jit);
56
+ return new ParallelCompilation(jit, browserOnlyBuild);
56
57
  }
57
58
  if (jit) {
58
59
  const { JitCompilation } = await Promise.resolve().then(() => __importStar(require('./jit-compilation')));
59
- return new JitCompilation();
60
+ return new JitCompilation(browserOnlyBuild);
60
61
  }
61
62
  else {
62
63
  const { AotCompilation } = await Promise.resolve().then(() => __importStar(require('./aot-compilation')));
63
- return new AotCompilation();
64
+ return new AotCompilation(browserOnlyBuild);
64
65
  }
65
66
  }
@@ -11,6 +11,8 @@ import { AngularHostOptions } from '../angular-host';
11
11
  import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation';
12
12
  export declare class JitCompilation extends AngularCompilation {
13
13
  #private;
14
+ private readonly browserOnlyBuild;
15
+ constructor(browserOnlyBuild: boolean);
14
16
  initialize(tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions): Promise<{
15
17
  affectedFiles: ReadonlySet<ts.SourceFile>;
16
18
  compilerOptions: ng.CompilerOptions;
@@ -17,6 +17,7 @@ const load_esm_1 = require("../../../utils/load-esm");
17
17
  const profiling_1 = require("../../esbuild/profiling");
18
18
  const angular_host_1 = require("../angular-host");
19
19
  const jit_resource_transformer_1 = require("../transformers/jit-resource-transformer");
20
+ const lazy_routes_transformer_1 = require("../transformers/lazy-routes-transformer");
20
21
  const web_worker_transformer_1 = require("../transformers/web-worker-transformer");
21
22
  const angular_compilation_1 = require("./angular-compilation");
22
23
  class JitCompilationState {
@@ -34,7 +35,12 @@ class JitCompilationState {
34
35
  }
35
36
  }
36
37
  class JitCompilation extends angular_compilation_1.AngularCompilation {
38
+ browserOnlyBuild;
37
39
  #state;
40
+ constructor(browserOnlyBuild) {
41
+ super();
42
+ this.browserOnlyBuild = browserOnlyBuild;
43
+ }
38
44
  async initialize(tsconfig, hostOptions, compilerOptionsTransformer) {
39
45
  // Dynamically load the Angular compiler CLI package
40
46
  const { constructorParametersDownlevelTransform } = await (0, load_esm_1.loadEsmModule)('@angular/compiler-cli/private/tooling');
@@ -42,7 +48,7 @@ class JitCompilation extends angular_compilation_1.AngularCompilation {
42
48
  const { options: originalCompilerOptions, rootNames, errors: configurationDiagnostics, } = await this.loadConfiguration(tsconfig);
43
49
  const compilerOptions = compilerOptionsTransformer?.(originalCompilerOptions) ?? originalCompilerOptions;
44
50
  // Create Angular compiler host
45
- const host = (0, angular_host_1.createAngularCompilerHost)(typescript_1.default, compilerOptions, hostOptions);
51
+ const host = (0, angular_host_1.createAngularCompilerHost)(typescript_1.default, compilerOptions, hostOptions, undefined);
46
52
  // Create the TypeScript Program
47
53
  const typeScriptProgram = (0, profiling_1.profileSync)('TS_CREATE_PROGRAM', () => typescript_1.default.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, compilerOptions, host, this.#state?.typeScriptProgram ?? typescript_1.default.readBuilderProgram(compilerOptions, host), configurationDiagnostics));
48
54
  const affectedFiles = (0, profiling_1.profileSync)('TS_FIND_AFFECTED', () => findAffectedFiles(typeScriptProgram));
@@ -71,7 +77,8 @@ class JitCompilation extends angular_compilation_1.AngularCompilation {
71
77
  emitAffectedFiles() {
72
78
  (0, node_assert_1.default)(this.#state, 'Compilation must be initialized prior to emitting files.');
73
79
  const { compilerHost, typeScriptProgram, constructorParametersDownlevelTransform, replaceResourcesTransform, webWorkerTransform, } = this.#state;
74
- const buildInfoFilename = typeScriptProgram.getCompilerOptions().tsBuildInfoFile ?? '.tsbuildinfo';
80
+ const compilerOptions = typeScriptProgram.getCompilerOptions();
81
+ const buildInfoFilename = compilerOptions.tsBuildInfoFile ?? '.tsbuildinfo';
75
82
  const emittedFiles = [];
76
83
  const writeFileCallback = (filename, contents, _a, _b, sourceFiles) => {
77
84
  if (!sourceFiles?.length && filename.endsWith(buildInfoFilename)) {
@@ -89,6 +96,9 @@ class JitCompilation extends angular_compilation_1.AngularCompilation {
89
96
  webWorkerTransform,
90
97
  ],
91
98
  };
99
+ if (!this.browserOnlyBuild) {
100
+ transformers.before.push((0, lazy_routes_transformer_1.lazyRoutesTransformer)(compilerOptions, compilerHost));
101
+ }
92
102
  // TypeScript will loop until there are no more affected files in the program
93
103
  while (typeScriptProgram.emitNextAffectedFile(writeFileCallback, undefined, undefined, transformers)) {
94
104
  /* empty */
@@ -20,8 +20,9 @@ import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-c
20
20
  */
21
21
  export declare class ParallelCompilation extends AngularCompilation {
22
22
  #private;
23
- readonly jit: boolean;
24
- constructor(jit: boolean);
23
+ private readonly jit;
24
+ private readonly browserOnlyBuild;
25
+ constructor(jit: boolean, browserOnlyBuild: boolean);
25
26
  initialize(tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: CompilerOptions) => CompilerOptions): Promise<{
26
27
  affectedFiles: ReadonlySet<SourceFile>;
27
28
  compilerOptions: CompilerOptions;
@@ -22,10 +22,12 @@ const angular_compilation_1 = require("./angular-compilation");
22
22
  */
23
23
  class ParallelCompilation extends angular_compilation_1.AngularCompilation {
24
24
  jit;
25
+ browserOnlyBuild;
25
26
  #worker;
26
- constructor(jit) {
27
+ constructor(jit, browserOnlyBuild) {
27
28
  super();
28
29
  this.jit = jit;
30
+ this.browserOnlyBuild = browserOnlyBuild;
29
31
  // TODO: Convert to import.meta usage during ESM transition
30
32
  const localRequire = (0, node_module_1.createRequire)(__filename);
31
33
  this.#worker = new worker_pool_1.WorkerPool({
@@ -82,6 +84,7 @@ class ParallelCompilation extends angular_compilation_1.AngularCompilation {
82
84
  fileReplacements: hostOptions.fileReplacements,
83
85
  tsconfig,
84
86
  jit: this.jit,
87
+ browserOnlyBuild: this.browserOnlyBuild,
85
88
  stylesheetPort: stylesheetChannel.port2,
86
89
  optionsPort: optionsChannel.port2,
87
90
  optionsSignal,
@@ -10,6 +10,7 @@ import { type MessagePort } from 'node:worker_threads';
10
10
  import type { DiagnosticModes } from './angular-compilation';
11
11
  export interface InitRequest {
12
12
  jit: boolean;
13
+ browserOnlyBuild: boolean;
13
14
  tsconfig: string;
14
15
  fileReplacements?: Record<string, string>;
15
16
  stylesheetPort: MessagePort;
@@ -23,7 +23,9 @@ const jit_compilation_1 = require("./jit-compilation");
23
23
  let compilation;
24
24
  const sourceFileCache = new source_file_cache_1.SourceFileCache();
25
25
  async function initialize(request) {
26
- compilation ??= request.jit ? new jit_compilation_1.JitCompilation() : new aot_compilation_1.AotCompilation();
26
+ compilation ??= request.jit
27
+ ? new jit_compilation_1.JitCompilation(request.browserOnlyBuild)
28
+ : new aot_compilation_1.AotCompilation(request.browserOnlyBuild);
27
29
  const stylesheetRequests = new Map();
28
30
  request.stylesheetPort.on('message', ({ requestId, value, error }) => {
29
31
  if (error) {
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ import ts from 'typescript';
9
+ /**
10
+ * A transformer factory that adds a property to the lazy-loaded route object.
11
+ * This property is used to allow for the retrieval of the module path during SSR.
12
+ *
13
+ * @param compilerOptions The compiler options.
14
+ * @param compilerHost The compiler host.
15
+ * @returns A transformer factory.
16
+ *
17
+ * @example
18
+ * **Before:**
19
+ * ```ts
20
+ * const routes: Routes = [
21
+ * {
22
+ * path: 'lazy',
23
+ * loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)
24
+ * }
25
+ * ];
26
+ * ```
27
+ *
28
+ * **After:**
29
+ * ```ts
30
+ * const routes: Routes = [
31
+ * {
32
+ * path: 'lazy',
33
+ * loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule),
34
+ * ...(typeof ngServerMode !== "undefined" && ngServerMode ? { ɵentryName: "./lazy/lazy.module.ts" }: {})
35
+ * }
36
+ * ];
37
+ * ```
38
+ */
39
+ export declare function lazyRoutesTransformer(compilerOptions: ts.CompilerOptions, compilerHost: ts.CompilerHost): ts.TransformerFactory<ts.SourceFile>;
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.lazyRoutesTransformer = lazyRoutesTransformer;
14
+ const node_assert_1 = __importDefault(require("node:assert"));
15
+ const posix_1 = require("node:path/posix");
16
+ const typescript_1 = __importDefault(require("typescript"));
17
+ /**
18
+ * A transformer factory that adds a property to the lazy-loaded route object.
19
+ * This property is used to allow for the retrieval of the module path during SSR.
20
+ *
21
+ * @param compilerOptions The compiler options.
22
+ * @param compilerHost The compiler host.
23
+ * @returns A transformer factory.
24
+ *
25
+ * @example
26
+ * **Before:**
27
+ * ```ts
28
+ * const routes: Routes = [
29
+ * {
30
+ * path: 'lazy',
31
+ * loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)
32
+ * }
33
+ * ];
34
+ * ```
35
+ *
36
+ * **After:**
37
+ * ```ts
38
+ * const routes: Routes = [
39
+ * {
40
+ * path: 'lazy',
41
+ * loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule),
42
+ * ...(typeof ngServerMode !== "undefined" && ngServerMode ? { ɵentryName: "./lazy/lazy.module.ts" }: {})
43
+ * }
44
+ * ];
45
+ * ```
46
+ */
47
+ function lazyRoutesTransformer(compilerOptions, compilerHost) {
48
+ const moduleResolutionCache = compilerHost.getModuleResolutionCache?.();
49
+ (0, node_assert_1.default)(typeof compilerOptions.basePath === 'string', 'compilerOptions.basePath should be a string.');
50
+ const basePath = compilerOptions.basePath;
51
+ return (context) => {
52
+ const factory = context.factory;
53
+ const visitor = (node) => {
54
+ if (!typescript_1.default.isObjectLiteralExpression(node)) {
55
+ // Not an object literal, so skip it.
56
+ return typescript_1.default.visitEachChild(node, visitor, context);
57
+ }
58
+ const loadFunction = getLoadComponentOrChildrenProperty(node)?.initializer;
59
+ // Check if the initializer is an arrow function or a function expression
60
+ if (!loadFunction ||
61
+ (!typescript_1.default.isArrowFunction(loadFunction) && !typescript_1.default.isFunctionExpression(loadFunction))) {
62
+ return typescript_1.default.visitEachChild(node, visitor, context);
63
+ }
64
+ let callExpression;
65
+ if (typescript_1.default.isArrowFunction(loadFunction)) {
66
+ // Handle arrow functions: body can either be a block or a direct call expression
67
+ const body = loadFunction.body;
68
+ if (typescript_1.default.isBlock(body)) {
69
+ // Arrow function with a block: check the first statement for a return call expression
70
+ const firstStatement = body.statements[0];
71
+ if (firstStatement &&
72
+ typescript_1.default.isReturnStatement(firstStatement) &&
73
+ firstStatement.expression &&
74
+ typescript_1.default.isCallExpression(firstStatement.expression)) {
75
+ callExpression = firstStatement.expression;
76
+ }
77
+ }
78
+ else if (typescript_1.default.isCallExpression(body)) {
79
+ // Arrow function with a direct call expression as its body
80
+ callExpression = body;
81
+ }
82
+ }
83
+ else if (typescript_1.default.isFunctionExpression(loadFunction)) {
84
+ // Handle function expressions: check for a return statement with a call expression
85
+ const returnExpression = loadFunction.body.statements.find(typescript_1.default.isReturnStatement)?.expression;
86
+ if (returnExpression && typescript_1.default.isCallExpression(returnExpression)) {
87
+ callExpression = returnExpression;
88
+ }
89
+ }
90
+ if (!callExpression) {
91
+ return typescript_1.default.visitEachChild(node, visitor, context);
92
+ }
93
+ // Optionally check for the 'then' property access expression
94
+ const expression = callExpression.expression;
95
+ if (!typescript_1.default.isCallExpression(expression) &&
96
+ typescript_1.default.isPropertyAccessExpression(expression) &&
97
+ expression.name.text !== 'then') {
98
+ return typescript_1.default.visitEachChild(node, visitor, context);
99
+ }
100
+ const importExpression = typescript_1.default.isPropertyAccessExpression(expression)
101
+ ? expression.expression // Navigate to the underlying expression for 'then'
102
+ : callExpression;
103
+ // Ensure the underlying expression is an import call
104
+ if (!typescript_1.default.isCallExpression(importExpression) ||
105
+ importExpression.expression.kind !== typescript_1.default.SyntaxKind.ImportKeyword) {
106
+ return typescript_1.default.visitEachChild(node, visitor, context);
107
+ }
108
+ // Check if the argument to the import call is a string literal
109
+ const callExpressionArgument = importExpression.arguments[0];
110
+ if (!typescript_1.default.isStringLiteralLike(callExpressionArgument)) {
111
+ // Not a string literal, so skip it.
112
+ return typescript_1.default.visitEachChild(node, visitor, context);
113
+ }
114
+ const resolvedPath = typescript_1.default.resolveModuleName(callExpressionArgument.text, node.getSourceFile().fileName, compilerOptions, compilerHost, moduleResolutionCache)?.resolvedModule?.resolvedFileName;
115
+ if (!resolvedPath) {
116
+ // Could not resolve the module, so skip it.
117
+ return typescript_1.default.visitEachChild(node, visitor, context);
118
+ }
119
+ const resolvedRelativePath = (0, posix_1.relative)(basePath, resolvedPath);
120
+ // Create the new property
121
+ // Example: `...(typeof ngServerMode !== "undefined" && ngServerMode ? { ɵentryName: "src/home.ts" }: {})`
122
+ const newProperty = factory.createSpreadAssignment(factory.createParenthesizedExpression(factory.createConditionalExpression(factory.createBinaryExpression(factory.createBinaryExpression(factory.createTypeOfExpression(factory.createIdentifier('ngServerMode')), factory.createToken(typescript_1.default.SyntaxKind.ExclamationEqualsEqualsToken), factory.createStringLiteral('undefined')), factory.createToken(typescript_1.default.SyntaxKind.AmpersandAmpersandToken), factory.createIdentifier('ngServerMode')), factory.createToken(typescript_1.default.SyntaxKind.QuestionToken), factory.createObjectLiteralExpression([
123
+ factory.createPropertyAssignment(factory.createIdentifier('ɵentryName'), factory.createStringLiteral(resolvedRelativePath)),
124
+ ]), factory.createToken(typescript_1.default.SyntaxKind.ColonToken), factory.createObjectLiteralExpression([]))));
125
+ // Add the new property to the object literal.
126
+ return factory.updateObjectLiteralExpression(node, [...node.properties, newProperty]);
127
+ };
128
+ return (sourceFile) => {
129
+ const text = sourceFile.text;
130
+ if (!text.includes('loadC')) {
131
+ // Fast check for 'loadComponent' and 'loadChildren'.
132
+ return sourceFile;
133
+ }
134
+ return typescript_1.default.visitEachChild(sourceFile, visitor, context);
135
+ };
136
+ };
137
+ }
138
+ /**
139
+ * Retrieves the property assignment for the `loadComponent` or `loadChildren` property of a route object.
140
+ *
141
+ * @param node The object literal expression to search.
142
+ * @returns The property assignment if found, otherwise `undefined`.
143
+ */
144
+ function getLoadComponentOrChildrenProperty(node) {
145
+ let hasPathProperty = false;
146
+ let loadComponentOrChildrenProperty;
147
+ for (const prop of node.properties) {
148
+ if (!typescript_1.default.isPropertyAssignment(prop) || !typescript_1.default.isIdentifier(prop.name)) {
149
+ continue;
150
+ }
151
+ const propertyNameText = prop.name.text;
152
+ if (propertyNameText === 'path') {
153
+ hasPathProperty = true;
154
+ }
155
+ else if (propertyNameText === 'loadComponent' || propertyNameText === 'loadChildren') {
156
+ loadComponentOrChildrenProperty = prop;
157
+ }
158
+ if (hasPathProperty && loadComponentOrChildrenProperty) {
159
+ break;
160
+ }
161
+ }
162
+ return loadComponentOrChildrenProperty;
163
+ }
@@ -13,6 +13,7 @@ export interface CompilerPluginOptions {
13
13
  sourcemap: boolean | 'external';
14
14
  tsconfig: string;
15
15
  jit?: boolean;
16
+ browserOnlyBuild?: boolean;
16
17
  /** Skip TypeScript compilation setup. This is useful to re-use the TypeScript compilation from another plugin. */
17
18
  noopTypeScriptCompilation?: boolean;
18
19
  advancedOptimizations?: boolean;
@@ -103,7 +103,7 @@ function createCompilerPlugin(pluginOptions, stylesheetBundler) {
103
103
  // Create new reusable compilation for the appropriate mode based on the `jit` plugin option
104
104
  const compilation = pluginOptions.noopTypeScriptCompilation
105
105
  ? new compilation_1.NoopCompilation()
106
- : await (0, compilation_1.createAngularCompilation)(!!pluginOptions.jit);
106
+ : await (0, compilation_1.createAngularCompilation)(!!pluginOptions.jit, !!pluginOptions.browserOnlyBuild);
107
107
  // Compilation is initially assumed to have errors until emitted
108
108
  let hasCompilationErrors = true;
109
109
  // Determines if TypeScript should process JavaScript files based on tsconfig `allowJs` option
@@ -136,6 +136,15 @@ function createServerPolyfillBundleOptions(options, target, loadResultCache) {
136
136
  if (!polyfillBundleOptions) {
137
137
  return;
138
138
  }
139
+ const jsBanner = [];
140
+ if (polyfillBundleOptions.external?.length) {
141
+ jsBanner.push(`globalThis['ngServerMode'] = true;`);
142
+ }
143
+ if (isNodePlatform) {
144
+ // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
145
+ // See: https://github.com/evanw/esbuild/issues/1921.
146
+ jsBanner.push(`import { createRequire } from 'node:module';`, `globalThis['require'] ??= createRequire(import.meta.url);`);
147
+ }
139
148
  const buildOptions = {
140
149
  ...polyfillBundleOptions,
141
150
  platform: isNodePlatform ? 'node' : 'neutral',
@@ -146,16 +155,9 @@ function createServerPolyfillBundleOptions(options, target, loadResultCache) {
146
155
  // More details: https://github.com/angular/angular-cli/issues/25405.
147
156
  mainFields: ['es2020', 'es2015', 'module', 'main'],
148
157
  entryNames: '[name]',
149
- banner: isNodePlatform
150
- ? {
151
- js: [
152
- // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
153
- // See: https://github.com/evanw/esbuild/issues/1921.
154
- `import { createRequire } from 'node:module';`,
155
- `globalThis['require'] ??= createRequire(import.meta.url);`,
156
- ].join('\n'),
157
- }
158
- : undefined,
158
+ banner: {
159
+ js: jsBanner.join('\n'),
160
+ },
159
161
  target,
160
162
  entryPoints: {
161
163
  'polyfills.server': namespace,
@@ -248,6 +250,8 @@ function createServerMainCodeBundleOptions(options, target, sourceFileCache, sty
248
250
  ɵextractRoutesAndCreateRouteTree,
249
251
  ɵgetOrCreateAngularServerApp,
250
252
  } from '@angular/ssr';`,
253
+ // Need for HMR
254
+ `export { ɵresetCompiledComponents } from '@angular/core';`,
251
255
  // Re-export all symbols including default export from 'main.server.ts'
252
256
  `export { default } from '${mainServerEntryPointJsImport}';`,
253
257
  `export * from '${mainServerEntryPointJsImport}';`,
@@ -274,19 +278,21 @@ function createSsrEntryCodeBundleOptions(options, target, sourceFileCache, style
274
278
  const ssrEntryNamespace = 'angular:ssr-entry';
275
279
  const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest';
276
280
  const isNodePlatform = options.ssrOptions?.platform !== schema_1.ExperimentalPlatform.Neutral;
281
+ const jsBanner = [];
282
+ if (options.externalDependencies?.length) {
283
+ jsBanner.push(`globalThis['ngServerMode'] = true;`);
284
+ }
285
+ if (isNodePlatform) {
286
+ // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
287
+ // See: https://github.com/evanw/esbuild/issues/1921.
288
+ jsBanner.push(`import { createRequire } from 'node:module';`, `globalThis['require'] ??= createRequire(import.meta.url);`);
289
+ }
277
290
  const buildOptions = {
278
291
  ...getEsBuildServerCommonOptions(options),
279
292
  target,
280
- banner: isNodePlatform
281
- ? {
282
- js: [
283
- // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
284
- // See: https://github.com/evanw/esbuild/issues/1921.
285
- `import { createRequire } from 'node:module';`,
286
- `globalThis['require'] ??= createRequire(import.meta.url);`,
287
- ].join('\n'),
288
- }
289
- : undefined,
293
+ banner: {
294
+ js: jsBanner.join('\n'),
295
+ },
290
296
  entryPoints: {
291
297
  'server': ssrEntryNamespace,
292
298
  },
@@ -12,6 +12,7 @@ function createCompilerPluginOptions(options, sourceFileCache, loadResultCache,
12
12
  const { sourcemapOptions, tsconfig, fileReplacements, advancedOptimizations, jit, externalRuntimeStyles, instrumentForCoverage, } = options;
13
13
  const incremental = !!options.watch;
14
14
  return {
15
+ browserOnlyBuild: !options.serverEntryPoint,
15
16
  sourcemap: !!sourcemapOptions.scripts && (sourcemapOptions.hidden ? 'external' : true),
16
17
  thirdPartySourcemaps: sourcemapOptions.vendor,
17
18
  tsconfig,
@@ -10,6 +10,7 @@ import { AngularMemoryOutputFiles } from '../utils';
10
10
  interface AngularMemoryPluginOptions {
11
11
  virtualProjectRoot: string;
12
12
  outputFiles: AngularMemoryOutputFiles;
13
+ templateUpdates?: ReadonlyMap<string, string>;
13
14
  external?: string[];
14
15
  skipViteClient?: boolean;
15
16
  }
@@ -15,6 +15,7 @@ const node_assert_1 = __importDefault(require("node:assert"));
15
15
  const promises_1 = require("node:fs/promises");
16
16
  const node_path_1 = require("node:path");
17
17
  const load_esm_1 = require("../../../utils/load-esm");
18
+ const ANGULAR_PREFIX = '/@ng/';
18
19
  async function createAngularMemoryPlugin(options) {
19
20
  const { virtualProjectRoot, outputFiles, external } = options;
20
21
  const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
@@ -22,7 +23,11 @@ async function createAngularMemoryPlugin(options) {
22
23
  name: 'vite:angular-memory',
23
24
  // Ensures plugin hooks run before built-in Vite hooks
24
25
  enforce: 'pre',
25
- async resolveId(source, importer) {
26
+ async resolveId(source, importer, { ssr }) {
27
+ // For SSR with component HMR, pass through as a virtual module
28
+ if (ssr && source.startsWith(ANGULAR_PREFIX)) {
29
+ return '\0' + source;
30
+ }
26
31
  // Prevent vite from resolving an explicit external dependency (`externalDependencies` option)
27
32
  if (external?.includes(source)) {
28
33
  // This is still not ideal since Vite will still transform the import specifier to
@@ -35,13 +40,31 @@ async function createAngularMemoryPlugin(options) {
35
40
  const [importerFile] = importer.split('?', 1);
36
41
  source = '/' + (0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.relative)(virtualProjectRoot, importerFile)), source);
37
42
  }
43
+ else if (!ssr &&
44
+ source[0] === '/' &&
45
+ importer.endsWith('index.html') &&
46
+ normalizePath(importer).startsWith(virtualProjectRoot)) {
47
+ // This is only needed when using SSR and `angularSsrMiddleware` (old style) to correctly resolve
48
+ // .js files when using lazy-loading.
49
+ // Remove query if present
50
+ const [importerFile] = importer.split('?', 1);
51
+ source =
52
+ '/' + (0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.relative)(virtualProjectRoot, importerFile)), (0, node_path_1.basename)(source));
53
+ }
38
54
  }
39
55
  const [file] = source.split('?', 1);
40
56
  if (outputFiles.has(file)) {
41
57
  return (0, node_path_1.join)(virtualProjectRoot, source);
42
58
  }
43
59
  },
44
- load(id) {
60
+ load(id, loadOptions) {
61
+ // For SSR component updates, return the component update module or empty if none
62
+ if (loadOptions?.ssr && id.startsWith(`\0${ANGULAR_PREFIX}`)) {
63
+ // Extract component identifier (first character is rollup virtual module null)
64
+ const requestUrl = new URL(id.slice(1), 'http://localhost');
65
+ const componentId = requestUrl.searchParams.get('c');
66
+ return (componentId && options.templateUpdates?.get(encodeURIComponent(componentId))) ?? '';
67
+ }
45
68
  const [file] = id.split('?', 1);
46
69
  const relativeFile = '/' + normalizePath((0, node_path_1.relative)(virtualProjectRoot, file));
47
70
  const codeContents = outputFiles.get(relativeFile)?.contents;
@@ -5,6 +5,8 @@
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.dev/license
7
7
  */
8
+ import type { DepOptimizationConfig } from 'vite';
9
+ import { JavaScriptTransformer } from '../esbuild/javascript-transformer';
8
10
  export type AngularMemoryOutputFiles = Map<string, {
9
11
  contents: Uint8Array;
10
12
  hash: string;
@@ -12,3 +14,15 @@ export type AngularMemoryOutputFiles = Map<string, {
12
14
  }>;
13
15
  export declare function pathnameWithoutBasePath(url: string, basePath: string): string;
14
16
  export declare function lookupMimeTypeFromRequest(url: string): string | undefined;
17
+ export type EsbuildLoaderOption = Exclude<DepOptimizationConfig['esbuildOptions'], undefined>['loader'];
18
+ export declare function getDepOptimizationConfig({ disabled, exclude, include, target, zoneless, prebundleTransformer, ssr, loader, thirdPartySourcemaps, }: {
19
+ disabled: boolean;
20
+ exclude: string[];
21
+ include: string[];
22
+ target: string[];
23
+ prebundleTransformer: JavaScriptTransformer;
24
+ ssr: boolean;
25
+ zoneless: boolean;
26
+ loader?: EsbuildLoaderOption;
27
+ thirdPartySourcemaps: boolean;
28
+ }): DepOptimizationConfig;