@remix-run/assets 0.0.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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +325 -2
  3. package/dist/assets.d.ts +3 -0
  4. package/dist/assets.d.ts.map +1 -0
  5. package/dist/assets.js +1 -0
  6. package/dist/lib/access.d.ts +10 -0
  7. package/dist/lib/access.d.ts.map +1 -0
  8. package/dist/lib/access.js +14 -0
  9. package/dist/lib/asset-server.d.ts +139 -0
  10. package/dist/lib/asset-server.d.ts.map +1 -0
  11. package/dist/lib/asset-server.js +338 -0
  12. package/dist/lib/compilation-error.d.ts +33 -0
  13. package/dist/lib/compilation-error.d.ts.map +1 -0
  14. package/dist/lib/compilation-error.js +32 -0
  15. package/dist/lib/file-matcher.d.ts +6 -0
  16. package/dist/lib/file-matcher.d.ts.map +1 -0
  17. package/dist/lib/file-matcher.js +44 -0
  18. package/dist/lib/fingerprint.d.ts +12 -0
  19. package/dist/lib/fingerprint.d.ts.map +1 -0
  20. package/dist/lib/fingerprint.js +49 -0
  21. package/dist/lib/module-store.d.ts +41 -0
  22. package/dist/lib/module-store.d.ts.map +1 -0
  23. package/dist/lib/module-store.js +230 -0
  24. package/dist/lib/paths.d.ts +8 -0
  25. package/dist/lib/paths.d.ts.map +1 -0
  26. package/dist/lib/paths.js +50 -0
  27. package/dist/lib/routes.d.ts +13 -0
  28. package/dist/lib/routes.d.ts.map +1 -0
  29. package/dist/lib/routes.js +94 -0
  30. package/dist/lib/scripts/cjs-check.d.ts +3 -0
  31. package/dist/lib/scripts/cjs-check.d.ts.map +1 -0
  32. package/dist/lib/scripts/cjs-check.js +398 -0
  33. package/dist/lib/scripts/compiler.d.ts +62 -0
  34. package/dist/lib/scripts/compiler.d.ts.map +1 -0
  35. package/dist/lib/scripts/compiler.js +439 -0
  36. package/dist/lib/scripts/emit.d.ts +25 -0
  37. package/dist/lib/scripts/emit.d.ts.map +1 -0
  38. package/dist/lib/scripts/emit.js +63 -0
  39. package/dist/lib/scripts/resolve.d.ts +50 -0
  40. package/dist/lib/scripts/resolve.d.ts.map +1 -0
  41. package/dist/lib/scripts/resolve.js +236 -0
  42. package/dist/lib/scripts/transform.d.ts +64 -0
  43. package/dist/lib/scripts/transform.d.ts.map +1 -0
  44. package/dist/lib/scripts/transform.js +373 -0
  45. package/dist/lib/source-maps.d.ts +4 -0
  46. package/dist/lib/source-maps.d.ts.map +1 -0
  47. package/dist/lib/source-maps.js +62 -0
  48. package/dist/lib/styles/compiler.d.ts +52 -0
  49. package/dist/lib/styles/compiler.d.ts.map +1 -0
  50. package/dist/lib/styles/compiler.js +272 -0
  51. package/dist/lib/styles/emit.d.ts +25 -0
  52. package/dist/lib/styles/emit.d.ts.map +1 -0
  53. package/dist/lib/styles/emit.js +78 -0
  54. package/dist/lib/styles/resolve.d.ts +48 -0
  55. package/dist/lib/styles/resolve.d.ts.map +1 -0
  56. package/dist/lib/styles/resolve.js +188 -0
  57. package/dist/lib/styles/transform.d.ts +47 -0
  58. package/dist/lib/styles/transform.d.ts.map +1 -0
  59. package/dist/lib/styles/transform.js +131 -0
  60. package/dist/lib/target.d.ts +21 -0
  61. package/dist/lib/target.d.ts.map +1 -0
  62. package/dist/lib/target.js +127 -0
  63. package/dist/lib/watch.d.ts +22 -0
  64. package/dist/lib/watch.d.ts.map +1 -0
  65. package/dist/lib/watch.js +96 -0
  66. package/package.json +55 -12
  67. package/src/assets.ts +2 -0
  68. package/src/lib/access.ts +24 -0
  69. package/src/lib/asset-server.ts +537 -0
  70. package/src/lib/compilation-error.ts +61 -0
  71. package/src/lib/file-matcher.ts +63 -0
  72. package/src/lib/fingerprint.ts +65 -0
  73. package/src/lib/module-store.ts +340 -0
  74. package/src/lib/paths.ts +66 -0
  75. package/src/lib/routes.ts +164 -0
  76. package/src/lib/scripts/cjs-check.ts +476 -0
  77. package/src/lib/scripts/compiler.ts +640 -0
  78. package/src/lib/scripts/emit.ts +122 -0
  79. package/src/lib/scripts/resolve.ts +433 -0
  80. package/src/lib/scripts/transform.ts +609 -0
  81. package/src/lib/source-maps.ts +75 -0
  82. package/src/lib/styles/compiler.ts +400 -0
  83. package/src/lib/styles/emit.ts +137 -0
  84. package/src/lib/styles/resolve.ts +316 -0
  85. package/src/lib/styles/transform.ts +226 -0
  86. package/src/lib/target.ts +196 -0
  87. package/src/lib/watch.ts +136 -0
