@remix-run/multiple-import-maps-polyfill 0.0.0 → 0.1.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.
@@ -0,0 +1,616 @@
1
+ import {
2
+ resolveAndComposeImportMap,
3
+ resolveImportMap,
4
+ resolveIfNotPlainOrUrl,
5
+ asURL,
6
+ } from './resolve.ts'
7
+ import type { ImportMap } from './resolve.ts'
8
+ import {
9
+ baseUrl as pageBaseUrl,
10
+ dynamicImport,
11
+ createBlob,
12
+ throwError,
13
+ fromParent,
14
+ hasDocument,
15
+ defaultFetchOpts,
16
+ } from './env.ts'
17
+ import {
18
+ featureDetectionPromise,
19
+ supportsImportMaps,
20
+ supportsMultipleImportMaps,
21
+ } from './features.ts'
22
+ import * as lexer from 'es-module-lexer'
23
+ import type { ExportSpecifier, ImportSpecifier } from 'es-module-lexer'
24
+
25
+ // This source is adapted from ES Module Shims 2.8.4.
26
+ const bridgeName = `remix.importMapPolyfill.runtime:${import.meta.url}`
27
+ const bridgeKey = Symbol.for(bridgeName)
28
+ const bridgeExpression = `globalThis[Symbol.for(${JSON.stringify(bridgeName)})]`
29
+
30
+ type ModuleNamespace = Record<string, unknown>
31
+ type Analysis = readonly [
32
+ imports: ReadonlyArray<ImportSpecifier>,
33
+ exports: ReadonlyArray<ExportSpecifier>,
34
+ facade: boolean,
35
+ hasModuleSyntax: boolean,
36
+ ]
37
+ type Seen = Record<string, 0 | 1>
38
+
39
+ interface ModuleMeta {
40
+ url: string
41
+ resolve(this: ModuleMeta, id: string, parentUrl?: string | URL): string
42
+ }
43
+
44
+ interface Load {
45
+ u: string
46
+ r: string
47
+ f: Promise<Load>
48
+ S: string | undefined
49
+ L: Promise<void> | undefined
50
+ a: Analysis
51
+ d: Dependency[]
52
+ b: string | undefined
53
+ s: string | undefined
54
+ n: boolean
55
+ N: boolean
56
+ m: ModuleMeta | null
57
+ }
58
+
59
+ interface NativeLoad {
60
+ u: string
61
+ b: string
62
+ s?: never
63
+ n?: never
64
+ N?: never
65
+ }
66
+
67
+ interface Dependency {
68
+ l: Load | NativeLoad
69
+ s: boolean
70
+ }
71
+
72
+ interface ResolveResult {
73
+ r: string
74
+ n: boolean
75
+ N: boolean
76
+ }
77
+
78
+ interface SourceResult {
79
+ url: string
80
+ source: string
81
+ }
82
+
83
+ const resolve = (id: string, parentUrl = pageBaseUrl): ResolveResult => {
84
+ let urlResolved = resolveIfNotPlainOrUrl(id, parentUrl) || asURL(id)
85
+ let firstResolved =
86
+ firstImportMap && resolveImportMap(firstImportMap, urlResolved || id, parentUrl)
87
+ let composedResolved =
88
+ composedImportMap === firstImportMap
89
+ ? firstResolved
90
+ : resolveImportMap(composedImportMap, urlResolved || id, parentUrl)
91
+ let resolved = composedResolved || firstResolved || throwUnresolved(id, parentUrl)
92
+ // needsShim, shouldShim per load record to set on parent
93
+ let n = false,
94
+ N = false
95
+ if (!supportsMultipleImportMaps) {
96
+ // bare specifier and not resolved by first import map -> needs shim
97
+ if (!urlResolved && !firstResolved) n = true
98
+ // resolution doesn't match first import map -> should shim
99
+ if (firstResolved && resolved !== firstResolved) N = true
100
+ }
101
+ return { r: resolved, n, N }
102
+ }
103
+
104
+ // import()
105
+ export async function importShim(
106
+ id: string,
107
+ opts?: string | ImportCallOptions,
108
+ parentUrl?: string,
109
+ ): Promise<ModuleNamespace> {
110
+ if (typeof opts === 'string') {
111
+ parentUrl = opts
112
+ opts = undefined
113
+ }
114
+ let sourceType = opts?.with?.type
115
+ await initPromise
116
+ processImportMaps()
117
+ legacyAcceptingImportMaps = false
118
+ return topLevelLoad(id, parentUrl || pageBaseUrl, defaultFetchOpts, undefined, sourceType)
119
+ }
120
+
121
+ export async function preloadShim(
122
+ ids: string | readonly string[],
123
+ parentUrl = pageBaseUrl,
124
+ ): Promise<void> {
125
+ await initPromise
126
+ processImportMaps()
127
+ await importMapPromise
128
+ await Promise.allSettled(
129
+ (typeof ids === 'string' ? [ids] : ids).map((id) =>
130
+ processPreload(resolve(id, parentUrl).r, defaultFetchOpts),
131
+ ),
132
+ )
133
+ }
134
+
135
+ const throwUnresolved = (id: string, parentUrl: string): never => {
136
+ throw Error(`Unable to resolve specifier '${id}'${fromParent(parentUrl)}`)
137
+ }
138
+
139
+ const metaResolve = function (this: ModuleMeta, id: string, parentUrl: string | URL = this.url) {
140
+ return resolve(id, `${parentUrl}`).r
141
+ }
142
+
143
+ const registry: Record<string, Load> = {}
144
+ const nativeModules = new Set<string>()
145
+ Reflect.set(globalThis, bridgeKey, Object.freeze({ importShim, registry }))
146
+
147
+ export const registerNativeModule = (url: string): void => {
148
+ nativeModules.add(url)
149
+ }
150
+
151
+ const loadAll = async (load: Load, seen: Seen): Promise<void> => {
152
+ seen[load.u] = 1
153
+ await load.L
154
+ await Promise.all(
155
+ load.d.map(({ l: dep, s: sourcePhase }) => {
156
+ if (dep.b || seen[dep.u]) return
157
+ if (sourcePhase) return (dep as Load).f
158
+ return loadAll(dep as Load, seen)
159
+ }),
160
+ )
161
+ }
162
+
163
+ let firstImportMap: ImportMap | null = null
164
+ // To support polyfilling multiple import maps, we separately track the composed import map from the first import map
165
+ let composedImportMap: ImportMap = { imports: {}, scopes: {}, integrity: {} }
166
+
167
+ const initPromise = Promise.all([lexer.init, featureDetectionPromise]).then(() => {
168
+ if (!hasDocument || !supportsImportMaps)
169
+ throw new TypeError('The multiple import map polyfill requires native import map support.')
170
+ attachMutationObserver()
171
+ })
172
+
173
+ const attachMutationObserver = () => {
174
+ let observer = new MutationObserver((mutations) => {
175
+ for (let mutation of mutations) {
176
+ if (mutation.type !== 'childList') continue
177
+ for (let node of mutation.addedNodes) {
178
+ if ((node as Element).tagName === 'SCRIPT') {
179
+ let script = node as HTMLScriptElement
180
+ if (script.type === 'importmap') processImportMap(script)
181
+ }
182
+ }
183
+ }
184
+ })
185
+ observer.observe(document, { childList: true })
186
+ observer.observe(document.head, { childList: true })
187
+ processImportMaps()
188
+ }
189
+
190
+ let importMapPromise = initPromise
191
+ let legacyAcceptingImportMaps = true
192
+
193
+ async function topLevelLoad(
194
+ url: string,
195
+ parentUrl: string,
196
+ fetchOpts: RequestInit,
197
+ source?: string,
198
+ sourceType?: string,
199
+ ): Promise<ModuleNamespace> {
200
+ await initPromise
201
+ await importMapPromise
202
+ url = (await resolve(url, parentUrl)).r
203
+
204
+ // we mock import('./x.css', { with: { type: 'css' }}) support via an inline static reexport
205
+ // because we can't syntactically pass through to dynamic import with a second argument
206
+ if (sourceType === 'css' || sourceType === 'json') {
207
+ // Direct reexport for hot reloading skipped due to Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=1965620
208
+ source = `import m from'${url}'with{type:"${sourceType}"};export default m;`
209
+ url += '?entry'
210
+ }
211
+
212
+ let load = getOrCreateLoad(url, fetchOpts, undefined, source)
213
+ if (source) load.N = true
214
+ linkLoad(load, fetchOpts)
215
+ let seen: Seen = {}
216
+ await loadAll(load, seen)
217
+ resolveDeps(load, seen)
218
+ let module: ModuleNamespace = await (load.n || load.N ? dynamicImport(load.b!) : import(load.u))
219
+ // if the top-level load is a shell, run its update function
220
+ if (load.s) ((await dynamicImport(load.s)).u$_ as (module: ModuleNamespace) => void)(module)
221
+ revokeObjectURLs(Object.keys(seen))
222
+ return module
223
+ }
224
+
225
+ const revokeObjectURLs = (registryKeys: string[]): void => {
226
+ let curIdx = 0
227
+ let handler =
228
+ globalThis.requestIdleCallback ||
229
+ globalThis.requestAnimationFrame ||
230
+ ((fn: () => void) => setTimeout(fn, 0))
231
+ handler(cleanup)
232
+ function cleanup() {
233
+ for (let key of registryKeys.slice(curIdx, (curIdx += 100))) {
234
+ let load = registry[key]
235
+ if (load && load.b && load.b !== load.u) URL.revokeObjectURL(load.b)
236
+ }
237
+ if (curIdx < registryKeys.length) handler(cleanup)
238
+ }
239
+ }
240
+
241
+ const urlJsString = (url: string): string => `'${url.replace(/'/g, "\\'")}'`
242
+
243
+ let resolvedSource = ''
244
+ let lastIndex = 0
245
+ const pushStringTo = (load: Load, originalIndex: number, dynamicImportEndStack: number[]) => {
246
+ while (dynamicImportEndStack[dynamicImportEndStack.length - 1] < originalIndex) {
247
+ let dynamicImportEnd = dynamicImportEndStack.pop()!
248
+ resolvedSource += `${load.S!.slice(lastIndex, dynamicImportEnd)}, ${urlJsString(load.r)}`
249
+ lastIndex = dynamicImportEnd
250
+ }
251
+ resolvedSource += load.S!.slice(lastIndex, originalIndex)
252
+ lastIndex = originalIndex
253
+ }
254
+
255
+ const pushSourceURL = (
256
+ load: Load,
257
+ commentPrefix: string,
258
+ commentStart: number,
259
+ dynamicImportEndStack: number[],
260
+ ) => {
261
+ let urlStart = commentStart + commentPrefix.length
262
+ let commentEnd = load.S!.indexOf('\n', urlStart)
263
+ let urlEnd = commentEnd !== -1 ? commentEnd : load.S!.length
264
+ let sourceUrl = load.S!.slice(urlStart, urlEnd)
265
+ try {
266
+ sourceUrl = new URL(sourceUrl, load.r).href
267
+ } catch (e) {}
268
+ pushStringTo(load, urlStart, dynamicImportEndStack)
269
+ resolvedSource += sourceUrl
270
+ lastIndex = urlEnd
271
+ }
272
+
273
+ const resolveDeps = (load: Load, seen: Seen): void => {
274
+ if (load.b || !seen[load.u]) return
275
+ seen[load.u] = 0
276
+
277
+ for (let { l: dep, s: sourcePhase } of load.d) {
278
+ if (!sourcePhase && !dep.b) {
279
+ resolveDeps(dep as Load, seen)
280
+ }
281
+ }
282
+
283
+ if (!load.n) load.n = load.d.some((dep) => dep.l.n)
284
+ if (!load.N) load.N = load.d.some((dep) => dep.l.N)
285
+
286
+ // use native loader whenever possible (n = needs shim) via executable subgraph passthrough
287
+ // so long as the module doesn't use dynamic import or unsupported URL mappings (N = should shim)
288
+ if (!load.n && !load.N) {
289
+ load.b = load.u
290
+ load.S = undefined
291
+ return
292
+ }
293
+
294
+ let [imports, exports] = load.a
295
+
296
+ // "execution"
297
+ let source = load.S!,
298
+ depIndex = 0,
299
+ dynamicImportEndStack: number[] = []
300
+
301
+ // once all deps have loaded we can inline the dependency resolution blobs
302
+ // and define this blob
303
+ resolvedSource = ''
304
+ lastIndex = 0
305
+
306
+ for (let {
307
+ s: start,
308
+ e: end,
309
+ ss: statementStart,
310
+ se: statementEnd,
311
+ d: dynamicImportIndex,
312
+ t,
313
+ a,
314
+ } of imports) {
315
+ // source phase
316
+ if (t === 4) {
317
+ let { l: depLoad } = load.d[depIndex++]
318
+ pushStringTo(load, start - 1, dynamicImportEndStack)
319
+ resolvedSource += `/*${source.slice(start - 1, end + 1)}*/'${depLoad.b}'`
320
+ lastIndex = end + 1
321
+ } else if (t === 5 || t === 6) {
322
+ throw new TypeError('Dynamic source imports and import defer are not supported.')
323
+ }
324
+ // dependency source replacements
325
+ else if (dynamicImportIndex === -1) {
326
+ let keepAssertion = a > 0
327
+ let { l: depLoad } = load.d[depIndex++],
328
+ blobUrl = depLoad.b,
329
+ cycleShell = !blobUrl
330
+ if (cycleShell) {
331
+ let cycleLoad = depLoad as Load
332
+ // circular shell creation
333
+ if (!(blobUrl = cycleLoad.s)) {
334
+ blobUrl = cycleLoad.s = createBlob(
335
+ `export function u$_(m){${cycleLoad.a[1]
336
+ .map(({ s, e }, i) => {
337
+ let depSource = cycleLoad.S!
338
+ let q = depSource[s] === '"' || depSource[s] === "'"
339
+ return `e$_${i}=m${q ? `[` : '.'}${depSource.slice(s, e)}${q ? `]` : ''}`
340
+ })
341
+ .join(',')}}${
342
+ cycleLoad.a[1].length
343
+ ? `let ${cycleLoad.a[1].map((_, i) => `e$_${i}`).join(',')};`
344
+ : ''
345
+ }export {${cycleLoad.a[1]
346
+ .map(({ s, e }, i) => `e$_${i} as ${cycleLoad.S!.slice(s, e)}`)
347
+ .join(',')}}\n//# sourceURL=${cycleLoad.r}?cycle`,
348
+ )
349
+ }
350
+ }
351
+
352
+ pushStringTo(load, start - 1, dynamicImportEndStack)
353
+ resolvedSource += `/*${source.slice(start - 1, end + 1)}*/'${blobUrl}'`
354
+
355
+ // circular shell execution
356
+ if (!cycleShell && depLoad.s) {
357
+ resolvedSource += `;import*as m$_${depIndex} from'${depLoad.b}';import{u$_ as u$_${depIndex}}from'${depLoad.s}';u$_${depIndex}(m$_${depIndex})`
358
+ depLoad.s = undefined
359
+ }
360
+ lastIndex = keepAssertion ? end + 1 : statementEnd
361
+ }
362
+ // import.meta
363
+ else if (dynamicImportIndex === -2) {
364
+ load.m = { url: load.r, resolve: metaResolve }
365
+ pushStringTo(load, start, dynamicImportEndStack)
366
+ resolvedSource += `${bridgeExpression}.registry[${urlJsString(load.u)}].m`
367
+ lastIndex = statementEnd
368
+ }
369
+ // dynamic import
370
+ else {
371
+ pushStringTo(load, statementStart, dynamicImportEndStack)
372
+ resolvedSource += `${bridgeExpression}.importShim(`
373
+ dynamicImportEndStack.push(statementEnd - 1)
374
+ lastIndex = start
375
+ }
376
+ }
377
+
378
+ // support progressive cycle binding updates (try statement avoids tdz errors)
379
+ if (load.s && (imports.length === 0 || imports[imports.length - 1].d === -1))
380
+ resolvedSource += `\n;import{u$_}from'${load.s}';try{u$_({${exports
381
+ .filter((e) => e.ln)
382
+ .map(({ s, e, ln }) => `${source.slice(s, e)}:${ln}`)
383
+ .join(',')}})}catch(_){};\n`
384
+
385
+ let sourceURLCommentStart = source.lastIndexOf(sourceURLCommentPrefix)
386
+ let sourceMapURLCommentStart = source.lastIndexOf(sourceMapURLCommentPrefix)
387
+
388
+ // ignore sourceMap comments before already spliced code
389
+ if (sourceURLCommentStart < lastIndex) sourceURLCommentStart = -1
390
+ if (sourceMapURLCommentStart < lastIndex) sourceMapURLCommentStart = -1
391
+
392
+ // sourceURL first / only
393
+ if (
394
+ sourceURLCommentStart !== -1 &&
395
+ (sourceMapURLCommentStart === -1 || sourceMapURLCommentStart > sourceURLCommentStart)
396
+ ) {
397
+ pushSourceURL(load, sourceURLCommentPrefix, sourceURLCommentStart, dynamicImportEndStack)
398
+ }
399
+ // sourceMappingURL
400
+ if (sourceMapURLCommentStart !== -1) {
401
+ pushSourceURL(load, sourceMapURLCommentPrefix, sourceMapURLCommentStart, dynamicImportEndStack)
402
+ // sourceURL last
403
+ if (sourceURLCommentStart !== -1 && sourceURLCommentStart > sourceMapURLCommentStart)
404
+ pushSourceURL(load, sourceURLCommentPrefix, sourceURLCommentStart, dynamicImportEndStack)
405
+ }
406
+
407
+ pushStringTo(load, source.length, dynamicImportEndStack)
408
+
409
+ if (sourceURLCommentStart === -1) resolvedSource += sourceURLCommentPrefix + load.r
410
+
411
+ load.b = createBlob(resolvedSource)
412
+ load.S = undefined
413
+ resolvedSource = ''
414
+ }
415
+
416
+ const sourceURLCommentPrefix = '\n//# sourceURL='
417
+ const sourceMapURLCommentPrefix = '\n//# sourceMappingURL='
418
+
419
+ // restrict in-flight fetches to a pool of 100
420
+ const p: Array<() => void> = []
421
+ let c = 0
422
+ const pushFetchPool = (): Promise<void> | undefined => {
423
+ if (++c > 100) return new Promise((resolve) => p.push(resolve))
424
+ }
425
+ const popFetchPool = () => {
426
+ c--
427
+ let next = p.shift()
428
+ if (next) next()
429
+ }
430
+
431
+ const doFetch = async (url: string, fetchOpts: RequestInit, parent?: string): Promise<Response> => {
432
+ let res,
433
+ poolQueue = pushFetchPool()
434
+ if (poolQueue) await poolQueue
435
+ try {
436
+ res = await fetch(url, fetchOpts)
437
+ } catch (e) {
438
+ let error = e as Error
439
+ error.message =
440
+ `Unable to fetch ${url}${fromParent(parent)} - see network log for details.\n` + error.message
441
+ throw error
442
+ } finally {
443
+ popFetchPool()
444
+ }
445
+
446
+ if (!res.ok) {
447
+ throw Object.assign(
448
+ new TypeError(`${res.status} ${res.statusText} ${res.url}${fromParent(parent)}`),
449
+ { response: res },
450
+ )
451
+ }
452
+ return res
453
+ }
454
+
455
+ async function defaultSourceHook(
456
+ url: string,
457
+ fetchOpts: RequestInit,
458
+ parent?: string,
459
+ ): Promise<SourceResult> {
460
+ let res = await doFetch(url, fetchOpts, parent),
461
+ contentType = res.headers.get('Content-Type') || ''
462
+ if (!/^(?:text|application)\/(?:x-)?(?:java|type)script(?:;|$)/i.test(contentType)) {
463
+ throw Error(
464
+ `Unsupported Content-Type "${contentType}" loading ${url}${fromParent(parent)}. Only JavaScript modules are supported and must be served with a valid MIME type like application/javascript.`,
465
+ )
466
+ }
467
+ return { url: res.url, source: await res.text() }
468
+ }
469
+
470
+ const fetchModule = async (
471
+ reqUrl: string,
472
+ fetchOpts: RequestInit,
473
+ parent?: string,
474
+ ): Promise<SourceResult> => {
475
+ let mapIntegrity = composedImportMap.integrity[reqUrl]
476
+ fetchOpts =
477
+ mapIntegrity && !fetchOpts.integrity ? { ...fetchOpts, integrity: mapIntegrity } : fetchOpts
478
+ let { url = reqUrl, source } = await defaultSourceHook(reqUrl, fetchOpts, parent)
479
+ return { url, source }
480
+ }
481
+
482
+ const getOrCreateLoad = (
483
+ url: string,
484
+ fetchOpts: RequestInit,
485
+ parent?: string,
486
+ source?: string,
487
+ ): Load => {
488
+ if (source && registry[url]) {
489
+ let i = 0
490
+ while (registry[url + '#' + ++i]) {}
491
+ url += '#' + i
492
+ }
493
+ let load = registry[url]
494
+ if (load) return load
495
+ registry[url] = load = {
496
+ // url
497
+ u: url,
498
+ // response url
499
+ r: source ? url : undefined!,
500
+ // fetchPromise
501
+ f: undefined!,
502
+ // source
503
+ S: source,
504
+ // linkPromise
505
+ L: undefined,
506
+ // analysis
507
+ a: undefined!,
508
+ // deps
509
+ d: undefined!,
510
+ // blobUrl
511
+ b: undefined,
512
+ // shellUrl
513
+ s: undefined,
514
+ // needsShim: does it fail execution in the current native loader?
515
+ n: false,
516
+ // shouldShim: does it need to be loaded by the polyfill loader?
517
+ N: false,
518
+ // meta
519
+ m: null,
520
+ }
521
+ load.f = (async () => {
522
+ if (load.S === undefined) {
523
+ // preload fetch options override fetch options (race)
524
+ ;({ url: load.r, source: load.S } = await (fetchCache[url] ||
525
+ fetchModule(url, fetchOpts, parent)))
526
+ }
527
+ try {
528
+ load.a = lexer.parse(load.S!, load.u)
529
+ } catch (e) {
530
+ throwError(e)
531
+ load.a = [[], [], false, false]
532
+ }
533
+ return load
534
+ })()
535
+ return load
536
+ }
537
+
538
+ const linkLoad = (load: Load, fetchOpts: RequestInit): void => {
539
+ if (load.L) return
540
+ load.L = load.f.then(() => {
541
+ let childFetchOpts = fetchOpts
542
+ let dependencies = load.a[0].map(({ n, d, t, a, se }): Dependency | undefined => {
543
+ let phaseImport = t >= 4
544
+ let sourcePhase = phaseImport && t < 6
545
+ if (phaseImport && t !== 4)
546
+ throw new TypeError('Dynamic source imports and import defer are not supported.')
547
+ // Unlike ESMS's automatic polyfill mode, this explicit loader must retain control of nested
548
+ // dynamic imports even when the currently linked graph can otherwise pass through natively.
549
+ if (d >= 0) {
550
+ load.N = true
551
+ return
552
+ }
553
+ if (d !== -1 || !n) return
554
+ let resolved = resolve(n, load.r || load.u)
555
+ if (resolved.n) load.n = true
556
+ if (resolved.N) load.N = true
557
+ let source = sourcePhase ? '' : undefined
558
+ if (a > 0) {
559
+ let assertion = load.S!.slice(a, se - 1)
560
+ // no need to fetch JSON/CSS if supported, since it's a leaf node, we'll just strip the assertion syntax
561
+ if (assertion.includes('json') || assertion.includes('css')) source = ''
562
+ }
563
+ // The ESM wrapper lazily imports this core. Loading the wrapper through the core would
564
+ // recurse and create a second copy of modules that import the wrapper.
565
+ if (nativeModules.has(resolved.r)) return { l: { u: resolved.r, b: resolved.r }, s: false }
566
+ if (childFetchOpts.integrity) childFetchOpts = { ...childFetchOpts, integrity: undefined }
567
+ let child = {
568
+ l: getOrCreateLoad(resolved.r, childFetchOpts, load.r, source),
569
+ s: sourcePhase,
570
+ }
571
+ // assertion case -> inline the CSS / JSON URL directly
572
+ if (source === '') child.l.b = child.l.u
573
+ if (!child.s) linkLoad(child.l, fetchOpts)
574
+ // load, sourcePhase
575
+ return child
576
+ })
577
+ load.d = dependencies.filter((dependency): dependency is Dependency => dependency !== undefined)
578
+ })
579
+ }
580
+
581
+ const processedImportMaps = new WeakSet<HTMLScriptElement>()
582
+
583
+ const processImportMaps = () => {
584
+ for (let script of document.querySelectorAll<HTMLScriptElement>('script[type=importmap]'))
585
+ processImportMap(script)
586
+ }
587
+
588
+ const processImportMap = (script: HTMLScriptElement): void => {
589
+ if (processedImportMaps.has(script)) return
590
+ processedImportMaps.add(script)
591
+ // we dont currently support external import maps in polyfill mode to match native
592
+ if (script.src) return
593
+ importMapPromise = importMapPromise
594
+ .then(() => {
595
+ composedImportMap = resolveAndComposeImportMap(
596
+ JSON.parse(script.innerHTML),
597
+ pageBaseUrl,
598
+ composedImportMap,
599
+ )
600
+ })
601
+ .catch((e) => {
602
+ if (e instanceof SyntaxError)
603
+ e = new Error(`Unable to parse import map ${e.message} in: ${script.innerHTML}`)
604
+ throwError(e)
605
+ })
606
+ if (!firstImportMap && legacyAcceptingImportMaps)
607
+ importMapPromise.then(() => (firstImportMap = composedImportMap))
608
+ legacyAcceptingImportMaps = false
609
+ }
610
+
611
+ const fetchCache: Record<string, Promise<SourceResult> | undefined> = {}
612
+ const processPreload = (url: string, fetchOpts: RequestInit): Promise<SourceResult> =>
613
+ initPromise.then(() => {
614
+ if (fetchCache[url]) return fetchCache[url]
615
+ return (fetchCache[url] = fetchModule(url, fetchOpts))
616
+ })
package/src/lib/env.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { self } from './self.ts'
2
+
3
+ export const hasDocument = typeof document !== 'undefined'
4
+
5
+ export const dynamicImport = (u: string): Promise<Record<string, unknown>> => import(u)
6
+
7
+ export const defaultFetchOpts = { credentials: 'same-origin' } satisfies RequestInit
8
+
9
+ export const version = `remix/multiple-import-maps-polyfill:${Date.now()}:${Math.random()}`
10
+
11
+ export let nonce = ''
12
+ if (hasDocument) {
13
+ let nonceElement = document.querySelector<HTMLScriptElement>('script[nonce]')
14
+ if (nonceElement) nonce = nonceElement.nonce || nonceElement.getAttribute('nonce') || ''
15
+ }
16
+
17
+ export const baseUrl = hasDocument
18
+ ? document.baseURI
19
+ : typeof location !== 'undefined'
20
+ ? `${location.protocol}//${location.host}${
21
+ location.pathname.includes('/')
22
+ ? location.pathname.slice(0, location.pathname.lastIndexOf('/') + 1)
23
+ : location.pathname
24
+ }`
25
+ : 'about:blank'
26
+
27
+ export const createBlob = (source: string): string =>
28
+ URL.createObjectURL(new Blob([source], { type: 'text/javascript' }))
29
+
30
+ const dispatchError = (error: unknown) =>
31
+ self.dispatchEvent(Object.assign(new Event('error'), { error }))
32
+
33
+ export const throwError = (err: unknown): void => {
34
+ ;(self.reportError || dispatchError)(err)
35
+ }
36
+
37
+ export const fromParent = (parent?: string): string => (parent ? ` imported from ${parent}` : '')