this repo has no description
at main 8.6 kB view raw
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);