@wordpress/interactivity-router 2.25.0 → 2.26.0

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 (74) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +22 -0
  3. package/build/assets/dynamic-importmap/fetch.js +60 -0
  4. package/build/assets/dynamic-importmap/fetch.js.map +1 -0
  5. package/build/assets/dynamic-importmap/index.js +91 -0
  6. package/build/assets/dynamic-importmap/index.js.map +1 -0
  7. package/build/assets/dynamic-importmap/loader.js +286 -0
  8. package/build/assets/dynamic-importmap/loader.js.map +1 -0
  9. package/build/assets/dynamic-importmap/resolver.js +220 -0
  10. package/build/assets/dynamic-importmap/resolver.js.map +1 -0
  11. package/build/assets/script-modules.js +64 -0
  12. package/build/assets/script-modules.js.map +1 -0
  13. package/build/assets/scs.js +62 -0
  14. package/build/assets/scs.js.map +1 -0
  15. package/build/assets/styles.js +246 -0
  16. package/build/assets/styles.js.map +1 -0
  17. package/build/full-page.js +43 -0
  18. package/build/full-page.js.map +1 -0
  19. package/build/index.js +140 -38
  20. package/build/index.js.map +1 -1
  21. package/build-module/assets/dynamic-importmap/fetch.js +54 -0
  22. package/build-module/assets/dynamic-importmap/fetch.js.map +1 -0
  23. package/build-module/assets/dynamic-importmap/index.js +72 -0
  24. package/build-module/assets/dynamic-importmap/index.js.map +1 -0
  25. package/build-module/assets/dynamic-importmap/loader.js +279 -0
  26. package/build-module/assets/dynamic-importmap/loader.js.map +1 -0
  27. package/build-module/assets/dynamic-importmap/resolver.js +213 -0
  28. package/build-module/assets/dynamic-importmap/resolver.js.map +1 -0
  29. package/build-module/assets/script-modules.js +55 -0
  30. package/build-module/assets/script-modules.js.map +1 -0
  31. package/build-module/assets/scs.js +56 -0
  32. package/build-module/assets/scs.js.map +1 -0
  33. package/build-module/assets/styles.js +235 -0
  34. package/build-module/assets/styles.js.map +1 -0
  35. package/build-module/full-page.js +39 -0
  36. package/build-module/full-page.js.map +1 -0
  37. package/build-module/index.js +142 -38
  38. package/build-module/index.js.map +1 -1
  39. package/build-types/assets/dynamic-importmap/fetch.d.ts +30 -0
  40. package/build-types/assets/dynamic-importmap/fetch.d.ts.map +1 -0
  41. package/build-types/assets/dynamic-importmap/index.d.ts +42 -0
  42. package/build-types/assets/dynamic-importmap/index.d.ts.map +1 -0
  43. package/build-types/assets/dynamic-importmap/loader.d.ts +68 -0
  44. package/build-types/assets/dynamic-importmap/loader.d.ts.map +1 -0
  45. package/build-types/assets/dynamic-importmap/resolver.d.ts +35 -0
  46. package/build-types/assets/dynamic-importmap/resolver.d.ts.map +1 -0
  47. package/build-types/assets/script-modules.d.ts +26 -0
  48. package/build-types/assets/script-modules.d.ts.map +1 -0
  49. package/build-types/assets/scs.d.ts +24 -0
  50. package/build-types/assets/scs.d.ts.map +1 -0
  51. package/build-types/assets/styles.d.ts +69 -0
  52. package/build-types/assets/styles.d.ts.map +1 -0
  53. package/build-types/full-page.d.ts +3 -0
  54. package/build-types/full-page.d.ts.map +1 -0
  55. package/build-types/index.d.ts +4 -5
  56. package/build-types/index.d.ts.map +1 -1
  57. package/package.json +9 -5
  58. package/src/assets/dynamic-importmap/fetch.ts +58 -0
  59. package/src/assets/dynamic-importmap/index.ts +87 -0
  60. package/src/assets/dynamic-importmap/loader.ts +366 -0
  61. package/src/assets/dynamic-importmap/resolver.ts +292 -0
  62. package/src/assets/script-modules.ts +69 -0
  63. package/src/assets/scs.ts +61 -0
  64. package/src/assets/styles.ts +272 -0
  65. package/src/assets/test/scs.test.ts +367 -0
  66. package/src/assets/test/styles.test.ts +758 -0
  67. package/src/full-page.ts +53 -0
  68. package/src/index.ts +165 -45
  69. package/tsconfig.full-page.json +12 -0
  70. package/tsconfig.full-page.tsbuildinfo +1 -0
  71. package/tsconfig.json +9 -4
  72. package/tsconfig.main.json +21 -0
  73. package/tsconfig.main.tsbuildinfo +1 -0
  74. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Calculates the Shortest Common Supersequence (SCS) of two sequences.
