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

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 +10 -10
  2. package/src/builders/application/execute-post-bundle.js +29 -18
  3. package/src/builders/application/i18n.js +2 -11
  4. package/src/builders/application/options.d.ts +11 -0
  5. package/src/builders/application/options.js +23 -1
  6. package/src/builders/application/setup-bundling.js +7 -7
  7. package/src/builders/dev-server/internal.d.ts +0 -1
  8. package/src/builders/dev-server/internal.js +1 -3
  9. package/src/builders/dev-server/vite-server.d.ts +7 -2
  10. package/src/builders/dev-server/vite-server.js +27 -12
  11. package/src/tools/angular/compilation/parallel-compilation.d.ts +1 -1
  12. package/src/tools/angular/compilation/parallel-compilation.js +5 -0
  13. package/src/tools/esbuild/angular/file-reference-tracker.d.ts +1 -1
  14. package/src/tools/esbuild/application-code-bundle.d.ts +1 -6
  15. package/src/tools/esbuild/application-code-bundle.js +105 -70
  16. package/src/tools/esbuild/bundler-context.js +14 -10
  17. package/src/tools/esbuild/cache.d.ts +1 -1
  18. package/src/tools/esbuild/javascript-transformer.js +6 -0
  19. package/src/tools/esbuild/utils.d.ts +9 -0
  20. package/src/tools/esbuild/utils.js +14 -0
  21. package/src/tools/sass/sass-service.js +15 -4
  22. package/src/tools/sass/worker.d.ts +13 -32
  23. package/src/tools/sass/worker.js +1 -0
  24. package/src/tools/vite/angular-memory-plugin.js +2 -2
  25. package/src/tools/vite/middlewares/ssr-middleware.d.ts +1 -4
  26. package/src/tools/vite/middlewares/ssr-middleware.js +25 -38
  27. package/src/typings.d.ts +7 -0
  28. package/src/utils/normalize-cache.js +1 -1
  29. package/src/utils/server-rendering/fetch-patch.js +4 -5
  30. package/src/utils/server-rendering/load-esm-from-memory.d.ts +12 -2
  31. package/src/utils/server-rendering/manifest.d.ts +44 -0
  32. package/src/utils/server-rendering/manifest.js +86 -0
  33. package/src/utils/server-rendering/prerender.d.ts +22 -2
  34. package/src/utils/server-rendering/prerender.js +51 -40
  35. package/src/utils/server-rendering/render-worker.d.ts +7 -8
  36. package/src/utils/server-rendering/render-worker.js +10 -13
  37. package/src/utils/server-rendering/routes-extractor-worker.d.ts +2 -6
  38. package/src/utils/server-rendering/routes-extractor-worker.js +3 -34
  39. package/src/utils/server-rendering/main-bundle-exports.d.ts +0 -27
  40. package/src/utils/server-rendering/main-bundle-exports.js +0 -9
  41. package/src/utils/server-rendering/render-page.d.ts +0 -26
  42. package/src/utils/server-rendering/render-page.js +0 -114
@@ -12,12 +12,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.createBrowserCodeBundleOptions = createBrowserCodeBundleOptions;
14
14
  exports.createBrowserPolyfillBundleOptions = createBrowserPolyfillBundleOptions;
15
- exports.createServerCodeBundleOptions = createServerCodeBundleOptions;
16
15
  exports.createServerPolyfillBundleOptions = createServerPolyfillBundleOptions;
16
+ exports.createServerMainCodeBundleOptions = createServerMainCodeBundleOptions;
17
17
  const node_assert_1 = __importDefault(require("node:assert"));
18
18
  const node_crypto_1 = require("node:crypto");
19
19
  const node_path_1 = require("node:path");
20
20
  const environment_options_1 = require("../../utils/environment-options");
21
+ const manifest_1 = require("../../utils/server-rendering/manifest");
21
22
  const compiler_plugin_1 = require("./angular/compiler-plugin");
22
23
  const compiler_plugin_options_1 = require("./compiler-plugin-options");
23
24
  const external_packages_plugin_1 = require("./external-packages-plugin");
