@angular/build 19.0.0-next.4 → 19.0.0-next.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/build",
3
- "version": "19.0.0-next.4",
3
+ "version": "19.0.0-next.6",
4
4
  "description": "Official build system for Angular",
5
5
  "keywords": [
6
6
  "Angular CLI",
@@ -23,7 +23,7 @@
23
23
  "builders": "builders.json",
24
24
  "dependencies": {
25
25
  "@ampproject/remapping": "2.3.0",
26
- "@angular-devkit/architect": "0.1900.0-next.4",
26
+ "@angular-devkit/architect": "0.1900.0-next.6",
27
27
  "@babel/core": "7.25.2",
28
28
  "@babel/helper-annotate-as-pure": "7.24.7",
29
29
  "@babel/helper-split-export-declaration": "7.24.7",
@@ -42,18 +42,19 @@
42
42
  "parse5-html-rewriting-stream": "7.0.0",
43
43
  "picomatch": "4.0.2",
44
44
  "piscina": "4.6.1",
45
- "rollup": "4.21.2",
45
+ "rollup": "4.21.3",
46
46
  "sass": "1.78.0",
47
47
  "semver": "7.6.3",
48
- "vite": "5.4.3",
48
+ "vite": "5.4.4",
49
49
  "watchpack": "2.4.2"
50
50
  },
51
51
  "peerDependencies": {
52
+ "@angular/compiler": "^19.0.0-next.0",
52
53
  "@angular/compiler-cli": "^19.0.0-next.0",
53
54
  "@angular/localize": "^19.0.0-next.0",
54
55
  "@angular/platform-server": "^19.0.0-next.0",
55
56
  "@angular/service-worker": "^19.0.0-next.0",
56
- "@angular/ssr": "^19.0.0-next.4",
57
+ "@angular/ssr": "^19.0.0-next.6",
57
58
  "less": "^4.2.0",
58
59
  "postcss": "^8.4.0",
59
60
  "tailwindcss": "^2.0.0 || ^3.0.0",
@@ -31,6 +31,6 @@ export declare function serveWithVite(serverOptions: NormalizedDevServerOptions,
31
31
  middleware?: Connect.NextHandleFunction[];
32
32
  buildPlugins?: Plugin[];
33
33
  }): AsyncIterableIterator<DevServerBuilderOutput>;
34
- export declare function setupServer(serverOptions: NormalizedDevServerOptions, outputFiles: Map<string, OutputFileRecord>, assets: Map<string, string>, preserveSymlinks: boolean | undefined, externalMetadata: DevServerExternalResultMetadata, ssr: boolean, prebundleTransformer: JavaScriptTransformer, target: string[], zoneless: boolean, prebundleLoaderExtensions: EsbuildLoaderOption | undefined, extensionMiddleware?: Connect.NextHandleFunction[], indexHtmlTransformer?: (content: string) => Promise<string>, thirdPartySourcemaps?: boolean): Promise<InlineConfig>;
34
+ export declare function setupServer(serverOptions: NormalizedDevServerOptions, outputFiles: Map<string, OutputFileRecord>, assets: Map<string, string>, preserveSymlinks: boolean | undefined, externalMetadata: DevServerExternalResultMetadata, ssr: boolean, prebundleTransformer: JavaScriptTransformer, target: string[], zoneless: boolean, usedComponentStyles: Map<string, string[]>, prebundleLoaderExtensions: EsbuildLoaderOption | undefined, extensionMiddleware?: Connect.NextHandleFunction[], indexHtmlTransformer?: (content: string) => Promise<string>, thirdPartySourcemaps?: boolean): Promise<InlineConfig>;
35
35
  type EsbuildLoaderOption = Exclude<DepOptimizationConfig['esbuildOptions'], undefined>['loader'];
36
36
  export {};
@@ -110,6 +110,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
110
110
  explicitBrowser: [],
111
111
  explicitServer: [],
112
112
  };
113
+ const usedComponentStyles = new Map();
113
114
  // Add cleanup logic via a builder teardown.
114
115
  let deferred;
