@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.
- package/LICENSE +43 -0
- package/README.md +105 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/core.d.ts +6 -0
- package/dist/lib/core.d.ts.map +1 -0
- package/dist/lib/core.js +470 -0
- package/dist/lib/env.d.ts +12 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +33 -0
- package/dist/lib/features.d.ts +4 -0
- package/dist/lib/features.d.ts.map +1 -0
- package/dist/lib/features.js +70 -0
- package/dist/lib/native-resolve.d.ts +2 -0
- package/dist/lib/native-resolve.d.ts.map +1 -0
- package/dist/lib/native-resolve.js +59 -0
- package/dist/lib/polyfill.d.ts +41 -0
- package/dist/lib/polyfill.d.ts.map +1 -0
- package/dist/lib/polyfill.js +77 -0
- package/dist/lib/resolve.d.ts +16 -0
- package/dist/lib/resolve.d.ts.map +1 -0
- package/dist/lib/resolve.js +167 -0
- package/dist/lib/self.d.ts +3 -0
- package/dist/lib/self.d.ts.map +1 -0
- package/dist/lib/self.js +2 -0
- package/dist/lib/trusted-types.d.ts +15 -0
- package/dist/lib/trusted-types.d.ts.map +1 -0
- package/dist/lib/trusted-types.js +21 -0
- package/package.json +44 -5
- package/src/index.ts +6 -0
- package/src/lib/core.ts +616 -0
- package/src/lib/env.ts +37 -0
- package/src/lib/features.ts +80 -0
- package/src/lib/native-resolve.ts +69 -0
- package/src/lib/polyfill.ts +89 -0
- package/src/lib/resolve.ts +225 -0
- package/src/lib/self.ts +2 -0
- package/src/lib/trusted-types.ts +49 -0
package/src/lib/core.ts
ADDED
|
@@ -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}` : '')
|