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

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 (71) hide show
  1. package/package.json +9 -9
  2. package/src/builders/application/execute-build.js +19 -2
  3. package/src/builders/application/execute-post-bundle.d.ts +2 -2
  4. package/src/builders/application/execute-post-bundle.js +30 -11
  5. package/src/builders/application/i18n.d.ts +2 -2
  6. package/src/builders/application/i18n.js +4 -5
  7. package/src/builders/application/index.js +8 -5
  8. package/src/builders/application/options.d.ts +18 -1
  9. package/src/builders/application/options.js +30 -2
  10. package/src/builders/application/schema.d.ts +15 -0
  11. package/src/builders/application/schema.js +11 -1
  12. package/src/builders/application/schema.json +5 -0
  13. package/src/builders/application/setup-bundling.js +6 -3
  14. package/src/builders/dev-server/vite-server.d.ts +2 -1
  15. package/src/builders/dev-server/vite-server.js +64 -47
  16. package/src/builders/extract-i18n/application-extraction.js +3 -3
  17. package/src/tools/angular/angular-host.d.ts +2 -1
  18. package/src/tools/angular/angular-host.js +20 -1
  19. package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
  20. package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
  21. package/src/tools/angular/compilation/aot-compilation.js +9 -1
  22. package/src/tools/angular/compilation/parallel-compilation.d.ts +1 -0
  23. package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
  24. package/src/tools/angular/compilation/parallel-worker.js +2 -1
  25. package/src/tools/esbuild/angular/compiler-plugin.d.ts +1 -0
  26. package/src/tools/esbuild/angular/compiler-plugin.js +42 -3
  27. package/src/tools/esbuild/angular/component-stylesheets.d.ts +8 -3
  28. package/src/tools/esbuild/angular/component-stylesheets.js +46 -11
  29. package/src/tools/esbuild/application-code-bundle.d.ts +1 -0
  30. package/src/tools/esbuild/application-code-bundle.js +109 -2
  31. package/src/tools/esbuild/budget-stats.js +1 -1
  32. package/src/tools/esbuild/bundler-context.d.ts +4 -3
  33. package/src/tools/esbuild/bundler-context.js +8 -4
  34. package/src/tools/esbuild/bundler-execution-result.d.ts +5 -2
  35. package/src/tools/esbuild/bundler-execution-result.js +7 -3
  36. package/src/tools/esbuild/cache.d.ts +5 -0
  37. package/src/tools/esbuild/cache.js +7 -0
  38. package/src/tools/esbuild/compiler-plugin-options.js +2 -1
  39. package/src/tools/esbuild/i18n-inliner.js +2 -1
  40. package/src/tools/esbuild/utils.js +7 -3
  41. package/src/tools/vite/middlewares/assets-middleware.js +2 -5
  42. package/src/tools/vite/middlewares/html-fallback-middleware.js +22 -6
  43. package/src/tools/vite/middlewares/index.d.ts +1 -1
  44. package/src/tools/vite/middlewares/index.js +3 -2
  45. package/src/tools/vite/middlewares/ssr-middleware.d.ts +2 -1
  46. package/src/tools/vite/middlewares/ssr-middleware.js +61 -15
  47. package/src/tools/vite/plugins/angular-memory-plugin.d.ts +16 -0
  48. package/src/tools/vite/{angular-memory-plugin.js → plugins/angular-memory-plugin.js} +19 -41
  49. package/src/tools/vite/{i18n-locale-plugin.d.ts → plugins/i18n-locale-plugin.d.ts} +0 -4
  50. package/src/tools/vite/{i18n-locale-plugin.js → plugins/i18n-locale-plugin.js} +2 -3
  51. package/src/tools/vite/plugins/index.d.ts +12 -0
  52. package/src/tools/vite/plugins/index.js +21 -0
  53. package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +41 -0
  54. package/src/tools/vite/plugins/setup-middlewares-plugin.js +62 -0
  55. package/src/tools/vite/plugins/ssr-transform-plugin.d.ts +9 -0
  56. package/src/tools/vite/plugins/ssr-transform-plugin.js +38 -0
  57. package/src/utils/environment-options.d.ts +2 -0
  58. package/src/utils/environment-options.js +5 -1
  59. package/src/utils/index-file/valid-self-closing-tags.js +1 -0
  60. package/src/utils/normalize-cache.js +1 -1
  61. package/src/utils/server-rendering/manifest.d.ts +8 -2
  62. package/src/utils/server-rendering/manifest.js +61 -12
  63. package/src/utils/server-rendering/models.d.ts +27 -0
  64. package/src/utils/server-rendering/models.js +22 -0
  65. package/src/utils/server-rendering/prerender.d.ts +6 -10
  66. package/src/utils/server-rendering/prerender.js +100 -63
  67. package/src/utils/server-rendering/routes-extractor-worker.d.ts +5 -10
  68. package/src/utils/server-rendering/routes-extractor-worker.js +3 -4
  69. package/src/tools/vite/angular-memory-plugin.d.ts +0 -22
  70. /package/src/tools/vite/{id-prefix-plugin.d.ts → plugins/id-prefix-plugin.d.ts} +0 -0
  71. /package/src/tools/vite/{id-prefix-plugin.js → plugins/id-prefix-plugin.js} +0 -0
