@moonlight-mod/esbuild-config 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright © 2025 moonlight contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # `@moonlight-mod/esbuild-config`
2
+
3
+ A simple and effective `esbuild` configuration for moonlight extensions and builds.
@@ -0,0 +1,63 @@
1
+ import fs from "node:fs";
2
+ import type { Plugin, BuildResult, BuildOptions } from "esbuild";
3
+ import betterLogging from "./plugins/betterLogging.js";
4
+ import webpackImports from "./plugins/webpackImports.js";
5
+ declare const sides: readonly ["index", "webpackModules", "node", "host"];
6
+ export interface ESBuildFactoryOptions {
7
+ /**
8
+ * The name of your extension.
9
+ * @remarks This is used for logging purposes, and should match the ID in your `manifest.json` file.
10
+ */
11
+ ext: string;
12
+ /**
13
+ * The input directory containing your extension's source (e.g. `./src`).
14
+ * @remarks If you use moonlight's `create-extension`, this is automatically generated.
15
+ */
16
+ entry: string;
17
+ /**
18
+ * The input directory containing your extension's source (e.g. `./src`).
19
+ * @remarks If you use moonlight's `create-extension`, this is automatically generated.
20
+ * @deprecated Use {@link entry} instead. This will be removed in a major version.
21
+ */
22
+ src: string;
23
+ /**
24
+ * The output directory containing your compiled extension (e.g. `./dist`).
25
+ * @remarks If you use moonlight's `create-extension`, this is automatically generated.
26
+ */
27
+ output: string;
28
+ /**
29
+ * The output directory containing your compiled extension (e.g. `./dist`).
30
+ * @remarks If you use moonlight's `create-extension`, this is automatically generated.
31
+ * @deprecated Use {@link output} instead. This will be removed in a major version.
32
+ */
33
+ dst: string;
34
+ /**
35
+ * A string determining what target to compile your extension to.
36
+ * @remarks In utility functions other than {@link defineConfig}, this is automatically generated.
37
+ */
38
+ side: typeof sides[number];
39
+ /**
40
+ * A list of extra "external" dependencies, (i.e., dependencies that the
41
+ * {@link side}'s runtime already provides and thus doesn't need to be bundled)
42
+ */
43
+ extraExternal?: string[];
44
+ /**
45
+ * A list of extra ESBuild {@link Plugin}s to be applied after the default ones.
46
+ */
47
+ extraPlugins?: Plugin[];
48
+ /**
49
+ * Extra {@link BuildOptions} to append to the finalized ESBuild config.
50
+ * Alternatively, you can destructure {@link defineConfig} and override default options.
51
+ */
52
+ extraConfig?: BuildOptions;
53
+ /**
54
+ * Whether or not to compile the extension's main entrypoint (e.g.`index.ts`) to ESM.
55
+ * @remarks Note that this doesn't compile webpackModules to ESM.
56
+ */
57
+ esm?: boolean;
58
+ }
59
+ export declare function defineConfig(options: ESBuildFactoryOptions): BuildOptions | null;
60
+ export declare function defineConfigs(options: Omit<ESBuildFactoryOptions, "side">): BuildOptions[];
61
+ export declare function buildExtension(options: Omit<ESBuildFactoryOptions, "side">): Promise<BuildResult[]>;
62
+ export declare function watchExtension(options: Omit<ESBuildFactoryOptions, "side">): Promise<fs.FSWatcher[]>;
63
+ export { defineConfig as makeExtConfig, defineConfigs as makeExtConfigs, buildExtension as buildExt, watchExtension as watchExt, betterLogging as deduplicatedLogging, webpackImports as wpImport, };
@@ -0,0 +1,105 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { build, context } from "esbuild";
4
+ import betterLogging from "./plugins/betterLogging.js";
5
+ import webpackImports from "./plugins/webpackImports.js";
6
+ import copyFile from "./plugins/copyFile.js";
7
+ const prod = process.env.NODE_ENV === "production";
8
+ const external = [
9
+ "electron",
10
+ "fs",
11
+ "path",
12
+ "module",
13
+ "events",
14
+ "original-fs",
15
+ "discord" // mappings
16
+ ];
17
+ const sides = ["index", "webpackModules", "node", "host"];
18
+ const fileExts = ["js", "jsx", "ts", "tsx"];
19
+ export function defineConfig(options) {
20
+ const entry = options.entry ?? options.src;
21
+ const output = options.output ?? options.dst;
22
+ const { ext, side, extraExternal = [], extraPlugins = [], extraConfig = {}, esm = false } = options;
23
+ const entryPoints = [];
24
+ if (side !== "webpackModules") {
25
+ for (const fileExt of fileExts) {
26
+ const filePath = path.join(entry, `${side}.${fileExt}`);
27
+ if (fs.existsSync(filePath))
28
+ entryPoints.push(filePath);
29
+ }
30
+ }
31
+ const wpModulesDir = path.join(entry, "webpackModules");
32
+ if (side === "webpackModules" && fs.existsSync(wpModulesDir)) {
33
+ const wpModules = fs.readdirSync(wpModulesDir);
34
+ for (const wpModule of wpModules) {
35
+ if (fs.statSync(path.join(wpModulesDir, wpModule)).isDirectory()) {
36
+ for (const fileExt of fileExts) {
37
+ const filePath = path.join(wpModulesDir, wpModule, `index.${fileExt}`);
38
+ if (fs.existsSync(filePath))
39
+ entryPoints.push({
40
+ in: filePath,
41
+ out: wpModule
42
+ });
43
+ }
44
+ }
45
+ else {
46
+ entryPoints.push(path.join(wpModulesDir, wpModule));
47
+ }
48
+ }
49
+ }
50
+ if (entryPoints.length === 0)
51
+ return null;
52
+ return {
53
+ entryPoints: entryPoints,
54
+ outdir: side === "webpackModules" ? path.join(output, "webpackModules") : output,
55
+ format: esm && side === "index" ? "esm" : "iife",
56
+ globalName: "module.exports",
57
+ platform: ["index", "webpackModules"].includes(side) ? "browser" : "node",
58
+ treeShaking: true,
59
+ bundle: true,
60
+ minify: prod,
61
+ sourcemap: "inline",
62
+ external: [...external, ...extraExternal],
63
+ plugins: [
64
+ copyFile(path.join(entry, "manifest.json"), path.join(output, "manifest.json")),
65
+ copyFile(path.join(entry, "style.css"), path.join(output, "style.css")),
66
+ webpackImports,
67
+ betterLogging(`${ext}/${side}`),
68
+ ...extraPlugins
69
+ ],
70
+ ...extraConfig
71
+ };
72
+ }
73
+ export function defineConfigs(options) {
74
+ return sides
75
+ .map((side) => defineConfig({ ...options, side }))
76
+ .filter((config) => config != null);
77
+ }
78
+ export async function buildExtension(options) {
79
+ const configs = defineConfigs(options);
80
+ const builds = [];
81
+ configs.forEach(async (config) => builds.push(await build(config)));
82
+ return builds;
83
+ }
84
+ export async function watchExtension(options) {
85
+ const buildConfigs = defineConfigs(options);
86
+ const watchers = [];
87
+ for (const config of buildConfigs) {
88
+ const ctx = await context(config);
89
+ async function rebuild() {
90
+ try {
91
+ await ctx.rebuild();
92
+ }
93
+ catch {
94
+ // esbuild will log errors, we just need to not do anything
95
+ }
96
+ }
97
+ await rebuild();
98
+ const watcher = fs.watch(options.entry, { recursive: true }, async () => await rebuild());
99
+ watchers.push(watcher);
100
+ }
101
+ return watchers;
102
+ }
103
+ // In GitHub PR #3, these original names were modified to be more descriptive.
104
+ // We re-export these as to not break backwards compatibility with older plugins.
105
+ export { defineConfig as makeExtConfig, defineConfigs as makeExtConfigs, buildExtension as buildExt, watchExtension as watchExt, betterLogging as deduplicatedLogging, webpackImports as wpImport, };
@@ -0,0 +1,2 @@
1
+ export * from "./factory.js";
2
+ export * from "./plugins/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./factory.js";
2
+ export * from "./plugins/index.js";
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "esbuild";
2
+ export declare const betterLogging: (tag: string) => Plugin;
3
+ export default betterLogging;
@@ -0,0 +1,31 @@
1
+ import { formatMessages } from "esbuild";
2
+ const lastMessages = new Set();
3
+ const timeFormatter = new Intl.DateTimeFormat(undefined, {
4
+ hour: "numeric",
5
+ minute: "numeric",
6
+ second: "numeric",
7
+ hour12: false
8
+ });
9
+ export const betterLogging = (tag) => ({
10
+ name: "better-logging",
11
+ setup(build) {
12
+ build.onStart(() => lastMessages.clear());
13
+ build.onEnd(async (result) => {
14
+ const formatted = await Promise.all([
15
+ formatMessages(result.warnings, {
16
+ kind: "warning",
17
+ color: true
18
+ }),
19
+ formatMessages(result.errors, { kind: "error", color: true }),
20
+ ]).then((a) => a.flat());
21
+ for (const message of formatted) {
22
+ if (lastMessages.has(message))
23
+ continue;
24
+ lastMessages.add(message);
25
+ console.log(message.trim());
26
+ }
27
+ console.log(`[${timeFormatter.format(new Date())}] [${tag}] build finished`);
28
+ });
29
+ }
30
+ });
31
+ export default betterLogging;
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "esbuild";
2
+ export declare const copyFile: (src: string, dest: string) => Plugin;
3
+ export default copyFile;
@@ -0,0 +1,14 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export const copyFile = (src, dest) => ({
4
+ name: "copy-files",
5
+ setup: build => build.onEnd(() => {
6
+ if (!fs.existsSync(src))
7
+ return;
8
+ const dir = path.dirname(dest);
9
+ if (!fs.existsSync(dir))
10
+ fs.mkdirSync(dir, { recursive: true });
11
+ fs.copyFileSync(src, dest);
12
+ }),
13
+ });
14
+ export default copyFile;
@@ -0,0 +1,3 @@
1
+ export * from "./betterLogging.js";
2
+ export * from "./copyFile.js";
3
+ export * from "./webpackImports.js";
@@ -0,0 +1,3 @@
1
+ export * from "./betterLogging.js";
2
+ export * from "./copyFile.js";
3
+ export * from "./webpackImports.js";
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "esbuild";
2
+ declare const webpackImports: Plugin;
3
+ export default webpackImports;
@@ -0,0 +1,8 @@
1
+ const webpackImports = {
2
+ name: "webpack-imports",
3
+ setup: build => build.onResolve({ filter: /^@moonlight-mod\/wp\// }, args => ({
4
+ path: args.path.replace(/^@moonlight-mod\/wp\//, ""),
5
+ external: true
6
+ })),
7
+ };
8
+ export default webpackImports;
@@ -0,0 +1 @@
1
+ {"root":["../src/factory.ts","../src/index.ts","../src/plugins/betterLogging.ts","../src/plugins/copyFile.ts","../src/plugins/index.ts","../src/plugins/webpackImports.ts"],"version":"5.8.3"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@moonlight-mod/esbuild-config",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "private": false,
6
+ "description": "Yet another Discord mod's shared esbuild config",
7
+ "license": "MIT",
8
+ "homepage": "https://moonlight-mod.github.io/",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/moonlight-mod/esbuild-config.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/moonlight-mod/esbuild-config/issues"
15
+ },
16
+ "engines": {
17
+ "node": ">=22",
18
+ "pnpm": ">=10",
19
+ "npm": "pnpm",
20
+ "yarn": "pnpm"
21
+ },
22
+ "main": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "devDependencies": {
28
+ "@types/node": "^22.14.0",
29
+ "prettier": "^3.4.2",
30
+ "esbuild": "^0.25.2",
31
+ "typescript": "^5.8.3"
32
+ },
33
+ "peerDependencies": {
34
+ "esbuild": ">= 0.19"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc --build",
38
+ "clean": "pnpm build --clean"
39
+ }
40
+ }