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