@@ -14,6 +14,7 @@ exports.createBrowserCodeBundleOptions = createBrowserCodeBundleOptions;
14
14
  exports.createBrowserPolyfillBundleOptions = createBrowserPolyfillBundleOptions;
15
15
  exports.createServerPolyfillBundleOptions = createServerPolyfillBundleOptions;
16
16
  exports.createServerMainCodeBundleOptions = createServerMainCodeBundleOptions;
17
+ exports.createSsrEntryCodeBundleOptions = createSsrEntryCodeBundleOptions;
17
18
  const node_assert_1 = __importDefault(require("node:assert"));
18
19
  const node_crypto_1 = require("node:crypto");
19
20
  const node_path_1 = require("node:path");
@@ -156,7 +157,7 @@ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
156
157
  return () => buildOptions;
157
158
  }
158
159
  function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
159
- const { serverEntryPoint: mainServerEntryPoint, workspaceRoot, externalPackages, ssrOptions, polyfills, } = options;
160
+ const { serverEntryPoint: mainServerEntryPoint, workspaceRoot, outputMode, externalPackages, ssrOptions, polyfills, } = options;
160
161
  (0, node_assert_1.default)(mainServerEntryPoint, 'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.');
161
162
  const { pluginOptions, styleOptions } = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, target, sourceFileCache);
162
163
  const mainServerNamespace = 'angular:main-server';
@@ -167,7 +168,8 @@ function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
167
168
  'main.server': mainServerNamespace,
168
169
  };
169
170
  const ssrEntryPoint = ssrOptions?.entry;
170
- if (ssrEntryPoint) {
171
+ const isOldBehaviour = !outputMode;
172
+ if (ssrEntryPoint && isOldBehaviour) {
171
173
  // Old behavior: 'server.ts' was bundled together with the SSR (Server-Side Rendering) code.
172
174
  // This approach combined server-side logic and rendering into a single bundle.
173
175
  entryPoints['server'] = ssrEntryPoint;
@@ -249,6 +251,111 @@ function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
249
251
  }
250
252
  return buildOptions;
251
253
  }
