@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
@@ -1,14 +1,18 @@
1
1
  import * as path from 'node:path'
2
2
  import * as fs from 'node:fs'
3
- import { isAssetServerCompilationError } from './compilation-error.ts'
4
3
  import { createAccessPolicy } from './access.ts'
5
- import { createModuleCompiler, createResponseForModule } from './scripts/compiler.ts'
4
+ import { isAssetServerCompilationError } from './compilation-error.ts'
5
+ import { getFingerprintRequestCacheControl, parseFingerprintSuffix } from './fingerprint.ts'
6
6
  import { normalizeFilePath } from './paths.ts'
7
7
  import { compileRoutes } from './routes.ts'
8
8
  import type { CompiledRoutes } from './routes.ts'
9
+ import { createResponseForScript, createScriptCompiler } from './scripts/compiler.ts'
10
+ import { supportedScriptExtensions } from './scripts/resolve.ts'
11
+ import { createResponseForStyle, createStyleCompiler, isStyleFilePath } from './styles/compiler.ts'
12
+ import { resolveScriptTarget, resolveStyleTarget } from './target.ts'
13
+ import type { AssetTarget, ResolvedScriptTarget, ResolvedStyleTarget } from './target.ts'
9
14
  import { createAssetServerWatcher } from './watch.ts'
10
- import type { ChokidarWatcher } from './watch.ts'
11
- import type { AssetServerWatcher } from './watch.ts'
15
+ import type { AssetServerWatcher, ChokidarWatcher } from './watch.ts'
12
16
 
13
17
  interface AssetServerWatchOptions {
14
18
  /**
@@ -28,30 +32,26 @@ interface AssetServerWatchOptions {
28
32
 
29
33
  interface FingerprintOptions {
30
34
  /**
31
- * Per-build invalidation token that must change whenever fingerprinted module URLs
35
+ * Per-build invalidation token that must change whenever fingerprinted asset URLs
32
36
  * should be invalidated together.
33
37
  */
34
38
  buildId: string
35
39
  }
36
40
 
37
- const scriptTargets = [
38
- 'es2015',
39
- 'es2016',
40
- 'es2017',
41
- 'es2018',
42
- 'es2019',
43
- 'es2020',
44
- 'es2021',
45
- 'es2022',
46
- 'es2023',
47
- 'es2024',
48
- 'es2025',
49
- 'es2026',
50
- 'esnext',
51
- ] as const
52
- const scriptTargetSet = new Set<string>(scriptTargets)
53
-
54
- export type ScriptsTarget = (typeof scriptTargets)[number]
41
+ type AssetSourceMaps = 'inline' | 'external'
42
+ type AssetSourceMapSourcePaths = 'url' | 'absolute'
43
+
44
+ interface AssetServerScriptOptions {
45
+ /**
46
+ * Replace global expressions with constant values during transform, e.g.
47
+ * `{ 'process.env.NODE_ENV': '"production"' }`
48
+ */
49
+ define?: Record<string, string>
50
+ /** Import specifiers to leave unrewritten (CDN URLs, import map entries, etc.) */
51
+ external?: string[]
52
+ }
53
+
54
+ const scriptExtensionSet = new Set<string>(supportedScriptExtensions)
55
55
 