@@ -0,0 +1,640 @@
1
+ import * as fs from 'node:fs'
2
+ import * as os from 'node:os'
3
+ import * as path from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { IfNoneMatch } from '@remix-run/headers'
6
+
7
+ import { createAssetServerCompilationError } from '../compilation-error.ts'
8
+ import { createFileMatcher } from '../file-matcher.ts'
9
+ import {
10
+ formatFingerprintedPathname,
11
+ getFingerprintRequestCacheControl,
12
+ parseFingerprintSuffix,
13
+ } from '../fingerprint.ts'
14
+ import { emitResolvedModule } from './emit.ts'
15
+ import { normalizeFilePath, resolveFilePath } from '../paths.ts'
16
+ import {
17
+ resolveModule,
18
+ resolverExtensionAlias,
19
+ resolverExtensions,
20
+ supportedScriptExtensions,
21
+ } from './resolve.ts'
22
+ import type { ResolveArgs, ResolvedModule } from './resolve.ts'
23
+ import type { CompiledRoutes } from '../routes.ts'
24
+ import type { ResolvedScriptTarget } from '../target.ts'
25
+ import { createModuleStore } from '../module-store.ts'
26
+ import type {
27
+ FileSnapshot,
28
+ ModuleRecord,
29
+ ModuleSnapshot,
30
+ ModuleStore,
31
+ ModuleWatchEvent,
32
+ } from '../module-store.ts'
33
+ import { createTsconfigTransformOptionsResolver, transformModule } from './transform.ts'
34
+ import type { ResolveModuleResult, TransformArgs, TransformedModule } from './transform.ts'
35
+ import { ResolverFactory } from 'oxc-resolver'
36
+ import type { EmittedAsset, EmittedModule } from './emit.ts'
37
+
38
+ type ScriptRecord = ModuleRecord<TransformedModule, ResolvedModule, EmittedModule>
39
+ type ScriptStore = ModuleStore<TransformedModule, ResolvedModule, EmittedModule>
40
+
41
+ type ScriptCompileResult = {
42
+ code: EmittedAsset
43
+ fingerprint: string | null
44
+ sourceMap: EmittedAsset | null
45
+ }
46
+
47
+ type ScriptGetResult =
48
+ | {
49
+ script: ScriptCompileResult
50
+ type: 'script'
51
+ }
52
+ | {
53
+ type: 'not-modified'
54
+ etag: string
55
+ }
56
+
57
+ type ScriptGetOptions = {
58
+ ifNoneMatch: string | null
59
+ isSourceMapRequest: boolean
60
+ requestedFingerprint: string | null
61
+ }
62
+
63
+ type ScriptCompilerOptions = {
64
+ buildId?: string
65
+ define?: Record<string, string>
66
+ external: string[]
67
+ fingerprintAssets: boolean
68
+ isAllowed(absolutePath: string): boolean
69
+ minify: boolean
70
+ onWatchDirectoriesChange?: (delta: { add: string[]; remove: string[] }) => void
71
+ rootDir: string
72
+ routes: CompiledRoutes
73
+ sourceMapSourcePaths: 'absolute' | 'url'
74
+ sourceMaps?: 'external' | 'inline'
75
+ target?: ResolvedScriptTarget
76
+ watchIgnore?: readonly string[]
77
+ watchMode: boolean
78
+ }
79
+
80
+ type ScriptCompiler = {
81
+ getScript(filePath: string, options: ScriptGetOptions): Promise<ScriptGetResult>
82
+ getPreloadLayers(filePath: string | readonly string[]): Promise<string[][]>
83
+ getHref(filePath: string): Promise<string>
84
+ handleFileEvent(filePath: string, event: ModuleWatchEvent): Promise<void>
85
+ parseRequestPathname(pathname: string): ParsedRequestPathname | null
86
+ }
87
+
88
+ type ParsedRequestPathname = {
89
+ cacheControl: string
90
+ filePath: string
91
+ isSourceMapRequest: boolean
92
+ requestedFingerprint: string | null
93
+ }
94
+
95
+ const supportedScriptExtensionSet = new Set<string>(supportedScriptExtensions)
96
+ const preloadConcurrency = Math.max(1, Math.min(8, os.availableParallelism() - 1))
97
+
98
+ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptCompiler {
99
+ let resolvedOptions = {
100
+ ...options,
101
+ externalSet: new Set(options.external),
102
+ watchIgnoreMatchers: (options.watchIgnore ?? []).map((pattern) =>
103
+ createFileMatcher(pattern, options.rootDir),
104
+ ),
105
+ }
106
+ let scriptStore: ScriptStore = createModuleStore<
107
+ TransformedModule,
108
+ ResolvedModule,
109
+ EmittedModule
110
+ >({
111
+ onWatchDirectoriesChange: options.onWatchDirectoriesChange,
112
+ })
113
+ let tsconfigTransformOptionsResolver = createTsconfigTransformOptionsResolver()
114
+ let resolverFactory = new ResolverFactory({
115
+ aliasFields: [['browser']],
116
+ conditionNames: ['browser', 'import', 'module', 'default'],
117
+ extensionAlias: resolverExtensionAlias,
118
+ extensions: resolverExtensions,
119
+ mainFields: ['browser', 'module', 'main'],
120
+ tsconfig: 'auto',
121
+ })
122
+ let resolveInFlightByCacheKey = new Map<string, Promise<ResolvedModule>>()
123
+ let emitInFlightByCacheKey = new Map<string, Promise<EmittedModule>>()
124
+
125
+ let transformArgs: TransformArgs = {
126
+ buildId: resolvedOptions.buildId ?? null,
127
+ define: resolvedOptions.define ?? null,
128
+ externalSet: resolvedOptions.externalSet,
129
+ isWatchIgnored,
130
+ minify: resolvedOptions.minify,
131
+ resolveActualPath,
132
+ routes: resolvedOptions.routes,
133
+ sourceMapSourcePaths: resolvedOptions.sourceMapSourcePaths,
134
+ sourceMaps: resolvedOptions.sourceMaps ?? null,
135
+ target: resolvedOptions.target ?? null,
136
+ tsconfigTransformOptionsResolver,
137
+ }
138
+ let resolveArgs: ResolveArgs = {
139
+ isAllowed: resolvedOptions.isAllowed,
140
+ isWatchIgnored,
141
+ resolveModulePath,
142
+ resolverFactory,
143
+ routes: resolvedOptions.routes,
144
+ }
145
+
146
+ return {
147
+ async getScript(filePath, getOptions) {
148
+ let resolvedModule = resolveServedScriptOrThrow(resolveInputFilePath(filePath))
149
+ let record = scriptStore.get(resolvedModule.identityPath)
150
+ let notModified = getNotModifiedScript(record, getOptions)
151
+ if (notModified) return notModified
152
+
153
+ let emitted = await getOrCreateEmittedScript(record)
154
+ return {
155
+ script: toScriptCompileResult(emitted),
156
+ type: 'script',
157
+ }
158
+ },
159
+
160
+ async getPreloadLayers(filePath) {
161
+ let resolvedEntries: string[] = []
162
+ let seen = new Set<string>()
163
+
164
+ for (let resolvedModule of (Array.isArray(filePath) ? filePath : [filePath]).map((nextPath) =>
165
+ resolveServedScriptOrThrow(resolveInputFilePath(nextPath)),
166
+ )) {
167
+ if (seen.has(resolvedModule.identityPath)) continue
168
+ seen.add(resolvedModule.identityPath)
169
+ resolvedEntries.push(resolvedModule.identityPath)
170
+ }
171
+
172
+ let visited = new Set(resolvedEntries)
173
+ let queue = [...resolvedEntries]
174
+ let layers: string[][] = []
175
+
176
+ while (queue.length > 0) {
177
+ let frontier = queue
178
+ queue = []
179
+ let resolvedModules = await getOrCreateResolvedScripts(
180
+ frontier.map((identityPath) => scriptStore.get(identityPath)),
181
+ )
182
+ let layer: string[] = []
183
+
184
+ for (let resolvedModule of resolvedModules) {
185
+ layer.push(getServedUrlForResolvedScript(resolvedModule))
186
+
187
+ for (let dep of resolvedModule.deps) {
188
+ if (visited.has(dep)) continue
189
+ visited.add(dep)
190
+ queue.push(dep)
191
+ }
192
+ }
193
+
194
+ layers.push(layer)
195
+ }
196
+
197
+ return layers
198
+ },
199
+
200
+ async getHref(filePath) {
201
+ let resolvedModule = resolveServedScriptOrThrow(resolveInputFilePath(filePath))
202
+ return getServedUrl(resolvedModule.identityPath)
203
+ },
204
+
205
+ async handleFileEvent(filePath, event) {
206
+ let normalizedFilePath = normalizeFilePath(filePath)
207
+ if (isWatchIgnored(normalizedFilePath)) return
208
+
209
+ if (shouldClearResolverCacheForFileEvent(normalizedFilePath, event)) {
210
+ resolverFactory.clearCache()
211
+ }
212
+
213
+ if (isTsconfigPath(normalizedFilePath)) {
214
+ tsconfigTransformOptionsResolver.clear()
215
+ scriptStore.invalidateAll()
216
+ return
217
+ }
218
+
219
+ if (isPackageJsonPath(normalizedFilePath)) {
220
+ scriptStore.invalidateAll()
221
+ return
222
+ }
223
+
224
+ scriptStore.invalidateForFileEvent(normalizedFilePath, event)
225
+ },
226
+
227
+ parseRequestPathname(pathname) {
228
+ let parsedPathname = parseServedPathname(pathname)
229
+ let filePath = resolvedOptions.routes.resolveUrlPathname(parsedPathname.stablePathname)
230
+ if (!filePath) return null
231
+ if (resolvedOptions.fingerprintAssets && parsedPathname.requestedFingerprint === null)
232
+ return null
233
+
234
+ return {
235
+ cacheControl: getFingerprintRequestCacheControl(parsedPathname.requestedFingerprint),
236
+ filePath,
237
+ isSourceMapRequest: parsedPathname.isSourceMapRequest,
238
+ requestedFingerprint: parsedPathname.requestedFingerprint,
239
+ }
240
+ },
241
+ }
242
+
243
+ function resolveInputFilePath(filePath: string): string {
244
+ if (filePath.startsWith('file://')) {
245
+ return normalizeFilePath(fileURLToPath(new URL(filePath)))
246
+ }
247
+
248
+ if (filePath.includes('://')) {
249
+ throw new TypeError(`Expected a file path or file:// URL, received "${filePath}"`)
250
+ }
251
+
252
+ return resolveFilePath(resolvedOptions.rootDir, filePath)
253
+ }
254
+
255
+ function resolveServedScriptOrThrow(absolutePath: string): ResolveModuleResult {
256
+ let resolvedModule = resolveModulePath(absolutePath)
257
+ if (!resolvedModule) {
258
+ throw createAssetServerCompilationError(`File not found: ${absolutePath}`, {
259
+ code: 'FILE_NOT_FOUND',
260
+ })
261
+ }
262
+
263
+ if (!resolvedOptions.isAllowed(resolvedModule.identityPath)) {
264
+ throw createAssetServerCompilationError(
265
+ `File is not allowed: ${resolvedModule.identityPath}`,
266
+ {
267
+ code: 'FILE_NOT_ALLOWED',
268
+ },
269
+ )
270
+ }
271
+
272
+ return resolvedModule
273
+ }
274
+
275
+ function getNotModifiedScript(
276
+ record: ScriptRecord,
277
+ options: ScriptGetOptions,
278
+ ): ScriptGetResult | null {
279
+ let current = getNotModifiedResult(record.emitted, options)
280
+ if (current) return current
281
+
282
+ if (!record.staleEmittedSnapshot || !isModuleSnapshotFresh(record.staleEmittedSnapshot)) {
283
+ return null
284
+ }
285
+
286
+ return getNotModifiedResult(record.staleEmitted, options)
287
+ }
288
+
289
+ async function getOrCreateResolvedScripts(records: ScriptRecord[]): Promise<ResolvedModule[]> {
290
+ return mapWithConcurrency(records, preloadConcurrency, (record) =>
291
+ getOrCreateResolvedScript(record),
292
+ )
293
+ }
294
+
295
+ async function getOrCreateResolvedScript(record: ScriptRecord): Promise<ResolvedModule> {
296
+ if (record.resolved) return record.resolved
297
+
298
+ let cacheKey = getRecordCacheKey(record)
299
+ let existing = resolveInFlightByCacheKey.get(cacheKey)
300
+ if (existing) return existing
301
+
302
+ let promise = (async () => {
303
+ let startedVersion = record.invalidationVersion
304
+ let transformedModule = await getOrCreateTransformedScript(record)
305
+ if (
306
+ resolvedOptions.watchMode &&
307
+ transformedModule.unresolvedImports.some((unresolved) =>
308
+ isBareImportSpecifier(unresolved.specifier),
309
+ )
310
+ ) {
311
+ resolverFactory.clearCache()
312
+ }
313
+ let resolveModuleResult = await resolveModule(record, transformedModule, resolveArgs)
314
+
315
+ if (!resolveModuleResult.ok) {
316
+ if (isFresh(record, startedVersion)) {
317
+ scriptStore.clearResolved(record.identityPath, [resolveModuleResult.tracking])
318
+ }
319
+ throw resolveModuleResult.error
320
+ }
321
+
322
+ if (isFresh(record, startedVersion)) {
323
+ scriptStore.setResolved(record.identityPath, resolveModuleResult.value, [
324
+ resolveModuleResult.tracking,
325
+ ])
326
+ }
327
+
328
+ return resolveModuleResult.value
329
+ })()
330
+
331
+ resolveInFlightByCacheKey.set(cacheKey, promise)
332
+
333
+ try {
334
+ return await promise
335
+ } finally {
336
+ if (resolveInFlightByCacheKey.get(cacheKey) === promise) {
337
+ resolveInFlightByCacheKey.delete(cacheKey)
338
+ }
339
+ }
340
+ }
341
+
342
+ async function getOrCreateTransformedScript(record: ScriptRecord): Promise<TransformedModule> {
343
+ if (record.transformed) return record.transformed
344
+
345
+ let startedVersion = record.invalidationVersion
346
+ let transformModuleResult = await transformModule(record, transformArgs)
347
+
348
+ if (!transformModuleResult.ok) {
349
+ if (isFresh(record, startedVersion)) {
350
+ scriptStore.clearTransformed(record.identityPath, [transformModuleResult.tracking])
351
+ }
352
+ throw transformModuleResult.error
353
+ }
354
+
355
+ if (isFresh(record, startedVersion)) {
356
+ scriptStore.setTransformed(record.identityPath, transformModuleResult.value, [
357
+ transformModuleResult.tracking,
358
+ ])
359
+ }
360
+
361
+ return transformModuleResult.value
362
+ }
363
+
364
+ async function getOrCreateEmittedScript(record: ScriptRecord): Promise<EmittedModule> {
365
+ if (record.emitted) return record.emitted
366
+
367
+ let cacheKey = getRecordCacheKey(record)
368
+ let existing = emitInFlightByCacheKey.get(cacheKey)
369
+ if (existing) return existing
370
+
371
+ let promise = (async () => {
372
+ let startedVersion = record.invalidationVersion
373
+ let resolvedModule = await getOrCreateResolvedScript(record)
374
+ let emitResolvedModuleResult = await emitResolvedModule(resolvedModule, {
375
+ getServedUrl,
376
+ sourceMaps: resolvedOptions.sourceMaps,
377
+ })
378
+
379
+ if (!emitResolvedModuleResult.ok) {
380
+ throw emitResolvedModuleResult.error
381
+ }
382
+
383
+ if (isFresh(record, startedVersion)) {
384
+ scriptStore.setEmitted(
385
+ record.identityPath,
386
+ emitResolvedModuleResult.value,
387
+ createModuleSnapshot(resolvedModule.trackedFiles),
388
+ )
389
+ }
390
+
391
+ return emitResolvedModuleResult.value
392
+ })()
393
+
394
+ emitInFlightByCacheKey.set(cacheKey, promise)
395
+
396
+ try {
397
+ return await promise
398
+ } finally {
399
+ if (emitInFlightByCacheKey.get(cacheKey) === promise) {
400
+ emitInFlightByCacheKey.delete(cacheKey)
401
+ }
402
+ }
403
+ }
404
+
405
+ async function getServedUrl(identityPath: string): Promise<string> {
406
+ return getServedUrlForResolvedScript(
407
+ await getOrCreateResolvedScript(scriptStore.get(identityPath)),
408
+ )
409
+ }
410
+
411
+ function getServedUrlForResolvedScript(resolvedModule: ResolvedModule): string {
412
+ return formatFingerprintedPathname(
413
+ resolvedModule.stableUrlPathname,
414
+ resolvedOptions.fingerprintAssets ? resolvedModule.fingerprint : null,
415
+ )
416
+ }
417
+
418
+ function isWatchIgnored(filePath: string): boolean {
419
+ return resolvedOptions.watchIgnoreMatchers.some((matcher) => matcher(filePath))
420
+ }
421
+ }
422
+
423
+ function getRecordCacheKey(record: ScriptRecord): string {
424
+ return `${record.identityPath}\0${record.invalidationVersion}`
425
+ }
426
+
427
+ function isFresh(record: ScriptRecord, version: number): boolean {
428
+ return record.invalidationVersion === version
429
+ }
430
+
431
+ function getNotModifiedResult(
432
+ emittedModule: EmittedModule | undefined,
433
+ options: ScriptGetOptions,
434
+ ): ScriptGetResult | null {
435
+ if (!emittedModule || options.ifNoneMatch === null) return null
436
+
437
+ if (
438
+ options.requestedFingerprint !== null &&
439
+ emittedModule.fingerprint !== options.requestedFingerprint
440
+ ) {
441
+ return null
442
+ }
443
+
444
+ let asset = getEmittedAssetForRequest(emittedModule, options.isSourceMapRequest)
445
+ if (!asset) return null
446
+
447
+ if (!IfNoneMatch.from(options.ifNoneMatch).matches(asset.etag)) return null
448
+ return { type: 'not-modified', etag: asset.etag }
449
+ }
450
+
451
+ function getEmittedAssetForRequest(
452
+ emittedModule: EmittedModule,
453
+ isSourceMapRequest: boolean,
454
+ ): EmittedAsset | null {
455
+ return isSourceMapRequest ? emittedModule.sourceMap : emittedModule.code
456
+ }
457
+
458
+ function createModuleSnapshot(filePaths: readonly string[]): ModuleSnapshot | null {
459
+ let snapshot = new Map<string, FileSnapshot>()
460
+
461
+ for (let filePath of filePaths) {
462
+ let fileSnapshot = getFileSnapshot(filePath)
463
+ if (!fileSnapshot) return null
464
+ snapshot.set(filePath, fileSnapshot)
465
+ }
466
+
467
+ return snapshot
468
+ }
469
+
470
+ function isModuleSnapshotFresh(snapshot: ModuleSnapshot): boolean {
471
+ for (let [filePath, previous] of snapshot) {
472
+ let current = getFileSnapshot(filePath)
473
+ if (!current) return false
474
+ if (current.mtimeNs !== previous.mtimeNs || current.size !== previous.size) return false
475
+ }
476
+
477
+ return true
478
+ }
479
+
480
+ function getFileSnapshot(filePath: string): FileSnapshot | null {
481
+ try {
482
+ let stats = fs.statSync(filePath, { bigint: true })
483
+ if (!stats.isFile()) return null
484
+ return {
485
+ mtimeNs: stats.mtimeNs,
486
+ size: stats.size,
487
+ }
488
+ } catch (error) {
489
+ if (isNoEntityError(error)) return null
490
+ throw error
491
+ }
492
+ }
493
+
494
+ function parseServedPathname(pathname: string): {
495
+ isSourceMapRequest: boolean
496
+ requestedFingerprint: string | null
497
+ stablePathname: string
498
+ } {
499
+ let isSourceMapRequest = pathname.endsWith('.map')
500
+ let pathWithoutMap = isSourceMapRequest ? pathname.slice(0, -4) : pathname
501
+ let fingerprint = parseFingerprintSuffix(pathWithoutMap)
502
+
503
+ return {
504
+ isSourceMapRequest,
505
+ requestedFingerprint: fingerprint.requestedFingerprint,
506
+ stablePathname: fingerprint.pathname,
507
+ }
508
+ }
509
+
510
+ async function mapWithConcurrency<item, result>(
511
+ items: item[],
512
+ concurrency: number,
513
+ mapper: (item: item, index: number) => Promise<result>,
514
+ ): Promise<result[]> {
515
+ if (items.length === 0) return []
516
+
517
+ let results = new Array<result>(items.length)
518
+ let nextIndex = 0
519
+
520
+ async function worker() {
521
+ while (nextIndex < items.length) {
522
+ let index = nextIndex++
523
+ results[index] = await mapper(items[index], index)
524
+ }
525
+ }
526
+
527
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()))
528
+ return results
529
+ }
530
+
531
+ function toScriptCompileResult(emittedModule: EmittedModule): ScriptCompileResult {
532
+ return {
533
+ code: emittedModule.code,
534
+ fingerprint: emittedModule.fingerprint,
535
+ sourceMap: emittedModule.sourceMap,
536
+ }
537
+ }
538
+
539
+ function isPackageJsonPath(filePath: string): boolean {
540
+ return filePath.endsWith('/package.json')
541
+ }
542
+
543
+ function isTsconfigPath(filePath: string): boolean {
544
+ return /\/tsconfig(?:\..+)?\.json$/.test(filePath)
545
+ }
546
+
547
+ function shouldClearResolverCacheForFileEvent(filePath: string, event: ModuleWatchEvent): boolean {
548
+ return event !== 'change' || isPackageJsonPath(filePath) || isTsconfigPath(filePath)
549
+ }
550
+
551
+ function resolveModulePath(absolutePath: string): ResolveModuleResult | null {
552
+ let resolvedPath: string
553
+
554
+ try {
555
+ resolvedPath = normalizeFilePath(fs.realpathSync(normalizeFilePath(absolutePath)))
556
+ } catch (error) {
557
+ if (isNoEntityError(error)) return null
558
+ throw error
559
+ }
560
+
561
+ if (!supportedScriptExtensionSet.has(path.extname(resolvedPath).toLowerCase())) {
562
+ return null
563
+ }
564
+
565
+ return {
566
+ identityPath: resolvedPath,
567
+ resolvedPath,
568
+ }
569
+ }
570
+
571
+ function resolveActualPath(identityPath: string): string | null {
572
+ try {
573
+ return normalizeFilePath(fs.realpathSync(identityPath))
574
+ } catch (error) {
575
+ if (isNoEntityError(error)) return null
576
+ throw error
577
+ }
578
+ }
579
+
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
+ function isNoEntityError(
593
+ error: unknown,
594
+ ): error is NodeJS.ErrnoException & { code: 'ENOENT' | 'ENOTDIR' } {
595
+ return (
596
+ error instanceof Error &&
597
+ 'code' in error &&
598
+ ((error as NodeJS.ErrnoException).code === 'ENOENT' ||
599
+ (error as NodeJS.ErrnoException).code === 'ENOTDIR')
600
+ )
601
+ }
602
+
603
+ export function createResponseForScript(
604
+ result: ScriptCompileResult,
605
+ options: {
606
+ cacheControl: string
607
+ ifNoneMatch: string | null
608
+ isSourceMapRequest: boolean
609
+ method: string
610
+ },
611
+ ): Response {
612
+ let body: string | null
613
+ let etag: string
614
+ let contentType: string
615
+
616
+ if (options.isSourceMapRequest) {
617
+ if (!result.sourceMap) {
618
+ return new Response('Not found', { status: 404 })
619
+ }
620
+ body = options.method === 'HEAD' ? null : result.sourceMap.content
621
+ etag = result.sourceMap.etag
622
+ contentType = 'application/json; charset=utf-8'
623
+ } else {
624
+ body = options.method === 'HEAD' ? null : result.code.content
625
+ etag = result.code.etag
626
+ contentType = 'application/javascript; charset=utf-8'
627
+ }
628
+
629
+ if (IfNoneMatch.from(options.ifNoneMatch).matches(etag)) {
630
+ return new Response(null, { status: 304, headers: { ETag: etag } })
631
+ }
632
+
633
+ return new Response(body, {
634
+ headers: {
635
+ 'Cache-Control': options.cacheControl,
636
+ 'Content-Type': contentType,
637
+ ETag: etag,
638
+ },
639
+ })
640
+ }