/
loader.ts
94 lines (83 loc) · 2.8 KB
/
loader.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import { fileURLToPath, pathToFileURL } from 'node:url'
import { readFile } from 'node:fs/promises'
import { hasCJSSyntax, isNodeBuiltin } from 'mlly'
import { normalizeModuleId } from 'vite-node/utils'
import { getWorkerState } from '../utils'
import type { Loader, ResolveResult, Resolver } from '../types/loader'
import { ModuleFormat } from '../types/loader'
// TODO fix in mlly (add "}" as a possible first character: "}export default")
const ESM_RE = /([\s;}]|^)(import[\w,{}\s*]*from|import\s*['"*{]|export\b\s*(?:[*{]|default|class|type|function|const|var|let|async function)|import\.meta\b)/m
function hasESMSyntax(code: string) {
return ESM_RE.test(code)
}
interface ContextCache {
isPseudoESM: boolean
source: string
}
const cache = new Map<string, ContextCache>()
const getPotentialSource = async (filepath: string, result: ResolveResult) => {
if (!result.url.startsWith('file://') || result.format === 'module')
return null
let source = cache.get(result.url)?.source
if (source == null)
source = await readFile(filepath, 'utf8')
return source
}
const detectESM = (url: string, source: string | null) => {
const cached = cache.get(url)
if (cached)
return cached.isPseudoESM
if (!source)
return false
return (hasESMSyntax(source) && !hasCJSSyntax(source))
}
// apply transformations only to libraries
// inline code processed by vite-node
// make Node pseudo ESM
export const resolve: Resolver = async (url, context, next) => {
const { parentURL } = context
const state = getWorkerState()
const resolver = state?.rpc.resolveId
if (!parentURL || isNodeBuiltin(url) || !resolver)
return next(url, context, next)
const id = normalizeModuleId(url)
const importer = normalizeModuleId(parentURL)
const resolved = await resolver(id, importer, state.ctx.environment.name)
let result: ResolveResult
let filepath: string
if (resolved) {
const resolvedUrl = pathToFileURL(resolved.id).toString()
filepath = resolved.id
result = {
url: resolvedUrl,
shortCircuit: true,
}
}
else {
const { url: resolvedUrl, format } = await next(url, context, next)
filepath = fileURLToPath(resolvedUrl)
result = {
url: resolvedUrl,
format,
shortCircuit: true,
}
}
const source = await getPotentialSource(filepath, result)
const isPseudoESM = detectESM(result.url, source)
if (typeof source === 'string')
cache.set(result.url, { isPseudoESM, source })
if (isPseudoESM)
result.format = ModuleFormat.Module
return result
}
export const load: Loader = async (url, context, next) => {
const result = await next(url, context, next)
const cached = cache.get(url)
if (cached?.isPseudoESM && result.format !== 'module') {
return {
source: cached.source,
format: ModuleFormat.Module,
}
}
return result
}