-
Notifications
You must be signed in to change notification settings - Fork 39
/
Copy pathbuild.mjs
148 lines (130 loc) · 3.8 KB
/
build.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
/* Horrifying fixer-upper for ESM imports */
import * as fs from "fs";
import {existsSync, promises as fsp, readFileSync} from "fs";
import * as path from "path";
const DIST = path.join(process.cwd(), "dist");
const NODE_MODULES = path.join(process.cwd(), "node_modules");
build();
async function build() {
for (const type of ["esm", "cjs"]) {
const dir = path.join(DIST, type);
const extn = type === "esm" ? "mjs" : "cjs";
// rename files first
await walkDir(dir, async filename => {
if (!filename.endsWith(".js"))
return;
await renameExtension(filename, extn);
});
// now fix imports
await walkDir(dir, async filename => {
if (!filename.endsWith(`.${extn}`))
return;
await fixImports(filename, type);
});
}
}
/**
* Recursively walk a directory
* @param {string} dirname Name of directory to walk
* @param {(filename: string) => Promise<void>} callback Callback
*/
async function walkDir(dirname, callback) {
const files = (await fsp.readdir(dirname)).map(filename => path.join(dirname, filename));
/* first rename all files */
await Promise.all(files.map(async filename => {
const stat = await fsp.stat(filename);
if (stat.isDirectory()) {
return walkDir(filename, callback);
}
await callback(filename);
}));
}
/**
* Add extensions to relative imports
* @param {string} filename File to operate on.
* @param {"esm" | "cjs"} type Javascript module system in use.
*/
async function fixImports(filename, type = "esm") {
let content = await fsp.readFile(filename, "utf8");
const regex = (type === "esm" ?
/^((?:ex|im)port .+? from\s+)(["'])(.+?)(\2;?)$/gm :
/(require\()(['"])(.+?)(\2\))/gm
);
content = content.replaceAll(regex, (match, head, q, name, tail) => {
// already has extension
if (name.match(/\.[cm]?js$/)) {
return match;
}
// relative imports
if (name.startsWith(".")) {
// figure out which file it's referring to
const target = findExtension(path.dirname(filename), name);
return (head + q + target + tail);
} else {
try {
const json = JSON.parse(readFileSync(findPackageJson(name), "utf8"));
if (json.exports) {
// ARGH
if (name === "react/jsx-runtime" && type === "esm") {
return head + q + "react/jsx-runtime.js" + tail;
}
} else {
}
} catch (e) {
console.error(e);
}
}
return match;
});
await fsp.writeFile(filename, content);
}
/**
* Find extension
*/
function findExtension(pathname, relative) {
const filename = path.resolve(pathname, relative);
for (const extn of ["mjs", "js", "cjs"]) {
const full = filename + "." + extn;
if (existsSync(full)) {
let rewrite = path.relative(pathname, full);
if (!rewrite.startsWith(".")) {
rewrite = "./" + rewrite;
}
return rewrite;
}
}
throw new Error(`Could not resolve ${filename}`);
}
/**
* Find package.json
* @param {string} name Name of package.
*/
function findPackageJson(name) {
const packageName = getPackageName(name);
let dirname = NODE_MODULES;
while (true) {
const filename = path.join(dirname, packageName, "package.json");
if (fs.existsSync(filename))
return filename;
dirname = path.normalize(path.join(dirname, ".."));
if (dirname === "/")
throw new Error(`Could not find package.json for ${name}`);
}
}
/**
* Get name of NPM package
* @param {string} name Import string to resolve.
*/
function getPackageName(name) {
const parts = name.split("/");
if (name.startsWith("@")) {
return parts.slice(0, 2).join("/");
}
return parts[0];
}
/**
* Change file extension
*/
async function renameExtension(filename, extn = "mjs") {
await fsp.rename(filename, filename.replace(/\.js$/, `.${extn}`));
}