@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
|
@@ -17,6 +17,7 @@ import { normalizeFilePath } from '../paths.ts'
|
|
|
17
17
|
import type { CompiledRoutes } from '../routes.ts'
|
|
18
18
|
import type { ResolveModuleResult, TransformedModule } from './transform.ts'
|
|
19
19
|
import type { EmittedModule } from './emit.ts'
|
|
20
|
+
import { isBareImportSpecifier } from './specifiers.ts'
|
|
20
21
|
|
|
21
22
|
type ScriptRecord = ModuleRecord<TransformedModule, ResolvedModule, EmittedModule>
|
|
22
23
|
|
|
@@ -31,14 +32,24 @@ export const supportedScriptExtensions = ['.ts', '.tsx', '.js', '.jsx', '.mts',
|
|
|
31
32
|
const supportedScriptExtensionSet = new Set<string>(supportedScriptExtensions)
|
|
32
33
|
|
|
33
34
|
type ResolvedImport = {
|
|
35
|
+
compiledSpecifier: string
|
|
34
36
|
depPath: string
|
|
35
37
|
end: number
|
|
36
38
|
quote?: '"' | "'" | '`'
|
|
39
|
+
scopePathname?: string
|
|
40
|
+
specifier: string
|
|
37
41
|
start: number
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
type ResolvedHmrAcceptedDependency = ResolvedImport
|
|
41
45
|
|
|
46
|
+
type PendingBareImportScope = {
|
|
47
|
+
imported: ResolvedImport
|
|
48
|
+
resolvedIdentityPath: string
|
|
49
|
+
specifier: string
|
|
50
|
+
trackedResolution: RelativeImportResolution | null
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
type RelativeImportResolution = {
|
|
43
54
|
candidatePaths: readonly string[]
|
|
44
55
|
candidatePrefixes: readonly string[]
|
|
@@ -51,7 +62,6 @@ type TrackedResolution = RelativeImportResolution & {
|
|
|
51
62
|
|
|
52
63
|
export type ResolvedModule = {
|
|
53
64
|
deps: string[]
|
|
54
|
-
fingerprint: string | null
|
|
55
65
|
hmr: Omit<TransformedModule['hmr'], 'acceptedDeps'> & {
|
|
56
66
|
acceptedDeps: ResolvedHmrAcceptedDependency[]
|
|
57
67
|
}
|
|
@@ -61,6 +71,7 @@ export type ResolvedModule = {
|
|
|
61
71
|
rawCode: string
|
|
62
72
|
resolvedPath: string
|
|
63
73
|
sourceMap: string | null
|
|
74
|
+
staticDeps: string[]
|
|
64
75
|
stableUrlPathname: string
|
|
65
76
|
}
|
|
66
77
|
|
|
@@ -78,10 +89,13 @@ type ResolveResult = {
|
|
|
78
89
|
)
|
|
79
90
|
|
|
80
91
|
export type ResolveArgs = {
|
|
92
|
+
concurrency: number
|
|
93
|
+
isDirectoryResolutionFileIndependent(directory: string): boolean
|
|
81
94
|
isAllowed(absolutePath: string): boolean
|
|
82
95
|
isWatchIgnored(filePath: string): boolean
|
|
83
96
|
resolveModulePath(absolutePath: string): ResolveModuleResult | null
|
|
84
97
|
resolverFactory: ResolverFactory
|
|
98
|
+
resolveDirectorySpecifierIdentity(directory: string, specifier: string): Promise<string | null>
|
|
85
99
|
routes: CompiledRoutes
|
|
86
100
|
}
|
|
87
101
|
|
|
@@ -121,8 +135,10 @@ export async function resolveModule(
|
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
let importsWithPaths: ResolvedImport[] = []
|
|
138
|
+
let pendingBareImportScopes: PendingBareImportScope[] = []
|
|
124
139
|
let acceptedDepsWithPaths: ResolvedHmrAcceptedDependency[] = []
|
|
125
140
|
let deps = new Set<string>()
|
|
141
|
+
let staticDeps = new Set<string>()
|
|
126
142
|
|
|
127
143
|
for (let unresolved of transformed.unresolvedImports) {
|
|
128
144
|
let displaySpecifier = getDisplayImportSpecifier(unresolved.specifier)
|
|
@@ -200,6 +216,7 @@ export async function resolveModule(
|
|
|
200
216
|
}
|
|
201
217
|
|
|
202
218
|
deps.add(resolvedImport.identityPath)
|
|
219
|
+
if (!unresolved.dynamic) staticDeps.add(resolvedImport.identityPath)
|
|
203
220
|
|
|
204
221
|
if (transformed.packageSpecifiers.includes(unresolved.specifier)) {
|
|
205
222
|
let packageJsonPath =
|
|
@@ -216,12 +233,50 @@ export async function resolveModule(
|
|
|
216
233
|
})
|
|
217
234
|
}
|
|
218
235
|
|
|
219
|
-
|
|
236
|
+
let imported: ResolvedImport = {
|
|
237
|
+
compiledSpecifier: unresolved.specifier,
|
|
220
238
|
depPath: resolvedImport.identityPath,
|
|
221
239
|
end: unresolved.end,
|
|
222
240
|
quote: unresolved.quote,
|
|
241
|
+
specifier: displaySpecifier,
|
|
223
242
|
start: unresolved.start,
|
|
224
|
-
}
|
|
243
|
+
}
|
|
244
|
+
importsWithPaths.push(imported)
|
|
245
|
+
|
|
246
|
+
if (isBareImportSpecifier(displaySpecifier)) {
|
|
247
|
+
pendingBareImportScopes.push({
|
|
248
|
+
imported,
|
|
249
|
+
resolvedIdentityPath: resolvedImport.identityPath,
|
|
250
|
+
specifier: normalizeSpecifierResolution(unresolved.specifier, transformed.resolvedPath)
|
|
251
|
+
.specifier,
|
|
252
|
+
trackedResolution,
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (pendingBareImportScopes.length > 0) {
|
|
258
|
+
let scopeResults = await resolveBareImportScopes(
|
|
259
|
+
pendingBareImportScopes,
|
|
260
|
+
transformed.resolvedPath,
|
|
261
|
+
args,
|
|
262
|
+
)
|
|
263
|
+
for (let index = 0; index < scopeResults.length; index++) {
|
|
264
|
+
let result = scopeResults[index]
|
|
265
|
+
let pending = pendingBareImportScopes[index]
|
|
266
|
+
if (!result.ok) {
|
|
267
|
+
return failResolve(
|
|
268
|
+
result.error,
|
|
269
|
+
trackedFiles,
|
|
270
|
+
trackedResolutions,
|
|
271
|
+
transformed.resolvedPath,
|
|
272
|
+
{
|
|
273
|
+
isWatchIgnored: args.isWatchIgnored,
|
|
274
|
+
trackedResolution: pending.trackedResolution,
|
|
275
|
+
},
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
pending.imported.scopePathname = result.scopePathname
|
|
279
|
+
}
|
|
225
280
|
}
|
|
226
281
|
|
|
227
282
|
for (let unresolved of transformed.hmr.acceptedDeps) {
|
|
@@ -324,9 +379,11 @@ export async function resolveModule(
|
|
|
324
379
|
}
|
|
325
380
|
|
|
326
381
|
acceptedDepsWithPaths.push({
|
|
382
|
+
compiledSpecifier: unresolved.specifier,
|
|
327
383
|
depPath: resolvedImport.identityPath,
|
|
328
384
|
end: unresolved.end,
|
|
329
385
|
quote: unresolved.quote,
|
|
386
|
+
specifier: displaySpecifier,
|
|
330
387
|
start: unresolved.start,
|
|
331
388
|
})
|
|
332
389
|
}
|
|
@@ -336,7 +393,6 @@ export async function resolveModule(
|
|
|
336
393
|
tracking: toResolveTracking(trackedFiles, trackedResolutions),
|
|
337
394
|
value: {
|
|
338
395
|
deps: [...deps],
|
|
339
|
-
fingerprint: transformed.fingerprint,
|
|
340
396
|
hmr: {
|
|
341
397
|
acceptedDeps: acceptedDepsWithPaths,
|
|
342
398
|
selfAccepting: transformed.hmr.selfAccepting,
|
|
@@ -348,11 +404,117 @@ export async function resolveModule(
|
|
|
348
404
|
rawCode: transformed.rawCode,
|
|
349
405
|
resolvedPath: transformed.resolvedPath,
|
|
350
406
|
sourceMap: transformed.sourceMap,
|
|
407
|
+
staticDeps: [...staticDeps],
|
|
351
408
|
stableUrlPathname: transformed.stableUrlPathname,
|
|
352
409
|
},
|
|
353
410
|
}
|
|
354
411
|
}
|
|
355
412
|
|
|
413
|
+
async function resolveBareImportScopes(
|
|
414
|
+
pendingScopes: PendingBareImportScope[],
|
|
415
|
+
importerPath: string,
|
|
416
|
+
args: ResolveArgs,
|
|
417
|
+
): Promise<
|
|
418
|
+
({ ok: true; scopePathname: string } | { ok: false; error: AssetServerCompilationError })[]
|
|
419
|
+
> {
|
|
420
|
+
let results = new Array<
|
|
421
|
+
{ ok: true; scopePathname: string } | { ok: false; error: AssetServerCompilationError }
|
|
422
|
+
>(pendingScopes.length)
|
|
423
|
+
let nextIndex = 0
|
|
424
|
+
|
|
425
|
+
async function worker(): Promise<void> {
|
|
426
|
+
while (nextIndex < pendingScopes.length) {
|
|
427
|
+
let index = nextIndex++
|
|
428
|
+
let pending = pendingScopes[index]
|
|
429
|
+
try {
|
|
430
|
+
results[index] = {
|
|
431
|
+
ok: true,
|
|
432
|
+
scopePathname: await getBareImportScopePathname({
|
|
433
|
+
importerPath,
|
|
434
|
+
resolvedIdentityPath: pending.resolvedIdentityPath,
|
|
435
|
+
specifier: pending.specifier,
|
|
436
|
+
...args,
|
|
437
|
+
}),
|
|
438
|
+
}
|
|
439
|
+
} catch (error) {
|
|
440
|
+
results[index] = {
|
|
441
|
+
ok: false,
|
|
442
|
+
error: isAssetServerCompilationError(error)
|
|
443
|
+
? error
|
|
444
|
+
: createAssetServerCompilationError(
|
|
445
|
+
`Failed to determine an import map scope for "${pending.imported.specifier}" in ${importerPath}. ${formatUnknownError(error)}`,
|
|
446
|
+
{ cause: error, code: 'IMPORT_RESOLUTION_FAILED' },
|
|
447
|
+
),
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (pendingScopes.length === 1) {
|
|
454
|
+
await worker()
|
|
455
|
+
} else {
|
|
456
|
+
await Promise.all(
|
|
457
|
+
Array.from({ length: Math.min(args.concurrency, pendingScopes.length) }, () => worker()),
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
return results
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function getBareImportScopePathname(args: {
|
|
464
|
+
importerPath: string
|
|
465
|
+
isDirectoryResolutionFileIndependent(directory: string): boolean
|
|
466
|
+
resolvedIdentityPath: string
|
|
467
|
+
resolveDirectorySpecifierIdentity(directory: string, specifier: string): Promise<string | null>
|
|
468
|
+
routes: CompiledRoutes
|
|
469
|
+
specifier: string
|
|
470
|
+
}): Promise<string> {
|
|
471
|
+
let importerDirectory = normalizeFilePath(path.dirname(args.importerPath))
|
|
472
|
+
let importerScopePathname = args.routes.toUrlPathname(importerDirectory)
|
|
473
|
+
if (!importerScopePathname) {
|
|
474
|
+
throw new Error(`Expected a URL pathname for ${importerDirectory}`)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
let importerDirectoryResolution: string | null = args.resolvedIdentityPath
|
|
478
|
+
if (!args.isDirectoryResolutionFileIndependent(importerDirectory)) {
|
|
479
|
+
importerDirectoryResolution = await args.resolveDirectorySpecifierIdentity(
|
|
480
|
+
importerDirectory,
|
|
481
|
+
args.specifier,
|
|
482
|
+
)
|
|
483
|
+
if (importerDirectoryResolution !== args.resolvedIdentityPath) {
|
|
484
|
+
throw createAssetServerCompilationError(
|
|
485
|
+
`Bare import "${args.specifier}" in ${args.importerPath} resolves differently based on the importer file. ` +
|
|
486
|
+
`Browser module resolution must be uniform for all files in the same directory.`,
|
|
487
|
+
{ code: 'IMPORT_RESOLUTION_NOT_DIRECTORY_UNIFORM' },
|
|
488
|
+
)
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
let directory = importerDirectory
|
|
493
|
+
let scopePathname = importerScopePathname
|
|
494
|
+
let broadestScopePathname = importerScopePathname
|
|
495
|
+
while (true) {
|
|
496
|
+
let resolvedIdentityPath =
|
|
497
|
+
directory === importerDirectory
|
|
498
|
+
? importerDirectoryResolution
|
|
499
|
+
: await args.resolveDirectorySpecifierIdentity(directory, args.specifier)
|
|
500
|
+
if (resolvedIdentityPath !== args.resolvedIdentityPath) break
|
|
501
|
+
|
|
502
|
+
broadestScopePathname = scopePathname
|
|
503
|
+
let parentDirectory = normalizeFilePath(path.dirname(directory))
|
|
504
|
+
if (parentDirectory === directory) break
|
|
505
|
+
let parentScopePathname = args.routes.toUrlPathname(parentDirectory)
|
|
506
|
+
if (!parentScopePathname) break
|
|
507
|
+
directory = parentDirectory
|
|
508
|
+
scopePathname = parentScopePathname
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return ensureTrailingSlash(broadestScopePathname)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function ensureTrailingSlash(value: string): string {
|
|
515
|
+
return value.endsWith('/') ? value : `${value}/`
|
|
516
|
+
}
|
|
517
|
+
|
|
356
518
|
function findNearestPackageJsonPath(filePath: string): string | null {
|
|
357
519
|
let directory = path.dirname(filePath)
|
|
358
520
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function isBareImportSpecifier(specifier: string): boolean {
|
|
2
|
+
return (
|
|
3
|
+
!specifier.startsWith('./') &&
|
|
4
|
+
!specifier.startsWith('../') &&
|
|
5
|
+
!specifier.startsWith('/') &&
|
|
6
|
+
!specifier.startsWith('file:') &&
|
|
7
|
+
!specifier.startsWith('data:') &&
|
|
8
|
+
!specifier.startsWith('http://') &&
|
|
9
|
+
!specifier.startsWith('https://')
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
isAssetServerCompilationError,
|
|
19
19
|
} from '../compilation-error.ts'
|
|
20
20
|
import type { AssetServerCompilationError } from '../compilation-error.ts'
|
|
21
|
-
import { generateFingerprint } from '../fingerprint.ts'
|
|
22
21
|
import {
|
|
23
22
|
maskAuthoredInjectedPackageSpecifier,
|
|
24
23
|
mayContainInjectedPackageSpecifier,
|
|
@@ -38,6 +37,7 @@ import type { EmittedModule } from './emit.ts'
|
|
|
38
37
|
import type { ResolvedScriptTarget } from '../target.ts'
|
|
39
38
|
import type { ResolvedModule } from './resolve.ts'
|
|
40
39
|
import { scriptLoaderConditions } from './conditions.ts'
|
|
40
|
+
import { isBareImportSpecifier } from './specifiers.ts'
|
|
41
41
|
|
|
42
42
|
type ScriptRecord = ModuleRecord<TransformedModule, ResolvedModule, EmittedModule>
|
|
43
43
|
|
|
@@ -74,16 +74,16 @@ export type ResolveModuleResult = {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
type UnresolvedImport = {
|
|
77
|
+
dynamic: boolean
|
|
77
78
|
end: number
|
|
78
79
|
quote?: '"' | "'" | '`'
|
|
79
80
|
specifier: string
|
|
80
81
|
start: number
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
type HmrAcceptedDependency = UnresolvedImport
|
|
84
|
+
type HmrAcceptedDependency = Omit<UnresolvedImport, 'dynamic'>
|
|
84
85
|
|
|
85
86
|
export type TransformedModule = {
|
|
86
|
-
fingerprint: string | null
|
|
87
87
|
hmr: {
|
|
88
88
|
acceptedDeps: HmrAcceptedDependency[]
|
|
89
89
|
selfAccepting: boolean
|
|
@@ -121,7 +121,6 @@ type TsconfigTransformOptions = {
|
|
|
121
121
|
type TsconfigTransformOptionsResolver = ReturnType<typeof createTsconfigTransformOptionsResolver>
|
|
122
122
|
|
|
123
123
|
export type TransformArgs = {
|
|
124
|
-
buildId: string | null
|
|
125
124
|
define: Record<string, string> | null
|
|
126
125
|
externalSet: ReadonlySet<string>
|
|
127
126
|
isWatchIgnored(filePath: string): boolean
|
|
@@ -272,13 +271,6 @@ export async function transformModule(
|
|
|
272
271
|
trackedFiles,
|
|
273
272
|
},
|
|
274
273
|
value: {
|
|
275
|
-
fingerprint:
|
|
276
|
-
args.buildId === null
|
|
277
|
-
? null
|
|
278
|
-
: await generateFingerprint({
|
|
279
|
-
buildId: args.buildId,
|
|
280
|
-
content: sourceText,
|
|
281
|
-
}),
|
|
282
274
|
hmr: getHmrAnalysis(analysis.rawCode),
|
|
283
275
|
identityPath: record.identityPath,
|
|
284
276
|
importerDir: path.dirname(resolvedPath),
|
|
@@ -434,18 +426,6 @@ function findNearestTsconfigPath(directory: string): string | null {
|
|
|
434
426
|
}
|
|
435
427
|
}
|
|
436
428
|
|
|
437
|
-
function isBareImportSpecifier(specifier: string): boolean {
|
|
438
|
-
return (
|
|
439
|
-
!specifier.startsWith('./') &&
|
|
440
|
-
!specifier.startsWith('../') &&
|
|
441
|
-
!specifier.startsWith('/') &&
|
|
442
|
-
!specifier.startsWith('file:') &&
|
|
443
|
-
!specifier.startsWith('data:') &&
|
|
444
|
-
!specifier.startsWith('http://') &&
|
|
445
|
-
!specifier.startsWith('https://')
|
|
446
|
-
)
|
|
447
|
-
}
|
|
448
|
-
|
|
449
429
|
async function analyzeModuleSource(
|
|
450
430
|
sourceText: string,
|
|
451
431
|
resolvedPath: string,
|
|
@@ -823,6 +803,7 @@ async function getUnresolvedImportsFromLexer(rawCode: string): Promise<Unresolve
|
|
|
823
803
|
let specifier = getStaticImportSpecifier(rawCode, imported)
|
|
824
804
|
if (specifier == null || isBrowserExternalModuleUrl(specifier)) continue
|
|
825
805
|
unresolvedImports.push({
|
|
806
|
+
dynamic: imported.d !== -1,
|
|
826
807
|
specifier,
|
|
827
808
|
start: imported.s,
|
|
828
809
|
end: imported.e,
|
|
@@ -2,6 +2,7 @@ import * as os from 'node:os'
|
|
|
2
2
|
import * as path from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
import { IfNoneMatch } from '@remix-run/headers/if-none-match'
|
|
5
|
+
import { createAssetServerCompilationError } from '../compilation-error.ts'
|
|
5
6
|
import { createFileMatcher } from '../file-matcher.ts'
|
|
6
7
|
import { formatFingerprintedPathname } from '../fingerprint.ts'
|
|
7
8
|
import { createModuleStore } from '../module-store.ts'
|
|
@@ -18,6 +19,11 @@ import type { TransformArgs, TransformedStyle } from './transform.ts'
|
|
|
18
19
|
|
|
19
20
|
type StyleRecord = ModuleRecord<TransformedStyle, ResolvedStyle, EmittedStyle>
|
|
20
21
|
type StyleStore = ModuleStore<TransformedStyle, ResolvedStyle, EmittedStyle>
|
|
22
|
+
type ResolvedStyleGraphEntry = {
|
|
23
|
+
invalidationVersion: number
|
|
24
|
+
resolvedStyle: ResolvedStyle
|
|
25
|
+
}
|
|
26
|
+
type ResolvedStyleGraph = ReadonlyMap<string, ResolvedStyleGraphEntry>
|
|
21
27
|
|
|
22
28
|
type StyleCompileResult = {
|
|
23
29
|
code: EmittedAsset
|
|
@@ -42,7 +48,6 @@ type StyleGetOptions = {
|
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
type StyleCompilerOptions = {
|
|
45
|
-
buildId?: string
|
|
46
51
|
fingerprintAssets: boolean
|
|
47
52
|
getServedFileUrl?(
|
|
48
53
|
identityPath: string,
|
|
@@ -109,7 +114,6 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
109
114
|
routes: resolvedOptions.routes,
|
|
110
115
|
}
|
|
111
116
|
let transformArgs: TransformArgs = {
|
|
112
|
-
buildId: resolvedOptions.buildId ?? null,
|
|
113
117
|
isWatchIgnored,
|
|
114
118
|
minify: resolvedOptions.minify,
|
|
115
119
|
routes: resolvedOptions.routes,
|
|
@@ -121,7 +125,8 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
121
125
|
return {
|
|
122
126
|
async getHref(filePath) {
|
|
123
127
|
let resolvedStyle = resolveServedStyleOrThrow(resolveInputFilePath(filePath), resolveArgs)
|
|
124
|
-
|
|
128
|
+
let graph = await resolveStyleGraph(resolvedStyle.identityPath)
|
|
129
|
+
return getServedUrlFromGraph(resolvedStyle.identityPath, graph)
|
|
125
130
|
},
|
|
126
131
|
|
|
127
132
|
async getPreloadLayers(filePath) {
|
|
@@ -139,17 +144,18 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
139
144
|
let visited = new Set(resolvedEntries)
|
|
140
145
|
let queue = [...resolvedEntries]
|
|
141
146
|
let layers: string[][] = []
|
|
147
|
+
let graph = await resolveStyleGraph(resolvedEntries)
|
|
142
148
|
|
|
143
149
|
while (queue.length > 0) {
|
|
144
150
|
let frontier = queue
|
|
145
151
|
queue = []
|
|
146
|
-
let resolvedStyles = await getOrCreateResolvedStyles(
|
|
147
|
-
frontier.map((identityPath) => styleStore.get(identityPath)),
|
|
148
|
-
)
|
|
149
152
|
let layer: string[] = []
|
|
150
153
|
|
|
151
|
-
for (let
|
|
152
|
-
|
|
154
|
+
for (let identityPath of frontier) {
|
|
155
|
+
let graphEntry = graph.get(identityPath)
|
|
156
|
+
if (!graphEntry) throw new Error(`Missing resolved style ${identityPath}`)
|
|
157
|
+
let resolvedStyle = graphEntry.resolvedStyle
|
|
158
|
+
layer.push(await getServedUrlFromGraph(identityPath, graph))
|
|
153
159
|
|
|
154
160
|
for (let dep of resolvedStyle.deps) {
|
|
155
161
|
if (visited.has(dep)) continue
|
|
@@ -220,12 +226,6 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
220
226
|
return resolveFilePath(resolvedOptions.rootDir, filePath)
|
|
221
227
|
}
|
|
222
228
|
|
|
223
|
-
async function getOrCreateResolvedStyles(records: StyleRecord[]): Promise<ResolvedStyle[]> {
|
|
224
|
-
return mapWithConcurrency(records, preloadConcurrency, (record) =>
|
|
225
|
-
getOrCreateResolvedStyle(record),
|
|
226
|
-
)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
229
|
async function getOrCreateResolvedStyle(record: StyleRecord): Promise<ResolvedStyle> {
|
|
230
230
|
if (record.resolved && styleStore.isResolvedFresh(record)) return record.resolved
|
|
231
231
|
|
|
@@ -296,16 +296,39 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
296
296
|
return record.emitted
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
-
let
|
|
299
|
+
let graph = await resolveStyleGraph(record.identityPath)
|
|
300
|
+
return getOrCreateEmittedStyleFromGraph(record, graph)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function getOrCreateEmittedStyleFromGraph(
|
|
304
|
+
record: StyleRecord,
|
|
305
|
+
graph: ResolvedStyleGraph,
|
|
306
|
+
): Promise<EmittedStyle> {
|
|
307
|
+
let graphEntry = graph.get(record.identityPath)
|
|
308
|
+
if (!graphEntry) {
|
|
309
|
+
throw new Error(`Missing resolved style ${record.identityPath}`)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (
|
|
313
|
+
record.invalidationVersion === graphEntry.invalidationVersion &&
|
|
314
|
+
record.emitted &&
|
|
315
|
+
styleStore.isEmittedFresh(record) &&
|
|
316
|
+
!hasHmrTimestampedDependency(record.resolved)
|
|
317
|
+
) {
|
|
318
|
+
return record.emitted
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let cacheKey = getCacheKey(record.identityPath, graphEntry.invalidationVersion)
|
|
300
322
|
let existing = emitInFlightByCacheKey.get(cacheKey)
|
|
301
323
|
if (existing) return existing
|
|
302
324
|
|
|
303
325
|
let promise = (async () => {
|
|
304
|
-
let
|
|
305
|
-
|
|
306
|
-
let emitResolvedStyleResult = await emitResolvedStyle(resolvedStyle, {
|
|
326
|
+
let emitResolvedStyleResult = await emitResolvedStyle(graphEntry.resolvedStyle, {
|
|
327
|
+
fingerprintAssets: resolvedOptions.fingerprintAssets,
|
|
307
328
|
getServedFileUrl: resolvedOptions.getServedFileUrl,
|
|
308
|
-
getServedUrl
|
|
329
|
+
getServedUrl(identityPath) {
|
|
330
|
+
return getServedUrlFromGraph(identityPath, graph)
|
|
331
|
+
},
|
|
309
332
|
sourceMaps: resolvedOptions.sourceMaps,
|
|
310
333
|
})
|
|
311
334
|
|
|
@@ -313,7 +336,7 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
313
336
|
throw emitResolvedStyleResult.error
|
|
314
337
|
}
|
|
315
338
|
|
|
316
|
-
if (isFresh(record,
|
|
339
|
+
if (isFresh(record, graphEntry.invalidationVersion)) {
|
|
317
340
|
styleStore.setEmitted(record.identityPath, emitResolvedStyleResult.value, null)
|
|
318
341
|
}
|
|
319
342
|
|
|
@@ -331,21 +354,95 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
331
354
|
}
|
|
332
355
|
}
|
|
333
356
|
|
|
334
|
-
async function
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
357
|
+
async function getServedUrlFromGraph(
|
|
358
|
+
identityPath: string,
|
|
359
|
+
graph: ResolvedStyleGraph,
|
|
360
|
+
): Promise<string> {
|
|
361
|
+
let graphEntry = graph.get(identityPath)
|
|
362
|
+
if (!graphEntry) throw new Error(`Missing resolved style ${identityPath}`)
|
|
363
|
+
let pathname = graphEntry.resolvedStyle.stableUrlPathname
|
|
364
|
+
|
|
365
|
+
if (resolvedOptions.fingerprintAssets) {
|
|
366
|
+
let emittedStyle = await getOrCreateEmittedStyleFromGraph(styleStore.get(identityPath), graph)
|
|
367
|
+
pathname = formatFingerprintedPathname(
|
|
368
|
+
graphEntry.resolvedStyle.stableUrlPathname,
|
|
369
|
+
emittedStyle.fingerprint,
|
|
370
|
+
)
|
|
371
|
+
}
|
|
339
372
|
|
|
340
|
-
|
|
341
|
-
let pathname = formatFingerprintedPathname(
|
|
342
|
-
resolvedStyle.stableUrlPathname,
|
|
343
|
-
resolvedOptions.fingerprintAssets ? resolvedStyle.fingerprint : null,
|
|
344
|
-
)
|
|
345
|
-
let timestamp = styleStore.getHmrUpdateTimestamp(resolvedStyle.identityPath)
|
|
373
|
+
let timestamp = styleStore.getHmrUpdateTimestamp(graphEntry.resolvedStyle.identityPath)
|
|
346
374
|
return timestamp ? appendTimestamp(pathname, timestamp) : pathname
|
|
347
375
|
}
|
|
348
376
|
|
|
377
|
+
async function resolveStyleGraph(
|
|
378
|
+
rootPath: string | readonly string[],
|
|
379
|
+
): Promise<ResolvedStyleGraph> {
|
|
380
|
+
let resolvedByPath = new Map<string, ResolvedStyleGraphEntry>()
|
|
381
|
+
let rootPaths = Array.isArray(rootPath) ? rootPath : [rootPath]
|
|
382
|
+
let discovered = new Set(rootPaths)
|
|
383
|
+
let queue = [...rootPaths]
|
|
384
|
+
|
|
385
|
+
while (queue.length > 0) {
|
|
386
|
+
let frontier = queue
|
|
387
|
+
queue = []
|
|
388
|
+
let graphEntries = await mapWithConcurrency(
|
|
389
|
+
frontier,
|
|
390
|
+
preloadConcurrency,
|
|
391
|
+
async (identityPath): Promise<ResolvedStyleGraphEntry> => {
|
|
392
|
+
let record = styleStore.get(identityPath)
|
|
393
|
+
let invalidationVersion = record.invalidationVersion
|
|
394
|
+
let resolvedStyle = await getOrCreateResolvedStyle(record)
|
|
395
|
+
return {
|
|
396
|
+
invalidationVersion,
|
|
397
|
+
resolvedStyle,
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
for (let [index, identityPath] of frontier.entries()) {
|
|
403
|
+
let graphEntry = graphEntries[index]
|
|
404
|
+
resolvedByPath.set(identityPath, graphEntry)
|
|
405
|
+
for (let depPath of graphEntry.resolvedStyle.deps) {
|
|
406
|
+
if (discovered.has(depPath)) continue
|
|
407
|
+
discovered.add(depPath)
|
|
408
|
+
queue.push(depPath)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
assertNoCircularImports(resolvedByPath, rootPaths)
|
|
414
|
+
return resolvedByPath
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function assertNoCircularImports(graph: ResolvedStyleGraph, rootPaths: readonly string[]): void {
|
|
418
|
+
let visited = new Set<string>()
|
|
419
|
+
let path: string[] = []
|
|
420
|
+
let pathIndexByIdentity = new Map<string, number>()
|
|
421
|
+
|
|
422
|
+
function visit(identityPath: string): void {
|
|
423
|
+
let cycleStart = pathIndexByIdentity.get(identityPath)
|
|
424
|
+
if (cycleStart !== undefined) {
|
|
425
|
+
let cycle = [...path.slice(cycleStart), identityPath]
|
|
426
|
+
throw createAssetServerCompilationError(
|
|
427
|
+
`Circular CSS imports are not supported: ${cycle.join(' -> ')}`,
|
|
428
|
+
{ code: 'EMIT_FAILED' },
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
if (visited.has(identityPath)) return
|
|
432
|
+
|
|
433
|
+
let graphEntry = graph.get(identityPath)
|
|
434
|
+
if (!graphEntry) throw new Error(`Missing resolved style ${identityPath}`)
|
|
435
|
+
pathIndexByIdentity.set(identityPath, path.length)
|
|
436
|
+
path.push(identityPath)
|
|
437
|
+
for (let depPath of graphEntry.resolvedStyle.deps) visit(depPath)
|
|
438
|
+
path.pop()
|
|
439
|
+
pathIndexByIdentity.delete(identityPath)
|
|
440
|
+
visited.add(identityPath)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
for (let identityPath of rootPaths) visit(identityPath)
|
|
444
|
+
}
|
|
445
|
+
|
|
349
446
|
function getHmrUpdatePathnames(identityPath: string, timestamp: number): string[] {
|
|
350
447
|
let resolvedStyles = findHmrUpdateStyles(identityPath, new Set())
|
|
351
448
|
// Mark update timestamps before invalidating so re-emitted importer stylesheets
|
|
@@ -389,7 +486,11 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
389
486
|
}
|
|
390
487
|
|
|
391
488
|
function getRecordCacheKey(record: StyleRecord): string {
|
|
392
|
-
return
|
|
489
|
+
return getCacheKey(record.identityPath, record.invalidationVersion)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function getCacheKey(identityPath: string, version: number): string {
|
|
493
|
+
return `${identityPath}\0${version}`
|
|
393
494
|
}
|
|
394
495
|
|
|
395
496
|
function isFresh(record: StyleRecord, version: number): boolean {
|
|
@@ -407,16 +508,13 @@ function getNotModifiedStyle(
|
|
|
407
508
|
let emittedStyle = record.emitted
|
|
408
509
|
if (!emittedStyle || options.ifNoneMatch === null) return null
|
|
409
510
|
|
|
410
|
-
if (
|
|
411
|
-
options.requestedFingerprint !== null &&
|
|
412
|
-
emittedStyle.fingerprint !== options.requestedFingerprint
|
|
413
|
-
) {
|
|
414
|
-
return null
|
|
415
|
-
}
|
|
416
|
-
|
|
417
511
|
let asset = getEmittedAssetForRequest(emittedStyle, options.isSourceMapRequest)
|
|
418
512
|
if (!asset) return null
|
|
419
513
|
|
|
514
|
+
if (options.requestedFingerprint !== null && asset.fingerprint !== options.requestedFingerprint) {
|
|
515
|
+
return null
|
|
516
|
+
}
|
|
517
|
+
|
|
420
518
|
if (!IfNoneMatch.from(options.ifNoneMatch).matches(asset.etag)) return null
|
|
421
519
|
return { etag: asset.etag, type: 'not-modified' }
|
|
422
520
|
}
|