@angular/build 19.0.0-next.7 → 19.0.0-next.9

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 (91) hide show
  1. package/LICENSE +5 -5
  2. package/package.json +10 -9
  3. package/src/builders/application/execute-build.js +19 -2
  4. package/src/builders/application/execute-post-bundle.d.ts +2 -2
  5. package/src/builders/application/execute-post-bundle.js +30 -11
  6. package/src/builders/application/i18n.d.ts +2 -2
  7. package/src/builders/application/i18n.js +4 -5
  8. package/src/builders/application/index.js +8 -5
  9. package/src/builders/application/options.d.ts +25 -1
  10. package/src/builders/application/options.js +31 -2
  11. package/src/builders/application/schema.d.ts +15 -0
  12. package/src/builders/application/schema.js +11 -1
  13. package/src/builders/application/schema.json +5 -0
  14. package/src/builders/application/setup-bundling.js +6 -3
  15. package/src/builders/dev-server/vite-server.d.ts +2 -1
  16. package/src/builders/dev-server/vite-server.js +71 -53
  17. package/src/builders/extract-i18n/application-extraction.js +3 -3
  18. package/src/tools/angular/angular-host.d.ts +2 -1
  19. package/src/tools/angular/angular-host.js +20 -1
  20. package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
  21. package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
  22. package/src/tools/angular/compilation/aot-compilation.js +9 -1
  23. package/src/tools/angular/compilation/parallel-compilation.d.ts +1 -0
  24. package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
  25. package/src/tools/angular/compilation/parallel-worker.js +2 -1
  26. package/src/tools/babel/plugins/add-code-coverage.d.ts +14 -0
  27. package/src/tools/babel/plugins/add-code-coverage.js +44 -0
  28. package/src/tools/babel/plugins/types.d.ts +20 -0
  29. package/src/tools/esbuild/angular/compiler-plugin.d.ts +2 -0
  30. package/src/tools/esbuild/angular/compiler-plugin.js +44 -4
  31. package/src/tools/esbuild/angular/component-stylesheets.d.ts +8 -3
  32. package/src/tools/esbuild/angular/component-stylesheets.js +46 -11
  33. package/src/tools/esbuild/application-code-bundle.d.ts +1 -0
  34. package/src/tools/esbuild/application-code-bundle.js +109 -2
  35. package/src/tools/esbuild/budget-stats.js +1 -1
  36. package/src/tools/esbuild/bundler-context.d.ts +4 -3
  37. package/src/tools/esbuild/bundler-context.js +8 -4
  38. package/src/tools/esbuild/bundler-execution-result.d.ts +5 -2
  39. package/src/tools/esbuild/bundler-execution-result.js +7 -3
  40. package/src/tools/esbuild/cache.d.ts +5 -0
  41. package/src/tools/esbuild/cache.js +7 -0
  42. package/src/tools/esbuild/compiler-plugin-options.js +3 -1
  43. package/src/tools/esbuild/i18n-inliner.js +2 -1
  44. package/src/tools/esbuild/javascript-transformer-worker.d.ts +1 -0
  45. package/src/tools/esbuild/javascript-transformer-worker.js +5 -1
  46. package/src/tools/esbuild/javascript-transformer.d.ts +2 -2
  47. package/src/tools/esbuild/javascript-transformer.js +5 -3
  48. package/src/tools/esbuild/utils.js +7 -3
  49. package/src/tools/vite/middlewares/assets-middleware.js +2 -5
  50. package/src/tools/vite/middlewares/html-fallback-middleware.js +22 -6
  51. package/src/tools/vite/middlewares/index.d.ts +1 -1
  52. package/src/tools/vite/middlewares/index.js +3 -2
  53. package/src/tools/vite/middlewares/ssr-middleware.d.ts +2 -1
  54. package/src/tools/vite/middlewares/ssr-middleware.js +62 -15
  55. package/src/tools/vite/plugins/angular-memory-plugin.d.ts +16 -0
  56. package/src/tools/vite/{angular-memory-plugin.js → plugins/angular-memory-plugin.js} +19 -41
  57. package/src/tools/vite/{i18n-locale-plugin.d.ts → plugins/i18n-locale-plugin.d.ts} +0 -4
  58. package/src/tools/vite/{i18n-locale-plugin.js → plugins/i18n-locale-plugin.js} +2 -3
  59. package/src/tools/vite/plugins/index.d.ts +12 -0
  60. package/src/tools/vite/plugins/index.js +21 -0
  61. package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +41 -0
  62. package/src/tools/vite/plugins/setup-middlewares-plugin.js +62 -0
  63. package/src/tools/vite/plugins/ssr-transform-plugin.d.ts +9 -0
  64. package/src/tools/vite/plugins/ssr-transform-plugin.js +38 -0
  65. package/src/utils/environment-options.d.ts +2 -0
  66. package/src/utils/environment-options.js +5 -1
  67. package/src/utils/index-file/index-html-generator.js +5 -0
  68. package/src/utils/index-file/ngcm-attribute.d.ts +15 -0
  69. package/src/utils/index-file/ngcm-attribute.js +37 -0
  70. package/src/utils/index-file/valid-self-closing-tags.js +28 -0
  71. package/src/utils/normalize-cache.js +1 -1
  72. package/src/utils/server-rendering/fetch-patch.d.ts +1 -1
  73. package/src/utils/server-rendering/fetch-patch.js +2 -2
  74. package/src/utils/server-rendering/launch-server.d.ts +14 -0
  75. package/src/utils/server-rendering/launch-server.js +63 -0
  76. package/src/utils/server-rendering/load-esm-from-memory.d.ts +7 -0
  77. package/src/utils/server-rendering/manifest.d.ts +8 -2
  78. package/src/utils/server-rendering/manifest.js +52 -12
  79. package/src/utils/server-rendering/models.d.ts +27 -0
  80. package/src/utils/server-rendering/models.js +22 -0
  81. package/src/utils/server-rendering/prerender.d.ts +6 -10
  82. package/src/utils/server-rendering/prerender.js +102 -63
  83. package/src/utils/server-rendering/render-worker.d.ts +4 -1
  84. package/src/utils/server-rendering/render-worker.js +13 -3
  85. package/src/utils/server-rendering/routes-extractor-worker.d.ts +6 -10
  86. package/src/utils/server-rendering/routes-extractor-worker.js +14 -5
  87. package/src/utils/server-rendering/utils.d.ts +11 -0
  88. package/src/utils/server-rendering/utils.js +17 -0
  89. package/src/tools/vite/angular-memory-plugin.d.ts +0 -22
  90. /package/src/tools/vite/{id-prefix-plugin.d.ts → plugins/id-prefix-plugin.d.ts} +0 -0
  91. /package/src/tools/vite/{id-prefix-plugin.js → plugins/id-prefix-plugin.js} +0 -0
