-
Notifications
You must be signed in to change notification settings - Fork 7
/
mod.ts
141 lines (119 loc) · 3.62 KB
/
mod.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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import {
esbuildNative,
esbuildWASM,
dirname,
toFileUrl,
DenoConfigurationFile,
ErrorStackParser,
resolveModuleSpecifier,
denoPlugin,
stripShebang,
} from './deps.ts'
export type Module = Record<string, unknown>
export interface ImportModuleOptions {
/** Force the use of the ponyfill even when native dynamic import could be used. */
force?: boolean
}
export interface ImportStringOptions {
/** The URL to use as a base for imports and exports in the string. */
base?: URL | string
}
const posibleDenoConfigurationFilepaths = [
new URL(`${toFileUrl(Deno.cwd()).href}/deno.json`),
new URL(`${toFileUrl(Deno.cwd()).href}/deno.jsonc`),
] as const
const isDeno = navigator.userAgent === `Deno/${Deno.version.deno}`
const isDenoCLI = isDeno && Deno.run
const isDenoCompiled = isDenoCLI && dirname(Deno.execPath()) === Deno.cwd()
const isDenoDeploy = isDeno && !isDenoCLI && Deno.env.get('DENO_REGION')
const esbuild = isDenoCLI ? esbuildNative : esbuildWASM
const AsyncFunction = async function () {}.constructor
const compilerOptions = isDeno ? await getDenoCompilerOptions() : null
const sharedEsbuildOptions: esbuildWASM.BuildOptions = {
jsx: ({
'preserve': 'preserve',
'react': 'transform',
'react-jsx': 'automatic',
'react-jsxdev': 'automatic',
'react-native': 'preserve',
}[compilerOptions?.jsx ?? 'react'] ??
'transform') as esbuildNative.BuildOptions['jsx'],
jsxDev: compilerOptions?.jsx === 'react-jsxdev',
jsxFactory: compilerOptions?.jsxFactory ?? 'h',
jsxFragment: compilerOptions?.jsxFragmentFactory ?? 'Fragment',
jsxImportSource: compilerOptions?.jsxImportSource,
bundle: true,
platform: 'neutral',
write: false,
logLevel: 'silent',
plugins: [denoPlugin({ useActiveImportMap: true })],
}
async function readTextFile(filepath: URL | string) {
const base = ErrorStackParser.parse(new Error())[1].fileName
const url = new URL(filepath, base)
return await (await fetch(url)).text()
}
async function getDenoCompilerOptions() {
for (const posibleDenoConfigurationFilepath of posibleDenoConfigurationFilepaths) {
try {
return (
(
JSON.parse(
await readTextFile(posibleDenoConfigurationFilepath),
) as DenoConfigurationFile
).compilerOptions ?? null
)
} catch {}
}
return null
}
async function buildAndEvaluate(options: Record<string, unknown>) {
!isDenoCLI && esbuild.initialize({ worker: !!Worker })
const buildResult = await esbuild.build(
Object.assign(options, sharedEsbuildOptions),
)
isDenoCLI && esbuild.stop()
const { text = '' } = buildResult.outputFiles?.[0] ?? {}
const [before, after = '}'] = text.split('export {')
const body =
stripShebang(before).replaceAll('import.meta', '{}') +
'return {' +
after.replaceAll(
/(?<local>\w+) (?:as) (?<exported>\w+)/gi,
'$<exported>: $<local>',
)
return AsyncFunction(body)()
}
export async function importModule(
moduleName: string,
{ force = false }: ImportModuleOptions = {},
): Promise<Module> {
try {
if (force) throw new Error('Forced')
return await import(moduleName)
} catch (error) {
if (!isDenoCompiled && !isDenoDeploy && error.message !== 'Forced')
throw error
const base = ErrorStackParser.parse(new Error())[1].fileName
const entryPoint = resolveModuleSpecifier(moduleName, base, {
useActiveImportMap: true,
})
return await buildAndEvaluate({
entryPoints: [entryPoint],
})
}
}
export async function importString(
moduleString: string,
{
base = ErrorStackParser.parse(new Error())[0].fileName,
}: ImportStringOptions = {},
) {
return await buildAndEvaluate({
stdin: {
contents: moduleString,
loader: 'tsx',
sourcefile: base instanceof URL ? base.href : base,
},
})
}