Skip to content

Commit ebb550d

Browse files
committed
Add 'scripts/' from commit '3305750206239255b23949c5ab6560f5f7ee1460'
git-subtree-dir: scripts git-subtree-mainline: 5fd771b git-subtree-split: 3305750
2 parents 5fd771b + 3305750 commit ebb550d

20 files changed

+2242
-0
lines changed

scripts/.eslintrc.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"use strict"
2+
3+
module.exports = {
4+
"extends": "../.eslintrc.js",
5+
"env": {
6+
"browser": null,
7+
"node": true,
8+
"es2022": true,
9+
},
10+
"parserOptions": {
11+
"ecmaVersion": 2022,
12+
},
13+
"rules": {
14+
"no-process-env": "off",
15+
},
16+
};

scripts/_bundler-impl.js

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
"use strict"
2+
3+
const fs = require("fs")
4+
const path = require("path")
5+
const execFileSync = require("child_process").execFileSync
6+
const util = require("util")
7+
8+
const readFile = util.promisify(fs.readFile)
9+
const access = util.promisify(fs.access)
10+
11+
function isFile(filepath) {
12+
return access(filepath).then(() => true, () => false)
13+
}
14+
function escapeRegExp(string) {
15+
return string.replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&")
16+
}
17+
function escapeReplace(string) {
18+
return string.replace(/\$/g, "\\$&")
19+
}
20+
21+
async function resolve(filepath, filename) {
22+
if (filename[0] !== ".") {
23+
// resolve as npm dependency
24+
const packagePath = `./node_modules/${filename}/package.json`
25+
let json, meta
26+
27+
try {
28+
json = await readFile(packagePath, "utf8")
29+
} catch (e) {
30+
meta = {}
31+
}
32+
33+
if (json) {
34+
try {
35+
meta = JSON.parse(json)
36+
}
37+
catch (e) {
38+
throw new Error(`invalid JSON for ${packagePath}: ${json}`)
39+
}
40+
}
41+
42+
const main = `./node_modules/${filename}/${meta.main || `${filename}.js`}`
43+
return path.resolve(await isFile(main) ? main : `./node_modules/${filename}/index.js`)
44+
}
45+
else {
46+
// resolve as local dependency
47+
return path.resolve(path.dirname(filepath), filename + ".js")
48+
}
49+
}
50+
51+
function matchAll(str, regexp) {
52+
regexp.lastIndex = 0
53+
const result = []
54+
let exec
55+
while ((exec = regexp.exec(str)) != null) result.push(exec)
56+
return result
57+
}
58+
59+
let error
60+
module.exports = async (input) => {
61+
const modules = new Map()
62+
const bindings = new Map()
63+
const declaration = /^\s*(?:var|let|const|function)[\t ]+([\w_$]+)/gm
64+
const include = /(?:((?:var|let|const|,|)[\t ]*)([\w_$\.\[\]"'`]+)(\s*=\s*))?require\(([^\)]+)\)(\s*[`\.\(\[])?/gm
65+
let uuid = 0
66+
async function process(filepath, data) {
67+
for (const [, binding] of matchAll(data, declaration)) bindings.set(binding, 0)
68+
69+
const tasks = []
70+
71+
for (const [, def = "", variable = "", eq = "", dep, rest = ""] of matchAll(data, include)) {
72+
tasks.push({filename: JSON.parse(dep), def, variable, eq, rest})
73+
}
74+
75+
const imports = await Promise.all(
76+
tasks.map((t) => resolve(filepath, t.filename))
77+
)
78+
79+
const results = []
80+
for (const [i, task] of tasks.entries()) {
81+
const dependency = imports[i]
82+
let pre = "", def = task.def
83+
if (def[0] === ",") def = "\nvar ", pre = "\n"
84+
const localUUID = uuid // global uuid can update from nested `process` call, ensure same id is used on declaration and consumption
85+
const existingModule = modules.get(dependency)
86+
modules.set(dependency, task.rest ? `_${localUUID}` : task.variable)
87+
const code = await process(
88+
dependency,
89+
pre + (
90+
existingModule == null
91+
? await exportCode(task.filename, dependency, def, task.variable, task.eq, task.rest, localUUID)
92+
: def + task.variable + task.eq + existingModule
93+
)
94+
)
95+
uuid++
96+
results.push(code + task.rest)
97+
}
98+
99+
let i = 0
100+
return data.replace(include, () => results[i++])
101+
}
102+
103+
async function exportCode(filename, filepath, def, variable, eq, rest, uuid) {
104+
let code = await readFile(filepath, "utf-8")
105+
// if there's a syntax error, report w/ proper stack trace
106+
try {
107+
new Function(code)
108+
}
109+
catch (e) {
110+
try {
111+
execFileSync("node", ["--check", filepath], {
112+
stdio: "pipe",
113+
})
114+
}
115+
catch (e) {
116+
if (e.message !== error) {
117+
error = e.message
118+
console.log(`\x1b[31m${e.message}\x1b[0m`)
119+
}
120+
}
121+
}
122+
123+
// disambiguate collisions
124+
const targetPromises = []
125+
code.replace(include, (match, def, variable, eq, dep) => {
126+
targetPromises.push(resolve(filepath, JSON.parse(dep)))
127+
})
128+
129+
const ignoredTargets = await Promise.all(targetPromises)
130+
const ignored = new Set()
131+
132+
for (const target of ignoredTargets) {
133+
const binding = modules.get(target)
134+
if (binding != null) ignored.add(binding)
135+
}
136+
137+
if (new RegExp(`module\\.exports\\s*=\\s*${variable}\s*$`, "m").test(code)) ignored.add(variable)
138+
for (const [binding, count] of bindings) {
139+
if (!ignored.has(binding)) {
140+
const before = code
141+
code = code.replace(
142+
new RegExp(`(\\b)${escapeRegExp(binding)}\\b`, "g"),
143+
escapeReplace(binding) + count
144+
)
145+
if (before !== code) bindings.set(binding, count + 1)
146+
}
147+
}
148+
149+
// fix strings that got mangled by collision disambiguation
150+
const string = /(["'])((?:\\\1|.)*?)(\1)/g
151+
const candidates = Array.from(bindings, ([binding, count]) => escapeRegExp(binding) + (count - 1)).join("|")
152+
const variables = new RegExp(candidates, "g")
153+
code = code.replace(string, (match, open, data, close) => {
154+
const fixed = data.replace(variables, (match) => match.replace(/\d+$/, ""))
155+
return open + fixed + close
156+
})
157+
158+
//fix props
159+
const props = new RegExp(`((?:[^:]\\/\\/.*)?\\.\\s*)(${candidates})|([\\{,]\\s*)(${candidates})(\\s*:)`, "gm")
160+
code = code.replace(props, (match, dot, a, pre, b, post) => {
161+
// Don't do anything because dot was matched in a comment
162+
if (dot && dot.indexOf("//") === 1) return match
163+
if (dot) return dot + a.replace(/\d+$/, "")
164+
return pre + b.replace(/\d+$/, "") + post
165+
})
166+
167+
return code
168+
.replace(/("|')use strict\1;?/gm, "") // remove extraneous "use strict"
169+
.replace(/module\.exports\s*=\s*/gm, escapeReplace(rest ? `var _${uuid}` + eq : def + (rest ? "_" : "") + variable + eq)) // export
170+
+ (rest ? `\n${def}${variable}${eq}_${uuid}` : "") // if `rest` is truthy, it means the expression is fluent or higher-order (e.g. require(path).foo or require(path)(foo)
171+
}
172+
173+
const code = ";(function() {\n" +
174+
(await process(path.resolve(input), await readFile(input, "utf-8")))
175+
.replace(/^\s*((?:var|let|const|)[\t ]*)([\w_$\.]+)(\s*=\s*)(\2)(?=[\s]+(\w)|;|$)/gm, "") // remove assignments to self
176+
.replace(/;+(\r|\n|$)/g, ";$1") // remove redundant semicolons
177+
.replace(/(\r|\n)+/g, "\n").replace(/(\r|\n)$/, "") + // remove multiline breaks
178+
"\n}());"
179+
180+
//try {new Function(code); console.log(`build completed at ${new Date()}`)} catch (e) {}
181+
error = null
182+
return code
183+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"use strict"
2+
3+
process.on("unhandledRejection", (e) => {
4+
process.exitCode = 1
5+
6+
if (!e.stdout || !e.stderr) throw e
7+
8+
console.error(e.stack)
9+
10+
if (e.stdout?.length) {
11+
console.error(e.stdout.toString("utf-8"))
12+
}
13+
14+
if (e.stderr?.length) {
15+
console.error(e.stderr.toString("utf-8"))
16+
}
17+
18+
// eslint-disable-next-line no-process-exit
19+
process.exit()
20+
})

0 commit comments

Comments
 (0)