@wordpress/interactivity-router 2.24.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 (81) hide show
  1. package/CHANGELOG.md +19 -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 +141 -112
  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 +138 -107
  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 +160 -135
  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/build/head.js +0 -113
  75. package/build/head.js.map +0 -1
  76. package/build-module/head.js +0 -103
  77. package/build-module/head.js.map +0 -1
  78. package/build-types/head.d.ts +0 -24
  79. package/build-types/head.d.ts.map +0 -1
  80. package/src/head.ts +0 -126
  81. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,366 @@
1
+ /**
2
+ * This code is derived from the following projects:
3
+ *
4
+ * 1. dynamic-importmap (https://github.com/keller-mark/dynamic-importmap)
5
+ * 2. es-module-shims (https://github.com/guybedford/es-module-shims)
6
+ *
7
+ * The original implementation was created by Guy Bedford in es-module-shims,
8
+ * then adapted by Mark Keller in dynamic-importmap, and further modified
9
+ * for use in this project.
10
+ *
11
+ * Both projects are licensed under the MIT license.
12
+ *
13
+ * MIT License: https://opensource.org/licenses/MIT
14
+ */
15
+
16
+ /**
17
+ * External dependencies
18
+ */
19
+ import * as lexer from 'es-module-lexer';
20
+
21
+ /**
22
+ * Internal dependencies
23
+ */
24
+ import { fetchModule } from './fetch';
25
+ import { resolve } from './resolver';
26
+
27
+ export interface ModuleLoad {
28
+ url?: string; // original URL
29
+ responseUrl?: string; // response url
30
+ fetchPromise?: Promise< ModuleLoad >; // fetch promise
31
+ source?: string; // source code
32
+ linkPromise?: Promise< void >; // link-promise (dependency fetch)
33
+ analysis?: ReturnType< typeof lexer.parse >; // analysis ([ imports, exports, ... ])
34
+ deps?: ModuleLoad[]; // deps
35
+ blobUrl?: string; // blobUrl
36
+ shellUrl?: string; // shellUrl for circular references
37
+ }
38
+
39
+ export const initPromise = lexer.init;
40
+
41
+ /**
42
+ * Script element containing the initial page's import map.
43
+ */
44
+ const initialImportMapElement =
45
+ window.document.querySelector< HTMLScriptElement >(
46
+ 'script#wp-importmap[type=importmap]'
47
+ );
48
+
49
+ /**
50
+ * Data from the initial page's import map.
51
+ *
52
+ * Pages containing any of the imports present on the original page
53
+ * in their import maps should ignore them, as those imports would
54
+ * be handled natively.
55
+ */
56
+ export const initialImportMap = initialImportMapElement
57
+ ? JSON.parse( initialImportMapElement.text )
58
+ : { imports: {}, scopes: {} };
59
+
60
+ const skip = ( id ) => Object.keys( initialImportMap.imports ).includes( id );
61
+
62
+ const fetchCache: Record< string, Promise< ModuleLoad > > = {};
63
+ export const registry = {};
64
+
65
+ // Init registry with importamp content.
66
+ Object.keys( initialImportMap.imports ).forEach( ( id ) => {
67
+ registry[ id ] = {
68
+ blobUrl: id,
69
+ };
70
+ } );
71
+
72
+ async function loadAll( load: ModuleLoad, seen: Record< string, any > ) {
73
+ if ( load.blobUrl || seen[ load.url ] ) {
74
+ return;
75
+ }
76
+ seen[ load.url ] = 1;
77
+ await load.linkPromise;
78
+ await Promise.all( load.deps.map( ( dep ) => loadAll( dep, seen ) ) );
79
+ }
80
+
81
+ function urlJsString( url: string ) {
82
+ return `'${ url.replace( /'/g, "\\'" ) }'`;
83
+ }
84
+
85
+ const createBlob = ( source: string, type = 'text/javascript' ) =>
86
+ URL.createObjectURL( new Blob( [ source ], { type } ) );
87
+
88
+ function resolveDeps( load: ModuleLoad, seen: Record< string, any > ) {
89
+ if ( load.blobUrl || ! seen[ load.url ] ) {
90
+ return;
91
+ }
92
+ seen[ load.url ] = 0;
93
+
94
+ for ( const dep of load.deps ) {
95
+ resolveDeps( dep, seen );
96
+ }
97
+
98
+ const [ imports, exports ] = load.analysis;
99
+ const source = load.source;
100
+
101
+ let resolvedSource = '';
102
+
103
+ if ( ! imports.length ) {
104
+ resolvedSource += source;
105
+ } else {
106
+ let lastIndex = 0;
107
+ let depIndex = 0;
108
+ const dynamicImportEndStack = [];
109
+
110
+ function pushStringTo( originalIndex: number ) {
111
+ while (
112
+ dynamicImportEndStack.length &&
113
+ dynamicImportEndStack[ dynamicImportEndStack.length - 1 ] <
114
+ originalIndex
115
+ ) {
116
+ const dynamicImportEnd = dynamicImportEndStack.pop();
117
+ resolvedSource += `${ source.slice(
118
+ lastIndex,
119
+ dynamicImportEnd
120
+ ) }, ${ urlJsString( load.responseUrl ) }`;
121
+ lastIndex = dynamicImportEnd;
122
+ }
123
+ resolvedSource += source.slice( lastIndex, originalIndex );
124
+ lastIndex = originalIndex;
125
+ }
126
+
127
+ for ( const {
128
+ s: start,
129
+ ss: statementStart,
130
+ se: statementEnd,
131
+ d: dynamicImportIndex,
132
+ } of imports ) {
133
+ // static import
134
+ if ( dynamicImportIndex === -1 ) {
135
+ const depLoad = load.deps[ depIndex++ ];
136
+ let blobUrl = depLoad.blobUrl;
137
+ const cycleShell = ! blobUrl;
138
+ if ( cycleShell ) {
139
+ // Circular shell creation
140
+ if ( ! ( blobUrl = depLoad.shellUrl ) ) {
141
+ blobUrl = depLoad.shellUrl = createBlob(
142
+ `export function u$_(m){${ depLoad.analysis[ 1 ]
143
+ .map( ( { s, e }, i ) => {
144
+ const q =
145
+ depLoad.source[ s ] === '"' ||
146
+ depLoad.source[ s ] === "'";
147
+ return `e$_${ i }=m${
148
+ q ? `[` : '.'
149
+ }${ depLoad.source.slice( s, e ) }${
150
+ q ? `]` : ''
151
+ }`;
152
+ } )
153
+ .join( ',' ) }}${
154
+ depLoad.analysis[ 1 ].length
155
+ ? `let ${ depLoad.analysis[ 1 ]
156
+ .map( ( _, i ) => `e$_${ i }` )
157
+ .join( ',' ) };`
158
+ : ''
159
+ }export {${ depLoad.analysis[ 1 ]
160
+ .map(
161
+ ( { s, e }, i ) =>
162
+ `e$_${ i } as ${ depLoad.source.slice(
163
+ s,
164
+ e
165
+ ) }`
166
+ )
167
+ .join( ',' ) }}\n//# sourceURL=${
168
+ depLoad.responseUrl
169
+ }?cycle`
170
+ );
171
+ }
172
+ }
173
+
174
+ pushStringTo( start - 1 );
175
+ resolvedSource += `/*${ source.slice(
176
+ start - 1,
177
+ statementEnd
178
+ ) }*/${ urlJsString( blobUrl ) }`;
179
+
180
+ // circular shell execution
181
+ if ( ! cycleShell && depLoad.shellUrl ) {
182
+ resolvedSource += `;import*as m$_${ depIndex } from'${ depLoad.blobUrl }';import{u$_ as u$_${ depIndex }}from'${ depLoad.shellUrl }';u$_${ depIndex }(m$_${ depIndex })`;
183
+ depLoad.shellUrl = undefined;
184
+ }
185
+ lastIndex = statementEnd;
186
+ }
187
+ // import.meta
188
+ else if ( dynamicImportIndex === -2 ) {
189
+ throw Error( 'The import.meta property is not supported.' );
190
+ }
191
+ // dynamic import
192
+ else {
193
+ pushStringTo( statementStart );
194
+ resolvedSource += `wpInteractivityRouterImport(`;
195
+ dynamicImportEndStack.push( statementEnd - 1 );
196
+ lastIndex = start;
197
+ }
198
+ }
199
+
200
+ // progressive cycle binding updates
201
+ if ( load.shellUrl ) {
202
+ resolvedSource += `\n;import{u$_}from'${
203
+ load.shellUrl
204
+ }';try{u$_({${ exports
205
+ .filter( ( e ) => e.ln )
206
+ .map( ( { s, e, ln } ) => `${ source.slice( s, e ) }:${ ln }` )
207
+ .join( ',' ) }})}catch(_){};\n`;
208
+ }
209
+
210
+ pushStringTo( source.length );
211
+ }
212
+
213
+ // ensure we have a proper sourceURL
214
+ let hasSourceURL = false;
215
+ resolvedSource = resolvedSource.replace(
216
+ sourceMapURLRegEx,
217
+ ( match, isMapping, url ) => {
218
+ hasSourceURL = ! isMapping;
219
+ return match.replace( url, () =>
220
+ new URL( url, load.responseUrl ).toString()
221
+ );
222
+ }
223
+ );
224
+ if ( ! hasSourceURL ) {
225
+ resolvedSource += '\n//# sourceURL=' + load.responseUrl;
226
+ }
227
+
228
+ load.blobUrl = createBlob( resolvedSource );
229
+ load.source = undefined; // free memory
230
+ }
231
+
232
+ const sourceMapURLRegEx =
233
+ /\n\/\/# source(Mapping)?URL=([^\n]+)\s*((;|\/\/[^#][^\n]*)\s*)*$/;
234
+
235
+ function getOrCreateLoad(
236
+ url: string,
237
+ fetchOpts: RequestInit,
238
+ parent: string
239
+ ): ModuleLoad {
240
+ let load: ModuleLoad = registry[ url ];
241
+ if ( load ) {
242
+ return load;
243
+ }
244
+
245
+ load = { url };
246
+
247
+ if ( registry[ url ] ) {
248
+ // If there's a naming conflict, keep incrementing until unique
249
+ let i = 0;
250
+ while ( registry[ load.url + ++i ] ) {
251
+ /* no-op */
252
+ }
253
+ load.url += i;
254
+ }
255
+ registry[ load.url ] = load;
256
+
257
+ load.fetchPromise = ( async () => {
258
+ let source;
259
+ ( { responseUrl: load.responseUrl, source: source } =
260
+ await ( fetchCache[ url ] ||
261
+ fetchModule( url, fetchOpts, parent ) ) );
262
+ try {
263
+ load.analysis = lexer.parse( source, load.url );
264
+ } catch ( e ) {
265
+ // eslint-disable-next-line no-console
266
+ console.error( e );
267
+ load.analysis = [ [], [], false, false ];
268
+ }
269
+ load.source = source;
270
+ return load;
271
+ } )();
272
+
273
+ load.linkPromise = load.fetchPromise.then( async () => {
274
+ let childFetchOpts = fetchOpts;
275
+ load.deps = (
276
+ await Promise.all(
277
+ load.analysis[ 0 ].map( async ( { n, d } ) => {
278
+ if ( d !== -1 || ! n ) {
279
+ return undefined;
280
+ }
281
+ const responseUrl = resolve(
282
+ n,
283
+ load.responseUrl || load.url
284
+ );
285
+ if ( skip && skip( responseUrl ) ) {
286
+ return { blobUrl: responseUrl } as ModuleLoad;
287
+ }
288
+ // remove integrity for child fetches
289
+ if ( childFetchOpts.integrity ) {
290
+ childFetchOpts = {
291
+ ...childFetchOpts,
292
+ integrity: undefined,
293
+ };
294
+ }
295
+ return getOrCreateLoad(
296
+ responseUrl,
297
+ childFetchOpts,
298
+ load.responseUrl
299
+ ).fetchPromise;
300
+ } )
301
+ )
302
+ ).filter( ( l ) => l );
303
+ } );
304
+
305
+ return load;
306
+ }
307
+
308
+ const dynamicImport = ( u: string ) => import( /* webpackIgnore: true */ u );
309
+
310
+ /**
311
+ * Resolves the passed module URL and fetches the corresponding module
312
+ * and their dependencies, returning a `ModuleLoad` object once all
313
+ * of them have been fetched.
314
+ *
315
+ * @param url Module URL.
316
+ * @param fetchOpts Fetch options.
317
+ * @return A promise with a `ModuleLoad` instance.
318
+ */
319
+ export async function preloadModule(
320
+ url: string,
321
+ fetchOpts?: RequestInit
322
+ ): Promise< ModuleLoad > {
323
+ await initPromise;
324
+ const load = getOrCreateLoad( url, fetchOpts, null );
325
+ const seen = {};
326
+ await loadAll( load, seen );
327
+ resolveDeps( load, seen );
328
+ // microtask scheduling – can help ensure Blob is fully ready
329
+ await Promise.resolve();
330
+ return load;
331
+ }
332
+
333
+ /**
334
+ * Imports the module represented by the passed `ModuleLoad` instance.
335
+ *
336
+ * @param load The `ModuleLoad` instance representing the module.
337
+ * @return A promise with the imported module.
338
+ */
339
+ export async function importPreloadedModule< Module = unknown >(
340
+ load: ModuleLoad
341
+ ): Promise< Module > {
342
+ const module = await dynamicImport( load.blobUrl );
343
+ // if the preloaded module ended up with a shell (circular refs), finalize it
344
+ if ( load.shellUrl ) {
345
+ ( await dynamicImport( load.shellUrl ) ).u$_( module );
346
+ }
347
+ return module;
348
+ }
349
+
350
+ /**
351
+ * Imports the module represented by the passed module URL.
352
+ *
353
+ * The module URL and all its dependencies are resolved using the
354
+ * current status of the internal dynamic import map.
355
+ *
356
+ * @param url Module URL.
357
+ * @param fetchOpts Fetch options.
358
+ * @return A promise with the imported module.
359
+ */
360
+ export async function topLevelLoad< Module = unknown >(
361
+ url: string,
362
+ fetchOpts?: RequestInit
363
+ ): Promise< Module > {
364
+ const load = await preloadModule( url, fetchOpts );
365
+ return importPreloadedModule< Module >( load );
366
+ }
@@ -0,0 +1,292 @@
1
+ /**
2
+ * This code is derived from the following projects:
3
+ *
4
+ * 1. dynamic-importmap (https://github.com/keller-mark/dynamic-importmap)
5
+ * 2. es-module-shims (https://github.com/guybedford/es-module-shims)
6
+ *
7
+ * The original implementation was created by Guy Bedford in es-module-shims,
8
+ * then adapted by Mark Keller in dynamic-importmap, and further modified
9
+ * for use in this project.
10
+ *
11
+ * Both projects are licensed under the MIT license.
12
+ *
13
+ * MIT License: https://opensource.org/licenses/MIT
14
+ */
15
+
16
+ const backslashRegEx = /\\/g;
17
+
18
+ function isURL( url: string ) {
19
+ if ( url.indexOf( ':' ) === -1 ) {
20
+ return false;
21
+ }
22
+ try {
23
+ new URL( url );
24
+ return true;
25
+ } catch ( _ ) {
26
+ return false;
27
+ }
28
+ }
29
+
30
+ function resolveIfNotPlainOrUrl( relUrl, parentUrl ) {
31
+ const hIdx = parentUrl.indexOf( '#' ),
32
+ qIdx = parentUrl.indexOf( '?' );
33
+ if ( hIdx + qIdx > -2 ) {
34
+ parentUrl = parentUrl.slice(
35
+ 0,
36
+ // eslint-disable-next-line no-nested-ternary
37
+ hIdx === -1 ? qIdx : qIdx === -1 || qIdx > hIdx ? hIdx : qIdx
38
+ );
39
+ }
40
+ if ( relUrl.indexOf( '\\' ) !== -1 ) {
41
+ relUrl = relUrl.replace( backslashRegEx, '/' );
42
+ }
43
+ // protocol-relative
44
+ if ( relUrl[ 0 ] === '/' && relUrl[ 1 ] === '/' ) {
45
+ return parentUrl.slice( 0, parentUrl.indexOf( ':' ) + 1 ) + relUrl;
46
+ }
47
+ // relative-url
48
+ else if (
49
+ ( relUrl[ 0 ] === '.' &&
50
+ ( relUrl[ 1 ] === '/' ||
51
+ ( relUrl[ 1 ] === '.' &&
52
+ ( relUrl[ 2 ] === '/' ||
53
+ ( relUrl.length === 2 && ( relUrl += '/' ) ) ) ) ||
54
+ ( relUrl.length === 1 && ( relUrl += '/' ) ) ) ) ||
55
+ relUrl[ 0 ] === '/'
56
+ ) {
57
+ const parentProtocol = parentUrl.slice(
58
+ 0,
59
+ parentUrl.indexOf( ':' ) + 1
60
+ );
61
+ // Disabled, but these cases will give inconsistent results for deep backtracking
62
+ //if (parentUrl[parentProtocol.length] !== '/')
63
+ // throw new Error('Cannot resolve');
64
+ // read pathname from parent URL
65
+ // pathname taken to be part after leading "/"
66
+ let pathname;
67
+ if ( parentUrl[ parentProtocol.length + 1 ] === '/' ) {
68
+ // resolving to a :// so we need to read out the auth and host
69
+ if ( parentProtocol !== 'file:' ) {
70
+ pathname = parentUrl.slice( parentProtocol.length + 2 );
71
+ pathname = pathname.slice( pathname.indexOf( '/' ) + 1 );
72
+ } else {
73
+ pathname = parentUrl.slice( 8 );
74
+ }
75
+ } else {
76
+ // resolving to :/ so pathname is the /... part
77
+ pathname = parentUrl.slice(
78
+ parentProtocol.length +
79
+ ( parentUrl[ parentProtocol.length ] === '/' )
80
+ );
81
+ }
82
+
83
+ if ( relUrl[ 0 ] === '/' ) {
84
+ return (
85
+ parentUrl.slice( 0, parentUrl.length - pathname.length - 1 ) +
86
+ relUrl
87
+ );
88
+ }
89
+
90
+ // join together and split for removal of .. and . segments
91
+ // looping the string instead of anything fancy for perf reasons
92
+ // '../../../../../z' resolved to 'x/y' is just 'z'
93
+ const segmented =
94
+ pathname.slice( 0, pathname.lastIndexOf( '/' ) + 1 ) + relUrl;
95
+
96
+ const output = [];
97
+ let segmentIndex = -1;
98
+ for ( let i = 0; i < segmented.length; i++ ) {
99
+ // busy reading a segment - only terminate on '/'
100
+ if ( segmentIndex !== -1 ) {
101
+ if ( segmented[ i ] === '/' ) {
102
+ output.push( segmented.slice( segmentIndex, i + 1 ) );
103
+ segmentIndex = -1;
104
+ }
105
+ continue;
106
+ }
107
+ // new segment - check if it is relative
108
+ else if ( segmented[ i ] === '.' ) {
109
+ // ../ segment
110
+ if (
111
+ segmented[ i + 1 ] === '.' &&
112
+ ( segmented[ i + 2 ] === '/' || i + 2 === segmented.length )
113
+ ) {
114
+ output.pop();
115
+ i += 2;
116
+ continue;
117
+ }
118
+ // ./ segment
119
+ else if (
120
+ segmented[ i + 1 ] === '/' ||
121
+ i + 1 === segmented.length
122
+ ) {
123
+ i += 1;
124
+ continue;
125
+ }
126
+ }
127
+ // it is the start of a new segment
128
+ while ( segmented[ i ] === '/' ) {
129
+ i++;
130
+ }
131
+ segmentIndex = i;
132
+ }
133
+ // finish reading out the last segment
134
+ if ( segmentIndex !== -1 ) {
135
+ output.push( segmented.slice( segmentIndex ) );
136
+ }
137
+ return (
138
+ parentUrl.slice( 0, parentUrl.length - pathname.length ) +
139
+ output.join( '' )
140
+ );
141
+ }
142
+ }
143
+
144
+ function resolveUrl( relUrl, parentUrl ) {
145
+ return (
146
+ resolveIfNotPlainOrUrl( relUrl, parentUrl ) ||
147
+ ( isURL( relUrl )
148
+ ? relUrl
149
+ : resolveIfNotPlainOrUrl( './' + relUrl, parentUrl ) )
150
+ );
151
+ }
152
+
153
+ function getMatch( path, matchObj ) {
154
+ if ( matchObj[ path ] ) {
155
+ return path;
156
+ }
157
+ let sepIndex = path.length;
158
+ do {
159
+ const segment = path.slice( 0, sepIndex + 1 );
160
+ if ( segment in matchObj ) {
161
+ return segment;
162
+ }
163
+ } while ( ( sepIndex = path.lastIndexOf( '/', sepIndex - 1 ) ) !== -1 );
164
+ }
165
+
166
+ function applyPackages( id, packages ) {
167
+ const pkgName = getMatch( id, packages );
168
+ if ( pkgName ) {
169
+ const pkg = packages[ pkgName ];
170
+ if ( pkg === null ) {
171
+ return;
172
+ }
173
+ return pkg + id.slice( pkgName.length );
174
+ }
175
+ }
176
+
177
+ function resolveImportMap( importMap, resolvedOrPlain, parentUrl ) {
178
+ let scopeUrl = parentUrl && getMatch( parentUrl, importMap.scopes );
179
+ while ( scopeUrl ) {
180
+ const packageResolution = applyPackages(
181
+ resolvedOrPlain,
182
+ importMap.scopes[ scopeUrl ]
183
+ );
184
+ if ( packageResolution ) {
185
+ return packageResolution;
186
+ }
187
+ scopeUrl = getMatch(
188
+ scopeUrl.slice( 0, scopeUrl.lastIndexOf( '/' ) ),
189
+ importMap.scopes
190
+ );
191
+ }
192
+ return (
193
+ applyPackages( resolvedOrPlain, importMap.imports ) ||
194
+ ( resolvedOrPlain.indexOf( ':' ) !== -1 && resolvedOrPlain )
195
+ );
196
+ }
197
+
198
+ function resolveAndComposePackages(
199
+ packages,
200
+ outPackages,
201
+ baseUrl,
202
+ parentMap
203
+ ) {
204
+ for ( const p in packages ) {
205
+ const resolvedLhs = resolveIfNotPlainOrUrl( p, baseUrl ) || p;
206
+ const target = packages[ p ];
207
+ if ( typeof target !== 'string' ) {
208
+ continue;
209
+ }
210
+ const mapped = resolveImportMap(
211
+ parentMap,
212
+ resolveIfNotPlainOrUrl( target, baseUrl ) || target,
213
+ baseUrl
214
+ );
215
+ if ( mapped ) {
216
+ outPackages[ resolvedLhs ] = mapped;
217
+ continue;
218
+ }
219
+ // console.warn(
220
+ // `Mapping "${ p }" -> "${ packages[ p ] }" does not resolve`
221
+ // );
222
+ }
223
+ }
224
+
225
+ function resolveAndComposeImportMap( json, baseUrl, parentMap ) {
226
+ const outMap = {
227
+ imports: Object.assign( {}, parentMap.imports ),
228
+ scopes: Object.assign( {}, parentMap.scopes ),
229
+ };
230
+
231
+ if ( json.imports ) {
232
+ resolveAndComposePackages(
233
+ json.imports,
234
+ outMap.imports,
235
+ baseUrl,
236
+ parentMap
237
+ );
238
+ }
239
+
240
+ if ( json.scopes ) {
241
+ for ( const s in json.scopes ) {
242
+ const resolvedScope = resolveUrl( s, baseUrl );
243
+ resolveAndComposePackages(
244
+ json.scopes[ s ],
245
+ outMap.scopes[ resolvedScope ] ||
246
+ ( outMap.scopes[ resolvedScope ] = {} ),
247
+ baseUrl,
248
+ parentMap
249
+ );
250
+ }
251
+ }
252
+
253
+ return outMap;
254
+ }
255
+
256
+ let importMap = { imports: {}, scopes: {} };
257
+
258
+ // TODO: check if this baseURI should change per document, and so
259
+ // it need to be passed as a parameter to methods like `resolve`.
260
+ const baseUrl = document.baseURI;
261
+ const pageBaseUrl = baseUrl;
262
+
263
+ /**
264
+ * Extends the internal dynamic import map with the passed one.
265
+ *
266
+ * @param importMapIn Import map.
267
+ * @param importMapIn.imports Imports declaration.
268
+ * @param importMapIn.scopes Scopes declaration.
269
+ */
270
+ export function addImportMap( importMapIn: {
271
+ imports?: Record< string, string >;
272
+ scopes?: Record< string, any >;
273
+ } ) {
274
+ importMap = resolveAndComposeImportMap(
275
+ importMapIn,
276
+ pageBaseUrl,
277
+ importMap
278
+ );
279
+ }
280
+
281
+ /**
282
+ * Resolves the URL of the passed module ID against the current internal
283
+ * dynamic import map.
284
+ *
285
+ * @param id Module ID.
286
+ * @param parentUrl Parent URL, in case the module ID is relative.
287
+ * @return Resolved module URL.
288
+ */
289
+ export function resolve( id: string, parentUrl: string ): string {
290
+ const urlResolved = resolveIfNotPlainOrUrl( id, parentUrl );
291
+ return resolveImportMap( importMap, urlResolved || id, parentUrl ) || id;
292
+ }