@remix-run/assets 0.2.0 → 0.4.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 +241 -28
- package/dist/assets.d.ts +1 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +1 -0
- package/dist/lib/access.d.ts +1 -0
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +10 -1
- package/dist/lib/asset-server.d.ts +33 -7
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +195 -23
- 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 +71 -0
- package/dist/lib/files/compiler.d.ts.map +1 -0
- package/dist/lib/files/compiler.js +552 -0
- package/dist/lib/files/config.d.ts +101 -0
- package/dist/lib/files/config.d.ts.map +1 -0
- package/dist/lib/files/config.js +219 -0
- package/dist/lib/files/store.d.ts +31 -0
- package/dist/lib/files/store.d.ts.map +1 -0
- package/dist/lib/files/store.js +63 -0
- package/dist/lib/fingerprint.d.ts +3 -3
- package/dist/lib/fingerprint.d.ts.map +1 -1
- package/dist/lib/fingerprint.js +5 -5
- package/dist/lib/injected-packages.d.ts +11 -0
- package/dist/lib/injected-packages.d.ts.map +1 -0
- package/dist/lib/injected-packages.js +86 -0
- package/dist/lib/paths.d.ts +1 -0
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/paths.js +12 -0
- package/dist/lib/routes.d.ts +5 -7
- package/dist/lib/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +41 -36
- package/dist/lib/scripts/compiler.d.ts +1 -0
- package/dist/lib/scripts/compiler.d.ts.map +1 -1
- package/dist/lib/scripts/compiler.js +30 -5
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +57 -10
- package/dist/lib/scripts/transform.d.ts.map +1 -1
- package/dist/lib/scripts/transform.js +79 -3
- package/dist/lib/styles/compiler.d.ts +4 -0
- package/dist/lib/styles/compiler.d.ts.map +1 -1
- package/dist/lib/styles/compiler.js +2 -0
- package/dist/lib/styles/emit.d.ts +3 -0
- package/dist/lib/styles/emit.d.ts.map +1 -1
- package/dist/lib/styles/emit.js +36 -1
- package/dist/lib/styles/resolve.d.ts +8 -1
- package/dist/lib/styles/resolve.d.ts.map +1 -1
- package/dist/lib/styles/resolve.js +85 -32
- package/dist/lib/watch.d.ts +2 -0
- package/dist/lib/watch.d.ts.map +1 -1
- package/dist/lib/watch.js +25 -8
- package/package.json +11 -5
- package/src/assets.ts +1 -0
- package/src/lib/access.ts +10 -1
- package/src/lib/asset-server.ts +297 -34
- package/src/lib/compilation-error.ts +9 -0
- package/src/lib/files/compiler.ts +885 -0
- package/src/lib/files/config.ts +479 -0
- package/src/lib/files/store.ts +109 -0
- package/src/lib/fingerprint.ts +8 -7
- package/src/lib/injected-packages.ts +117 -0
- package/src/lib/paths.ts +28 -0
- package/src/lib/routes.ts +67 -55
- package/src/lib/scripts/compiler.ts +43 -5
- package/src/lib/scripts/resolve.ts +89 -11
- package/src/lib/scripts/transform.ts +105 -3
- package/src/lib/styles/compiler.ts +9 -0
- package/src/lib/styles/emit.ts +75 -1
- package/src/lib/styles/resolve.ts +132 -35
- package/src/lib/watch.ts +34 -12
|
@@ -2,10 +2,13 @@ import * as fs from 'node:fs'
|
|
|
2
2
|
import * as fsp from 'node:fs/promises'
|
|
3
3
|
import * as path from 'node:path'
|
|
4
4
|
import { getTsconfig } from 'get-tsconfig'
|
|
5
|
+
import MagicString from 'magic-string'
|
|
5
6
|
import { minify } from 'oxc-minify'
|
|
7
|
+
import { parseSync, visitorKeys } from 'oxc-parser'
|
|
6
8
|
import { transform as oxcTransform } from 'oxc-transform'
|
|
7
9
|
import { init as esModuleLexerInit, parse as esModuleLexer } from 'es-module-lexer'
|
|
8
10
|
import type { Cache, TsConfigJsonResolved } from 'get-tsconfig'
|
|
11
|
+
import type { Node, Program } from 'oxc-parser'
|
|
9
12
|
import type { TransformOptions as OxcTransformOptions } from 'oxc-transform'
|
|
10
13
|
|
|
11
14
|
import { isCommonJS, mayContainCommonJSModuleGlobals } from './cjs-check.ts'
|
|
@@ -15,6 +18,11 @@ import {
|
|
|
15
18
|
} from '../compilation-error.ts'
|
|
16
19
|
import type { AssetServerCompilationError } from '../compilation-error.ts'
|
|
17
20
|
import { generateFingerprint } from '../fingerprint.ts'
|
|
21
|
+
import {
|
|
22
|
+
maskAuthoredInjectedPackageSpecifier,
|
|
23
|
+
mayContainInjectedPackageSpecifier,
|
|
24
|
+
restoreAuthoredInjectedPackageSpecifier,
|
|
25
|
+
} from '../injected-packages.ts'
|
|
18
26
|
import type { ModuleRecord, ModuleTracking } from '../module-store.ts'
|
|
19
27
|
import { normalizeFilePath } from '../paths.ts'
|
|
20
28
|
import type { CompiledRoutes } from '../routes.ts'
|
|
@@ -205,7 +213,7 @@ export async function transformModule(
|
|
|
205
213
|
})
|
|
206
214
|
|
|
207
215
|
analysis.unresolvedImports = analysis.unresolvedImports.filter(
|
|
208
|
-
(unresolved) => !args.externalSet.has(unresolved.specifier),
|
|
216
|
+
(unresolved) => !args.externalSet.has(getDisplayImportSpecifier(unresolved.specifier)),
|
|
209
217
|
)
|
|
210
218
|
|
|
211
219
|
if (mayContainCommonJSModuleGlobals(sourceText) && isCommonJS(analysis.rawCode)) {
|
|
@@ -253,7 +261,7 @@ export async function transformModule(
|
|
|
253
261
|
content: sourceText,
|
|
254
262
|
}),
|
|
255
263
|
identityPath: record.identityPath,
|
|
256
|
-
importerDir: path.dirname(
|
|
264
|
+
importerDir: path.dirname(record.identityPath),
|
|
257
265
|
packageSpecifiers: analysis.unresolvedImports
|
|
258
266
|
.filter((unresolved) => isPackageImportSpecifier(unresolved.specifier))
|
|
259
267
|
.map((unresolved) => unresolved.specifier),
|
|
@@ -306,11 +314,12 @@ async function analyzeModuleSource(
|
|
|
306
314
|
target?: ResolvedScriptTarget
|
|
307
315
|
},
|
|
308
316
|
) {
|
|
317
|
+
let maskedSourceText = maskAuthoredInjectedPackageImports(sourceText, resolvedPath)
|
|
309
318
|
let transformResult: { code: string; errors?: Array<{ message?: string }>; map?: unknown }
|
|
310
319
|
try {
|
|
311
320
|
transformResult = await oxcTransform(
|
|
312
321
|
resolvedPath,
|
|
313
|
-
|
|
322
|
+
maskedSourceText,
|
|
314
323
|
getTransformOptions(resolvedPath, transformOptions, options),
|
|
315
324
|
)
|
|
316
325
|
assertNoCompilerErrors(transformResult.errors, resolvedPath, 'transform')
|
|
@@ -541,6 +550,99 @@ async function getUnresolvedImportsFromLexer(rawCode: string): Promise<Unresolve
|
|
|
541
550
|
return unresolvedImports
|
|
542
551
|
}
|
|
543
552
|
|
|
553
|
+
function getDisplayImportSpecifier(specifier: string): string {
|
|
554
|
+
return restoreAuthoredInjectedPackageSpecifier(specifier) ?? specifier
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function maskAuthoredInjectedPackageImports(sourceText: string, resolvedPath: string): string {
|
|
558
|
+
if (!mayContainInjectedPackageSpecifier(sourceText)) {
|
|
559
|
+
return sourceText
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
let parseResult = parseSync(resolvedPath, sourceText, {
|
|
563
|
+
lang: getSourceLanguageForPath(resolvedPath),
|
|
564
|
+
sourceType: 'module',
|
|
565
|
+
})
|
|
566
|
+
if (parseResult.errors.length > 0) {
|
|
567
|
+
return sourceText
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
let replacements: Array<{ end: number; specifier: string; start: number }> = []
|
|
571
|
+
|
|
572
|
+
walkAst(parseResult.program, (node) => {
|
|
573
|
+
if (
|
|
574
|
+
node.type !== 'ImportDeclaration' &&
|
|
575
|
+
node.type !== 'ExportAllDeclaration' &&
|
|
576
|
+
node.type !== 'ExportNamedDeclaration' &&
|
|
577
|
+
node.type !== 'ImportExpression'
|
|
578
|
+
) {
|
|
579
|
+
return
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
let source = 'source' in node ? node.source : null
|
|
583
|
+
if (!isStringLiteralNode(source)) return
|
|
584
|
+
|
|
585
|
+
let maskedSpecifier = maskAuthoredInjectedPackageSpecifier(source.value)
|
|
586
|
+
if (maskedSpecifier == null) return
|
|
587
|
+
|
|
588
|
+
replacements.push({
|
|
589
|
+
end: source.end - 1,
|
|
590
|
+
specifier: maskedSpecifier,
|
|
591
|
+
start: source.start + 1,
|
|
592
|
+
})
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
if (replacements.length === 0) return sourceText
|
|
596
|
+
|
|
597
|
+
let rewrittenSource = new MagicString(sourceText)
|
|
598
|
+
for (let replacement of replacements) {
|
|
599
|
+
rewrittenSource.overwrite(replacement.start, replacement.end, replacement.specifier)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return rewrittenSource.toString()
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function walkAst(node: Program | Node, visit: (node: Program | Node) => void): void {
|
|
606
|
+
visit(node)
|
|
607
|
+
|
|
608
|
+
let keys = visitorKeys[node.type]
|
|
609
|
+
if (!keys) return
|
|
610
|
+
|
|
611
|
+
let walkableNode = node as unknown as Record<string, unknown>
|
|
612
|
+
for (let key of keys) {
|
|
613
|
+
let value = walkableNode[key]
|
|
614
|
+
if (Array.isArray(value)) {
|
|
615
|
+
for (let child of value) {
|
|
616
|
+
if (isAstNode(child)) {
|
|
617
|
+
walkAst(child, visit)
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
continue
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (isAstNode(value)) {
|
|
624
|
+
walkAst(value, visit)
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function isAstNode(value: unknown): value is Node {
|
|
630
|
+
return typeof value === 'object' && value !== null && 'type' in value
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function isStringLiteralNode(node: Node | null | undefined): node is Node & {
|
|
634
|
+
end: number
|
|
635
|
+
start: number
|
|
636
|
+
value: string
|
|
637
|
+
} {
|
|
638
|
+
return (
|
|
639
|
+
node?.type === 'Literal' &&
|
|
640
|
+
typeof node.start === 'number' &&
|
|
641
|
+
typeof node.end === 'number' &&
|
|
642
|
+
typeof node.value === 'string'
|
|
643
|
+
)
|
|
644
|
+
}
|
|
645
|
+
|
|
544
646
|
function getStaticImportSpecifier(
|
|
545
647
|
source: string,
|
|
546
648
|
imported: ReturnType<typeof esModuleLexer>[0][number],
|
|
@@ -44,7 +44,14 @@ type StyleGetOptions = {
|
|
|
44
44
|
type StyleCompilerOptions = {
|
|
45
45
|
buildId?: string
|
|
46
46
|
fingerprintAssets: boolean
|
|
47
|
+
getServedFileUrl?(
|
|
48
|
+
identityPath: string,
|
|
49
|
+
options: {
|
|
50
|
+
transform: readonly string[] | null
|
|
51
|
+
},
|
|
52
|
+
): Promise<string>
|
|
47
53
|
isAllowed(absolutePath: string): boolean
|
|
54
|
+
isServedFilePath(filePath: string): boolean
|
|
48
55
|
minify: boolean
|
|
49
56
|
onWatchDirectoriesChange?: (delta: { add: string[]; remove: string[] }) => void
|
|
50
57
|
rootDir: string
|
|
@@ -79,6 +86,7 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
79
86
|
let emitInFlightByCacheKey = new Map<string, Promise<EmittedStyle>>()
|
|
80
87
|
let resolveArgs: ResolveArgs = {
|
|
81
88
|
isAllowed: resolvedOptions.isAllowed,
|
|
89
|
+
isServedFilePath: resolvedOptions.isServedFilePath,
|
|
82
90
|
isWatchIgnored,
|
|
83
91
|
routes: resolvedOptions.routes,
|
|
84
92
|
}
|
|
@@ -248,6 +256,7 @@ export function createStyleCompiler(options: StyleCompilerOptions): StyleCompile
|
|
|
248
256
|
let startedVersion = record.invalidationVersion
|
|
249
257
|
let resolvedStyle = await getOrCreateResolvedStyle(record)
|
|
250
258
|
let emitResolvedStyleResult = await emitResolvedStyle(resolvedStyle, {
|
|
259
|
+
getServedFileUrl: resolvedOptions.getServedFileUrl,
|
|
251
260
|
getServedUrl,
|
|
252
261
|
sourceMaps: resolvedOptions.sourceMaps,
|
|
253
262
|
})
|
package/src/lib/styles/emit.ts
CHANGED
|
@@ -34,6 +34,12 @@ type EmitResult =
|
|
|
34
34
|
export async function emitResolvedStyle(
|
|
35
35
|
resolvedStyle: ResolvedStyle,
|
|
36
36
|
options: {
|
|
37
|
+
getServedFileUrl?(
|
|
38
|
+
identityPath: string,
|
|
39
|
+
options: {
|
|
40
|
+
transform: readonly string[] | null
|
|
41
|
+
},
|
|
42
|
+
): Promise<string>
|
|
37
43
|
getServedUrl(identityPath: string): Promise<string>
|
|
38
44
|
sourceMaps?: 'external' | 'inline'
|
|
39
45
|
},
|
|
@@ -77,6 +83,12 @@ async function rewriteDependencies(
|
|
|
77
83
|
resolvedStyle: ResolvedStyle,
|
|
78
84
|
options: {
|
|
79
85
|
getServedUrl(identityPath: string): Promise<string>
|
|
86
|
+
getServedFileUrl?(
|
|
87
|
+
identityPath: string,
|
|
88
|
+
options: {
|
|
89
|
+
transform: readonly string[] | null
|
|
90
|
+
},
|
|
91
|
+
): Promise<string>
|
|
80
92
|
},
|
|
81
93
|
): Promise<{ code: string; sourceMap: string | null }> {
|
|
82
94
|
if (resolvedStyle.dependencies.length === 0) {
|
|
@@ -92,7 +104,14 @@ async function rewriteDependencies(
|
|
|
92
104
|
let replacement =
|
|
93
105
|
dependency.kind === 'external'
|
|
94
106
|
? dependency.replacement
|
|
95
|
-
:
|
|
107
|
+
: dependency.kind === 'style'
|
|
108
|
+
? `${await options.getServedUrl(dependency.depPath)}${dependency.suffix}`
|
|
109
|
+
: appendUrlSuffix(
|
|
110
|
+
await getServedFileUrl(options, resolvedStyle.identityPath, dependency.depPath, {
|
|
111
|
+
transform: dependency.requestTransform,
|
|
112
|
+
}),
|
|
113
|
+
dependency.suffix,
|
|
114
|
+
)
|
|
96
115
|
let start = resolvedStyle.rawCode.indexOf(dependency.placeholder)
|
|
97
116
|
if (start < 0) {
|
|
98
117
|
throw createAssetServerCompilationError(
|
|
@@ -117,6 +136,61 @@ async function rewriteDependencies(
|
|
|
117
136
|
}
|
|
118
137
|
}
|
|
119
138
|
|
|
139
|
+
function appendUrlSuffix(url: string, suffix: string): string {
|
|
140
|
+
if (!suffix.startsWith('?') || !url.includes('?')) {
|
|
141
|
+
return `${url}${suffix}`
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return `${url}&${suffix.slice(1)}`
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function getServedFileUrl(
|
|
148
|
+
options: {
|
|
149
|
+
getServedFileUrl?(
|
|
150
|
+
identityPath: string,
|
|
151
|
+
options: {
|
|
152
|
+
transform: readonly string[] | null
|
|
153
|
+
},
|
|
154
|
+
): Promise<string>
|
|
155
|
+
getServedUrl(identityPath: string): Promise<string>
|
|
156
|
+
},
|
|
157
|
+
importerPath: string,
|
|
158
|
+
identityPath: string,
|
|
159
|
+
request: {
|
|
160
|
+
transform: readonly string[] | null
|
|
161
|
+
},
|
|
162
|
+
): Promise<string> {
|
|
163
|
+
if (!options.getServedFileUrl) {
|
|
164
|
+
throw createAssetServerCompilationError(`Missing file URL resolver for ${identityPath}.`, {
|
|
165
|
+
code: 'EMIT_FAILED',
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
return await options.getServedFileUrl(identityPath, request)
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (
|
|
173
|
+
request.transform !== null &&
|
|
174
|
+
isAssetServerCompilationError(error) &&
|
|
175
|
+
error.code === 'FILE_TRANSFORM_QUERY_INVALID'
|
|
176
|
+
) {
|
|
177
|
+
console.warn(
|
|
178
|
+
`Invalid file transform request "${request.transform.join(',')}" in CSS asset ${importerPath} for ${identityPath}: ${error.message}`,
|
|
179
|
+
)
|
|
180
|
+
let href = await options.getServedFileUrl(identityPath, { transform: null })
|
|
181
|
+
let searchParams = new URLSearchParams()
|
|
182
|
+
for (let transform of request.transform) {
|
|
183
|
+
searchParams.append('transform', transform)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let search = searchParams.toString()
|
|
187
|
+
return search.length > 0 ? `${href}?${search}` : href
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
throw error
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
120
194
|
async function createEmittedAsset(content: string): Promise<EmittedAsset> {
|
|
121
195
|
return {
|
|
122
196
|
content,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from 'node:fs'
|
|
2
2
|
import * as path from 'node:path'
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
createAssetServerCompilationError,
|
|
@@ -22,10 +23,17 @@ type ResolvedDependency =
|
|
|
22
23
|
}
|
|
23
24
|
| {
|
|
24
25
|
depPath: string
|
|
25
|
-
kind: '
|
|
26
|
+
kind: 'style'
|
|
26
27
|
placeholder: string
|
|
27
28
|
suffix: string
|
|
28
29
|
}
|
|
30
|
+
| {
|
|
31
|
+
depPath: string
|
|
32
|
+
kind: 'file'
|
|
33
|
+
placeholder: string
|
|
34
|
+
requestTransform: readonly string[] | null
|
|
35
|
+
suffix: string
|
|
36
|
+
}
|
|
29
37
|
|
|
30
38
|
export type ResolvedStyle = {
|
|
31
39
|
dependencies: ResolvedDependency[]
|
|
@@ -41,6 +49,7 @@ export type ResolvedStyle = {
|
|
|
41
49
|
|
|
42
50
|
export type ResolveArgs = {
|
|
43
51
|
isAllowed(absolutePath: string): boolean
|
|
52
|
+
isServedFilePath(filePath: string): boolean
|
|
44
53
|
isWatchIgnored(filePath: string): boolean
|
|
45
54
|
routes: CompiledRoutes
|
|
46
55
|
}
|
|
@@ -58,6 +67,12 @@ type ResolveResult = {
|
|
|
58
67
|
}
|
|
59
68
|
)
|
|
60
69
|
|
|
70
|
+
type ParsedRelativeUrl = {
|
|
71
|
+
hash: string
|
|
72
|
+
resolvedPath: string
|
|
73
|
+
search: string
|
|
74
|
+
}
|
|
75
|
+
|
|
61
76
|
export async function resolveStyle(
|
|
62
77
|
record: StyleRecord,
|
|
63
78
|
transformed: TransformedStyle,
|
|
@@ -71,7 +86,7 @@ export async function resolveStyle(
|
|
|
71
86
|
let trackedFile =
|
|
72
87
|
unresolved.type === 'import'
|
|
73
88
|
? getTrackedImportFilePath(unresolved.url, transformed.resolvedPath)
|
|
74
|
-
:
|
|
89
|
+
: getTrackedUrlFilePath(unresolved.url, transformed.resolvedPath)
|
|
75
90
|
if (trackedFile && !args.isWatchIgnored(trackedFile)) {
|
|
76
91
|
trackedFiles.add(trackedFile)
|
|
77
92
|
}
|
|
@@ -85,14 +100,22 @@ export async function resolveStyle(
|
|
|
85
100
|
unresolved.placeholder,
|
|
86
101
|
args,
|
|
87
102
|
)
|
|
88
|
-
: resolveUrlDependency(
|
|
103
|
+
: resolveUrlDependency(
|
|
104
|
+
unresolved.url,
|
|
105
|
+
transformed.resolvedPath,
|
|
106
|
+
unresolved.placeholder,
|
|
107
|
+
args,
|
|
108
|
+
)
|
|
89
109
|
|
|
90
110
|
dependencies.push(resolved)
|
|
91
111
|
|
|
92
|
-
if (resolved.kind === '
|
|
112
|
+
if (resolved.kind === 'style' || resolved.kind === 'file') {
|
|
93
113
|
if (!args.isWatchIgnored(resolved.depPath)) {
|
|
94
114
|
trackedFiles.add(resolved.depPath)
|
|
95
115
|
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (resolved.kind === 'style') {
|
|
96
119
|
deps.add(resolved.depPath)
|
|
97
120
|
}
|
|
98
121
|
} catch (error) {
|
|
@@ -172,16 +195,8 @@ function resolveImportDependency(
|
|
|
172
195
|
}
|
|
173
196
|
}
|
|
174
197
|
|
|
175
|
-
let
|
|
176
|
-
if (
|
|
177
|
-
return {
|
|
178
|
-
kind: 'external',
|
|
179
|
-
placeholder,
|
|
180
|
-
replacement: url,
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (pathname.startsWith('/')) {
|
|
198
|
+
let parsedUrl = parseRelativeUrl(url, importerPath)
|
|
199
|
+
if (parsedUrl === null) {
|
|
185
200
|
return {
|
|
186
201
|
kind: 'external',
|
|
187
202
|
placeholder,
|
|
@@ -189,7 +204,8 @@ function resolveImportDependency(
|
|
|
189
204
|
}
|
|
190
205
|
}
|
|
191
206
|
|
|
192
|
-
let
|
|
207
|
+
let { hash, resolvedPath, search } = parsedUrl
|
|
208
|
+
let resolvedFilePath = resolvedPath
|
|
193
209
|
let identityPath = resolveExistingFilePath(resolvedFilePath)
|
|
194
210
|
if (!identityPath || !isStyleFilePath(identityPath)) {
|
|
195
211
|
throw createAssetServerCompilationError(
|
|
@@ -222,17 +238,82 @@ function resolveImportDependency(
|
|
|
222
238
|
|
|
223
239
|
return {
|
|
224
240
|
depPath: identityPath,
|
|
225
|
-
kind: '
|
|
241
|
+
kind: 'style',
|
|
226
242
|
placeholder,
|
|
227
|
-
suffix
|
|
243
|
+
suffix: `${search}${hash}`,
|
|
228
244
|
}
|
|
229
245
|
}
|
|
230
246
|
|
|
231
|
-
function resolveUrlDependency(
|
|
247
|
+
function resolveUrlDependency(
|
|
248
|
+
url: string,
|
|
249
|
+
importerPath: string,
|
|
250
|
+
placeholder: string,
|
|
251
|
+
args: ResolveArgs,
|
|
252
|
+
): ResolvedDependency {
|
|
253
|
+
if (isExternalUrl(url)) {
|
|
254
|
+
return {
|
|
255
|
+
kind: 'external',
|
|
256
|
+
placeholder,
|
|
257
|
+
replacement: url,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let parsedUrl = parseRelativeUrl(url, importerPath)
|
|
262
|
+
if (parsedUrl === null) {
|
|
263
|
+
return {
|
|
264
|
+
kind: 'external',
|
|
265
|
+
placeholder,
|
|
266
|
+
replacement: url,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let { hash, resolvedPath, search } = parsedUrl
|
|
271
|
+
let resolvedFilePath = resolvedPath
|
|
272
|
+
let identityPath = resolveExistingFilePath(resolvedFilePath)
|
|
273
|
+
if (!identityPath) {
|
|
274
|
+
throw createAssetServerCompilationError(`Failed to resolve url("${url}") in ${importerPath}.`, {
|
|
275
|
+
code: 'URL_RESOLUTION_FAILED',
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!args.isServedFilePath(identityPath)) {
|
|
280
|
+
throw createAssetServerCompilationError(
|
|
281
|
+
`URL "${url}" in ${importerPath}, resolved to "${identityPath}", is not a supported file asset. ` +
|
|
282
|
+
`Add the file extension to files.extensions to serve this asset.`,
|
|
283
|
+
{
|
|
284
|
+
code: 'URL_NOT_SUPPORTED',
|
|
285
|
+
},
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!args.isAllowed(identityPath)) {
|
|
290
|
+
throw createAssetServerCompilationError(
|
|
291
|
+
`URL "${url}" in ${importerPath}, resolved to "${identityPath}", is not allowed by the asset server allow/deny configuration. ` +
|
|
292
|
+
`Add a matching allow rule for this file path or remove a conflicting deny rule for this file path.`,
|
|
293
|
+
{
|
|
294
|
+
code: 'URL_NOT_ALLOWED',
|
|
295
|
+
},
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!args.routes.toUrlPathname(identityPath)) {
|
|
300
|
+
throw createAssetServerCompilationError(
|
|
301
|
+
`URL "${url}" in ${importerPath}, resolved to "${identityPath}", is outside all configured fileMap entries. ` +
|
|
302
|
+
`Add a matching fileMap entry for this file path.`,
|
|
303
|
+
{
|
|
304
|
+
code: 'URL_OUTSIDE_FILE_MAP',
|
|
305
|
+
},
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let parsedRequest = parseResolvedFileRequest({ hash, search })
|
|
310
|
+
|
|
232
311
|
return {
|
|
233
|
-
|
|
312
|
+
depPath: identityPath,
|
|
313
|
+
kind: 'file',
|
|
234
314
|
placeholder,
|
|
235
|
-
|
|
315
|
+
requestTransform: parsedRequest.transform,
|
|
316
|
+
suffix: parsedRequest.suffix,
|
|
236
317
|
}
|
|
237
318
|
}
|
|
238
319
|
|
|
@@ -245,34 +326,50 @@ function resolveExistingFilePath(filePath: string): string | null {
|
|
|
245
326
|
}
|
|
246
327
|
}
|
|
247
328
|
|
|
248
|
-
function
|
|
249
|
-
|
|
329
|
+
function parseRelativeUrl(url: string, importerPath: string): ParsedRelativeUrl | null {
|
|
330
|
+
if (url.length === 0 || url.startsWith('?') || url.startsWith('/') || isExternalUrl(url)) {
|
|
331
|
+
return null
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let parsed = new URL(url, pathToFileURL(importerPath))
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
hash: parsed.hash,
|
|
338
|
+
resolvedPath: normalizeFilePath(fileURLToPath(parsed)),
|
|
339
|
+
search: parsed.search,
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function parseResolvedFileRequest(url: Pick<ParsedRelativeUrl, 'hash' | 'search'>): {
|
|
250
344
|
suffix: string
|
|
345
|
+
transform: readonly string[] | null
|
|
251
346
|
} {
|
|
252
|
-
let
|
|
253
|
-
let hashIndex = url.indexOf('#')
|
|
254
|
-
let endIndex = [queryIndex, hashIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0]
|
|
347
|
+
let { hash, search } = url
|
|
255
348
|
|
|
256
|
-
if (
|
|
349
|
+
if (search.length === 0) {
|
|
257
350
|
return {
|
|
258
|
-
|
|
259
|
-
|
|
351
|
+
suffix: hash,
|
|
352
|
+
transform: null,
|
|
260
353
|
}
|
|
261
354
|
}
|
|
262
355
|
|
|
356
|
+
let searchParams = new URLSearchParams(search.slice(1))
|
|
357
|
+
let transform = searchParams.getAll('transform')
|
|
358
|
+
searchParams.delete('transform')
|
|
359
|
+
let remainingSearch = searchParams.toString()
|
|
360
|
+
|
|
263
361
|
return {
|
|
264
|
-
|
|
265
|
-
|
|
362
|
+
suffix: `${remainingSearch.length > 0 ? `?${remainingSearch}` : ''}${hash}`,
|
|
363
|
+
transform: transform.length > 0 ? transform : null,
|
|
266
364
|
}
|
|
267
365
|
}
|
|
268
366
|
|
|
269
367
|
function getTrackedImportFilePath(specifier: string, importerPath: string): string | null {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return normalizeFilePath(path.resolve(path.dirname(importerPath), pathname))
|
|
273
|
-
}
|
|
368
|
+
return parseRelativeUrl(specifier, importerPath)?.resolvedPath ?? null
|
|
369
|
+
}
|
|
274
370
|
|
|
275
|
-
|
|
371
|
+
function getTrackedUrlFilePath(url: string, importerPath: string): string | null {
|
|
372
|
+
return parseRelativeUrl(url, importerPath)?.resolvedPath ?? null
|
|
276
373
|
}
|
|
277
374
|
|
|
278
375
|
function isStyleFilePath(filePath: string): boolean {
|
package/src/lib/watch.ts
CHANGED
|
@@ -17,7 +17,10 @@ export type ChokidarWatcher = ReturnType<typeof chokidar.watch>
|
|
|
17
17
|
export type AssetServerWatcher = {
|
|
18
18
|
close(): Promise<void>
|
|
19
19
|
getWatchedTargets(): readonly string[]
|
|
20
|
-
updateWatchedDirectories(
|
|
20
|
+
updateWatchedDirectories(
|
|
21
|
+
delta: { add: readonly string[]; remove: readonly string[] },
|
|
22
|
+
options?: { includeAncestors?: boolean },
|
|
23
|
+
): void
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export function createAssetServerWatcher(options: AssetServerWatcherOptions): AssetServerWatcher {
|
|
@@ -27,7 +30,8 @@ export function createAssetServerWatcher(options: AssetServerWatcherOptions): As
|
|
|
27
30
|
...resolveChokidarWatchOptions(options),
|
|
28
31
|
})
|
|
29
32
|
options.onChokidarWatcherCreated?.(watcher)
|
|
30
|
-
let
|
|
33
|
+
let watchedResolutionDirectories = new Set<string>()
|
|
34
|
+
let watchedFileDirectories = new Set<string>()
|
|
31
35
|
let watchedTargets = new Set<string>()
|
|
32
36
|
|
|
33
37
|
for (let event of ['add', 'change', 'unlink'] as const) {
|
|
@@ -46,8 +50,12 @@ export function createAssetServerWatcher(options: AssetServerWatcherOptions): As
|
|
|
46
50
|
getWatchedTargets() {
|
|
47
51
|
return [...watchedTargets]
|
|
48
52
|
},
|
|
49
|
-
updateWatchedDirectories(delta) {
|
|
50
|
-
let
|
|
53
|
+
updateWatchedDirectories(delta, updateOptions = {}) {
|
|
54
|
+
let includeAncestors = updateOptions.includeAncestors ?? true
|
|
55
|
+
let previousDirectories = includeAncestors
|
|
56
|
+
? watchedResolutionDirectories
|
|
57
|
+
: watchedFileDirectories
|
|
58
|
+
let nextWatchedDirectories = new Set(previousDirectories)
|
|
51
59
|
|
|
52
60
|
for (let directory of delta.add) {
|
|
53
61
|
nextWatchedDirectories.add(directory)
|
|
@@ -56,7 +64,17 @@ export function createAssetServerWatcher(options: AssetServerWatcherOptions): As
|
|
|
56
64
|
nextWatchedDirectories.delete(directory)
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
|
|
67
|
+
if (includeAncestors) {
|
|
68
|
+
watchedResolutionDirectories = nextWatchedDirectories
|
|
69
|
+
} else {
|
|
70
|
+
watchedFileDirectories = nextWatchedDirectories
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let nextTargets = getWatchTargets({
|
|
74
|
+
rootDir: options.rootDir,
|
|
75
|
+
fileDirectories: [...watchedFileDirectories],
|
|
76
|
+
resolutionDirectories: [...watchedResolutionDirectories],
|
|
77
|
+
})
|
|
60
78
|
let targetsToAdd = [...nextTargets].filter((target) => !watchedTargets.has(target))
|
|
61
79
|
let targetsToRemove = [...watchedTargets].filter((target) => !nextTargets.has(target))
|
|
62
80
|
|
|
@@ -67,7 +85,6 @@ export function createAssetServerWatcher(options: AssetServerWatcherOptions): As
|
|
|
67
85
|
watcher.add(targetsToAdd)
|
|
68
86
|
}
|
|
69
87
|
|
|
70
|
-
watchedDirectories = nextWatchedDirectories
|
|
71
88
|
watchedTargets = nextTargets
|
|
72
89
|
},
|
|
73
90
|
}
|
|
@@ -88,15 +105,20 @@ function resolveChokidarWatchOptions(
|
|
|
88
105
|
}
|
|
89
106
|
}
|
|
90
107
|
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
108
|
+
function getWatchTargets(options: {
|
|
109
|
+
fileDirectories: readonly string[]
|
|
110
|
+
resolutionDirectories: readonly string[]
|
|
111
|
+
rootDir: string
|
|
112
|
+
}): Set<string> {
|
|
113
|
+
let normalizedRootDir = normalizeFilePath(options.rootDir)
|
|
96
114
|
let targets = new Set<string>()
|
|
97
115
|
let configAncestors = new Set<string>()
|
|
98
116
|
|
|
99
|
-
for (let directory of
|
|
117
|
+
for (let directory of options.fileDirectories) {
|
|
118
|
+
targets.add(normalizeFilePath(directory).replace(/\/+$/, ''))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (let directory of options.resolutionDirectories) {
|
|
100
122
|
let normalizedDirectory = normalizeFilePath(directory).replace(/\/+$/, '')
|
|
101
123
|
|
|
102
124
|
targets.add(normalizedDirectory)
|