@remix-run/assets 0.4.3 → 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.d.ts.map +1 -1
- package/dist/lib/routes.js +13 -13
- 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/routes.ts +24 -14
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs'
|
|
2
2
|
import * as fsp from 'node:fs/promises'
|
|
3
3
|
import * as path from 'node:path'
|
|
4
|
+
import { pathToFileURL } from 'node:url'
|
|
4
5
|
import { getTsconfig } from 'get-tsconfig'
|
|
5
6
|
import MagicString from 'magic-string'
|
|
6
7
|
import { minify } from 'oxc-minify'
|
|
@@ -24,16 +25,24 @@ import {
|
|
|
24
25
|
restoreAuthoredInjectedPackageSpecifier,
|
|
25
26
|
} from '../injected-packages.ts'
|
|
26
27
|
import type { ModuleRecord, ModuleTracking } from '../module-store.ts'
|
|
28
|
+
import type {
|
|
29
|
+
ModuleLoadContext,
|
|
30
|
+
ModuleLoader,
|
|
31
|
+
ModuleLoadResult,
|
|
32
|
+
NextModuleLoader,
|
|
33
|
+
} from '../loaders.ts'
|
|
27
34
|
import { normalizeFilePath } from '../paths.ts'
|
|
28
35
|
import type { CompiledRoutes } from '../routes.ts'
|
|
29
36
|
import { composeSourceMaps, rewriteSourceMapSources, stringifySourceMap } from '../source-maps.ts'
|
|
30
37
|
import type { EmittedModule } from './emit.ts'
|
|
31
38
|
import type { ResolvedScriptTarget } from '../target.ts'
|
|
32
39
|
import type { ResolvedModule } from './resolve.ts'
|
|
40
|
+
import { scriptLoaderConditions } from './conditions.ts'
|
|
33
41
|
|
|
34
42
|
type ScriptRecord = ModuleRecord<TransformedModule, ResolvedModule, EmittedModule>
|
|
35
43
|
|
|
36
44
|
type SourceLanguage = 'js' | 'jsx' | 'ts' | 'tsx'
|
|
45
|
+
type ModuleLoadSource = NonNullable<ModuleLoadResult['source']>
|
|
37
46
|
|
|
38
47
|
const scriptModuleTypes = [
|
|
39
48
|
{ extension: '.js', lang: 'js' },
|
|
@@ -71,8 +80,15 @@ type UnresolvedImport = {
|
|
|
71
80
|
start: number
|
|
72
81
|
}
|
|
73
82
|
|
|
83
|
+
type HmrAcceptedDependency = UnresolvedImport
|
|
84
|
+
|
|
74
85
|
export type TransformedModule = {
|
|
75
86
|
fingerprint: string | null
|
|
87
|
+
hmr: {
|
|
88
|
+
acceptedDeps: HmrAcceptedDependency[]
|
|
89
|
+
selfAccepting: boolean
|
|
90
|
+
usesImportMetaHot: boolean
|
|
91
|
+
}
|
|
76
92
|
identityPath: string
|
|
77
93
|
importerDir: string
|
|
78
94
|
packageSpecifiers: string[]
|
|
@@ -110,6 +126,7 @@ export type TransformArgs = {
|
|
|
110
126
|
externalSet: ReadonlySet<string>
|
|
111
127
|
isWatchIgnored(filePath: string): boolean
|
|
112
128
|
minify: boolean
|
|
129
|
+
loaders: readonly ModuleLoader[]
|
|
113
130
|
resolveActualPath(identityPath: string): string | null
|
|
114
131
|
routes: CompiledRoutes
|
|
115
132
|
sourceMapSourcePaths: 'absolute' | 'url'
|
|
@@ -205,9 +222,21 @@ export async function transformModule(
|
|
|
205
222
|
}
|
|
206
223
|
|
|
207
224
|
try {
|
|
225
|
+
let stableUrlPathname = args.routes.toUrlPathname(record.identityPath)
|
|
226
|
+
if (!stableUrlPathname) {
|
|
227
|
+
throw createAssetServerCompilationError(
|
|
228
|
+
`File ${record.identityPath} is outside all configured fileMap entries.`,
|
|
229
|
+
{
|
|
230
|
+
code: 'FILE_OUTSIDE_FILE_MAP',
|
|
231
|
+
},
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
208
235
|
let analysis = await analyzeModuleSource(sourceText, resolvedPath, transformOptions, {
|
|
209
236
|
define: args.define ?? undefined,
|
|
210
237
|
minify: args.minify,
|
|
238
|
+
loaders: args.loaders,
|
|
239
|
+
moduleUrl: stableUrlPathname,
|
|
211
240
|
sourceMaps: args.sourceMaps ?? undefined,
|
|
212
241
|
target: args.target ?? undefined,
|
|
213
242
|
})
|
|
@@ -227,16 +256,6 @@ export async function transformModule(
|
|
|
227
256
|
)
|
|
228
257
|
}
|
|
229
258
|
|
|
230
|
-
let stableUrlPathname = args.routes.toUrlPathname(record.identityPath)
|
|
231
|
-
if (!stableUrlPathname) {
|
|
232
|
-
throw createAssetServerCompilationError(
|
|
233
|
-
`File ${record.identityPath} is outside all configured fileMap entries.`,
|
|
234
|
-
{
|
|
235
|
-
code: 'FILE_OUTSIDE_FILE_MAP',
|
|
236
|
-
},
|
|
237
|
-
)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
259
|
let sourceMap = analysis.sourceMap
|
|
241
260
|
? rewriteSourceMapSources(
|
|
242
261
|
analysis.sourceMap,
|
|
@@ -260,6 +279,7 @@ export async function transformModule(
|
|
|
260
279
|
buildId: args.buildId,
|
|
261
280
|
content: sourceText,
|
|
262
281
|
}),
|
|
282
|
+
hmr: getHmrAnalysis(analysis.rawCode),
|
|
263
283
|
identityPath: record.identityPath,
|
|
264
284
|
importerDir: path.dirname(resolvedPath),
|
|
265
285
|
packageSpecifiers: analysis.unresolvedImports
|
|
@@ -284,6 +304,121 @@ export async function transformModule(
|
|
|
284
304
|
}
|
|
285
305
|
}
|
|
286
306
|
|
|
307
|
+
export function getHmrAnalysis(rawCode: string): TransformedModule['hmr'] {
|
|
308
|
+
let mayUseImportMetaHot = rawCode.includes('import.meta.hot')
|
|
309
|
+
let usesImportMetaHot = false
|
|
310
|
+
let acceptedDeps: HmrAcceptedDependency[] = []
|
|
311
|
+
let selfAccepting = false
|
|
312
|
+
|
|
313
|
+
if (mayUseImportMetaHot) {
|
|
314
|
+
try {
|
|
315
|
+
let parseResult = parseSync('hmr-analysis.js', rawCode, {
|
|
316
|
+
lang: 'js',
|
|
317
|
+
sourceType: 'module',
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
if (parseResult.errors.length > 0) {
|
|
321
|
+
throw createAssetServerCompilationError(
|
|
322
|
+
`Failed to analyze HMR usage in transformed script. ${parseResult.errors[0]?.message ?? 'Unknown parse error'}`,
|
|
323
|
+
{
|
|
324
|
+
code: 'TRANSFORM_FAILED',
|
|
325
|
+
},
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
walkAst(parseResult.program, (node) => {
|
|
330
|
+
if (isImportMetaHotNode(node)) {
|
|
331
|
+
usesImportMetaHot = true
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (node.type !== 'CallExpression') return
|
|
335
|
+
if (!isImportMetaHotAcceptCallee(node.callee)) return
|
|
336
|
+
|
|
337
|
+
let [firstArgument] = node.arguments
|
|
338
|
+
if (firstArgument === undefined || !isAcceptedDependencyArgument(firstArgument)) {
|
|
339
|
+
selfAccepting = true
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
acceptedDeps.push(...getAcceptedDependencies(firstArgument, rawCode))
|
|
344
|
+
})
|
|
345
|
+
} catch (error) {
|
|
346
|
+
if (isAssetServerCompilationError(error)) throw error
|
|
347
|
+
throw createAssetServerCompilationError(
|
|
348
|
+
`Failed to analyze HMR usage in transformed script.`,
|
|
349
|
+
{
|
|
350
|
+
cause: error,
|
|
351
|
+
code: 'TRANSFORM_FAILED',
|
|
352
|
+
},
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
acceptedDeps,
|
|
359
|
+
selfAccepting,
|
|
360
|
+
usesImportMetaHot,
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function isImportMetaHotAcceptCallee(node: Node): boolean {
|
|
365
|
+
let callee = unwrapChainExpression(node)
|
|
366
|
+
if (callee.type !== 'MemberExpression') return false
|
|
367
|
+
if (callee.computed || !isIdentifierNode(callee.property, 'accept')) return false
|
|
368
|
+
|
|
369
|
+
return isImportMetaHotNode(callee.object)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function isImportMetaHotNode(node: Node): boolean {
|
|
373
|
+
let hot = unwrapChainExpression(node)
|
|
374
|
+
if (hot.type !== 'MemberExpression') return false
|
|
375
|
+
if (hot.computed || !isIdentifierNode(hot.property, 'hot')) return false
|
|
376
|
+
|
|
377
|
+
let meta = unwrapChainExpression(hot.object)
|
|
378
|
+
return (
|
|
379
|
+
meta.type === 'MetaProperty' &&
|
|
380
|
+
isIdentifierNode(meta.meta, 'import') &&
|
|
381
|
+
isIdentifierNode(meta.property, 'meta')
|
|
382
|
+
)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function unwrapChainExpression(node: Node): Node {
|
|
386
|
+
return node.type === 'ChainExpression' ? node.expression : node
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function isIdentifierNode(node: Node, name: string): boolean {
|
|
390
|
+
return node.type === 'Identifier' && node.name === name
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function isAcceptedDependencyArgument(node: Node): boolean {
|
|
394
|
+
return isStringLiteralNode(node) || node.type === 'ArrayExpression'
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function getAcceptedDependencies(node: Node, source: string): HmrAcceptedDependency[] {
|
|
398
|
+
if (isStringLiteralNode(node)) {
|
|
399
|
+
return [
|
|
400
|
+
{
|
|
401
|
+
end: node.end - 1,
|
|
402
|
+
specifier: node.value,
|
|
403
|
+
start: node.start + 1,
|
|
404
|
+
},
|
|
405
|
+
]
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (node.type !== 'ArrayExpression') return []
|
|
409
|
+
|
|
410
|
+
let deps: HmrAcceptedDependency[] = []
|
|
411
|
+
for (let element of node.elements) {
|
|
412
|
+
if (!isStringLiteralNode(element)) continue
|
|
413
|
+
deps.push({
|
|
414
|
+
end: element.end - 1,
|
|
415
|
+
specifier: element.value,
|
|
416
|
+
start: element.start + 1,
|
|
417
|
+
})
|
|
418
|
+
}
|
|
419
|
+
return deps
|
|
420
|
+
}
|
|
421
|
+
|
|
287
422
|
function findNearestTsconfigPath(directory: string): string | null {
|
|
288
423
|
let currentDirectory = directory
|
|
289
424
|
|
|
@@ -318,6 +453,8 @@ async function analyzeModuleSource(
|
|
|
318
453
|
options: {
|
|
319
454
|
define?: Record<string, string>
|
|
320
455
|
minify: boolean
|
|
456
|
+
loaders: readonly ModuleLoader[]
|
|
457
|
+
moduleUrl: string
|
|
321
458
|
sourceMaps?: 'external' | 'inline'
|
|
322
459
|
target?: ResolvedScriptTarget
|
|
323
460
|
},
|
|
@@ -345,6 +482,40 @@ async function analyzeModuleSource(
|
|
|
345
482
|
let rawCode = transformResult.code.trimEnd()
|
|
346
483
|
let sourceMap = stringifySourceMap(transformResult.map)
|
|
347
484
|
|
|
485
|
+
if (options.loaders.length > 0) {
|
|
486
|
+
let loaderUrl = pathToFileURL(resolvedPath).href
|
|
487
|
+
let loaderInputSource = sourceMap ? appendInlineSourceMap(rawCode, sourceMap) : rawCode
|
|
488
|
+
let loaderOutputSource: string
|
|
489
|
+
try {
|
|
490
|
+
let loaderResult = runModuleLoaders(loaderUrl, {
|
|
491
|
+
format: 'module',
|
|
492
|
+
loaders: options.loaders,
|
|
493
|
+
moduleUrl: options.moduleUrl,
|
|
494
|
+
source: loaderInputSource,
|
|
495
|
+
})
|
|
496
|
+
loaderOutputSource = moduleLoadSourceToString(getModuleLoadSource(loaderUrl, loaderResult))
|
|
497
|
+
} catch (error) {
|
|
498
|
+
if (isAssetServerCompilationError(error)) throw error
|
|
499
|
+
throw createAssetServerCompilationError(
|
|
500
|
+
`Module loader failed for ${loaderUrl}. ${formatUnknownError(error)}`,
|
|
501
|
+
{
|
|
502
|
+
cause: error,
|
|
503
|
+
code: 'LOADER_FAILED',
|
|
504
|
+
},
|
|
505
|
+
)
|
|
506
|
+
}
|
|
507
|
+
if (loaderOutputSource !== loaderInputSource) {
|
|
508
|
+
let loaderOutput = extractInlineSourceMap(loaderOutputSource)
|
|
509
|
+
rawCode = loaderOutput.code.trimEnd()
|
|
510
|
+
sourceMap =
|
|
511
|
+
loaderOutput.sourceMap && loaderOutput.sourceMap !== sourceMap
|
|
512
|
+
? sourceMap
|
|
513
|
+
? composeSourceMaps(loaderOutput.sourceMap, sourceMap)
|
|
514
|
+
: loaderOutput.sourceMap
|
|
515
|
+
: null
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
348
519
|
if (options.minify) {
|
|
349
520
|
let minifyResult = await minifyModule(rawCode, resolvedPath, options.target, options.sourceMaps)
|
|
350
521
|
rawCode = minifyResult.code.trimEnd()
|
|
@@ -364,6 +535,110 @@ async function analyzeModuleSource(
|
|
|
364
535
|
}
|
|
365
536
|
}
|
|
366
537
|
|
|
538
|
+
function runModuleLoaders(
|
|
539
|
+
url: string,
|
|
540
|
+
options: {
|
|
541
|
+
format: string
|
|
542
|
+
loaders: readonly ModuleLoader[]
|
|
543
|
+
moduleUrl: string
|
|
544
|
+
source: string
|
|
545
|
+
},
|
|
546
|
+
): ModuleLoadResult {
|
|
547
|
+
let loadContext: ModuleLoadContext = {
|
|
548
|
+
conditions: [...scriptLoaderConditions],
|
|
549
|
+
format: options.format,
|
|
550
|
+
importAttributes: {},
|
|
551
|
+
moduleUrl: options.moduleUrl,
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
let load: NextModuleLoader = (_url, context) => ({
|
|
555
|
+
format: context?.format ?? options.format,
|
|
556
|
+
source: options.source,
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
for (let loader of options.loaders) {
|
|
560
|
+
let nextLoad = load
|
|
561
|
+
load = (nextUrl, nextContext) => {
|
|
562
|
+
let nextLoadCalled = false
|
|
563
|
+
let wrappedNextLoad: NextModuleLoader = (wrappedUrl, wrappedContext) => {
|
|
564
|
+
if (wrappedUrl !== nextUrl) {
|
|
565
|
+
throw createAssetServerCompilationError(
|
|
566
|
+
`Module loader for ${nextUrl} attempted to change the module URL to ${wrappedUrl}. Loaders cannot change module URLs.`,
|
|
567
|
+
{
|
|
568
|
+
code: 'LOADER_FAILED',
|
|
569
|
+
},
|
|
570
|
+
)
|
|
571
|
+
}
|
|
572
|
+
nextLoadCalled = true
|
|
573
|
+
return nextLoad(wrappedUrl, wrappedContext)
|
|
574
|
+
}
|
|
575
|
+
let result = loader(
|
|
576
|
+
nextUrl,
|
|
577
|
+
{
|
|
578
|
+
conditions: nextContext?.conditions ?? loadContext.conditions,
|
|
579
|
+
format: nextContext?.format ?? loadContext.format,
|
|
580
|
+
importAttributes: nextContext?.importAttributes ?? loadContext.importAttributes,
|
|
581
|
+
moduleUrl: nextContext?.moduleUrl ?? loadContext.moduleUrl,
|
|
582
|
+
},
|
|
583
|
+
wrappedNextLoad,
|
|
584
|
+
)
|
|
585
|
+
if (!nextLoadCalled && result.shortCircuit !== true) {
|
|
586
|
+
throw createAssetServerCompilationError(
|
|
587
|
+
`Module loader for ${nextUrl} returned without calling nextLoad or setting shortCircuit: true.`,
|
|
588
|
+
{
|
|
589
|
+
code: 'LOADER_FAILED',
|
|
590
|
+
},
|
|
591
|
+
)
|
|
592
|
+
}
|
|
593
|
+
return result
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return load(url, loadContext)
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function getModuleLoadSource(url: string, result: ModuleLoadResult): ModuleLoadSource {
|
|
601
|
+
if (result.format !== 'module') {
|
|
602
|
+
throw createAssetServerCompilationError(
|
|
603
|
+
`Module loader for ${url} returned unsupported format ${JSON.stringify(result.format)}. Only "module" is supported.`,
|
|
604
|
+
{
|
|
605
|
+
code: 'LOADER_FAILED',
|
|
606
|
+
},
|
|
607
|
+
)
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (result.source !== undefined && result.source !== null) return result.source
|
|
611
|
+
|
|
612
|
+
throw createAssetServerCompilationError(`Module loader for ${url} did not return source.`, {
|
|
613
|
+
code: 'LOADER_FAILED',
|
|
614
|
+
})
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function moduleLoadSourceToString(source: ModuleLoadSource): string {
|
|
618
|
+
if (typeof source === 'string') return source
|
|
619
|
+
return new TextDecoder().decode(source)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function appendInlineSourceMap(source: string, sourceMap: string): string {
|
|
623
|
+
let encoded = Buffer.from(sourceMap).toString('base64')
|
|
624
|
+
return `${source}\n//# sourceMappingURL=data:application/json;base64,${encoded}`
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function extractInlineSourceMap(source: string): {
|
|
628
|
+
code: string
|
|
629
|
+
sourceMap: string | null
|
|
630
|
+
} {
|
|
631
|
+
let sourceMapPattern =
|
|
632
|
+
/(?:\/\/# sourceMappingURL=data:application\/json;base64,([A-Za-z0-9+/=]+)|\/\*# sourceMappingURL=data:application\/json;base64,([A-Za-z0-9+/=]+) \*\/)\s*$/g
|
|
633
|
+
let sourceMap: string | null = null
|
|
634
|
+
let code = source.replace(sourceMapPattern, (_match, lineComment, blockComment) => {
|
|
635
|
+
sourceMap = Buffer.from(lineComment ?? blockComment, 'base64').toString('utf-8')
|
|
636
|
+
return ''
|
|
637
|
+
})
|
|
638
|
+
|
|
639
|
+
return { code, sourceMap }
|
|
640
|
+
}
|
|
641
|
+
|
|
367
642
|
async function minifyModule(
|
|
368
643
|
rawCode: string,
|
|
369
644
|
resolvedPath: string,
|
|
@@ -546,7 +821,7 @@ async function getUnresolvedImportsFromLexer(rawCode: string): Promise<Unresolve
|
|
|
546
821
|
|
|
547
822
|
for (let imported of imports) {
|
|
548
823
|
let specifier = getStaticImportSpecifier(rawCode, imported)
|
|
549
|
-
if (specifier == null ||
|
|
824
|
+
if (specifier == null || isBrowserExternalModuleUrl(specifier)) continue
|
|
550
825
|
unresolvedImports.push({
|
|
551
826
|
specifier,
|
|
552
827
|
start: imported.s,
|
|
@@ -562,6 +837,10 @@ function getDisplayImportSpecifier(specifier: string): string {
|
|
|
562
837
|
return restoreAuthoredInjectedPackageSpecifier(specifier) ?? specifier
|
|
563
838
|
}
|
|
564
839
|
|
|
840
|
+
function isBrowserExternalModuleUrl(url: string): boolean {
|
|
841
|
+
return url.startsWith('data:') || url.startsWith('http://') || url.startsWith('https://')
|
|
842
|
+
}
|
|
843
|
+
|
|
565
844
|
function maskAuthoredInjectedPackageImports(sourceText: string, resolvedPath: string): string {
|
|
566
845
|
if (!mayContainInjectedPackageSpecifier(sourceText)) {
|
|
567
846
|
return sourceText
|
|
@@ -675,14 +954,6 @@ function isStaticTemplateLiteral(specifier: string): boolean {
|
|
|
675
954
|
return specifier.startsWith('`') && specifier.endsWith('`') && !specifier.includes('${')
|
|
676
955
|
}
|
|
677
956
|
|
|
678
|
-
function shouldSkipImportSpecifier(specifier: string): boolean {
|
|
679
|
-
return (
|
|
680
|
-
specifier.startsWith('data:') ||
|
|
681
|
-
specifier.startsWith('http://') ||
|
|
682
|
-
specifier.startsWith('https://')
|
|
683
|
-
)
|
|
684
|
-
}
|
|
685
|
-
|
|
686
957
|
function getImportQuote(source: string, start: number): '"' | "'" | '`' | undefined {
|
|
687
958
|
let firstCharacter = source[start]
|
|
688
959
|
if (firstCharacter === '"' || firstCharacter === "'" || firstCharacter === '`') {
|
|
@@ -50,10 +50,14 @@ type StyleCompilerOptions = {
|
|
|
50
50
|
transform: readonly string[] | null
|
|
51
51
|
},
|
|
52
52
|
): Promise<string>
|
|
53
|
+
hmr?: {
|
|
54
|
+
send(pathname: string, timestamp: number): void
|
|
55
|
+
}
|
|
53
56
|
isAllowed(absolutePath: string): boolean
|
|
54
57
|
isServedFilePath(filePath: string): boolean
|
|
55
58
|
minify: boolean
|
|
56
59
|
onWatchDirectoriesChange?: (delta: { add: string[]; remove: string[] }) => void
|
|
60
|
+
onWatchFilesChange?: (delta: { add: string[]; remove: string[] }) => void
|
|
57
61
|
rootDir: string
|
|
58
62
|
routes: CompiledRoutes
|
|
59
63
|
sourceMapSourcePaths: 'absolute' | 'url'
|
|
@@ -66,7 +70,17 @@ type StyleCompiler = {
|
|
|
66
70
|
getHref(filePath: string): Promise<string>
|
|
67
71
|
getPreloadLayers(filePath: string | readonly string[]): Promise<string[][]>
|
|
68
72
|
getStyle(filePath: string, options: StyleGetOptions): Promise<StyleGetResult>
|
|
69
|
-
|
|
73
|
+
classifyHmrFileEvent(
|
|
74
|
+
filePath: string,
|
|
75
|
+
event: 'add' | 'change' | 'unlink',
|
|
76
|
+
): Promise<StyleHmrUpdate[]>
|
|
77
|
+
invalidateFileEvent(filePath: string, event: 'add' | 'change' | 'unlink'): void
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type StyleHmrUpdate = {
|
|
81
|
+
filePath: string
|
|
82
|
+
path: string
|
|
83
|
+
timestamp: number
|
|
70
84
|
}
|
|
71
85
|
|
|
72
86
|
const preloadConcurrency = Math.max(1, Math.min(8, os.availableParallelism() - 1))
|
|
@@ -80,7 +94,11 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
80
94
|
),
|
|
81
95
|
}
|
|
82
96
|
let styleStore: StyleStore = createModuleStore<TransformedStyle, ResolvedStyle, EmittedStyle>({
|
|
97
|
+
getDependencies(resolvedStyle) {
|
|
98
|
+
return resolvedStyle.deps
|
|
99
|
+
},
|
|
83
100
|
onWatchDirectoriesChange: options.onWatchDirectoriesChange,
|
|
101
|
+
onWatchFilesChange: options.onWatchFilesChange,
|
|
84
102
|
})
|
|
85
103
|
let resolveInFlightByCacheKey = new Map<string, Promise<ResolvedStyle>>()
|
|
86
104
|
let emitInFlightByCacheKey = new Map<string, Promise<EmittedStyle>>()
|
|
@@ -149,7 +167,12 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
149
167
|
async getStyle(filePath, getOptions) {
|
|
150
168
|
let resolvedStyle = resolveServedStyleOrThrow(resolveInputFilePath(filePath), resolveArgs)
|
|
151
169
|
let record = styleStore.get(resolvedStyle.identityPath)
|
|
152
|
-
let notModified = getNotModifiedStyle(
|
|
170
|
+
let notModified = getNotModifiedStyle(
|
|
171
|
+
record,
|
|
172
|
+
styleStore,
|
|
173
|
+
getOptions,
|
|
174
|
+
hasHmrTimestampedDependency,
|
|
175
|
+
)
|
|
153
176
|
if (notModified) return notModified
|
|
154
177
|
|
|
155
178
|
let emitted = await getOrCreateEmittedStyle(record)
|
|
@@ -159,7 +182,26 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
159
182
|
}
|
|
160
183
|
},
|
|
161
184
|
|
|
162
|
-
async
|
|
185
|
+
async classifyHmrFileEvent(filePath, event) {
|
|
186
|
+
let normalizedFilePath = normalizeFilePath(filePath)
|
|
187
|
+
if (isWatchIgnored(normalizedFilePath)) return []
|
|
188
|
+
let timestamp = Date.now()
|
|
189
|
+
let updatePathnames =
|
|
190
|
+
event === 'change' ? getHmrUpdatePathnames(normalizedFilePath, timestamp) : []
|
|
191
|
+
|
|
192
|
+
styleStore.invalidateForFileEvent(normalizedFilePath, event)
|
|
193
|
+
|
|
194
|
+
for (let updatePathname of updatePathnames) {
|
|
195
|
+
resolvedOptions.hmr?.send(updatePathname, timestamp)
|
|
196
|
+
}
|
|
197
|
+
return updatePathnames.map((path) => ({
|
|
198
|
+
filePath: normalizedFilePath,
|
|
199
|
+
path,
|
|
200
|
+
timestamp,
|
|
201
|
+
}))
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
invalidateFileEvent(filePath, event) {
|
|
163
205
|
let normalizedFilePath = normalizeFilePath(filePath)
|
|
164
206
|
if (isWatchIgnored(normalizedFilePath)) return
|
|
165
207
|
styleStore.invalidateForFileEvent(normalizedFilePath, event)
|
|
@@ -185,7 +227,7 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
185
227
|
}
|
|
186
228
|
|
|
187
229
|
async function getOrCreateResolvedStyle(record: StyleRecord): Promise<ResolvedStyle> {
|
|
188
|
-
if (record.resolved) return record.resolved
|
|
230
|
+
if (record.resolved && styleStore.isResolvedFresh(record)) return record.resolved
|
|
189
231
|
|
|
190
232
|
let cacheKey = getRecordCacheKey(record)
|
|
191
233
|
let existing = resolveInFlightByCacheKey.get(cacheKey)
|
|
@@ -224,7 +266,7 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
224
266
|
}
|
|
225
267
|
|
|
226
268
|
async function getOrCreateTransformedStyle(record: StyleRecord): Promise<TransformedStyle> {
|
|
227
|
-
if (record.transformed) return record.transformed
|
|
269
|
+
if (record.transformed && styleStore.isTransformedFresh(record)) return record.transformed
|
|
228
270
|
|
|
229
271
|
let startedVersion = record.invalidationVersion
|
|
230
272
|
let transformStyleResult = await transformStyle(record, transformArgs)
|
|
@@ -246,7 +288,13 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
246
288
|
}
|
|
247
289
|
|
|
248
290
|
async function getOrCreateEmittedStyle(record: StyleRecord): Promise<EmittedStyle> {
|
|
249
|
-
if (
|
|
291
|
+
if (
|
|
292
|
+
record.emitted &&
|
|
293
|
+
styleStore.isEmittedFresh(record) &&
|
|
294
|
+
!hasHmrTimestampedDependency(record.resolved)
|
|
295
|
+
) {
|
|
296
|
+
return record.emitted
|
|
297
|
+
}
|
|
250
298
|
|
|
251
299
|
let cacheKey = getRecordCacheKey(record)
|
|
252
300
|
let existing = emitInFlightByCacheKey.get(cacheKey)
|
|
@@ -290,10 +338,49 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
290
338
|
}
|
|
291
339
|
|
|
292
340
|
function getServedUrlForResolvedStyle(resolvedStyle: ResolvedStyle): string {
|
|
293
|
-
|
|
341
|
+
let pathname = formatFingerprintedPathname(
|
|
294
342
|
resolvedStyle.stableUrlPathname,
|
|
295
343
|
resolvedOptions.fingerprintAssets ? resolvedStyle.fingerprint : null,
|
|
296
344
|
)
|
|
345
|
+
let timestamp = styleStore.getHmrUpdateTimestamp(resolvedStyle.identityPath)
|
|
346
|
+
return timestamp ? appendTimestamp(pathname, timestamp) : pathname
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function getHmrUpdatePathnames(identityPath: string, timestamp: number): string[] {
|
|
350
|
+
let resolvedStyles = findHmrUpdateStyles(identityPath, new Set())
|
|
351
|
+
// Mark update timestamps before invalidating so re-emitted importer stylesheets
|
|
352
|
+
// can rewrite imports to the changed dependency with this HMR timestamp.
|
|
353
|
+
for (let resolvedStyle of resolvedStyles) {
|
|
354
|
+
styleStore.setHmrUpdateTimestamp(resolvedStyle.identityPath, timestamp)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return dedupeHmrUpdatePathnames(
|
|
358
|
+
resolvedStyles
|
|
359
|
+
.filter((resolvedStyle) => styleStore.getImporters(resolvedStyle.identityPath).size === 0)
|
|
360
|
+
.map((resolvedStyle) => resolvedStyle.stableUrlPathname),
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function findHmrUpdateStyles(identityPath: string, traversed: Set<string>): ResolvedStyle[] {
|
|
365
|
+
if (traversed.has(identityPath)) return []
|
|
366
|
+
traversed.add(identityPath)
|
|
367
|
+
|
|
368
|
+
let resolvedStyle = styleStore.getLastResolved(identityPath)
|
|
369
|
+
if (!resolvedStyle) return []
|
|
370
|
+
|
|
371
|
+
let resolvedStyles = [resolvedStyle]
|
|
372
|
+
for (let importerPath of styleStore.getImporters(identityPath)) {
|
|
373
|
+
resolvedStyles.push(...findHmrUpdateStyles(importerPath, traversed))
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return resolvedStyles
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function hasHmrTimestampedDependency(resolvedStyle: ResolvedStyle | undefined): boolean {
|
|
380
|
+
return (
|
|
381
|
+
resolvedStyle?.deps.some((depPath) => styleStore.getHmrUpdateTimestamp(depPath) != null) ===
|
|
382
|
+
true
|
|
383
|
+
)
|
|
297
384
|
}
|
|
298
385
|
|
|
299
386
|
function isWatchIgnored(filePath: string): boolean {
|
|
@@ -310,9 +397,14 @@ function isFresh(record: StyleRecord, version: number): boolean {
|
|
|
310
397
|
}
|
|
311
398
|
|
|
312
399
|
function getNotModifiedStyle(
|
|
313
|
-
|
|
400
|
+
record: StyleRecord,
|
|
401
|
+
styleStore: StyleStore,
|
|
314
402
|
options: StyleGetOptions,
|
|
403
|
+
hasHmrTimestampedDependency: (resolvedStyle: ResolvedStyle | undefined) => boolean,
|
|
315
404
|
): StyleGetResult | null {
|
|
405
|
+
if (hasHmrTimestampedDependency(record.resolved)) return null
|
|
406
|
+
if (!styleStore.isEmittedFresh(record)) return null
|
|
407
|
+
let emittedStyle = record.emitted
|
|
316
408
|
if (!emittedStyle || options.ifNoneMatch === null) return null
|
|
317
409
|
|
|
318
410
|
if (
|
|
@@ -329,6 +421,14 @@ function getNotModifiedStyle(
|
|
|
329
421
|
return { etag: asset.etag, type: 'not-modified' }
|
|
330
422
|
}
|
|
331
423
|
|
|
424
|
+
function appendTimestamp(pathname: string, timestamp: number): string {
|
|
425
|
+
return `${pathname}${pathname.includes('?') ? '&' : '?'}t=${timestamp}`
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function dedupeHmrUpdatePathnames(pathnames: string[]): string[] {
|
|
429
|
+
return [...new Set(pathnames)]
|
|
430
|
+
}
|
|
431
|
+
|
|
332
432
|
function getEmittedAssetForRequest(
|
|
333
433
|
emittedStyle: EmittedStyle,
|
|
334
434
|
isSourceMapRequest: boolean,
|
package/src/lib/styles/emit.ts
CHANGED
|
@@ -105,7 +105,7 @@ async function rewriteDependencies(
|
|
|
105
105
|
dependency.kind === 'external'
|
|
106
106
|
? dependency.replacement
|
|
107
107
|
: dependency.kind === 'style'
|
|
108
|
-
?
|
|
108
|
+
? appendUrlSuffix(await options.getServedUrl(dependency.depPath), dependency.suffix)
|
|
109
109
|
: appendUrlSuffix(
|
|
110
110
|
await getServedFileUrl(options, resolvedStyle.identityPath, dependency.depPath, {
|
|
111
111
|
transform: dependency.requestTransform,
|
|
@@ -163,9 +163,13 @@ export function resolveServedStyleOrThrow(
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
if (!args.isAllowed(identityPath)) {
|
|
166
|
-
throw createAssetServerCompilationError(
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
throw createAssetServerCompilationError(
|
|
167
|
+
`File "${identityPath}" is not allowed by the asset server access configuration. ` +
|
|
168
|
+
`Add a matching allowFiles or allowPackages rule, or remove a conflicting denyFiles rule.`,
|
|
169
|
+
{
|
|
170
|
+
code: 'FILE_NOT_ALLOWED',
|
|
171
|
+
},
|
|
172
|
+
)
|
|
169
173
|
}
|
|
170
174
|
|
|
171
175
|
let stableUrlPathname = args.routes.toUrlPathname(identityPath)
|
|
@@ -218,8 +222,8 @@ function resolveImportDependency(
|
|
|
218
222
|
|
|
219
223
|
if (!args.isAllowed(identityPath)) {
|
|
220
224
|
throw createAssetServerCompilationError(
|
|
221
|
-
`Import "${url}" in ${importerPath}, resolved to "${identityPath}", is not allowed by the asset server
|
|
222
|
-
`Add a matching
|
|
225
|
+
`Import "${url}" in ${importerPath}, resolved to "${identityPath}", is not allowed by the asset server access configuration. ` +
|
|
226
|
+
`Add a matching allowFiles or allowPackages rule, remove a conflicting denyFiles rule, or mark this import as external.`,
|
|
223
227
|
{
|
|
224
228
|
code: 'IMPORT_NOT_ALLOWED',
|
|
225
229
|
},
|
|
@@ -288,8 +292,8 @@ function resolveUrlDependency(
|
|
|
288
292
|
|
|
289
293
|
if (!args.isAllowed(identityPath)) {
|
|
290
294
|
throw createAssetServerCompilationError(
|
|
291
|
-
`URL "${url}" in ${importerPath}, resolved to "${identityPath}", is not allowed by the asset server
|
|
292
|
-
`Add a matching
|
|
295
|
+
`URL "${url}" in ${importerPath}, resolved to "${identityPath}", is not allowed by the asset server access configuration. ` +
|
|
296
|
+
`Add a matching allowFiles or allowPackages rule, or remove a conflicting denyFiles rule.`,
|
|
293
297
|
{
|
|
294
298
|
code: 'URL_NOT_ALLOWED',
|
|
295
299
|
},
|