56
56
  export interface AssetServerOptions {
57
57
  /** File patterns keyed by public URL patterns. */
@@ -69,45 +69,37 @@ export interface AssetServerOptions {
69
69
  */
70
70
  deny?: readonly string[]
71
71
  /**
72
- * Controls optional source-based URL fingerprinting for rewritten import URLs.
72
+ * Controls optional source-based URL fingerprinting for rewritten asset URLs.
73
73
  *
74
- * When omitted, all served modules use stable non-fingerprinted URLs with `Cache-Control: no-cache`.
74
+ * When omitted, all served assets use stable non-fingerprinted URLs with `Cache-Control: no-cache`.
75
75
  * Cannot be used together with active watch mode. Set `watch: false` when fingerprinting.
76
76
  */
77
77
  fingerprint?: FingerprintOptions
78
78
  /**
79
- * Script pipeline configuration. Omit to use defaults.
79
+ * Shared compatibility target for scripts and styles. Browser targets apply to both
80
+ * pipelines, and `es` only affects scripts.
80
81
  */
81
- scripts?: {
82
- /**
83
- * Source map mode (disabled when omitted).
84
- * - `'external'`: serve source maps as separate `.map` files; adds `//# sourceMappingURL=` comment
85
- * - `'inline'`: embed source maps as a base64 data URL directly in the JS; no separate `.map` file
86
- */
87
- sourceMaps?: 'inline' | 'external'
88
- /**
89
- * Controls the source paths written into source map `sources`.
90
- * - `'url'` (default): use the stable server path (e.g. `'/assets/app/entry.ts'`)
91
- * - `'absolute'`: use the original filesystem path on disk
92
- */
93
- sourceMapSourcePaths?: 'url' | 'absolute'
94
- /**
95
- * Minify emitted modules.
96
- */
97
- minify?: boolean
98
- /**
99
- * Replace global expressions with constant values during transform, e.g.
100
- * `{ 'process.env.NODE_ENV': '"production"' }`
101
- */
102
- define?: Record<string, string>
103
- /**
104
- * Lower emitted syntax to a specific ECMAScript target. Omit this option to preserve
105
- * modern syntax unless project configuration already requests a lower target.
106
- */
107
- target?: ScriptsTarget
108
- /** Import specifiers to leave unrewritten (CDN URLs, import map entries, etc.) */
109
- external?: string[]
110
- }
82
+ target?: AssetTarget
83
+ /**
84
+ * Source map mode for scripts and styles.
85
+ * - `'external'`: serve source maps as separate `.map` files
86
+ * - `'inline'`: embed source maps as a base64 data URL in the compiled asset
87
+ */
88
+ sourceMaps?: AssetSourceMaps
89
+ /**
90
+ * Source path strategy for source map `sources`.
91
+ * - `'url'` (default): use the stable server path (e.g. `'/assets/app/entry.ts'`)
92
+ * - `'absolute'`: use the original filesystem path on disk
93
+ */
94
+ sourceMapSourcePaths?: AssetSourceMapSourcePaths
95
+ /**
96
+ * Minification setting for emitted scripts and styles.
97
+ */
98
+ minify?: boolean
99
+ /**
100
+ * Script-only configuration.
101
+ */
102
+ scripts?: AssetServerScriptOptions
111
103
  /**
112
104
  * Enable filesystem-backed cache invalidation for long-lived server instances.
113
105
  * Enabled by default. Pass `true` to use the default watcher options, an options
@@ -123,16 +115,16 @@ export interface AssetServerOptions {
123
115
 
124
116
  export interface AssetServer {
125
117
  /**
126
- * Serves a script request. Returns `Response | null` — null means the request was not
127
- * handled by this server, letting the router fall through to a 404.
118
+ * Serves a script or style request. Returns `Response | null` — null means the request
119
+ * was not handled by this server, letting the router fall through to a 404.
128
120
  */
129
121
  fetch(request: Request): Promise<Response | null>
130
122
  /**
131
- * Returns the request href for a served module file.
123
+ * Returns the request href for a served asset file.
132
124
  */
133
125
  getHref(filePath: string): Promise<string>
134
126
  /**
135
- * Returns preload URLs for one or more served module files, ordered shallowest-first.
127
+ * Returns preload URLs for one or more served asset files, ordered shallowest-first.
136
128
  */
137
129
  getPreloads(filePath: string | readonly string[]): Promise<string[]>
138
130
  /**
@@ -147,14 +139,15 @@ type ResolvedAssetServerOptions = {
147
139
  define?: Record<string, string>
148
140
  deny?: readonly string[]
149
141
  external: string[]
150
- fingerprintModules: boolean
142
+ fingerprintAssets: boolean
151
143
  minify: boolean
152
144
  onError: NonNullable<AssetServerOptions['onError']>
153
145
  rootDir: string
154
146
  routes: CompiledRoutes
155
147
  sourceMapSourcePaths: 'url' | 'absolute'
156
148
  sourceMaps?: 'inline' | 'external'
157
- scriptsTarget?: ScriptsTarget
149
+ scriptsTarget?: ResolvedScriptTarget
150
+ stylesTarget?: ResolvedStyleTarget
158
151
  watchOptions: AssetServerWatchOptions | null
159
152
  }
160
153
 
@@ -172,8 +165,8 @@ export function getInternalWatchTargets(assetServer: AssetServer): readonly stri
172
165
  /**
173
166
  * Create an asset server instance
174
167
  *
175
- * Compiles TypeScript/JavaScript modules on demand with optional source-based URL
176
- * fingerprinting, caching, and configurable file mapping.
168
+ * Compiles TypeScript/JavaScript scripts and CSS styles on demand with optional
169
+ * source-based URL fingerprinting, caching, and configurable file mapping.
177
170
  *
178
171
  * @param options Server configuration
179
172
  * @returns A {@link AssetServer} with `fetch()`, `getHref()`, and `getPreloads()` methods
@@ -199,11 +192,11 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
199
192
  })
200
193
  let watcher: AssetServerWatcher | null = null
201
194
  let chokidarWatcher: ChokidarWatcher | null = null
202
- let moduleCompiler = createModuleCompiler({
195
+ let scriptCompiler = createScriptCompiler({
203
196
  buildId: resolvedOptions.buildId,
204
197
  define: resolvedOptions.define,
205
198
  external: resolvedOptions.external,
206
- fingerprintModules: resolvedOptions.fingerprintModules,
199
+ fingerprintAssets: resolvedOptions.fingerprintAssets,
207
200
  isAllowed: accessPolicy.isAllowed,
208
201
  minify: resolvedOptions.minify,
209
202
  onWatchDirectoriesChange: (delta) => {
@@ -218,6 +211,22 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
218
211
  watchIgnore: resolvedOptions.watchOptions?.ignore,
219
212
  watchMode: resolvedOptions.watchOptions !== null,
220
213
  })
214
+ let styleCompiler = createStyleCompiler({
215
+ buildId: resolvedOptions.buildId,
216
+ fingerprintAssets: resolvedOptions.fingerprintAssets,
217
+ isAllowed: accessPolicy.isAllowed,
218
+ minify: resolvedOptions.minify,
219
+ onWatchDirectoriesChange: (delta) => {
220
+ if (!watcher) return
221
+ watcher.updateWatchedDirectories(delta)
222
+ },
223
+ rootDir: resolvedOptions.rootDir,
224
+ routes: resolvedOptions.routes,
225
+ sourceMapSourcePaths: resolvedOptions.sourceMapSourcePaths,
226
+ sourceMaps: resolvedOptions.sourceMaps,
227
+ targets: resolvedOptions.stylesTarget,
228
+ watchIgnore: resolvedOptions.watchOptions?.ignore,
229
+ })
221
230
  if (resolvedOptions.watchOptions) {
222
231
  watcher = createAssetServerWatcher({
223
232
  ...resolvedOptions.watchOptions,
@@ -241,7 +250,8 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
241
250
  async function handleWatchEvent(filePath: string, event: 'add' | 'change' | 'unlink') {
242
251
  try {
243
252
  let normalizedFilePath = normalizeFilePath(filePath)
244
- await moduleCompiler.handleFileEvent(normalizedFilePath, event)
253
+ await scriptCompiler.handleFileEvent(normalizedFilePath, event)
254
+ await styleCompiler.handleFileEvent(normalizedFilePath, event)
245
255
  } catch (error) {
246
256
  console.error(`There was an error invalidating the asset server cache: ${error}`)
247
257
  }
@@ -251,30 +261,66 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
251
261
  async fetch(request) {
252
262
  if (request.method !== 'GET' && request.method !== 'HEAD') return null
253
263
 
254
- let parsedRequestPathname = moduleCompiler.parseRequestPathname(new URL(request.url).pathname)
264
+ let parsedRequestPathname = parseAssetRequestPathname(new URL(request.url).pathname, {
265
+ fingerprintAssets: resolvedOptions.fingerprintAssets,
266
+ routes: resolvedOptions.routes,
267
+ })
255
268
  if (!parsedRequestPathname) return null
256
269
 
257
270
  try {
258
271
  let ifNoneMatch = request.headers.get('If-None-Match')
259
- let moduleResult = await moduleCompiler.getModule(parsedRequestPathname.filePath, {
272
+
273
+ if (isStyleFilePath(parsedRequestPathname.filePath)) {
274
+ let styleResult = await styleCompiler.getStyle(parsedRequestPathname.filePath, {
275
+ ifNoneMatch,
276
+ isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
277
+ requestedFingerprint: parsedRequestPathname.requestedFingerprint,
278
+ })
279
+ if (styleResult.type === 'not-modified') {
280
+ return new Response(null, {
281
+ status: 304,
282
+ headers: { ETag: styleResult.etag },
283
+ })
284
+ }
285
+
286
+ let compiledStyle = styleResult.style
287
+
288
+ if (parsedRequestPathname.requestedFingerprint !== null) {
289
+ if (compiledStyle.fingerprint !== parsedRequestPathname.requestedFingerprint)
290
+ return null
291
+ }
292
+
293
+ return createResponseForStyle(compiledStyle, {
294
+ cacheControl: parsedRequestPathname.cacheControl,
295
+ ifNoneMatch,
296
+ isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
297
+ method: request.method,
298
+ })
299
+ }
300
+
301
+ if (!isScriptFilePath(parsedRequestPathname.filePath)) {
302
+ return null
303
+ }
304
+
305
+ let scriptResult = await scriptCompiler.getScript(parsedRequestPathname.filePath, {
260
306
  ifNoneMatch,
261
307
  isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
262
308
  requestedFingerprint: parsedRequestPathname.requestedFingerprint,
263
309
  })
264
- if (moduleResult.type === 'not-modified') {
310
+ if (scriptResult.type === 'not-modified') {
265
311
  return new Response(null, {
266
312
  status: 304,
267
- headers: { ETag: moduleResult.etag },
313
+ headers: { ETag: scriptResult.etag },
268
314
  })
269
315
  }
270
316
 
271
- let compiledModule = moduleResult.module
317
+ let compiledScript = scriptResult.script
272
318
 
273
319
  if (parsedRequestPathname.requestedFingerprint !== null) {
274
- if (compiledModule.fingerprint !== parsedRequestPathname.requestedFingerprint) return null
320
+ if (compiledScript.fingerprint !== parsedRequestPathname.requestedFingerprint) return null
275
321
  }
276
322
 
277
- return createResponseForModule(compiledModule, {
323
+ return createResponseForScript(compiledScript, {
278
324
  cacheControl: parsedRequestPathname.cacheControl,
279
325
  ifNoneMatch,
280
326
  isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
@@ -286,7 +332,7 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
286
332
  // handled here" so the outer router can continue to its own 404 behavior.
287
333
  if (
288
334
  isAssetServerCompilationError(error) &&
289
- (error.code === 'MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_ALLOWED')
335
+ (error.code === 'FILE_NOT_FOUND' || error.code === 'FILE_NOT_ALLOWED')
290
336
  ) {
291
337
  return null
292
338
  }
@@ -296,10 +342,46 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
296
342
  },
297
343
 
298
344
  async getHref(filePath) {
299
- return moduleCompiler.getHref(filePath)
345
+ if (isStyleFilePath(filePath)) {
346
+ return styleCompiler.getHref(filePath)
347
+ }
348
+
349
+ return scriptCompiler.getHref(filePath)
300
350
  },
301
351
  async getPreloads(filePath) {
302
- return moduleCompiler.getPreloadUrls(filePath)
352
+ let filePaths = Array.isArray(filePath) ? filePath : [filePath]
353
+ let styleFiles: string[] = []
354
+ let scriptFiles: string[] = []
355
+
356
+ for (let nextFilePath of filePaths) {
357
+ if (isStyleFilePath(nextFilePath)) {
358
+ styleFiles.push(nextFilePath)
359
+ continue
360
+ }
361
+
362
+ scriptFiles.push(nextFilePath)
363
+ }
364
+
365
+ if (styleFiles.length === 0 && scriptFiles.length === 0) {
366
+ return []
367
+ }
368
+
369
+ if (styleFiles.length === 0) {
370
+ return flattenPreloadLayers(await scriptCompiler.getPreloadLayers(filePath))
371
+ }
372
+
373
+ if (scriptFiles.length === 0) {
374
+ return flattenPreloadLayers(await styleCompiler.getPreloadLayers(filePath))
375
+ }
376
+
377
+ // Mixed asset type preloads need to be merged, so we merge in order of first asset type seen
378
+ let scriptPreloadLayersPromise = scriptCompiler.getPreloadLayers(scriptFiles)
379
+ let stylePreloadLayersPromise = styleCompiler.getPreloadLayers(styleFiles)
380
+ let preloadLayerGroups = isStyleFilePath(filePaths[0])
381
+ ? [stylePreloadLayersPromise, scriptPreloadLayersPromise]
382
+ : [scriptPreloadLayersPromise, stylePreloadLayersPromise]
383
+
384
+ return mergePreloadLayers(await Promise.all(preloadLayerGroups))
303
385
  },
304
386
  async close() {
305
387
  await watcher?.close()
@@ -323,6 +405,28 @@ function internalServerError(): Response {
323
405
  })
324
406
  }
325
407
 
408
+ function mergePreloadLayers(preloadLayersByRoot: readonly (readonly string[][])[]): string[] {
409
+ let urls: string[] = []
410
+ let seen = new Set<string>()
411
+ let maxDepth = Math.max(0, ...preloadLayersByRoot.map((layers) => layers.length))
412
+
413
+ for (let depth = 0; depth < maxDepth; depth++) {
414
+ for (let preloadLayers of preloadLayersByRoot) {
415
+ for (let url of preloadLayers[depth] ?? []) {
416
+ if (seen.has(url)) continue
417
+ seen.add(url)
418
+ urls.push(url)
419
+ }
420
+ }
421
+ }
422
+
423
+ return urls
424
+ }
425
+
426
+ function flattenPreloadLayers(preloadLayers: readonly (readonly string[])[]): string[] {
427
+ return preloadLayers.flatMap((layer) => layer)
428
+ }
429
+
326
430
  function defaultErrorHandler(error: unknown): void {
327
431
  console.error(error)
328
432
  }
@@ -341,35 +445,22 @@ function resolveAssetServerOptions(options: AssetServerOptions): ResolvedAssetSe
341
445
  define: scriptOptions.define,
342
446
  deny: options.deny,
343
447
  external: scriptOptions.external ?? [],
344
- fingerprintModules: fingerprintOptions.enabled,
345
- minify: scriptOptions.minify ?? false,
448
+ fingerprintAssets: fingerprintOptions.enabled,
449
+ minify: options.minify ?? false,
346
450
  onError: options.onError ?? defaultErrorHandler,
347
451
  rootDir,
348
452
  routes: compileRoutes({
349
453
  fileMap: options.fileMap,
350
454
  rootDir,
351
455
  }),
352
- sourceMapSourcePaths: scriptOptions.sourceMapSourcePaths ?? 'url',
353
- sourceMaps: scriptOptions.sourceMaps,
354
- scriptsTarget: normalizeTarget(scriptOptions.target),
456
+ sourceMapSourcePaths: options.sourceMapSourcePaths ?? 'url',
457
+ sourceMaps: options.sourceMaps,
458
+ scriptsTarget: resolveScriptTarget(options.target),
459
+ stylesTarget: resolveStyleTarget(options.target),
355
460
  watchOptions: normalizeWatchOptions(options.watch),
356
461
  }
357
462
  }
358
463
 
359
- function normalizeTarget(
360
- target: NonNullable<AssetServerOptions['scripts']>['target'],
361
- ): ScriptsTarget | undefined {
362
- if (target == null) return undefined
363
-
364
- if (typeof target !== 'string' || !scriptTargetSet.has(target)) {
365
- throw new TypeError(
366
- `Expected target to be one of ${scriptTargets.map((value) => `"${value}"`).join(', ')}. Received "${target}".`,
367
- )
368
- }
369
-
370
- return target as ScriptsTarget
371
- }
372
-
373
464
  function normalizeFingerprintOptions(options: {
374
465
  fingerprint: AssetServerOptions['fingerprint']
375
466
  watch: AssetServerOptions['watch']
@@ -413,3 +504,34 @@ function normalizeWatchOptions(
413
504
  if (options == null || options === true) return {}
414
505
  return options
415
506
  }
507
+
508
+ function parseAssetRequestPathname(
509
+ pathname: string,
510
+ options: {
511
+ fingerprintAssets: boolean
512
+ routes: CompiledRoutes
513
+ },
514
+ ): {
515
+ cacheControl: string
516
+ filePath: string
517
+ isSourceMapRequest: boolean
518
+ requestedFingerprint: string | null
519
+ } | null {
520
+ let isSourceMapRequest = pathname.endsWith('.map')
521
+ let pathWithoutMap = isSourceMapRequest ? pathname.slice(0, -4) : pathname
522
+ let fingerprint = parseFingerprintSuffix(pathWithoutMap)
523
+ let filePath = options.routes.resolveUrlPathname(fingerprint.pathname)
524
+ if (!filePath) return null
525
+ if (options.fingerprintAssets && fingerprint.requestedFingerprint === null) return null
526
+
527
+ return {
528
+ cacheControl: getFingerprintRequestCacheControl(fingerprint.requestedFingerprint),
529
+ filePath,
530
+ isSourceMapRequest,
531
+ requestedFingerprint: fingerprint.requestedFingerprint,
532
+ }
533
+ }
534
+
535
+ function isScriptFilePath(filePath: string): boolean {
536
+ return scriptExtensionSet.has(path.extname(filePath).toLowerCase())
537
+ }
@@ -1,17 +1,17 @@
1
1
  type AssetServerCompilationErrorCode =
2
- | 'MODULE_NOT_FOUND'
3
- | 'MODULE_NOT_ALLOWED'
4
- | 'MODULE_OUTSIDE_FILE_MAP'
5
- | 'MODULE_COMMONJS_NOT_SUPPORTED'
6
- | 'MODULE_TRANSFORM_FAILED'
7
- | 'MODULE_EMIT_FAILED'
2
+ | 'FILE_NOT_FOUND'
3
+ | 'FILE_NOT_ALLOWED'
4
+ | 'FILE_OUTSIDE_FILE_MAP'
5
+ | 'COMMONJS_NOT_SUPPORTED'
6
+ | 'TRANSFORM_FAILED'
7
+ | 'EMIT_FAILED'
8
8
  | 'IMPORT_RESOLUTION_FAILED'
9
9
  | 'IMPORT_NOT_SUPPORTED'
10
10
  | 'IMPORT_NOT_ALLOWED'
11
11
  | 'IMPORT_OUTSIDE_FILE_MAP'
12
12
 
13
13
  /**
14
- * Internal error used by the request-time module compilation pipeline.
14
+ * Internal error used by the request-time asset compilation pipeline.
15
15
  */
16
16
  export class AssetServerCompilationError extends Error {
17
17
  code: AssetServerCompilationErrorCode
@@ -1,5 +1,5 @@
1
1
  import * as fs from 'node:fs'
2
- import * as path from 'node:path'
2
+ import picomatch from 'picomatch'
3
3
 
4
4
  import { normalizeFilePath, resolveFilePath } from './paths.ts'
5
5
 
@@ -37,7 +37,8 @@ export function createFileMatcher(
37
37
  return (filePath) => filePath === resolvedPatternPath
38
38
  }
39
39
 
40
- return (filePath) => path.posix.matchesGlob(filePath, resolvedPatternPath)
40
+ let globMatcher = picomatch(resolvedPatternPath, { dot: true })
41
+ return (filePath) => globMatcher(filePath)
41
42
  }
42
43
 
43
44
  function isSameOrDescendantPath(filePath: string, directoryPath: string): boolean {