From 01ae0983b378ca478e7b6891e183718e8c45ed02 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sun, 16 Oct 2022 17:15:15 +0200 Subject: [PATCH] Optimise Web via treeshaking, cleanup build scripts --- build.mjs | 124 -------------------------------- buildWeb.mjs | 110 ---------------------------- package.json | 6 +- scripts/build/build.mjs | 53 ++++++++++++++ scripts/build/buildWeb.mjs | 61 ++++++++++++++++ scripts/build/common.mjs | 80 +++++++++++++++++++++ src/Vencord.ts | 38 +++++----- src/components/Settings.tsx | 4 +- src/components/Updater.tsx | 6 +- src/globals.d.ts | 20 ++++++ src/plugins/clickableRoleDot.ts | 11 ++- src/plugins/noRPC.ts | 1 + src/plugins/noSystemBadge.ts | 1 + src/plugins/settings.ts | 15 ++-- 14 files changed, 261 insertions(+), 269 deletions(-) delete mode 100755 build.mjs delete mode 100644 buildWeb.mjs create mode 100755 scripts/build/build.mjs create mode 100644 scripts/build/buildWeb.mjs create mode 100644 scripts/build/common.mjs diff --git a/build.mjs b/build.mjs deleted file mode 100755 index e5797865..00000000 --- a/build.mjs +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/node -import { execSync } from "child_process"; -import esbuild from "esbuild"; -import { readdirSync } from "fs"; - -/** - * @type {esbuild.WatchMode|false} - */ -const watch = process.argv.includes("--watch"); - -// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294 -/** - * @type {esbuild.Plugin} - */ -const makeAllPackagesExternalPlugin = { - name: "make-all-packages-external", - setup(build) { - let filter = /^[^.\/]|^\.[^.\/]|^\.\.[^\/]/; // Must not start with "/" or "./" or "../" - build.onResolve({ filter }, args => ({ path: args.path, external: true })); - }, -}; - -/** - * @type {esbuild.Plugin} - */ -const globPlugins = { - name: "glob-plugins", - setup: build => { - build.onResolve({ filter: /^plugins$/ }, args => { - return { - namespace: "import-plugins", - path: args.path - }; - }); - - build.onLoad({ filter: /^plugins$/, namespace: "import-plugins" }, () => { - const files = readdirSync("./src/plugins"); - let code = ""; - let obj = ""; - for (let i = 0; i < files.length; i++) { - if (files[i] === "index.ts") { - continue; - } - const mod = `__pluginMod${i}`; - code += `import ${mod} from "./${files[i].replace(/.tsx?$/, "")}";\n`; - obj += `[${mod}.name]: ${mod},`; - } - code += `export default {${obj}}`; - return { - contents: code, - resolveDir: "./src/plugins" - }; - }); - } -}; - -const gitHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim(); -/** - * @type {esbuild.Plugin} - */ -const gitHashPlugin = { - name: "git-hash-plugin", - setup: build => { - const filter = /^git-hash$/; - build.onResolve({ filter }, args => ({ - namespace: "git-hash", path: args.path - })); - build.onLoad({ filter, namespace: "git-hash" }, () => ({ - contents: `export default "${gitHash}"` - })); - } -}; - -await Promise.all([ - esbuild.build({ - logLevel: "info", - entryPoints: ["src/preload.ts"], - outfile: "dist/preload.js", - format: "cjs", - bundle: true, - platform: "node", - target: ["esnext"], - sourcemap: "linked", - plugins: [makeAllPackagesExternalPlugin], - watch - }), - esbuild.build({ - logLevel: "info", - entryPoints: ["src/patcher.ts"], - outfile: "dist/patcher.js", - bundle: true, - format: "cjs", - target: ["esnext"], - external: ["electron"], - platform: "node", - sourcemap: "linked", - plugins: [makeAllPackagesExternalPlugin], - watch - }), - esbuild.build({ - logLevel: "info", - entryPoints: ["src/Vencord.ts"], - outfile: "dist/renderer.js", - format: "iife", - bundle: true, - target: ["esnext"], - footer: { js: "//# sourceURL=VencordRenderer" }, - globalName: "Vencord", - external: ["plugins", "git-hash"], - plugins: [ - globPlugins, - gitHashPlugin - ], - sourcemap: false, - watch, - minify: true, - }), -]).catch(err => { - console.error("Build failed"); - console.error(err.message); - // make ci fail - if (!watch) - process.exitCode = 1; -}); diff --git a/buildWeb.mjs b/buildWeb.mjs deleted file mode 100644 index 0bd6618b..00000000 --- a/buildWeb.mjs +++ /dev/null @@ -1,110 +0,0 @@ -// TODO: Modularise the plugins since both build scripts use them - -import { execSync } from "child_process"; -import { createWriteStream, readdirSync, readFileSync } from "fs"; -import yazl from "yazl"; -import esbuild from "esbuild"; -// wtf is this assert syntax -import PackageJSON from "./package.json" assert { type: "json" }; - -/** - * @type {esbuild.Plugin} - */ -const globPlugins = { - name: "glob-plugins", - setup: build => { - build.onResolve({ filter: /^plugins$/ }, args => { - return { - namespace: "import-plugins", - path: args.path - }; - }); - - build.onLoad({ filter: /^plugins$/, namespace: "import-plugins" }, () => { - const files = readdirSync("./src/plugins"); - let code = ""; - let obj = ""; - for (let i = 0; i < files.length; i++) { - if (files[i] === "index.ts") { - continue; - } - const mod = `__pluginMod${i}`; - code += `import ${mod} from "./${files[i].replace(/.tsx?$/, "")}";\n`; - obj += `[${mod}.name]: ${mod},`; - } - code += `export default {${obj}}`; - return { - contents: code, - resolveDir: "./src/plugins" - }; - }); - } -}; - -const gitHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim(); -/** - * @type {esbuild.Plugin} - */ -const gitHashPlugin = { - name: "git-hash-plugin", - setup: build => { - const filter = /^git-hash$/; - build.onResolve({ filter }, args => ({ - namespace: "git-hash", path: args.path - })); - build.onLoad({ filter, namespace: "git-hash" }, () => ({ - contents: `export default "${gitHash}"` - })); - } -}; - -/** - * @type {esbuild.BuildOptions} - */ -const commonOptions = { - logLevel: "info", - entryPoints: ["browser/Vencord.ts"], - globalName: "Vencord", - format: "iife", - bundle: true, - minify: true, - sourcemap: false, - external: ["plugins", "git-hash"], - plugins: [ - globPlugins, - gitHashPlugin - ], - target: ["esnext"], -}; - -await Promise.all( - [ - esbuild.build({ - ...commonOptions, - outfile: "dist/browser.js", - footer: { js: "//# sourceURL=VencordWeb" }, - }), - esbuild.build({ - ...commonOptions, - outfile: "dist/Vencord.user.js", - banner: { - js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", PackageJSON.version) - }, - footer: { - // UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local - js: "Object.defineProperty(window,'Vencord',{get:()=>Vencord});" - }, - }) - ] -); - -const zip = new yazl.ZipFile(); -zip.outputStream.pipe(createWriteStream("dist/extension.zip")).on("close", () => { - console.info("Extension written to dist/extension.zip"); -}); - -zip.addFile("dist/browser.js", "dist/Vencord.js"); -["background.js", "content.js", "manifest.json"].forEach(f => { - zip.addFile(`browser/${f}`, `${f}`); -}); -zip.end(); diff --git a/package.json b/package.json index 48734b3d..711fde14 100644 --- a/package.json +++ b/package.json @@ -18,15 +18,15 @@ "doc": "docs" }, "scripts": { - "build": "node build.mjs", - "buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js buildWeb.mjs", + "build": "node scripts/build/build.mjs", + "buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs", "inject": "node scripts/patcher/install.js", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "lint:fix": "pnpm lint --fix", "test": "pnpm lint && pnpm build && pnpm testTsc", "testTsc": "tsc --noEmit", "uninject": "node scripts/patcher/uninstall.js", - "watch": "node build.mjs --watch" + "watch": "node scripts/build/build.mjs --watch" }, "dependencies": { "console-menu": "^0.1.0", diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs new file mode 100755 index 00000000..281246af --- /dev/null +++ b/scripts/build/build.mjs @@ -0,0 +1,53 @@ +#!/usr/bin/node +import esbuild from "esbuild"; +import { commonOpts, gitHashPlugin, globPlugins, makeAllPackagesExternalPlugin } from "./common.mjs"; + +/** + * @type {esbuild.BuildOptions} + */ +const nodeCommonOpts = { + ...commonOpts, + format: "cjs", + platform: "node", + target: ["esnext"], + sourcemap: "linked", + plugins: [makeAllPackagesExternalPlugin], +}; + +await Promise.all([ + esbuild.build({ + ...nodeCommonOpts, + entryPoints: ["src/preload.ts"], + outfile: "dist/preload.js", + }), + esbuild.build({ + ...nodeCommonOpts, + entryPoints: ["src/patcher.ts"], + outfile: "dist/patcher.js", + }), + esbuild.build({ + ...commonOpts, + entryPoints: ["src/Vencord.ts"], + outfile: "dist/renderer.js", + format: "iife", + target: ["esnext"], + footer: { js: "//# sourceURL=VencordRenderer" }, + globalName: "Vencord", + external: ["plugins", "git-hash"], + plugins: [ + globPlugins, + gitHashPlugin + ], + sourcemap: "inline", + minify: true, + define: { + IS_WEB: "false" + } + }), +]).catch(err => { + console.error("Build failed"); + console.error(err.message); + // make ci fail + if (!watch) + process.exitCode = 1; +}); diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs new file mode 100644 index 00000000..dd8cb5df --- /dev/null +++ b/scripts/build/buildWeb.mjs @@ -0,0 +1,61 @@ +// TODO: Modularise the plugins since both build scripts use them + +import { createWriteStream, readFileSync } from "fs"; +import yazl from "yazl"; +import esbuild from "esbuild"; +// wtf is this assert syntax +import PackageJSON from "../../package.json" assert { type: "json" }; +import { commonOpts, gitHashPlugin, globPlugins } from "./common.mjs"; + +/** + * @type {esbuild.BuildOptions} + */ +const commonOptions = { + ...commonOpts, + entryPoints: ["browser/Vencord.ts"], + globalName: "Vencord", + format: "iife", + minify: true, + sourcemap: false, + external: ["plugins", "git-hash"], + plugins: [ + globPlugins, + gitHashPlugin + ], + target: ["esnext"], + define: { + IS_WEB: "true" + } +}; + +await Promise.all( + [ + esbuild.build({ + ...commonOptions, + outfile: "dist/browser.js", + footer: { js: "//# sourceURL=VencordWeb" }, + }), + esbuild.build({ + ...commonOptions, + outfile: "dist/Vencord.user.js", + banner: { + js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", PackageJSON.version) + }, + footer: { + // UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local + js: "Object.defineProperty(window,'Vencord',{get:()=>Vencord});" + }, + }) + ] +); + +const zip = new yazl.ZipFile(); +zip.outputStream.pipe(createWriteStream("dist/extension.zip")).on("close", () => { + console.info("Extension written to dist/extension.zip"); +}); + +zip.addFile("dist/browser.js", "dist/Vencord.js"); +["background.js", "content.js", "manifest.json"].forEach(f => { + zip.addFile(`browser/${f}`, `${f}`); +}); +zip.end(); diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs new file mode 100644 index 00000000..d9b2878d --- /dev/null +++ b/scripts/build/common.mjs @@ -0,0 +1,80 @@ +import { execSync } from "child_process"; +import esbuild from "esbuild"; +import { readdir } from "fs/promises"; + +/** + * @type {esbuild.WatchMode|false} + */ +export const watch = process.argv.includes("--watch"); + +/** + * @type {esbuild.BuildOptions} + */ +export const commonOpts = { + logLevel: "info", + bundle: true, + watch +}; + +// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294 +/** + * @type {esbuild.Plugin} + */ +export const makeAllPackagesExternalPlugin = { + name: "make-all-packages-external", + setup(build) { + let filter = /^[^.\/]|^\.[^.\/]|^\.\.[^\/]/; // Must not start with "/" or "./" or "../" + build.onResolve({ filter }, args => ({ path: args.path, external: true })); + }, +}; + +/** + * @type {esbuild.Plugin} + */ +export const globPlugins = { + name: "glob-plugins", + setup: build => { + build.onResolve({ filter: /^plugins$/ }, args => { + return { + namespace: "import-plugins", + path: args.path + }; + }); + + build.onLoad({ filter: /^plugins$/, namespace: "import-plugins" }, async () => { + const files = await readdir("./src/plugins"); + let code = ""; + let plugins = "\n"; + for (let i = 0; i < files.length; i++) { + if (files[i] === "index.ts") { + continue; + } + const mod = `p${i}`; + code += `import ${mod} from "./${files[i].replace(/.tsx?$/, "")}";\n`; + plugins += `[${mod}.name]:${mod},\n`; + } + code += `export default {${plugins}};`; + return { + contents: code, + resolveDir: "./src/plugins" + }; + }); + } +}; + +const gitHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim(); +/** + * @type {esbuild.Plugin} + */ +export const gitHashPlugin = { + name: "git-hash-plugin", + setup: build => { + const filter = /^git-hash$/; + build.onResolve({ filter }, args => ({ + namespace: "git-hash", path: args.path + })); + build.onLoad({ filter, namespace: "git-hash" }, () => ({ + contents: `export default "${gitHash}"` + })); + } +}; diff --git a/src/Vencord.ts b/src/Vencord.ts index 48006210..659d032b 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -17,12 +17,6 @@ import { checkForUpdates, UpdateLogger } from "./utils/updater"; import { onceReady } from "./webpack"; import { Router } from "./webpack/common"; -Object.defineProperty(window, "IS_WEB", { - get: () => !window.DiscordNative, - configurable: true, - enumerable: true -}); - export let Components: any; async function init() { @@ -30,21 +24,23 @@ async function init() { startAllPlugins(); Components = await import("./components"); - try { - const isOutdated = await checkForUpdates(); - if (isOutdated && Settings.notifyAboutUpdates) - setTimeout(() => { - showNotice( - "A Vencord update is available!", - "View Update", - () => { - popNotice(); - Router.open("VencordUpdater"); - } - ); - }, 10000); - } catch (err) { - UpdateLogger.error("Failed to check for updates", err); + if (!IS_WEB) { + try { + const isOutdated = await checkForUpdates(); + if (isOutdated && Settings.notifyAboutUpdates) + setTimeout(() => { + showNotice( + "A Vencord update is available!", + "View Update", + () => { + popNotice(); + Router.open("VencordUpdater"); + } + ); + }, 10000); + } catch (err) { + UpdateLogger.error("Failed to check for updates", err); + } } } diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 6c5b5014..2a2cc5d8 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -72,7 +72,7 @@ export default ErrorBoundary.wrap(function Settings() { SettingsDir: {settingsDir} - {!IS_WEB && + {!IS_WEB && } + - Settings settings.useQuickCss = v} diff --git a/src/components/Updater.tsx b/src/components/Updater.tsx index 31060b48..153d5e56 100644 --- a/src/components/Updater.tsx +++ b/src/components/Updater.tsx @@ -158,7 +158,7 @@ function Newer(props: CommonProps) { ); } -export default ErrorBoundary.wrap(function Updater() { +function Updater() { const [repo, err, repoPending] = useAwaiter(getRepo, "Loading..."); React.useEffect(() => { @@ -188,4 +188,6 @@ export default ErrorBoundary.wrap(function Updater() { {isNewer ? : } ); -}); +} + +export default IS_WEB ? null : ErrorBoundary.wrap(Updater); diff --git a/src/globals.d.ts b/src/globals.d.ts index 4320e1c6..72b0b282 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -1,10 +1,30 @@ declare global { + /** + * This exists only at build time, so references to it in patches should insert it + * via String interpolation OR use different replacement code based on this + * but NEVER refrence it inside the patched code + * + * @example + * // BAD + * replace: "IS_WEB?foo:bar" + * // GOOD + * replace: IS_WEB ? "foo" : "bar" + * // also good + * replace: `${IS_WEB}?foo:bar` + */ export var IS_WEB: boolean; export var VencordNative: typeof import("./VencordNative").default; export var Vencord: typeof import("./Vencord"); export var appSettings: { set(setting: string, v: any): void; }; + /** + * Only available when running in Electron, undefined on web. + * Thus, avoid using this or only use it inside an {@link IS_WEB} guard. + * + * If you really must use it, mark your plugin as Desktop App only via + * `target: "DESKTOP"` + */ export var DiscordNative: any; interface Window { diff --git a/src/plugins/clickableRoleDot.ts b/src/plugins/clickableRoleDot.ts index 61e6b289..28a511d6 100644 --- a/src/plugins/clickableRoleDot.ts +++ b/src/plugins/clickableRoleDot.ts @@ -18,7 +18,16 @@ export default definePlugin({ ], copyToClipBoard(color: string) { - window.DiscordNative.clipboard.copy(color); + if (IS_WEB) { + navigator.clipboard.writeText(color) + .then(() => this.notifySuccess); + } else { + DiscordNative.clipboard.copy(color); + this.notifySuccess(); + } + }, + + notifySuccess() { Toasts.show({ message: "Copied to Clipboard!", type: Toasts.Type.SUCCESS, diff --git a/src/plugins/noRPC.ts b/src/plugins/noRPC.ts index 95dcf04e..f1094fd9 100644 --- a/src/plugins/noRPC.ts +++ b/src/plugins/noRPC.ts @@ -5,6 +5,7 @@ export default definePlugin({ name: "No RPC", description: "Disables Discord's RPC server.", authors: [Devs.Cyn], + target: "DESKTOP", patches: [ { find: '.ensureModule("discord_rpc")', diff --git a/src/plugins/noSystemBadge.ts b/src/plugins/noSystemBadge.ts index 25f873b6..7b687c7b 100644 --- a/src/plugins/noSystemBadge.ts +++ b/src/plugins/noSystemBadge.ts @@ -5,6 +5,7 @@ export default definePlugin({ name: "NoSystemBadge", description: "Disables the taskbar and system tray unread count badge.", authors: [Devs.rushii], + target: "DESKTOP", patches: [ { find: "setSystemTrayApplications:function", diff --git a/src/plugins/settings.ts b/src/plugins/settings.ts index afd3fd30..d26688a6 100644 --- a/src/plugins/settings.ts +++ b/src/plugins/settings.ts @@ -28,12 +28,15 @@ export default definePlugin({ find: "Messages.ACTIVITY_SETTINGS", replacement: { match: /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.ACTIVITY_SETTINGS\}/, - replace: (m, mod) => - `{section:${mod}.ID.HEADER,label:"Vencord"},` + - '{section:"VencordSetting",label:"Vencord",element:Vencord.Components.Settings},' + - '{section:"VencordUpdater",label:"Updater",element:Vencord.Components.Updater,predicate:()=>!IS_WEB},' + - `{section:${mod}.ID.DIVIDER},${m}` - + replace: (m, mod) => { + const updater = !IS_WEB ? '{section:"VencordUpdater",label:"Updater",element:Vencord.Components.Updater},' : ""; + return ( + `{section:${mod}.ID.HEADER,label:"Vencord"},` + + '{section:"VencordSetting",label:"Vencord",element:Vencord.Components.Settings},' + + updater + + `{section:${mod}.ID.DIVIDER},${m}` + ); + } } }] });