this repo has no description
1/* eslint-disable no-console */
2import * as esbuild from "esbuild";
3import copyStaticFiles from "esbuild-copy-static-files";
4
5import path from "path";
6import fs from "fs";
7
8const 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
14const prod = process.env.NODE_ENV === "production";
15const watch = process.argv.includes("--watch");
16const browser = process.argv.includes("--browser");
17const mv2 = process.argv.includes("--mv2");
18const clean = process.argv.includes("--clean");
19
20const buildBranch = process.env.MOONLIGHT_BRANCH ?? "dev";
21const buildVersion = process.env.MOONLIGHT_VERSION ?? "dev";
22
23const external = [
24 "electron",
25 "fs",
26 "path",
27 "module",
28 "discord", // mappings
29
30 // Silence an esbuild warning
31 "./node-preload.js"
32];
33
34let lastMessages = new Set();
35/** @type {import("esbuild").Plugin} */
36const 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
62const timeFormatter = new Intl.DateTimeFormat(undefined, {
63 hour: "numeric",
64 minute: "numeric",
65 second: "numeric",
66 hour12: false
67});
68/** @type {import("esbuild").Plugin} */
69const 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
78async 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
209async 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
298const promises = [];
299
300if (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
321await Promise.all(promises);