115
116
  context.addTeardown(async () => {
@@ -214,7 +215,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
214
215
  await server.restart();
215
216
  }
216
217
  else {
217
- await handleUpdate(normalizePath, generatedFiles, server, serverOptions, context.logger);
218
+ await handleUpdate(normalizePath, generatedFiles, server, serverOptions, context.logger, usedComponentStyles);
218
219
  }
219
220
  }
220
221
  else {
@@ -238,7 +239,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
238
239
  ? browserOptions.polyfills
239
240
  : [browserOptions.polyfills];
240
241
  // Setup server and start listening
241
- const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, !!browserOptions.ssr, prebundleTransformer, target, (0, internal_1.isZonelessApp)(polyfills), browserOptions.loader, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps);
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);
242
243
  server = await createServer(serverConfiguration);
243
244
  await server.listen();
244
245
  if (browserOptions.ssr && serverOptions.prebundle !== false) {
@@ -277,7 +278,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
277
278
  }
278
279
  await new Promise((resolve) => (deferred = resolve));
279
280
  }
280
- async function handleUpdate(normalizePath, generatedFiles, server, serverOptions, logger) {
281
+ async function handleUpdate(normalizePath, generatedFiles, server, serverOptions, logger, usedComponentStyles) {
281
282
  const updatedFiles = [];
282
283
  let isServerFileUpdated = false;
283
284
  // Invalidate any updated files
@@ -302,7 +303,21 @@ async function handleUpdate(normalizePath, generatedFiles, server, serverOptions
302
303
  const timestamp = Date.now();
303
304
  server.hot.send({
304
305
  type: 'update',
305
- updates: updatedFiles.map((filePath) => {
306
+ updates: updatedFiles.flatMap((filePath) => {
307
+ // For component styles, an HMR update must be sent for each one with the corresponding
308
+ // component identifier search parameter (`ngcomp`). The Vite client code will not keep
309
+ // the existing search parameters when it performs an update and each one must be
310
+ // specified explicitly. Typically, there is only one each though as specific style files
311
+ // are not typically reused across components.
312
+ const componentIds = usedComponentStyles.get(filePath);
313
+ if (componentIds) {
314
+ return componentIds.map((id) => ({
315
+ type: 'css-update',
316
+ timestamp,
317
+ path: `${filePath}?ngcomp` + (id ? `=${id}` : ''),
318
+ acceptedPath: filePath,
319
+ }));
320
+ }
306
321
  return {
307
322
  type: 'css-update',
308
323
  timestamp,
@@ -377,7 +392,7 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
377
392
  }
378
393
  }
379
394
  }
380
- async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssr, prebundleTransformer, target, zoneless, prebundleLoaderExtensions, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
395
+ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssr, prebundleTransformer, target, zoneless, usedComponentStyles, prebundleLoaderExtensions, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
381
396
  const proxy = await (0, utils_1.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig);
382
397
  // dynamically import Vite for ESM compatibility
383
398
  const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
@@ -471,6 +486,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
471
486
  indexHtmlTransformer,
472
487
  extensionMiddleware,
473
488
  normalizePath,
489
+ usedComponentStyles,
474
490
  }),
475
491
  (0, id_prefix_plugin_1.createRemoveIdPrefixPlugin)(externalMetadata.explicitBrowser),
476
492
  ],
@@ -40,11 +40,6 @@ class ParallelCompilation extends angular_compilation_1.AngularCompilation {
40
40
  useAtomics: !process.versions.webcontainer,
41
41
  filename: localRequire.resolve('./parallel-worker'),
42
42
  recordTiming: false,
43
- env: {
44
- ...process.env,
45
- // Enable compile code caching if enabled for the main process (only exists on Node.js v22.8+)
46
- 'NODE_COMPILE_CACHE': (0, node_module_1.getCompileCacheDir)?.(),
47
- },
48
43
  });
49
44
  }
50
45
  initialize(tsconfig, hostOptions, compilerOptionsTransformer) {
@@ -232,7 +232,6 @@ function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
232
232
  `export * from '${mainServerEntryPointJsImport}';`,
233
233
  // Add @angular/ssr exports
234
234
  `export {
235
- ɵServerRenderContext,
236
235
  ɵdestroyAngularServerApp,
237
236
  ɵextractRoutesAndCreateRouteTree,
238
237
  ɵgetOrCreateAngularServerApp,
@@ -13,7 +13,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.JavaScriptTransformer = void 0;
14
14
  const node_crypto_1 = require("node:crypto");
15
15
  const promises_1 = require("node:fs/promises");
16
- const node_module_1 = require("node:module");
17
16
  const piscina_1 = __importDefault(require("piscina"));
18
17
  /**
19
18
  * A class that performs transformation of JavaScript files and raw data.
@@ -49,11 +48,6 @@ class JavaScriptTransformer {
49
48
  // Shutdown idle threads after 1 second of inactivity
50
49
  idleTimeout: 1000,
51
50
  recordTiming: false,
52
- env: {
53
- ...process.env,
54
- // Enable compile code caching if enabled for the main process (only exists on Node.js v22.8+)
55
- 'NODE_COMPILE_CACHE': (0, node_module_1.getCompileCacheDir)?.(),
56
- },
57
51
  });
58
52
  return this.#workerPool;
59
53
  }
@@ -64,7 +64,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
64
64
  Object.defineProperty(exports, "__esModule", { value: true });
65
65
  exports.SassWorkerImplementation = void 0;
66
66
  const node_assert_1 = __importDefault(require("node:assert"));
67
- const node_module_1 = require("node:module");
68
67
  const node_url_1 = require("node:url");
69
68
  const node_worker_threads_1 = require("node:worker_threads");
70
69
  const piscina_1 = require("piscina");
@@ -101,11 +100,6 @@ class SassWorkerImplementation {
101
100
  // Shutdown idle threads after 1 second of inactivity
102
101
  idleTimeout: 1000,
103
102
  recordTiming: false,
104
- env: {
105
- ...process.env,
106
- // Enable compile code caching if enabled for the main process (only exists on Node.js v22.8+)
107
- 'NODE_COMPILE_CACHE': (0, node_module_1.getCompileCacheDir)?.(),
108
- },
109
103
  });
110
104
  return this.#workerPool;
111
105
  }
@@ -17,5 +17,6 @@ export interface AngularMemoryPluginOptions {
17
17
  extensionMiddleware?: Connect.NextHandleFunction[];
18
18
  indexHtmlTransformer?: (content: string) => Promise<string>;
19
19
  normalizePath: (path: string) => string;
20
+ usedComponentStyles: Map<string, string[]>;
20
21
  }
21
22
  export declare function createAngularMemoryPlugin(options: AngularMemoryPluginOptions): Plugin;
@@ -17,7 +17,7 @@ const promises_1 = require("node:fs/promises");
17
17
  const node_path_1 = require("node:path");
18
18
  const middlewares_1 = require("./middlewares");
19
19
  function createAngularMemoryPlugin(options) {
20
- const { workspaceRoot, virtualProjectRoot, outputFiles, assets, external, ssr, extensionMiddleware, indexHtmlTransformer, normalizePath, } = options;
20
+ const { workspaceRoot, virtualProjectRoot, outputFiles, assets, external, ssr, extensionMiddleware, indexHtmlTransformer, normalizePath, usedComponentStyles, } = options;
21
21
  return {
22
22
  name: 'vite:angular-memory',
23
23
  // Ensures plugin hooks run before built-in Vite hooks
@@ -75,7 +75,7 @@ function createAngularMemoryPlugin(options) {
75
75
  };
76
76
  };
77
77
  // Assets and resources get handled first
78
- server.middlewares.use((0, middlewares_1.createAngularAssetsMiddleware)(server, assets, outputFiles));
78
+ server.middlewares.use((0, middlewares_1.createAngularAssetsMiddleware)(server, assets, outputFiles, usedComponentStyles));
79
79
  if (extensionMiddleware?.length) {
80
80
  extensionMiddleware.forEach((middleware) => server.middlewares.use(middleware));
81
81
  }
@@ -7,4 +7,4 @@
7
7
  */
8
8
  import type { Connect, ViteDevServer } from 'vite';
9
9
  import { AngularMemoryOutputFiles } from '../utils';
10
- export declare function createAngularAssetsMiddleware(server: ViteDevServer, assets: Map<string, string>, outputFiles: AngularMemoryOutputFiles): Connect.NextHandleFunction;
10
+ export declare function createAngularAssetsMiddleware(server: ViteDevServer, assets: Map<string, string>, outputFiles: AngularMemoryOutputFiles, usedComponentStyles: Map<string, string[]>): Connect.NextHandleFunction;
@@ -10,8 +10,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.createAngularAssetsMiddleware = createAngularAssetsMiddleware;
11
11
  const mrmime_1 = require("mrmime");
12
12
  const node_path_1 = require("node:path");
13
+ const load_esm_1 = require("../../../utils/load-esm");
13
14
  const utils_1 = require("../utils");
14
- function createAngularAssetsMiddleware(server, assets, outputFiles) {
15
+ const COMPONENT_REGEX = /%COMP%/g;
16
+ function createAngularAssetsMiddleware(server, assets, outputFiles, usedComponentStyles) {
15
17
  return function (req, res, next) {
16
18
  if (req.url === undefined || res.writableEnded) {
17
19
  return;
@@ -53,13 +55,50 @@ function createAngularAssetsMiddleware(server, assets, outputFiles) {
53
55
  if (extension !== '.js' && extension !== '.html') {
54
56
  const outputFile = outputFiles.get(pathname);
55
57
  if (outputFile?.servable) {
58
+ const data = outputFile.contents;
59
+ if (extension === '.css') {
60
+ // Inject component ID for view encapsulation if requested
61
+ const componentId = new URL(req.url, 'http://localhost').searchParams.get('ngcomp');
62
+ if (componentId !== null) {
63
+ // Record the component style usage for HMR updates
64
+ const usedIds = usedComponentStyles.get(pathname);
65
+ if (usedIds === undefined) {
66
+ usedComponentStyles.set(pathname, [componentId]);
67
+ }
68
+ else {
69
+ usedIds.push(componentId);
70
+ }
71
+ // Shim the stylesheet if a component ID is provided
72
+ if (componentId.length > 0) {
73
+ // Validate component ID
74
+ if (/[_.-A-Za-z0-9]+-c\d{9}$/.test(componentId)) {
75
+ (0, load_esm_1.loadEsmModule)('@angular/compiler')
76
+ .then((compilerModule) => {
77
+ const encapsulatedData = compilerModule
78
+ .encapsulateStyle(new TextDecoder().decode(data))
79
+ .replaceAll(COMPONENT_REGEX, componentId);
80
+ res.setHeader('Content-Type', 'text/css');
81
+ res.setHeader('Cache-Control', 'no-cache');
82
+ (0, utils_1.appendServerConfiguredHeaders)(server, res);
83
+ res.end(encapsulatedData);
84
+ })
85
+ .catch((e) => next(e));
86
+ return;
87
+ }
88
+ else {
89
+ // eslint-disable-next-line no-console
90
+ console.error('Invalid component stylesheet ID request: ' + componentId);
91
+ }
92
+ }
93
+ }
94
+ }
56
95
  const mimeType = (0, mrmime_1.lookup)(extension);
57
96
  if (mimeType) {
58
97
  res.setHeader('Content-Type', mimeType);
59
98
  }
60
99
  res.setHeader('Cache-Control', 'no-cache');
61
100
  (0, utils_1.appendServerConfiguredHeaders)(server, res);
62
- res.end(outputFile.contents);
101
+ res.end(data);
63
102
  return;
64
103
  }
65
104
  }
package/src/typings.d.ts CHANGED
@@ -17,10 +17,3 @@
17
17
  declare module 'esbuild' {
18
18
  export * from 'esbuild-wasm';
19
19
  }
20
-
21
- /**
22
- * Augment the Node.js module builtin types to support the v22.8+ compile cache functions
23
- */
24
- declare module 'node:module' {
25
- function getCompileCacheDir(): string | undefined;
26
- }
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.normalizeCacheOptions = normalizeCacheOptions;
11
11
  const node_path_1 = require("node:path");
12
12
  /** Version placeholder is replaced during the build process with actual package version */
13
- const VERSION = '19.0.0-next.4';
13
+ const VERSION = '19.0.0-next.6';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&
@@ -6,13 +6,12 @@
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
8
  import type { ApplicationRef, Type } from '@angular/core';
9
- import type { ɵServerRenderContext, ɵextractRoutesAndCreateRouteTree, ɵgetOrCreateAngularServerApp } from '@angular/ssr';
9
+ import type { ɵextractRoutesAndCreateRouteTree, ɵgetOrCreateAngularServerApp } from '@angular/ssr';
10
10
  /**
11
11
  * Represents the exports available from the main server bundle.
12
12
  */
13
13
  interface MainServerBundleExports {
14
14
  default: (() => Promise<ApplicationRef>) | Type<unknown>;
15
- ɵServerRenderContext: typeof ɵServerRenderContext;
16
15
  ɵextractRoutesAndCreateRouteTree: typeof ɵextractRoutesAndCreateRouteTree;
17
16
  ɵgetOrCreateAngularServerApp: typeof ɵgetOrCreateAngularServerApp;
18
17
  }
@@ -112,12 +112,12 @@ async function renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspace
112
112
  for (const route of allRoutes) {
113
113
  // Remove base href from file output path.
114
114
  const routeWithoutBaseHref = addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length - 1));
115
- const isAppShellRoute = appShellRoute === routeWithoutBaseHref;
116
- const render = renderWorker.run({ url: route, isAppShellRoute });
115
+ const render = renderWorker.run({ url: route });
117
116
  const renderResult = render
118
117
  .then((content) => {
119
118
  if (content !== null) {
120
119
  const outPath = node_path_1.posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');
120
+ const isAppShellRoute = appShellRoute === routeWithoutBaseHref;
121
121
  output[outPath] = { content, appShellRoute: isAppShellRoute };
122
122
  }
123
123
  })
@@ -11,11 +11,10 @@ export interface RenderWorkerData extends ESMInMemoryFileLoaderWorkerData {
11
11
  }
12
12
  export interface RenderOptions {
13
13
  url: string;
14
- isAppShellRoute: boolean;
15
14
  }
16
15
  /**
17
16
  * Renders each route in routes and writes them to <outputPath>/<route>/index.html.
18
17
  */
19
- declare function renderPage({ url, isAppShellRoute }: RenderOptions): Promise<string | null>;
18
+ declare function renderPage({ url }: RenderOptions): Promise<string | null>;
20
19
  declare const _default: typeof renderPage;
21
20
  export default _default;
@@ -12,12 +12,10 @@ const load_esm_from_memory_1 = require("./load-esm-from-memory");
12
12
  /**
13
13
  * Renders each route in routes and writes them to <outputPath>/<route>/index.html.
14
14
  */
15
- async function renderPage({ url, isAppShellRoute }) {
16
- const { ɵgetOrCreateAngularServerApp: getOrCreateAngularServerApp, ɵServerRenderContext: ServerRenderContext, } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
15
+ async function renderPage({ url }) {
16
+ const { ɵgetOrCreateAngularServerApp: getOrCreateAngularServerApp } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
17
17
  const angularServerApp = getOrCreateAngularServerApp();
18
- const response = await angularServerApp.render(new Request(new URL(url, 'http://local-angular-prerender'), {
19
- signal: AbortSignal.timeout(30_000),
20
- }), undefined, isAppShellRoute ? ServerRenderContext.AppShell : ServerRenderContext.SSG);
18
+ const response = await angularServerApp.renderStatic(new URL(url, 'http://local-angular-prerender'), AbortSignal.timeout(30_000));
21
19
  return response ? response.text() : null;
22
20
  }
23
21
  function initialize() {
@@ -12,7 +12,9 @@ const load_esm_from_memory_1 = require("./load-esm-from-memory");
12
12
  /** Renders an application based on a provided options. */
13
13
  async function extractRoutes() {
14
14
  const { ɵextractRoutesAndCreateRouteTree: extractRoutesAndCreateRouteTree } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
15
- const routeTree = await extractRoutesAndCreateRouteTree(new URL('http://local-angular-prerender/'));
15
+ const routeTree = await extractRoutesAndCreateRouteTree(new URL('http://local-angular-prerender/'),
16
+ /** manifest */ undefined,
17
+ /** invokeGetPrerenderParams */ true);
16
18
  return routeTree.toObject();
17
19
  }
18
20
  function initialize() {