@@ -39,10 +39,9 @@ const node_assert_1 = __importDefault(require("node:assert"));
39
39
  const promises_1 = require("node:fs/promises");
40
40
  const node_module_1 = require("node:module");
41
41
  const node_path_1 = require("node:path");
42
- const angular_memory_plugin_1 = require("../../tools/vite/angular-memory-plugin");
43
- const i18n_locale_plugin_1 = require("../../tools/vite/i18n-locale-plugin");
44
- const id_prefix_plugin_1 = require("../../tools/vite/id-prefix-plugin");
42
+ const plugins_1 = require("../../tools/vite/plugins");
45
43
  const utils_1 = require("../../utils");
44
+ const environment_options_1 = require("../../utils/environment-options");
46
45
  const load_esm_1 = require("../../utils/load-esm");
47
46
  const results_1 = require("../application/results");
48
47
  const internal_1 = require("./internal");
@@ -66,17 +65,17 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
66
65
  }
67
66
  // TODO: Adjust architect to not force a JsonObject derived return type
68
67
  const browserOptions = (await context.validateOptions(rawBrowserOptions, builderName));
69
- if (browserOptions.prerender || browserOptions.ssr) {
68
+ if (browserOptions.prerender) {
70
69
  // Disable prerendering if enabled and force SSR.
71
70
  // This is so instead of prerendering all the routes for every change, the page is "prerendered" when it is requested.
72
71
  browserOptions.prerender = false;
73
- // Avoid bundling and processing the ssr entry-point as this is not used by the dev-server.
74
- browserOptions.ssr = true;
75
- // https://nodejs.org/api/process.html#processsetsourcemapsenabledval
76
- process.setSourceMapsEnabled(true);
72
+ browserOptions.ssr ||= true;
77
73
  }
78
74
  // Set all packages as external to support Vite's prebundle caching
79
75
  browserOptions.externalPackages = serverOptions.prebundle;
76
+ // Disable generating a full manifest with routes.
77
+ // This is done during runtime when using the dev-server.
78
+ browserOptions.partialSSRBuild = true;
80
79
  // The development server currently only supports a single locale when localizing.
81
80
  // This matches the behavior of the Webpack-based development server but could be expanded in the future.
82
81
  if (browserOptions.localize === true ||
@@ -88,7 +87,13 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
88
87
  // When localization is enabled with a single locale, force a flat path to maintain behavior with the existing Webpack-based dev server.
89
88
  browserOptions.forceI18nFlatOutput = true;
90
89
  }
91
- const { vendor: thirdPartySourcemaps } = (0, utils_1.normalizeSourceMaps)(browserOptions.sourceMap ?? false);
90
+ const { vendor: thirdPartySourcemaps, scripts: scriptsSourcemaps } = (0, utils_1.normalizeSourceMaps)(browserOptions.sourceMap ?? false);
91
+ if (scriptsSourcemaps && browserOptions.server) {
92
+ // https://nodejs.org/api/process.html#processsetsourcemapsenabledval
93
+ process.setSourceMapsEnabled(true);
94
+ }
95
+ // TODO: Enable by default once full support across CLI and FW is integrated
96
+ browserOptions.externalRuntimeStyles = environment_options_1.useComponentStyleHmr;
92
97
  // Setup the prebundling transformer that will be shared across Vite prebundling requests
93
98
  const prebundleTransformer = new internal_1.JavaScriptTransformer(
94
99
  // Always enable JIT linking to support applications built with and without AOT.
@@ -124,7 +129,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
124
129
  case results_1.ResultKind.Failure:
125
130
  if (result.errors.length && server) {
126
131
  hadError = true;
127
- server.hot.send({
132
+ server.ws.send({
128
133
  type: 'error',
129
134
  err: {
130
135
  message: result.errors[0].text,
@@ -171,7 +176,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
171
176
  if (hadError && server) {
172
177
  hadError = false;
173
178
  // Send an empty update to clear the error overlay
174
- server.hot.send({
179
+ server.ws.send({
175
180
  'type': 'update',
176
181
  updates: [],
177
182
  });
@@ -180,8 +185,8 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
180
185
  let requiresServerRestart = false;
181
186
  if (result.detail?.['externalMetadata']) {
182
187
  const { implicitBrowser, implicitServer, explicit } = result.detail['externalMetadata'];
183
- const implicitServerFiltered = implicitServer.filter((m) => removeNodeJsBuiltinModules(m) && removeAbsoluteUrls(m));
184
- const implicitBrowserFiltered = implicitBrowser.filter(removeAbsoluteUrls);
188
+ const implicitServerFiltered = implicitServer.filter((m) => !(0, node_module_1.isBuiltin)(m) && !isAbsoluteUrl(m));
189
+ const implicitBrowserFiltered = implicitBrowser.filter((m) => !isAbsoluteUrl(m));
185
190
  if (browserOptions.ssr && serverOptions.prebundle !== false) {
186
191
  const previousImplicitServer = new Set(externalMetadata.implicitServer);
187
192
  // Restart the server to force SSR dep re-optimization when a dependency has been added.
@@ -194,7 +199,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
194
199
  externalMetadata.implicitServer.length = 0;
195
200
  externalMetadata.implicitBrowser.length = 0;
196
201
  externalMetadata.explicitBrowser.push(...explicit);
197
- externalMetadata.explicitServer.push(...explicit, ...nodeJsBuiltinModules);
202
+ externalMetadata.explicitServer.push(...explicit, ...node_module_1.builtinModules);
198
203
  externalMetadata.implicitServer.push(...implicitServerFiltered);
199
204
  externalMetadata.implicitBrowser.push(...implicitBrowserFiltered);
200
205
  // The below needs to be sorted as Vite uses these options are part of the hashing invalidation algorithm.
@@ -238,15 +243,19 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
238
243
  const polyfills = Array.isArray((browserOptions.polyfills ??= []))
239
244
  ? browserOptions.polyfills
240
245
  : [browserOptions.polyfills];
246
+ let ssrMode = plugins_1.ServerSsrMode.NoSsr;
247
+ if (browserOptions.outputMode &&
248
+ typeof browserOptions.ssr === 'object' &&
249
+ browserOptions.ssr.entry) {
250
+ ssrMode = plugins_1.ServerSsrMode.ExternalSsrMiddleware;
251
+ }
252
+ else if (browserOptions.server) {
253
+ ssrMode = plugins_1.ServerSsrMode.InternalSsrMiddleware;
254
+ }
241
255
  // Setup server and start listening
242
- const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, !!browserOptions.ssr, prebundleTransformer, target, (0, internal_1.isZonelessApp)(polyfills), usedComponentStyles, browserOptions.loader, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps);
256
+ const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, (0, internal_1.isZonelessApp)(polyfills), usedComponentStyles, browserOptions.loader, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps);
243
257
  server = await createServer(serverConfiguration);
244
258
  await server.listen();
245
- if (browserOptions.ssr && serverOptions.prebundle !== false) {
246
- // Warm up the SSR request and begin optimizing dependencies.
247
- // Without this, Vite will only start optimizing SSR modules when the first request is made.
248
- void server.warmupRequest('./main.server.mjs', { ssr: true });
249
- }
250
259
  const urls = server.resolvedUrls;
251
260
  if (urls && (urls.local.length || urls.network.length)) {
252
261
  serverUrl = new URL(urls.local[0] ?? urls.network[0]);
@@ -260,7 +269,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
260
269
  key: 'r',
261
270
  description: 'force reload browser',
262
271
  action(server) {
263
- server.hot.send({
272
+ server.ws.send({
264
273
  type: 'full-reload',
265
274
  path: '*',
266
275
  });
@@ -280,28 +289,30 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
280
289
  }
281
290
  async function handleUpdate(normalizePath, generatedFiles, server, serverOptions, logger, usedComponentStyles) {
282
291
  const updatedFiles = [];
283
- let isServerFileUpdated = false;
292
+ let destroyAngularServerAppCalled = false;
284
293
  // Invalidate any updated files
285
- for (const [file, record] of generatedFiles) {
286
- if (record.updated) {
287
- updatedFiles.push(file);
288
- isServerFileUpdated ||= record.type === internal_1.BuildOutputFileType.Server;
289
- const updatedModules = server.moduleGraph.getModulesByFile(normalizePath((0, node_path_1.join)(server.config.root, file)));
290
- updatedModules?.forEach((m) => server?.moduleGraph.invalidateModule(m));
294
+ for (const [file, { updated, type }] of generatedFiles) {
295
+ if (!updated) {
296
+ continue;
297
+ }
298
+ if (type === internal_1.BuildOutputFileType.ServerApplication && !destroyAngularServerAppCalled) {
299
+ // Clear the server app cache
300
+ // This must be done before module invalidation.
301
+ const { ɵdestroyAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs'));
302
+ ɵdestroyAngularServerApp();
303
+ destroyAngularServerAppCalled = true;
291
304
  }
305
+ updatedFiles.push(file);
306
+ const updatedModules = server.moduleGraph.getModulesByFile(normalizePath((0, node_path_1.join)(server.config.root, file)));
307
+ updatedModules?.forEach((m) => server.moduleGraph.invalidateModule(m));
292
308
  }
293
309
  if (!updatedFiles.length) {
294
310
  return;
295
311
  }
296
- // clean server apps cache
297
- if (isServerFileUpdated) {
298
- const { ɵdestroyAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs'));
299
- ɵdestroyAngularServerApp();
300
- }
301
312
  if (serverOptions.liveReload || serverOptions.hmr) {
302
313
  if (updatedFiles.every((f) => f.endsWith('.css'))) {
303
314
  const timestamp = Date.now();
304
- server.hot.send({
315
+ server.ws.send({
305
316
  type: 'update',
306
317
  updates: updatedFiles.flatMap((filePath) => {
307
318
  // For component styles, an HMR update must be sent for each one with the corresponding
@@ -332,7 +343,7 @@ async function handleUpdate(normalizePath, generatedFiles, server, serverOptions
332
343
  }
333
344
  // Send reload command to clients
334
345
  if (serverOptions.liveReload) {
335
- server.hot.send({
346
+ server.ws.send({
336
347
  type: 'full-reload',
337
348
  path: '*',
338
349
  });
@@ -392,13 +403,13 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
392
403
  }
393
404
  }
394
405
  }
395
- async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssr, prebundleTransformer, target, zoneless, usedComponentStyles, prebundleLoaderExtensions, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
406
+ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, zoneless, usedComponentStyles, prebundleLoaderExtensions, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
396
407
  const proxy = await (0, utils_1.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig);
397
408
  // dynamically import Vite for ESM compatibility
398
409
  const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
399
410
  // Path will not exist on disk and only used to provide separate path for Vite requests
400
411
  const virtualProjectRoot = normalizePath((0, node_path_1.join)(serverOptions.workspaceRoot, `.angular/vite-root`, serverOptions.buildTarget.project));
401
- const cacheDir = (0, node_path_1.join)(serverOptions.cacheOptions.path, 'vite');
412
+ const cacheDir = (0, node_path_1.join)(serverOptions.cacheOptions.path, serverOptions.buildTarget.project, 'vite');
402
413
  const configuration = {
403
414
  configFile: false,
404
415
  envFile: false,
@@ -427,6 +438,9 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
427
438
  preserveSymlinks,
428
439
  },
429
440
  server: {
441
+ warmup: {
442
+ ssrFiles: ['./main.server.mjs', './server.mjs'],
443
+ },
430
444
  port: serverOptions.port,
431
445
  strictPort: true,
432
446
  host: serverOptions.host,
@@ -475,20 +489,22 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
475
489
  }),
476
490
  },
477
491
  plugins: [
478
- (0, i18n_locale_plugin_1.createAngularLocaleDataPlugin)(),
479
- (0, angular_memory_plugin_1.createAngularMemoryPlugin)({
480
- workspaceRoot: serverOptions.workspaceRoot,
481
- virtualProjectRoot,
492
+ (0, plugins_1.createAngularLocaleDataPlugin)(),
493
+ (0, plugins_1.createAngularSetupMiddlewaresPlugin)({
482
494
  outputFiles,
483
495
  assets,
484
- ssr,
485
- external: externalMetadata.explicitBrowser,
486
496
  indexHtmlTransformer,
487
497
  extensionMiddleware,
488
- normalizePath,
489
498
  usedComponentStyles,
499
+ ssrMode,
500
+ }),
501
+ (0, plugins_1.createRemoveIdPrefixPlugin)(externalMetadata.explicitBrowser),
502
+ await (0, plugins_1.createAngularSsrTransformPlugin)(serverOptions.workspaceRoot),
503
+ await (0, plugins_1.createAngularMemoryPlugin)({
504
+ virtualProjectRoot,
505
+ outputFiles,
506
+ external: externalMetadata.explicitBrowser,
490
507
  }),
491
- (0, id_prefix_plugin_1.createRemoveIdPrefixPlugin)(externalMetadata.explicitBrowser),
492
508
  ],
493
509
  // Browser only optimizeDeps. (This does not run for SSR dependencies).
494
510
  optimizeDeps: getDepOptimizationConfig({
@@ -556,12 +572,14 @@ function getDepOptimizationConfig({ disabled, exclude, include, target, zoneless
556
572
  },
557
573
  };
558
574
  }
559
- const nodeJsBuiltinModules = new Set(node_module_1.builtinModules);
560
- /** Remove any Node.js builtin modules to avoid Vite's prebundling from processing them as files. */
561
- function removeNodeJsBuiltinModules(value) {
562
- return !nodeJsBuiltinModules.has(value);
563
- }
564
- /** Remove any absolute URLs (http://, https://, //) to avoid Vite's prebundling from processing them as files. */
565
- function removeAbsoluteUrls(value) {
566
- return !/^(?:https?:)?\/\//.test(value);
575
+ /**
576
+ * Checks if the given value is an absolute URL.
577
+ *
578
+ * This function helps in avoiding Vite's prebundling from processing absolute URLs (http://, https://, //) as files.
579
+ *
580
+ * @param value - The URL or path to check.
581
+ * @returns `true` if the value is not an absolute URL; otherwise, `false`.
582
+ */
583
+ function isAbsoluteUrl(value) {
584
+ return /^(?:https?:)?\/\//.test(value);
567
585
  }
@@ -15,6 +15,7 @@ const node_fs_1 = require("node:fs");
15
15
  const node_path_1 = __importDefault(require("node:path"));
16
16
  const application_1 = require("../application");
17
17
  const results_1 = require("../application/results");
18
+ const schema_1 = require("../application/schema");
18
19
  async function extractMessages(options, builderName, context, extractorConstructor, extensions) {
19
20
  const messages = [];
20
21
  // Setup the build options for the application based on the buildTarget option
@@ -25,9 +26,8 @@ async function extractMessages(options, builderName, context, extractorConstruct
25
26
  buildOptions.budgets = undefined;
26
27
  buildOptions.index = false;
27
28
  buildOptions.serviceWorker = false;
28
- buildOptions.ssr = false;
29
- buildOptions.appShell = false;
30
- buildOptions.prerender = false;
29
+ buildOptions.outputMode = schema_1.OutputMode.Static;
30
+ buildOptions.server = undefined;
31
31
  // Build the application with the build options
32
32
  const builderResult = await first((0, application_1.buildApplicationInternal)(buildOptions, context, extensions));
33
33
  let success = false;
@@ -13,7 +13,8 @@ export interface AngularHostOptions {
13
13
  fileReplacements?: Record<string, string>;
14
14
  sourceFileCache?: Map<string, ts.SourceFile>;
15
15
  modifiedFiles?: Set<string>;
16
- transformStylesheet(data: string, containingFile: string, stylesheetFile?: string): Promise<string | null>;
16
+ externalStylesheets?: Map<string, string>;
17
+ transformStylesheet(data: string, containingFile: string, stylesheetFile?: string, order?: number): Promise<string | null>;
17
18
  processWebWorker(workerFile: string, containingFile: string): string;
18
19
  }
19
20
  /**
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.ensureSourceFileVersions = ensureSourceFileVersions;
14
14
  exports.createAngularCompilerHost = createAngularCompilerHost;
15
+ const node_assert_1 = __importDefault(require("node:assert"));
15
16
  const node_crypto_1 = require("node:crypto");
16
17
  const node_path_1 = __importDefault(require("node:path"));
17
18
  /**
@@ -107,13 +108,31 @@ function createAngularCompilerHost(typescript, compilerOptions, hostOptions) {
107
108
  if (context.type !== 'style') {
108
109
  return null;
109
110
  }
111
+ (0, node_assert_1.default)(!context.resourceFile || !hostOptions.externalStylesheets?.has(context.resourceFile), 'External runtime stylesheets should not be transformed: ' + context.resourceFile);
110
112
  // No transformation required if the resource is empty
111
113
  if (data.trim().length === 0) {
112
114
  return { content: '' };
113
115
  }
114
- const result = await hostOptions.transformStylesheet(data, context.containingFile, context.resourceFile ?? undefined);
116
+ const result = await hostOptions.transformStylesheet(data, context.containingFile, context.resourceFile ?? undefined,
117
+ // TODO: Remove once available in compiler-cli types
118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
+ context.order);
115
120
  return typeof result === 'string' ? { content: result } : null;
116
121
  };
122
+ host.resourceNameToFileName = function (resourceName, containingFile) {
123
+ const resolvedPath = node_path_1.default.join(node_path_1.default.dirname(containingFile), resourceName);
124
+ // All resource names that have HTML file extensions are assumed to be templates
125
+ if (resourceName.endsWith('.html') || !hostOptions.externalStylesheets) {
126
+ return resolvedPath;
127
+ }
128
+ // For external stylesheets, create a unique identifier and store the mapping
129
+ let externalId = hostOptions.externalStylesheets.get(resolvedPath);
130
+ if (externalId === undefined) {
131
+ externalId = (0, node_crypto_1.createHash)('sha256').update(resolvedPath).digest('hex');
132
+ hostOptions.externalStylesheets.set(resolvedPath, externalId);
133
+ }
134
+ return externalId + '.css';
135
+ };
117
136
  // Allow the AOT compiler to request the set of changed templates and styles
118
137
  host.getModifiedResourceFiles = function () {
119
138
  return hostOptions.modifiedFiles;
@@ -30,6 +30,7 @@ export declare abstract class AngularCompilation {
30
30
  affectedFiles: ReadonlySet<ts.SourceFile>;
31
31
  compilerOptions: ng.CompilerOptions;
32
32
  referencedFiles: readonly string[];
33
+ externalStylesheets?: ReadonlyMap<string, string>;
33
34
  }>;
34
35
  abstract emitAffectedFiles(): Iterable<EmitFileResult> | Promise<Iterable<EmitFileResult>>;
35
36
  protected abstract collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic> | Promise<Iterable<ts.Diagnostic>>;
@@ -15,6 +15,7 @@ export declare class AotCompilation extends AngularCompilation {
15
15
  affectedFiles: ReadonlySet<ts.SourceFile>;
16
16
  compilerOptions: ng.CompilerOptions;
17
17
  referencedFiles: readonly string[];
18
+ externalStylesheets?: ReadonlyMap<string, string>;
18
19
  }>;
19
20
  collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic>;
20
21
  emitAffectedFiles(): Iterable<EmitFileResult>;
@@ -47,6 +47,9 @@ class AotCompilation extends angular_compilation_1.AngularCompilation {
47
47
  // Load the compiler configuration and transform as needed
48
48
  const { options: originalCompilerOptions, rootNames, errors: configurationDiagnostics, } = await this.loadConfiguration(tsconfig);
49
49
  const compilerOptions = compilerOptionsTransformer?.(originalCompilerOptions) ?? originalCompilerOptions;
50
+ if (compilerOptions.externalRuntimeStyles) {
51
+ hostOptions.externalStylesheets ??= new Map();
52
+ }
50
53
  // Create Angular compiler host
51
54
  const host = (0, angular_host_1.createAngularCompilerHost)(typescript_1.default, compilerOptions, hostOptions);
52
55
  // Create the Angular specific program that contains the Angular compiler
@@ -82,7 +85,12 @@ class AotCompilation extends angular_compilation_1.AngularCompilation {
82
85
  return [sourceFile.fileName, ...resourceDependencies];
83
86
  });
84
87
  this.#state = new AngularCompilationState(angularProgram, host, typeScriptProgram, affectedFiles, affectedFiles.size === 1 ? OptimizeFor.SingleFile : OptimizeFor.WholeProgram, (0, web_worker_transformer_1.createWorkerTransformer)(hostOptions.processWebWorker.bind(hostOptions)), this.#state?.diagnosticCache);
85
- return { affectedFiles, compilerOptions, referencedFiles };
88
+ return {
89
+ affectedFiles,
90
+ compilerOptions,
91
+ referencedFiles,
92
+ externalStylesheets: hostOptions.externalStylesheets,
93
+ };
86
94
  }
87
95
  *collectDiagnostics(modes) {
88
96
  (0, node_assert_1.default)(this.#state, 'Angular compilation must be initialized prior to collecting diagnostics.');
@@ -26,6 +26,7 @@ export declare class ParallelCompilation extends AngularCompilation {
26
26
  affectedFiles: ReadonlySet<SourceFile>;
27
27
  compilerOptions: CompilerOptions;
28
28
  referencedFiles: readonly string[];
29
+ externalStylesheets?: ReadonlyMap<string, string>;
29
30
  }>;
30
31
  /**
31
32
  * This is not needed with this compilation type since the worker will already send a response
@@ -19,6 +19,7 @@ export interface InitRequest {
19
19
  webWorkerSignal: Int32Array;
20
20
  }
21
21
  export declare function initialize(request: InitRequest): Promise<{
22
+ externalStylesheets: ReadonlyMap<string, string> | undefined;
22
23
  referencedFiles: readonly string[];
23
24
  compilerOptions: {
24
25
  allowJs: boolean | undefined;
@@ -33,7 +33,7 @@ async function initialize(request) {
33
33
  stylesheetRequests.get(requestId)?.[0](value);
34
34
  }
35
35
  });
36
- const { compilerOptions, referencedFiles } = await compilation.initialize(request.tsconfig, {
36
+ const { compilerOptions, referencedFiles, externalStylesheets } = await compilation.initialize(request.tsconfig, {
37
37
  fileReplacements: request.fileReplacements,
38
38
  sourceFileCache,
39
39
  modifiedFiles: sourceFileCache.modifiedFiles,
@@ -69,6 +69,7 @@ async function initialize(request) {
69
69
  return result?.transformedOptions ?? compilerOptions;
70
70
  });
71
71
  return {
72
+ externalStylesheets,
72
73
  referencedFiles,
73
74
  // TODO: Expand? `allowJs`, `isolatedModules`, `sourceMap`, `inlineSourceMap` are the only fields needed currently.
74
75
  compilerOptions: {
@@ -0,0 +1,14 @@
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 { PluginObj } from '@babel/core';
9
+ /**
10
+ * A babel plugin factory function for adding istanbul instrumentation.
11
+ *
12
+ * @returns A babel plugin object instance.
13
+ */
14
+ export default function (): PluginObj;
@@ -0,0 +1,44 @@
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.default = default_1;
14
+ const core_1 = require("@babel/core");
15
+ const istanbul_lib_instrument_1 = require("istanbul-lib-instrument");
16
+ const node_assert_1 = __importDefault(require("node:assert"));
17
+ /**
18
+ * A babel plugin factory function for adding istanbul instrumentation.
19
+ *
20
+ * @returns A babel plugin object instance.
21
+ */
22
+ function default_1() {
23
+ const visitors = new WeakMap();
24
+ return {
25
+ visitor: {
26
+ Program: {
27
+ enter(path, state) {
28
+ const visitor = (0, istanbul_lib_instrument_1.programVisitor)(core_1.types, state.filename, {
29
+ // Babel returns a Converter object from the `convert-source-map` package
30
+ inputSourceMap: state.file.inputMap?.toObject(),
31
+ });
32
+ visitors.set(path, visitor);
33
+ visitor.enter(path);
34
+ },
35
+ exit(path) {
36
+ const visitor = visitors.get(path);
37
+ (0, node_assert_1.default)(visitor, 'Instrumentation visitor should always be present for program path.');
38
+ visitor.exit(path);
39
+ visitors.delete(path);
40
+ },
41
+ },
42
+ },
43
+ };
44
+ }
@@ -0,0 +1,20 @@
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
+
9
+ declare module 'istanbul-lib-instrument' {
10
+ export interface Visitor {
11
+ enter(path: import('@babel/core').NodePath<types.Program>): void;
12
+ exit(path: import('@babel/core').NodePath<types.Program>): void;
13
+ }
14
+
15
+ export function programVisitor(
16
+ types: typeof import('@babel/core').types,
17
+ filePath?: string,
18
+ options?: { inputSourceMap?: object | null },
19
+ ): Visitor;
20
+ }
@@ -21,6 +21,8 @@ export interface CompilerPluginOptions {
21
21
  sourceFileCache?: SourceFileCache;
22
22
  loadResultCache?: LoadResultCache;
23
23
  incremental: boolean;
24
+ externalRuntimeStyles?: boolean;
25
+ instrumentForCoverage?: (request: string) => boolean;
24
26
  }
25
27
  export declare function createCompilerPlugin(pluginOptions: CompilerPluginOptions, styleOptions: BundleStylesheetOptions & {
26
28
  inlineStyleLanguage: string;
@@ -35,6 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.createCompilerPlugin = createCompilerPlugin;
37
37
  const node_assert_1 = __importDefault(require("node:assert"));
38
+ const node_crypto_1 = require("node:crypto");
38
39
  const path = __importStar(require("node:path"));
39
40
  const environment_options_1 = require("../../../utils/environment-options");
40
41
  const compilation_1 = require("../../angular/compilation");
@@ -120,13 +121,14 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
120
121
  // Angular compiler which does not have direct knowledge of transitive resource
121
122
  // dependencies or web worker processing.
122
123
  let modifiedFiles;
124
+ let invalidatedStylesheetEntries;
123
125
  if (pluginOptions.sourceFileCache?.modifiedFiles.size &&
124
126
  referencedFileTracker &&
125
127
  !pluginOptions.noopTypeScriptCompilation) {
126
128
  // TODO: Differentiate between changed input files and stale output files
127
129
  modifiedFiles = referencedFileTracker.update(pluginOptions.sourceFileCache.modifiedFiles);
128
130
  pluginOptions.sourceFileCache.invalidate(modifiedFiles);
129
- stylesheetBundler.invalidate(modifiedFiles);
131
+ invalidatedStylesheetEntries = stylesheetBundler.invalidate(modifiedFiles);
130
132
  }
131
133
  if (!pluginOptions.noopTypeScriptCompilation &&
132
134
  compilation.update &&
@@ -138,7 +140,7 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
138
140
  fileReplacements: pluginOptions.fileReplacements,
139
141
  modifiedFiles,
140
142
  sourceFileCache: pluginOptions.sourceFileCache,
141
- async transformStylesheet(data, containingFile, stylesheetFile) {
143
+ async transformStylesheet(data, containingFile, stylesheetFile, order) {
142
144
  let stylesheetResult;
143
145
  // Stylesheet file only exists for external stylesheets
144
146
  if (stylesheetFile) {
@@ -147,7 +149,17 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
147
149
  else {
148
150
  stylesheetResult = await stylesheetBundler.bundleInline(data, containingFile,
149
151
  // Inline stylesheets from a template style element are always CSS
150
- containingFile.endsWith('.html') ? 'css' : styleOptions.inlineStyleLanguage);
152
+ containingFile.endsWith('.html') ? 'css' : styleOptions.inlineStyleLanguage,
153
+ // When external runtime styles are enabled, an identifier for the style that does not change
154
+ // based on the content is required to avoid emitted JS code changes. Any JS code changes will
155
+ // invalid the output and force a full page reload for HMR cases. The containing file and order
156
+ // of the style within the containing file is used.
157
+ pluginOptions.externalRuntimeStyles
158
+ ? (0, node_crypto_1.createHash)('sha-256')
159
+ .update(containingFile)
160
+ .update((order ?? 0).toString())
161
+ .digest('hex')
162
+ : undefined);
151
163
  }
152
164
  const { contents, outputFiles, metafile, referencedFiles, errors, warnings } = stylesheetResult;
153
165
  if (errors) {
@@ -201,6 +213,7 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
201
213
  // Initialize the Angular compilation for the current build.
202
214
  // In watch mode, previous build state will be reused.
203
215
  let referencedFiles;
216
+ let externalStylesheets;
204
217
  try {
205
218
  const initializationResult = await compilation.initialize(pluginOptions.tsconfig, hostOptions, createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserveSymlinks));
206
219
  shouldTsIgnoreJs = !initializationResult.compilerOptions.allowJs;
@@ -211,6 +224,7 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
211
224
  !!initializationResult.compilerOptions.sourceMap ||
212
225
  !!initializationResult.compilerOptions.inlineSourceMap;
213
226
  referencedFiles = initializationResult.referencedFiles;
227
+ externalStylesheets = initializationResult.externalStylesheets;
214
228
  }
215
229
  catch (error) {
216
230
  (result.errors ??= []).push({
@@ -231,6 +245,19 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
231
245
  hasCompilationErrors = await sharedTSCompilationState.waitUntilReady;
232
246
  return result;
233
247
  }
248
+ if (externalStylesheets) {
249
+ // Process any new external stylesheets
250
+ for (const [stylesheetFile, externalId] of externalStylesheets) {
251
+ await bundleExternalStylesheet(stylesheetBundler, stylesheetFile, externalId, result, additionalResults);
252
+ }
253
+ // Process any updated stylesheets
254
+ if (invalidatedStylesheetEntries) {
255
+ for (const stylesheetFile of invalidatedStylesheetEntries) {
256
+ // externalId is already linked in the bundler context so only enabling is required here
257
+ await bundleExternalStylesheet(stylesheetBundler, stylesheetFile, true, result, additionalResults);
258
+ }
259
+ }
260
+ }
234
261
  // Update TypeScript file output cache for all affected files
235
262
  try {
236
263
  await (0, profiling_1.profileAsync)('NG_EMIT_TS', async () => {
@@ -312,7 +339,8 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
312
339
  // A string indicates untransformed output from the TS/NG compiler.
313
340
  // This step is unneeded when using esbuild transpilation.
314
341
  const sideEffects = await hasSideEffects(request);
315
- contents = await javascriptTransformer.transformData(request, contents, true /* skipLinker */, sideEffects);
342
+ const instrumentForCoverage = pluginOptions.instrumentForCoverage?.(request);
343
+ contents = await javascriptTransformer.transformData(request, contents, true /* skipLinker */, sideEffects, instrumentForCoverage);
316
344
  // Store as the returned Uint8Array to allow caching the fully transformed code
317
345
  typeScriptFileCache.set(request, contents);
318
346
  }
@@ -376,6 +404,17 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
376
404
  },
377
405
  };
378
406
  }
407
+ async function bundleExternalStylesheet(stylesheetBundler, stylesheetFile, externalId, result, additionalResults) {
408
+ const { outputFiles, metafile, errors, warnings } = await stylesheetBundler.bundleFile(stylesheetFile, externalId);
409
+ if (errors) {
410
+ (result.errors ??= []).push(...errors);
411
+ }
412
+ (result.warnings ??= []).push(...warnings);
413
+ additionalResults.set(stylesheetFile, {
414
+ outputFiles,
415
+ metafile,
416
+ });
417
+ }
379
418
  function createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserveSymlinks) {
380
419
  return (compilerOptions) => {
381
420
  // target of 9 is ES2022 (using the number avoids an expensive import of typescript just for an enum)
@@ -433,6 +472,7 @@ function createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserve
433
472
  mapRoot: undefined,
434
473
  sourceRoot: undefined,
435
474
  preserveSymlinks,
475
+ externalRuntimeStyles: pluginOptions.externalRuntimeStyles,
436
476
  };
437
477
  };
438
478
  }