tb-harmony-modern-ts/webpack.config.ts
2025-11-13 12:14:15 +01:00

146 lines
3.8 KiB
TypeScript

import path from "path";
import { readdirSync } from "fs";
import { Compilation, sources } from "webpack";
import type {
Configuration,
RuleSetRule,
Compiler,
EntryObject,
} from "webpack";
interface FindReplacePluginOptions {
replace: { from: RegExp; to: string }[];
}
class FindReplacePlugin {
options: FindReplacePluginOptions;
constructor(options: FindReplacePluginOptions) {
this.options = options;
}
apply(compiler: Compiler) {
const pluginName = FindReplacePlugin.name;
const logger = compiler.getInfrastructureLogger(pluginName);
compiler.hooks.compilation.tap({ name: pluginName }, (compilation) => {
compilation.hooks.processAssets.tap(
{
name: pluginName,
stage: Compilation.PROCESS_ASSETS_STAGE_ANALYSE,
},
(assets) => {
Object.entries(assets).forEach(([pathname, source]) => {
if (path.extname(pathname) !== ".js") return;
let replaced = source.source().toString();
// For every regex pattern, replace it in the source content
for (const { from, to } of this.options.replace) {
const matches = replaced.match(from);
logger.info(
from,
to,
`Found ${matches ? matches.length : 0} matches`,
matches,
);
replaced = replaced.replace(from, to);
}
const newSource = new sources.RawSource(replaced);
const previousSize = source.size();
compilation.updateAsset(pathname, newSource);
const newSize = newSource.size();
if (newSize !== previousSize) {
logger.info(
`Found and replaced in ${pathname}: ${previousSize} -> ${newSize}`,
);
}
});
},
);
});
}
}
const babelRule: RuleSetRule = {
test: /\.(ts|js)$/,
use: {
loader: "babel-loader",
options: {
targets: "defaults",
sourceType: "unambiguous",
presets: [
["@babel/preset-typescript"],
[
"@babel/preset-env",
{
// Use this to cover the lowest possible syntax requirement for QtScript
// See: https://browsersl.ist/
targets: "cover 100%",
useBuiltIns: "usage",
corejs: "3.46.0",
},
],
],
},
},
};
/**
* Dynamically returns an object of script entries from src/scripts/*.ts files
*/
function getScriptsEntryObject(): EntryObject {
const entries: EntryObject = {};
const scriptsPath = path.resolve(__dirname, "src", "scripts");
const scripts = readdirSync(scriptsPath).filter((file) =>
file.endsWith(".ts"),
);
scripts.forEach((script) => {
entries[path.parse(script).name] = path.resolve(scriptsPath, script);
});
return entries;
}
const config: Configuration = {
mode: "production",
module: {
rules: [babelRule],
},
entry: getScriptsEntryObject(),
resolve: { extensions: [".ts", ".js"] },
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
environment: { arrowFunction: false },
},
plugins: [
new FindReplacePlugin({
replace: [
/**
* Replaces reserved words as property names
* For example obj.for = 0 -> obj["for"] = 0
*/
{ from: /\.(for|return)([^\w])/g, to: '["$1"]$2' },
/**
* Replaces reserved words as object literal property names
* See: https://eslint.style/rules/quote-props#quote-props
* For example { for: 0 } -> { "for": 0 }
*/
{ from: /(for|return):/g, to: '"$1":' },
{ from: /\/(.*)\[([^\]]*)\/([^\]]*)\](.*)\//g, to: "/$1[$2\/$3]$4/" },
],
}),
],
optimization: {
minimize: false,
},
};
export default config;