@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.
- package/LICENSE +21 -0
- package/README.md +325 -2
- package/dist/assets.d.ts +3 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/assets.js +1 -0
- package/dist/lib/access.d.ts +10 -0
- package/dist/lib/access.d.ts.map +1 -0
- package/dist/lib/access.js +14 -0
- package/dist/lib/asset-server.d.ts +139 -0
- package/dist/lib/asset-server.d.ts.map +1 -0
- package/dist/lib/asset-server.js +338 -0
- package/dist/lib/compilation-error.d.ts +33 -0
- package/dist/lib/compilation-error.d.ts.map +1 -0
- package/dist/lib/compilation-error.js +32 -0
- package/dist/lib/file-matcher.d.ts +6 -0
- package/dist/lib/file-matcher.d.ts.map +1 -0
- package/dist/lib/file-matcher.js +44 -0
- package/dist/lib/fingerprint.d.ts +12 -0
- package/dist/lib/fingerprint.d.ts.map +1 -0
- package/dist/lib/fingerprint.js +49 -0
- package/dist/lib/module-store.d.ts +41 -0
- package/dist/lib/module-store.d.ts.map +1 -0
- package/dist/lib/module-store.js +230 -0
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +50 -0
- package/dist/lib/routes.d.ts +13 -0
- package/dist/lib/routes.d.ts.map +1 -0
- package/dist/lib/routes.js +94 -0
- package/dist/lib/scripts/cjs-check.d.ts +3 -0
- package/dist/lib/scripts/cjs-check.d.ts.map +1 -0
- package/dist/lib/scripts/cjs-check.js +398 -0
- package/dist/lib/scripts/compiler.d.ts +62 -0
- package/dist/lib/scripts/compiler.d.ts.map +1 -0
- package/dist/lib/scripts/compiler.js +439 -0
- package/dist/lib/scripts/emit.d.ts +25 -0
- package/dist/lib/scripts/emit.d.ts.map +1 -0
- package/dist/lib/scripts/emit.js +63 -0
- package/dist/lib/scripts/resolve.d.ts +50 -0
- package/dist/lib/scripts/resolve.d.ts.map +1 -0
- package/dist/lib/scripts/resolve.js +236 -0
- package/dist/lib/scripts/transform.d.ts +64 -0
- package/dist/lib/scripts/transform.d.ts.map +1 -0
- package/dist/lib/scripts/transform.js +373 -0
- package/dist/lib/source-maps.d.ts +4 -0
- package/dist/lib/source-maps.d.ts.map +1 -0
- package/dist/lib/source-maps.js +62 -0
- package/dist/lib/styles/compiler.d.ts +52 -0
- package/dist/lib/styles/compiler.d.ts.map +1 -0
- package/dist/lib/styles/compiler.js +272 -0
- package/dist/lib/styles/emit.d.ts +25 -0
- package/dist/lib/styles/emit.d.ts.map +1 -0
- package/dist/lib/styles/emit.js +78 -0
- package/dist/lib/styles/resolve.d.ts +48 -0
- package/dist/lib/styles/resolve.d.ts.map +1 -0
- package/dist/lib/styles/resolve.js +188 -0
- package/dist/lib/styles/transform.d.ts +47 -0
- package/dist/lib/styles/transform.d.ts.map +1 -0
- package/dist/lib/styles/transform.js +131 -0
- package/dist/lib/target.d.ts +21 -0
- package/dist/lib/target.d.ts.map +1 -0
- package/dist/lib/target.js +127 -0
- package/dist/lib/watch.d.ts +22 -0
- package/dist/lib/watch.d.ts.map +1 -0
- package/dist/lib/watch.js +96 -0
- package/package.json +55 -12
- package/src/assets.ts +2 -0
- package/src/lib/access.ts +24 -0
- package/src/lib/asset-server.ts +537 -0
- package/src/lib/compilation-error.ts +61 -0
- package/src/lib/file-matcher.ts +63 -0
- package/src/lib/fingerprint.ts +65 -0
- package/src/lib/module-store.ts +340 -0
- package/src/lib/paths.ts +66 -0
- package/src/lib/routes.ts +164 -0
- package/src/lib/scripts/cjs-check.ts +476 -0
- package/src/lib/scripts/compiler.ts +640 -0
- package/src/lib/scripts/emit.ts +122 -0
- package/src/lib/scripts/resolve.ts +433 -0
- package/src/lib/scripts/transform.ts +609 -0
- package/src/lib/source-maps.ts +75 -0
- package/src/lib/styles/compiler.ts +400 -0
- package/src/lib/styles/emit.ts +137 -0
- package/src/lib/styles/resolve.ts +316 -0
- package/src/lib/styles/transform.ts +226 -0
- package/src/lib/target.ts +196 -0
- package/src/lib/watch.ts +136 -0
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as fsp from 'node:fs/promises'
|
|
3
|
+
import * as path from 'node:path'
|
|
4
|
+
import { getTsconfig } from 'get-tsconfig'
|
|
5
|
+
import { minify } from 'oxc-minify'
|
|
6
|
+
import { transform as oxcTransform } from 'oxc-transform'
|
|
7
|
+
import { init as esModuleLexerInit, parse as esModuleLexer } from 'es-module-lexer'
|
|
8
|
+
import type { Cache, TsConfigJsonResolved } from 'get-tsconfig'
|
|
9
|
+
import type { TransformOptions as OxcTransformOptions } from 'oxc-transform'
|
|
10
|
+
|
|
11
|
+
import { isCommonJS, mayContainCommonJSModuleGlobals } from './cjs-check.ts'
|
|
12
|
+
import {
|
|
13
|
+
createAssetServerCompilationError,
|
|
14
|
+
isAssetServerCompilationError,
|
|
15
|
+
} from '../compilation-error.ts'
|
|
16
|
+
import type { AssetServerCompilationError } from '../compilation-error.ts'
|
|
17
|
+
import { generateFingerprint } from '../fingerprint.ts'
|
|
18
|
+
import type { ModuleRecord, ModuleTracking } from '../module-store.ts'
|
|
19
|
+
import { normalizeFilePath } from '../paths.ts'
|
|
20
|
+
import type { CompiledRoutes } from '../routes.ts'
|
|
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>
|
|
27
|
+
|
|
28
|
+
type SourceLanguage = 'js' | 'jsx' | 'ts' | 'tsx'
|
|
29
|
+
|
|
30
|
+
const scriptModuleTypes = [
|
|
31
|
+
{ extension: '.js', lang: 'js' },
|
|
32
|
+
{ extension: '.jsx', lang: 'jsx' },
|
|
33
|
+
{ extension: '.mjs', lang: 'js' },
|
|
34
|
+
{ extension: '.mts', lang: 'ts' },
|
|
35
|
+
{ extension: '.ts', lang: 'ts' },
|
|
36
|
+
{ extension: '.tsx', lang: 'tsx' },
|
|
37
|
+
] as const satisfies ReadonlyArray<{ extension: string; lang: SourceLanguage }>
|
|
38
|
+
|
|
39
|
+
const sourceLanguageByExtension = new Map<string, SourceLanguage>(
|
|
40
|
+
scriptModuleTypes.map(({ extension, lang }) => [extension, lang] as const),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const supportedTsconfigTransformCompilerOptions = {
|
|
44
|
+
allowNamespaces: 'allowNamespaces',
|
|
45
|
+
emitDecoratorMetadata: 'emitDecoratorMetadata',
|
|
46
|
+
experimentalDecorators: 'experimentalDecorators',
|
|
47
|
+
jsx: 'jsx',
|
|
48
|
+
jsxFactory: 'jsxFactory',
|
|
49
|
+
jsxFragmentFactory: 'jsxFragmentFactory',
|
|
50
|
+
jsxImportSource: 'jsxImportSource',
|
|
51
|
+
useDefineForClassFields: 'useDefineForClassFields',
|
|
52
|
+
} as const
|
|
53
|
+
|
|
54
|
+
export type ResolveModuleResult = {
|
|
55
|
+
identityPath: string
|
|
56
|
+
resolvedPath: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type UnresolvedImport = {
|
|
60
|
+
end: number
|
|
61
|
+
quote?: '"' | "'" | '`'
|
|
62
|
+
specifier: string
|
|
63
|
+
start: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type TransformedModule = {
|
|
67
|
+
fingerprint: string | null
|
|
68
|
+
identityPath: string
|
|
69
|
+
importerDir: string
|
|
70
|
+
packageSpecifiers: string[]
|
|
71
|
+
rawCode: string
|
|
72
|
+
resolvedPath: string
|
|
73
|
+
sourceMap: string | null
|
|
74
|
+
stableUrlPathname: string
|
|
75
|
+
trackedFiles: string[]
|
|
76
|
+
unresolvedImports: UnresolvedImport[]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type TransformResult = {
|
|
80
|
+
tracking: ModuleTracking
|
|
81
|
+
} & (
|
|
82
|
+
| {
|
|
83
|
+
ok: true
|
|
84
|
+
value: TransformedModule
|
|
85
|
+
}
|
|
86
|
+
| {
|
|
87
|
+
ok: false
|
|
88
|
+
error: AssetServerCompilationError
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
type TsconfigTransformOptions = {
|
|
93
|
+
trackedFiles: string[]
|
|
94
|
+
tsconfigRaw?: TsConfigJsonResolved
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
type TsconfigTransformOptionsResolver = ReturnType<typeof createTsconfigTransformOptionsResolver>
|
|
98
|
+
|
|
99
|
+
export type TransformArgs = {
|
|
100
|
+
buildId: string | null
|
|
101
|
+
define: Record<string, string> | null
|
|
102
|
+
externalSet: ReadonlySet<string>
|
|
103
|
+
isWatchIgnored(filePath: string): boolean
|
|
104
|
+
minify: boolean
|
|
105
|
+
resolveActualPath(identityPath: string): string | null
|
|
106
|
+
routes: CompiledRoutes
|
|
107
|
+
sourceMapSourcePaths: 'absolute' | 'url'
|
|
108
|
+
sourceMaps: 'external' | 'inline' | null
|
|
109
|
+
target: ResolvedScriptTarget | null
|
|
110
|
+
tsconfigTransformOptionsResolver: TsconfigTransformOptionsResolver
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function createTsconfigTransformOptionsResolver() {
|
|
114
|
+
let fileSystemCache: Cache = new Map()
|
|
115
|
+
let transformOptionsByDirectory = new Map<string, TsconfigTransformOptions>()
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
clear() {
|
|
119
|
+
fileSystemCache = new Map()
|
|
120
|
+
transformOptionsByDirectory.clear()
|
|
121
|
+
},
|
|
122
|
+
getTransformOptions(
|
|
123
|
+
filePath: string,
|
|
124
|
+
isWatchIgnored: (filePath: string) => boolean,
|
|
125
|
+
): TsconfigTransformOptions {
|
|
126
|
+
let directory = path.dirname(filePath)
|
|
127
|
+
let cached = transformOptionsByDirectory.get(directory)
|
|
128
|
+
if (cached) return cached
|
|
129
|
+
|
|
130
|
+
let tsconfig = getTsconfig(directory, 'tsconfig.json', fileSystemCache)
|
|
131
|
+
if (!tsconfig) {
|
|
132
|
+
let transformOptions = { trackedFiles: [] }
|
|
133
|
+
transformOptionsByDirectory.set(directory, transformOptions)
|
|
134
|
+
return transformOptions
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let tsconfigPath = findNearestTsconfigPath(directory)
|
|
138
|
+
let transformOptions: TsconfigTransformOptions = {
|
|
139
|
+
trackedFiles: tsconfigPath && !isWatchIgnored(tsconfigPath) ? [tsconfigPath] : [],
|
|
140
|
+
tsconfigRaw: tsconfig.config,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
transformOptionsByDirectory.set(directory, transformOptions)
|
|
144
|
+
return transformOptions
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function transformModule(
|
|
150
|
+
record: ScriptRecord,
|
|
151
|
+
args: TransformArgs,
|
|
152
|
+
): Promise<TransformResult> {
|
|
153
|
+
let resolvedPath = args.resolveActualPath(record.identityPath)
|
|
154
|
+
if (!resolvedPath) {
|
|
155
|
+
return {
|
|
156
|
+
ok: false,
|
|
157
|
+
error: createAssetServerCompilationError(`File not found: ${record.identityPath}`, {
|
|
158
|
+
code: 'FILE_NOT_FOUND',
|
|
159
|
+
}),
|
|
160
|
+
tracking: {
|
|
161
|
+
trackedFiles: args.isWatchIgnored(record.identityPath) ? [] : [record.identityPath],
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let transformOptions = args.tsconfigTransformOptionsResolver.getTransformOptions(
|
|
167
|
+
resolvedPath,
|
|
168
|
+
args.isWatchIgnored,
|
|
169
|
+
)
|
|
170
|
+
let trackedFiles = [
|
|
171
|
+
...(args.isWatchIgnored(resolvedPath) ? [] : [resolvedPath]),
|
|
172
|
+
...transformOptions.trackedFiles,
|
|
173
|
+
]
|
|
174
|
+
let sourceText: string
|
|
175
|
+
try {
|
|
176
|
+
sourceText = await fsp.readFile(resolvedPath, 'utf-8')
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (isNoEntityError(error)) {
|
|
179
|
+
return {
|
|
180
|
+
ok: false,
|
|
181
|
+
error: createAssetServerCompilationError(`File not found: ${resolvedPath}`, {
|
|
182
|
+
cause: error,
|
|
183
|
+
code: 'FILE_NOT_FOUND',
|
|
184
|
+
}),
|
|
185
|
+
tracking: {
|
|
186
|
+
trackedFiles,
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
error: toTransformFailedError(error, resolvedPath),
|
|
193
|
+
tracking: {
|
|
194
|
+
trackedFiles,
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
let analysis = await analyzeModuleSource(sourceText, resolvedPath, transformOptions, {
|
|
201
|
+
define: args.define ?? undefined,
|
|
202
|
+
minify: args.minify,
|
|
203
|
+
sourceMaps: args.sourceMaps ?? undefined,
|
|
204
|
+
target: args.target ?? undefined,
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
analysis.unresolvedImports = analysis.unresolvedImports.filter(
|
|
208
|
+
(unresolved) => !args.externalSet.has(unresolved.specifier),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if (mayContainCommonJSModuleGlobals(sourceText) && isCommonJS(analysis.rawCode)) {
|
|
212
|
+
throw createAssetServerCompilationError(
|
|
213
|
+
`CommonJS module detected: ${resolvedPath}. ` +
|
|
214
|
+
`This module uses CommonJS (require/module.exports) which is not supported. ` +
|
|
215
|
+
`Please use an ESM-compatible module.`,
|
|
216
|
+
{
|
|
217
|
+
code: 'COMMONJS_NOT_SUPPORTED',
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let stableUrlPathname = args.routes.toUrlPathname(record.identityPath)
|
|
223
|
+
if (!stableUrlPathname) {
|
|
224
|
+
throw createAssetServerCompilationError(
|
|
225
|
+
`File ${record.identityPath} is outside all configured fileMap entries.`,
|
|
226
|
+
{
|
|
227
|
+
code: 'FILE_OUTSIDE_FILE_MAP',
|
|
228
|
+
},
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let sourceMap = analysis.sourceMap
|
|
233
|
+
? rewriteSourceMapSources(
|
|
234
|
+
analysis.sourceMap,
|
|
235
|
+
resolvedPath,
|
|
236
|
+
stableUrlPathname,
|
|
237
|
+
args.sourceMapSourcePaths,
|
|
238
|
+
sourceText,
|
|
239
|
+
)
|
|
240
|
+
: null
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
ok: true,
|
|
244
|
+
tracking: {
|
|
245
|
+
trackedFiles,
|
|
246
|
+
},
|
|
247
|
+
value: {
|
|
248
|
+
fingerprint:
|
|
249
|
+
args.buildId === null
|
|
250
|
+
? null
|
|
251
|
+
: await generateFingerprint({
|
|
252
|
+
buildId: args.buildId,
|
|
253
|
+
content: sourceText,
|
|
254
|
+
}),
|
|
255
|
+
identityPath: record.identityPath,
|
|
256
|
+
importerDir: path.dirname(resolvedPath),
|
|
257
|
+
packageSpecifiers: analysis.unresolvedImports
|
|
258
|
+
.filter((unresolved) => isPackageImportSpecifier(unresolved.specifier))
|
|
259
|
+
.map((unresolved) => unresolved.specifier),
|
|
260
|
+
rawCode: analysis.rawCode,
|
|
261
|
+
resolvedPath,
|
|
262
|
+
sourceMap,
|
|
263
|
+
stableUrlPathname,
|
|
264
|
+
trackedFiles,
|
|
265
|
+
unresolvedImports: analysis.unresolvedImports,
|
|
266
|
+
},
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
return {
|
|
270
|
+
ok: false,
|
|
271
|
+
error: toTransformFailedError(error, resolvedPath),
|
|
272
|
+
tracking: {
|
|
273
|
+
trackedFiles,
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function findNearestTsconfigPath(directory: string): string | null {
|
|
280
|
+
let currentDirectory = directory
|
|
281
|
+
|
|
282
|
+
while (true) {
|
|
283
|
+
let tsconfigPath = path.join(currentDirectory, 'tsconfig.json')
|
|
284
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
285
|
+
return normalizeFilePath(tsconfigPath)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let parentDirectory = path.dirname(currentDirectory)
|
|
289
|
+
if (parentDirectory === currentDirectory) return null
|
|
290
|
+
currentDirectory = parentDirectory
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function isPackageImportSpecifier(specifier: string): boolean {
|
|
295
|
+
return !specifier.startsWith('./') && !specifier.startsWith('../') && !specifier.startsWith('/')
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function analyzeModuleSource(
|
|
299
|
+
sourceText: string,
|
|
300
|
+
resolvedPath: string,
|
|
301
|
+
transformOptions: TsconfigTransformOptions,
|
|
302
|
+
options: {
|
|
303
|
+
define?: Record<string, string>
|
|
304
|
+
minify: boolean
|
|
305
|
+
sourceMaps?: 'external' | 'inline'
|
|
306
|
+
target?: ResolvedScriptTarget
|
|
307
|
+
},
|
|
308
|
+
) {
|
|
309
|
+
let transformResult: { code: string; errors?: Array<{ message?: string }>; map?: unknown }
|
|
310
|
+
try {
|
|
311
|
+
transformResult = await oxcTransform(
|
|
312
|
+
resolvedPath,
|
|
313
|
+
sourceText,
|
|
314
|
+
getTransformOptions(resolvedPath, transformOptions, options),
|
|
315
|
+
)
|
|
316
|
+
assertNoCompilerErrors(transformResult.errors, resolvedPath, 'transform')
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (isAssetServerCompilationError(error)) throw error
|
|
319
|
+
throw createAssetServerCompilationError(
|
|
320
|
+
`Failed to transform script ${resolvedPath}. ${formatUnknownError(error)}`,
|
|
321
|
+
{
|
|
322
|
+
cause: error,
|
|
323
|
+
code: 'TRANSFORM_FAILED',
|
|
324
|
+
},
|
|
325
|
+
)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let rawCode = transformResult.code.trimEnd()
|
|
329
|
+
let sourceMap = stringifySourceMap(transformResult.map)
|
|
330
|
+
|
|
331
|
+
if (options.minify) {
|
|
332
|
+
let minifyResult = await minifyModule(rawCode, resolvedPath, options.target, options.sourceMaps)
|
|
333
|
+
rawCode = minifyResult.code.trimEnd()
|
|
334
|
+
let minifyMap = stringifySourceMap(minifyResult.map)
|
|
335
|
+
sourceMap =
|
|
336
|
+
minifyMap == null
|
|
337
|
+
? sourceMap
|
|
338
|
+
: sourceMap == null
|
|
339
|
+
? minifyMap
|
|
340
|
+
: composeSourceMaps(minifyMap, sourceMap)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
rawCode,
|
|
345
|
+
sourceMap,
|
|
346
|
+
unresolvedImports: await getUnresolvedImportsFromLexer(rawCode),
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function minifyModule(
|
|
351
|
+
rawCode: string,
|
|
352
|
+
resolvedPath: string,
|
|
353
|
+
target: ResolvedScriptTarget | undefined,
|
|
354
|
+
sourceMaps?: 'external' | 'inline',
|
|
355
|
+
) {
|
|
356
|
+
try {
|
|
357
|
+
let result = await minify(resolvedPath, rawCode, {
|
|
358
|
+
compress: target ? { target } : true,
|
|
359
|
+
mangle: true,
|
|
360
|
+
module: true,
|
|
361
|
+
sourcemap: sourceMaps != null,
|
|
362
|
+
})
|
|
363
|
+
assertNoCompilerErrors(result.errors, resolvedPath, 'minify')
|
|
364
|
+
return result
|
|
365
|
+
} catch (error) {
|
|
366
|
+
if (isAssetServerCompilationError(error)) throw error
|
|
367
|
+
throw createAssetServerCompilationError(
|
|
368
|
+
`Failed to minify script ${resolvedPath}. ${formatUnknownError(error)}`,
|
|
369
|
+
{
|
|
370
|
+
cause: error,
|
|
371
|
+
code: 'TRANSFORM_FAILED',
|
|
372
|
+
},
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function getTransformOptions(
|
|
378
|
+
resolvedPath: string,
|
|
379
|
+
transformOptions: TsconfigTransformOptions,
|
|
380
|
+
options: {
|
|
381
|
+
define?: Record<string, string>
|
|
382
|
+
sourceMaps?: 'external' | 'inline'
|
|
383
|
+
target?: ResolvedScriptTarget
|
|
384
|
+
},
|
|
385
|
+
): OxcTransformOptions {
|
|
386
|
+
let compilerOptions = transformOptions.tsconfigRaw?.compilerOptions as
|
|
387
|
+
| Record<string, unknown>
|
|
388
|
+
| undefined
|
|
389
|
+
let useDefineForClassFields = getBooleanOption(
|
|
390
|
+
compilerOptions,
|
|
391
|
+
supportedTsconfigTransformCompilerOptions.useDefineForClassFields,
|
|
392
|
+
)
|
|
393
|
+
let jsxFactory = getStringOption(
|
|
394
|
+
compilerOptions,
|
|
395
|
+
supportedTsconfigTransformCompilerOptions.jsxFactory,
|
|
396
|
+
)
|
|
397
|
+
let jsxFragmentFactory = getStringOption(
|
|
398
|
+
compilerOptions,
|
|
399
|
+
supportedTsconfigTransformCompilerOptions.jsxFragmentFactory,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
assumptions:
|
|
404
|
+
useDefineForClassFields === false
|
|
405
|
+
? {
|
|
406
|
+
setPublicClassFields: true,
|
|
407
|
+
}
|
|
408
|
+
: undefined,
|
|
409
|
+
decorator: getDecoratorOptions(compilerOptions),
|
|
410
|
+
define: options.define,
|
|
411
|
+
jsx: getJsxOptions(resolvedPath, compilerOptions),
|
|
412
|
+
lang: getSourceLanguageForPath(resolvedPath),
|
|
413
|
+
sourceType: 'module' as const,
|
|
414
|
+
sourcemap: options.sourceMaps != null,
|
|
415
|
+
target: options.target,
|
|
416
|
+
typescript: {
|
|
417
|
+
allowNamespaces: getBooleanOption(
|
|
418
|
+
compilerOptions,
|
|
419
|
+
supportedTsconfigTransformCompilerOptions.allowNamespaces,
|
|
420
|
+
),
|
|
421
|
+
jsxPragma: jsxFactory,
|
|
422
|
+
jsxPragmaFrag: jsxFragmentFactory,
|
|
423
|
+
removeClassFieldsWithoutInitializer: useDefineForClassFields === false ? true : undefined,
|
|
424
|
+
},
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function getJsxOptions(
|
|
429
|
+
resolvedPath: string,
|
|
430
|
+
compilerOptions?: Record<string, unknown>,
|
|
431
|
+
): OxcTransformOptions['jsx'] | undefined {
|
|
432
|
+
let language = getSourceLanguageForPath(resolvedPath)
|
|
433
|
+
if (language !== 'jsx' && language !== 'tsx') return undefined
|
|
434
|
+
|
|
435
|
+
let jsx = getStringOption(compilerOptions, supportedTsconfigTransformCompilerOptions.jsx)
|
|
436
|
+
let importSource = getStringOption(
|
|
437
|
+
compilerOptions,
|
|
438
|
+
supportedTsconfigTransformCompilerOptions.jsxImportSource,
|
|
439
|
+
)
|
|
440
|
+
let pragma = getStringOption(
|
|
441
|
+
compilerOptions,
|
|
442
|
+
supportedTsconfigTransformCompilerOptions.jsxFactory,
|
|
443
|
+
)
|
|
444
|
+
let pragmaFrag = getStringOption(
|
|
445
|
+
compilerOptions,
|
|
446
|
+
supportedTsconfigTransformCompilerOptions.jsxFragmentFactory,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
if (jsx === 'preserve' || jsx === 'react-native') {
|
|
450
|
+
throw createAssetServerCompilationError(
|
|
451
|
+
`Unsupported tsconfig compilerOptions.jsx = "${jsx}" for ${resolvedPath}. ` +
|
|
452
|
+
`Asset server must compile JSX to browser-runnable JavaScript.`,
|
|
453
|
+
{
|
|
454
|
+
code: 'TRANSFORM_FAILED',
|
|
455
|
+
},
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (jsx === 'react-jsx' || jsx === 'react-jsxdev') {
|
|
460
|
+
return {
|
|
461
|
+
development: jsx === 'react-jsxdev',
|
|
462
|
+
importSource,
|
|
463
|
+
runtime: 'automatic',
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
pragma,
|
|
469
|
+
pragmaFrag,
|
|
470
|
+
runtime: 'classic',
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function getDecoratorOptions(
|
|
475
|
+
compilerOptions?: Record<string, unknown>,
|
|
476
|
+
): OxcTransformOptions['decorator'] | undefined {
|
|
477
|
+
let legacy = getBooleanOption(
|
|
478
|
+
compilerOptions,
|
|
479
|
+
supportedTsconfigTransformCompilerOptions.experimentalDecorators,
|
|
480
|
+
)
|
|
481
|
+
let emitDecoratorMetadata = getBooleanOption(
|
|
482
|
+
compilerOptions,
|
|
483
|
+
supportedTsconfigTransformCompilerOptions.emitDecoratorMetadata,
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
if (legacy !== true && emitDecoratorMetadata !== true) return undefined
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
emitDecoratorMetadata,
|
|
490
|
+
legacy,
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function getBooleanOption(
|
|
495
|
+
compilerOptions: Record<string, unknown> | undefined,
|
|
496
|
+
key: string,
|
|
497
|
+
): boolean | undefined {
|
|
498
|
+
let value = compilerOptions?.[key]
|
|
499
|
+
return typeof value === 'boolean' ? value : undefined
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function getStringOption(
|
|
503
|
+
compilerOptions: Record<string, unknown> | undefined,
|
|
504
|
+
key: string,
|
|
505
|
+
): string | undefined {
|
|
506
|
+
let value = compilerOptions?.[key]
|
|
507
|
+
return typeof value === 'string' ? value : undefined
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function assertNoCompilerErrors(
|
|
511
|
+
errors: Array<{ message?: string }> | undefined,
|
|
512
|
+
resolvedPath: string,
|
|
513
|
+
operation: 'transform' | 'minify',
|
|
514
|
+
) {
|
|
515
|
+
if (!errors || errors.length === 0) return
|
|
516
|
+
|
|
517
|
+
throw createAssetServerCompilationError(
|
|
518
|
+
`Failed to ${operation} script ${resolvedPath}. ${errors[0].message ?? 'Unknown error'}`,
|
|
519
|
+
{
|
|
520
|
+
code: 'TRANSFORM_FAILED',
|
|
521
|
+
},
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async function getUnresolvedImportsFromLexer(rawCode: string): Promise<UnresolvedImport[]> {
|
|
526
|
+
await esModuleLexerInit
|
|
527
|
+
let [imports] = esModuleLexer(rawCode)
|
|
528
|
+
let unresolvedImports: UnresolvedImport[] = []
|
|
529
|
+
|
|
530
|
+
for (let imported of imports) {
|
|
531
|
+
let specifier = getStaticImportSpecifier(rawCode, imported)
|
|
532
|
+
if (specifier == null || shouldSkipImportSpecifier(specifier)) continue
|
|
533
|
+
unresolvedImports.push({
|
|
534
|
+
specifier,
|
|
535
|
+
start: imported.s,
|
|
536
|
+
end: imported.e,
|
|
537
|
+
quote: getImportQuote(rawCode, imported.s),
|
|
538
|
+
})
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return unresolvedImports
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function getStaticImportSpecifier(
|
|
545
|
+
source: string,
|
|
546
|
+
imported: ReturnType<typeof esModuleLexer>[0][number],
|
|
547
|
+
): string | null {
|
|
548
|
+
if (imported.n != null) {
|
|
549
|
+
return imported.n
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (imported.d < 0) {
|
|
553
|
+
return null
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
let rawSpecifier = source.slice(imported.s, imported.e)
|
|
557
|
+
if (!isStaticTemplateLiteral(rawSpecifier)) {
|
|
558
|
+
return null
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return rawSpecifier.slice(1, -1)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function isStaticTemplateLiteral(specifier: string): boolean {
|
|
565
|
+
return specifier.startsWith('`') && specifier.endsWith('`') && !specifier.includes('${')
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function shouldSkipImportSpecifier(specifier: string): boolean {
|
|
569
|
+
return (
|
|
570
|
+
specifier.startsWith('data:') ||
|
|
571
|
+
specifier.startsWith('http://') ||
|
|
572
|
+
specifier.startsWith('https://')
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function getImportQuote(source: string, start: number): '"' | "'" | '`' | undefined {
|
|
577
|
+
let firstCharacter = source[start]
|
|
578
|
+
if (firstCharacter === '"' || firstCharacter === "'" || firstCharacter === '`') {
|
|
579
|
+
return firstCharacter
|
|
580
|
+
}
|
|
581
|
+
return undefined
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function getSourceLanguageForPath(resolvedPath: string): SourceLanguage {
|
|
585
|
+
let extension = path.extname(resolvedPath).toLowerCase()
|
|
586
|
+
return sourceLanguageByExtension.get(extension) ?? 'js'
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function formatUnknownError(error: unknown): string {
|
|
590
|
+
return error instanceof Error ? error.message : String(error)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function toTransformFailedError(error: unknown, resolvedPath: string): AssetServerCompilationError {
|
|
594
|
+
if (isAssetServerCompilationError(error)) return error
|
|
595
|
+
|
|
596
|
+
return createAssetServerCompilationError(
|
|
597
|
+
`Failed to transform script ${resolvedPath}. ${formatUnknownError(error)}`,
|
|
598
|
+
{
|
|
599
|
+
cause: error,
|
|
600
|
+
code: 'TRANSFORM_FAILED',
|
|
601
|
+
},
|
|
602
|
+
)
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function isNoEntityError(error: unknown): error is NodeJS.ErrnoException & { code: 'ENOENT' } {
|
|
606
|
+
return (
|
|
607
|
+
error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT'
|
|
608
|
+
)
|
|
609
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { SourceMapConsumer, SourceMapGenerator } from 'source-map-js'
|
|
2
|
+
|
|
3
|
+
import { normalizeFilePath } from './paths.ts'
|
|
4
|
+
|
|
5
|
+
export function composeSourceMaps(rewriteSourceMap: string, transformSourceMap: string): string {
|
|
6
|
+
let rewriteConsumer = new SourceMapConsumer(JSON.parse(rewriteSourceMap))
|
|
7
|
+
let transformConsumer = new SourceMapConsumer(JSON.parse(transformSourceMap))
|
|
8
|
+
let generator = new SourceMapGenerator()
|
|
9
|
+
|
|
10
|
+
rewriteConsumer.eachMapping((mapping) => {
|
|
11
|
+
if (
|
|
12
|
+
mapping.originalLine == null ||
|
|
13
|
+
mapping.originalColumn == null ||
|
|
14
|
+
mapping.generatedLine == null ||
|
|
15
|
+
mapping.generatedColumn == null
|
|
16
|
+
) {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let original = transformConsumer.originalPositionFor({
|
|
21
|
+
line: mapping.originalLine,
|
|
22
|
+
column: mapping.originalColumn,
|
|
23
|
+
})
|
|
24
|
+
if (original.line == null || original.column == null || original.source == null) return
|
|
25
|
+
|
|
26
|
+
generator.addMapping({
|
|
27
|
+
generated: {
|
|
28
|
+
line: mapping.generatedLine,
|
|
29
|
+
column: mapping.generatedColumn,
|
|
30
|
+
},
|
|
31
|
+
original: {
|
|
32
|
+
line: original.line,
|
|
33
|
+
column: original.column,
|
|
34
|
+
},
|
|
35
|
+
source: original.source,
|
|
36
|
+
name: original.name ?? mapping.name ?? undefined,
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
for (let source of transformConsumer.sources) {
|
|
41
|
+
let sourceContent = transformConsumer.sourceContentFor(source, true)
|
|
42
|
+
if (sourceContent !== null) {
|
|
43
|
+
generator.setSourceContent(source, sourceContent)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return JSON.stringify(generator.toJSON())
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function rewriteSourceMapSources(
|
|
51
|
+
sourceMap: string,
|
|
52
|
+
resolvedPath: string,
|
|
53
|
+
stableUrlPathname: string,
|
|
54
|
+
sourceMapSourcePaths: 'absolute' | 'url',
|
|
55
|
+
sourceContent?: string,
|
|
56
|
+
): string {
|
|
57
|
+
let json = JSON.parse(sourceMap) as { sources?: string[]; sourcesContent?: string[] }
|
|
58
|
+
json.sources = [
|
|
59
|
+
sourceMapSourcePaths === 'absolute' ? normalizeFilePath(resolvedPath) : stableUrlPathname,
|
|
60
|
+
]
|
|
61
|
+
if (sourceContent !== undefined) {
|
|
62
|
+
json.sourcesContent = [sourceContent]
|
|
63
|
+
}
|
|
64
|
+
return JSON.stringify(json)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function stringifySourceMap(map: unknown): string | null {
|
|
68
|
+
if (!map) return null
|
|
69
|
+
if (typeof map === 'string') return map
|
|
70
|
+
if (map instanceof Uint8Array) {
|
|
71
|
+
return Buffer.from(map).toString('utf8')
|
|
72
|
+
}
|
|
73
|
+
if (typeof map === 'object' && map !== null) return JSON.stringify(map)
|
|
74
|
+
return String(map)
|
|
75
|
+
}
|