254
+ function createSsrEntryCodeBundleOptions(options, target, sourceFileCache) {
255
+ const { workspaceRoot, ssrOptions, externalPackages } = options;
256
+ const serverEntryPoint = ssrOptions?.entry;
257
+ (0, node_assert_1.default)(serverEntryPoint, 'createSsrEntryCodeBundleOptions should not be called without a defined serverEntryPoint.');
258
+ const { pluginOptions, styleOptions } = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, target, sourceFileCache);
259
+ const ssrEntryNamespace = 'angular:ssr-entry';
260
+ const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest';
261
+ const ssrInjectRequireNamespace = 'angular:ssr-entry-inject-require';
262
+ const buildOptions = {
263
+ ...getEsBuildServerCommonOptions(options),
264
+ target,
265
+ entryPoints: {
266
+ // TODO: consider renaming to index
267
+ 'server': ssrEntryNamespace,
268
+ },
269
+ supported: (0, utils_1.getFeatureSupport)(target, true),
270
+ plugins: [
271
+ (0, sourcemap_ignorelist_plugin_1.createSourcemapIgnorelistPlugin)(),
272
+ (0, compiler_plugin_1.createCompilerPlugin)(
273
+ // JS/TS options
274
+ { ...pluginOptions, noopTypeScriptCompilation: true },
275
+ // Component stylesheet options
276
+ styleOptions),
277
+ ],
278
+ inject: [ssrInjectRequireNamespace, ssrInjectManifestNamespace],
279
+ };
280
+ buildOptions.plugins ??= [];
281
+ if (externalPackages) {
282
+ buildOptions.packages = 'external';
283
+ }
284
+ else {
285
+ buildOptions.plugins.push((0, rxjs_esm_resolution_plugin_1.createRxjsEsmResolutionPlugin)());
286
+ }
287
+ // Mark manifest file as external. As this will be generated later on.
288
+ (buildOptions.external ??= []).push('*/main.server.mjs', ...utils_1.SERVER_GENERATED_EXTERNALS);
289
+ buildOptions.plugins.push({
290
+ name: 'angular-ssr-metadata',
291
+ setup(build) {
292
+ build.onEnd((result) => {
293
+ if (result.metafile) {
294
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
295
+ result.metafile['ng-ssr-entry-bundle'] = true;
296
+ }
297
+ });
298
+ },
299
+ }, (0, virtual_module_plugin_1.createVirtualModulePlugin)({
300
+ namespace: ssrInjectRequireNamespace,
301
+ cache: sourceFileCache?.loadResultCache,
302
+ loadContent: () => {
303
+ const contents = [
304
+ // Note: Needed as esbuild does not provide require shims / proxy from ESModules.
305
+ // See: https://github.com/evanw/esbuild/issues/1921.
306
+ `import { createRequire } from 'node:module';`,
307
+ `globalThis['require'] ??= createRequire(import.meta.url);`,
308
+ ];
309
+ return {
310
+ contents: contents.join('\n'),
311
+ loader: 'js',
312
+ resolveDir: workspaceRoot,
313
+ };
314
+ },
315
+ }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
316
+ namespace: ssrInjectManifestNamespace,
317
+ cache: sourceFileCache?.loadResultCache,
318
+ loadContent: () => {
319
+ const contents = [
320
+ // Configure `@angular/ssr` app engine manifest.
321
+ `import manifest from './${manifest_1.SERVER_APP_ENGINE_MANIFEST_FILENAME}';`,
322
+ `import { ɵsetAngularAppEngineManifest } from '@angular/ssr';`,
323
+ `ɵsetAngularAppEngineManifest(manifest);`,
324
+ ];
325
+ return {
326
+ contents: contents.join('\n'),
327
+ loader: 'js',
328
+ resolveDir: workspaceRoot,
329
+ };
330
+ },
331
+ }), (0, virtual_module_plugin_1.createVirtualModulePlugin)({
332
+ namespace: ssrEntryNamespace,
333
+ cache: sourceFileCache?.loadResultCache,
334
+ loadContent: () => {
335
+ const serverEntryPointJsImport = entryFileToWorkspaceRelative(workspaceRoot, serverEntryPoint);
336
+ const contents = [
337
+ // Re-export all symbols including default export
338
+ `import * as server from '${serverEntryPointJsImport}';`,
339
+ `export * from '${serverEntryPointJsImport}';`,
340
+ // The below is needed to avoid
341
+ // `Import "default" will always be undefined because there is no matching export` warning when no default is present.
342
+ `const defaultExportName = 'default';`,
343
+ `export default server[defaultExportName]`,
344
+ // Add @angular/ssr exports
345
+ `export { AngularAppEngine } from '@angular/ssr';`,
346
+ ];
347
+ return {
348
+ contents: contents.join('\n'),
349
+ loader: 'js',
350
+ resolveDir: workspaceRoot,
351
+ };
352
+ },
353
+ }));
354
+ if (options.plugins) {
355
+ buildOptions.plugins.push(...options.plugins);
356
+ }
357
+ return buildOptions;
358
+ }
252
359
  function getEsBuildServerCommonOptions(options) {
253
360
  return {
254
361
  ...getEsBuildCommonOptions(options),
@@ -28,7 +28,7 @@ function generateBudgetStats(metafile, outputFiles, initialFiles) {
28
28
  continue;
29
29
  }
30
30
  // Exclude server bundles
31
- if (type === bundler_context_1.BuildOutputFileType.Server) {
31
+ if (type === bundler_context_1.BuildOutputFileType.ServerApplication || type === bundler_context_1.BuildOutputFileType.ServerRoot) {
32
32
  continue;
33
33
  }
34
34
  const initialRecord = initialFiles.get(file);
@@ -31,9 +31,10 @@ export interface InitialFileRecord {
31
31
  depth: number;
32
32
  }
33
33
  export declare enum BuildOutputFileType {
34
- Browser = 1,
35
- Media = 2,
36
- Server = 3,
34
+ Browser = 0,
35
+ Media = 1,
36
+ ServerApplication = 2,
37
+ ServerRoot = 3,
37
38
  Root = 4
38
39
  }
39
40
  export interface BuildOutputFile extends OutputFile {
@@ -18,9 +18,10 @@ const load_result_cache_1 = require("./load-result-cache");
18
18
  const utils_1 = require("./utils");
19
19
  var BuildOutputFileType;
20
20
  (function (BuildOutputFileType) {
21
- BuildOutputFileType[BuildOutputFileType["Browser"] = 1] = "Browser";
22
- BuildOutputFileType[BuildOutputFileType["Media"] = 2] = "Media";
23
- BuildOutputFileType[BuildOutputFileType["Server"] = 3] = "Server";
21
+ BuildOutputFileType[BuildOutputFileType["Browser"] = 0] = "Browser";
22
+ BuildOutputFileType[BuildOutputFileType["Media"] = 1] = "Media";
23
+ BuildOutputFileType[BuildOutputFileType["ServerApplication"] = 2] = "ServerApplication";
24
+ BuildOutputFileType[BuildOutputFileType["ServerRoot"] = 3] = "ServerRoot";
24
25
  BuildOutputFileType[BuildOutputFileType["Root"] = 4] = "Root";
25
26
  })(BuildOutputFileType || (exports.BuildOutputFileType = BuildOutputFileType = {}));
26
27
  /**
@@ -292,7 +293,10 @@ class BundlerContext {
292
293
  fileType = BuildOutputFileType.Media;
293
294
  }
294
295
  else if (this.#platformIsServer) {
295
- fileType = BuildOutputFileType.Server;
296
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
297
+ fileType = result.metafile['ng-ssr-entry-bundle']
298
+ ? BuildOutputFileType.ServerRoot
299
+ : BuildOutputFileType.ServerApplication;
296
300
  }
297
301
  else {
298
302
  fileType = BuildOutputFileType.Browser;
@@ -24,6 +24,9 @@ export interface ExternalResultMetadata {
24
24
  implicitServer: string[];
25
25
  explicit: string[];
26
26
  }
27
+ export type PrerenderedRoutesRecord = Record<string, {
28
+ headers?: Record<string, string>;
29
+ }>;
27
30
  /**
28
31
  * Represents the result of a single builder execute call.
29
32
  */
@@ -33,7 +36,7 @@ export declare class ExecutionResult {
33
36
  outputFiles: BuildOutputFile[];
34
37
  assetFiles: BuildOutputAsset[];
35
38
  errors: (Message | PartialMessage)[];
36
- prerenderedRoutes: string[];
39
+ prerenderedRoutes: PrerenderedRoutesRecord;
37
40
  warnings: (Message | PartialMessage)[];
38
41
  logs: string[];
39
42
  externalMetadata?: ExternalResultMetadata;
@@ -46,7 +49,7 @@ export declare class ExecutionResult {
46
49
  addLog(value: string): void;
47
50
  addError(error: PartialMessage | string): void;
48
51
  addErrors(errors: (PartialMessage | string)[]): void;
49
- addPrerenderedRoutes(routes: string[]): void;
52
+ addPrerenderedRoutes(routes: PrerenderedRoutesRecord): void;
50
53
  addWarning(error: PartialMessage | string): void;
51
54
  addWarnings(errors: (PartialMessage | string)[]): void;
52
55
  /**
@@ -19,7 +19,7 @@ class ExecutionResult {
19
19
  outputFiles = [];
20
20
  assetFiles = [];
21
21
  errors = [];
22
- prerenderedRoutes = [];
22
+ prerenderedRoutes = {};
23
23
  warnings = [];
24
24
  logs = [];
25
25
  externalMetadata;
@@ -53,9 +53,13 @@ class ExecutionResult {
53
53
  }
54
54
  }
55
55
  addPrerenderedRoutes(routes) {
56
- this.prerenderedRoutes.push(...routes);
56
+ Object.assign(this.prerenderedRoutes, routes);
57
57
  // Sort the prerendered routes.
58
- this.prerenderedRoutes.sort((a, b) => a.localeCompare(b));
58
+ const sortedObj = {};
59
+ for (const key of Object.keys(this.prerenderedRoutes).sort()) {
60
+ sortedObj[key] = this.prerenderedRoutes[key];
61
+ }
62
+ this.prerenderedRoutes = sortedObj;
59
63
  }
60
64
  addWarning(error) {
61
65
  if (typeof error === 'string') {
@@ -85,4 +85,9 @@ export declare class MemoryCache<V> extends Cache<V, Map<string, V>> {
85
85
  * @returns An iterable of all values in the cache.
86
86
  */
87
87
  values(): MapIterator<V>;
88
+ /**
89
+ * Provides all the keys/values currently present in the cache instance.
90
+ * @returns An iterable of all key/value pairs in the cache.
91
+ */
92
+ entries(): MapIterator<[string, V]>;
88
93
  }
@@ -88,5 +88,12 @@ class MemoryCache extends Cache {
88
88
  values() {
89
89
  return this.store.values();
90
90
  }
91
+ /**
92
+ * Provides all the keys/values currently present in the cache instance.
93
+ * @returns An iterable of all key/value pairs in the cache.
94
+ */
95
+ entries() {
96
+ return this.store.entries();
97
+ }
91
98
  }
92
99
  exports.MemoryCache = MemoryCache;
@@ -9,7 +9,7 @@
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.createCompilerPluginOptions = createCompilerPluginOptions;
11
11
  function createCompilerPluginOptions(options, target, sourceFileCache) {
12
- const { workspaceRoot, optimizationOptions, sourcemapOptions, tsconfig, outputNames, fileReplacements, externalDependencies, preserveSymlinks, stylePreprocessorOptions, advancedOptimizations, inlineStyleLanguage, jit, cacheOptions, tailwindConfiguration, postcssConfiguration, publicPath, } = options;
12
+ const { workspaceRoot, optimizationOptions, sourcemapOptions, tsconfig, outputNames, fileReplacements, externalDependencies, preserveSymlinks, stylePreprocessorOptions, advancedOptimizations, inlineStyleLanguage, jit, cacheOptions, tailwindConfiguration, postcssConfiguration, publicPath, externalRuntimeStyles, } = options;
13
13
  return {
14
14
  // JS/TS options
15
15
  pluginOptions: {
@@ -22,6 +22,7 @@ function createCompilerPluginOptions(options, target, sourceFileCache) {
22
22
  sourceFileCache,
23
23
  loadResultCache: sourceFileCache?.loadResultCache,
24
24
  incremental: !!options.watch,
25
+ externalRuntimeStyles,
25
26
  },
26
27
  // Component stylesheet options
27
28
  styleOptions: {
@@ -36,7 +36,8 @@ class I18nInliner {
36
36
  const files = new Map();
37
37
  const pendingMaps = [];
38
38
  for (const file of options.outputFiles) {
39
- if (file.type === bundler_context_1.BuildOutputFileType.Root) {
39
+ if (file.type === bundler_context_1.BuildOutputFileType.Root || file.type === bundler_context_1.BuildOutputFileType.ServerRoot) {
40
+ // Skip also the server entry-point.
40
41
  // Skip stats and similar files.
41
42
  continue;
42
43
  }
@@ -30,6 +30,7 @@ const node_path_1 = require("node:path");
30
30
  const node_url_1 = require("node:url");
31
31
  const node_zlib_1 = require("node:zlib");
32
32
  const semver_1 = require("semver");
33
+ const schema_1 = require("../../builders/application/schema");
33
34
  const manifest_1 = require("../../utils/server-rendering/manifest");
34
35
  const stats_table_1 = require("../../utils/stats-table");
35
36
  const bundler_context_1 = require("./bundler-context");
@@ -47,7 +48,7 @@ function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, c
47
48
  ++unchangedCount;
48
49
  continue;
49
50
  }
50
- const isPlatformServer = type === bundler_context_1.BuildOutputFileType.Server;
51
+ const isPlatformServer = type === bundler_context_1.BuildOutputFileType.ServerApplication || type === bundler_context_1.BuildOutputFileType.ServerRoot;
51
52
  if (isPlatformServer && !ssrOutputEnabled) {
52
53
  // Only log server build stats when SSR is enabled.
53
54
  continue;
@@ -342,7 +343,7 @@ function getSupportedNodeTargets() {
342
343
  return SUPPORTED_NODE_VERSIONS.split('||').map((v) => 'node' + (0, semver_1.coerce)(v)?.version);
343
344
  }
344
345
  async function createJsonBuildManifest(result, normalizedOptions) {
345
- const { colors: color, outputOptions: { base, server, browser }, ssrOptions, } = normalizedOptions;
346
+ const { colors: color, outputOptions: { base, server, browser }, ssrOptions, outputMode, } = normalizedOptions;
346
347
  const { warnings, errors, prerenderedRoutes } = result;
347
348
  const manifest = {
348
349
  errors: errors.length ? await (0, esbuild_1.formatMessages)(errors, { kind: 'error', color }) : [],
@@ -350,7 +351,9 @@ async function createJsonBuildManifest(result, normalizedOptions) {
350
351
  outputPaths: {
351
352
  root: (0, node_url_1.pathToFileURL)(base),
352
353
  browser: (0, node_url_1.pathToFileURL)((0, node_path_1.join)(base, browser)),
353
- server: ssrOptions ? (0, node_url_1.pathToFileURL)((0, node_path_1.join)(base, server)) : undefined,
354
+ server: outputMode !== schema_1.OutputMode.Static && ssrOptions
355
+ ? (0, node_url_1.pathToFileURL)((0, node_path_1.join)(base, server))
356
+ : undefined,
354
357
  },
355
358
  prerenderedRoutes,
356
359
  };
@@ -398,4 +401,5 @@ function getEntryPointName(entryPoint) {
398
401
  exports.SERVER_GENERATED_EXTERNALS = new Set([
399
402
  './polyfills.server.mjs',
400
403
  './' + manifest_1.SERVER_APP_MANIFEST_FILENAME,
404
+ './' + manifest_1.SERVER_APP_ENGINE_MANIFEST_FILENAME,
401
405
  ]);
@@ -12,7 +12,6 @@ const mrmime_1 = require("mrmime");
12
12
  const node_path_1 = require("node:path");
13
13
  const load_esm_1 = require("../../../utils/load-esm");
14
14
  const utils_1 = require("../utils");
15
- const COMPONENT_REGEX = /%COMP%/g;
16
15
  function createAngularAssetsMiddleware(server, assets, outputFiles, usedComponentStyles) {
17
16
  return function angularAssetsMiddleware(req, res, next) {
18
17
  if (req.url === undefined || res.writableEnded) {
@@ -76,12 +75,10 @@ function createAngularAssetsMiddleware(server, assets, outputFiles, usedComponen
76
75
  // Shim the stylesheet if a component ID is provided
77
76
  if (componentId.length > 0) {
78
77
  // Validate component ID
79
- if (/[_.-A-Za-z0-9]+-c\d{9}$/.test(componentId)) {
78
+ if (/^[_.\-\p{Letter}\d]+-c\d{9}$/u.test(componentId)) {
80
79
  (0, load_esm_1.loadEsmModule)('@angular/compiler')
81
80
  .then((compilerModule) => {
82
- const encapsulatedData = compilerModule
83
- .encapsulateStyle(new TextDecoder().decode(data))
84
- .replaceAll(COMPONENT_REGEX, componentId);
81
+ const encapsulatedData = compilerModule.encapsulateStyle(new TextDecoder().decode(data), componentId);
85
82
  res.setHeader('Content-Type', 'text/css');
86
83
  res.setHeader('Cache-Control', 'no-cache');
87
84
  res.end(encapsulatedData);
@@ -9,15 +9,31 @@
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.angularHtmlFallbackMiddleware = angularHtmlFallbackMiddleware;
11
11
  const utils_1 = require("../utils");
12
+ const ALLOWED_FALLBACK_METHODS = Object.freeze(['GET', 'HEAD']);
12
13
  function angularHtmlFallbackMiddleware(req, _res, next) {
13
14
  // Similar to how it is handled in vite
14
15
  // https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/htmlFallback.ts#L15C19-L15C45
15
- if ((req.method === 'GET' || req.method === 'HEAD') &&
16
- (!req.url || !(0, utils_1.lookupMimeTypeFromRequest)(req.url)) &&
17
- (!req.headers.accept ||
18
- req.headers.accept.includes('text/html') ||
19
- req.headers.accept.includes('text/*') ||
20
- req.headers.accept.includes('*/*'))) {
16
+ if (!req.method || !ALLOWED_FALLBACK_METHODS.includes(req.method)) {
17
+ // No fallback for unsupported request methods
18
+ next();
19
+ return;
20
+ }
21
+ if (req.url) {
22
+ const mimeType = (0, utils_1.lookupMimeTypeFromRequest)(req.url);
23
+ if (mimeType === 'text/html' || mimeType === 'application/xhtml+xml') {
24
+ // eslint-disable-next-line no-console
25
+ console.warn(`Request for HTML file "${req.url}" was received but no asset found. Asset may be missing from build.`);
26
+ }
27
+ else if (mimeType) {
28
+ // No fallback for request of asset-like files
29
+ next();
30
+ return;
31
+ }
32
+ }
33
+ if (!req.headers.accept ||
34
+ req.headers.accept.includes('text/html') ||
35
+ req.headers.accept.includes('text/*') ||
36
+ req.headers.accept.includes('*/*')) {
21
37
  req.url = '/index.html';
22
38
  }
23
39
  next();
@@ -8,5 +8,5 @@
8
8
  export { createAngularAssetsMiddleware } from './assets-middleware';
9
9
  export { angularHtmlFallbackMiddleware } from './html-fallback-middleware';
10
10
  export { createAngularIndexHtmlMiddleware } from './index-html-middleware';
11
- export { createAngularSSRMiddleware } from './ssr-middleware';
11
+ export { createAngularSsrExternalMiddleware, createAngularSsrInternalMiddleware, } from './ssr-middleware';
12
12
  export { createAngularHeadersMiddleware } from './headers-middleware';
@@ -7,7 +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.createAngularHeadersMiddleware = exports.createAngularSSRMiddleware = exports.createAngularIndexHtmlMiddleware = exports.angularHtmlFallbackMiddleware = exports.createAngularAssetsMiddleware = void 0;
10
+ exports.createAngularHeadersMiddleware = exports.createAngularSsrInternalMiddleware = exports.createAngularSsrExternalMiddleware = exports.createAngularIndexHtmlMiddleware = exports.angularHtmlFallbackMiddleware = exports.createAngularAssetsMiddleware = void 0;
11
11
  var assets_middleware_1 = require("./assets-middleware");
12
12
  Object.defineProperty(exports, "createAngularAssetsMiddleware", { enumerable: true, get: function () { return assets_middleware_1.createAngularAssetsMiddleware; } });
13
13
  var html_fallback_middleware_1 = require("./html-fallback-middleware");
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "angularHtmlFallbackMiddleware", { enumerable: tr
15
15
  var index_html_middleware_1 = require("./index-html-middleware");
16
16
  Object.defineProperty(exports, "createAngularIndexHtmlMiddleware", { enumerable: true, get: function () { return index_html_middleware_1.createAngularIndexHtmlMiddleware; } });
17
17
  var ssr_middleware_1 = require("./ssr-middleware");
18
- Object.defineProperty(exports, "createAngularSSRMiddleware", { enumerable: true, get: function () { return ssr_middleware_1.createAngularSSRMiddleware; } });
18
+ Object.defineProperty(exports, "createAngularSsrExternalMiddleware", { enumerable: true, get: function () { return ssr_middleware_1.createAngularSsrExternalMiddleware; } });
19
+ Object.defineProperty(exports, "createAngularSsrInternalMiddleware", { enumerable: true, get: function () { return ssr_middleware_1.createAngularSsrInternalMiddleware; } });
19
20
  var headers_middleware_1 = require("./headers-middleware");
20
21
  Object.defineProperty(exports, "createAngularHeadersMiddleware", { enumerable: true, get: function () { return headers_middleware_1.createAngularHeadersMiddleware; } });
@@ -6,4 +6,5 @@
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, indexHtmlTransformer?: (content: string) => Promise<string>): Connect.NextHandleFunction;
9
+ export declare function createAngularSsrInternalMiddleware(server: ViteDevServer, indexHtmlTransformer?: (content: string) => Promise<string>): Connect.NextHandleFunction;
10
+ export declare function createAngularSsrExternalMiddleware(server: ViteDevServer, indexHtmlTransformer?: (content: string) => Promise<string>): Promise<Connect.NextHandleFunction>;
@@ -7,36 +7,82 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.createAngularSSRMiddleware = createAngularSSRMiddleware;
11
- function createAngularSSRMiddleware(server, indexHtmlTransformer) {
10
+ exports.createAngularSsrInternalMiddleware = createAngularSsrInternalMiddleware;
11
+ exports.createAngularSsrExternalMiddleware = createAngularSsrExternalMiddleware;
12
+ const load_esm_1 = require("../../../utils/load-esm");
13
+ function createAngularSsrInternalMiddleware(server, indexHtmlTransformer) {
12
14
  let cachedAngularServerApp;
13
- return function angularSSRMiddleware(req, res, next) {
15
+ return function angularSsrMiddleware(req, res, next) {
14
16
  if (req.url === undefined) {
15
17
  return next();
16
18
  }
17
- const resolvedUrls = server.resolvedUrls;
18
- const baseUrl = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
19
- const url = new URL(req.url, baseUrl);
20
19
  (async () => {
20
+ const { writeResponseToNodeResponse, createWebRequestFromNodeRequest } = await (0, load_esm_1.loadEsmModule)('@angular/ssr/node');
21
21
  const { ɵgetOrCreateAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs'));
22
22
  const angularServerApp = ɵgetOrCreateAngularServerApp();
23
23
  // Only Add the transform hook only if it's a different instance.
24
24
  if (cachedAngularServerApp !== angularServerApp) {
25
- angularServerApp.hooks.on('html:transform:pre', async ({ html }) => {
25
+ angularServerApp.hooks.on('html:transform:pre', async ({ html, url }) => {
26
26
  const processedHtml = await server.transformIndexHtml(url.pathname, html);
27
27
  return indexHtmlTransformer?.(processedHtml) ?? processedHtml;
28
28
  });
29
29
  cachedAngularServerApp = angularServerApp;
30
30
  }
31
- const response = await angularServerApp.render(new Request(url, { signal: AbortSignal.timeout(30_000) }), undefined);
32
- return response?.text();
33
- })()
34
- .then((content) => {
35
- if (typeof content !== 'string') {
31
+ const webReq = new Request(createWebRequestFromNodeRequest(req), {
32
+ signal: AbortSignal.timeout(30_000),
33
+ });
34
+ const webRes = await angularServerApp.render(webReq);
35
+ if (!webRes) {
36
36
  return next();
37
37
  }
38
- res.end(content);
39
- })
40
- .catch((error) => next(error));
38
+ return writeResponseToNodeResponse(webRes, res);
39
+ })().catch(next);
41
40
  };
42
41
  }
42
+ async function createAngularSsrExternalMiddleware(server, indexHtmlTransformer) {
43
+ let fallbackWarningShown = false;
44
+ let cachedAngularAppEngine;
45
+ let angularSsrInternalMiddleware;
46
+ const { createWebRequestFromNodeRequest, writeResponseToNodeResponse } = await (0, load_esm_1.loadEsmModule)('@angular/ssr/node');
47
+ return function angularSsrExternalMiddleware(req, res, next) {
48
+ (async () => {
49
+ const { default: handler, AngularAppEngine } = (await server.ssrLoadModule('./server.mjs'));
50
+ if (!isSsrNodeRequestHandler(handler) && !isSsrRequestHandler(handler)) {
51
+ if (!fallbackWarningShown) {
52
+ // eslint-disable-next-line no-console
53
+ console.warn(`The default export in 'server.ts' does not provide a Node.js request handler. ` +
54
+ 'Using the internal SSR middleware instead.');
55
+ fallbackWarningShown = true;
56
+ }
57
+ angularSsrInternalMiddleware ??= createAngularSsrInternalMiddleware(server, indexHtmlTransformer);
58
+ angularSsrInternalMiddleware(req, res, next);
59
+ return;
60
+ }
61
+ if (cachedAngularAppEngine !== AngularAppEngine) {
62
+ AngularAppEngine.ɵhooks.on('html:transform:pre', async ({ html, url }) => {
63
+ const processedHtml = await server.transformIndexHtml(url.pathname, html);
64
+ return indexHtmlTransformer?.(processedHtml) ?? processedHtml;
65
+ });
66
+ cachedAngularAppEngine = AngularAppEngine;
67
+ }
68
+ // Forward the request to the middleware in server.ts
69
+ if (isSsrNodeRequestHandler(handler)) {
70
+ await handler(req, res, next);
71
+ }
72
+ else {
73
+ const webRes = await handler(createWebRequestFromNodeRequest(req));
74
+ if (!webRes) {
75
+ next();
76
+ return;
77
+ }
78
+ await writeResponseToNodeResponse(webRes, res);
79
+ }
80
+ })().catch(next);
81
+ };
82
+ }
83
+ function isSsrNodeRequestHandler(value) {
84
+ return typeof value === 'function' && '__ng_node_request_handler__' in value;
85
+ }
86
+ function isSsrRequestHandler(value) {
87
+ return typeof value === 'function' && '__ng_request_handler__' in value;
88
+ }
@@ -0,0 +1,16 @@
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 type { Plugin } from 'vite';
9
+ import { AngularMemoryOutputFiles } from '../utils';
10
+ interface AngularMemoryPluginOptions {
11
+ virtualProjectRoot: string;
12
+ outputFiles: AngularMemoryOutputFiles;
13
+ external?: string[];
14
+ }
15
+ export declare function createAngularMemoryPlugin(options: AngularMemoryPluginOptions): Promise<Plugin>;
16
+ export {};
@@ -11,13 +11,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.createAngularMemoryPlugin = createAngularMemoryPlugin;
14
- const remapping_1 = __importDefault(require("@ampproject/remapping"));
15
14
  const node_assert_1 = __importDefault(require("node:assert"));
16
15
  const promises_1 = require("node:fs/promises");
17
16
  const node_path_1 = require("node:path");
18
- const middlewares_1 = require("./middlewares");
19
- function createAngularMemoryPlugin(options) {
20
- const { workspaceRoot, virtualProjectRoot, outputFiles, assets, external, ssr, extensionMiddleware, indexHtmlTransformer, normalizePath, usedComponentStyles, } = options;
17
+ const load_esm_1 = require("../../../utils/load-esm");
18
+ async function createAngularMemoryPlugin(options) {
19
+ const { virtualProjectRoot, outputFiles, external } = options;
20
+ const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
21
+ // See: https://github.com/vitejs/vite/blob/a34a73a3ad8feeacc98632c0f4c643b6820bbfda/packages/vite/src/node/server/pluginContainer.ts#L331-L334
22
+ const defaultImporter = (0, node_path_1.join)(virtualProjectRoot, 'index.html');
21
23
  return {
22
24
  name: 'vite:angular-memory',
23
25
  // Ensures plugin hooks run before built-in Vite hooks
@@ -29,11 +31,19 @@ function createAngularMemoryPlugin(options) {
29
31
  // `/@id/${source}` but is currently closer to a raw external than a resolved file path.
30
32
  return source;
31
33
  }
32
- if (importer && source[0] === '.' && normalizePath(importer).startsWith(virtualProjectRoot)) {
33
- // Remove query if present
34
- const [importerFile] = importer.split('?', 1);
35
- source =
36
- '/' + normalizePath((0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.relative)(virtualProjectRoot, importerFile)), source));
34
+ if (importer) {
35
+ let normalizedSource;
36
+ if (source[0] === '.' && normalizePath(importer).startsWith(virtualProjectRoot)) {
37
+ // Remove query if present
38
+ const [importerFile] = importer.split('?', 1);
39
+ normalizedSource = (0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.relative)(virtualProjectRoot, importerFile)), source);
40
+ }
41
+ else if (source[0] === '/' && importer === defaultImporter) {
42
+ normalizedSource = (0, node_path_1.basename)(source);
43
+ }
44
+ if (normalizedSource) {
45
+ source = '/' + normalizePath(normalizedSource);
46
+ }
37
47
  }
38
48
  const [file] = source.split('?', 1);
39
49
  if (outputFiles.has(file)) {
@@ -58,38 +68,6 @@ function createAngularMemoryPlugin(options) {
58
68
  map: mapContents && Buffer.from(mapContents).toString('utf-8'),
59
69
  };
60
70
  },
61
- // eslint-disable-next-line max-lines-per-function
62
- configureServer(server) {
63
- const originalssrTransform = server.ssrTransform;
64
- server.ssrTransform = async (code, map, url, originalCode) => {
65
- const result = await originalssrTransform(code, null, url, originalCode);
66
- if (!result || !result.map || !map) {
67
- return result;
68
- }
69
- const remappedMap = (0, remapping_1.default)([result.map, map], () => null);
70
- // Set the sourcemap root to the workspace root. This is needed since we set a virtual path as root.
71
- remappedMap.sourceRoot = normalizePath(workspaceRoot) + '/';
72
- return {
73
- ...result,
74
- map: remappedMap,
75
- };
76
- };
77
- server.middlewares.use((0, middlewares_1.createAngularHeadersMiddleware)(server));
78
- // Assets and resources get handled first
79
- server.middlewares.use((0, middlewares_1.createAngularAssetsMiddleware)(server, assets, outputFiles, usedComponentStyles));
80
- if (extensionMiddleware?.length) {
81
- extensionMiddleware.forEach((middleware) => server.middlewares.use(middleware));
82
- }
83
- // Returning a function, installs middleware after the main transform middleware but
84
- // before the built-in HTML middleware
85
- return () => {
86
- if (ssr) {
87
- server.middlewares.use((0, middlewares_1.createAngularSSRMiddleware)(server, indexHtmlTransformer));
88
- }
89
- server.middlewares.use(middlewares_1.angularHtmlFallbackMiddleware);
90
- server.middlewares.use((0, middlewares_1.createAngularIndexHtmlMiddleware)(server, outputFiles, indexHtmlTransformer));
91
- };
92
- },
93
71
  };
94
72
  }
95
73
  /**