321 lines
8.6 kB
1
/* eslint-disable no-console */
2
import * as esbuild from "esbuild";
3
import copyStaticFiles from "esbuild-copy-static-files";
4
5
import path from "path";
6
import fs from "fs";
7
8
const config = {
9
injector: "packages/injector/src/index.ts",
10
"node-preload": "packages/node-preload/src/index.ts",
11
"web-preload": "packages/web-preload/src/index.ts"
12
};
13
14
const prod = process.env.NODE_ENV === "production";
15
const watch = process.argv.includes("--watch");
16
const browser = process.argv.includes("--browser");
17
const mv2 = process.argv.includes("--mv2");
18
const clean = process.argv.includes("--clean");
19
20
const buildBranch = process.env.MOONLIGHT_BRANCH ?? "dev";
21
const buildVersion = process.env.MOONLIGHT_VERSION ?? "dev";
22
23
const external = [
24
"electron",
25
"fs",
26
"path",
27
"module",
28
"discord", // mappings
29
30
// Silence an esbuild warning
31
"./node-preload.js"
32
];
33
34
let lastMessages = new Set();
35
/** @type {import("esbuild").Plugin} */
36
const deduplicatedLogging = {
37
name: "deduplicated-logging",
38
setup(build) {
39
build.onStart(() => {
40
lastMessages.clear();
41
});
42
43
build.onEnd(async (result) => {
44
const formatted = await Promise.all([
45
esbuild.formatMessages(result.warnings, {
46
kind: "warning",
47
color: true
48
}),
49
esbuild.formatMessages(result.errors, { kind: "error", color: true })
50
]).then((a) => a.flat());
51
52
// console.log(formatted);
53
for (const message of formatted) {
54
if (lastMessages.has(message)) continue;
55
lastMessages.add(message);
56
console.log(message.trim());
57
}
58
});
59
}
60
};
61
62
const timeFormatter = new Intl.DateTimeFormat(undefined, {
63
hour: "numeric",
64
minute: "numeric",
65
second: "numeric",
66
hour12: false
67
});
68
/** @type {import("esbuild").Plugin} */
69
const taggedBuildLog = (tag) => ({
70
name: "build-log",
71
setup(build) {
72
build.onEnd((result) => {
73
console.log(`[${timeFormatter.format(new Date())}] [${tag}] build finished`);
74
});
75
}
76
});
77
78
async function build(name, entry) {
79
let outfile = path.join("./dist", name + ".js");
80
const browserDir = mv2 ? "browser-mv2" : "browser";
81
if (name === "browser") outfile = path.join("./dist", browserDir, "index.js");
82
83
const dropLabels = [];
84
const labels = {
85
injector: ["injector"],
86
nodePreload: ["node-preload"],
87
webPreload: ["web-preload"],
88
browser: ["browser"],
89
90
webTarget: ["web-preload", "browser"],
91
nodeTarget: ["node-preload", "injector"]
92
};
93
for (const [label, targets] of Object.entries(labels)) {
94
if (!targets.includes(name)) {
95
dropLabels.push(label);
96
}
97
}
98
99
const define = {
100
MOONLIGHT_ENV: `"${name}"`,
101
MOONLIGHT_PROD: prod.toString(),
102
MOONLIGHT_BRANCH: `"${buildBranch}"`,
103
MOONLIGHT_VERSION: `"${buildVersion}"`
104
};
105
106
for (const iterName of ["injector", "node-preload", "web-preload", "browser"]) {
107
const snake = iterName.replace(/-/g, "_").toUpperCase();
108
define[`MOONLIGHT_${snake}`] = (name === iterName).toString();
109
}
110
111
const nodeDependencies = ["glob"];
112
const ignoredExternal = name === "web-preload" ? nodeDependencies : [];
113
114
const plugins = [deduplicatedLogging, taggedBuildLog(name)];
115
if (name === "browser") {
116
plugins.push(
117
copyStaticFiles({
118
src: mv2 ? "./packages/browser/manifestv2.json" : "./packages/browser/manifest.json",
119
dest: `./dist/${browserDir}/manifest.json`
120
})
121
);
122
123
if (!mv2) {
124
plugins.push(
125
copyStaticFiles({
126
src: "./packages/browser/modifyResponseHeaders.json",
127
dest: `./dist/${browserDir}/modifyResponseHeaders.json`
128
})
129
);
130
plugins.push(
131
copyStaticFiles({
132
src: "./packages/browser/blockLoading.json",
133
dest: `./dist/${browserDir}/blockLoading.json`
134
})
135
);
136
}
137
138
plugins.push(
139
copyStaticFiles({
140
src: mv2 ? "./packages/browser/src/background-mv2.js" : "./packages/browser/src/background.js",
141
dest: `./dist/${browserDir}/background.js`
142
})
143
);
144
}
145
146
/** @type {import("esbuild").BuildOptions} */
147
const esbuildConfig = {
148
entryPoints: [entry],
149
outfile,
150
151
format: "iife",
152
globalName: "module.exports",
153
154
platform: ["web-preload", "browser"].includes(name) ? "browser" : "node",
155
156
treeShaking: true,
157
bundle: true,
158
minify: prod,
159
sourcemap: "inline",
160
161
external: [...ignoredExternal, ...external],
162
163
define,
164
dropLabels,
165
166
logLevel: "silent",
167
plugins,
168
169
// https://github.com/evanw/esbuild/issues/3944
170
footer:
171
name === "web-preload"
172
? {
173
js: `\n//# sourceURL=${name}.js`
174
}
175
: undefined
176
};
177
178
if (name === "browser") {
179
const coreExtensionsJson = {};
180
181
function readDir(dir) {
182
const files = fs.readdirSync(dir);
183
for (const file of files) {
184
const filePath = dir + "/" + file;
185
const normalizedPath = filePath.replace("./dist/core-extensions/", "");
186
if (fs.statSync(filePath).isDirectory()) {
187
readDir(filePath);
188
} else {
189
coreExtensionsJson[normalizedPath] = fs.readFileSync(filePath, "utf8");
190
}
191
}
192
}
193
194
readDir("./dist/core-extensions");
195
196
esbuildConfig.banner = {
197
js: `window._moonlight_coreExtensionsStr = ${JSON.stringify(JSON.stringify(coreExtensionsJson))};`
198
};
199
}
200
201
if (watch) {
202
const ctx = await esbuild.context(esbuildConfig);
203
await ctx.watch();
204
} else {
205
await esbuild.build(esbuildConfig);
206
}
207
}
208
209
async function buildExt(ext, side, fileExt) {
210
const outdir = path.join("./dist", "core-extensions", ext);
211
if (!fs.existsSync(outdir)) {
212
fs.mkdirSync(outdir, { recursive: true });
213
}
214
215
const entryPoints = [`packages/core-extensions/src/${ext}/${side}.${fileExt}`];
216
217
const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`;
218
if (fs.existsSync(wpModulesDir) && side === "index") {
219
const wpModules = fs.opendirSync(wpModulesDir);
220
for await (const wpModule of wpModules) {
221
if (wpModule.isFile()) {
222
entryPoints.push(`packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}`);
223
} else {
224
for (const fileExt of ["ts", "tsx"]) {
225
const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`;
226
if (fs.existsSync(path)) {
227
entryPoints.push({
228
in: path,
229
out: `webpackModules/${wpModule.name}`
230
});
231
}
232
}
233
}
234
}
235
}
236
237
const wpImportPlugin = {
238
name: "webpackImports",
239
setup(build) {
240
build.onResolve({ filter: /^@moonlight-mod\/wp\// }, (args) => {
241
const wpModule = args.path.replace(/^@moonlight-mod\/wp\//, "");
242
return {
243
path: wpModule,
244
external: true
245
};
246
});
247
}
248
};
249
250
const styleInput = `packages/core-extensions/src/${ext}/style.css`;
251
const styleOutput = `dist/core-extensions/${ext}/style.css`;
252
253
const esbuildConfig = {
254
entryPoints,
255
outdir,
256
257
format: "iife",
258
globalName: "module.exports",
259
platform: "node",
260
261
treeShaking: true,
262
bundle: true,
263
sourcemap: prod ? false : "inline",
264
265
external,
266
267
logOverride: {
268
"commonjs-variable-in-esm": "verbose"
269
},
270
logLevel: "silent",
271
plugins: [
272
copyStaticFiles({
273
src: `./packages/core-extensions/src/${ext}/manifest.json`,
274
dest: `./dist/core-extensions/${ext}/manifest.json`
275
}),
276
...(fs.existsSync(styleInput)
277
? [
278
copyStaticFiles({
279
src: styleInput,
280
dest: styleOutput
281
})
282
]
283
: []),
284
wpImportPlugin,
285
deduplicatedLogging,
286
taggedBuildLog(`ext/${ext}`)
287
]
288
};
289
290
if (watch) {
291
const ctx = await esbuild.context(esbuildConfig);
292
await ctx.watch();
293
} else {
294
await esbuild.build(esbuildConfig);
295
}
296
}
297
298
const promises = [];
299
300
if (clean) {
301
fs.rmSync("./dist", { recursive: true, force: true });
302
} else if (browser) {
303
build("browser", "packages/browser/src/index.ts");
304
} else {
305
for (const [name, entry] of Object.entries(config)) {
306
promises.push(build(name, entry));
307
}
308
309
const coreExtensions = fs.readdirSync("./packages/core-extensions/src");
310
for (const ext of coreExtensions) {
311
for (const fileExt of ["ts", "tsx"]) {
312
for (const type of ["index", "node", "host"]) {
313
if (fs.existsSync(`./packages/core-extensions/src/${ext}/${type}.${fileExt}`)) {
314
promises.push(buildExt(ext, type, fileExt));
315
}
316
}
317
}
318
}
319
}
320
321
await Promise.all(promises);
322