@remix-run/assets 0.5.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 +185 -104
- package/dist/assets.d.ts +4 -1
- package/dist/assets.d.ts.map +1 -1
- package/dist/lib/access.d.ts +29 -2
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +52 -26
- package/dist/lib/asset-server.d.ts +61 -25
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +127 -55
- 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 +28 -31
- 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/injected-packages.d.ts +3 -2
- package/dist/lib/injected-packages.d.ts.map +1 -1
- package/dist/lib/injected-packages.js +11 -8
- package/dist/lib/inspection.d.ts +39 -0
- package/dist/lib/inspection.d.ts.map +1 -0
- package/dist/lib/inspection.js +160 -0
- package/dist/lib/routes.d.ts +12 -3
- package/dist/lib/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +124 -80
- 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 +188 -38
- 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 +119 -12
- 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 +4 -18
- 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 +8 -9
- 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 +2 -9
- 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 -6
- package/src/assets.ts +9 -1
- package/src/lib/access.ts +83 -30
- package/src/lib/asset-server.ts +224 -94
- package/src/lib/compilation-error.ts +4 -3
- package/src/lib/files/compiler.ts +32 -42
- 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/injected-packages.ts +16 -10
- package/src/lib/inspection.ts +229 -0
- package/src/lib/routes.ts +158 -126
- package/src/lib/scripts/compiler.ts +240 -54
- package/src/lib/scripts/emit.ts +34 -19
- package/src/lib/scripts/resolve.ts +175 -13
- package/src/lib/scripts/specifiers.ts +11 -0
- package/src/lib/scripts/transform.ts +6 -25
- package/src/lib/styles/compiler.ts +137 -39
- package/src/lib/styles/emit.ts +18 -13
- package/src/lib/styles/resolve.ts +8 -10
- package/src/lib/styles/transform.ts +2 -12
- package/src/lib/virtual-store.ts +124 -0
|
@@ -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
|
}
|
package/src/lib/styles/emit.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
createAssetServerCompilationError,
|
|
5
5
|
isAssetServerCompilationError,
|
|
6
6
|
} from '../compilation-error.ts'
|
|
7
|
-
import { hashContent } from '../fingerprint.ts'
|
|
7
|
+
import { formatFingerprintedPathname, hashContent } from '../fingerprint.ts'
|
|
8
8
|
import { composeSourceMaps } from '../source-maps.ts'
|
|
9
9
|
import type { AssetServerCompilationError } from '../compilation-error.ts'
|
|
10
10
|
import type { ResolvedStyle } from './resolve.ts'
|
|
@@ -12,12 +12,12 @@ import type { ResolvedStyle } from './resolve.ts'
|
|
|
12
12
|
export type EmittedAsset = {
|
|
13
13
|
content: string
|
|
14
14
|
etag: string
|
|
15
|
+
fingerprint: string
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export type EmittedStyle = {
|
|
18
19
|
code: EmittedAsset
|
|
19
20
|
fingerprint: string | null
|
|
20
|
-
importUrls: string[]
|
|
21
21
|
sourceMap: EmittedAsset | null
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -34,6 +34,7 @@ type EmitResult =
|
|
|
34
34
|
export async function emitResolvedStyle(
|
|
35
35
|
resolvedStyle: ResolvedStyle,
|
|
36
36
|
options: {
|
|
37
|
+
fingerprintAssets: boolean
|
|
37
38
|
getServedFileUrl?(
|
|
38
39
|
identityPath: string,
|
|
39
40
|
options: {
|
|
@@ -45,30 +46,32 @@ export async function emitResolvedStyle(
|
|
|
45
46
|
},
|
|
46
47
|
): Promise<EmitResult> {
|
|
47
48
|
try {
|
|
48
|
-
let importUrls = await Promise.all(
|
|
49
|
-
resolvedStyle.deps.map((depPath) => options.getServedUrl(depPath)),
|
|
50
|
-
)
|
|
51
49
|
let rewriteResult = await rewriteDependencies(resolvedStyle, options)
|
|
52
50
|
let finalCode = rewriteResult.code
|
|
51
|
+
let sourceMap = rewriteResult.sourceMap
|
|
52
|
+
? await createEmittedAsset(rewriteResult.sourceMap)
|
|
53
|
+
: null
|
|
53
54
|
|
|
54
55
|
if (rewriteResult.sourceMap) {
|
|
55
56
|
if (options.sourceMaps === 'inline') {
|
|
56
57
|
let encoded = Buffer.from(rewriteResult.sourceMap).toString('base64')
|
|
57
58
|
finalCode += `\n/*# sourceMappingURL=data:application/json;base64,${encoded} */`
|
|
58
59
|
} else if (options.sourceMaps === 'external') {
|
|
59
|
-
finalCode += `\n/*# sourceMappingURL=${
|
|
60
|
+
finalCode += `\n/*# sourceMappingURL=${formatFingerprintedPathname(
|
|
61
|
+
resolvedStyle.stableUrlPathname,
|
|
62
|
+
options.fingerprintAssets && sourceMap ? sourceMap.fingerprint : null,
|
|
63
|
+
)}.map */`
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
let code = await createEmittedAsset(finalCode)
|
|
68
|
+
|
|
63
69
|
return {
|
|
64
70
|
ok: true,
|
|
65
71
|
value: {
|
|
66
|
-
code
|
|
67
|
-
fingerprint:
|
|
68
|
-
|
|
69
|
-
sourceMap: rewriteResult.sourceMap
|
|
70
|
-
? await createEmittedAsset(rewriteResult.sourceMap)
|
|
71
|
-
: null,
|
|
72
|
+
code,
|
|
73
|
+
fingerprint: options.fingerprintAssets ? code.fingerprint : null,
|
|
74
|
+
sourceMap,
|
|
72
75
|
},
|
|
73
76
|
}
|
|
74
77
|
} catch (error) {
|
|
@@ -192,9 +195,11 @@ async function getServedFileUrl(
|
|
|
192
195
|
}
|
|
193
196
|
|
|
194
197
|
async function createEmittedAsset(content: string): Promise<EmittedAsset> {
|
|
198
|
+
let fingerprint = await hashContent(content)
|
|
195
199
|
return {
|
|
196
200
|
content,
|
|
197
|
-
etag: `W/"${
|
|
201
|
+
etag: `W/"${fingerprint}"`,
|
|
202
|
+
fingerprint,
|
|
198
203
|
}
|
|
199
204
|
}
|
|
200
205
|
|
|
@@ -38,7 +38,6 @@ type ResolvedDependency =
|
|
|
38
38
|
export type ResolvedStyle = {
|
|
39
39
|
dependencies: ResolvedDependency[]
|
|
40
40
|
deps: string[]
|
|
41
|
-
fingerprint: string | null
|
|
42
41
|
identityPath: string
|
|
43
42
|
rawCode: string
|
|
44
43
|
resolvedPath: string
|
|
@@ -131,7 +130,6 @@ export async function resolveStyle(
|
|
|
131
130
|
value: {
|
|
132
131
|
dependencies,
|
|
133
132
|
deps: [...deps],
|
|
134
|
-
fingerprint: transformed.fingerprint,
|
|
135
133
|
identityPath: record.identityPath,
|
|
136
134
|
rawCode: transformed.rawCode,
|
|
137
135
|
resolvedPath: transformed.resolvedPath,
|
|
@@ -175,9 +173,9 @@ export function resolveServedStyleOrThrow(
|
|
|
175
173
|
let stableUrlPathname = args.routes.toUrlPathname(identityPath)
|
|
176
174
|
if (!stableUrlPathname) {
|
|
177
175
|
throw createAssetServerCompilationError(
|
|
178
|
-
`File ${identityPath} is outside all configured
|
|
176
|
+
`File ${identityPath} is outside all configured mounts.`,
|
|
179
177
|
{
|
|
180
|
-
code: '
|
|
178
|
+
code: 'FILE_OUTSIDE_MOUNTS',
|
|
181
179
|
},
|
|
182
180
|
)
|
|
183
181
|
}
|
|
@@ -232,10 +230,10 @@ function resolveImportDependency(
|
|
|
232
230
|
|
|
233
231
|
if (!args.routes.toUrlPathname(identityPath)) {
|
|
234
232
|
throw createAssetServerCompilationError(
|
|
235
|
-
`Import "${url}" in ${importerPath}, resolved to "${identityPath}", is outside all configured
|
|
236
|
-
`Add a matching
|
|
233
|
+
`Import "${url}" in ${importerPath}, resolved to "${identityPath}", is outside all configured mounts. ` +
|
|
234
|
+
`Add a matching mount for this file path, or mark this import as external.`,
|
|
237
235
|
{
|
|
238
|
-
code: '
|
|
236
|
+
code: 'IMPORT_OUTSIDE_MOUNTS',
|
|
239
237
|
},
|
|
240
238
|
)
|
|
241
239
|
}
|
|
@@ -302,10 +300,10 @@ function resolveUrlDependency(
|
|
|
302
300
|
|
|
303
301
|
if (!args.routes.toUrlPathname(identityPath)) {
|
|
304
302
|
throw createAssetServerCompilationError(
|
|
305
|
-
`URL "${url}" in ${importerPath}, resolved to "${identityPath}", is outside all configured
|
|
306
|
-
`Add a matching
|
|
303
|
+
`URL "${url}" in ${importerPath}, resolved to "${identityPath}", is outside all configured mounts. ` +
|
|
304
|
+
`Add a matching mount for this file path.`,
|
|
307
305
|
{
|
|
308
|
-
code: '
|
|
306
|
+
code: 'URL_OUTSIDE_MOUNTS',
|
|
309
307
|
},
|
|
310
308
|
)
|
|
311
309
|
}
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
createAssetServerCompilationError,
|
|
6
6
|
isAssetServerCompilationError,
|
|
7
7
|
} from '../compilation-error.ts'
|
|
8
|
-
import { generateFingerprint } from '../fingerprint.ts'
|
|
9
8
|
import type { ModuleTracking } from '../module-store.ts'
|
|
10
9
|
import { rewriteSourceMapSources, stringifySourceMap } from '../source-maps.ts'
|
|
11
10
|
import type { AssetServerCompilationError } from '../compilation-error.ts'
|
|
@@ -25,7 +24,6 @@ type TransformedStyleDependency =
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
export type TransformedStyle = {
|
|
28
|
-
fingerprint: string | null
|
|
29
27
|
identityPath: string
|
|
30
28
|
rawCode: string
|
|
31
29
|
resolvedPath: string
|
|
@@ -49,7 +47,6 @@ type TransformResult = {
|
|
|
49
47
|
)
|
|
50
48
|
|
|
51
49
|
export type TransformArgs = {
|
|
52
|
-
buildId: string | null
|
|
53
50
|
isWatchIgnored(filePath: string): boolean
|
|
54
51
|
minify: boolean
|
|
55
52
|
routes: CompiledRoutes
|
|
@@ -99,9 +96,9 @@ export async function transformStyle(
|
|
|
99
96
|
let stableUrlPathname = args.routes.toUrlPathname(record.identityPath)
|
|
100
97
|
if (!stableUrlPathname) {
|
|
101
98
|
throw createAssetServerCompilationError(
|
|
102
|
-
`File ${record.identityPath} is outside all configured
|
|
99
|
+
`File ${record.identityPath} is outside all configured mounts.`,
|
|
103
100
|
{
|
|
104
|
-
code: '
|
|
101
|
+
code: 'FILE_OUTSIDE_MOUNTS',
|
|
105
102
|
},
|
|
106
103
|
)
|
|
107
104
|
}
|
|
@@ -149,13 +146,6 @@ export async function transformStyle(
|
|
|
149
146
|
trackedFiles,
|
|
150
147
|
},
|
|
151
148
|
value: {
|
|
152
|
-
fingerprint:
|
|
153
|
-
args.buildId === null
|
|
154
|
-
? null
|
|
155
|
-
: await generateFingerprint({
|
|
156
|
-
buildId: args.buildId,
|
|
157
|
-
content: sourceText,
|
|
158
|
-
}),
|
|
159
149
|
identityPath: record.identityPath,
|
|
160
150
|
rawCode: Buffer.from(transformResult.code).toString('utf8'),
|
|
161
151
|
resolvedPath,
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as path from 'node:path'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getFilePathBaseName,
|
|
6
|
+
getFilePathDirectory,
|
|
7
|
+
normalizeFilePath,
|
|
8
|
+
resolveFilePath,
|
|
9
|
+
} from './paths.ts'
|
|
10
|
+
import { resolveMountFileRoot } from './routes.ts'
|
|
11
|
+
|
|
12
|
+
const modulesStateFileName = '.modules.yaml'
|
|
13
|
+
const virtualStoreBasePath = '/__@remix/virtual-store'
|
|
14
|
+
const virtualStoreDirLineRE = /^virtualStoreDir:[ \t]*(.+)$/m
|
|
15
|
+
|
|
16
|
+
// pnpm's global virtual store links packages from a machine-wide directory, so package realpaths
|
|
17
|
+
// land outside `rootDir` and would otherwise have no public URL. The store location comes from
|
|
18
|
+
// `node_modules/.modules.yaml`, which pnpm rewrites on every install.
|
|
19
|
+
export function getVirtualStoreMountConfigs(options: {
|
|
20
|
+
mounts: Readonly<Record<string, string>>
|
|
21
|
+
rootDir: string
|
|
22
|
+
}): {
|
|
23
|
+
mounts: Record<string, string>
|
|
24
|
+
rootDir: string
|
|
25
|
+
}[] {
|
|
26
|
+
let virtualStoreDir = findVirtualStoreDir(options.rootDir)
|
|
27
|
+
if (virtualStoreDir === null) return []
|
|
28
|
+
// A store inside a mount, such as pnpm's default `node_modules/.pnpm`, already has URLs.
|
|
29
|
+
if (isWithinMounts(virtualStoreDir, options)) return []
|
|
30
|
+
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
mounts: {
|
|
34
|
+
[virtualStoreBasePath]: getFilePathBaseName(virtualStoreDir),
|
|
35
|
+
},
|
|
36
|
+
rootDir: getFilePathDirectory(virtualStoreDir),
|
|
37
|
+
},
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function findVirtualStoreDir(rootDir: string): string | null {
|
|
42
|
+
let directory = normalizeFilePath(rootDir)
|
|
43
|
+
|
|
44
|
+
// Workspace members share the workspace root's file, so walk up to the nearest one.
|
|
45
|
+
while (true) {
|
|
46
|
+
let modulesDir = `${directory}/node_modules`
|
|
47
|
+
let modulesState = readModulesState(`${modulesDir}/${modulesStateFileName}`)
|
|
48
|
+
if (modulesState !== null) {
|
|
49
|
+
let virtualStoreDir = readVirtualStoreDir(modulesState)
|
|
50
|
+
if (virtualStoreDir === null) return null
|
|
51
|
+
return realpathDirectory(resolveFilePath(modulesDir, virtualStoreDir))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let parentDirectory = normalizeFilePath(path.dirname(directory))
|
|
55
|
+
if (parentDirectory === directory) return null
|
|
56
|
+
directory = parentDirectory
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readVirtualStoreDir(modulesState: string): string | null {
|
|
61
|
+
// pnpm 10.34 and later write JSON, earlier versions write YAML, and package managers that
|
|
62
|
+
// mirror the global virtual store write either one. Reading the one scalar this needs keeps
|
|
63
|
+
// both formats working without a YAML parser.
|
|
64
|
+
try {
|
|
65
|
+
let parsedState = JSON.parse(modulesState) as unknown
|
|
66
|
+
if (parsedState === null || typeof parsedState !== 'object') return null
|
|
67
|
+
|
|
68
|
+
let virtualStoreDir = (parsedState as Record<string, unknown>).virtualStoreDir
|
|
69
|
+
return typeof virtualStoreDir === 'string' ? virtualStoreDir : null
|
|
70
|
+
} catch {
|
|
71
|
+
let match = virtualStoreDirLineRE.exec(modulesState)
|
|
72
|
+
return match === null ? null : unquoteScalar(match[1].trim())
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function unquoteScalar(value: string): string {
|
|
77
|
+
let quote = value[0]
|
|
78
|
+
if (value.length > 1 && (quote === "'" || quote === '"') && value.endsWith(quote)) {
|
|
79
|
+
return value.slice(1, -1)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return value
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isWithinMounts(
|
|
86
|
+
directory: string,
|
|
87
|
+
options: {
|
|
88
|
+
mounts: Readonly<Record<string, string>>
|
|
89
|
+
rootDir: string
|
|
90
|
+
},
|
|
91
|
+
): boolean {
|
|
92
|
+
return Object.values(options.mounts).some((fileRoot) => {
|
|
93
|
+
let mountRoot = resolveMountFileRoot(options.rootDir, fileRoot)
|
|
94
|
+
return directory === mountRoot || directory.startsWith(`${mountRoot}/`)
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function readModulesState(filePath: string): string | null {
|
|
99
|
+
try {
|
|
100
|
+
return fs.readFileSync(filePath, 'utf-8')
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (isPathNotFoundError(error)) return null
|
|
103
|
+
throw error
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function realpathDirectory(filePath: string): string | null {
|
|
108
|
+
try {
|
|
109
|
+
let realPath = fs.realpathSync(filePath)
|
|
110
|
+
return fs.statSync(realPath).isDirectory() ? normalizeFilePath(realPath) : null
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (isPathNotFoundError(error)) return null
|
|
113
|
+
throw error
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isPathNotFoundError(error: unknown): boolean {
|
|
118
|
+
return (
|
|
119
|
+
error instanceof Error &&
|
|
120
|
+
'code' in error &&
|
|
121
|
+
((error as NodeJS.ErrnoException).code === 'ENOENT' ||
|
|
122
|
+
(error as NodeJS.ErrnoException).code === 'ENOTDIR')
|
|
123
|
+
)
|
|
124
|
+
}
|