/**
* @file exports/optimization.js
* @copyright @spmhome @_2025
* @author Scott Meesseman @spmeesseman
* @description @see {@link https://webpack.js.org/configuration/optimization webpack.js.org/optimization}
*//** */
const WpwWebpackExport = require("./base");
const Terser = require("terser-webpack-plugin");
/**
* @augments WpwWebpackExport
*/
class WpwOptimizationExport extends WpwWebpackExport
{
// /**
// * @private
// * @static
// */
// static regex = {
// dotPath: /(?:^|[\\/])\.[\w\-_]+$/,
// nodeModulePath: /[\\/]node_modules/,
// nodeModulesPathBaseAbs: /^.+?[/\\]node_modules/,
// nodeModulesPathBaseAbsTrSep: /^.*?[\\/]node_modules[\\/]/,
// relativeImportPath: /^\.\.?\//,
// scopedPackage: /^@[\w\-_]+[\\/][\w\-_]+$/,
// scopedPackageBaseDir: /^@[\w\-_]+$/
// };
// /**
// * @private
// * @type {string[]}
// */
// isAddedToGroupCache = [];
/**
* @param {WpwExportOptions} options
*/
constructor(options)
{
super(options); // reset for typings
this.buildOptions = /** @type {WpwBuildOptionsExportConfig<"optimization">} */(this.buildOptions);
}
/**
* @override
*/
static create = WpwOptimizationExport.wrap.bind(this);
/**
* @private
* @param {WpwBuild} build
*/
addRuntimeChunk(build)
{
if (this.buildOptions.runtime !== "none")
{
build.logger.write("add runtime chunk", 1);
build.wpc.optimization.runtimeChunk = this.buildOptions.runtime || "single"; // "multiple";
}
}
isRelativeToChunk(mod, name)
{
if (mod.identifier().includes(name)) return true;
if (mod.issuer) return this.isRelativeToChunk(mod.issuer, name);
}
// getSplitChunksRuleForWidget(name)
// {
// return {
// test: (mod) => this.isRelativeToBuild(mod, name),
// name: widgetName,
// chunks: 'async',
// enforce: true,
// }
// }
/**
* @private
* @param {WpwBuild} build
* @param {IWpwExportConfigOptimizationCacheGroup} cacheGroup
*/
addSplitChunk(build, cacheGroup)
{
if (build.wpc.optimization.splitChunks === false) { return; }
this._obj_.applyIf(cacheGroup, { maxSize: undefined, minSize: undefined });
this.logger.values([
[ "pattern regex", cacheGroup.test ], [ "chunks", cacheGroup.chunks, 3 ],
[ "min chunk size", cacheGroup.minSize, 3 ], [ "max chunk size", cacheGroup.maxSize, 3 ]
], 2, " ", false, `configure '${cacheGroup.name}' split chunk cache group`);
build.wpc.optimization.splitChunks.cacheGroups[cacheGroup.name] = {
name: cacheGroup.name,
chunks: cacheGroup.chunks,
layer: cacheGroup.layer,
maxSize: cacheGroup.maxSize,
minSize: cacheGroup.minSize,
test: new RegExp(cacheGroup.test)
};
}
/**
* @private
* @param {WpwBuild} build
*/
addSplitChunks(build)
{
this.configureSplitChunks(build);
build.wpc.optimization.splitChunks = {
chunks: "all", cacheGroups: { vendors: false, default: false }
};
this.addSplitChunk(build, this.buildOptions.eslintCacheGroup);
this.addSplitChunk(build, this.buildOptions.spmhCacheGroup);
this.addSplitChunk(build, this.buildOptions.spmhCacheGroupApp);
this.addSplitChunk(build, this.buildOptions.spmhCacheGroupLib);
this.addSplitChunk(build, this.buildOptions.vendorCacheGroup);
if (build.isReact) {
this.addSplitChunk(build, this.buildOptions.reactCacheGroup);
}
this._arr_.asArray(this.buildOptions.customCacheGroup).forEach((c) => this.addSplitChunk(build, c));
}
/**
* @override
* @param {WpwBuild} build
*/
app(build)
{
this._obj_.apply(build.wpc.optimization,
{
moduleIds: "named",
chunkIds: "named",
usedExports: true,
// sideEffects: true,
concatenateModules: build.isProdMode
});
this.addRuntimeChunk(build);
this.addSplitChunks(build);
}
/**
* @override
* @param {WpwBuild} build
*/
base(build)
{
if (build.isAnyAppOrLib)
{
this._obj_.apply(build.wpc.optimization,
{
minimize: false,
// sideEffects: true,
// usedExports: true,
splitChunks: false,
// emitOnErrors: false,
runtimeChunk: false,
chunkIds: "natural",
moduleIds: "natural",
minimizer: undefined
// providedExports: true, // build.mode === "production"
// removeEmptyChunks: true,
// mergeDuplicateChunks: true,
// removeAvailableModules: false,
// checkWasmType: build.isProdMode,
// avoidEntryIife: build.isProdMode,
// flagIncludedChunks: build.isProdMode,
// concatenateModules: build.isProdMode
});
this.configureMinification(build);
}
}
/**
* @private
* @param {WpwBuild} build
*/
configureMinification(build)
{
const b= build,
bo = b.options,
minifyForce = b.options.optimization?.minify === true,
enabled = (minifyForce && !b.isDocs && !b.isResource) ||
(b.isProdMode && (b.isAnyAppOrLib || (b.isScript && bo.script.minify === true) ||
(b.isSchema && bo.schema.minify === true)));
b.logger.write("configure terser minimizer", 1);
if (b.isProdMode) {
b.logger.write(" minify, license extraction, and comment removal", 1);
}
else {
b.logger.write(" comment removal", 1);
}
const co = b.tsc.compilerOptions,
// Terser = require("terser-webpack-plugin"),
lib = (this._arr_.asArray(b.target).find((t) => t.startsWith("es")) ||
co.target || co.lib[0] || "es2015").toLowerCase().replace("es", ""),
num = (lib !== "next" && this._types_.isNumeric(lib) ? parseInt(lib, 10) : 2020),
ecma = /** @type {TerserECMA} */(num <= 2020 ? num : 2020);
const minimizerOptions = this._obj_.apply({},
{
parallel: true,
extractComments: enabled ?
{ banner: false,
filename: "vendor.LICENSE",
// filename: (fileData) => `${fileData.filename}.LICENSE${fileData.query}`,
condition: /^ *(?=\/\/|\*).*?licens(?:e|ing|ed|)\s.+$/i
} : false,
/** @type {MinifyOptions} */
terserOptions:
{ ecma,
sourceMap: false,
module: b.isModule,
keep_classnames: true,
parse: { shebang: true },
safari10: b.loader === "babel",
compress: enabled ? {drop_debugger: true, passes: 2, module: b.isModule } : false,
mangle: enabled ? { keep_classnames: true, module: b.isModule } : false,
format: { comments: /(?:.*?copyright|^#!\/).+$/i, shebang: true }
}
}, b.loader !== "esbuild" || !enabled ? {} : Terser.esbuildMinify);
this._obj_.apply(b.wpc.optimization,
{
minimize: enabled,
minimizer: [{
apply: (/** @type {WebpackCompiler} */compiler) => { new Terser(minimizerOptions).apply(compiler); }
}]
});
}
/**
* @private
* @param {WpwBuild} build
*/
configureSplitChunks(build)
{
let cstPriority = 10;
const bo = this.buildOptions,
nmPat = "[\\\\/]node_modules[\\\\/]";
this._arr_.asArray(bo.customCacheGroup).filter((cg) => !this._types_.isDefined(cg.priority))
.forEach((cg) => {
cg.priority = cstPriority--;
});
this._obj_.merge(bo,
{
eslintCacheGroup:
{
name: "eslint",
priority: 1,
test: `${nmPat}.*eslint(?:\\\\|\\/||$|\\-[a-z]{3,})`
},
reactCacheGroup: !build.isReact ? {} :
{
name: "react",
priority: 1,
test: `${nmPat}react(?:\\\\|\\/||$|\\-[a-z]{3,})`
},
spmhCacheGroup:
{
name: "spmhc",
priority: 1,
test: `${nmPat}@spmhome[\\\\/](?!(svr|exec|type|app|log|cli|arg|vscode))`
},
spmhCacheGroupApp:
{
name: "spmha",
priority: 2,
test: `${nmPat}@spmhome[\\\\/](?:app|log|cli|arg|vscode|exec)`
},
spmhCacheGroupLib:
{
name: "spmhl",
priority: 3,
test: `${nmPat}@spmhome[\\\\/](?:svr|type)`
},
vendorCacheGroup:
{
name: "vendor",
priority: 10,
test: `${nmPat}(?!(@spmhome|react(?:\\\\|\\/|$||\\-[a-z]{3,})|(?:@|.+?\\-)eslint(?:$|\\\\|\\/|\\-)))`
}
});
}
// /**
// * @private
// * @param {string} ctx
// * @returns {string}
// */
// moduleNameFromCtx(ctx)
// {
// if (WpwOptimizationExport.regex.nodeModulePath.test(ctx))
// {
// const name = ctx.replace(WpwOptimizationExport.regex.nodeModulesPathBaseAbsTrSep, "")
// .replace(WpwOptimizationExport.regex.scopedPackage, "");
// return !name.includes("/") ? name : name.split("/").slice(0, 2).join("/");
// }
// return "_relative_";
// }
/**
* @override
* @param {WpwBuild} _build
*/
doxygen(_build) {}
/**
* @override
* @param {WpwBuild} _build
*/
jsdoc(_build) {}
/**
* @override
* @param {WpwBuild} build
*/
lib(build)
{
this._obj_.apply(build.wpc.optimization,
{
moduleIds: "named",
chunkIds: "named",
usedExports: true,
// sideEffects: true,
removeAvailableModules: false,
concatenateModules: build.isProdMode
});
}
/**
* @override
* @param {WpwBuild} build
*/
plugin(build) { this.app(build); }
/**
* @override
* @param {WpwBuild} _build
*/
resource(_build) {}
/**
* @override
* @param {WpwBuild} _build
*/
schema(_build) {}
/**
* @override
* @param {WpwBuild} _build
*/
script(_build) {}
/**
* @override
* @param {WpwBuild} _build
*/
tests(_build) {}
/**
* @override
* @param {WpwBuild} _build
*/
types(_build) {}
/**
* @override
* @param {WpwBuild} build
*/
webapp(build)
{
this._obj_.apply(this.build.wpc.optimization,
{
moduleIds: "named",
chunkIds: "named",
usedExports: true,
// sideEffects: true,
removeAvailableModules: false,
concatenateModules: build.isProdMode
});
this.addRuntimeChunk(build);
this.addSplitChunks(build);
}
}
module.exports = WpwOptimizationExport.create;