@remix-run/assets 0.1.0 → 0.2.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 +53 -47
- package/dist/lib/asset-server.d.ts +45 -43
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +142 -46
- package/dist/lib/compilation-error.d.ts +2 -2
- package/dist/lib/compilation-error.d.ts.map +1 -1
- package/dist/lib/compilation-error.js +1 -1
- package/dist/lib/file-matcher.d.ts.map +1 -1
- package/dist/lib/file-matcher.js +3 -2
- package/dist/lib/module-store.d.ts +41 -0
- package/dist/lib/module-store.d.ts.map +1 -0
- package/dist/lib/{scripts/store.js → module-store.js} +43 -41
- package/dist/lib/scripts/compiler.d.ts +15 -15
- package/dist/lib/scripts/compiler.d.ts.map +1 -1
- package/dist/lib/scripts/compiler.js +50 -46
- package/dist/lib/scripts/emit.js +2 -2
- package/dist/lib/scripts/resolve.d.ts +7 -17
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +15 -9
- package/dist/lib/scripts/transform.d.ts +11 -9
- package/dist/lib/scripts/transform.d.ts.map +1 -1
- package/dist/lib/scripts/transform.js +32 -21
- package/dist/lib/source-maps.d.ts +1 -1
- package/dist/lib/source-maps.d.ts.map +1 -1
- package/dist/lib/source-maps.js +7 -1
- package/dist/lib/styles/compiler.d.ts +52 -0
- package/dist/lib/styles/compiler.d.ts.map +1 -0
- package/dist/lib/styles/compiler.js +272 -0
- package/dist/lib/styles/emit.d.ts +25 -0
- package/dist/lib/styles/emit.d.ts.map +1 -0
- package/dist/lib/styles/emit.js +78 -0
- package/dist/lib/styles/resolve.d.ts +48 -0
- package/dist/lib/styles/resolve.d.ts.map +1 -0
- package/dist/lib/styles/resolve.js +188 -0
- package/dist/lib/styles/transform.d.ts +47 -0
- package/dist/lib/styles/transform.d.ts.map +1 -0
- package/dist/lib/styles/transform.js +131 -0
- package/dist/lib/target.d.ts +21 -0
- package/dist/lib/target.d.ts.map +1 -0
- package/dist/lib/target.js +127 -0
- package/package.json +7 -2
- package/src/lib/asset-server.ts +218 -96
- package/src/lib/compilation-error.ts +7 -7
- package/src/lib/file-matcher.ts +3 -2
- package/src/lib/{scripts/store.ts → module-store.ts} +100 -87
- package/src/lib/scripts/compiler.ts +88 -70
- package/src/lib/scripts/emit.ts +2 -2
- package/src/lib/scripts/resolve.ts +34 -23
- package/src/lib/scripts/transform.ts +49 -34
- package/src/lib/source-maps.ts +8 -1
- package/src/lib/styles/compiler.ts +400 -0
- package/src/lib/styles/emit.ts +137 -0
- package/src/lib/styles/resolve.ts +316 -0
- package/src/lib/styles/transform.ts +226 -0
- package/src/lib/target.ts +196 -0
- package/dist/lib/scripts/store.d.ts +0 -40
- package/dist/lib/scripts/store.d.ts.map +0 -1
|
@@ -15,11 +15,15 @@ import {
|
|
|
15
15
|
} from '../compilation-error.ts'
|
|
16
16
|
import type { AssetServerCompilationError } from '../compilation-error.ts'
|
|
17
17
|
import { generateFingerprint } from '../fingerprint.ts'
|
|
18
|
+
import type { ModuleRecord, ModuleTracking } from '../module-store.ts'
|
|
18
19
|
import { normalizeFilePath } from '../paths.ts'
|
|
19
20
|
import type { CompiledRoutes } from '../routes.ts'
|
|
20
|
-
import type { ScriptsTarget } from '../asset-server.ts'
|
|
21
|
-
import type { ModuleRecord } from './store.ts'
|
|
22
21
|
import { composeSourceMaps, rewriteSourceMapSources, stringifySourceMap } from '../source-maps.ts'
|
|
22
|
+
import type { EmittedModule } from './emit.ts'
|
|
23
|
+
import type { ResolvedScriptTarget } from '../target.ts'
|
|
24
|
+
import type { ResolvedModule } from './resolve.ts'
|
|
25
|
+
|
|
26
|
+
type ScriptRecord = ModuleRecord<TransformedModule, ResolvedModule, EmittedModule>
|
|
23
27
|
|
|
24
28
|
type SourceLanguage = 'js' | 'jsx' | 'ts' | 'tsx'
|
|
25
29
|
|
|
@@ -72,19 +76,18 @@ export type TransformedModule = {
|
|
|
72
76
|
unresolvedImports: UnresolvedImport[]
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
type TransformResult =
|
|
79
|
+
type TransformResult = {
|
|
80
|
+
tracking: ModuleTracking
|
|
81
|
+
} & (
|
|
80
82
|
| {
|
|
81
83
|
ok: true
|
|
82
84
|
value: TransformedModule
|
|
83
85
|
}
|
|
84
|
-
|
|
|
86
|
+
| {
|
|
85
87
|
ok: false
|
|
86
88
|
error: AssetServerCompilationError
|
|
87
|
-
}
|
|
89
|
+
}
|
|
90
|
+
)
|
|
88
91
|
|
|
89
92
|
type TsconfigTransformOptions = {
|
|
90
93
|
trackedFiles: string[]
|
|
@@ -103,7 +106,7 @@ export type TransformArgs = {
|
|
|
103
106
|
routes: CompiledRoutes
|
|
104
107
|
sourceMapSourcePaths: 'absolute' | 'url'
|
|
105
108
|
sourceMaps: 'external' | 'inline' | null
|
|
106
|
-
target:
|
|
109
|
+
target: ResolvedScriptTarget | null
|
|
107
110
|
tsconfigTransformOptionsResolver: TsconfigTransformOptionsResolver
|
|
108
111
|
}
|
|
109
112
|
|
|
@@ -144,17 +147,19 @@ export function createTsconfigTransformOptionsResolver() {
|
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
export async function transformModule(
|
|
147
|
-
record:
|
|
150
|
+
record: ScriptRecord,
|
|
148
151
|
args: TransformArgs,
|
|
149
152
|
): Promise<TransformResult> {
|
|
150
153
|
let resolvedPath = args.resolveActualPath(record.identityPath)
|
|
151
154
|
if (!resolvedPath) {
|
|
152
155
|
return {
|
|
153
156
|
ok: false,
|
|
154
|
-
error: createAssetServerCompilationError(`
|
|
155
|
-
code: '
|
|
157
|
+
error: createAssetServerCompilationError(`File not found: ${record.identityPath}`, {
|
|
158
|
+
code: 'FILE_NOT_FOUND',
|
|
156
159
|
}),
|
|
157
|
-
|
|
160
|
+
tracking: {
|
|
161
|
+
trackedFiles: args.isWatchIgnored(record.identityPath) ? [] : [record.identityPath],
|
|
162
|
+
},
|
|
158
163
|
}
|
|
159
164
|
}
|
|
160
165
|
|
|
@@ -173,17 +178,21 @@ export async function transformModule(
|
|
|
173
178
|
if (isNoEntityError(error)) {
|
|
174
179
|
return {
|
|
175
180
|
ok: false,
|
|
176
|
-
error: createAssetServerCompilationError(`
|
|
181
|
+
error: createAssetServerCompilationError(`File not found: ${resolvedPath}`, {
|
|
177
182
|
cause: error,
|
|
178
|
-
code: '
|
|
183
|
+
code: 'FILE_NOT_FOUND',
|
|
179
184
|
}),
|
|
180
|
-
|
|
185
|
+
tracking: {
|
|
186
|
+
trackedFiles,
|
|
187
|
+
},
|
|
181
188
|
}
|
|
182
189
|
}
|
|
183
190
|
return {
|
|
184
191
|
ok: false,
|
|
185
192
|
error: toTransformFailedError(error, resolvedPath),
|
|
186
|
-
|
|
193
|
+
tracking: {
|
|
194
|
+
trackedFiles,
|
|
195
|
+
},
|
|
187
196
|
}
|
|
188
197
|
}
|
|
189
198
|
|
|
@@ -205,7 +214,7 @@ export async function transformModule(
|
|
|
205
214
|
`This module uses CommonJS (require/module.exports) which is not supported. ` +
|
|
206
215
|
`Please use an ESM-compatible module.`,
|
|
207
216
|
{
|
|
208
|
-
code: '
|
|
217
|
+
code: 'COMMONJS_NOT_SUPPORTED',
|
|
209
218
|
},
|
|
210
219
|
)
|
|
211
220
|
}
|
|
@@ -213,9 +222,9 @@ export async function transformModule(
|
|
|
213
222
|
let stableUrlPathname = args.routes.toUrlPathname(record.identityPath)
|
|
214
223
|
if (!stableUrlPathname) {
|
|
215
224
|
throw createAssetServerCompilationError(
|
|
216
|
-
`
|
|
225
|
+
`File ${record.identityPath} is outside all configured fileMap entries.`,
|
|
217
226
|
{
|
|
218
|
-
code: '
|
|
227
|
+
code: 'FILE_OUTSIDE_FILE_MAP',
|
|
219
228
|
},
|
|
220
229
|
)
|
|
221
230
|
}
|
|
@@ -226,11 +235,15 @@ export async function transformModule(
|
|
|
226
235
|
resolvedPath,
|
|
227
236
|
stableUrlPathname,
|
|
228
237
|
args.sourceMapSourcePaths,
|
|
238
|
+
sourceText,
|
|
229
239
|
)
|
|
230
240
|
: null
|
|
231
241
|
|
|
232
242
|
return {
|
|
233
243
|
ok: true,
|
|
244
|
+
tracking: {
|
|
245
|
+
trackedFiles,
|
|
246
|
+
},
|
|
234
247
|
value: {
|
|
235
248
|
fingerprint:
|
|
236
249
|
args.buildId === null
|
|
@@ -256,7 +269,9 @@ export async function transformModule(
|
|
|
256
269
|
return {
|
|
257
270
|
ok: false,
|
|
258
271
|
error: toTransformFailedError(error, resolvedPath),
|
|
259
|
-
|
|
272
|
+
tracking: {
|
|
273
|
+
trackedFiles,
|
|
274
|
+
},
|
|
260
275
|
}
|
|
261
276
|
}
|
|
262
277
|
}
|
|
@@ -288,7 +303,7 @@ async function analyzeModuleSource(
|
|
|
288
303
|
define?: Record<string, string>
|
|
289
304
|
minify: boolean
|
|
290
305
|
sourceMaps?: 'external' | 'inline'
|
|
291
|
-
target?:
|
|
306
|
+
target?: ResolvedScriptTarget
|
|
292
307
|
},
|
|
293
308
|
) {
|
|
294
309
|
let transformResult: { code: string; errors?: Array<{ message?: string }>; map?: unknown }
|
|
@@ -302,10 +317,10 @@ async function analyzeModuleSource(
|
|
|
302
317
|
} catch (error) {
|
|
303
318
|
if (isAssetServerCompilationError(error)) throw error
|
|
304
319
|
throw createAssetServerCompilationError(
|
|
305
|
-
`Failed to transform
|
|
320
|
+
`Failed to transform script ${resolvedPath}. ${formatUnknownError(error)}`,
|
|
306
321
|
{
|
|
307
322
|
cause: error,
|
|
308
|
-
code: '
|
|
323
|
+
code: 'TRANSFORM_FAILED',
|
|
309
324
|
},
|
|
310
325
|
)
|
|
311
326
|
}
|
|
@@ -335,7 +350,7 @@ async function analyzeModuleSource(
|
|
|
335
350
|
async function minifyModule(
|
|
336
351
|
rawCode: string,
|
|
337
352
|
resolvedPath: string,
|
|
338
|
-
target:
|
|
353
|
+
target: ResolvedScriptTarget | undefined,
|
|
339
354
|
sourceMaps?: 'external' | 'inline',
|
|
340
355
|
) {
|
|
341
356
|
try {
|
|
@@ -350,10 +365,10 @@ async function minifyModule(
|
|
|
350
365
|
} catch (error) {
|
|
351
366
|
if (isAssetServerCompilationError(error)) throw error
|
|
352
367
|
throw createAssetServerCompilationError(
|
|
353
|
-
`Failed to minify
|
|
368
|
+
`Failed to minify script ${resolvedPath}. ${formatUnknownError(error)}`,
|
|
354
369
|
{
|
|
355
370
|
cause: error,
|
|
356
|
-
code: '
|
|
371
|
+
code: 'TRANSFORM_FAILED',
|
|
357
372
|
},
|
|
358
373
|
)
|
|
359
374
|
}
|
|
@@ -365,7 +380,7 @@ function getTransformOptions(
|
|
|
365
380
|
options: {
|
|
366
381
|
define?: Record<string, string>
|
|
367
382
|
sourceMaps?: 'external' | 'inline'
|
|
368
|
-
target?:
|
|
383
|
+
target?: ResolvedScriptTarget
|
|
369
384
|
},
|
|
370
385
|
): OxcTransformOptions {
|
|
371
386
|
let compilerOptions = transformOptions.tsconfigRaw?.compilerOptions as
|
|
@@ -436,7 +451,7 @@ function getJsxOptions(
|
|
|
436
451
|
`Unsupported tsconfig compilerOptions.jsx = "${jsx}" for ${resolvedPath}. ` +
|
|
437
452
|
`Asset server must compile JSX to browser-runnable JavaScript.`,
|
|
438
453
|
{
|
|
439
|
-
code: '
|
|
454
|
+
code: 'TRANSFORM_FAILED',
|
|
440
455
|
},
|
|
441
456
|
)
|
|
442
457
|
}
|
|
@@ -500,9 +515,9 @@ function assertNoCompilerErrors(
|
|
|
500
515
|
if (!errors || errors.length === 0) return
|
|
501
516
|
|
|
502
517
|
throw createAssetServerCompilationError(
|
|
503
|
-
`Failed to ${operation}
|
|
518
|
+
`Failed to ${operation} script ${resolvedPath}. ${errors[0].message ?? 'Unknown error'}`,
|
|
504
519
|
{
|
|
505
|
-
code: '
|
|
520
|
+
code: 'TRANSFORM_FAILED',
|
|
506
521
|
},
|
|
507
522
|
)
|
|
508
523
|
}
|
|
@@ -579,10 +594,10 @@ function toTransformFailedError(error: unknown, resolvedPath: string): AssetServ
|
|
|
579
594
|
if (isAssetServerCompilationError(error)) return error
|
|
580
595
|
|
|
581
596
|
return createAssetServerCompilationError(
|
|
582
|
-
`Failed to transform
|
|
597
|
+
`Failed to transform script ${resolvedPath}. ${formatUnknownError(error)}`,
|
|
583
598
|
{
|
|
584
599
|
cause: error,
|
|
585
|
-
code: '
|
|
600
|
+
code: 'TRANSFORM_FAILED',
|
|
586
601
|
},
|
|
587
602
|
)
|
|
588
603
|
}
|
package/src/lib/source-maps.ts
CHANGED
|
@@ -52,17 +52,24 @@ export function rewriteSourceMapSources(
|
|
|
52
52
|
resolvedPath: string,
|
|
53
53
|
stableUrlPathname: string,
|
|
54
54
|
sourceMapSourcePaths: 'absolute' | 'url',
|
|
55
|
+
sourceContent?: string,
|
|
55
56
|
): string {
|
|
56
|
-
let json = JSON.parse(sourceMap) as { sources?: string[] }
|
|
57
|
+
let json = JSON.parse(sourceMap) as { sources?: string[]; sourcesContent?: string[] }
|
|
57
58
|
json.sources = [
|
|
58
59
|
sourceMapSourcePaths === 'absolute' ? normalizeFilePath(resolvedPath) : stableUrlPathname,
|
|
59
60
|
]
|
|
61
|
+
if (sourceContent !== undefined) {
|
|
62
|
+
json.sourcesContent = [sourceContent]
|
|
63
|
+
}
|
|
60
64
|
return JSON.stringify(json)
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
export function stringifySourceMap(map: unknown): string | null {
|
|
64
68
|
if (!map) return null
|
|
65
69
|
if (typeof map === 'string') return map
|
|
70
|
+
if (map instanceof Uint8Array) {
|
|
71
|
+
return Buffer.from(map).toString('utf8')
|
|
72
|
+
}
|
|
66
73
|
if (typeof map === 'object' && map !== null) return JSON.stringify(map)
|
|
67
74
|
return String(map)
|
|
68
75
|
}
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import * as os from 'node:os'
|
|
2
|
+
import * as path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { IfNoneMatch } from '@remix-run/headers'
|
|
5
|
+
import { createFileMatcher } from '../file-matcher.ts'
|
|
6
|
+
import { formatFingerprintedPathname } from '../fingerprint.ts'
|
|
7
|
+
import { createModuleStore } from '../module-store.ts'
|
|
8
|
+
import type { ModuleRecord, ModuleStore } from '../module-store.ts'
|
|
9
|
+
import { normalizeFilePath, resolveFilePath } from '../paths.ts'
|
|
10
|
+
import type { CompiledRoutes } from '../routes.ts'
|
|
11
|
+
import type { ResolvedStyleTarget } from '../target.ts'
|
|
12
|
+
import { emitResolvedStyle } from './emit.ts'
|
|
13
|
+
import type { EmittedAsset, EmittedStyle } from './emit.ts'
|
|
14
|
+
import { resolveServedStyleOrThrow, resolveStyle } from './resolve.ts'
|
|
15
|
+
import type { ResolveArgs, ResolvedStyle } from './resolve.ts'
|
|
16
|
+
import { transformStyle } from './transform.ts'
|
|
17
|
+
import type { TransformArgs, TransformedStyle } from './transform.ts'
|
|
18
|
+
|
|
19
|
+
type StyleRecord = ModuleRecord<TransformedStyle, ResolvedStyle, EmittedStyle>
|
|
20
|
+
type StyleStore = ModuleStore<TransformedStyle, ResolvedStyle, EmittedStyle>
|
|
21
|
+
|
|
22
|
+
type StyleCompileResult = {
|
|
23
|
+
code: EmittedAsset
|
|
24
|
+
fingerprint: string | null
|
|
25
|
+
sourceMap: EmittedAsset | null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type StyleGetResult =
|
|
29
|
+
| {
|
|
30
|
+
style: StyleCompileResult
|
|
31
|
+
type: 'style'
|
|
32
|
+
}
|
|
33
|
+
| {
|
|
34
|
+
etag: string
|
|
35
|
+
type: 'not-modified'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type StyleGetOptions = {
|
|
39
|
+
ifNoneMatch: string | null
|
|
40
|
+
isSourceMapRequest: boolean
|
|
41
|
+
requestedFingerprint: string | null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type StyleCompilerOptions = {
|
|
45
|
+
buildId?: string
|
|
46
|
+
fingerprintAssets: boolean
|
|
47
|
+
isAllowed(absolutePath: string): boolean
|
|
48
|
+
minify: boolean
|
|
49
|
+
onWatchDirectoriesChange?: (delta: { add: string[]; remove: string[] }) => void
|
|
50
|
+
rootDir: string
|
|
51
|
+
routes: CompiledRoutes
|
|
52
|
+
sourceMapSourcePaths: 'absolute' | 'url'
|
|
53
|
+
sourceMaps?: 'external' | 'inline'
|
|
54
|
+
targets?: ResolvedStyleTarget
|
|
55
|
+
watchIgnore?: readonly string[]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type StyleCompiler = {
|
|
59
|
+
getHref(filePath: string): Promise<string>
|
|
60
|
+
getPreloadLayers(filePath: string | readonly string[]): Promise<string[][]>
|
|
61
|
+
getStyle(filePath: string, options: StyleGetOptions): Promise<StyleGetResult>
|
|
62
|
+
handleFileEvent(filePath: string, event: 'add' | 'change' | 'unlink'): Promise<void>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const preloadConcurrency = Math.max(1, Math.min(8, os.availableParallelism() - 1))
|
|
66
|
+
const styleExtension = '.css'
|
|
67
|
+
|
|
68
|
+
export function createStyleCompiler(options: StyleCompilerOptions): StyleCompiler {
|
|
69
|
+
let resolvedOptions = {
|
|
70
|
+
...options,
|
|
71
|
+
watchIgnoreMatchers: (options.watchIgnore ?? []).map((pattern) =>
|
|
72
|
+
createFileMatcher(pattern, options.rootDir),
|
|
73
|
+
),
|
|
74
|
+
}
|
|
75
|
+
let styleStore: StyleStore = createModuleStore<TransformedStyle, ResolvedStyle, EmittedStyle>({
|
|
76
|
+
onWatchDirectoriesChange: options.onWatchDirectoriesChange,
|
|
77
|
+
})
|
|
78
|
+
let resolveInFlightByCacheKey = new Map<string, Promise<ResolvedStyle>>()
|
|
79
|
+
let emitInFlightByCacheKey = new Map<string, Promise<EmittedStyle>>()
|
|
80
|
+
let resolveArgs: ResolveArgs = {
|
|
81
|
+
isAllowed: resolvedOptions.isAllowed,
|
|
82
|
+
isWatchIgnored,
|
|
83
|
+
routes: resolvedOptions.routes,
|
|
84
|
+
}
|
|
85
|
+
let transformArgs: TransformArgs = {
|
|
86
|
+
buildId: resolvedOptions.buildId ?? null,
|
|
87
|
+
isWatchIgnored,
|
|
88
|
+
minify: resolvedOptions.minify,
|
|
89
|
+
routes: resolvedOptions.routes,
|
|
90
|
+
sourceMapSourcePaths: resolvedOptions.sourceMapSourcePaths,
|
|
91
|
+
sourceMaps: resolvedOptions.sourceMaps ?? null,
|
|
92
|
+
targets: resolvedOptions.targets ?? null,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
async getHref(filePath) {
|
|
97
|
+
let resolvedStyle = resolveServedStyleOrThrow(resolveInputFilePath(filePath), resolveArgs)
|
|
98
|
+
return getServedUrl(resolvedStyle.identityPath)
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async getPreloadLayers(filePath) {
|
|
102
|
+
let resolvedEntries: string[] = []
|
|
103
|
+
let seen = new Set<string>()
|
|
104
|
+
|
|
105
|
+
for (let resolvedStyle of (Array.isArray(filePath) ? filePath : [filePath]).map((nextPath) =>
|
|
106
|
+
resolveServedStyleOrThrow(resolveInputFilePath(nextPath), resolveArgs),
|
|
107
|
+
)) {
|
|
108
|
+
if (seen.has(resolvedStyle.identityPath)) continue
|
|
109
|
+
seen.add(resolvedStyle.identityPath)
|
|
110
|
+
resolvedEntries.push(resolvedStyle.identityPath)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let visited = new Set(resolvedEntries)
|
|
114
|
+
let queue = [...resolvedEntries]
|
|
115
|
+
let layers: string[][] = []
|
|
116
|
+
|
|
117
|
+
while (queue.length > 0) {
|
|
118
|
+
let frontier = queue
|
|
119
|
+
queue = []
|
|
120
|
+
let resolvedStyles = await getOrCreateResolvedStyles(
|
|
121
|
+
frontier.map((identityPath) => styleStore.get(identityPath)),
|
|
122
|
+
)
|
|
123
|
+
let layer: string[] = []
|
|
124
|
+
|
|
125
|
+
for (let resolvedStyle of resolvedStyles) {
|
|
126
|
+
layer.push(getServedUrlForResolvedStyle(resolvedStyle))
|
|
127
|
+
|
|
128
|
+
for (let dep of resolvedStyle.deps) {
|
|
129
|
+
if (visited.has(dep)) continue
|
|
130
|
+
visited.add(dep)
|
|
131
|
+
queue.push(dep)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
layers.push(layer)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return layers
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
async getStyle(filePath, getOptions) {
|
|
142
|
+
let resolvedStyle = resolveServedStyleOrThrow(resolveInputFilePath(filePath), resolveArgs)
|
|
143
|
+
let record = styleStore.get(resolvedStyle.identityPath)
|
|
144
|
+
let notModified = getNotModifiedStyle(record.emitted, getOptions)
|
|
145
|
+
if (notModified) return notModified
|
|
146
|
+
|
|
147
|
+
let emitted = await getOrCreateEmittedStyle(record)
|
|
148
|
+
return {
|
|
149
|
+
style: toStyleCompileResult(emitted),
|
|
150
|
+
type: 'style',
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
async handleFileEvent(filePath, event) {
|
|
155
|
+
let normalizedFilePath = normalizeFilePath(filePath)
|
|
156
|
+
if (isWatchIgnored(normalizedFilePath)) return
|
|
157
|
+
styleStore.invalidateForFileEvent(normalizedFilePath, event)
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function resolveInputFilePath(filePath: string): string {
|
|
162
|
+
if (filePath.startsWith('file://')) {
|
|
163
|
+
return normalizeFilePath(fileURLToPath(new URL(filePath)))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (filePath.includes('://')) {
|
|
167
|
+
throw new TypeError(`Expected a file path or file:// URL, received "${filePath}"`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return resolveFilePath(resolvedOptions.rootDir, filePath)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function getOrCreateResolvedStyles(records: StyleRecord[]): Promise<ResolvedStyle[]> {
|
|
174
|
+
return mapWithConcurrency(records, preloadConcurrency, (record) =>
|
|
175
|
+
getOrCreateResolvedStyle(record),
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function getOrCreateResolvedStyle(record: StyleRecord): Promise<ResolvedStyle> {
|
|
180
|
+
if (record.resolved) return record.resolved
|
|
181
|
+
|
|
182
|
+
let cacheKey = getRecordCacheKey(record)
|
|
183
|
+
let existing = resolveInFlightByCacheKey.get(cacheKey)
|
|
184
|
+
if (existing) return existing
|
|
185
|
+
|
|
186
|
+
let promise = (async () => {
|
|
187
|
+
let startedVersion = record.invalidationVersion
|
|
188
|
+
let transformedStyle = await getOrCreateTransformedStyle(record)
|
|
189
|
+
let resolvedStyleResult = await resolveStyle(record, transformedStyle, resolveArgs)
|
|
190
|
+
|
|
191
|
+
if (!resolvedStyleResult.ok) {
|
|
192
|
+
if (isFresh(record, startedVersion)) {
|
|
193
|
+
styleStore.clearResolved(record.identityPath, [resolvedStyleResult.tracking])
|
|
194
|
+
}
|
|
195
|
+
throw resolvedStyleResult.error
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (isFresh(record, startedVersion)) {
|
|
199
|
+
styleStore.setResolved(record.identityPath, resolvedStyleResult.value, [
|
|
200
|
+
resolvedStyleResult.tracking,
|
|
201
|
+
])
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return resolvedStyleResult.value
|
|
205
|
+
})()
|
|
206
|
+
|
|
207
|
+
resolveInFlightByCacheKey.set(cacheKey, promise)
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
return await promise
|
|
211
|
+
} finally {
|
|
212
|
+
if (resolveInFlightByCacheKey.get(cacheKey) === promise) {
|
|
213
|
+
resolveInFlightByCacheKey.delete(cacheKey)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function getOrCreateTransformedStyle(record: StyleRecord): Promise<TransformedStyle> {
|
|
219
|
+
if (record.transformed) return record.transformed
|
|
220
|
+
|
|
221
|
+
let startedVersion = record.invalidationVersion
|
|
222
|
+
let transformStyleResult = await transformStyle(record, transformArgs)
|
|
223
|
+
|
|
224
|
+
if (!transformStyleResult.ok) {
|
|
225
|
+
if (isFresh(record, startedVersion)) {
|
|
226
|
+
styleStore.clearTransformed(record.identityPath, [transformStyleResult.tracking])
|
|
227
|
+
}
|
|
228
|
+
throw transformStyleResult.error
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (isFresh(record, startedVersion)) {
|
|
232
|
+
styleStore.setTransformed(record.identityPath, transformStyleResult.value, [
|
|
233
|
+
transformStyleResult.tracking,
|
|
234
|
+
])
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return transformStyleResult.value
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function getOrCreateEmittedStyle(record: StyleRecord): Promise<EmittedStyle> {
|
|
241
|
+
if (record.emitted) return record.emitted
|
|
242
|
+
|
|
243
|
+
let cacheKey = getRecordCacheKey(record)
|
|
244
|
+
let existing = emitInFlightByCacheKey.get(cacheKey)
|
|
245
|
+
if (existing) return existing
|
|
246
|
+
|
|
247
|
+
let promise = (async () => {
|
|
248
|
+
let startedVersion = record.invalidationVersion
|
|
249
|
+
let resolvedStyle = await getOrCreateResolvedStyle(record)
|
|
250
|
+
let emitResolvedStyleResult = await emitResolvedStyle(resolvedStyle, {
|
|
251
|
+
getServedUrl,
|
|
252
|
+
sourceMaps: resolvedOptions.sourceMaps,
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
if (!emitResolvedStyleResult.ok) {
|
|
256
|
+
throw emitResolvedStyleResult.error
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (isFresh(record, startedVersion)) {
|
|
260
|
+
styleStore.setEmitted(record.identityPath, emitResolvedStyleResult.value, null)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return emitResolvedStyleResult.value
|
|
264
|
+
})()
|
|
265
|
+
|
|
266
|
+
emitInFlightByCacheKey.set(cacheKey, promise)
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
return await promise
|
|
270
|
+
} finally {
|
|
271
|
+
if (emitInFlightByCacheKey.get(cacheKey) === promise) {
|
|
272
|
+
emitInFlightByCacheKey.delete(cacheKey)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function getServedUrl(identityPath: string): Promise<string> {
|
|
278
|
+
return getServedUrlForResolvedStyle(
|
|
279
|
+
await getOrCreateResolvedStyle(styleStore.get(identityPath)),
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function getServedUrlForResolvedStyle(resolvedStyle: ResolvedStyle): string {
|
|
284
|
+
return formatFingerprintedPathname(
|
|
285
|
+
resolvedStyle.stableUrlPathname,
|
|
286
|
+
resolvedOptions.fingerprintAssets ? resolvedStyle.fingerprint : null,
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function isWatchIgnored(filePath: string): boolean {
|
|
291
|
+
return resolvedOptions.watchIgnoreMatchers.some((matcher) => matcher(filePath))
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function getRecordCacheKey(record: StyleRecord): string {
|
|
296
|
+
return `${record.identityPath}\0${record.invalidationVersion}`
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function isFresh(record: StyleRecord, version: number): boolean {
|
|
300
|
+
return record.invalidationVersion === version
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function getNotModifiedStyle(
|
|
304
|
+
emittedStyle: EmittedStyle | undefined,
|
|
305
|
+
options: StyleGetOptions,
|
|
306
|
+
): StyleGetResult | null {
|
|
307
|
+
if (!emittedStyle || options.ifNoneMatch === null) return null
|
|
308
|
+
|
|
309
|
+
if (
|
|
310
|
+
options.requestedFingerprint !== null &&
|
|
311
|
+
emittedStyle.fingerprint !== options.requestedFingerprint
|
|
312
|
+
) {
|
|
313
|
+
return null
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
let asset = getEmittedAssetForRequest(emittedStyle, options.isSourceMapRequest)
|
|
317
|
+
if (!asset) return null
|
|
318
|
+
|
|
319
|
+
if (!IfNoneMatch.from(options.ifNoneMatch).matches(asset.etag)) return null
|
|
320
|
+
return { etag: asset.etag, type: 'not-modified' }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function getEmittedAssetForRequest(
|
|
324
|
+
emittedStyle: EmittedStyle,
|
|
325
|
+
isSourceMapRequest: boolean,
|
|
326
|
+
): EmittedAsset | null {
|
|
327
|
+
return isSourceMapRequest ? emittedStyle.sourceMap : emittedStyle.code
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function mapWithConcurrency<item, result>(
|
|
331
|
+
items: item[],
|
|
332
|
+
concurrency: number,
|
|
333
|
+
mapper: (item: item, index: number) => Promise<result>,
|
|
334
|
+
): Promise<result[]> {
|
|
335
|
+
if (items.length === 0) return []
|
|
336
|
+
|
|
337
|
+
let results = new Array<result>(items.length)
|
|
338
|
+
let nextIndex = 0
|
|
339
|
+
|
|
340
|
+
async function worker() {
|
|
341
|
+
while (nextIndex < items.length) {
|
|
342
|
+
let index = nextIndex++
|
|
343
|
+
results[index] = await mapper(items[index], index)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()))
|
|
348
|
+
return results
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function toStyleCompileResult(emittedStyle: EmittedStyle): StyleCompileResult {
|
|
352
|
+
return {
|
|
353
|
+
code: emittedStyle.code,
|
|
354
|
+
fingerprint: emittedStyle.fingerprint,
|
|
355
|
+
sourceMap: emittedStyle.sourceMap,
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function isStyleFilePath(filePath: string): boolean {
|
|
360
|
+
return path.extname(filePath).toLowerCase() === styleExtension
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function createResponseForStyle(
|
|
364
|
+
result: StyleCompileResult,
|
|
365
|
+
options: {
|
|
366
|
+
cacheControl: string
|
|
367
|
+
ifNoneMatch: string | null
|
|
368
|
+
isSourceMapRequest: boolean
|
|
369
|
+
method: string
|
|
370
|
+
},
|
|
371
|
+
): Response {
|
|
372
|
+
let body: string | null
|
|
373
|
+
let etag: string
|
|
374
|
+
let contentType: string
|
|
375
|
+
|
|
376
|
+
if (options.isSourceMapRequest) {
|
|
377
|
+
if (!result.sourceMap) {
|
|
378
|
+
return new Response('Not found', { status: 404 })
|
|
379
|
+
}
|
|
380
|
+
body = options.method === 'HEAD' ? null : result.sourceMap.content
|
|
381
|
+
etag = result.sourceMap.etag
|
|
382
|
+
contentType = 'application/json; charset=utf-8'
|
|
383
|
+
} else {
|
|
384
|
+
body = options.method === 'HEAD' ? null : result.code.content
|
|
385
|
+
etag = result.code.etag
|
|
386
|
+
contentType = 'text/css; charset=utf-8'
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (IfNoneMatch.from(options.ifNoneMatch).matches(etag)) {
|
|
390
|
+
return new Response(null, { status: 304, headers: { ETag: etag } })
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return new Response(body, {
|
|
394
|
+
headers: {
|
|
395
|
+
'Cache-Control': options.cacheControl,
|
|
396
|
+
'Content-Type': contentType,
|
|
397
|
+
ETag: etag,
|
|
398
|
+
},
|
|
399
|
+
})
|
|
400
|
+
}
|