@angular/ssr 19.0.5 → 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
  */
@@ -698,6 +737,10 @@ declare interface RouteTreeNodeMetadata {
698
737
  * Specifies the rendering mode used for this route.
699
738
  */
700
739
  renderMode: RenderMode;
740
+ /**
741
+ * A list of resource that should be preloaded by the browser.
742
+ */
743
+ preload?: readonly string[];
701
744
  }
702
745
 
703
746
  /**
@@ -918,10 +961,11 @@ export declare function ɵgetOrCreateAngularServerApp(options?: Readonly<Angular
918
961
  * @param invokeGetPrerenderParams - A boolean flag indicating whether to invoke `getPrerenderParams` for parameterized SSG routes
919
962
  * to handle prerendering paths. Defaults to `false`.
920
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.
921
965
  *
922
966
  * @returns A promise that resolves to an object of type `AngularRouterConfigResult` or errors.
923
967
  */
924
- 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>;
925
969
 
926
970
  export declare class ɵInlineCriticalCssProcessor extends BeastiesBase {
927
971
  readFile: (path: string) => Promise<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/ssr",
3
- "version": "19.0.5",
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",
@@ -16,18 +16,23 @@
16
16
  "tslib": "^2.3.0"
17
17
  },
18
18
  "peerDependencies": {
19
- "@angular/common": "^19.0.0",
20
- "@angular/core": "^19.0.0",
21
- "@angular/platform-server": "^19.0.0",
22
- "@angular/router": "^19.0.0"
19
+ "@angular/common": "^19.0.0 || ^19.1.0-next.0",
20
+ "@angular/core": "^19.0.0 || ^19.1.0-next.0",
21
+ "@angular/platform-server": "^19.0.0 || ^19.1.0-next.0",
22
+ "@angular/router": "^19.0.0 || ^19.1.0-next.0"
23
+ },
24
+ "peerDependenciesMeta": {
25
+ "@angular/platform-server": {
26
+ "optional": true
27
+ }
23
28
  },
24
29
  "devDependencies": {
25
- "@angular/common": "19.0.0",
26
- "@angular/compiler": "19.0.0",
27
- "@angular/core": "19.0.0",
28
- "@angular/platform-browser": "19.0.0",
29
- "@angular/platform-server": "19.0.0",
30
- "@angular/router": "19.0.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",
31
36
  "@bazel/runfiles": "^5.8.1"
32
37
  },
33
38
  "sideEffects": false,
@@ -2003,26 +2003,39 @@ var hasRequiredNonSecure;
2003
2003
  function requireNonSecure () {
2004
2004
  if (hasRequiredNonSecure) return nonSecure;
2005
2005
  hasRequiredNonSecure = 1;
2006
+ // This alphabet uses `A-Za-z0-9_-` symbols.
2007
+ // The order of characters is optimized for better gzip and brotli compression.
2008
+ // References to the same file (works both for gzip and brotli):
2009
+ // `'use`, `andom`, and `rict'`
2010
+ // References to the brotli default dictionary:
2011
+ // `-26T`, `1983`, `40px`, `75px`, `bush`, `jack`, `mind`, `very`, and `wolf`
2006
2012
  let urlAlphabet =
2007
2013
  'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
2014
+
2008
2015
  let customAlphabet = (alphabet, defaultSize = 21) => {
2009
2016
  return (size = defaultSize) => {
2010
2017
  let id = '';
2011
- let i = size;
2018
+ // A compact alternative for `for (var i = 0; i < step; i++)`.
2019
+ let i = size | 0;
2012
2020
  while (i--) {
2021
+ // `| 0` is more compact and faster than `Math.floor()`.
2013
2022
  id += alphabet[(Math.random() * alphabet.length) | 0];
2014
2023
  }
2015
2024
  return id
2016
2025
  }
2017
2026
  };
2027
+
2018
2028
  let nanoid = (size = 21) => {
2019
2029
  let id = '';
2020
- let i = size;
2030
+ // A compact alternative for `for (var i = 0; i < step; i++)`.
2031
+ let i = size | 0;
2021
2032
  while (i--) {
2033
+ // `| 0` is more compact and faster than `Math.floor()`.
2022
2034
  id += urlAlphabet[(Math.random() * 64) | 0];
2023
2035
  }
2024
2036
  return id
2025
2037
  };
2038
+
2026
2039
  nonSecure = { nanoid, customAlphabet };
2027
2040
  return nonSecure;
2028
2041
  }
