@angular/ssr 19.1.0-next.0 → 19.1.0-next.1

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/index.d.ts CHANGED
@@ -36,9 +36,9 @@ export declare class AngularAppEngine {
36
36
  */
37
37
  private readonly manifest;
38
38
  /**
39
- * The number of entry points available in the server application's manifest.
39
+ * A map of supported locales from the server application's manifest.
40
40
  */
41
- private readonly entryPointsCount;
41
+ private readonly supportedLocales;
42
42
  /**
43
43
  * A cache that holds entry points, keyed by their potential locale string.
44
44
  */
@@ -55,6 +55,15 @@ export declare class AngularAppEngine {
55
55
  * corresponding to `https://www.example.com/page`.
56
56
  */
57
57
  handle(request: Request, requestContext?: unknown): Promise<Response | null>;
58
+ /**
59
+ * Handles requests for the base path when i18n is enabled.
60
+ * Redirects the user to a locale-specific path based on the `Accept-Language` header.
61
+ *
62
+ * @param request The incoming request.
63
+ * @returns A `Response` object with a 302 redirect, or `null` if i18n is not enabled
64
+ * or the request is not for the base path.
65
+ */
66
+ private redirectBasedOnAcceptLanguage;
58
67
  /**
59
68
  * Retrieves the Angular server application instance for a given request.
60
69
  *
@@ -95,7 +104,7 @@ declare interface AngularAppEngineManifest {
95
104
  /**
96
105
  * A readonly record of entry points for the server application.
97
106
  * Each entry consists of:
98
- * - `key`: The base href for the entry point.
107
+ * - `key`: The url segment for the entry point.
99
108
  * - `value`: A function that returns a promise resolving to an object of type `EntryPointExports`.
100
109
  */
101
110
  readonly entryPoints: Readonly<Record<string, (() => Promise<EntryPointExports>) | undefined>>;
@@ -104,6 +113,13 @@ declare interface AngularAppEngineManifest {
104
113
  * This is used to determine the root path of the application.
105
114
  */
106
115
  readonly basePath: string;
116
+ /**
117
+ * A readonly record mapping supported locales to their respective entry-point paths.
118
+ * Each entry consists of:
119
+ * - `key`: The locale identifier (e.g., 'en', 'fr').
120
+ * - `value`: The url segment associated with that locale.
121
+ */
122
+ readonly supportedLocales: Readonly<Record<string, string | undefined>>;
107
123
  }
108
124
 
109
125
  /**
@@ -144,6 +160,26 @@ declare interface AngularAppManifest {
144
160
  * the application, aiding with localization and rendering content specific to the locale.
145
161
  */
146
162
  readonly locale?: string;
163
+ /**
164
+ * Maps entry-point names to their corresponding browser bundles and loading strategies.
165
+ *
166
+ * - **Key**: The entry-point name, typically the value of `ɵentryName`.
167
+ * - **Value**: An array of objects, each representing a browser bundle with:
168
+ * - `path`: The filename or URL of the associated JavaScript bundle to preload.
169
+ * - `dynamicImport`: A boolean indicating whether the bundle is loaded via a dynamic `import()`.
170
+ * If `true`, the bundle is lazily loaded, impacting its preloading behavior.
171
+ *
172
+ * ### Example
173
+ * ```ts
174
+ * {
175
+ * 'src/app/lazy/lazy.ts': [{ path: 'src/app/lazy/lazy.js', dynamicImport: true }]
176
+ * }
177
+ * ```
178
+ */
179
+ readonly entryPointToBrowserMapping?: Readonly<Record<string, ReadonlyArray<{
180
+ path: string;
181
+ dynamicImport: boolean;
182
+ }> | undefined>>;
147
183
  }
148
184
 
149
185
  /**
@@ -293,6 +329,7 @@ declare class AngularServerApp {
293
329
  *
294
330
  * @param html - The raw HTML content to be transformed.
295
331
  * @param url - The URL associated with the HTML content, used for context during transformations.
332
+ * @param preload - An array of URLs representing the JavaScript resources to preload.
296
333
  * @returns A promise that resolves to the transformed HTML string.
297
334
  */
298
335
  private runTransformsOnHtml;
@@ -369,6 +406,8 @@ declare interface EntryPointExports {
369
406
  ɵdestroyAngularServerApp: () => void;
370
407
  }
371
408
 
409
+ declare type EntryPointToBrowserMapping = AngularAppManifest['entryPointToBrowserMapping'];
410
+
372
411
  /**
373
412
  * Defines the names of available hooks for registering and triggering custom logic within the application.
374
413
  */
@@ -493,8 +532,6 @@ export declare enum PrerenderFallback {
493
532
  * @param options - (Optional) An object containing additional configuration options for server routes.
494
533
  * @returns An `EnvironmentProviders` instance with the server routes configuration.
495
534
  *
496
- * @returns An `EnvironmentProviders` object that contains the server routes configuration.
497
- *
498
535
  * @see {@link ServerRoute}
499
536
  * @see {@link ServerRoutesConfigOptions}
500
537
  * @developerPreview
@@ -700,6 +737,10 @@ declare interface RouteTreeNodeMetadata {
700
737
  * Specifies the rendering mode used for this route.
701
738
  */
702
739
  renderMode: RenderMode;
740
+ /**
741
+ * A list of resource that should be preloaded by the browser.
742
+ */
743
+ preload?: readonly string[];
703
744
  }
704
745
 
705
746
  /**
@@ -920,10 +961,11 @@ export declare function ɵgetOrCreateAngularServerApp(options?: Readonly<Angular
920
961
  * @param invokeGetPrerenderParams - A boolean flag indicating whether to invoke `getPrerenderParams` for parameterized SSG routes
921
962
  * to handle prerendering paths. Defaults to `false`.
922
963
  * @param includePrerenderFallbackRoutes - A flag indicating whether to include fallback routes in the result. Defaults to `true`.
964
+ * @param entryPointToBrowserMapping - Maps the entry-point name to the associated JavaScript browser bundles.
923
965
  *
924
966
  * @returns A promise that resolves to an object of type `AngularRouterConfigResult` or errors.
925
967
  */
926
- export declare function ɵgetRoutesFromAngularRouterConfig(bootstrap: AngularBootstrap, document: string, url: URL, invokeGetPrerenderParams?: boolean, includePrerenderFallbackRoutes?: boolean): Promise<AngularRouterConfigResult>;
968
+ export declare function ɵgetRoutesFromAngularRouterConfig(bootstrap: AngularBootstrap, document: string, url: URL, invokeGetPrerenderParams?: boolean, includePrerenderFallbackRoutes?: boolean, entryPointToBrowserMapping?: EntryPointToBrowserMapping | undefined): Promise<AngularRouterConfigResult>;
927
969
 
928
970
  export declare class ɵInlineCriticalCssProcessor extends BeastiesBase {
929
971
  readFile: (path: string) => Promise<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/ssr",
3
- "version": "19.1.0-next.0",
3
+ "version": "19.1.0-next.1",
4
4
  "description": "Angular server side rendering utilities",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/angular/angular-cli",
@@ -27,12 +27,12 @@
27
27
  }
28
28
  },
29
29
  "devDependencies": {
30
- "@angular/common": "19.1.0-next.0",
31
- "@angular/compiler": "19.1.0-next.0",
32
- "@angular/core": "19.1.0-next.0",
33
- "@angular/platform-browser": "19.1.0-next.0",
34
- "@angular/platform-server": "19.1.0-next.0",
35
- "@angular/router": "19.1.0-next.0",
30
+ "@angular/common": "19.1.0-next.2",
31
+ "@angular/compiler": "19.1.0-next.2",
32
+ "@angular/core": "19.1.0-next.2",
33
+ "@angular/platform-browser": "19.1.0-next.2",
34
+ "@angular/platform-server": "19.1.0-next.2",
35
+ "@angular/router": "19.1.0-next.2",
36
36
  "@bazel/runfiles": "^5.8.1"
37
37
  },
38
38
  "sideEffects": false,
@@ -10964,36 +10964,43 @@ function parseStylesheet(stylesheet) {
10964
10964
  return parse$2(stylesheet);
10965
10965
  }
10966
10966
  function serializeStylesheet(ast, options) {
10967
- let cssStr = "";
10967
+ const cssParts = [];
10968
10968
  stringify(ast, (result, node, type) => {
10969
10969
  if (node?.type === "decl" && node.value.includes("</style>")) {
10970
10970
  return;
10971
10971
  }
10972
10972
  if (!options.compress) {
10973
- cssStr += result;
10973
+ cssParts.push(result);
10974
10974
  return;
10975
10975
  }
10976
10976
  if (node?.type === "comment")
10977
10977
  return;
10978
10978
  if (node?.type === "decl") {
10979
10979
  const prefix = node.prop + node.raws.between;
10980
- cssStr += result.replace(prefix, prefix.trim());
10980
+ cssParts.push(result.replace(prefix, prefix.trim()));
10981
10981
  return;
10982
10982
  }
10983
10983
  if (type === "start") {
10984
10984
  if (node?.type === "rule" && node.selectors) {
10985
- cssStr += `${node.selectors.join(",")}{`;
10985
+ if (node.selectors.length === 1) {
10986
+ cssParts.push(node.selectors[0] ?? "", "{");
10987
+ } else {
10988
+ cssParts.push(node.selectors.join(","), "{");
10989
+ }
10986
10990
  } else {
10987
- cssStr += result.replace(/\s\{$/, "{");
10991
+ cssParts.push(result.trim());
10988
10992
  }
10989
10993
  return;
10990
10994
  }
10991
10995
  if (type === "end" && result === "}" && node?.raws?.semicolon) {
10992
- cssStr = cssStr.slice(0, -1);
10996
+ const lastItemIdx = cssParts.length - 2;
10997
+ if (lastItemIdx >= 0 && cssParts[lastItemIdx]) {
10998
+ cssParts[lastItemIdx] = cssParts[lastItemIdx].slice(0, -1);
10999
+ }
10993
11000
  }
10994
- cssStr += result.trim();
11001
+ cssParts.push(result.trim());
10995
11002
  });
10996
- return cssStr;
11003
+ return cssParts.join("");
10997
11004
  }
10998
11005
  function markOnly(predicate) {
10999
11006
  return (rule) => {
@@ -11176,7 +11183,7 @@ function extendElement(element) {
11176
11183
  return this.getAttribute("id");
11177
11184
  },
11178
11185
  set(value) {
11179
- this.setAttribue("id", value);
11186
+ this.setAttribute("id", value);
11180
11187
  }
11181
11188
  },
11182
11189
  className: {
@@ -11336,21 +11343,40 @@ function extendDocument(document) {
11336
11343
  }
11337
11344
  });
11338
11345
  }
11346
+ const selectorTokensCache = /* @__PURE__ */ new Map();
11339
11347
  function cachedQuerySelector(sel, node) {
11340
- const selectorTokens = parse$1(sel);
11341
- for (const tokens of selectorTokens) {
11342
- if (tokens.length === 1) {
11343
- const token = tokens[0];
11344
- if (token.type === "attribute" && token.name === "class") {
11348
+ let selectorTokens = selectorTokensCache.get(sel);
11349
+ if (selectorTokens === void 0) {
11350
+ selectorTokens = parseRelevantSelectors(sel);
11351
+ selectorTokensCache.set(sel, selectorTokens);
11352
+ }
11353
+ if (selectorTokens) {
11354
+ for (const token of selectorTokens) {
11355
+ if (token.name === "class") {
11345
11356
  return classCache.has(token.value);
11346
11357
  }
11347
- if (token.type === "attribute" && token.name === "id") {
11358
+ if (token.name === "id") {
11348
11359
  return idCache.has(token.value);
11349
11360
  }
11350
11361
  }
11351
11362
  }
11352
11363
  return !!selectOne(sel, node);
11353
11364
  }
11365
+ function parseRelevantSelectors(sel) {
11366
+ const tokens = parse$1(sel);
11367
+ const relevantTokens = [];
11368
+ for (let i = 0; i < tokens.length; i++) {
11369
+ const tokenGroup = tokens[i];
11370
+ if (tokenGroup?.length !== 1) {
11371
+ continue;
11372
+ }
11373
+ const token = tokenGroup[0];
11374
+ if (token?.type === "attribute" && (token.name === "class" || token.name === "id")) {
11375
+ relevantTokens.push(token);
11376
+ }
11377
+ }
11378
+ return relevantTokens.length > 0 ? relevantTokens : null;
11379
+ }
11354
11380
 
11355
11381
  const LOG_LEVELS = ["trace", "debug", "info", "warn", "error", "silent"];
11356
11382
  const defaultLogger = {
@@ -11393,8 +11419,25 @@ var __publicField = (obj, key, value) => {
11393
11419
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
11394
11420
  return value;
11395
11421
  };
11422
+ var __accessCheck = (obj, member, msg) => {
11423
+ if (!member.has(obj))
11424
+ throw TypeError("Cannot " + msg);
11425
+ };
11426
+ var __privateGet = (obj, member, getter) => {
11427
+ __accessCheck(obj, member, "read from private field");
11428
+ return getter ? getter.call(obj) : member.get(obj);
11429
+ };
11430
+ var __privateAdd = (obj, member, value) => {
11431
+ if (member.has(obj))
11432
+ throw TypeError("Cannot add the same private member more than once");
11433
+ member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
11434
+ };
11435
+ var _selectorCache;
11436
+ const removePseudoClassesAndElementsPattern = /(?<!\\)::?[a-z-]+(?:\(.+\))?/gi;
11437
+ const removeTrailingCommasPattern = /\(\s*,|,\s*\)/g;
11396
11438
  class Beasties {
11397
11439
  constructor(options = {}) {
11440
+ __privateAdd(this, _selectorCache, /* @__PURE__ */ new Map());
11398
11441
  __publicField(this, "options");
11399
11442
  __publicField(this, "logger");
11400
11443
  __publicField(this, "fs");
@@ -11435,22 +11478,20 @@ class Beasties {
11435
11478
  const start = Date.now();
11436
11479
  const document = createDocument(html);
11437
11480
  if (this.options.additionalStylesheets.length > 0) {
11438
- this.embedAdditionalStylesheet(document);
11481
+ await this.embedAdditionalStylesheet(document);
11439
11482
  }
11440
11483
  if (this.options.external !== false) {
11441
- const externalSheets = [].slice.call(
11442
- document.querySelectorAll('link[rel="stylesheet"]')
11443
- );
11484
+ const externalSheets = [...document.querySelectorAll('link[rel="stylesheet"]')];
11444
11485
  await Promise.all(
11445
11486
  externalSheets.map((link) => this.embedLinkedStylesheet(link, document))
11446
11487
  );
11447
11488
  }
11448
11489
  const styles = this.getAffectedStyleTags(document);
11449
- await Promise.all(
11450
- styles.map((style) => this.processStyle(style, document))
11451
- );
11490
+ for (const style of styles) {
11491
+ this.processStyle(style, document);
11492
+ }
11452
11493
  if (this.options.mergeStylesheets !== false && styles.length !== 0) {
11453
- await this.mergeStylesheets(document);
11494
+ this.mergeStylesheets(document);
11454
11495
  }
11455
11496
  const output = serializeDocument(document);
11456
11497
  const end = Date.now();
@@ -11467,7 +11508,7 @@ class Beasties {
11467
11508
  }
11468
11509
  return styles;
11469
11510
  }
11470
- async mergeStylesheets(document) {
11511
+ mergeStylesheets(document) {
11471
11512
  const styles = this.getAffectedStyleTags(document);
11472
11513
  if (styles.length === 0) {
11473
11514
  this.logger.warn?.(
@@ -11654,7 +11695,7 @@ class Beasties {
11654
11695
  /**
11655
11696
  * Parse the stylesheet within a <style> element, then reduce it to contain only rules used by the document.
11656
11697
  */
11657
- async processStyle(style, document) {
11698
+ processStyle(style, document) {
11658
11699
  if (style.$$reduce === false)
11659
11700
  return;
11660
11701
  const name = style.$$name ? style.$$name.replace(/^\//, "") : "inline CSS";
@@ -11733,10 +11774,10 @@ class Beasties {
11733
11774
  });
11734
11775
  if (isAllowedRule)
11735
11776
  return true;
11736
- if (sel === ":root" || sel === "html" || sel === "body" || /^::?(?:before|after)$/.test(sel)) {
11777
+ if (sel === ":root" || sel === "html" || sel === "body" || sel[0] === ":" && /^::?(?:before|after)$/.test(sel)) {
11737
11778
  return true;
11738
11779
  }
11739
- sel = sel.replace(/(?<!\\)::?[a-z-]+(?![a-z-(])/gi, "").replace(/::?not\(\s*\)/g, "").replace(/\(\s*,/g, "(").replace(/,\s*\)/g, ")").trim();
11780
+ sel = this.normalizeCssSelector(sel);
11740
11781
  if (!sel)
11741
11782
  return false;
11742
11783
  try {
@@ -11769,8 +11810,8 @@ class Beasties {
11769
11810
  }
11770
11811
  if (rule.type === "atrule" && rule.name === "font-face")
11771
11812
  return;
11772
- const rules = "nodes" in rule ? rule.nodes?.filter((rule2) => !rule2.$$remove) : void 0;
11773
- return !rules || rules.length !== 0;
11813
+ const hasRemainingRules = ("nodes" in rule && rule.nodes?.some((rule2) => !rule2.$$remove)) ?? true;
11814
+ return hasRemainingRules;
11774
11815
  })
11775
11816
  );
11776
11817
  if (failedSelectors.length !== 0) {
@@ -11852,7 +11893,17 @@ class Beasties {
11852
11893
  `\x1B[32mInlined ${formatSize(sheet.length)} (${percent}% of original ${formatSize(before.length)}) of ${name}${afterText}.\x1B[39m`
11853
11894
  );
11854
11895
  }
11896
+ normalizeCssSelector(sel) {
11897
+ let normalizedSelector = __privateGet(this, _selectorCache).get(sel);
11898
+ if (normalizedSelector !== void 0) {
11899
+ return normalizedSelector;
11900
+ }
11901
+ normalizedSelector = sel.replace(removePseudoClassesAndElementsPattern, "").replace(removeTrailingCommasPattern, (match) => match.includes("(") ? "(" : ")").trim();
11902
+ __privateGet(this, _selectorCache).set(sel, normalizedSelector);
11903
+ return normalizedSelector;
11904
+ }
11855
11905
  }
11906
+ _selectorCache = new WeakMap();
11856
11907
  function formatSize(size) {
11857
11908
  if (size <= 0) {
11858
11909
  return "0 bytes";