tb-harmony-modern-ts/webpack.config.ts

164 lines
4.6 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 }[];
}
/**
* Simple find and replace in assets plugin for Webpack
*/
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);
if (matches) {
logger.info(`${from} | Found ${matches.length} 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|mjs)$/,
// https://webpack.js.org/loaders/babel-loader/#exclude-libraries-that-should-not-be-transpiled
exclude: [
// \\ for Windows, / for macOS and Linux
/node_modules[\\/]core-js/,
],
use: {
loader: "babel-loader",
options: {
targets: "defaults",
sourceType: "unambiguous",
presets: [
// Parse TypeScript
["@babel/preset-typescript"],
[
"@babel/preset-env",
{
// Use this to cover the lowest possible syntax requirement for QtScript
// See: https://browsersl.ist/
targets: "cover 100%",
// Adds at the top of each file imports of polyfills only for features used in the current and not supported by target environments
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", ".mjs"],
// Explicitly define where node_modules is in this project for corejs imports
// See: https://stackoverflow.com/a/62129649
modules: [path.resolve(__dirname, "node_modules")],
},
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":' },
/**
* Some Regexp uses "/" character in character sets but must be escaped in QtScript syntax (don't know why)
*/
{ from: /\[\^\\s\(\/\]/g, to: "[^\\s(\\/]" },
{ from: /\[\\w\.\/\]/g, to: "[\\w.\\/]" },
{ from: /\[a-z_\+-\/\]/g, to: "[a-z_+-\\/]" },
],
}),
],
optimization: {
minimize: false,
},
};
export default config;