3
+ *
4
+ * A supersequence is a sequence that contains both input sequences as subsequences.
5
+ * The shortest common supersequence is the shortest possible such sequence.
6
+ *
7
+ * This implementation uses dynamic programming with a time complexity of O(mn)
8
+ * and space complexity of O(mn), where m and n are the lengths of sequences X and Y.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const seq1 = [1, 3, 5];
13
+ * const seq2 = [2, 3, 4];
14
+ * const scs = shortestCommonSupersequence(seq1, seq2); // [1, 2, 3, 4, 5]
15
+ * ```
16
+ *
17
+ * @param X The first sequence.
18
+ * @param Y The second sequence.
19
+ * @param isEqual Optional equality function to compare elements.
20
+ * Defaults to strict equality (===).
21
+ * @return The shortest common supersequence of X and Y.
22
+ */
23
+ export function shortestCommonSupersequence(X, Y, isEqual = (a, b) => a === b) {
24
+ const m = X.length;
25
+ const n = Y.length;
26
+
27
+ // Create a 2D dp table where dp[i][j] is the SCS for X[0..i-1] and Y[0..j-1].
28
+ const dp = Array.from({
29
+ length: m + 1
30
+ }, () => Array(n + 1).fill(null));
31
+
32
+ // Base cases: one of the sequences is empty.
33
+ for (let i = 0; i <= m; i++) {
34
+ dp[i][0] = X.slice(0, i);
35
+ }
36
+ for (let j = 0; j <= n; j++) {
37
+ dp[0][j] = Y.slice(0, j);
38
+ }
39
+
40
+ // Fill in the dp table.
41
+ for (let i = 1; i <= m; i++) {
42
+ for (let j = 1; j <= n; j++) {
43
+ if (isEqual(X[i - 1], Y[j - 1])) {
44
+ // When X[i-1] equals Y[j-1], use the reference from X.
45
+ dp[i][j] = dp[i - 1][j - 1].concat(X[i - 1]);
46
+ } else {
47
+ // Choose the shorter option between appending X[i-1] or Y[j-1].
48
+ const option1 = dp[i - 1][j].concat(X[i - 1]);
49
+ const option2 = dp[i][j - 1].concat(Y[j - 1]);
50
+ dp[i][j] = option1.length <= option2.length ? option1 : option2;
51
+ }
52
+ }
53
+ }
54
+ return dp[m][n];
55
+ }
56
+ //# sourceMappingURL=scs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["shortestCommonSupersequence","X","Y","isEqual","a","b","m","length","n","dp","Array","from","fill","i","slice","j","concat","option1","option2"],"sources":["@wordpress/interactivity-router/src/assets/scs.ts"],"sourcesContent":["/**\n * Calculates the Shortest Common Supersequence (SCS) of two sequences.\n *\n * A supersequence is a sequence that contains both input sequences as subsequences.\n * The shortest common supersequence is the shortest possible such sequence.\n *\n * This implementation uses dynamic programming with a time complexity of O(mn)\n * and space complexity of O(mn), where m and n are the lengths of sequences X and Y.\n *\n * @example\n * ```ts\n * const seq1 = [1, 3, 5];\n * const seq2 = [2, 3, 4];\n * const scs = shortestCommonSupersequence(seq1, seq2); // [1, 2, 3, 4, 5]\n * ```\n *\n * @param X The first sequence.\n * @param Y The second sequence.\n * @param isEqual Optional equality function to compare elements.\n * Defaults to strict equality (===).\n * @return The shortest common supersequence of X and Y.\n */\nexport function shortestCommonSupersequence< E = unknown >(\n\tX: E[],\n\tY: E[],\n\tisEqual = ( a: E, b: E ) => a === b\n) {\n\tconst m = X.length;\n\tconst n = Y.length;\n\n\t// Create a 2D dp table where dp[i][j] is the SCS for X[0..i-1] and Y[0..j-1].\n\tconst dp: E[][][] = Array.from( { length: m + 1 }, () =>\n\t\tArray( n + 1 ).fill( null )\n\t);\n\n\t// Base cases: one of the sequences is empty.\n\tfor ( let i = 0; i <= m; i++ ) {\n\t\tdp[ i ][ 0 ] = X.slice( 0, i );\n\t}\n\tfor ( let j = 0; j <= n; j++ ) {\n\t\tdp[ 0 ][ j ] = Y.slice( 0, j );\n\t}\n\n\t// Fill in the dp table.\n\tfor ( let i = 1; i <= m; i++ ) {\n\t\tfor ( let j = 1; j <= n; j++ ) {\n\t\t\tif ( isEqual( X[ i - 1 ], Y[ j - 1 ] ) ) {\n\t\t\t\t// When X[i-1] equals Y[j-1], use the reference from X.\n\t\t\t\tdp[ i ][ j ] = dp[ i - 1 ][ j - 1 ].concat( X[ i - 1 ] );\n\t\t\t} else {\n\t\t\t\t// Choose the shorter option between appending X[i-1] or Y[j-1].\n\t\t\t\tconst option1 = dp[ i - 1 ][ j ].concat( X[ i - 1 ] );\n\t\t\t\tconst option2 = dp[ i ][ j - 1 ].concat( Y[ j - 1 ] );\n\t\t\t\tdp[ i ][ j ] =\n\t\t\t\t\toption1.length <= option2.length ? option1 : option2;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn dp[ m ][ n ];\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASA,2BAA2BA,CAC1CC,CAAM,EACNC,CAAM,EACNC,OAAO,GAAGA,CAAEC,CAAI,EAAEC,CAAI,KAAMD,CAAC,KAAKC,CAAC,EAClC;EACD,MAAMC,CAAC,GAAGL,CAAC,CAACM,MAAM;EAClB,MAAMC,CAAC,GAAGN,CAAC,CAACK,MAAM;;EAElB;EACA,MAAME,EAAW,GAAGC,KAAK,CAACC,IAAI,CAAE;IAAEJ,MAAM,EAAED,CAAC,GAAG;EAAE,CAAC,EAAE,MAClDI,KAAK,CAAEF,CAAC,GAAG,CAAE,CAAC,CAACI,IAAI,CAAE,IAAK,CAC3B,CAAC;;EAED;EACA,KAAM,IAAIC,CAAC,GAAG,CAAC,EAAEA,CAAC,IAAIP,CAAC,EAAEO,CAAC,EAAE,EAAG;IAC9BJ,EAAE,CAAEI,CAAC,CAAE,CAAE,CAAC,CAAE,GAAGZ,CAAC,CAACa,KAAK,CAAE,CAAC,EAAED,CAAE,CAAC;EAC/B;EACA,KAAM,IAAIE,CAAC,GAAG,CAAC,EAAEA,CAAC,IAAIP,CAAC,EAAEO,CAAC,EAAE,EAAG;IAC9BN,EAAE,CAAE,CAAC,CAAE,CAAEM,CAAC,CAAE,GAAGb,CAAC,CAACY,KAAK,CAAE,CAAC,EAAEC,CAAE,CAAC;EAC/B;;EAEA;EACA,KAAM,IAAIF,CAAC,GAAG,CAAC,EAAEA,CAAC,IAAIP,CAAC,EAAEO,CAAC,EAAE,EAAG;IAC9B,KAAM,IAAIE,CAAC,GAAG,CAAC,EAAEA,CAAC,IAAIP,CAAC,EAAEO,CAAC,EAAE,EAAG;MAC9B,IAAKZ,OAAO,CAAEF,CAAC,CAAEY,CAAC,GAAG,CAAC,CAAE,EAAEX,CAAC,CAAEa,CAAC,GAAG,CAAC,CAAG,CAAC,EAAG;QACxC;QACAN,EAAE,CAAEI,CAAC,CAAE,CAAEE,CAAC,CAAE,GAAGN,EAAE,CAAEI,CAAC,GAAG,CAAC,CAAE,CAAEE,CAAC,GAAG,CAAC,CAAE,CAACC,MAAM,CAAEf,CAAC,CAAEY,CAAC,GAAG,CAAC,CAAG,CAAC;MACzD,CAAC,MAAM;QACN;QACA,MAAMI,OAAO,GAAGR,EAAE,CAAEI,CAAC,GAAG,CAAC,CAAE,CAAEE,CAAC,CAAE,CAACC,MAAM,CAAEf,CAAC,CAAEY,CAAC,GAAG,CAAC,CAAG,CAAC;QACrD,MAAMK,OAAO,GAAGT,EAAE,CAAEI,CAAC,CAAE,CAAEE,CAAC,GAAG,CAAC,CAAE,CAACC,MAAM,CAAEd,CAAC,CAAEa,CAAC,GAAG,CAAC,CAAG,CAAC;QACrDN,EAAE,CAAEI,CAAC,CAAE,CAAEE,CAAC,CAAE,GACXE,OAAO,CAACV,MAAM,IAAIW,OAAO,CAACX,MAAM,GAAGU,OAAO,GAAGC,OAAO;MACtD;IACD;EACD;EAEA,OAAOT,EAAE,CAAEH,CAAC,CAAE,CAAEE,CAAC,CAAE;AACpB","ignoreList":[]}
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import { shortestCommonSupersequence } from './scs';
5
+ /**
6
+ * Compares the passed style or link elements to check if they can be
7
+ * considered equal.
8
+ *
9
+ * @param a `<style>` or `<link>` element.
10
+ * @param b `<style>` or `<link>` element.
11
+ * @return Whether they are considered equal.
12
+ */
13
+ const areNodesEqual = (a, b) => a.isEqualNode(b);
14
+
15
+ /**
16
+ * Normalizes the passed style or link element, reverting the changes
17
+ * made by {@link prepareStylePromise|`prepareStylePromise`} to the
18
+ * `data-original-media` and `media`.
19
+ *
20
+ * @example
21
+ * The following elements should be normalized to the same element:
22
+ * ```html
23
+ * <link rel="stylesheet" src="./assets/styles.css">
24
+ * <link rel="stylesheet" src="./assets/styles.css" media="all">
25
+ * <link rel="stylesheet" src="./assets/styles.css" media="preload">
26
+ * <link rel="stylesheet" src="./assets/styles.css" media="preload" data-original-media="all">
27
+ * ```
28
+ *
29
+ * @param element `<style>` or `<link>` element.
30
+ * @return Normalized node.
31
+ */
32
+ export const normalizeMedia = element => {
33
+ element = element.cloneNode(true);
34
+ const media = element.media;
35
+ const {
36
+ originalMedia
37
+ } = element.dataset;
38
+ if (media === 'preload') {
39
+ element.media = originalMedia || 'all';
40
+ element.removeAttribute('data-original-media');
41
+ } else if (!element.media) {
42
+ element.media = 'all';
43
+ }
44
+ return element;
45
+ };
46
+
47
+ /**
48
+ * Adds the minimum style elements from Y around those in X using a
49
+ * shortest common supersequence algorithm, returning a list of
50
+ * promises for all the elements in Y.
51
+ *
52
+ * If X is empty, it appends all elements in Y to the passed parent
53
+ * element or to `document.head` instead.
54
+ *
55
+ * The returned promises resolve once the corresponding style element
56
+ * is loaded and ready. Those elements that are also in X return a
57
+ * cached promise.
58
+ *
59
+ * The algorithm ensures that the final style elements present in the
60
+ * document (or the passed `parent` element) are in the correct order
61
+ * and they are included in either X or Y.
62
+ *
63
+ * @param X Base list of style elements.
64
+ * @param Y List of style elements.
65
+ * @param parent Optional parent element to append to the new style elements.
66
+ * @return List of promises that resolve once the elements in Y are ready.
67
+ */
68
+ export function updateStylesWithSCS(X, Y, parent = window.document.head) {
69
+ if (X.length === 0) {
70
+ return Y.map(element => {
71
+ const promise = prepareStylePromise(element);
72
+ parent.appendChild(element);
73
+ return promise;
74
+ });
75
+ }
76
+
77
+ // Create normalized arrays for comparison.
78
+ const xNormalized = X.map(normalizeMedia);
79
+ const yNormalized = Y.map(normalizeMedia);
80
+
81
+ // The `scs` array contains normalized elements.
82
+ const scs = shortestCommonSupersequence(xNormalized, yNormalized, areNodesEqual);
83
+ const xLength = X.length;
84
+ const yLength = Y.length;
85
+ const promises = [];
86
+ let last = X[xLength - 1];
87
+ let xIndex = 0;
88
+ let yIndex = 0;
89
+ for (const scsElement of scs) {
90
+ // Actual elements that will end up in the DOM.
91
+ const xElement = X[xIndex];
92
+ const yElement = Y[yIndex];
93
+ // Normalized elements for comparison.
94
+ const xNormEl = xNormalized[xIndex];
95
+ const yNormEl = yNormalized[yIndex];
96
+ if (xIndex < xLength && areNodesEqual(xNormEl, scsElement)) {
97
+ if (yIndex < yLength && areNodesEqual(yNormEl, scsElement)) {
98
+ promises.push(prepareStylePromise(xElement));
99
+ yIndex++;
100
+ }
101
+ xIndex++;
102
+ } else {
103
+ promises.push(prepareStylePromise(yElement));
104
+ if (xIndex < xLength) {
105
+ xElement.before(yElement);
106
+ } else {
107
+ last.after(yElement);
108
+ last = yElement;
109
+ }
110
+ yIndex++;
111
+ }
112
+ }
113
+ return promises;
114
+ }
115
+
116
+ /**
117
+ * Cache of promises per style elements.
118
+ *
119
+ * Each style element has their own associated `Promise` that resolves
120
+ * once the element has been loaded and is ready.
121
+ */
122
+ const stylePromiseCache = new WeakMap();
123
+
124
+ /**
125
+ * Prepares and returns the corresponding `Promise` for the passed style
126
+ * element.
127
+ *
128
+ * It returns the cached promise if it exists. Otherwise, constructs
129
+ * a `Promise` that resolves once the element has finished loading.
130
+ *
131
+ * For those elements that are not in the DOM yet, this function
132
+ * injects a `media="preload"` attribute to the passed element so the
133
+ * style is loaded without applying any styles to the document.
134
+ *
135
+ * @param element Style element.
136
+ * @return The associated `Promise` to the passed element.
137
+ */
138
+ const prepareStylePromise = element => {
139
+ if (stylePromiseCache.has(element)) {
140
+ return stylePromiseCache.get(element);
141
+ }
142
+
143
+ // When the element exists in the main document and its media attribute
144
+ // is not "preload", that means the element comes from the initial page.
145
+ // The `media` attribute doesn't need to be handled in this case.
146
+ if (window.document.contains(element) && element.media !== 'preload') {
147
+ const promise = Promise.resolve(element);
148
+ stylePromiseCache.set(element, promise);
149
+ return promise;
150
+ }
151
+ if (element.hasAttribute('media') && element.media !== 'all') {
152
+ element.dataset.originalMedia = element.media;
153
+ }
154
+ element.media = 'preload';
155
+ if (element instanceof HTMLStyleElement) {
156
+ const promise = Promise.resolve(element);
157
+ stylePromiseCache.set(element, promise);
158
+ return promise;
159
+ }
160
+ const promise = new Promise((resolve, reject) => {
161
+ element.addEventListener('load', () => resolve(element));
162
+ element.addEventListener('error', event => {
163
+ const {
164
+ href
165
+ } = event.target;
166
+ reject(Error(`The style sheet with the following URL failed to load: ${href}`));
167
+ });
168
+ });
169
+ stylePromiseCache.set(element, promise);
170
+ return promise;
171
+ };
172
+
173
+ /**
174
+ * Cache of style promise lists per URL.
175
+ *
176
+ * It contains the list of style elements associated to the page with the
177
+ * passed URL. The original order is preserved to respect the CSS cascade.
178
+ *
179
+ * Each included promise resolves when the associated style element is ready.
180
+ */
181
+ const styleSheetCache = new Map();
182
+
183
+ /**
184
+ * Prepares all style elements contained in the passed document.
185
+ *
186
+ * This function calls {@link updateStylesWithSCS|`updateStylesWithSCS`}
187
+ * to insert only the minimum amount of style elements into the DOM, so
188
+ * those present in the passed document end up in the DOM while the order
189
+ * is respected.
190
+ *
191
+ * New appended style elements contain a `media=preload` attribute to
192
+ * make them effectively disabled until they are applied with the
193
+ * {@link applyStyles|`applyStyles`} function.
194
+ *
195
+ * @param doc Document instance.
196
+ * @param url URL for the passed document.
197
+ * @return A list of promises for each style element in the passed document.
198
+ */
199
+ export const preloadStyles = (doc, url) => {
200
+ if (!styleSheetCache.has(url)) {
201
+ const currentStyleElements = Array.from(window.document.querySelectorAll('style,link[rel=stylesheet]'));
202
+ const newStyleElements = Array.from(doc.querySelectorAll('style,link[rel=stylesheet]'));
203
+
204
+ // Set styles in order.
205
+ const stylePromises = updateStylesWithSCS(currentStyleElements, newStyleElements);
206
+ styleSheetCache.set(url, stylePromises);
207
+ }
208
+ return styleSheetCache.get(url);
209
+ };
210
+
211
+ /**
212
+ * Traverses all style elements in the DOM, enabling only those included
213
+ * in the passed list and disabling the others.
214
+ *
215
+ * If the style element has the `data-original-media` attribute, the
216
+ * original `media` value is restored.
217
+ *
218
+ * @param styles List of style elements to apply.
219
+ */
220
+ export const applyStyles = styles => {
221
+ window.document.querySelectorAll('style,link[rel=stylesheet]').forEach(el => {
222
+ if (el.sheet) {
223
+ if (styles.includes(el)) {
224
+ const {
225
+ originalMedia = 'all'
226
+ } = el.dataset;
227
+ el.sheet.media.mediaText = originalMedia;
228
+ el.sheet.disabled = false;
229
+ } else {
230
+ el.sheet.disabled = true;
231
+ }
232
+ }
233
+ });
234
+ };
235
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["shortestCommonSupersequence","areNodesEqual","a","b","isEqualNode","normalizeMedia","element","cloneNode","media","originalMedia","dataset","removeAttribute","updateStylesWithSCS","X","Y","parent","window","document","head","length","map","promise","prepareStylePromise","appendChild","xNormalized","yNormalized","scs","xLength","yLength","promises","last","xIndex","yIndex","scsElement","xElement","yElement","xNormEl","yNormEl","push","before","after","stylePromiseCache","WeakMap","has","get","contains","Promise","resolve","set","hasAttribute","HTMLStyleElement","reject","addEventListener","event","href","target","Error","styleSheetCache","Map","preloadStyles","doc","url","currentStyleElements","Array","from","querySelectorAll","newStyleElements","stylePromises","applyStyles","styles","forEach","el","sheet","includes","mediaText","disabled"],"sources":["@wordpress/interactivity-router/src/assets/styles.ts"],"sourcesContent":["/**\n * Internal dependencies\n */\nimport { shortestCommonSupersequence } from './scs';\n\nexport type StyleElement = HTMLLinkElement | HTMLStyleElement;\n\n/**\n * Compares the passed style or link elements to check if they can be\n * considered equal.\n *\n * @param a `<style>` or `<link>` element.\n * @param b `<style>` or `<link>` element.\n * @return Whether they are considered equal.\n */\nconst areNodesEqual = ( a: StyleElement, b: StyleElement ): boolean =>\n\ta.isEqualNode( b );\n\n/**\n * Normalizes the passed style or link element, reverting the changes\n * made by {@link prepareStylePromise|`prepareStylePromise`} to the\n * `data-original-media` and `media`.\n *\n * @example\n * The following elements should be normalized to the same element:\n * ```html\n * <link rel=\"stylesheet\" src=\"./assets/styles.css\">\n * <link rel=\"stylesheet\" src=\"./assets/styles.css\" media=\"all\">\n * <link rel=\"stylesheet\" src=\"./assets/styles.css\" media=\"preload\">\n * <link rel=\"stylesheet\" src=\"./assets/styles.css\" media=\"preload\" data-original-media=\"all\">\n * ```\n *\n * @param element `<style>` or `<link>` element.\n * @return Normalized node.\n */\nexport const normalizeMedia = ( element: StyleElement ): StyleElement => {\n\telement = element.cloneNode( true ) as StyleElement;\n\tconst media = element.media;\n\tconst { originalMedia } = element.dataset;\n\n\tif ( media === 'preload' ) {\n\t\telement.media = originalMedia || 'all';\n\t\telement.removeAttribute( 'data-original-media' );\n\t} else if ( ! element.media ) {\n\t\telement.media = 'all';\n\t}\n\treturn element;\n};\n\n/**\n * Adds the minimum style elements from Y around those in X using a\n * shortest common supersequence algorithm, returning a list of\n * promises for all the elements in Y.\n *\n * If X is empty, it appends all elements in Y to the passed parent\n * element or to `document.head` instead.\n *\n * The returned promises resolve once the corresponding style element\n * is loaded and ready. Those elements that are also in X return a\n * cached promise.\n *\n * The algorithm ensures that the final style elements present in the\n * document (or the passed `parent` element) are in the correct order\n * and they are included in either X or Y.\n *\n * @param X Base list of style elements.\n * @param Y List of style elements.\n * @param parent Optional parent element to append to the new style elements.\n * @return List of promises that resolve once the elements in Y are ready.\n */\nexport function updateStylesWithSCS(\n\tX: StyleElement[],\n\tY: StyleElement[],\n\tparent: Element = window.document.head\n) {\n\tif ( X.length === 0 ) {\n\t\treturn Y.map( ( element ) => {\n\t\t\tconst promise = prepareStylePromise( element );\n\t\t\tparent.appendChild( element );\n\t\t\treturn promise;\n\t\t} );\n\t}\n\n\t// Create normalized arrays for comparison.\n\tconst xNormalized = X.map( normalizeMedia );\n\tconst yNormalized = Y.map( normalizeMedia );\n\n\t// The `scs` array contains normalized elements.\n\tconst scs = shortestCommonSupersequence(\n\t\txNormalized,\n\t\tyNormalized,\n\t\tareNodesEqual\n\t);\n\tconst xLength = X.length;\n\tconst yLength = Y.length;\n\tconst promises = [];\n\tlet last = X[ xLength - 1 ];\n\tlet xIndex = 0;\n\tlet yIndex = 0;\n\n\tfor ( const scsElement of scs ) {\n\t\t// Actual elements that will end up in the DOM.\n\t\tconst xElement = X[ xIndex ];\n\t\tconst yElement = Y[ yIndex ];\n\t\t// Normalized elements for comparison.\n\t\tconst xNormEl = xNormalized[ xIndex ];\n\t\tconst yNormEl = yNormalized[ yIndex ];\n\t\tif ( xIndex < xLength && areNodesEqual( xNormEl, scsElement ) ) {\n\t\t\tif ( yIndex < yLength && areNodesEqual( yNormEl, scsElement ) ) {\n\t\t\t\tpromises.push( prepareStylePromise( xElement ) );\n\t\t\t\tyIndex++;\n\t\t\t}\n\t\t\txIndex++;\n\t\t} else {\n\t\t\tpromises.push( prepareStylePromise( yElement ) );\n\t\t\tif ( xIndex < xLength ) {\n\t\t\t\txElement.before( yElement );\n\t\t\t} else {\n\t\t\t\tlast.after( yElement );\n\t\t\t\tlast = yElement;\n\t\t\t}\n\t\t\tyIndex++;\n\t\t}\n\t}\n\n\treturn promises;\n}\n\n/**\n * Cache of promises per style elements.\n *\n * Each style element has their own associated `Promise` that resolves\n * once the element has been loaded and is ready.\n */\nconst stylePromiseCache = new WeakMap<\n\tStyleElement,\n\tPromise< StyleElement >\n>();\n\n/**\n * Prepares and returns the corresponding `Promise` for the passed style\n * element.\n *\n * It returns the cached promise if it exists. Otherwise, constructs\n * a `Promise` that resolves once the element has finished loading.\n *\n * For those elements that are not in the DOM yet, this function\n * injects a `media=\"preload\"` attribute to the passed element so the\n * style is loaded without applying any styles to the document.\n *\n * @param element Style element.\n * @return The associated `Promise` to the passed element.\n */\nconst prepareStylePromise = (\n\telement: StyleElement\n): Promise< StyleElement > => {\n\tif ( stylePromiseCache.has( element ) ) {\n\t\treturn stylePromiseCache.get( element );\n\t}\n\n\t// When the element exists in the main document and its media attribute\n\t// is not \"preload\", that means the element comes from the initial page.\n\t// The `media` attribute doesn't need to be handled in this case.\n\tif ( window.document.contains( element ) && element.media !== 'preload' ) {\n\t\tconst promise = Promise.resolve( element );\n\t\tstylePromiseCache.set( element, promise );\n\t\treturn promise;\n\t}\n\n\tif ( element.hasAttribute( 'media' ) && element.media !== 'all' ) {\n\t\telement.dataset.originalMedia = element.media;\n\t}\n\n\telement.media = 'preload';\n\n\tif ( element instanceof HTMLStyleElement ) {\n\t\tconst promise = Promise.resolve( element );\n\t\tstylePromiseCache.set( element, promise );\n\t\treturn promise;\n\t}\n\n\tconst promise = new Promise< HTMLLinkElement >( ( resolve, reject ) => {\n\t\telement.addEventListener( 'load', () => resolve( element ) );\n\t\telement.addEventListener( 'error', ( event ) => {\n\t\t\tconst { href } = event.target as HTMLLinkElement;\n\t\t\treject(\n\t\t\t\tError(\n\t\t\t\t\t`The style sheet with the following URL failed to load: ${ href }`\n\t\t\t\t)\n\t\t\t);\n\t\t} );\n\t} );\n\n\tstylePromiseCache.set( element, promise );\n\treturn promise;\n};\n\n/**\n * Cache of style promise lists per URL.\n *\n * It contains the list of style elements associated to the page with the\n * passed URL. The original order is preserved to respect the CSS cascade.\n *\n * Each included promise resolves when the associated style element is ready.\n */\nconst styleSheetCache = new Map< string, Promise< StyleElement >[] >();\n\n/**\n * Prepares all style elements contained in the passed document.\n *\n * This function calls {@link updateStylesWithSCS|`updateStylesWithSCS`}\n * to insert only the minimum amount of style elements into the DOM, so\n * those present in the passed document end up in the DOM while the order\n * is respected.\n *\n * New appended style elements contain a `media=preload` attribute to\n * make them effectively disabled until they are applied with the\n * {@link applyStyles|`applyStyles`} function.\n *\n * @param doc Document instance.\n * @param url URL for the passed document.\n * @return A list of promises for each style element in the passed document.\n */\nexport const preloadStyles = (\n\tdoc: Document,\n\turl: string\n): Promise< StyleElement >[] => {\n\tif ( ! styleSheetCache.has( url ) ) {\n\t\tconst currentStyleElements = Array.from(\n\t\t\twindow.document.querySelectorAll< StyleElement >(\n\t\t\t\t'style,link[rel=stylesheet]'\n\t\t\t)\n\t\t);\n\t\tconst newStyleElements = Array.from(\n\t\t\tdoc.querySelectorAll< StyleElement >( 'style,link[rel=stylesheet]' )\n\t\t);\n\n\t\t// Set styles in order.\n\t\tconst stylePromises = updateStylesWithSCS(\n\t\t\tcurrentStyleElements,\n\t\t\tnewStyleElements\n\t\t);\n\n\t\tstyleSheetCache.set( url, stylePromises );\n\t}\n\treturn styleSheetCache.get( url );\n};\n\n/**\n * Traverses all style elements in the DOM, enabling only those included\n * in the passed list and disabling the others.\n *\n * If the style element has the `data-original-media` attribute, the\n * original `media` value is restored.\n *\n * @param styles List of style elements to apply.\n */\nexport const applyStyles = ( styles: StyleElement[] ) => {\n\twindow.document\n\t\t.querySelectorAll( 'style,link[rel=stylesheet]' )\n\t\t.forEach( ( el: HTMLLinkElement | HTMLStyleElement ) => {\n\t\t\tif ( el.sheet ) {\n\t\t\t\tif ( styles.includes( el ) ) {\n\t\t\t\t\tconst { originalMedia = 'all' } = el.dataset;\n\t\t\t\t\tel.sheet.media.mediaText = originalMedia;\n\t\t\t\t\tel.sheet.disabled = false;\n\t\t\t\t} else {\n\t\t\t\t\tel.sheet.disabled = true;\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n};\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,39 @@
1
+ /* wp:polyfill */
2
+ // Check if the link is valid for client-side navigation.
3
+ const isValidLink = ref => ref && ref instanceof window.HTMLAnchorElement && ref.href && (!ref.target || ref.target === '_self') && ref.origin === window.location.origin && !ref.pathname.startsWith('/wp-admin') && !ref.pathname.startsWith('/wp-login.php') && !ref.getAttribute('href').startsWith('#') && !new URL(ref.href).searchParams.has('_wpnonce');
4
+
5
+ // Check if the event is valid for client-side navigation.
6
+ const isValidEvent = event => event && event.button === 0 &&
7
+ // Left clicks only.
8
+ !event.metaKey &&
9
+ // Open in new tab (Mac).
10
+ !event.ctrlKey &&
11
+ // Open in new tab (Windows).
12
+ !event.altKey &&
13
+ // Download.
14
+ !event.shiftKey && !event.defaultPrevented;
15
+
16
+ // Navigate on click.
17
+ document.addEventListener('click', async event => {
18
+ const ref = event.target.closest('a');
19
+ if (isValidLink(ref) && isValidEvent(event)) {
20
+ event.preventDefault();
21
+ const {
22
+ actions
23
+ } = await import('@wordpress/interactivity-router');
24
+ actions.navigate(ref.href);
25
+ }
26
+ }, true);
27
+ // Prefetch on hover.
28
+ document.addEventListener('mouseenter', async event => {
29
+ if (event.target?.nodeName === 'A') {
30
+ const ref = event.target.closest('a');
31
+ if (isValidLink(ref) && isValidEvent(event)) {
32
+ const {
33
+ actions
34
+ } = await import('@wordpress/interactivity-router');
35
+ actions.prefetch(ref.href);
36
+ }
37
+ }
38
+ }, true);
39
+ //# sourceMappingURL=full-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["isValidLink","ref","window","HTMLAnchorElement","href","target","origin","location","pathname","startsWith","getAttribute","URL","searchParams","has","isValidEvent","event","button","metaKey","ctrlKey","altKey","shiftKey","defaultPrevented","document","addEventListener","closest","preventDefault","actions","navigate","nodeName","prefetch"],"sources":["@wordpress/interactivity-router/src/full-page.ts"],"sourcesContent":["// Check if the link is valid for client-side navigation.\nconst isValidLink = ( ref: HTMLAnchorElement ) =>\n\tref &&\n\tref instanceof window.HTMLAnchorElement &&\n\tref.href &&\n\t( ! ref.target || ref.target === '_self' ) &&\n\tref.origin === window.location.origin &&\n\t! ref.pathname.startsWith( '/wp-admin' ) &&\n\t! ref.pathname.startsWith( '/wp-login.php' ) &&\n\t! ref.getAttribute( 'href' ).startsWith( '#' ) &&\n\t! new URL( ref.href ).searchParams.has( '_wpnonce' );\n\n// Check if the event is valid for client-side navigation.\nconst isValidEvent = ( event: MouseEvent ) =>\n\tevent &&\n\tevent.button === 0 && // Left clicks only.\n\t! event.metaKey && // Open in new tab (Mac).\n\t! event.ctrlKey && // Open in new tab (Windows).\n\t! event.altKey && // Download.\n\t! event.shiftKey &&\n\t! event.defaultPrevented;\n\n// Navigate on click.\ndocument.addEventListener(\n\t'click',\n\tasync ( event ) => {\n\t\tconst ref = ( event.target as Element ).closest( 'a' );\n\t\tif ( isValidLink( ref ) && isValidEvent( event ) ) {\n\t\t\tevent.preventDefault();\n\t\t\tconst { actions } = await import(\n\t\t\t\t'@wordpress/interactivity-router'\n\t\t\t);\n\t\t\tactions.navigate( ref.href );\n\t\t}\n\t},\n\ttrue\n);\n// Prefetch on hover.\ndocument.addEventListener(\n\t'mouseenter',\n\tasync ( event ) => {\n\t\tif ( ( event.target as Element )?.nodeName === 'A' ) {\n\t\t\tconst ref = ( event.target as Element ).closest( 'a' );\n\t\t\tif ( isValidLink( ref ) && isValidEvent( event ) ) {\n\t\t\t\tconst { actions } = await import(\n\t\t\t\t\t'@wordpress/interactivity-router'\n\t\t\t\t);\n\t\t\t\tactions.prefetch( ref.href );\n\t\t\t}\n\t\t}\n\t},\n\ttrue\n);\n"],"mappings":";AAAA;AACA,MAAMA,WAAW,GAAKC,GAAsB,IAC3CA,GAAG,IACHA,GAAG,YAAYC,MAAM,CAACC,iBAAiB,IACvCF,GAAG,CAACG,IAAI,KACN,CAAEH,GAAG,CAACI,MAAM,IAAIJ,GAAG,CAACI,MAAM,KAAK,OAAO,CAAE,IAC1CJ,GAAG,CAACK,MAAM,KAAKJ,MAAM,CAACK,QAAQ,CAACD,MAAM,IACrC,CAAEL,GAAG,CAACO,QAAQ,CAACC,UAAU,CAAE,WAAY,CAAC,IACxC,CAAER,GAAG,CAACO,QAAQ,CAACC,UAAU,CAAE,eAAgB,CAAC,IAC5C,CAAER,GAAG,CAACS,YAAY,CAAE,MAAO,CAAC,CAACD,UAAU,CAAE,GAAI,CAAC,IAC9C,CAAE,IAAIE,GAAG,CAAEV,GAAG,CAACG,IAAK,CAAC,CAACQ,YAAY,CAACC,GAAG,CAAE,UAAW,CAAC;;AAErD;AACA,MAAMC,YAAY,GAAKC,KAAiB,IACvCA,KAAK,IACLA,KAAK,CAACC,MAAM,KAAK,CAAC;AAAI;AACtB,CAAED,KAAK,CAACE,OAAO;AAAI;AACnB,CAAEF,KAAK,CAACG,OAAO;AAAI;AACnB,CAAEH,KAAK,CAACI,MAAM;AAAI;AAClB,CAAEJ,KAAK,CAACK,QAAQ,IAChB,CAAEL,KAAK,CAACM,gBAAgB;;AAEzB;AACAC,QAAQ,CAACC,gBAAgB,CACxB,OAAO,EACP,MAAQR,KAAK,IAAM;EAClB,MAAMd,GAAG,GAAKc,KAAK,CAACV,MAAM,CAAcmB,OAAO,CAAE,GAAI,CAAC;EACtD,IAAKxB,WAAW,CAAEC,GAAI,CAAC,IAAIa,YAAY,CAAEC,KAAM,CAAC,EAAG;IAClDA,KAAK,CAACU,cAAc,CAAC,CAAC;IACtB,MAAM;MAAEC;IAAQ,CAAC,GAAG,MAAM,MAAM,CAC/B,iCACD,CAAC;IACDA,OAAO,CAACC,QAAQ,CAAE1B,GAAG,CAACG,IAAK,CAAC;EAC7B;AACD,CAAC,EACD,IACD,CAAC;AACD;AACAkB,QAAQ,CAACC,gBAAgB,CACxB,YAAY,EACZ,MAAQR,KAAK,IAAM;EAClB,IAAOA,KAAK,CAACV,MAAM,EAAeuB,QAAQ,KAAK,GAAG,EAAG;IACpD,MAAM3B,GAAG,GAAKc,KAAK,CAACV,MAAM,CAAcmB,OAAO,CAAE,GAAI,CAAC;IACtD,IAAKxB,WAAW,CAAEC,GAAI,CAAC,IAAIa,YAAY,CAAEC,KAAM,CAAC,EAAG;MAClD,MAAM;QAAEW;MAAQ,CAAC,GAAG,MAAM,MAAM,CAC/B,iCACD,CAAC;MACDA,OAAO,CAACG,QAAQ,CAAE5B,GAAG,CAACG,IAAK,CAAC;IAC7B;EACD;AACD,CAAC,EACD,IACD,CAAC","ignoreList":[]}
@@ -3,6 +3,12 @@
3
3
  * WordPress dependencies
4
4
  */
5
5
  import { store, privateApis, getConfig } from '@wordpress/interactivity';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { preloadStyles, applyStyles } from './assets/styles';
11
+ import { preloadScriptModules, importScriptModules, markScriptModuleAsResolved } from './assets/script-modules';
6
12
  const {
7
13
  directivePrefix,
8
14
  getRegionRootFragment,
@@ -13,10 +19,9 @@ const {
13
19
  populateServerData,
14
20
  batch
15
21
  } = privateApis('I acknowledge that using private APIs means my theme or plugin will inevitably break in the next version of WordPress.');
16
- // Check if the navigation mode is full page or region based. The only supported
17
- // mode for now is 'regionBased'.
18
- const navigationMode = 'regionBased';
19
-
22
+ const regionAttr = `data-${directivePrefix}-router-region`;
23
+ const interactiveAttr = `data-${directivePrefix}-interactive`;
24
+ const regionsSelector = `[${interactiveAttr}][${regionAttr}]:not([${interactiveAttr}] [${interactiveAttr}])`;
20
25
  // The cache of visited and prefetched pages, stylesheets and scripts.
21
26
  const pages = new Map();
22
27
 
@@ -27,7 +32,40 @@ const getPagePath = url => {
27
32
  return u.pathname + u.search;
28
33
  };
29
34
 
30
- // Fetch a new page and convert it to a static virtual DOM.
35
+ /**
36
+ * Parses the given region's directive.
37
+ *
38
+ * @param region Region element.
39
+ * @return Data contained in the region directive value.
40
+ */
41
+ const parseRegionAttribute = region => {
42
+ const value = region.getAttribute(regionAttr);
43
+ try {
44
+ const {
45
+ id,
46
+ attachTo
47
+ } = JSON.parse(value);
48
+ return {
49
+ id,
50
+ attachTo
51
+ };
52
+ } catch (e) {
53
+ return {
54
+ id: value
55
+ };
56
+ }
57
+ };
58
+
59
+ /**
60
+ * Fetches and prepares a page from a given URL.
61
+ *
62
+ * @param url The URL of the page to fetch.
63
+ * @param options Options for the fetch operation.
64
+ * @param options.html Optional HTML content. If provided, the function will use
65
+ * this instead of fetching from the URL.
66
+ * @return A Promise that resolves to the prepared page, or false if
67
+ * there was an error during fetching or preparation.
68
+ */
31
69
  const fetchPage = async (url, {
32
70
  html
33
71
  }) => {
@@ -40,56 +78,115 @@ const fetchPage = async (url, {
40
78
  html = await res.text();
41
79
  }
42
80
  const dom = new window.DOMParser().parseFromString(html, 'text/html');
43
- return regionsToVdom(dom);
81
+ return await preparePage(url, dom);
44
82
  } catch (e) {
45
83
  return false;
46
84
  }
47
85
  };
48
86
 
49
- // Return an object with VDOM trees of those HTML regions marked with a
50
- // `router-region` directive.
51
- const regionsToVdom = async (dom, {
87
+ /**
88
+ * Processes a DOM document to extract router regions and related resources.
89
+ *
90
+ * This function analyzes the provided DOM document and creates a virtual DOM
91
+ * representation of all HTML regions marked with a `router-region` directive.
92
+ * It also extracts and preloads associated styles and scripts to prepare for
93
+ * rendering the page.
94
+ *
95
+ * @param url The URL associated with the page, used for asset
96
+ * loading and caching.
97
+ * @param dom The DOM document to process.
98
+ * @param vdomParams Optional parameters for virtual DOM processing.
99
+ * @param vdomParams.vdom An optional existing virtual DOM cache to check for
100
+ * regions. If a region exists in this cache, it will be
101
+ * reused instead of creating a new vDOM representation.
102
+ * @return A Promise that resolves to a {@link Page} object
103
+ * containing the virtual DOM for all router regions,
104
+ * preloaded styles and scripts, page title, and initial
105
+ * server-rendered data.
106
+ */
107
+ const preparePage = async (url, dom, {
52
108
  vdom
53
109
  } = {}) => {
54
- const regions = {
55
- body: undefined
56
- };
57
- if (navigationMode === 'regionBased') {
58
- const attrName = `data-${directivePrefix}-router-region`;
59
- dom.querySelectorAll(`[${attrName}]`).forEach(region => {
60
- const id = region.getAttribute(attrName);
61
- regions[id] = vdom?.has(region) ? vdom.get(region) : toVdom(region);
62
- });
63
- }
110
+ const regions = {};
111
+ const regionsToAttach = {};
112
+ dom.querySelectorAll(regionsSelector).forEach(region => {
113
+ const {
114
+ id,
115
+ attachTo
116
+ } = parseRegionAttribute(region);
117
+ regions[id] = vdom?.has(region) ? vdom.get(region) : toVdom(region);
118
+ if (attachTo) {
119
+ regionsToAttach[id] = attachTo;
120
+ }
121
+ });
64
122
  const title = dom.querySelector('title')?.innerText;
65
123
  const initialData = parseServerData(dom);
124
+
125
+ // Wait for styles and modules to be ready.
126
+ const [styles, scriptModules] = await Promise.all([Promise.all(preloadStyles(dom, url)), Promise.all(preloadScriptModules(dom))]);
66
127
  return {
67
128
  regions,
129
+ regionsToAttach,
130
+ styles,
131
+ scriptModules,
68
132
  title,
69
- initialData
133
+ initialData,
134
+ url
70
135
  };
71
136
  };
72
137
 
73
- // Render all interactive regions contained in the given page.
74
- const renderRegions = async page => {
75
- if (navigationMode === 'regionBased') {
76
- const attrName = `data-${directivePrefix}-router-region`;
77
- batch(() => {
78
- populateServerData(page.initialData);
79
- document.querySelectorAll(`[${attrName}]`).forEach(region => {
80
- const id = region.getAttribute(attrName);
81
- const fragment = getRegionRootFragment(region);
82
- render(page.regions[id], fragment);
83
- });
138
+ /**
139
+ * Renders a page by applying styles, populating server data, rendering regions,
140
+ * and updating the document title.
141
+ *
142
+ * @param page The {@link Page} object to render.
143
+ */
144
+ const renderPage = page => {
145
+ applyStyles(page.styles);
146
+
147
+ // Clone regionsToAttach.
148
+ const regionsToAttach = {
149
+ ...page.regionsToAttach
150
+ };
151
+ batch(() => {
152
+ populateServerData(page.initialData);
153
+ document.querySelectorAll(regionsSelector).forEach(region => {
154
+ const {
155
+ id
156
+ } = parseRegionAttribute(region);
157
+ const fragment = getRegionRootFragment(region);
158
+ render(page.regions[id], fragment);
159
+ // If this is an attached region, remove it from the list.
160
+ delete regionsToAttach[id];
84
161
  });
85
- }
162
+
163
+ // Render unattached regions.
164
+ for (const id in regionsToAttach) {
165
+ const parent = document.querySelector(regionsToAttach[id]);
166
+
167
+ // Get the type from the vnode. If wrapped with Directives, get the
168
+ // original type from `props.type`.
169
+ const {
170
+ props,
171
+ type
172
+ } = page.regions[id];
173
+ const elementType = typeof type === 'function' ? props.type : type;
174
+
175
+ // Create an element with the obtained type where the region will be
176
+ // rendered. The type should match the one of the root vnode.
177
+ const region = document.createElement(elementType);
178
+ parent.appendChild(region);
179
+ const fragment = getRegionRootFragment(region);
180
+ render(page.regions[id], fragment);
181
+ }
182
+ });
86
183
  if (page.title) {
87
184
  document.title = page.title;
88
185
  }
89
186
  };
90
187
 
91
188
  /**
92
- * Load the given page forcing a full page reload.
189
+ * Loads the given page forcing a full page reload.
93
190
  *
94
191
  * The function returns a promise that won't resolve, useful to prevent any
95
192
  * potential feedback indicating that the navigation has finished while the new
@@ -109,7 +206,7 @@ window.addEventListener('popstate', async () => {
109
206
  const pagePath = getPagePath(window.location.href); // Remove hash.
110
207
  const page = pages.has(pagePath) && (await pages.get(pagePath));
111
208
  if (page) {
112
- await renderRegions(page);
209
+ renderPage(page);
113
210
  // Update the URL in the state.
114
211
  state.url = window.location.href;
115
212
  } else {
@@ -118,7 +215,10 @@ window.addEventListener('popstate', async () => {
118
215
  });
119
216
 
120
217
  // Initialize the router and cache the initial page using the initial vDOM.
121
- pages.set(getPagePath(window.location.href), Promise.resolve(regionsToVdom(document, {
218
+ window.document.querySelectorAll('script[type=module][src]').forEach(({
219
+ src
220
+ }) => markScriptModuleAsResolved(src));
221
+ pages.set(getPagePath(window.location.href), Promise.resolve(preparePage(getPagePath(window.location.href), document, {
122
222
  vdom: initialVdom
123
223
  })));
124
224
 
@@ -207,7 +307,8 @@ export const {
207
307
  return;
208
308
  }
209
309
  if (page && !page.initialData?.config?.['core/router']?.clientNavigationDisabled) {
210
- yield renderRegions(page);
310
+ yield importScriptModules(page.scriptModules);
311
+ renderPage(page);
211
312
  window.history[options.replace ? 'replaceState' : 'pushState']({}, '', href);
212
313
 
213
314
  // Update the URL in the state.
@@ -244,8 +345,10 @@ export const {
244
345
  * @param [options] Options object.
245
346
  * @param [options.force] Force fetching the URL again.
246
347
  * @param [options.html] HTML string to be used instead of fetching the requested URL.
348
+ *
349
+ * @return Promise that resolves once the page has been fetched.
247
350
  */
248
- prefetch(url, options = {}) {
351
+ *prefetch(url, options = {}) {
249
352
  const {
250
353
  clientNavigationDisabled
251
354
  } = getConfig();
@@ -258,6 +361,7 @@ export const {
258
361
  html: options.html
259
362
  }));
260
363
  }
364
+ yield pages.get(pagePath);
261
365
  }
262
366
  }
263
367
  });
@@ -266,7 +370,7 @@ export const {
266
370
  * Announces a message to screen readers.
267
371
  *
268
372
  * This is a wrapper around the `@wordpress/a11y` package's `speak` function. It handles importing
269
- * the package on demand and should be used instead of calling `ally.speak` direacly.
373
+ * the package on demand and should be used instead of calling `a11y.speak` directly.
270
374
  *
271
375
  * @param messageKey The message to be announced by assistive technologies.
272
376
  */