@@ -110,29 +111,28 @@ function createBrowserPolyfillBundleOptions(options, target, sourceFileCache) {
110
111
  // cannot be used with fully incremental bundling yet.
111
112
  return hasTypeScriptEntries ? buildOptions : () => buildOptions;
112
113
  }
113
- /**
114
- * Create an esbuild 'build' options object for the server bundle.
115
- * @param options The builder's user-provider normalized options.
116
- * @returns An esbuild BuildOptions object.
117
- */
118
- function createServerCodeBundleOptions(options, target, sourceFileCache) {
119
- const { serverEntryPoint, workspaceRoot, ssrOptions, watch, externalPackages, prerenderOptions, polyfills, } = options;
120
- (0, node_assert_1.default)(serverEntryPoint, 'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.');
121
- const { pluginOptions, styleOptions } = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, target, sourceFileCache);
122
- const mainServerNamespace = 'angular:server-render-utils';
123
- const entryPoints = {
124
- 'render-utils.server': mainServerNamespace,
125
- 'main.server': serverEntryPoint,
126
- };
127
- const ssrEntryPoint = ssrOptions?.entry;
128
- if (ssrEntryPoint) {
129
- entryPoints['server'] = ssrEntryPoint;
114
+ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
115
+ const serverPolyfills = [];
116
+ const polyfillsFromConfig = new Set(options.polyfills);
117
+ if (!(0, utils_1.isZonelessApp)(options.polyfills)) {
118
+ serverPolyfills.push('zone.js/node');
119
+ }
120
+ if (polyfillsFromConfig.has('@angular/localize') ||
121
+ polyfillsFromConfig.has('@angular/localize/init')) {
122
+ serverPolyfills.push('@angular/localize/init');
123
+ }
124
+ serverPolyfills.push('@angular/platform-server/init');
125
+ const namespace = 'angular:polyfills-server';
126
+ const polyfillBundleOptions = getEsBuildCommonPolyfillsOptions({
127
+ ...options,
128
+ polyfills: serverPolyfills,
129
+ }, namespace, false, sourceFileCache);
130
+ if (!polyfillBundleOptions) {
131
+ return;
130
132
  }
131
- const zoneless = (0, utils_1.isZonelessApp)(polyfills);
132
133
  const buildOptions = {
133
- ...getEsBuildCommonOptions(options),
134
+ ...polyfillBundleOptions,
134
135
  platform: 'node',
135
- splitting: true,
136
136
  outExtension: { '.js': '.mjs' },
137
137
  // Note: `es2015` is needed for RxJS v6. If not specified, `module` would
138
138
  // match and the ES5 distribution would be bundled and ends up breaking at
@@ -140,14 +140,45 @@ function createServerCodeBundleOptions(options, target, sourceFileCache) {
140
140
  // More details: https://github.com/angular/angular-cli/issues/25405.
141
141
  mainFields: ['es2020', 'es2015', 'module', 'main'],
142
142
  entryNames: '[name]',
143
- target,
144
143
  banner: {
145
- js: `import './polyfills.server.mjs';`,
144
+ js: [
145
+ // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
146
+ // See: https://github.com/evanw/esbuild/issues/1921.
147
+ `import { createRequire } from 'node:module';`,
148
+ `globalThis['require'] ??= createRequire(import.meta.url);`,
149
+ ].join('\n'),
150
+ },
151
+ target,
152
+ entryPoints: {
153
+ 'polyfills.server': namespace,
146
154
  },
155
+ };
156
+ return () => buildOptions;
157
+ }
158
+ function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
159
+ const { serverEntryPoint: mainServerEntryPoint, workspaceRoot, externalPackages, ssrOptions, polyfills, } = options;
160
+ (0, node_assert_1.default)(mainServerEntryPoint, 'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.');
161
+ const { pluginOptions, styleOptions } = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, target, sourceFileCache);
162
+ const mainServerNamespace = 'angular:main-server';
163
+ const mainServerInjectPolyfillsNamespace = 'angular:main-server-inject-polyfills';
164
+ const mainServerInjectManifestNamespace = 'angular:main-server-inject-manifest';
165
+ const zoneless = (0, utils_1.isZonelessApp)(polyfills);
166
+ const entryPoints = {
167
+ 'main.server': mainServerNamespace,
168
+ };
169
+ const ssrEntryPoint = ssrOptions?.entry;
170
+ if (ssrEntryPoint) {
171
+ // Old behavior: 'server.ts' was bundled together with the SSR (Server-Side Rendering) code.
172
+ // This approach combined server-side logic and rendering into a single bundle.
173
+ entryPoints['server'] = ssrEntryPoint;
174
+ }
175
+ const buildOptions = {
176
+ ...getEsBuildServerCommonOptions(options),
177
+ target,
178
+ inject: [mainServerInjectPolyfillsNamespace, mainServerInjectManifestNamespace],
147
179
  entryPoints,
148
180
  supported: (0, utils_1.getFeatureSupport)(target, zoneless),
149
181
  plugins: [
150
- (0, loader_import_attribute_plugin_1.createLoaderImportAttributePlugin)(),
151
182
  (0, wasm_plugin_1.createWasmPlugin)({ allowAsync: zoneless, cache: sourceFileCache?.loadResultCache }),
152
183
  (0, sourcemap_ignorelist_plugin_1.createSourcemapIgnorelistPlugin)(),
153
184
  (0, compiler_plugin_1.createCompilerPlugin)(
@@ -164,20 +195,49 @@ function createServerCodeBundleOptions(options, target, sourceFileCache) {
164
195
  else {
165
196
  buildOptions.plugins.push((0, rxjs_esm_resolution_plugin_1.createRxjsEsmResolutionPlugin)());
166
197
  }
198
+ // Mark manifest and polyfills file as external as these are generated by a different bundle step.
199
+ (buildOptions.external ??= []).push(...utils_1.SERVER_GENERATED_EXTERNALS);
167
200
  buildOptions.plugins.push((0, virtual_module_plugin_1.createVirtualModulePlugin)({
201
+ namespace: mainServerInjectPolyfillsNamespace,
202
+ cache: sourceFileCache?.loadResultCache,
203
+ loadContent: () => ({
204
+ contents: `import './polyfills.server.mjs';`,
205
+ loader: 'js',
206
+ resolveDir: workspaceRoot,
207
+ }),
208
+ }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
209
+ namespace: mainServerInjectManifestNamespace,
210
+ cache: sourceFileCache?.loadResultCache,
211
+ loadContent: async () => {
212
+ const contents = [
213
+ // Configure `@angular/ssr` manifest.
214
+ `import manifest from './${manifest_1.SERVER_APP_MANIFEST_FILENAME}';`,
215
+ `import { ɵsetAngularAppManifest } from '@angular/ssr';`,
216
+ `ɵsetAngularAppManifest(manifest);`,
217
+ ];
218
+ return {
219
+ contents: contents.join('\n'),
220
+ loader: 'js',
221
+ resolveDir: workspaceRoot,
222
+ };
223
+ },
224
+ }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
168
225
  namespace: mainServerNamespace,
169
226
  cache: sourceFileCache?.loadResultCache,
170
227
  loadContent: async () => {
228
+ const mainServerEntryPointJsImport = entryFileToWorkspaceRelative(workspaceRoot, mainServerEntryPoint);
171
229
  const contents = [
172
- `export { ɵConsole } from '@angular/core';`,
173
- `export { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';`,
230
+ // Re-export all symbols including default export from 'main.server.ts'
231
+ `export { default } from '${mainServerEntryPointJsImport}';`,
232
+ `export * from '${mainServerEntryPointJsImport}';`,
233
+ // Add @angular/ssr exports
234
+ `export {
235
+ ɵServerRenderContext,
236
+ ɵdestroyAngularServerApp,
237
+ ɵextractRoutesAndCreateRouteTree,
238
+ ɵgetOrCreateAngularServerApp,
239
+ } from '@angular/ssr';`,
174
240
  ];
175
- if (watch) {
176
- contents.push(`export { ɵresetCompiledComponents } from '@angular/core';`);
177
- }
178
- if (prerenderOptions?.discoverRoutes) {
179
- contents.push(`export { ɵgetRoutesFromAngularRouterConfig } from '@angular/ssr';`);
180
- }
181
241
  return {
182
242
  contents: contents.join('\n'),
183
243
  loader: 'js',
@@ -190,27 +250,9 @@ function createServerCodeBundleOptions(options, target, sourceFileCache) {
190
250
  }
191
251
  return buildOptions;
192
252
  }
193
- function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
194
- const serverPolyfills = [];
195
- const polyfillsFromConfig = new Set(options.polyfills);
196
- if (!(0, utils_1.isZonelessApp)(options.polyfills)) {
197
- serverPolyfills.push('zone.js/node');
198
- }
199
- if (polyfillsFromConfig.has('@angular/localize') ||
200
- polyfillsFromConfig.has('@angular/localize/init')) {
201
- serverPolyfills.push('@angular/localize/init');
202
- }
203
- serverPolyfills.push('@angular/platform-server/init');
204
- const namespace = 'angular:polyfills-server';
205
- const polyfillBundleOptions = getEsBuildCommonPolyfillsOptions({
206
- ...options,
207
- polyfills: serverPolyfills,
208
- }, namespace, false, sourceFileCache);
209
- if (!polyfillBundleOptions) {
210
- return;
211
- }
212
- const buildOptions = {
213
- ...polyfillBundleOptions,
253
+ function getEsBuildServerCommonOptions(options) {
254
+ return {
255
+ ...getEsBuildCommonOptions(options),
214
256
  platform: 'node',
215
257
  outExtension: { '.js': '.mjs' },
216
258
  // Note: `es2015` is needed for RxJS v6. If not specified, `module` would
@@ -219,31 +261,18 @@ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
219
261
  // More details: https://github.com/angular/angular-cli/issues/25405.
220
262
  mainFields: ['es2020', 'es2015', 'module', 'main'],
221
263
  entryNames: '[name]',
222
- banner: {
223
- js: [
224
- // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
225
- // See: https://github.com/evanw/esbuild/issues/1921.
226
- `import { createRequire } from 'node:module';`,
227
- `globalThis['require'] ??= createRequire(import.meta.url);`,
228
- ].join('\n'),
229
- },
230
- target,
231
- entryPoints: {
232
- 'polyfills.server': namespace,
233
- },
234
264
  };
235
- return () => buildOptions;
236
265
  }
237
266
  function getEsBuildCommonOptions(options) {
238
- const { workspaceRoot, outExtension, optimizationOptions, sourcemapOptions, tsconfig, externalDependencies, outputNames, preserveSymlinks, jit, loaderExtensions, jsonLogs, } = options;
267
+ const { workspaceRoot, outExtension, optimizationOptions, sourcemapOptions, tsconfig, externalDependencies, outputNames, preserveSymlinks, jit, loaderExtensions, jsonLogs, i18nOptions, } = options;
239
268
  // Ensure unique hashes for i18n translation changes when using post-process inlining.
240
269
  // This hash value is added as a footer to each file and ensures that the output file names (with hashes)
241
270
  // change when translation files have changed. If this is not done the post processed files may have
242
271
  // different content but would retain identical production file names which would lead to browser caching problems.
243
272
  let footer;
244
- if (options.i18nOptions.shouldInline) {
273
+ if (i18nOptions.shouldInline) {
245
274
  // Update file hashes to include translation file content
246
- const i18nHash = Object.values(options.i18nOptions.locales).reduce((data, locale) => data + locale.files.map((file) => file.integrity || '').join('|'), '');
275
+ const i18nHash = Object.values(i18nOptions.locales).reduce((data, locale) => data + locale.files.map((file) => file.integrity || '').join('|'), '');
247
276
  footer = { js: `/**i18n:${(0, node_crypto_1.createHash)('sha256').update(i18nHash).digest('hex')}*/` };
248
277
  }
249
278
  return {
@@ -267,7 +296,7 @@ function getEsBuildCommonOptions(options) {
267
296
  splitting: true,
268
297
  chunkNames: options.namedChunks ? '[name]-[hash]' : 'chunk-[hash]',
269
298
  tsconfig,
270
- external: externalDependencies,
299
+ external: externalDependencies ? [...externalDependencies] : undefined,
271
300
  write: false,
272
301
  preserveSymlinks,
273
302
  define: {
@@ -360,3 +389,9 @@ function getEsBuildCommonPolyfillsOptions(options, namespace, tryToResolvePolyfi
360
389
  }));
361
390
  return buildOptions;
362
391
  }
392
+ function entryFileToWorkspaceRelative(workspaceRoot, entryFile) {
393
+ return ('./' +
394
+ (0, node_path_1.relative)(workspaceRoot, entryFile)
395
+ .replace(/.[mc]?ts$/, '')
396
+ .replace(/\\/g, '/'));
397
+ }
@@ -138,6 +138,7 @@ class BundlerContext {
138
138
  }
139
139
  return result;
140
140
  }
141
+ // eslint-disable-next-line max-lines-per-function
141
142
  async #performBundle() {
142
143
  // Create esbuild options if not present
143
144
  if (this.#esbuildOptions === undefined) {
@@ -165,12 +166,6 @@ class BundlerContext {
165
166
  // For non-incremental builds, perform a single build
166
167
  result = await (0, esbuild_1.build)(this.#esbuildOptions);
167
168
  }
168
- if (this.#platformIsServer) {
169
- for (const entry of Object.values(result.metafile.outputs)) {
170
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
- entry['ng-platform-server'] = true;
172
- }
173
- }
174
169
  }
175
170
  catch (failure) {
176
171
  // Build failures will throw an exception which contains errors/warnings
@@ -280,6 +275,7 @@ class BundlerContext {
280
275
  for (const { imports } of Object.values(result.metafile.outputs)) {
281
276
  for (const importData of imports) {
282
277
  if (!importData.external ||
278
+ utils_1.SERVER_GENERATED_EXTERNALS.has(importData.path) ||
283
279
  (importData.kind !== 'import-statement' &&
284
280
  importData.kind !== 'dynamic-import' &&
285
281
  importData.kind !== 'require-call')) {
@@ -295,13 +291,21 @@ class BundlerContext {
295
291
  if (!/\.([cm]?js|css|wasm)(\.map)?$/i.test(file.path)) {
296
292
  fileType = BuildOutputFileType.Media;
297
293
  }
294
+ else if (this.#platformIsServer) {
295
+ fileType = BuildOutputFileType.Server;
296
+ }
298
297
  else {
299
- fileType = this.#platformIsServer
300
- ? BuildOutputFileType.Server
301
- : BuildOutputFileType.Browser;
298
+ fileType = BuildOutputFileType.Browser;
302
299
  }
303
300
  return (0, utils_1.convertOutputFile)(file, fileType);
304
301
  });
302
+ let externalConfiguration = this.#esbuildOptions.external;
303
+ if (this.#platformIsServer && externalConfiguration) {
304
+ externalConfiguration = externalConfiguration.filter((dep) => !utils_1.SERVER_GENERATED_EXTERNALS.has(dep));
305
+ if (!externalConfiguration.length) {
306
+ externalConfiguration = undefined;
307
+ }
308
+ }
305
309
  // Return the successful build results
306
310
  return {
307
311
  ...result,
@@ -310,7 +314,7 @@ class BundlerContext {
310
314
  externalImports: {
311
315
  [this.#platformIsServer ? 'server' : 'browser']: externalImports,
312
316
  },
313
- externalConfiguration: this.#esbuildOptions.external,
317
+ externalConfiguration,
314
318
  errors: undefined,
315
319
  };
316
320
  }
@@ -84,5 +84,5 @@ export declare class MemoryCache<V> extends Cache<V, Map<string, V>> {
84
84
  * Provides all the values currently present in the cache instance.
85
85
  * @returns An iterable of all values in the cache.
86
86
  */
87
- values(): IterableIterator<V>;
87
+ values(): MapIterator<V>;
88
88
  }
@@ -13,6 +13,7 @@ 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");
16
17
  const piscina_1 = __importDefault(require("piscina"));
17
18
  /**
18
19
  * A class that performs transformation of JavaScript files and raw data.
@@ -48,6 +49,11 @@ class JavaScriptTransformer {
48
49
  // Shutdown idle threads after 1 second of inactivity
49
50
  idleTimeout: 1000,
50
51
  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
+ },
51
57
  });
52
58
  return this.#workerPool;
53
59
  }
@@ -47,3 +47,12 @@ export declare function logMessages(logger: BuilderContext['logger'], executionR
47
47
  */
48
48
  export declare function isZonelessApp(polyfills: string[] | undefined): boolean;
49
49
  export declare function getEntryPointName(entryPoint: string): string;
50
+ /**
51
+ * A set of server-generated dependencies that are treated as external.
52
+ *
53
+ * These dependencies are marked as external because they are produced by a
54
+ * separate bundling process and are not included in the primary bundle. This
55
+ * ensures that these generated files are resolved from an external source rather
56
+ * than being part of the main bundle.
57
+ */
58
+ export declare const SERVER_GENERATED_EXTERNALS: Set<string>;
@@ -7,6 +7,7 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.SERVER_GENERATED_EXTERNALS = void 0;
10
11
  exports.logBuildStats = logBuildStats;
11
12
  exports.getChunkNameFromMetafile = getChunkNameFromMetafile;
12
13
  exports.calculateEstimatedTransferSizes = calculateEstimatedTransferSizes;
@@ -29,6 +30,7 @@ const node_path_1 = require("node:path");
29
30
  const node_url_1 = require("node:url");
30
31
  const node_zlib_1 = require("node:zlib");
31
32
  const semver_1 = require("semver");
33
+ const manifest_1 = require("../../utils/server-rendering/manifest");
32
34
  const stats_table_1 = require("../../utils/stats-table");
33
35
  const bundler_context_1 = require("./bundler-context");
34
36
  function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, changedFiles, estimatedTransferSizes, ssrOutputEnabled, verbose) {
@@ -385,3 +387,15 @@ function getEntryPointName(entryPoint) {
385
387
  .replace(/\.[cm]?[jt]s$/, '')
386
388
  .replace(/[\\/.]/g, '-');
387
389
  }
390
+ /**
391
+ * A set of server-generated dependencies that are treated as external.
392
+ *
393
+ * These dependencies are marked as external because they are produced by a
394
+ * separate bundling process and are not included in the primary bundle. This
395
+ * ensures that these generated files are resolved from an external source rather
396
+ * than being part of the main bundle.
397
+ */
398
+ exports.SERVER_GENERATED_EXTERNALS = new Set([
399
+ './polyfills.server.mjs',
400
+ './' + manifest_1.SERVER_APP_MANIFEST_FILENAME,
401
+ ]);
@@ -34,17 +34,22 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
34
34
  env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
35
35
  env.hasError = true;
36
36
  }
37
+ var r, s = 0;
37
38
  function next() {
38
- while (env.stack.length) {
39
- var rec = env.stack.pop();
39
+ while (r = env.stack.pop()) {
40
40
  try {
41
- var result = rec.dispose && rec.dispose.call(rec.value);
42
- if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
41
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
42
+ if (r.dispose) {
43
+ var result = r.dispose.call(r.value);
44
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
45
+ }
46
+ else s |= 1;
43
47
  }
44
48
  catch (e) {
45
49
  fail(e);
46
50
  }
47
51
  }
52
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
48
53
  if (env.hasError) throw env.error;
49
54
  }
50
55
  return next();
@@ -59,6 +64,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
59
64
  Object.defineProperty(exports, "__esModule", { value: true });
60
65
  exports.SassWorkerImplementation = void 0;
61
66
  const node_assert_1 = __importDefault(require("node:assert"));
67
+ const node_module_1 = require("node:module");
62
68
  const node_url_1 = require("node:url");
63
69
  const node_worker_threads_1 = require("node:worker_threads");
64
70
  const piscina_1 = require("piscina");
@@ -95,6 +101,11 @@ class SassWorkerImplementation {
95
101
  // Shutdown idle threads after 1 second of inactivity
96
102
  idleTimeout: 1000,
97
103
  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
+ },
98
109
  });
99
110
  return this.#workerPool;
100
111
  }
@@ -5,6 +5,7 @@
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 { RawSourceMap } from '@ampproject/remapping';
8
9
  import { MessagePort } from 'node:worker_threads';
9
10
  import { SourceSpan, StringOptions } from 'sass';
10
11
  import type { SerializableWarningMessage } from './sass-service';
@@ -38,45 +39,25 @@ interface RenderRequestMessage {
38
39
  */
39
40
  rebase: boolean;
40
41
  }
41
- export default function renderSassStylesheet(request: RenderRequestMessage): Promise<{
42
+ interface RenderResult {
42
43
  warnings: SerializableWarningMessage[] | undefined;
43
44
  result: {
44
- loadedUrls: string[];
45
45
  css: string;
46
- sourceMap?: import("source-map-js").RawSourceMap;
46
+ loadedUrls: string[];
47
+ sourceMap?: RawSourceMap;
47
48
  };
48
- error?: undefined;
49
- } | {
49
+ }
50
+ interface RenderError {
50
51
  warnings: SerializableWarningMessage[] | undefined;
51
52
  error: {
52
- span: Omit<SourceSpan, "url"> & {
53
+ message: string;
54
+ stack?: string;
55
+ span?: Omit<SourceSpan, 'url'> & {
53
56
  url?: string;
54
57
  };
55
- message: string;
56
- stack: string | undefined;
57
- sassMessage: string;
58
- sassStack: string;
59
- };
60
- result?: undefined;
61
- } | {
62
- warnings: SerializableWarningMessage[] | undefined;
63
- error: {
64
- message: string;
65
- stack: string | undefined;
66
- span?: undefined;
67
- sassMessage?: undefined;
68
- sassStack?: undefined;
58
+ sassMessage?: string;
59
+ sassStack?: string;
69
60
  };
70
- result?: undefined;
71
- } | {
72
- warnings: SerializableWarningMessage[] | undefined;
73
- error: {
74
- message: string;
75
- span?: undefined;
76
- stack?: undefined;
77
- sassMessage?: undefined;
78
- sassStack?: undefined;
79
- };
80
- result?: undefined;
81
- }>;
61
+ }
62
+ export default function renderSassStylesheet(request: RenderRequestMessage): Promise<RenderResult | RenderError>;
82
63
  export {};
@@ -95,6 +95,7 @@ async function renderSassStylesheet(request) {
95
95
  warnings,
96
96
  result: {
97
97
  ...result,
98
+ sourceMap: result.sourceMap,
98
99
  // URL is not serializable so to convert to string here and back to URL in the parent.
99
100
  loadedUrls: result.loadedUrls.map((p) => (0, node_url_1.fileURLToPath)(p)),
100
101
  },
@@ -82,10 +82,10 @@ function createAngularMemoryPlugin(options) {
82
82
  // Returning a function, installs middleware after the main transform middleware but
83
83
  // before the built-in HTML middleware
84
84
  return () => {
85
- server.middlewares.use(middlewares_1.angularHtmlFallbackMiddleware);
86
85
  if (ssr) {
87
- server.middlewares.use((0, middlewares_1.createAngularSSRMiddleware)(server, outputFiles, indexHtmlTransformer));
86
+ server.middlewares.use((0, middlewares_1.createAngularSSRMiddleware)(server, indexHtmlTransformer));
88
87
  }
88
+ server.middlewares.use(middlewares_1.angularHtmlFallbackMiddleware);
89
89
  server.middlewares.use((0, middlewares_1.createAngularIndexHtmlMiddleware)(server, outputFiles, indexHtmlTransformer));
90
90
  };
91
91
  },
@@ -6,7 +6,4 @@
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
8
  import type { Connect, ViteDevServer } from 'vite';
9
- export declare function createAngularSSRMiddleware(server: ViteDevServer, outputFiles: Map<string, {
10
- contents: Uint8Array;
11
- servable: boolean;
12
- }>, indexHtmlTransformer?: (content: string) => Promise<string>): Connect.NextHandleFunction;
9
+ export declare function createAngularSSRMiddleware(server: ViteDevServer, indexHtmlTransformer?: (content: string) => Promise<string>): Connect.NextHandleFunction;
@@ -8,49 +8,36 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.createAngularSSRMiddleware = createAngularSSRMiddleware;
11
- const render_page_1 = require("../../../utils/server-rendering/render-page");
12
11
  const utils_1 = require("../utils");
13
- function createAngularSSRMiddleware(server, outputFiles, indexHtmlTransformer) {
12
+ function createAngularSSRMiddleware(server, indexHtmlTransformer) {
13
+ let cachedAngularServerApp;
14
14
  return function (req, res, next) {
15
- const url = req.originalUrl;
16
- if (!req.url ||
17
- // Skip if path is not defined.
18
- !url ||
19
- // Skip if path is like a file.
20
- // NOTE: We use a mime type lookup to mitigate against matching requests like: /browse/pl.0ef59752c0cd457dbf1391f08cbd936f
21
- (0, utils_1.lookupMimeTypeFromRequest)(url)) {
22
- next();
23
- return;
15
+ if (req.url === undefined) {
16
+ return next();
24
17
  }
25
- const rawHtml = outputFiles.get('/index.server.html')?.contents;
26
- if (!rawHtml) {
27
- next();
28
- return;
29
- }
30
- server
31
- .transformIndexHtml(req.url, Buffer.from(rawHtml).toString('utf-8'))
32
- .then(async (processedHtml) => {
33
- const resolvedUrls = server.resolvedUrls;
34
- const baseUrl = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
35
- if (indexHtmlTransformer) {
36
- processedHtml = await indexHtmlTransformer(processedHtml);
18
+ const resolvedUrls = server.resolvedUrls;
19
+ const baseUrl = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
20
+ const url = new URL(req.url, baseUrl);
21
+ (async () => {
22
+ const { ɵgetOrCreateAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs'));
23
+ const angularServerApp = ɵgetOrCreateAngularServerApp();
24
+ // Only Add the transform hook only if it's a different instance.
25
+ if (cachedAngularServerApp !== angularServerApp) {
26
+ angularServerApp.hooks.on('html:transform:pre', async ({ html }) => {
27
+ const processedHtml = await server.transformIndexHtml(url.pathname, html);
28
+ return indexHtmlTransformer?.(processedHtml) ?? processedHtml;
29
+ });
30
+ cachedAngularServerApp = angularServerApp;
31
+ }
32
+ const response = await angularServerApp.render(new Request(url, { signal: AbortSignal.timeout(30_000) }), undefined);
33
+ return response?.text();
34
+ })()
35
+ .then((content) => {
36
+ if (typeof content !== 'string') {
37
+ return next();
37
38
  }
38
- const { content: ssrContent } = await (0, render_page_1.renderPage)({
39
- document: processedHtml,
40
- route: new URL(req.originalUrl ?? '/', baseUrl).toString(),
41
- serverContext: 'ssr',
42
- loadBundle: (uri) =>
43
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
- server.ssrLoadModule(uri.slice(1)),
45
- // Files here are only needed for critical CSS inlining.
46
- outputFiles: {},
47
- // TODO: add support for critical css inlining.
48
- inlineCriticalCss: false,
49
- });
50
- res.setHeader('Content-Type', 'text/html');
51
- res.setHeader('Cache-Control', 'no-cache');
52
39
  (0, utils_1.appendServerConfiguredHeaders)(server, res);
53
- res.end(ssrContent);
40
+ res.end(content);
54
41
  })
55
42
  .catch((error) => next(error));
56
43
  };
package/src/typings.d.ts CHANGED
@@ -17,3 +17,10 @@
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.2';
13
+ const VERSION = '19.0.0-next.4';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&