This repository was archived by the owner on Sep 28, 2025. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathloader.mjs
More file actions
111 lines (107 loc) · 3.68 KB
/
loader.mjs
File metadata and controls
111 lines (107 loc) · 3.68 KB
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
/* eslint-disable no-console */
//@ts-check
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import * as esbuild from "esbuild";
const sourceRoot =
process.env.NODE_V8_COVERAGE && `${pathToFileURL(process.cwd())}/`;
/**
* https://nodejs.org/api/esm.html#resolvespecifier-context-nextresolve
* @typedef {Record<string, unknown>} ImportAssertions
* @typedef {{conditions: Array<string>, importAssertions: ImportAssertions, parentURL?: string}} ResolveContext
* @typedef {{format?: string | null, importAssertions: ImportAssertions, shortCircuit?: boolean, url: string}} ResolveResult
* @param {string} specifier
* @param {ResolveContext} context
* @param {(specifier: string, context: ResolveContext) => ResolveResult | Promise<ResolveResult>} nextResolve
* @returns {ResolveResult | Promise<ResolveResult>}
*/
// export const resolve = (specifier, context, nextResolve) => {
// return nextResolve(specifier, context);
// };
/**
* https://nodejs.org/api/esm.html#loadurl-context-nextload
* @typedef {{conditions: Array<string>, importAssertions: ImportAssertions, format?: string | null}} LoadContext
* @typedef {{format: string, shortCircuit?: boolean, source: string | ArrayBuffer | Uint8Array}} LoadResult
* @param {string} url
* @param {LoadContext} context
* @param {(specifier: string, context: LoadContext) => LoadResult | Promise<LoadResult>} nextLoad
* @returns {Promise<LoadResult>}
*/
export const load = async (url, context, nextLoad) => {
if (url.startsWith("file://")) {
const filePath = fileURLToPath(url);
if (!url.includes("/node_modules/") && /\.m?[tj]sx?$/.test(url)) {
/** @type {esbuild.BuildOptions} */
const options = {
entryPoints: [filePath],
plugins: [markExternalPlugin],
format: "esm",
bundle: true,
write: false,
sourcemap: "inline",
};
if (sourceRoot) {
options.sourceRoot = sourceRoot;
}
const result = await esbuild.build({ ...options, write: false });
return {
format: "module",
shortCircuit: true,
source: result.outputFiles[0].contents,
};
}
if (!/[^/]\.\w+$/.test(url)) {
/**
* Workaround for ERR_UNKNOWN_FILE_EXTENSION
* Read the file here instead of nextLoad if extension is missing.
* @example
* $ NODE_OPTIONS='--experimental-loader=@nlib/tsm' next
* > TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for
* > node_modules/next/dist/bin/next
*/
const source = await fs.readFile(filePath, "utf8");
let format = "commonjs";
if (/^;?\s*(?:import|export)\W/.test(source)) {
format = "module";
}
return { format, shortCircuit: true, source };
}
}
return nextLoad(url, context);
};
/** @type {esbuild.Plugin} */
const markExternalPlugin = {
name: "mark-external",
setup(build) {
/** @param {string} file */
const checkFile = async (file) => (await fs.stat(file)) && file;
/** @param {esbuild.OnResolveArgs} args */
const findImportee = async (args) => {
const resolved = path.resolve(args.resolveDir, args.path);
const results = await Promise.allSettled([
checkFile(resolved),
checkFile(resolved.replace(/js$/, "ts")),
]);
for (const result of results) {
if (result.status === "fulfilled") {
const ext = path.extname(result.value);
return `${args.path.slice(0, -ext.length)}${ext}`;
}
}
return null;
};
build.onResolve({ filter: /./ }, async (args) => {
if (!args.importer) {
return null;
}
if (args.path.startsWith(".")) {
const importee = await findImportee(args);
if (importee) {
return { path: importee, external: true };
}
}
return { path: args.path, external: true };
});
},
};