@remix-run/assets 0.4.4 → 0.5.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 +322 -56
- package/dist/assets.d.ts +2 -1
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +2 -2
- package/dist/lib/access.d.ts +6 -2
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +300 -5
- package/dist/lib/asset-server.d.ts +118 -5
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +260 -24
- package/dist/lib/compilation-error.d.ts +1 -1
- package/dist/lib/compilation-error.d.ts.map +1 -1
- package/dist/lib/file-matcher.js +1 -1
- package/dist/lib/files/compiler.d.ts.map +1 -1
- package/dist/lib/files/compiler.js +7 -6
- package/dist/lib/files/config.js +1 -1
- package/dist/lib/hmr.d.ts +37 -0
- package/dist/lib/hmr.d.ts.map +1 -0
- package/dist/lib/hmr.js +357 -0
- package/dist/lib/injected-packages.d.ts.map +1 -1
- package/dist/lib/injected-packages.js +36 -13
- package/dist/lib/loaders.d.ts +57 -0
- package/dist/lib/loaders.d.ts.map +1 -0
- package/dist/lib/loaders.js +1 -0
- package/dist/lib/module-store.d.ts +24 -0
- package/dist/lib/module-store.d.ts.map +1 -1
- package/dist/lib/module-store.js +136 -11
- package/dist/lib/routes.js +1 -1
- package/dist/lib/scripts/compiler.d.ts +24 -1
- package/dist/lib/scripts/compiler.d.ts.map +1 -1
- package/dist/lib/scripts/compiler.js +183 -29
- package/dist/lib/scripts/conditions.d.ts +2 -0
- package/dist/lib/scripts/conditions.d.ts.map +1 -0
- package/dist/lib/scripts/conditions.js +1 -0
- package/dist/lib/scripts/emit.d.ts +3 -0
- package/dist/lib/scripts/emit.d.ts.map +1 -1
- package/dist/lib/scripts/emit.js +24 -5
- package/dist/lib/scripts/resolve.d.ts +4 -0
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +70 -5
- package/dist/lib/scripts/transform.d.ts +9 -0
- package/dist/lib/scripts/transform.d.ts.map +1 -1
- package/dist/lib/scripts/transform.js +222 -18
- package/dist/lib/source-maps.js +1 -1
- package/dist/lib/styles/compiler.d.ts +14 -1
- package/dist/lib/styles/compiler.d.ts.map +1 -1
- package/dist/lib/styles/compiler.js +78 -14
- package/dist/lib/styles/emit.js +4 -4
- package/dist/lib/styles/resolve.d.ts.map +1 -1
- package/dist/lib/styles/resolve.js +8 -7
- package/dist/lib/styles/transform.js +3 -3
- package/dist/lib/target.d.ts +1 -1
- package/dist/lib/target.d.ts.map +1 -1
- package/dist/lib/watch.js +1 -1
- package/dist/types/hmr.d.ts +36 -0
- package/package.json +17 -11
- package/src/assets.ts +2 -1
- package/src/lib/access.ts +386 -5
- package/src/lib/asset-server.ts +429 -18
- package/src/lib/compilation-error.ts +1 -0
- package/src/lib/files/compiler.ts +7 -3
- package/src/lib/hmr.ts +397 -0
- package/src/lib/injected-packages.ts +41 -11
- package/src/lib/loaders.ts +80 -0
- package/src/lib/module-store.ts +190 -9
- package/src/lib/scripts/compiler.ts +248 -24
- package/src/lib/scripts/conditions.ts +1 -0
- package/src/lib/scripts/emit.ts +50 -5
- package/src/lib/scripts/resolve.ts +124 -2
- package/src/lib/scripts/transform.ts +290 -19
- package/src/lib/styles/compiler.ts +108 -8
- package/src/lib/styles/emit.ts +1 -1
- package/src/lib/styles/resolve.ts +11 -7
- package/src/types/hmr.d.ts +36 -0
|
@@ -23,6 +23,7 @@ import type { ResolveArgs, ResolvedModule } from './resolve.ts'
|
|
|
23
23
|
import type { CompiledRoutes } from '../routes.ts'
|
|
24
24
|
import type { ResolvedScriptTarget } from '../target.ts'
|
|
25
25
|
import { createModuleStore } from '../module-store.ts'
|
|
26
|
+
import type { ModuleLoader } from '../loaders.ts'
|
|
26
27
|
import type {
|
|
27
28
|
FileSnapshot,
|
|
28
29
|
ModuleRecord,
|
|
@@ -65,9 +66,15 @@ type ScriptCompilerOptions = {
|
|
|
65
66
|
define?: Record<string, string>
|
|
66
67
|
external: string[]
|
|
67
68
|
fingerprintAssets: boolean
|
|
69
|
+
hmr?: {
|
|
70
|
+
clientPathname: string
|
|
71
|
+
send(updates: ScriptHmrUpdate[]): void
|
|
72
|
+
}
|
|
68
73
|
isAllowed(absolutePath: string): boolean
|
|
69
74
|
minify: boolean
|
|
75
|
+
loaders: readonly ModuleLoader[]
|
|
70
76
|
onWatchDirectoriesChange?: (delta: { add: string[]; remove: string[] }) => void
|
|
77
|
+
onWatchFilesChange?: (delta: { add: string[]; remove: string[] }) => void
|
|
71
78
|
rootDir: string
|
|
72
79
|
routes: CompiledRoutes
|
|
73
80
|
sourceMapSourcePaths: 'absolute' | 'url'
|
|
@@ -81,7 +88,8 @@ type ScriptCompiler = {
|
|
|
81
88
|
getScript(filePath: string, options: ScriptGetOptions): Promise<ScriptGetResult>
|
|
82
89
|
getPreloadLayers(filePath: string | readonly string[]): Promise<string[][]>
|
|
83
90
|
getHref(filePath: string): Promise<string>
|
|
84
|
-
|
|
91
|
+
classifyHmrFileEvent(filePath: string, event: ModuleWatchEvent): Promise<ScriptHmrUpdate[]>
|
|
92
|
+
invalidateFileEvent(filePath: string, event: ModuleWatchEvent): void
|
|
85
93
|
parseRequestPathname(pathname: string): ParsedRequestPathname | null
|
|
86
94
|
}
|
|
87
95
|
|
|
@@ -92,6 +100,26 @@ type ParsedRequestPathname = {
|
|
|
92
100
|
requestedFingerprint: string | null
|
|
93
101
|
}
|
|
94
102
|
|
|
103
|
+
export type ScriptHmrUpdate =
|
|
104
|
+
| {
|
|
105
|
+
accepted: false
|
|
106
|
+
filePath: string
|
|
107
|
+
path: string
|
|
108
|
+
timestamp: number
|
|
109
|
+
}
|
|
110
|
+
| {
|
|
111
|
+
accepted: true
|
|
112
|
+
acceptedPath: string
|
|
113
|
+
filePath: string
|
|
114
|
+
path: string
|
|
115
|
+
timestamp: number
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type ScriptHmrBoundary = {
|
|
119
|
+
acceptedModule: ResolvedModule
|
|
120
|
+
boundaryModule: ResolvedModule
|
|
121
|
+
}
|
|
122
|
+
|
|
95
123
|
const supportedScriptExtensionSet = new Set<string>(supportedScriptExtensions)
|
|
96
124
|
const preloadConcurrency = Math.max(1, Math.min(8, os.availableParallelism() - 1))
|
|
97
125
|
|
|
@@ -108,7 +136,14 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
108
136
|
ResolvedModule,
|
|
109
137
|
EmittedModule
|
|
110
138
|
>({
|
|
139
|
+
getAcceptedDependencies(resolvedModule) {
|
|
140
|
+
return resolvedModule.hmr.acceptedDeps.map((acceptedDep) => acceptedDep.depPath)
|
|
141
|
+
},
|
|
142
|
+
getDependencies(resolvedModule) {
|
|
143
|
+
return resolvedModule.deps
|
|
144
|
+
},
|
|
111
145
|
onWatchDirectoriesChange: options.onWatchDirectoriesChange,
|
|
146
|
+
onWatchFilesChange: options.onWatchFilesChange,
|
|
112
147
|
})
|
|
113
148
|
let tsconfigTransformOptionsResolver = createTsconfigTransformOptionsResolver()
|
|
114
149
|
let resolverFactory = new ResolverFactory({
|
|
@@ -128,6 +163,7 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
128
163
|
externalSet: resolvedOptions.externalSet,
|
|
129
164
|
isWatchIgnored,
|
|
130
165
|
minify: resolvedOptions.minify,
|
|
166
|
+
loaders: resolvedOptions.loaders,
|
|
131
167
|
resolveActualPath,
|
|
132
168
|
routes: resolvedOptions.routes,
|
|
133
169
|
sourceMapSourcePaths: resolvedOptions.sourceMapSourcePaths,
|
|
@@ -202,26 +238,38 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
202
238
|
return getServedUrl(resolvedModule.identityPath)
|
|
203
239
|
},
|
|
204
240
|
|
|
205
|
-
async
|
|
241
|
+
async classifyHmrFileEvent(filePath, event) {
|
|
206
242
|
let normalizedFilePath = normalizeFilePath(filePath)
|
|
207
|
-
if (isWatchIgnored(normalizedFilePath)) return
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
243
|
+
if (isWatchIgnored(normalizedFilePath)) return []
|
|
244
|
+
|
|
245
|
+
let timestamp = Date.now()
|
|
246
|
+
let previousResolvedModule = scriptStore.getLastResolved(normalizedFilePath)
|
|
247
|
+
let updatePathname = previousResolvedModule?.stableUrlPathname
|
|
248
|
+
|
|
249
|
+
invalidateScriptFileEvent(normalizedFilePath, event)
|
|
250
|
+
|
|
251
|
+
let resolvedModule =
|
|
252
|
+
event === 'change' && updatePathname
|
|
253
|
+
? await tryGetOrCreateResolvedScript(scriptStore.get(normalizedFilePath))
|
|
254
|
+
: undefined
|
|
255
|
+
let hmrUpdate =
|
|
256
|
+
event === 'change' && updatePathname
|
|
257
|
+
? getHmrUpdatesForChange(
|
|
258
|
+
resolvedModule ?? previousResolvedModule,
|
|
259
|
+
previousResolvedModule,
|
|
260
|
+
updatePathname,
|
|
261
|
+
timestamp,
|
|
262
|
+
)
|
|
263
|
+
: []
|
|
264
|
+
|
|
265
|
+
if (hmrUpdate.length > 0) {
|
|
266
|
+
resolvedOptions.hmr?.send(hmrUpdate)
|
|
222
267
|
}
|
|
268
|
+
return hmrUpdate
|
|
269
|
+
},
|
|
223
270
|
|
|
224
|
-
|
|
271
|
+
invalidateFileEvent(filePath, event) {
|
|
272
|
+
invalidateScriptFileEvent(normalizeFilePath(filePath), event)
|
|
225
273
|
},
|
|
226
274
|
|
|
227
275
|
parseRequestPathname(pathname) {
|
|
@@ -252,6 +300,27 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
252
300
|
return resolveFilePath(resolvedOptions.rootDir, filePath)
|
|
253
301
|
}
|
|
254
302
|
|
|
303
|
+
function invalidateScriptFileEvent(normalizedFilePath: string, event: ModuleWatchEvent): void {
|
|
304
|
+
if (isWatchIgnored(normalizedFilePath)) return
|
|
305
|
+
|
|
306
|
+
if (shouldClearResolverCacheForFileEvent(normalizedFilePath, event)) {
|
|
307
|
+
resolverFactory.clearCache()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (isTsconfigPath(normalizedFilePath)) {
|
|
311
|
+
tsconfigTransformOptionsResolver.clear()
|
|
312
|
+
scriptStore.invalidateAll()
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (isPackageJsonPath(normalizedFilePath)) {
|
|
317
|
+
scriptStore.invalidateAll()
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
scriptStore.invalidateForFileEvent(normalizedFilePath, event)
|
|
322
|
+
}
|
|
323
|
+
|
|
255
324
|
function resolveServedScriptOrThrow(absolutePath: string): ResolveModuleResult {
|
|
256
325
|
let resolvedModule = resolveModulePath(absolutePath)
|
|
257
326
|
if (!resolvedModule) {
|
|
@@ -262,7 +331,8 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
262
331
|
|
|
263
332
|
if (!resolvedOptions.isAllowed(resolvedModule.identityPath)) {
|
|
264
333
|
throw createAssetServerCompilationError(
|
|
265
|
-
`File is not allowed
|
|
334
|
+
`File "${resolvedModule.identityPath}" is not allowed by the asset server access configuration. ` +
|
|
335
|
+
`Add a matching allowFiles or allowPackages rule, or remove a conflicting denyFiles rule.`,
|
|
266
336
|
{
|
|
267
337
|
code: 'FILE_NOT_ALLOWED',
|
|
268
338
|
},
|
|
@@ -276,8 +346,14 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
276
346
|
record: ScriptRecord,
|
|
277
347
|
options: ScriptGetOptions,
|
|
278
348
|
): ScriptGetResult | null {
|
|
279
|
-
|
|
280
|
-
|
|
349
|
+
if (hasHmrTimestampedDependency(record.resolved)) {
|
|
350
|
+
return null
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (scriptStore.isEmittedFresh(record)) {
|
|
354
|
+
let current = getNotModifiedResult(record.emitted, options)
|
|
355
|
+
if (current) return current
|
|
356
|
+
}
|
|
281
357
|
|
|
282
358
|
if (!record.staleEmittedSnapshot || !isModuleSnapshotFresh(record.staleEmittedSnapshot)) {
|
|
283
359
|
return null
|
|
@@ -293,7 +369,7 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
293
369
|
}
|
|
294
370
|
|
|
295
371
|
async function getOrCreateResolvedScript(record: ScriptRecord): Promise<ResolvedModule> {
|
|
296
|
-
if (record.resolved) return record.resolved
|
|
372
|
+
if (record.resolved && scriptStore.isResolvedFresh(record)) return record.resolved
|
|
297
373
|
|
|
298
374
|
let cacheKey = getRecordCacheKey(record)
|
|
299
375
|
let existing = resolveInFlightByCacheKey.get(cacheKey)
|
|
@@ -339,8 +415,18 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
339
415
|
}
|
|
340
416
|
}
|
|
341
417
|
|
|
418
|
+
async function tryGetOrCreateResolvedScript(
|
|
419
|
+
record: ScriptRecord,
|
|
420
|
+
): Promise<ResolvedModule | undefined> {
|
|
421
|
+
try {
|
|
422
|
+
return await getOrCreateResolvedScript(record)
|
|
423
|
+
} catch {
|
|
424
|
+
return undefined
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
342
428
|
async function getOrCreateTransformedScript(record: ScriptRecord): Promise<TransformedModule> {
|
|
343
|
-
if (record.transformed) return record.transformed
|
|
429
|
+
if (record.transformed && scriptStore.isTransformedFresh(record)) return record.transformed
|
|
344
430
|
|
|
345
431
|
let startedVersion = record.invalidationVersion
|
|
346
432
|
let transformModuleResult = await transformModule(record, transformArgs)
|
|
@@ -362,7 +448,13 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
362
448
|
}
|
|
363
449
|
|
|
364
450
|
async function getOrCreateEmittedScript(record: ScriptRecord): Promise<EmittedModule> {
|
|
365
|
-
if (
|
|
451
|
+
if (
|
|
452
|
+
record.emitted &&
|
|
453
|
+
scriptStore.isEmittedFresh(record) &&
|
|
454
|
+
!hasHmrTimestampedDependency(record.resolved)
|
|
455
|
+
) {
|
|
456
|
+
return record.emitted
|
|
457
|
+
}
|
|
366
458
|
|
|
367
459
|
let cacheKey = getRecordCacheKey(record)
|
|
368
460
|
let existing = emitInFlightByCacheKey.get(cacheKey)
|
|
@@ -373,6 +465,9 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
373
465
|
let resolvedModule = await getOrCreateResolvedScript(record)
|
|
374
466
|
let emitResolvedModuleResult = await emitResolvedModule(resolvedModule, {
|
|
375
467
|
getServedUrl,
|
|
468
|
+
getStableUrl,
|
|
469
|
+
getHmrImportTimestamp,
|
|
470
|
+
hmrClientPathname: resolvedOptions.hmr?.clientPathname,
|
|
376
471
|
sourceMaps: resolvedOptions.sourceMaps,
|
|
377
472
|
})
|
|
378
473
|
|
|
@@ -415,11 +510,140 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
415
510
|
)
|
|
416
511
|
}
|
|
417
512
|
|
|
513
|
+
function getStableUrl(identityPath: string): string {
|
|
514
|
+
let stableUrlPathname = resolvedOptions.routes.toUrlPathname(identityPath)
|
|
515
|
+
if (!stableUrlPathname) {
|
|
516
|
+
throw createAssetServerCompilationError(
|
|
517
|
+
`File ${identityPath} is outside all configured fileMap entries.`,
|
|
518
|
+
{
|
|
519
|
+
code: 'FILE_OUTSIDE_FILE_MAP',
|
|
520
|
+
},
|
|
521
|
+
)
|
|
522
|
+
}
|
|
523
|
+
return stableUrlPathname
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function getHmrImportTimestamp(identityPath: string): number | null {
|
|
527
|
+
return scriptStore.getHmrUpdateTimestamp(identityPath) ?? null
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function hasHmrTimestampedDependency(resolvedModule: ResolvedModule | undefined): boolean {
|
|
531
|
+
return resolvedModule?.deps.some((depPath) => getHmrImportTimestamp(depPath) !== null) === true
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function getHmrUpdatesForChange(
|
|
535
|
+
resolvedModule: ResolvedModule | undefined,
|
|
536
|
+
previousResolvedModule: ResolvedModule | undefined,
|
|
537
|
+
updatePathname: string,
|
|
538
|
+
timestamp: number,
|
|
539
|
+
): ScriptHmrUpdate[] {
|
|
540
|
+
if (resolvedModule) {
|
|
541
|
+
scriptStore.setHmrUpdateTimestamp(resolvedModule.identityPath, timestamp)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (resolvedModule?.hmr.selfAccepting === true) {
|
|
545
|
+
return [
|
|
546
|
+
{
|
|
547
|
+
accepted: true,
|
|
548
|
+
acceptedPath: updatePathname,
|
|
549
|
+
filePath: resolvedModule.identityPath,
|
|
550
|
+
path: updatePathname,
|
|
551
|
+
timestamp,
|
|
552
|
+
},
|
|
553
|
+
]
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
let sourceFilePath = resolvedModule?.identityPath
|
|
557
|
+
let boundaries = findHmrBoundaries(sourceFilePath)
|
|
558
|
+
if (sourceFilePath !== undefined && boundaries) {
|
|
559
|
+
return dedupeHmrBoundaries(boundaries).map(({ acceptedModule, boundaryModule }) => ({
|
|
560
|
+
accepted: true,
|
|
561
|
+
acceptedPath: acceptedModule.stableUrlPathname,
|
|
562
|
+
filePath: sourceFilePath,
|
|
563
|
+
path: boundaryModule.stableUrlPathname,
|
|
564
|
+
timestamp,
|
|
565
|
+
}))
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return [
|
|
569
|
+
{
|
|
570
|
+
accepted: false,
|
|
571
|
+
filePath:
|
|
572
|
+
resolvedModule?.identityPath ?? previousResolvedModule?.identityPath ?? updatePathname,
|
|
573
|
+
path: updatePathname,
|
|
574
|
+
timestamp,
|
|
575
|
+
},
|
|
576
|
+
]
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function findHmrBoundaries(identityPath: string | undefined): ScriptHmrBoundary[] | null {
|
|
580
|
+
if (identityPath === undefined) return null
|
|
581
|
+
return propagateHmrUpdate(identityPath, new Set())
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function propagateHmrUpdate(
|
|
585
|
+
identityPath: string,
|
|
586
|
+
traversed: Set<string>,
|
|
587
|
+
): ScriptHmrBoundary[] | null {
|
|
588
|
+
if (traversed.has(identityPath)) return []
|
|
589
|
+
traversed.add(identityPath)
|
|
590
|
+
|
|
591
|
+
let resolvedModule = scriptStore.getLastResolved(identityPath)
|
|
592
|
+
if (!resolvedModule) return null
|
|
593
|
+
|
|
594
|
+
if (resolvedModule.hmr.selfAccepting) {
|
|
595
|
+
return [
|
|
596
|
+
{
|
|
597
|
+
acceptedModule: resolvedModule,
|
|
598
|
+
boundaryModule: resolvedModule,
|
|
599
|
+
},
|
|
600
|
+
]
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
let importerPaths = scriptStore.getImporters(identityPath)
|
|
604
|
+
if (!importerPaths || importerPaths.size === 0) return null
|
|
605
|
+
|
|
606
|
+
let acceptedImporterPaths = scriptStore.getAcceptedImporters(identityPath)
|
|
607
|
+
let boundaries: ScriptHmrBoundary[] = []
|
|
608
|
+
for (let importerPath of importerPaths) {
|
|
609
|
+
let importer = scriptStore.getLastResolved(importerPath)
|
|
610
|
+
if (!importer) return null
|
|
611
|
+
|
|
612
|
+
if (acceptedImporterPaths?.has(importerPath)) {
|
|
613
|
+
boundaries.push({
|
|
614
|
+
acceptedModule: resolvedModule,
|
|
615
|
+
boundaryModule: importer,
|
|
616
|
+
})
|
|
617
|
+
continue
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
let importerBoundaries = propagateHmrUpdate(importerPath, traversed)
|
|
621
|
+
if (!importerBoundaries) return null
|
|
622
|
+
boundaries.push(...importerBoundaries)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return boundaries
|
|
626
|
+
}
|
|
627
|
+
|
|
418
628
|
function isWatchIgnored(filePath: string): boolean {
|
|
419
629
|
return resolvedOptions.watchIgnoreMatchers.some((matcher) => matcher(filePath))
|
|
420
630
|
}
|
|
421
631
|
}
|
|
422
632
|
|
|
633
|
+
function dedupeHmrBoundaries(boundaries: ScriptHmrBoundary[]): ScriptHmrBoundary[] {
|
|
634
|
+
let seen = new Set<string>()
|
|
635
|
+
let result: ScriptHmrBoundary[] = []
|
|
636
|
+
|
|
637
|
+
for (let boundary of boundaries) {
|
|
638
|
+
let key = `${boundary.boundaryModule.identityPath}\0${boundary.acceptedModule.identityPath}`
|
|
639
|
+
if (seen.has(key)) continue
|
|
640
|
+
seen.add(key)
|
|
641
|
+
result.push(boundary)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return result
|
|
645
|
+
}
|
|
646
|
+
|
|
423
647
|
function getRecordCacheKey(record: ScriptRecord): string {
|
|
424
648
|
return `${record.identityPath}\0${record.invalidationVersion}`
|
|
425
649
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const scriptLoaderConditions = ['browser', 'import', 'module', 'default'] as const
|
package/src/lib/scripts/emit.ts
CHANGED
|
@@ -31,10 +31,19 @@ type EmitResult =
|
|
|
31
31
|
error: AssetServerCompilationError
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
type RewriteImportsOptions = {
|
|
35
|
+
getHmrImportTimestamp(identityPath: string): number | null
|
|
36
|
+
getServedUrl(identityPath: string): Promise<string>
|
|
37
|
+
getStableUrl(identityPath: string): string
|
|
38
|
+
}
|
|
39
|
+
|
|
34
40
|
export async function emitResolvedModule(
|
|
35
41
|
resolvedModule: ResolvedModule,
|
|
36
42
|
options: {
|
|
43
|
+
getHmrImportTimestamp(identityPath: string): number | null
|
|
37
44
|
getServedUrl(identityPath: string): Promise<string>
|
|
45
|
+
getStableUrl(identityPath: string): string
|
|
46
|
+
hmrClientPathname?: string
|
|
38
47
|
sourceMaps?: 'external' | 'inline'
|
|
39
48
|
},
|
|
40
49
|
): Promise<EmitResult> {
|
|
@@ -43,7 +52,7 @@ export async function emitResolvedModule(
|
|
|
43
52
|
resolvedModule.deps.map((depPath) => options.getServedUrl(depPath)),
|
|
44
53
|
)
|
|
45
54
|
let rewriteResult = await rewriteImports(resolvedModule, options)
|
|
46
|
-
let finalCode = rewriteResult.code
|
|
55
|
+
let finalCode = prependHmrContext(resolvedModule, rewriteResult.code, options)
|
|
47
56
|
|
|
48
57
|
if (rewriteResult.sourceMap) {
|
|
49
58
|
if (options.sourceMaps === 'inline') {
|
|
@@ -75,14 +84,16 @@ export async function emitResolvedModule(
|
|
|
75
84
|
|
|
76
85
|
async function rewriteImports(
|
|
77
86
|
resolvedModule: ResolvedModule,
|
|
78
|
-
options:
|
|
79
|
-
getServedUrl(identityPath: string): Promise<string>
|
|
80
|
-
},
|
|
87
|
+
options: RewriteImportsOptions,
|
|
81
88
|
): Promise<{ code: string; sourceMap: string | null }> {
|
|
82
89
|
let rewrittenSource = new MagicString(resolvedModule.rawCode)
|
|
83
90
|
|
|
84
91
|
for (let imported of resolvedModule.imports) {
|
|
85
92
|
let url = await options.getServedUrl(imported.depPath)
|
|
93
|
+
let hmrImportTimestamp = options.getHmrImportTimestamp(imported.depPath)
|
|
94
|
+
if (hmrImportTimestamp !== null) {
|
|
95
|
+
url = addTimestampQuery(url, hmrImportTimestamp)
|
|
96
|
+
}
|
|
86
97
|
rewrittenSource.overwrite(
|
|
87
98
|
imported.start,
|
|
88
99
|
imported.end,
|
|
@@ -90,9 +101,19 @@ async function rewriteImports(
|
|
|
90
101
|
)
|
|
91
102
|
}
|
|
92
103
|
|
|
104
|
+
for (let acceptedDep of resolvedModule.hmr.acceptedDeps) {
|
|
105
|
+
let url = options.getStableUrl(acceptedDep.depPath)
|
|
106
|
+
rewrittenSource.overwrite(
|
|
107
|
+
acceptedDep.start,
|
|
108
|
+
acceptedDep.end,
|
|
109
|
+
acceptedDep.quote ? `${acceptedDep.quote}${url}${acceptedDep.quote}` : url,
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
93
113
|
let code = rewrittenSource.toString()
|
|
94
114
|
let sourceMap =
|
|
95
|
-
resolvedModule.sourceMap &&
|
|
115
|
+
resolvedModule.sourceMap &&
|
|
116
|
+
(resolvedModule.imports.length > 0 || resolvedModule.hmr.acceptedDeps.length > 0)
|
|
96
117
|
? composeSourceMaps(
|
|
97
118
|
rewrittenSource.generateMap({ hires: true }).toString(),
|
|
98
119
|
resolvedModule.sourceMap,
|
|
@@ -102,6 +123,30 @@ async function rewriteImports(
|
|
|
102
123
|
return { code, sourceMap }
|
|
103
124
|
}
|
|
104
125
|
|
|
126
|
+
function addTimestampQuery(pathname: string, timestamp: number): string {
|
|
127
|
+
return `${pathname}${pathname.includes('?') ? '&' : '?'}t=${timestamp}`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function prependHmrContext(
|
|
131
|
+
resolvedModule: ResolvedModule,
|
|
132
|
+
code: string,
|
|
133
|
+
options: {
|
|
134
|
+
hmrClientPathname?: string
|
|
135
|
+
},
|
|
136
|
+
): string {
|
|
137
|
+
if (!options.hmrClientPathname || !resolvedModule.hmr.usesImportMetaHot) return code
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
`import { createHotContext as __remixCreateHotContext } from ${JSON.stringify(
|
|
141
|
+
options.hmrClientPathname,
|
|
142
|
+
)};\n` +
|
|
143
|
+
`import.meta.hot = __remixCreateHotContext(${JSON.stringify(
|
|
144
|
+
resolvedModule.stableUrlPathname,
|
|
145
|
+
)});\n` +
|
|
146
|
+
code
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
105
150
|
async function createEmittedAsset(content: string): Promise<EmittedAsset> {
|
|
106
151
|
return {
|
|
107
152
|
content,
|
|
@@ -37,6 +37,8 @@ type ResolvedImport = {
|
|
|
37
37
|
start: number
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
type ResolvedHmrAcceptedDependency = ResolvedImport
|
|
41
|
+
|
|
40
42
|
type RelativeImportResolution = {
|
|
41
43
|
candidatePaths: readonly string[]
|
|
42
44
|
candidatePrefixes: readonly string[]
|
|
@@ -50,6 +52,9 @@ type TrackedResolution = RelativeImportResolution & {
|
|
|
50
52
|
export type ResolvedModule = {
|
|
51
53
|
deps: string[]
|
|
52
54
|
fingerprint: string | null
|
|
55
|
+
hmr: Omit<TransformedModule['hmr'], 'acceptedDeps'> & {
|
|
56
|
+
acceptedDeps: ResolvedHmrAcceptedDependency[]
|
|
57
|
+
}
|
|
53
58
|
identityPath: string
|
|
54
59
|
imports: ResolvedImport[]
|
|
55
60
|
trackedFiles: string[]
|
|
@@ -116,6 +121,7 @@ export async function resolveModule(
|
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
let importsWithPaths: ResolvedImport[] = []
|
|
124
|
+
let acceptedDepsWithPaths: ResolvedHmrAcceptedDependency[] = []
|
|
119
125
|
let deps = new Set<string>()
|
|
120
126
|
|
|
121
127
|
for (let unresolved of transformed.unresolvedImports) {
|
|
@@ -163,8 +169,8 @@ export async function resolveModule(
|
|
|
163
169
|
if (!args.isAllowed(resolvedImport.identityPath)) {
|
|
164
170
|
return failResolve(
|
|
165
171
|
createAssetServerCompilationError(
|
|
166
|
-
`Import "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedImport.identityPath}", is not allowed by the asset server
|
|
167
|
-
`Add a matching
|
|
172
|
+
`Import "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedImport.identityPath}", is not allowed by the asset server access configuration. ` +
|
|
173
|
+
`Add a matching allowFiles or allowPackages rule, remove a conflicting denyFiles rule, or mark this import as external.`,
|
|
168
174
|
{
|
|
169
175
|
code: 'IMPORT_NOT_ALLOWED',
|
|
170
176
|
},
|
|
@@ -218,12 +224,124 @@ export async function resolveModule(
|
|
|
218
224
|
})
|
|
219
225
|
}
|
|
220
226
|
|
|
227
|
+
for (let unresolved of transformed.hmr.acceptedDeps) {
|
|
228
|
+
if (isBrowserExternalModuleUrl(unresolved.specifier)) continue
|
|
229
|
+
|
|
230
|
+
let displaySpecifier = getDisplayImportSpecifier(unresolved.specifier)
|
|
231
|
+
let trackedResolution = getTrackedRelativeImportResolution(
|
|
232
|
+
transformed.importerDir,
|
|
233
|
+
displaySpecifier,
|
|
234
|
+
args.isWatchIgnored,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
let resolvedSpec = resolvedImports.get(unresolved.specifier)
|
|
238
|
+
if (!resolvedSpec?.absolutePath) {
|
|
239
|
+
try {
|
|
240
|
+
resolvedSpec = await batchResolveSpecifiers(
|
|
241
|
+
[unresolved.specifier],
|
|
242
|
+
transformed.resolvedPath,
|
|
243
|
+
args.resolverFactory,
|
|
244
|
+
).then((resolved) => resolved.get(unresolved.specifier))
|
|
245
|
+
} catch (error) {
|
|
246
|
+
return failResolve(error, trackedFiles, trackedResolutions, transformed.resolvedPath, {
|
|
247
|
+
isWatchIgnored: args.isWatchIgnored,
|
|
248
|
+
trackedResolution,
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!resolvedSpec?.absolutePath) {
|
|
254
|
+
return failResolve(
|
|
255
|
+
createAssetServerCompilationError(
|
|
256
|
+
`Failed to resolve accepted HMR dependency "${displaySpecifier}" in ${transformed.resolvedPath}. ` +
|
|
257
|
+
`Ensure it resolves to a file within the configured asset server fileMap, or mark it as external.`,
|
|
258
|
+
{
|
|
259
|
+
code: 'IMPORT_RESOLUTION_FAILED',
|
|
260
|
+
},
|
|
261
|
+
),
|
|
262
|
+
trackedFiles,
|
|
263
|
+
trackedResolutions,
|
|
264
|
+
transformed.resolvedPath,
|
|
265
|
+
{ isWatchIgnored: args.isWatchIgnored, trackedResolution },
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let resolvedImport = args.resolveModulePath(resolvedSpec.absolutePath)
|
|
270
|
+
if (!resolvedImport) {
|
|
271
|
+
return failResolve(
|
|
272
|
+
createAssetServerCompilationError(
|
|
273
|
+
`Accepted HMR dependency "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedSpec.absolutePath}", is not a supported script file. ` +
|
|
274
|
+
`Supported extensions are ${supportedScriptExtensions.join(', ')}.`,
|
|
275
|
+
{
|
|
276
|
+
code: 'IMPORT_NOT_SUPPORTED',
|
|
277
|
+
},
|
|
278
|
+
),
|
|
279
|
+
trackedFiles,
|
|
280
|
+
trackedResolutions,
|
|
281
|
+
transformed.resolvedPath,
|
|
282
|
+
{ isWatchIgnored: args.isWatchIgnored, trackedResolution },
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (!args.isAllowed(resolvedImport.identityPath)) {
|
|
287
|
+
return failResolve(
|
|
288
|
+
createAssetServerCompilationError(
|
|
289
|
+
`Accepted HMR dependency "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedImport.identityPath}", is not allowed by the asset server allow/deny configuration. ` +
|
|
290
|
+
`Add a matching allow rule for this file path, remove a conflicting deny rule for this file path, or mark this import as external.`,
|
|
291
|
+
{
|
|
292
|
+
code: 'IMPORT_NOT_ALLOWED',
|
|
293
|
+
},
|
|
294
|
+
),
|
|
295
|
+
trackedFiles,
|
|
296
|
+
trackedResolutions,
|
|
297
|
+
transformed.resolvedPath,
|
|
298
|
+
{ isWatchIgnored: args.isWatchIgnored, trackedResolution },
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
let stableUrlPathname = args.routes.toUrlPathname(resolvedImport.identityPath)
|
|
303
|
+
if (!stableUrlPathname) {
|
|
304
|
+
return failResolve(
|
|
305
|
+
createAssetServerCompilationError(
|
|
306
|
+
`Accepted HMR dependency "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedImport.identityPath}", is outside all configured fileMap entries. ` +
|
|
307
|
+
`Add a matching fileMap entry for this file path, or mark this import as external.`,
|
|
308
|
+
{
|
|
309
|
+
code: 'IMPORT_OUTSIDE_FILE_MAP',
|
|
310
|
+
},
|
|
311
|
+
),
|
|
312
|
+
trackedFiles,
|
|
313
|
+
trackedResolutions,
|
|
314
|
+
transformed.resolvedPath,
|
|
315
|
+
{ isWatchIgnored: args.isWatchIgnored, trackedResolution },
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (trackedResolution) {
|
|
320
|
+
trackedResolutions.push({
|
|
321
|
+
...trackedResolution,
|
|
322
|
+
resolvedIdentityPath: resolvedImport.identityPath,
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
acceptedDepsWithPaths.push({
|
|
327
|
+
depPath: resolvedImport.identityPath,
|
|
328
|
+
end: unresolved.end,
|
|
329
|
+
quote: unresolved.quote,
|
|
330
|
+
start: unresolved.start,
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
221
334
|
return {
|
|
222
335
|
ok: true,
|
|
223
336
|
tracking: toResolveTracking(trackedFiles, trackedResolutions),
|
|
224
337
|
value: {
|
|
225
338
|
deps: [...deps],
|
|
226
339
|
fingerprint: transformed.fingerprint,
|
|
340
|
+
hmr: {
|
|
341
|
+
acceptedDeps: acceptedDepsWithPaths,
|
|
342
|
+
selfAccepting: transformed.hmr.selfAccepting,
|
|
343
|
+
usesImportMetaHot: transformed.hmr.usesImportMetaHot,
|
|
344
|
+
},
|
|
227
345
|
identityPath: record.identityPath,
|
|
228
346
|
imports: importsWithPaths,
|
|
229
347
|
trackedFiles: [...trackedFiles],
|
|
@@ -416,6 +534,10 @@ function getDisplayImportSpecifier(specifier: string): string {
|
|
|
416
534
|
return restoreAuthoredInjectedPackageSpecifier(specifier) ?? specifier
|
|
417
535
|
}
|
|
418
536
|
|
|
537
|
+
function isBrowserExternalModuleUrl(url: string): boolean {
|
|
538
|
+
return url.startsWith('data:') || url.startsWith('http://') || url.startsWith('https://')
|
|
539
|
+
}
|
|
540
|
+
|
|
419
541
|
function failResolve(
|
|
420
542
|
error: unknown,
|
|
421
543
|
trackedFiles: ReadonlySet<string>,
|