@remix-run/assets 0.3.0 → 0.4.1
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 +206 -8
- 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 +7 -1
- package/dist/lib/asset-server.d.ts +29 -6
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +176 -17
- 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/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +28 -20
- 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 +31 -14
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +35 -8
- 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.map +1 -1
- package/dist/lib/scripts/transform.js +3 -5
- 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 +7 -5
- package/src/assets.ts +1 -0
- package/src/lib/access.ts +8 -1
- package/src/lib/asset-server.ts +273 -28
- 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/routes.ts +36 -33
- package/src/lib/scripts/compiler.ts +44 -17
- package/src/lib/scripts/resolve.ts +57 -9
- package/src/lib/scripts/specifiers.ts +11 -0
- package/src/lib/scripts/transform.ts +3 -6
- 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
package/src/lib/routes.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { RoutePattern } from '@remix-run/route-pattern'
|
|
2
|
+
import { createHref } from '@remix-run/route-pattern/href'
|
|
3
|
+
import { createMatcher, type Matcher } from '@remix-run/route-pattern/match'
|
|
2
4
|
|
|
3
5
|
import {
|
|
4
6
|
getRelativeFilePath,
|
|
@@ -21,7 +23,9 @@ interface RouteConfig {
|
|
|
21
23
|
interface CompiledRoute {
|
|
22
24
|
rootDir: string
|
|
23
25
|
urlPattern: RoutePattern
|
|
26
|
+
urlMatcher: Matcher
|
|
24
27
|
filePattern: RoutePattern
|
|
28
|
+
fileMatcher: Matcher
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
export interface CompiledRoutes {
|
|
@@ -67,9 +71,9 @@ export function compileRoutes(
|
|
|
67
71
|
let normalizedPathname = normalizePathname(pathname)
|
|
68
72
|
|
|
69
73
|
for (let route of compiledRoutes) {
|
|
70
|
-
let match = route.
|
|
74
|
+
let match = route.urlMatcher.match(`http://remix.run${normalizedPathname}`)
|
|
71
75
|
if (!match) continue
|
|
72
|
-
let relativeFilePath = route.filePattern
|
|
76
|
+
let relativeFilePath = createHref(route.filePattern, match.params).replace(/^\/+/, '')
|
|
73
77
|
return resolveFilePath(route.rootDir, relativeFilePath)
|
|
74
78
|
}
|
|
75
79
|
|
|
@@ -80,9 +84,9 @@ export function compileRoutes(
|
|
|
80
84
|
|
|
81
85
|
for (let route of compiledRoutes) {
|
|
82
86
|
let relativeFilePath = getRelativeFilePath(route.rootDir, normalizedFilePath)
|
|
83
|
-
let match = route.
|
|
87
|
+
let match = route.fileMatcher.match(`http://remix.run/${relativeFilePath}`)
|
|
84
88
|
if (!match) continue
|
|
85
|
-
return normalizePathname(route.urlPattern
|
|
89
|
+
return normalizePathname(createHref(route.urlPattern, match.params))
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
return null
|
|
@@ -104,8 +108,8 @@ function compileRoute(
|
|
|
104
108
|
)
|
|
105
109
|
let filePatternSource = normalizeFilePattern(route.filePattern)
|
|
106
110
|
|
|
107
|
-
let urlPattern =
|
|
108
|
-
let filePattern =
|
|
111
|
+
let urlPattern = RoutePattern.parse(urlPatternSource)
|
|
112
|
+
let filePattern = RoutePattern.parse(filePatternSource)
|
|
109
113
|
|
|
110
114
|
validateNoUnnamedWildcards(urlPattern, 'URL')
|
|
111
115
|
validateNoUnnamedWildcards(filePattern, 'File')
|
|
@@ -114,37 +118,30 @@ function compileRoute(
|
|
|
114
118
|
return {
|
|
115
119
|
rootDir: normalizeFilePath(options.rootDir).replace(/\/+$/, ''),
|
|
116
120
|
urlPattern,
|
|
121
|
+
urlMatcher: createMatcher(urlPattern),
|
|
117
122
|
filePattern,
|
|
123
|
+
fileMatcher: createMatcher(stripDotSegments(filePatternSource)),
|
|
118
124
|
}
|
|
119
125
|
}
|
|
120
126
|
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
match: Array<{ name: string; type: ':' | '*'; value: string }>,
|
|
124
|
-
): Record<string, string | undefined> {
|
|
125
|
-
let params: Record<string, string | undefined> = {}
|
|
127
|
+
function stripDotSegments(pattern: string): string {
|
|
128
|
+
let segments: string[] = []
|
|
126
129
|
|
|
127
|
-
for (let
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
params[param.name] = param.value
|
|
130
|
+
for (let segment of pattern.split('/')) {
|
|
131
|
+
if (segment === '' || segment === '.') continue
|
|
132
|
+
if (segment === '..') {
|
|
133
|
+
segments.pop()
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
segments.push(segment)
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
return
|
|
139
|
+
return segments.join('/')
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
function validateRoutePatterns(urlPattern: RoutePattern, filePattern: RoutePattern): void {
|
|
141
|
-
let urlParams = urlPattern
|
|
142
|
-
|
|
143
|
-
)
|
|
144
|
-
let fileParams = filePattern.ast.pathname.params.map(
|
|
145
|
-
(param: { name: string; type: ':' | '*' }) => `${param.type}:${param.name}`,
|
|
146
|
-
)
|
|
147
|
-
|
|
143
|
+
let urlParams = getPathnameParams(urlPattern)
|
|
144
|
+
let fileParams = getPathnameParams(filePattern)
|
|
148
145
|
if (urlParams.length !== fileParams.length) {
|
|
149
146
|
throw new Error(
|
|
150
147
|
`Route patterns must have matching capture structure.\nURL: ${urlPattern}\nFile: ${filePattern}`,
|
|
@@ -152,7 +149,9 @@ function validateRoutePatterns(urlPattern: RoutePattern, filePattern: RoutePatte
|
|
|
152
149
|
}
|
|
153
150
|
|
|
154
151
|
for (let i = 0; i < urlParams.length; i++) {
|
|
155
|
-
|
|
152
|
+
let urlParam = urlParams[i]
|
|
153
|
+
let fileParam = fileParams[i]
|
|
154
|
+
if (urlParam.type !== fileParam.type || urlParam.name !== fileParam.name) {
|
|
156
155
|
throw new Error(
|
|
157
156
|
`Route patterns must have matching capture structure.\nURL: ${urlPattern}\nFile: ${filePattern}`,
|
|
158
157
|
)
|
|
@@ -161,13 +160,17 @@ function validateRoutePatterns(urlPattern: RoutePattern, filePattern: RoutePatte
|
|
|
161
160
|
}
|
|
162
161
|
|
|
163
162
|
function validateNoUnnamedWildcards(pattern: RoutePattern, label: string): void {
|
|
164
|
-
if (
|
|
165
|
-
pattern.ast.pathname.params.some(
|
|
166
|
-
(param: { name: string; type: ':' | '*' }) => param.type === '*' && param.name === '*',
|
|
167
|
-
)
|
|
168
|
-
) {
|
|
163
|
+
if (pattern.pathname.tokens.some((token) => token.type === '*' && token.name === '*')) {
|
|
169
164
|
throw new Error(
|
|
170
165
|
`${label} route patterns must use named wildcards for reversible mapping.\nPattern: ${pattern}`,
|
|
171
166
|
)
|
|
172
167
|
}
|
|
173
168
|
}
|
|
169
|
+
|
|
170
|
+
type PathnameParam = Extract<RoutePattern['pathname']['tokens'][number], { type: ':' | '*' }>
|
|
171
|
+
|
|
172
|
+
function getPathnameParams(pattern: RoutePattern): Array<PathnameParam> {
|
|
173
|
+
return pattern.pathname.tokens.filter(
|
|
174
|
+
(token): token is PathnameParam => token.type === ':' || token.type === '*',
|
|
175
|
+
)
|
|
176
|
+
}
|
|
@@ -34,6 +34,7 @@ import { createTsconfigTransformOptionsResolver, transformModule } from './trans
|
|
|
34
34
|
import type { ResolveModuleResult, TransformArgs, TransformedModule } from './transform.ts'
|
|
35
35
|
import { ResolverFactory } from 'oxc-resolver'
|
|
36
36
|
import type { EmittedAsset, EmittedModule } from './emit.ts'
|
|
37
|
+
import { isBareImportSpecifier } from './specifiers.ts'
|
|
37
38
|
|
|
38
39
|
type ScriptRecord = ModuleRecord<TransformedModule, ResolvedModule, EmittedModule>
|
|
39
40
|
type ScriptStore = ModuleStore<TransformedModule, ResolvedModule, EmittedModule>
|
|
@@ -66,6 +67,7 @@ type ScriptCompilerOptions = {
|
|
|
66
67
|
external: string[]
|
|
67
68
|
fingerprintAssets: boolean
|
|
68
69
|
isAllowed(absolutePath: string): boolean
|
|
70
|
+
isDenied(absolutePath: string): boolean
|
|
69
71
|
minify: boolean
|
|
70
72
|
onWatchDirectoriesChange?: (delta: { add: string[]; remove: string[] }) => void
|
|
71
73
|
rootDir: string
|
|
@@ -117,8 +119,14 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
117
119
|
extensionAlias: resolverExtensionAlias,
|
|
118
120
|
extensions: resolverExtensions,
|
|
119
121
|
mainFields: ['browser', 'module', 'main'],
|
|
122
|
+
symlinks: false,
|
|
120
123
|
tsconfig: 'auto',
|
|
121
124
|
})
|
|
125
|
+
let resolveModulePathOptions = {
|
|
126
|
+
isAllowed: resolvedOptions.isAllowed,
|
|
127
|
+
isDenied: resolvedOptions.isDenied,
|
|
128
|
+
routes: resolvedOptions.routes,
|
|
129
|
+
}
|
|
122
130
|
let resolveInFlightByCacheKey = new Map<string, Promise<ResolvedModule>>()
|
|
123
131
|
let emitInFlightByCacheKey = new Map<string, Promise<EmittedModule>>()
|
|
124
132
|
|
|
@@ -138,7 +146,9 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
138
146
|
let resolveArgs: ResolveArgs = {
|
|
139
147
|
isAllowed: resolvedOptions.isAllowed,
|
|
140
148
|
isWatchIgnored,
|
|
141
|
-
resolveModulePath
|
|
149
|
+
resolveModulePath(absolutePath) {
|
|
150
|
+
return resolveModulePath(absolutePath, resolveModulePathOptions)
|
|
151
|
+
},
|
|
142
152
|
resolverFactory,
|
|
143
153
|
routes: resolvedOptions.routes,
|
|
144
154
|
}
|
|
@@ -253,7 +263,7 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
253
263
|
}
|
|
254
264
|
|
|
255
265
|
function resolveServedScriptOrThrow(absolutePath: string): ResolveModuleResult {
|
|
256
|
-
let resolvedModule = resolveModulePath(absolutePath)
|
|
266
|
+
let resolvedModule = resolveModulePath(absolutePath, resolveModulePathOptions)
|
|
257
267
|
if (!resolvedModule) {
|
|
258
268
|
throw createAssetServerCompilationError(`File not found: ${absolutePath}`, {
|
|
259
269
|
code: 'FILE_NOT_FOUND',
|
|
@@ -548,11 +558,19 @@ function shouldClearResolverCacheForFileEvent(filePath: string, event: ModuleWat
|
|
|
548
558
|
return event !== 'change' || isPackageJsonPath(filePath) || isTsconfigPath(filePath)
|
|
549
559
|
}
|
|
550
560
|
|
|
551
|
-
function resolveModulePath(
|
|
561
|
+
function resolveModulePath(
|
|
562
|
+
absolutePath: string,
|
|
563
|
+
options: {
|
|
564
|
+
isAllowed(absolutePath: string): boolean
|
|
565
|
+
isDenied(absolutePath: string): boolean
|
|
566
|
+
routes: CompiledRoutes
|
|
567
|
+
},
|
|
568
|
+
): ResolveModuleResult | null {
|
|
569
|
+
let candidateIdentityPath = normalizeFilePath(absolutePath)
|
|
552
570
|
let resolvedPath: string
|
|
553
571
|
|
|
554
572
|
try {
|
|
555
|
-
resolvedPath = normalizeFilePath(fs.realpathSync(
|
|
573
|
+
resolvedPath = normalizeFilePath(fs.realpathSync(candidateIdentityPath))
|
|
556
574
|
} catch (error) {
|
|
557
575
|
if (isNoEntityError(error)) return null
|
|
558
576
|
throw error
|
|
@@ -563,11 +581,32 @@ function resolveModulePath(absolutePath: string): ResolveModuleResult | null {
|
|
|
563
581
|
}
|
|
564
582
|
|
|
565
583
|
return {
|
|
566
|
-
identityPath: resolvedPath,
|
|
584
|
+
identityPath: getModuleIdentityPath(candidateIdentityPath, resolvedPath, options),
|
|
567
585
|
resolvedPath,
|
|
568
586
|
}
|
|
569
587
|
}
|
|
570
588
|
|
|
589
|
+
function getModuleIdentityPath(
|
|
590
|
+
candidateIdentityPath: string,
|
|
591
|
+
resolvedPath: string,
|
|
592
|
+
options: {
|
|
593
|
+
isAllowed(absolutePath: string): boolean
|
|
594
|
+
isDenied(absolutePath: string): boolean
|
|
595
|
+
routes: CompiledRoutes
|
|
596
|
+
},
|
|
597
|
+
): string {
|
|
598
|
+
if (candidateIdentityPath === resolvedPath) return resolvedPath
|
|
599
|
+
if (!containsNodeModulesPathSegment(candidateIdentityPath)) return resolvedPath
|
|
600
|
+
if (!options.routes.toUrlPathname(candidateIdentityPath)) return resolvedPath
|
|
601
|
+
if (!options.isAllowed(candidateIdentityPath)) return resolvedPath
|
|
602
|
+
if (options.isDenied(resolvedPath)) return resolvedPath
|
|
603
|
+
return candidateIdentityPath
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function containsNodeModulesPathSegment(filePath: string): boolean {
|
|
607
|
+
return filePath.split('/').includes('node_modules')
|
|
608
|
+
}
|
|
609
|
+
|
|
571
610
|
function resolveActualPath(identityPath: string): string | null {
|
|
572
611
|
try {
|
|
573
612
|
return normalizeFilePath(fs.realpathSync(identityPath))
|
|
@@ -577,18 +616,6 @@ function resolveActualPath(identityPath: string): string | null {
|
|
|
577
616
|
}
|
|
578
617
|
}
|
|
579
618
|
|
|
580
|
-
function isBareImportSpecifier(specifier: string): boolean {
|
|
581
|
-
return (
|
|
582
|
-
!specifier.startsWith('./') &&
|
|
583
|
-
!specifier.startsWith('../') &&
|
|
584
|
-
!specifier.startsWith('/') &&
|
|
585
|
-
!specifier.startsWith('file:') &&
|
|
586
|
-
!specifier.startsWith('data:') &&
|
|
587
|
-
!specifier.startsWith('http://') &&
|
|
588
|
-
!specifier.startsWith('https://')
|
|
589
|
-
)
|
|
590
|
-
}
|
|
591
|
-
|
|
592
619
|
function isNoEntityError(
|
|
593
620
|
error: unknown,
|
|
594
621
|
): error is NodeJS.ErrnoException & { code: 'ENOENT' | 'ENOTDIR' } {
|
|
@@ -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
|
|
|
@@ -91,6 +92,11 @@ type NormalizedSpecifierResolution = {
|
|
|
91
92
|
specifier: string
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
type SpecifierResolutionImporter = {
|
|
96
|
+
identityPath: string
|
|
97
|
+
resolvedPath: string
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
export async function resolveModule(
|
|
95
101
|
record: ScriptRecord,
|
|
96
102
|
transformed: TransformedModule,
|
|
@@ -105,7 +111,10 @@ export async function resolveModule(
|
|
|
105
111
|
transformed.unresolvedImports.length > 0
|
|
106
112
|
? await batchResolveSpecifiers(
|
|
107
113
|
getUniqueSpecifiers(transformed.unresolvedImports),
|
|
108
|
-
|
|
114
|
+
{
|
|
115
|
+
identityPath: transformed.identityPath,
|
|
116
|
+
resolvedPath: transformed.resolvedPath,
|
|
117
|
+
},
|
|
109
118
|
args.resolverFactory,
|
|
110
119
|
)
|
|
111
120
|
: new Map<string, ResolvedSpec>()
|
|
@@ -196,8 +205,10 @@ export async function resolveModule(
|
|
|
196
205
|
deps.add(resolvedImport.identityPath)
|
|
197
206
|
|
|
198
207
|
if (transformed.packageSpecifiers.includes(unresolved.specifier)) {
|
|
199
|
-
let packageJsonPath =
|
|
200
|
-
resolvedSpec.packageJsonPath
|
|
208
|
+
let packageJsonPath = resolvePackageJsonPath(
|
|
209
|
+
resolvedSpec.packageJsonPath,
|
|
210
|
+
resolvedImport.resolvedPath,
|
|
211
|
+
)
|
|
201
212
|
if (packageJsonPath && !args.isWatchIgnored(packageJsonPath)) {
|
|
202
213
|
trackedFiles.add(packageJsonPath)
|
|
203
214
|
}
|
|
@@ -235,6 +246,25 @@ export async function resolveModule(
|
|
|
235
246
|
}
|
|
236
247
|
}
|
|
237
248
|
|
|
249
|
+
function resolvePackageJsonPath(
|
|
250
|
+
packageJsonPath: string | null,
|
|
251
|
+
resolvedPath: string,
|
|
252
|
+
): string | null {
|
|
253
|
+
return (
|
|
254
|
+
(packageJsonPath ? resolveExistingPath(packageJsonPath) : null) ??
|
|
255
|
+
findNearestPackageJsonPath(resolvedPath)
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function resolveExistingPath(filePath: string): string | null {
|
|
260
|
+
try {
|
|
261
|
+
return normalizeFilePath(fs.realpathSync(filePath))
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (isNoEntityError(error)) return null
|
|
264
|
+
throw error
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
238
268
|
function findNearestPackageJsonPath(filePath: string): string | null {
|
|
239
269
|
let directory = path.dirname(filePath)
|
|
240
270
|
|
|
@@ -250,6 +280,17 @@ function findNearestPackageJsonPath(filePath: string): string | null {
|
|
|
250
280
|
}
|
|
251
281
|
}
|
|
252
282
|
|
|
283
|
+
function isNoEntityError(
|
|
284
|
+
error: unknown,
|
|
285
|
+
): error is NodeJS.ErrnoException & { code: 'ENOENT' | 'ENOTDIR' } {
|
|
286
|
+
return (
|
|
287
|
+
error instanceof Error &&
|
|
288
|
+
'code' in error &&
|
|
289
|
+
((error as NodeJS.ErrnoException).code === 'ENOENT' ||
|
|
290
|
+
(error as NodeJS.ErrnoException).code === 'ENOTDIR')
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
253
294
|
function isRelativeImportSpecifier(specifier: string): boolean {
|
|
254
295
|
return specifier.startsWith('./') || specifier.startsWith('../')
|
|
255
296
|
}
|
|
@@ -326,7 +367,7 @@ function resolveCandidateBasePath(importerDir: string, specifier: string): strin
|
|
|
326
367
|
|
|
327
368
|
async function batchResolveSpecifiers(
|
|
328
369
|
specifiers: string[],
|
|
329
|
-
|
|
370
|
+
importer: SpecifierResolutionImporter,
|
|
330
371
|
resolverFactory: ResolveArgs['resolverFactory'],
|
|
331
372
|
): Promise<Map<string, ResolvedSpec>> {
|
|
332
373
|
let resolvedBySpecifier = new Map<string, ResolvedSpec>()
|
|
@@ -334,7 +375,7 @@ async function batchResolveSpecifiers(
|
|
|
334
375
|
|
|
335
376
|
try {
|
|
336
377
|
for (let specifier of specifiers) {
|
|
337
|
-
let normalizedResolution = normalizeSpecifierResolution(specifier,
|
|
378
|
+
let normalizedResolution = normalizeSpecifierResolution(specifier, importer)
|
|
338
379
|
let resolutionResult = await resolverFactory.resolveFileAsync(
|
|
339
380
|
normalizedResolution.importerPath,
|
|
340
381
|
normalizedResolution.specifier,
|
|
@@ -368,7 +409,7 @@ async function batchResolveSpecifiers(
|
|
|
368
409
|
}
|
|
369
410
|
|
|
370
411
|
throw createAssetServerCompilationError(
|
|
371
|
-
`Failed to resolve imports in ${
|
|
412
|
+
`Failed to resolve imports in ${importer.identityPath}. ${formatUnknownError(error)}`,
|
|
372
413
|
{
|
|
373
414
|
cause: error,
|
|
374
415
|
code: 'IMPORT_RESOLUTION_FAILED',
|
|
@@ -389,12 +430,12 @@ function formatUnknownError(error: unknown): string {
|
|
|
389
430
|
|
|
390
431
|
function normalizeSpecifierResolution(
|
|
391
432
|
specifier: string,
|
|
392
|
-
|
|
433
|
+
importer: SpecifierResolutionImporter,
|
|
393
434
|
): NormalizedSpecifierResolution {
|
|
394
435
|
let authoredInjectedPackageSpecifier = restoreAuthoredInjectedPackageSpecifier(specifier)
|
|
395
436
|
if (authoredInjectedPackageSpecifier) {
|
|
396
437
|
return {
|
|
397
|
-
importerPath,
|
|
438
|
+
importerPath: getSpecifierImporterPath(authoredInjectedPackageSpecifier, importer),
|
|
398
439
|
specifier: authoredInjectedPackageSpecifier,
|
|
399
440
|
}
|
|
400
441
|
}
|
|
@@ -407,11 +448,18 @@ function normalizeSpecifierResolution(
|
|
|
407
448
|
}
|
|
408
449
|
|
|
409
450
|
return {
|
|
410
|
-
importerPath,
|
|
451
|
+
importerPath: getSpecifierImporterPath(specifier, importer),
|
|
411
452
|
specifier,
|
|
412
453
|
}
|
|
413
454
|
}
|
|
414
455
|
|
|
456
|
+
function getSpecifierImporterPath(
|
|
457
|
+
specifier: string,
|
|
458
|
+
importer: SpecifierResolutionImporter,
|
|
459
|
+
): string {
|
|
460
|
+
return isBareImportSpecifier(specifier) ? importer.resolvedPath : importer.identityPath
|
|
461
|
+
}
|
|
462
|
+
|
|
415
463
|
function getDisplayImportSpecifier(specifier: string): string {
|
|
416
464
|
return restoreAuthoredInjectedPackageSpecifier(specifier) ?? specifier
|
|
417
465
|
}
|
|
@@ -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
|
+
}
|
|
@@ -30,6 +30,7 @@ import { composeSourceMaps, rewriteSourceMapSources, stringifySourceMap } from '
|
|
|
30
30
|
import type { EmittedModule } from './emit.ts'
|
|
31
31
|
import type { ResolvedScriptTarget } from '../target.ts'
|
|
32
32
|
import type { ResolvedModule } from './resolve.ts'
|
|
33
|
+
import { isBareImportSpecifier } from './specifiers.ts'
|
|
33
34
|
|
|
34
35
|
type ScriptRecord = ModuleRecord<TransformedModule, ResolvedModule, EmittedModule>
|
|
35
36
|
|
|
@@ -261,9 +262,9 @@ export async function transformModule(
|
|
|
261
262
|
content: sourceText,
|
|
262
263
|
}),
|
|
263
264
|
identityPath: record.identityPath,
|
|
264
|
-
importerDir: path.dirname(
|
|
265
|
+
importerDir: path.dirname(record.identityPath),
|
|
265
266
|
packageSpecifiers: analysis.unresolvedImports
|
|
266
|
-
.filter((unresolved) =>
|
|
267
|
+
.filter((unresolved) => isBareImportSpecifier(unresolved.specifier))
|
|
267
268
|
.map((unresolved) => unresolved.specifier),
|
|
268
269
|
rawCode: analysis.rawCode,
|
|
269
270
|
resolvedPath,
|
|
@@ -299,10 +300,6 @@ function findNearestTsconfigPath(directory: string): string | null {
|
|
|
299
300
|
}
|
|
300
301
|
}
|
|
301
302
|
|
|
302
|
-
function isPackageImportSpecifier(specifier: string): boolean {
|
|
303
|
-
return !specifier.startsWith('./') && !specifier.startsWith('../') && !specifier.startsWith('/')
|
|
304
|
-
}
|
|
305
|
-
|
|
306
303
|
async function analyzeModuleSource(
|
|
307
304
|
sourceText: string,
|
|
308
305
|
resolvedPath: string,
|
|
@@ -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,
|