@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.
Files changed (57) hide show
  1. package/README.md +53 -47
  2. package/dist/lib/asset-server.d.ts +45 -43
  3. package/dist/lib/asset-server.d.ts.map +1 -1
  4. package/dist/lib/asset-server.js +142 -46
  5. package/dist/lib/compilation-error.d.ts +2 -2
  6. package/dist/lib/compilation-error.d.ts.map +1 -1
  7. package/dist/lib/compilation-error.js +1 -1
  8. package/dist/lib/file-matcher.d.ts.map +1 -1
  9. package/dist/lib/file-matcher.js +3 -2
  10. package/dist/lib/module-store.d.ts +41 -0
  11. package/dist/lib/module-store.d.ts.map +1 -0
  12. package/dist/lib/{scripts/store.js → module-store.js} +43 -41
  13. package/dist/lib/scripts/compiler.d.ts +15 -15
  14. package/dist/lib/scripts/compiler.d.ts.map +1 -1
  15. package/dist/lib/scripts/compiler.js +50 -46
  16. package/dist/lib/scripts/emit.js +2 -2
  17. package/dist/lib/scripts/resolve.d.ts +7 -17
  18. package/dist/lib/scripts/resolve.d.ts.map +1 -1
  19. package/dist/lib/scripts/resolve.js +15 -9
  20. package/dist/lib/scripts/transform.d.ts +11 -9
  21. package/dist/lib/scripts/transform.d.ts.map +1 -1
  22. package/dist/lib/scripts/transform.js +32 -21
  23. package/dist/lib/source-maps.d.ts +1 -1
  24. package/dist/lib/source-maps.d.ts.map +1 -1
  25. package/dist/lib/source-maps.js +7 -1
  26. package/dist/lib/styles/compiler.d.ts +52 -0
  27. package/dist/lib/styles/compiler.d.ts.map +1 -0
  28. package/dist/lib/styles/compiler.js +272 -0
  29. package/dist/lib/styles/emit.d.ts +25 -0
  30. package/dist/lib/styles/emit.d.ts.map +1 -0
  31. package/dist/lib/styles/emit.js +78 -0
  32. package/dist/lib/styles/resolve.d.ts +48 -0
  33. package/dist/lib/styles/resolve.d.ts.map +1 -0
  34. package/dist/lib/styles/resolve.js +188 -0
  35. package/dist/lib/styles/transform.d.ts +47 -0
  36. package/dist/lib/styles/transform.d.ts.map +1 -0
  37. package/dist/lib/styles/transform.js +131 -0
  38. package/dist/lib/target.d.ts +21 -0
  39. package/dist/lib/target.d.ts.map +1 -0
  40. package/dist/lib/target.js +127 -0
  41. package/package.json +7 -2
  42. package/src/lib/asset-server.ts +218 -96
  43. package/src/lib/compilation-error.ts +7 -7
  44. package/src/lib/file-matcher.ts +3 -2
  45. package/src/lib/{scripts/store.ts → module-store.ts} +100 -87
  46. package/src/lib/scripts/compiler.ts +88 -70
  47. package/src/lib/scripts/emit.ts +2 -2
  48. package/src/lib/scripts/resolve.ts +34 -23
  49. package/src/lib/scripts/transform.ts +49 -34
  50. package/src/lib/source-maps.ts +8 -1
  51. package/src/lib/styles/compiler.ts +400 -0
  52. package/src/lib/styles/emit.ts +137 -0
  53. package/src/lib/styles/resolve.ts +316 -0
  54. package/src/lib/styles/transform.ts +226 -0
  55. package/src/lib/target.ts +196 -0
  56. package/dist/lib/scripts/store.d.ts +0 -40
  57. 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
- export type TransformFailureState = {
76
- trackedFiles: readonly string[]
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
- } & TransformFailureState)
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: ScriptsTarget | null
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: ModuleRecord,
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(`Module not found: ${record.identityPath}`, {
155
- code: 'MODULE_NOT_FOUND',
157
+ error: createAssetServerCompilationError(`File not found: ${record.identityPath}`, {
158
+ code: 'FILE_NOT_FOUND',
156
159
  }),
157
- trackedFiles: args.isWatchIgnored(record.identityPath) ? [] : [record.identityPath],
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(`Module not found: ${resolvedPath}`, {
181
+ error: createAssetServerCompilationError(`File not found: ${resolvedPath}`, {
177
182
  cause: error,
178
- code: 'MODULE_NOT_FOUND',
183
+ code: 'FILE_NOT_FOUND',
179
184
  }),
180
- trackedFiles,
185
+ tracking: {
186
+ trackedFiles,
187
+ },
181
188
  }
182
189
  }
183
190
  return {
184
191
  ok: false,
185
192
  error: toTransformFailedError(error, resolvedPath),
186
- trackedFiles,
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: 'MODULE_COMMONJS_NOT_SUPPORTED',
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
- `Module ${record.identityPath} is outside all configured fileMap entries.`,
225
+ `File ${record.identityPath} is outside all configured fileMap entries.`,
217
226
  {
218
- code: 'MODULE_OUTSIDE_FILE_MAP',
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
- trackedFiles,
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?: ScriptsTarget
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 module ${resolvedPath}. ${formatUnknownError(error)}`,
320
+ `Failed to transform script ${resolvedPath}. ${formatUnknownError(error)}`,
306
321
  {
307
322
  cause: error,
308
- code: 'MODULE_TRANSFORM_FAILED',
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: string | undefined,
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 module ${resolvedPath}. ${formatUnknownError(error)}`,
368
+ `Failed to minify script ${resolvedPath}. ${formatUnknownError(error)}`,
354
369
  {
355
370
  cause: error,
356
- code: 'MODULE_TRANSFORM_FAILED',
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?: string
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: 'MODULE_TRANSFORM_FAILED',
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} module ${resolvedPath}. ${errors[0].message ?? 'Unknown error'}`,
518
+ `Failed to ${operation} script ${resolvedPath}. ${errors[0].message ?? 'Unknown error'}`,
504
519
  {
505
- code: 'MODULE_TRANSFORM_FAILED',
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 module ${resolvedPath}. ${formatUnknownError(error)}`,
597
+ `Failed to transform script ${resolvedPath}. ${formatUnknownError(error)}`,
583
598
  {
584
599
  cause: error,
585
- code: 'MODULE_TRANSFORM_FAILED',
600
+ code: 'TRANSFORM_FAILED',
586
601
  },
587
602
  )
588
603
  }
@@ -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
+ }