@@ -10951,36 +10964,43 @@ function parseStylesheet(stylesheet) {
10951
10964
  return parse$2(stylesheet);
10952
10965
  }
10953
10966
  function serializeStylesheet(ast, options) {
10954
- let cssStr = "";
10967
+ const cssParts = [];
10955
10968
  stringify(ast, (result, node, type) => {
10956
10969
  if (node?.type === "decl" && node.value.includes("</style>")) {
10957
10970
  return;
10958
10971
  }
10959
10972
  if (!options.compress) {
10960
- cssStr += result;
10973
+ cssParts.push(result);
10961
10974
  return;
10962
10975
  }
10963
10976
  if (node?.type === "comment")
10964
10977
  return;
10965
10978
  if (node?.type === "decl") {
10966
10979
  const prefix = node.prop + node.raws.between;
10967
- cssStr += result.replace(prefix, prefix.trim());
10980
+ cssParts.push(result.replace(prefix, prefix.trim()));
10968
10981
  return;
10969
10982
  }
10970
10983
  if (type === "start") {
10971
10984
  if (node?.type === "rule" && node.selectors) {
10972
- 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
+ }
10973
10990
  } else {
10974
- cssStr += result.replace(/\s\{$/, "{");
10991
+ cssParts.push(result.trim());
10975
10992
  }
10976
10993
  return;
10977
10994
  }
10978
10995
  if (type === "end" && result === "}" && node?.raws?.semicolon) {
10979
- 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
+ }
10980
11000
  }
10981
- cssStr += result.trim();
11001
+ cssParts.push(result.trim());
10982
11002
  });
10983
- return cssStr;
11003
+ return cssParts.join("");
10984
11004
  }
10985
11005
  function markOnly(predicate) {
10986
11006
  return (rule) => {
@@ -11163,7 +11183,7 @@ function extendElement(element) {
11163
11183
  return this.getAttribute("id");
11164
11184
  },
11165
11185
  set(value) {
11166
- this.setAttribue("id", value);
11186
+ this.setAttribute("id", value);
11167
11187
  }
11168
11188
  },
11169
11189
  className: {
@@ -11323,21 +11343,40 @@ function extendDocument(document) {
11323
11343
  }
11324
11344
  });
11325
11345
  }
11346
+ const selectorTokensCache = /* @__PURE__ */ new Map();
11326
11347
  function cachedQuerySelector(sel, node) {
11327
- const selectorTokens = parse$1(sel);
11328
- for (const tokens of selectorTokens) {
11329
- if (tokens.length === 1) {
11330
- const token = tokens[0];
11331
- 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") {
11332
11356
  return classCache.has(token.value);
11333
11357
  }
11334
- if (token.type === "attribute" && token.name === "id") {
11358
+ if (token.name === "id") {
11335
11359
  return idCache.has(token.value);
11336
11360
  }
11337
11361
  }
11338
11362
  }
11339
11363
  return !!selectOne(sel, node);
11340
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
+ }
11341
11380
 
11342
11381
  const LOG_LEVELS = ["trace", "debug", "info", "warn", "error", "silent"];
