@remix-run/assets 0.6.0 → 0.7.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/README.md +101 -13
- package/dist/assets.d.ts +2 -1
- package/dist/assets.d.ts.map +1 -1
- package/dist/lib/asset-server.d.ts +41 -19
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +98 -41
- package/dist/lib/compilation-error.d.ts +1 -1
- package/dist/lib/compilation-error.d.ts.map +1 -1
- package/dist/lib/files/compiler.d.ts +1 -1
- package/dist/lib/files/compiler.d.ts.map +1 -1
- package/dist/lib/files/compiler.js +26 -29
- package/dist/lib/files/config.d.ts +6 -0
- package/dist/lib/files/config.d.ts.map +1 -1
- package/dist/lib/files/config.js +9 -0
- package/dist/lib/fingerprint.d.ts +0 -4
- package/dist/lib/fingerprint.d.ts.map +1 -1
- package/dist/lib/fingerprint.js +0 -4
- package/dist/lib/hmr.d.ts +7 -0
- package/dist/lib/hmr.d.ts.map +1 -1
- package/dist/lib/hmr.js +198 -25
- package/dist/lib/routes.d.ts +1 -0
- package/dist/lib/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +1 -1
- package/dist/lib/scripts/compiler.d.ts +8 -2
- package/dist/lib/scripts/compiler.d.ts.map +1 -1
- package/dist/lib/scripts/compiler.js +186 -36
- package/dist/lib/scripts/emit.d.ts +2 -1
- package/dist/lib/scripts/emit.d.ts.map +1 -1
- package/dist/lib/scripts/emit.js +25 -16
- package/dist/lib/scripts/resolve.d.ts +7 -1
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +110 -3
- package/dist/lib/scripts/specifiers.d.ts +2 -0
- package/dist/lib/scripts/specifiers.d.ts.map +1 -0
- package/dist/lib/scripts/specifiers.js +9 -0
- package/dist/lib/scripts/transform.d.ts +2 -3
- package/dist/lib/scripts/transform.d.ts.map +1 -1
- package/dist/lib/scripts/transform.js +2 -16
- package/dist/lib/styles/compiler.d.ts +0 -1
- package/dist/lib/styles/compiler.d.ts.map +1 -1
- package/dist/lib/styles/compiler.js +105 -25
- package/dist/lib/styles/emit.d.ts +2 -1
- package/dist/lib/styles/emit.d.ts.map +1 -1
- package/dist/lib/styles/emit.js +12 -10
- package/dist/lib/styles/resolve.d.ts +0 -1
- package/dist/lib/styles/resolve.d.ts.map +1 -1
- package/dist/lib/styles/resolve.js +0 -1
- package/dist/lib/styles/transform.d.ts +0 -2
- package/dist/lib/styles/transform.d.ts.map +1 -1
- package/dist/lib/styles/transform.js +0 -7
- package/dist/lib/virtual-store.d.ts +8 -0
- package/dist/lib/virtual-store.d.ts.map +1 -0
- package/dist/lib/virtual-store.js +100 -0
- package/package.json +6 -5
- package/src/assets.ts +7 -1
- package/src/lib/asset-server.ts +175 -73
- package/src/lib/compilation-error.ts +1 -0
- package/src/lib/files/compiler.ts +30 -40
- package/src/lib/files/config.ts +17 -0
- package/src/lib/fingerprint.ts +0 -9
- package/src/lib/hmr.ts +210 -26
- package/src/lib/routes.ts +1 -1
- package/src/lib/scripts/compiler.ts +238 -52
- package/src/lib/scripts/emit.ts +34 -19
- package/src/lib/scripts/resolve.ts +166 -4
- package/src/lib/scripts/specifiers.ts +11 -0
- package/src/lib/scripts/transform.ts +4 -23
- package/src/lib/styles/compiler.ts +137 -39
- package/src/lib/styles/emit.ts +18 -13
- package/src/lib/styles/resolve.ts +0 -2
- package/src/lib/styles/transform.ts +0 -10
- package/src/lib/virtual-store.ts +124 -0
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
isAssetServerCompilationError,
|
|
11
11
|
} from '../compilation-error.ts'
|
|
12
12
|
import type { AssetServerCompilationError } from '../compilation-error.ts'
|
|
13
|
-
import { formatFingerprintedPathname,
|
|
13
|
+
import { formatFingerprintedPathname, hashContent } from '../fingerprint.ts'
|
|
14
14
|
import { normalizeFilePath, resolveFilePath } from '../paths.ts'
|
|
15
15
|
import type { CompiledRoutes } from '../routes.ts'
|
|
16
16
|
import type { AssetFileTransformResult, ResolvedAssetRequestTransformMap } from './config.ts'
|
|
@@ -56,8 +56,8 @@ type FileGetHrefOptions = {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
type FileCompilerOptions = {
|
|
59
|
-
buildId?: string
|
|
60
59
|
cache?: FileStorage
|
|
60
|
+
cacheKey?: string
|
|
61
61
|
extensions: readonly string[]
|
|
62
62
|
fingerprintAssets: boolean
|
|
63
63
|
globalTransforms: readonly {
|
|
@@ -128,7 +128,7 @@ export function createFileCompiler(options: FileCompilerOptions): FileCompiler {
|
|
|
128
128
|
let transformedAssetMetadataByCacheKey = new Map<string, EmittedFileMetadata>()
|
|
129
129
|
let transformedCacheKeysByIdentityPath = new Map<string, Set<string>>()
|
|
130
130
|
let transformedEmitInFlightByCacheKey = new Map<string, Promise<EmittedFile>>()
|
|
131
|
-
let
|
|
131
|
+
let cacheKey = resolvedOptions.cacheKey ?? crypto.randomUUID()
|
|
132
132
|
let resolveArgs: ResolveArgs = {
|
|
133
133
|
extensions: resolvedOptions.extensionSet,
|
|
134
134
|
isAllowed: resolvedOptions.isAllowed,
|
|
@@ -140,9 +140,13 @@ export function createFileCompiler(options: FileCompilerOptions): FileCompiler {
|
|
|
140
140
|
let resolvedFile = resolveServedFileOrThrow(resolveInputFilePath(filePath), resolveArgs)
|
|
141
141
|
let record = getFreshSourceFileRecord(resolvedFile.identityPath)
|
|
142
142
|
if (shouldUseTransformPipeline(getOptions.transform)) {
|
|
143
|
-
let
|
|
143
|
+
let transformedCacheKey = getTransformedRecordCacheKey(
|
|
144
|
+
cacheKey,
|
|
145
|
+
record,
|
|
146
|
+
getOptions.transform,
|
|
147
|
+
)
|
|
144
148
|
let notModified = getNotModifiedFile(
|
|
145
|
-
transformedAssetMetadataByCacheKey.get(
|
|
149
|
+
transformedAssetMetadataByCacheKey.get(transformedCacheKey),
|
|
146
150
|
getOptions,
|
|
147
151
|
)
|
|
148
152
|
if (notModified) return notModified
|
|
@@ -179,12 +183,13 @@ export function createFileCompiler(options: FileCompilerOptions): FileCompiler {
|
|
|
179
183
|
async getHref(filePath, hrefOptions) {
|
|
180
184
|
let resolvedFile = resolveServedFileOrThrow(resolveInputFilePath(filePath), resolveArgs)
|
|
181
185
|
let record = getFreshSourceFileRecord(resolvedFile.identityPath)
|
|
182
|
-
let href =
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
)
|
|
187
|
-
|
|
186
|
+
let href = resolvedFile.stableUrlPathname
|
|
187
|
+
if (resolvedOptions.fingerprintAssets) {
|
|
188
|
+
let emittedFile = shouldUseTransformPipeline(hrefOptions.transform)
|
|
189
|
+
? await getOrCreateTransformedFile(record, hrefOptions.transform)
|
|
190
|
+
: await getOrCreateSourceFile(record)
|
|
191
|
+
href = formatFingerprintedPathname(href, emittedFile.fingerprint)
|
|
192
|
+
}
|
|
188
193
|
|
|
189
194
|
if (shouldUseTransformPipeline(hrefOptions.transform)) {
|
|
190
195
|
return appendTransformQuery(href, hrefOptions.transform)
|
|
@@ -291,19 +296,15 @@ export function createFileCompiler(options: FileCompilerOptions): FileCompiler {
|
|
|
291
296
|
resolvedOptions.transforms,
|
|
292
297
|
resolvedOptions.maxRequestTransforms,
|
|
293
298
|
)
|
|
294
|
-
let
|
|
295
|
-
let existing = transformedEmitInFlightByCacheKey.get(
|
|
299
|
+
let transformedCacheKey = getTransformedRecordCacheKey(cacheKey, record, transformQuery)
|
|
300
|
+
let existing = transformedEmitInFlightByCacheKey.get(transformedCacheKey)
|
|
296
301
|
if (existing) return existing
|
|
297
302
|
|
|
298
303
|
let promise = (async () => {
|
|
299
|
-
let
|
|
300
|
-
let cachedFile = await getCachedTransformedFile(
|
|
301
|
-
cacheKey,
|
|
302
|
-
record.identityPath,
|
|
303
|
-
sourceFile.fingerprint,
|
|
304
|
-
)
|
|
304
|
+
let cachedFile = await getCachedTransformedFile(transformedCacheKey, record.identityPath)
|
|
305
305
|
if (cachedFile) return cachedFile
|
|
306
306
|
|
|
307
|
+
let sourceFile = await getOrCreateSourceFile(record)
|
|
307
308
|
let transformedFile = await applyTransforms(record.identityPath, sourceFile, parsedTransforms)
|
|
308
309
|
if (transformedFile === null) {
|
|
309
310
|
return sourceFile
|
|
@@ -312,24 +313,23 @@ export function createFileCompiler(options: FileCompilerOptions): FileCompiler {
|
|
|
312
313
|
let emittedFile = await createEmittedFile(transformedFile.body, {
|
|
313
314
|
extension: transformedFile.extension,
|
|
314
315
|
filePath: record.identityPath,
|
|
315
|
-
fingerprint: sourceFile.fingerprint,
|
|
316
316
|
})
|
|
317
317
|
rememberTransformedAssetMetadata(
|
|
318
|
-
|
|
318
|
+
transformedCacheKey,
|
|
319
319
|
record.identityPath,
|
|
320
320
|
toEmittedFileMetadata(emittedFile),
|
|
321
321
|
)
|
|
322
|
-
await setCachedTransformedFile(
|
|
322
|
+
await setCachedTransformedFile(transformedCacheKey, record.identityPath, emittedFile)
|
|
323
323
|
return emittedFile
|
|
324
324
|
})()
|
|
325
325
|
|
|
326
|
-
transformedEmitInFlightByCacheKey.set(
|
|
326
|
+
transformedEmitInFlightByCacheKey.set(transformedCacheKey, promise)
|
|
327
327
|
|
|
328
328
|
try {
|
|
329
329
|
return await promise
|
|
330
330
|
} finally {
|
|
331
|
-
if (transformedEmitInFlightByCacheKey.get(
|
|
332
|
-
transformedEmitInFlightByCacheKey.delete(
|
|
331
|
+
if (transformedEmitInFlightByCacheKey.get(transformedCacheKey) === promise) {
|
|
332
|
+
transformedEmitInFlightByCacheKey.delete(transformedCacheKey)
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
335
|
}
|
|
@@ -338,13 +338,6 @@ export function createFileCompiler(options: FileCompilerOptions): FileCompiler {
|
|
|
338
338
|
return createEmittedFile(body, {
|
|
339
339
|
extension: path.extname(identityPath).toLowerCase(),
|
|
340
340
|
filePath: identityPath,
|
|
341
|
-
fingerprint:
|
|
342
|
-
resolvedOptions.fingerprintAssets && resolvedOptions.buildId
|
|
343
|
-
? await generateFingerprint({
|
|
344
|
-
buildId: resolvedOptions.buildId,
|
|
345
|
-
content: body,
|
|
346
|
-
})
|
|
347
|
-
: null,
|
|
348
341
|
})
|
|
349
342
|
}
|
|
350
343
|
|
|
@@ -439,7 +432,6 @@ export function createFileCompiler(options: FileCompilerOptions): FileCompiler {
|
|
|
439
432
|
async function getCachedTransformedFile(
|
|
440
433
|
cacheKey: string,
|
|
441
434
|
identityPath: string,
|
|
442
|
-
fingerprint: string | null,
|
|
443
435
|
): Promise<EmittedFile | null> {
|
|
444
436
|
if (!resolvedOptions.cache) return null
|
|
445
437
|
|
|
@@ -452,7 +444,6 @@ export function createFileCompiler(options: FileCompilerOptions): FileCompiler {
|
|
|
452
444
|
(await createEmittedFileMetadata(body, {
|
|
453
445
|
extension: path.extname(file.name).toLowerCase(),
|
|
454
446
|
filePath: identityPath,
|
|
455
|
-
fingerprint,
|
|
456
447
|
}))
|
|
457
448
|
|
|
458
449
|
rememberTransformedAssetMetadata(cacheKey, identityPath, metadata)
|
|
@@ -484,7 +475,6 @@ export function createFileCompiler(options: FileCompilerOptions): FileCompiler {
|
|
|
484
475
|
options: {
|
|
485
476
|
extension: string
|
|
486
477
|
filePath: string
|
|
487
|
-
fingerprint: string | null
|
|
488
478
|
},
|
|
489
479
|
): Promise<EmittedFile> {
|
|
490
480
|
let metadata = await createEmittedFileMetadata(body, options)
|
|
@@ -522,9 +512,9 @@ export function createFileCompiler(options: FileCompilerOptions): FileCompiler {
|
|
|
522
512
|
options: {
|
|
523
513
|
extension: string
|
|
524
514
|
filePath: string
|
|
525
|
-
fingerprint: string | null
|
|
526
515
|
},
|
|
527
516
|
): Promise<EmittedFileMetadata> {
|
|
517
|
+
let contentHash = await hashContent(body)
|
|
528
518
|
let contentType =
|
|
529
519
|
detectContentType(`file${options.extension}`) ??
|
|
530
520
|
detectContentType(options.filePath) ??
|
|
@@ -532,9 +522,9 @@ export function createFileCompiler(options: FileCompilerOptions): FileCompiler {
|
|
|
532
522
|
|
|
533
523
|
return {
|
|
534
524
|
contentType,
|
|
535
|
-
etag: `W/"${
|
|
525
|
+
etag: `W/"${contentHash}"`,
|
|
536
526
|
extension: options.extension,
|
|
537
|
-
fingerprint:
|
|
527
|
+
fingerprint: resolvedOptions.fingerprintAssets ? contentHash : null,
|
|
538
528
|
}
|
|
539
529
|
}
|
|
540
530
|
}
|
|
@@ -609,12 +599,12 @@ function getRecordCacheKey(record: SourceFileRecord): string {
|
|
|
609
599
|
}
|
|
610
600
|
|
|
611
601
|
function getTransformedRecordCacheKey(
|
|
612
|
-
|
|
602
|
+
cacheKey: string,
|
|
613
603
|
record: SourceFileRecord,
|
|
614
604
|
transformQuery: readonly string[] | null,
|
|
615
605
|
): string {
|
|
616
606
|
return [
|
|
617
|
-
encodeCacheKeyPart(
|
|
607
|
+
encodeCacheKeyPart(cacheKey),
|
|
618
608
|
encodeCacheKeyPart(record.identityPath),
|
|
619
609
|
String(record.invalidationVersion),
|
|
620
610
|
encodeCacheKeyPart(JSON.stringify(transformQuery ?? [])),
|
package/src/lib/files/config.ts
CHANGED
|
@@ -115,10 +115,16 @@ export interface AssetServerFilesOptions<transforms extends AssetRequestTransfor
|
|
|
115
115
|
* Optional backing store for cached transformed file outputs.
|
|
116
116
|
*/
|
|
117
117
|
cache?: FileStorage
|
|
118
|
+
/**
|
|
119
|
+
* Optional namespace for cached transformed file outputs. Use a stable value such as a
|
|
120
|
+
* commit SHA to reuse transformed files across server restarts for the same deployment.
|
|
121
|
+
*/
|
|
122
|
+
cacheKey?: string
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
export interface ResolvedAssetServerFilesOptions {
|
|
121
126
|
cache?: FileStorage
|
|
127
|
+
cacheKey?: string
|
|
122
128
|
extensions: readonly string[]
|
|
123
129
|
globalTransforms: readonly ResolvedAssetGlobalTransform[]
|
|
124
130
|
hasTransforms: boolean
|
|
@@ -303,8 +309,19 @@ export function normalizeFilesOptions<transforms extends AssetRequestTransformMa
|
|
|
303
309
|
}
|
|
304
310
|
}
|
|
305
311
|
|
|
312
|
+
if (files.cacheKey !== undefined) {
|
|
313
|
+
if (typeof files.cacheKey !== 'string') {
|
|
314
|
+
throw new TypeError('files.cacheKey must be a string')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (files.cacheKey.length === 0) {
|
|
318
|
+
throw new TypeError('files.cacheKey must be a non-empty string')
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
306
322
|
return {
|
|
307
323
|
cache: files.cache,
|
|
324
|
+
cacheKey: files.cacheKey,
|
|
308
325
|
extensions: normalizedExtensions,
|
|
309
326
|
globalTransforms: normalizedGlobalTransforms,
|
|
310
327
|
hasTransforms: normalizedTransforms.size > 0 || normalizedGlobalTransforms.length > 0,
|
package/src/lib/fingerprint.ts
CHANGED
|
@@ -7,15 +7,6 @@ export async function hashContent(content: string | Uint8Array<ArrayBufferLike>)
|
|
|
7
7
|
return Buffer.from(hashBuffer).toString('base64url').slice(0, 6)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export async function generateFingerprint(args: {
|
|
11
|
-
buildId: string
|
|
12
|
-
content: string | Uint8Array<ArrayBufferLike>
|
|
13
|
-
}): Promise<string> {
|
|
14
|
-
let content =
|
|
15
|
-
typeof args.content === 'string' ? args.content : Buffer.from(args.content).toString('base64')
|
|
16
|
-
return hashContent(JSON.stringify([content, args.buildId]))
|
|
17
|
-
}
|
|
18
|
-
|
|
19
10
|
export function parseFingerprintSuffix(pathname: string): {
|
|
20
11
|
pathname: string
|
|
21
12
|
requestedFingerprint: string | null
|
package/src/lib/hmr.ts
CHANGED
|
@@ -16,6 +16,11 @@ export type HmrPayload =
|
|
|
16
16
|
| {
|
|
17
17
|
/** Importing module whose dependency-accept handler accepts this update. */
|
|
18
18
|
acceptedPath?: string
|
|
19
|
+
/** Import map entries required before this update can be evaluated. */
|
|
20
|
+
importMap?: {
|
|
21
|
+
imports: Record<string, string>
|
|
22
|
+
scopes?: Record<string, Record<string, string>>
|
|
23
|
+
}
|
|
19
24
|
/** Public URL of the changed JavaScript module. */
|
|
20
25
|
path: string
|
|
21
26
|
/** Identifies a JavaScript module update. */
|
|
@@ -38,8 +43,18 @@ export type HmrPayload =
|
|
|
38
43
|
type: 'browser:reload'
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
export function createHmrClientSource(options: {
|
|
46
|
+
export function createHmrClientSource(options: {
|
|
47
|
+
dataKey: string
|
|
48
|
+
eventPathname: string
|
|
49
|
+
moduleImporter: string | null
|
|
50
|
+
}): string {
|
|
42
51
|
return `
|
|
52
|
+
${
|
|
53
|
+
options.moduleImporter
|
|
54
|
+
? `import { importModule as __remixImport } from ${JSON.stringify(options.moduleImporter)}`
|
|
55
|
+
: 'const __remixImport = (specifier) => import(specifier)'
|
|
56
|
+
}
|
|
57
|
+
|
|
43
58
|
const contexts = new Map()
|
|
44
59
|
const dataByPath = new Map()
|
|
45
60
|
|
|
@@ -111,6 +126,13 @@ let reconnectPending = false
|
|
|
111
126
|
let pageReloadTimer
|
|
112
127
|
let failedJavaScriptUpdates = new Map()
|
|
113
128
|
let stylesheetUpdatePromise = Promise.resolve()
|
|
129
|
+
let installedImportMap = { imports: new Map(), scopes: new Map() }
|
|
130
|
+
let processedScripts = new WeakSet()
|
|
131
|
+
|
|
132
|
+
let observer = new MutationObserver(processMutations)
|
|
133
|
+
observer.observe(document, { childList: true })
|
|
134
|
+
observer.observe(document.head, { childList: true })
|
|
135
|
+
processImportMaps()
|
|
114
136
|
|
|
115
137
|
let events = new EventSource(${JSON.stringify(options.eventPathname)})
|
|
116
138
|
|
|
@@ -136,11 +158,18 @@ events.onerror = () => {
|
|
|
136
158
|
|
|
137
159
|
events.onmessage = (event) => {
|
|
138
160
|
let payload = JSON.parse(event.data)
|
|
161
|
+
if (payload.type === 'browser:update') {
|
|
162
|
+
let update = payload.data?.[${JSON.stringify(options.dataKey)}]
|
|
163
|
+
if (!update) return
|
|
164
|
+
handleBrowserUpdate(update).catch((error) => {
|
|
165
|
+
console.error('[remix] HMR update failed', error)
|
|
166
|
+
if (update.updates.some((entry) => entry.type !== 'js')) reloadPage()
|
|
167
|
+
})
|
|
168
|
+
return
|
|
169
|
+
}
|
|
139
170
|
handlePayload(payload).catch((error) => {
|
|
140
171
|
console.error('[remix] HMR update failed', error)
|
|
141
|
-
|
|
142
|
-
reloadPage()
|
|
143
|
-
}
|
|
172
|
+
reloadPage()
|
|
144
173
|
})
|
|
145
174
|
}
|
|
146
175
|
|
|
@@ -155,27 +184,28 @@ async function handlePayload(payload) {
|
|
|
155
184
|
await dispatchCustomEvent(payload.type, payload)
|
|
156
185
|
return
|
|
157
186
|
}
|
|
187
|
+
}
|
|
158
188
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
189
|
+
async function handleBrowserUpdate(payload) {
|
|
190
|
+
for (let update of payload.updates) {
|
|
191
|
+
if (update.type === 'css') {
|
|
192
|
+
let updated = await queueStylesheetUpdate(update.path, payload.timestamp)
|
|
193
|
+
if (updated) console.debug('[remix] HMR updated stylesheet', update.path)
|
|
194
|
+
continue
|
|
195
|
+
}
|
|
166
196
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
197
|
+
try {
|
|
198
|
+
if (update.importMap && !installImportMap(update.importMap)) return
|
|
199
|
+
let updated = await updateJavaScriptModule(
|
|
200
|
+
update.path,
|
|
201
|
+
update.acceptedPath ?? update.path,
|
|
202
|
+
payload.timestamp,
|
|
203
|
+
)
|
|
204
|
+
failedJavaScriptUpdates.delete(update.path)
|
|
205
|
+
if (updated) console.debug('[remix] HMR accepted update', update.path)
|
|
206
|
+
} catch (error) {
|
|
207
|
+
failedJavaScriptUpdates.set(update.path, update.acceptedPath ?? update.path)
|
|
208
|
+
throw error
|
|
179
209
|
}
|
|
180
210
|
}
|
|
181
211
|
}
|
|
@@ -244,7 +274,7 @@ async function updateJavaScriptModule(path, acceptedPath, timestamp) {
|
|
|
244
274
|
await callback(previousContext.data)
|
|
245
275
|
}
|
|
246
276
|
|
|
247
|
-
let updatedModule = await
|
|
277
|
+
let updatedModule = await __remixImport(withTimestamp(path, timestamp), import.meta.url)
|
|
248
278
|
previousContext.invalidated = false
|
|
249
279
|
previousContext.updating = true
|
|
250
280
|
try {
|
|
@@ -267,7 +297,7 @@ async function updateJavaScriptModule(path, acceptedPath, timestamp) {
|
|
|
267
297
|
}
|
|
268
298
|
}
|
|
269
299
|
|
|
270
|
-
let updatedModule = await
|
|
300
|
+
let updatedModule = await __remixImport(withTimestamp(acceptedPath, timestamp), import.meta.url)
|
|
271
301
|
previousContext.invalidated = false
|
|
272
302
|
previousContext.updating = true
|
|
273
303
|
try {
|
|
@@ -298,7 +328,7 @@ async function propagateInvalidatedJavaScriptModule(path, timestamp) {
|
|
|
298
328
|
await callback(importerContext.data)
|
|
299
329
|
}
|
|
300
330
|
|
|
301
|
-
let updatedModule = await
|
|
331
|
+
let updatedModule = await __remixImport(withTimestamp(path, timestamp), import.meta.url)
|
|
302
332
|
importerContext.invalidated = false
|
|
303
333
|
importerContext.updating = true
|
|
304
334
|
try {
|
|
@@ -386,6 +416,160 @@ function withTimestamp(path, timestamp) {
|
|
|
386
416
|
return url.pathname + url.search
|
|
387
417
|
}
|
|
388
418
|
|
|
419
|
+
function installImportMap(importMap) {
|
|
420
|
+
processMutations(observer.takeRecords())
|
|
421
|
+
processImportMaps()
|
|
422
|
+
let delta = getImportMapDelta(installedImportMap, importMap)
|
|
423
|
+
if (delta === null) {
|
|
424
|
+
reloadPage()
|
|
425
|
+
return false
|
|
426
|
+
}
|
|
427
|
+
if (!delta) return true
|
|
428
|
+
|
|
429
|
+
let script = document.createElement('script')
|
|
430
|
+
script.setAttribute('data-rmx-import-map', '')
|
|
431
|
+
script.type = 'importmap'
|
|
432
|
+
script.textContent = JSON.stringify(delta)
|
|
433
|
+
document.head.appendChild(script)
|
|
434
|
+
processedScripts.add(script)
|
|
435
|
+
mergeImportMap(installedImportMap, delta)
|
|
436
|
+
return true
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function processImportMap(script) {
|
|
440
|
+
if (processedScripts.has(script)) return
|
|
441
|
+
processedScripts.add(script)
|
|
442
|
+
|
|
443
|
+
let importMap
|
|
444
|
+
try {
|
|
445
|
+
importMap = JSON.parse(script.textContent ?? '')
|
|
446
|
+
} catch {
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
if (!importMap || typeof importMap !== 'object') return
|
|
450
|
+
mergeImportMap(installedImportMap, importMap)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function processImportMaps() {
|
|
454
|
+
for (let script of document.querySelectorAll('script[type="importmap"]')) {
|
|
455
|
+
processImportMap(script)
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function processMutations(mutations) {
|
|
460
|
+
for (let mutation of mutations) {
|
|
461
|
+
if (mutation.type !== 'childList') continue
|
|
462
|
+
for (let node of mutation.addedNodes) {
|
|
463
|
+
if (node instanceof HTMLScriptElement && node.matches('script[type="importmap"]')) {
|
|
464
|
+
processImportMap(node)
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function getImportMapDelta(installed, importMap) {
|
|
471
|
+
let imports = getImportMapImportsDelta(installed.imports, importMap.imports)
|
|
472
|
+
if (imports === null) return null
|
|
473
|
+
|
|
474
|
+
let scopes
|
|
475
|
+
if (importMap.scopes && typeof importMap.scopes === 'object') {
|
|
476
|
+
scopes = {}
|
|
477
|
+
for (let [scope, entries] of Object.entries(importMap.scopes)) {
|
|
478
|
+
let normalizedScope = normalizeImportMapUrl(scope)
|
|
479
|
+
if (!normalizedScope || !entries || typeof entries !== 'object') continue
|
|
480
|
+
let installedEntries = installed.scopes.get(normalizedScope) ?? new Map()
|
|
481
|
+
let entriesDelta = getImportMapImportsDelta(installedEntries, entries, scope)
|
|
482
|
+
if (entriesDelta === null) return null
|
|
483
|
+
if (entriesDelta) scopes[scope] = entriesDelta
|
|
484
|
+
}
|
|
485
|
+
if (Object.keys(scopes).length === 0) scopes = undefined
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (!imports && !scopes) return undefined
|
|
489
|
+
return {
|
|
490
|
+
...(imports ? { imports } : null),
|
|
491
|
+
...(scopes ? { scopes } : null),
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function getImportMapImportsDelta(installed, entries, scope) {
|
|
496
|
+
if (!entries || typeof entries !== 'object') return undefined
|
|
497
|
+
let delta = {}
|
|
498
|
+
for (let [specifier, href] of Object.entries(entries)) {
|
|
499
|
+
if (href !== null && typeof href !== 'string') continue
|
|
500
|
+
let normalizedSpecifier = normalizeImportMapSpecifier(specifier)
|
|
501
|
+
if (!normalizedSpecifier) continue
|
|
502
|
+
let normalizedHref = normalizeImportMapAddress(href)
|
|
503
|
+
let existing = installed.get(normalizedSpecifier)
|
|
504
|
+
if (existing === normalizedHref) continue
|
|
505
|
+
if (existing !== undefined) {
|
|
506
|
+
let scopeDescription = scope ? \` in scope "\${scope}"\` : ''
|
|
507
|
+
console.warn(
|
|
508
|
+
\`[remix] HMR reloading page after import map conflict for "\${specifier}"\${scopeDescription}: \` +
|
|
509
|
+
formatImportMapAddress(existing) +
|
|
510
|
+
' is already installed, but the new map points to ' +
|
|
511
|
+
formatImportMapAddress(normalizedHref),
|
|
512
|
+
)
|
|
513
|
+
return null
|
|
514
|
+
}
|
|
515
|
+
delta[specifier] = href
|
|
516
|
+
}
|
|
517
|
+
return Object.keys(delta).length > 0 ? delta : undefined
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function mergeImportMap(installed, importMap) {
|
|
521
|
+
mergeImportMapImports(installed.imports, importMap.imports)
|
|
522
|
+
if (!importMap.scopes || typeof importMap.scopes !== 'object') return
|
|
523
|
+
for (let [scope, entries] of Object.entries(importMap.scopes)) {
|
|
524
|
+
let normalizedScope = normalizeImportMapUrl(scope)
|
|
525
|
+
if (!normalizedScope || !entries || typeof entries !== 'object') continue
|
|
526
|
+
let installedEntries = installed.scopes.get(normalizedScope)
|
|
527
|
+
if (!installedEntries) {
|
|
528
|
+
installedEntries = new Map()
|
|
529
|
+
installed.scopes.set(normalizedScope, installedEntries)
|
|
530
|
+
}
|
|
531
|
+
mergeImportMapImports(installedEntries, entries)
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function mergeImportMapImports(installed, entries) {
|
|
536
|
+
if (!entries || typeof entries !== 'object') return
|
|
537
|
+
for (let [specifier, href] of Object.entries(entries)) {
|
|
538
|
+
if (href !== null && typeof href !== 'string') continue
|
|
539
|
+
let normalizedSpecifier = normalizeImportMapSpecifier(specifier)
|
|
540
|
+
if (!normalizedSpecifier || installed.has(normalizedSpecifier)) continue
|
|
541
|
+
installed.set(normalizedSpecifier, normalizeImportMapAddress(href))
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function normalizeImportMapSpecifier(specifier) {
|
|
546
|
+
if (
|
|
547
|
+
specifier.startsWith('/') ||
|
|
548
|
+
specifier.startsWith('./') ||
|
|
549
|
+
specifier.startsWith('../') ||
|
|
550
|
+
URL.canParse(specifier)
|
|
551
|
+
) {
|
|
552
|
+
return normalizeImportMapUrl(specifier)
|
|
553
|
+
}
|
|
554
|
+
return specifier
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function normalizeImportMapAddress(href) {
|
|
558
|
+
return href === null ? null : normalizeImportMapUrl(href)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function formatImportMapAddress(href) {
|
|
562
|
+
return href === null ? 'null' : '"' + href + '"'
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function normalizeImportMapUrl(value) {
|
|
566
|
+
try {
|
|
567
|
+
return new URL(value, document.baseURI).href
|
|
568
|
+
} catch {
|
|
569
|
+
return null
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
389
573
|
function reloadPage() {
|
|
390
574
|
if (pageReloadTimer) clearTimeout(pageReloadTimer)
|
|
391
575
|
pageReloadTimer = setTimeout(() => {
|
package/src/lib/routes.ts
CHANGED
|
@@ -137,7 +137,7 @@ function normalizeMountUrlRoot(urlRoot: string): string {
|
|
|
137
137
|
return url.pathname.replace(/\/+$/, '') || '/'
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
function resolveMountFileRoot(rootDir: string, fileRoot: string): string {
|
|
140
|
+
export function resolveMountFileRoot(rootDir: string, fileRoot: string): string {
|
|
141
141
|
let resolvedRoot = resolveFilePath(rootDir, fileRoot)
|
|
142
142
|
|
|
143
143
|
try {
|