Files
abode/webpack.config.ts
T
2026-06-29 23:07:14 +00:00

285 lines
8.6 KiB
TypeScript

import { DefinePlugin, IgnorePlugin, type Configuration } from "webpack";
import { findNatives } from "./src/meta/pack/natives.ts";
import { existingSources } from "./src/meta/pack/sources.ts";
import CopyPlugin from "copy-webpack-plugin";
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
import { readdir } from "node:fs/promises";
const self = new URL(import.meta.url).pathname;
function file(path: string): string {
return new URL(path, import.meta.url).pathname;
}
export default async (): Promise<Configuration[]> => {
// global webpack config options
const defines: Record<string, string> = {};
const copies: CopyPlugin.Pattern[] = [];
const aliases: Record<string, string> = {};
const compiledSources: string[] = [];
// read environment to configure the build
const env =
process.env.NODE_ENV === "production" ? "production" : "development";
const dbSources =
process.env.DB_SOURCES === "dynamic"
? "dynamic"
: process.env.DB_SOURCES === "static"
? "static"
: "shared";
let disableDbSqlite = process.env.DISABLE_DB_SQLITE === "1";
let disableBs3 = disableDbSqlite || process.env.DISABLE_BS3 === "1";
let disableNodeSqlite =
disableDbSqlite || process.env.DISABLE_NODE_SQLITE === "1";
const disableDbApi = process.env.DISABLE_DB_API === "1";
// find natives and add them to the build
const natives: Record<string, string | null> = await findNatives();
if (disableBs3) natives.sqlite = null;
if (!natives.sqlite) disableBs3 = true;
for (const [k, v] of Object.entries(natives)) {
if (!v) {
defines[`natives.${k}`] = "null";
continue;
}
defines[`natives.${k}`] =
"(__dirname+" +
JSON.stringify("/../natives/" + v.split("/").at(-1)!) +
")";
copies.push({ from: v, to: "./natives" });
}
// find binaries and add them to the build
const binaries = Object.fromEntries(
(await readdir(file("./src/bin"))).map((bin) => [
bin.replace(/\..+$/, ""),
file(`./src/bin/${bin}`),
])
);
for (const bin of Object.keys(binaries)) {
copies.push({
from: self,
to: `./bin/${bin}`,
toType: "file",
transform: () =>
`#!/bin/sh\nexec node --enable-source-maps "$(dirname "$0")/${bin}.cjs" "$@"\n`,
});
}
// find schemas and add them to the build
const schemas: string[] = [];
{
async function impl(dir: string) {
for (const file of await readdir(dir, { withFileTypes: true })) {
if (file.isDirectory()) {
await impl(dir + "/" + file.name);
} else if (file.isFile() && file.name.endsWith(".schema.json")) {
schemas.push(dir + "/" + file.name);
}
}
}
await impl(file("./src/schema"));
for (const schema of schemas) {
copies.push({ from: schema, to: "./schema" });
}
}
// resolve db sources statically or dynamically
if (dbSources === "dynamic") {
console.log("Resolving db interfaces dynamically");
aliases[file("./src/db/dbSources.ts")] = file("./src/db/dbSources.dyn.ts");
} else if (dbSources === "static") {
console.log("Resolving db interfaces statically");
aliases[file("./src/db/dbSources.ts")] = file(
"./src/db/dbSources.static.ts"
);
} else {
console.log("Resolving db interfaces shared");
aliases[file("./src/db/dbSources.ts")] = file(
"./src/db/dbSources.shared.ts"
);
}
// disable parts or all of the db interface
if (disableBs3 && disableNodeSqlite) disableDbSqlite = true;
if (disableDbSqlite) {
console.log("Disabling sqlite db interface completely");
aliases[file("./src/db/sqlite/getdb.static.ts")] = file("./src/db/stub.ts");
aliases[file("./src/db/sqlite/getdb.dyn.ts")] = file("./src/db/stub.ts");
} else if (disableBs3) {
console.log("Disabling better-sqlite3 sqlite db backend");
aliases[file("./src/db/sqlite/impl/implementations.ts")] = file(
"./src/db/sqlite/impl/implementations.node.ts"
);
compiledSources.push("sqlite");
} else if (disableNodeSqlite) {
console.log("Disabling node:sqlite sqlite db backend");
aliases[file("./src/db/sqlite/impl/implementations.ts")] = file(
"./src/db/sqlite/impl/implementations.bs3.ts"
);
compiledSources.push("sqlite");
} else {
console.log(
"Enabling sqlite db interface with better-sqlite3 and node:sqlite backends"
);
aliases[file("./src/db/sqlite/impl/implementations.ts")] = file(
"./src/db/sqlite/impl/implementations.all.ts"
);
compiledSources.push("sqlite");
}
// disable the api db interface
if (disableDbApi) {
console.log("Disabling api db interface completely");
aliases[file("./src/db/api/getdb.static.ts")] = file("./src/db/stub.ts");
aliases[file("./src/db/api/getdb.dyn.ts")] = file("./src/db/stub.ts");
} else {
console.log("Enabling api db interface");
compiledSources.push("api");
}
// check the compiled sources and add it to the defines
compiledSources.sort();
defines.compiledSources = JSON.stringify(compiledSources);
for (const source of existingSources)
defines[`compiledSources.${source}`] = JSON.stringify(
compiledSources.includes(source)
);
if (!compiledSources.length) {
console.warn(
"No db interface enabled, the builds will be completely useless"
);
process.exit(1);
}
// log about the natives we have
console.log(
`Using ${Object.values(natives).filter(Boolean).length} natives:`
);
for (const [key, path] of Object.entries(natives)) {
if (path) console.log(`- ${key}: ${path}`);
else console.warn(`- ${key}: (not found)`);
}
// log about the binaries we're making
console.log(`Generating ${Object.entries(binaries).length} binaries:`);
for (const [key, path] of Object.entries(binaries)) {
console.log(`- ${key}: ${path}`);
}
// generate a json report for the stuff included in the build
{
const report = {
binaries: Object.keys(binaries),
natives: Object.entries(natives)
.filter((x) => x[1])
.map((x) => x[0]),
db: { mode: dbSources, sources: compiledSources },
schemas: schemas.map((x) => x.split("/").at(-1)!),
};
if (!disableDbSqlite)
Object.assign(report.db, {
sqlite: { bs3: !disableBs3, node: !disableNodeSqlite },
});
copies.push({
from: self,
to: "./report/build.json",
toType: "file",
transform: () => JSON.stringify(report, null, 2),
});
}
const config: Configuration[] = [];
config.push({
mode: env,
devtool: "source-map",
target: "node",
entry: binaries,
output: {
path: file("./dist"),
filename: "bin/[name].cjs",
chunkFilename: "chunk/bin.[name].chunk.cjs",
},
resolve: {
extensionAlias: {
".js": [".js", ".ts", ".tsx"],
},
extensions: [".js", ".ts"],
alias: {
...aliases,
},
},
module: {
rules: [
{
test: /\.sql$/,
include: file("./src"),
loader: "raw-loader",
},
{
type: "javascript/esm",
include: file("./src/schema/validators.ts"),
use: [
{
loader: "val-loader",
options: {
executableFile: file("./src/meta/pack/valLoader.ts"),
loader: file("./src/meta/pack/validators.ts"),
},
},
],
},
{
test: /\.tsx?$/,
type: "javascript/esm",
include: file("./src"),
use: [
{
loader: "ts-loader",
options: {
ignoreDiagnostics: true,
compilerOptions: {
noEmit: false,
jsx: "react-jsx",
diagnostics: false,
noEmitOnError: false,
},
},
},
],
},
],
},
plugins: [
new DefinePlugin({
...defines,
"import.meta.hot": "undefined",
}),
new IgnorePlugin({
resourceRegExp: /^react-devtools-core$|^\.\/devtools\.js$|^bindings$/,
}),
new CopyPlugin({
patterns: copies,
}),
new BundleAnalyzerPlugin({
openAnalyzer: false,
analyzerMode: "static",
reportFilename: "report/bin.html",
}),
],
ignoreWarnings: [
{
// webpack doesn't like the `require` used by better-sqlite3
// it's an indirect call to a runtime string, so it can't be analyzed
// the way we use it however, we force the argument to be constant at build time
module: /\/node_modules\/better-sqlite3\/lib\/database\.js/,
},
],
});
return config;
};