11343
11382
  const defaultLogger = {
@@ -11380,8 +11419,25 @@ var __publicField = (obj, key, value) => {
11380
11419
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
11381
11420
  return value;
11382
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;
11383
11438
  class Beasties {
11384
11439
  constructor(options = {}) {
11440
+ __privateAdd(this, _selectorCache, /* @__PURE__ */ new Map());
11385
11441
  __publicField(this, "options");
11386
11442
  __publicField(this, "logger");
11387
11443
  __publicField(this, "fs");
@@ -11422,22 +11478,20 @@ class Beasties {
11422
11478
  const start = Date.now();
11423
11479
  const document = createDocument(html);
11424
11480
  if (this.options.additionalStylesheets.length > 0) {
11425
- this.embedAdditionalStylesheet(document);
11481
+ await this.embedAdditionalStylesheet(document);
11426
11482
  }
11427
11483
  if (this.options.external !== false) {
11428
- const externalSheets = [].slice.call(
11429
- document.querySelectorAll('link[rel="stylesheet"]')
11430
- );
11484
+ const externalSheets = [...document.querySelectorAll('link[rel="stylesheet"]')];
11431
11485
  await Promise.all(
11432
11486
  externalSheets.map((link) => this.embedLinkedStylesheet(link, document))
11433
11487
  );
11434
11488
  }
11435
11489
  const styles = this.getAffectedStyleTags(document);
11436
- await Promise.all(
11437
- styles.map((style) => this.processStyle(style, document))
11438
- );
11490
+ for (const style of styles) {
11491
+ this.processStyle(style, document);
11492
+ }
11439
11493
  if (this.options.mergeStylesheets !== false && styles.length !== 0) {
11440
- await this.mergeStylesheets(document);
11494
+ this.mergeStylesheets(document);
11441
11495
  }
11442
11496
  const output = serializeDocument(document);
11443
11497
  const end = Date.now();
@@ -11454,7 +11508,7 @@ class Beasties {
11454
11508
  }
11455
11509
  return styles;
11456
11510
  }
11457
- async mergeStylesheets(document) {
11511
+ mergeStylesheets(document) {
11458
11512
  const styles = this.getAffectedStyleTags(document);
11459
11513
  if (styles.length === 0) {
11460
11514
  this.logger.warn?.(
@@ -11641,7 +11695,7 @@ class Beasties {
11641
11695
  /**
11642
11696
  * Parse the stylesheet within a <style> element, then reduce it to contain only rules used by the document.
11643
11697
  */
11644
- async processStyle(style, document) {
11698
+ processStyle(style, document) {
11645
11699
  if (style.$$reduce === false)
11646
11700
  return;
11647
11701
  const name = style.$$name ? style.$$name.replace(/^\//, "") : "inline CSS";
@@ -11720,10 +11774,10 @@ class Beasties {
11720
11774
  });
11721
11775
  if (isAllowedRule)
11722
11776
  return true;
11723
- 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)) {
11724
11778
  return true;
11725
11779
  }
11726
- sel = sel.replace(/(?<!\\)::?[a-z-]+(?![a-z-(])/gi, "").replace(/::?not\(\s*\)/g, "").replace(/\(\s*,/g, "(").replace(/,\s*\)/g, ")").trim();
11780
+ sel = this.normalizeCssSelector(sel);
11727
11781
  if (!sel)
11728
11782
  return false;
11729
11783
  try {
@@ -11756,8 +11810,8 @@ class Beasties {
11756
11810
  }
11757
11811
  if (rule.type === "atrule" && rule.name === "font-face")
11758
11812
  return;
11759
- const rules = "nodes" in rule ? rule.nodes?.filter((rule2) => !rule2.$$remove) : void 0;
11760
- return !rules || rules.length !== 0;
11813
+ const hasRemainingRules = ("nodes" in rule && rule.nodes?.some((rule2) => !rule2.$$remove)) ?? true;
11814
+ return hasRemainingRules;
11761
11815
  })
11762
11816
  );
11763
11817
  if (failedSelectors.length !== 0) {
@@ -11839,7 +11893,17 @@ class Beasties {
11839
11893
  `\x1B[32mInlined ${formatSize(sheet.length)} (${percent}% of original ${formatSize(before.length)}) of ${name}${afterText}.\x1B[39m`
11840
11894
  );
11841
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
+ }
11842
11905
  }
11906
+ _selectorCache = new WeakMap();
11843
11907
  function formatSize(size) {
11844
11908
  if (size <= 0) {
11845
11909
  return "0 bytes";