mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
131 lines
4.5 KiB
JavaScript
131 lines
4.5 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import * as esbuild from "esbuild";
|
|
import { BACKEND_EXTERNALS, FRONTEND_EXTERNALS } from "../lib/externals.mjs";
|
|
import { staticUrlImports } from "../lib/static-url-imports.mjs";
|
|
import { readManifest, resolveEntry, copyDir } from "../lib/plugin-dir.mjs";
|
|
|
|
const BACKEND_ENTRIES = [
|
|
"src/backend/index.ts",
|
|
"src/backend/index.mjs",
|
|
"src/backend/index.js",
|
|
];
|
|
const FRONTEND_ENTRIES = [
|
|
"src/frontend/index.tsx",
|
|
"src/frontend/index.ts",
|
|
"src/frontend/index.mjs",
|
|
"src/frontend/index.js",
|
|
];
|
|
|
|
/**
|
|
* A bundled CommonJS package that calls require("react/jsx-runtime") (or any
|
|
* other host-provided package) cannot reach it: the package is external, and
|
|
* esbuild's ESM output has no require, so the plugin throws "Dynamic require
|
|
* is not supported" as soon as it loads. Each such require is pointed at a
|
|
* small ES module that imports the external and re-exports it, which esbuild
|
|
* can hand to CommonJS code.
|
|
*/
|
|
export function externalRequireInterop(externals) {
|
|
const names = new Set(externals.filter((name) => !name.includes("*")));
|
|
return {
|
|
name: "termix-external-require",
|
|
setup(build) {
|
|
build.onResolve({ filter: /.*/ }, (args) => {
|
|
if (args.kind !== "require-call" || !names.has(args.path)) return;
|
|
return { path: args.path, namespace: "termix-external-require" };
|
|
});
|
|
build.onLoad(
|
|
{ filter: /.*/, namespace: "termix-external-require" },
|
|
(args) => {
|
|
const id = JSON.stringify(args.path);
|
|
return {
|
|
contents: `import * as mod from ${id};\nexport * from ${id};\nexport default mod.default ?? mod;\n`,
|
|
loader: "js",
|
|
};
|
|
},
|
|
);
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function build({ cwd }) {
|
|
const manifest = readManifest(cwd);
|
|
const pluginId = manifest.id ?? path.basename(cwd);
|
|
const outDir = path.join(cwd, "dist");
|
|
|
|
fs.rmSync(outDir, { recursive: true, force: true });
|
|
fs.mkdirSync(outDir, { recursive: true });
|
|
|
|
const backendEntry = resolveEntry(cwd, BACKEND_ENTRIES);
|
|
if (!backendEntry) {
|
|
throw new Error(
|
|
`${pluginId}: no backend entry (looked for ${BACKEND_ENTRIES.join(", ")})`,
|
|
);
|
|
}
|
|
|
|
await esbuild.build({
|
|
entryPoints: [backendEntry],
|
|
outfile: path.join(outDir, "backend.js"),
|
|
bundle: true,
|
|
format: "esm",
|
|
platform: "node",
|
|
target: "node22",
|
|
sourcemap: true,
|
|
logLevel: "warning",
|
|
// A native dependency's compiled .node binary is resolved by the
|
|
// package's own relative paths, which break once esbuild inlines its JS
|
|
// elsewhere. Declaring it in nativeDependencies keeps it a real
|
|
// node_modules import instead, resolved at runtime like a host-provided
|
|
// package.
|
|
external: [...BACKEND_EXTERNALS, ...(manifest.nativeDependencies ?? [])],
|
|
// Bundled CJS deps still call require() for Node builtins at runtime.
|
|
// ESM has no ambient require, so give esbuild's require shim a real one.
|
|
banner: {
|
|
js: "import { createRequire as __termixCreateRequire } from 'node:module'; const require = __termixCreateRequire(import.meta.url);",
|
|
},
|
|
});
|
|
|
|
// Without this Node finds the host's typeless package.json, tries backend.js
|
|
// as CommonJS first and warns before reparsing it as ESM.
|
|
fs.writeFileSync(
|
|
path.join(outDir, "package.json"),
|
|
`${JSON.stringify({ type: "module" }, null, 2)}\n`,
|
|
);
|
|
|
|
const frontendEntry = resolveEntry(cwd, FRONTEND_ENTRIES);
|
|
if (frontendEntry) {
|
|
await esbuild.build({
|
|
entryPoints: [frontendEntry],
|
|
outfile: path.join(outDir, "frontend.js"),
|
|
bundle: true,
|
|
format: "esm",
|
|
platform: "browser",
|
|
target: "es2022",
|
|
jsx: "automatic",
|
|
sourcemap: true,
|
|
logLevel: "warning",
|
|
external: FRONTEND_EXTERNALS,
|
|
// Vite replaces these in the dev server; a plugin bundle has to have
|
|
// them too, or code that reads them throws when the plugin loads.
|
|
define: {
|
|
"process.env.NODE_ENV": '"production"',
|
|
"import.meta.env.DEV": "false",
|
|
"import.meta.env.PROD": "true",
|
|
"import.meta.env.MODE": '"production"',
|
|
"import.meta.env.SSR": "false",
|
|
"import.meta.env":
|
|
'{"DEV":false,"PROD":true,"MODE":"production","SSR":false,"BASE_URL":"/"}',
|
|
},
|
|
plugins: [
|
|
externalRequireInterop(FRONTEND_EXTERNALS),
|
|
staticUrlImports({ outDir }),
|
|
],
|
|
});
|
|
}
|
|
|
|
copyDir(path.join(cwd, "locales"), path.join(outDir, "locales"));
|
|
copyDir(path.join(cwd, "migrations"), path.join(outDir, "migrations"));
|
|
|
|
console.log(`built ${pluginId}`);
|
|
}
|