-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhooks.mjs
181 lines (155 loc) · 5.26 KB
/
hooks.mjs
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/**
* Benjamin's ESbuild loader for TypeScript (BESTS)
*
* @author Benjamin Gwynn <[email protected]>
* @format
*/
import esbuild from "esbuild"
import path from "node:path"
import url from "node:url"
import fs from "fs"
import fsp from "fs/promises"
import {getTsconfig} from "get-tsconfig"
const print = (...args) => console.error("\x1b[36m" + "bests\t" + "\x1b[0m", ...args)
const debug = process.env.DEBUG?.includes("bests") ? print : () => {}
// useful for debugging async timings
// const tick = () => {
// return new Promise((resolve) => {
// setTimeout(() => {
// resolve()
// }, 1)
// })
// }
function findTS(specifier) {
if (!specifier) return null
const ts = getTsconfig(specifier)
if (!ts) return null
// debug(specifier, "->", ts.path)
let root = path.join(ts.path, "..")
if (ts.config.compilerOptions.rootDirs) {
console.warn("[warn] compilerOptions.rootDirs is not supported/implemented")
}
if (ts.config.compilerOptions.baseUrl) {
console.warn('[warn] compilerOptions.baseUrl is not supported/implemented. @ "' + ts.path + '" from "' + specifier + '"')
}
if (ts.config.compilerOptions.rootDir) {
root = path.join(root, ts.config.compilerOptions.rootDir)
}
return {
tsconfig: ts.config,
root,
}
}
/** @public Receives data from `register`. */
export async function initialize() {}
/** @public Take an `import` or `require` specifier and resolve it to a URL. */
export async function resolve(specifier, context, nextResolve) {
// if we're loading from somewhere
if (context?.parentURL?.startsWith("file://")) {
const parentPath = url.fileURLToPath(context.parentURL)
// if explicitly not a TS file and in node modules, do nothing
if (path.extname(parentPath) !== ".ts" && parentPath.includes("/node_modules/")) {
debug("[*!*]", "ignoring:", parentPath)
return nextResolve(specifier)
}
const ts = findTS(parentPath)
// and we're loading from a typescript location
if (ts) {
debug("[...]", context.parentURL, "is trying to load", specifier, "@", ts.root)
const {tsconfig, root} = ts
// resolves `paths` from tsconfig to ts files for the loader
// baseURL is intentionally ignored, don't use this option.
if (tsconfig.compilerOptions.paths) {
for (const [importName, sources] of Object.entries(tsconfig.compilerOptions.paths)) {
if (specifier === importName) {
for (const source of sources) {
const maybePath = path.join(root, source)
try {
await fsp.access(maybePath, fs.constants.ROK)
} catch (err) {
debug("[!!!] error accessing", maybePath, err)
continue
}
const asUrl = url.pathToFileURL(maybePath).href
return {
shortCircuit: true,
url: asUrl,
}
}
} else if (importName.endsWith("/*")) {
const here = importName.substring(0, importName.length - 1)
if (specifier.startsWith(here)) {
const rel = path.relative(here, specifier)
for (const source of sources) {
const maybePath = path.join(root, source.substring(0, source.length - 1), rel)
if (!source.endsWith("/*")) throw new Error("Expected source to end in /*")
try {
await fsp.access(maybePath, fs.constants.ROK)
} catch (err) {
debug("[!!!] error accessing", maybePath, err)
continue
}
const asUrl = url.pathToFileURL(maybePath).href
return {
shortCircuit: true,
url: asUrl,
}
}
}
}
}
}
// if the file looks like its relative, and we don't have an extension, try loading a `.ts` file
if (specifier && specifier.startsWith("./") && path.extname(specifier) === "") {
const specifier2 = path.join(parentPath, "..", specifier + ".ts")
try {
debug("[???] test access", specifier2)
await fsp.access(specifier2, fs.constants.ROK)
debug("[ok!] can access", specifier2)
const asUrl = url.pathToFileURL(specifier2).href
return {
shortCircuit: true,
url: asUrl,
}
} catch (err) {
debug("[[[!!!]]] Cannot access", specifier2, err)
}
}
} else {
debug("[!!!]", "found no ts root for this ts file:", parentPath)
}
}
// debug("nextResolve:", specifier)
//await tick()
return nextResolve(specifier)
}
/** @public Take a resolved URL and return the source code to be evaluated. */
export async function load(resolvedUrl, context, nextLoad) {
if (resolvedUrl.startsWith("file://")) {
const resolvedPath = url.fileURLToPath(resolvedUrl)
if (path.extname(resolvedPath) === ".ts") {
debug("transforming ts file:", resolvedPath, "...")
// debug(">>>>> OVERRIDE", resolvedPath)
const tsRoot = findTS(resolvedPath)
const tsconfig = tsRoot ? tsRoot.tsconfig : undefined
if (!tsconfig) {
console.warn("[warn] the following TS file has no associated tsconfig:", resolvedPath)
}
const buffer = await fsp.readFile(resolvedPath)
const output = await esbuild.transform(buffer, {
sourcemap: process.execArgv.includes("--enable-source-maps") ? "inline" : false,
tsconfigRaw: tsconfig,
sourcefile: resolvedPath,
platform: "node",
loader: "ts",
})
debug("transformed ts file for:", resolvedPath)
return {
format: "module",
shortCircuit: true,
source: output.code,
}
}
}
return nextLoad(resolvedUrl, context)
}