/**
* @file plugin/runtimevars.js
* @copyright @spmhome @_2025
* @author Scott Meesseman @spmeesseman
*//** */
const WpwCache = require("../services/cache");
const WpwPlugin = require("./base");
const { apply } = require("@spmhome/type-utils");
/**
* @augments WpwPlugin
*/
class WpwRuntimeVarsPlugin extends WpwPlugin
{
/**
* @param {WpwPluginOptions} options Plugin options to be applied
*/
constructor(options)
{
super(options);
this.buildOptions = /** @type {WpwBuildOptionsConfig<"runtimevars">} */(this.buildOptions);
}
/**
* @override
*/
static create = WpwRuntimeVarsPlugin.wrap.bind(this);
/**
* @override
* @returns {WpwPluginTapOptions<any, any, boolean>}
*/
onApply()
{
return {
replaceRuntimePlaceholderVariables: {
hook: "compilation",
stage: "ADDITIONS",
statsProperty: this.optionsKey,
callback: this.runtimeVars.bind(this)
}
};
}
/**
* @private
* @param {WebpackAssetInfo} info
* @returns {WebpackAssetInfo}
*/
info = (info) => apply({ ...(info || {}) }, { runtimeVars: true });
/**
* @private
* @param {WebpackCompilationAssets} assets
*/
runtimeVars(assets)
{
this.hookstart("replace runtime placeholder variables");
try
{ Object.entries(assets).filter(([ file ]) => this.isOutputAsset(file)).forEach(([ file ]) =>
{
this.build.logger.write(` queue asset '${file}' for variable replacement`, 2);
this.compilation.updateAsset(file, source => this.source(file, source), this.info.bind(this))
}, this);
}
catch (e)
{ this.addMessage({
exception: e, code: this.MsgCode.ERROR_PLUGIN_HOOK_FAILED,
message: "runtime placeholder variable replacement pllugin failed"
})
}
this.hookdone("runtime placeholder variable replacement completed");
};
/**
* Performs all source code modifications
* @private
* @param {string} file
* @param {WebpackSource} sourceInfo
* @returns {WebpackSource}
*/
source(file, sourceInfo)
{
const sourceCode = this.sourceUpdateVars(file, sourceInfo.source().toString());
return this.sourceObj(file, sourceCode, sourceInfo);
}
/**
* @private
* @param {string} file
* @param {string | Buffer} content
* @param {WebpackSource} sourceInfo
* @returns {WebpackSource}
*/
sourceObj(file, content, sourceInfo)
{
const { source, map } = sourceInfo.sourceAndMap();
return map && this.build.options.devtool?.enabled ?
new this.build.wp.sources.SourceMapSource(content, file, map, source) :
new this.build.wp.sources.RawSource(content);
}
/**
* Performs source code modifications and populates predefined build-time variable names
* with their respective values, e.g. :
*
* __WPW__.contentHash.[chunkName]
*
* Where 'chunkName' is one of `vendor`, `runtime`, `workbox` or the build.name/alias / sharedChunkName
*
* @private
* @param {string} file
* @param {string} sourceCode
* @returns {string}
* @throws {Error}
*/
sourceUpdateVars(file, sourceCode)
{
let rCt = 0;
const l = this.logger;
l.value(" process source code for placeholder variables", file, 1);
l.write(" process variable[1] 'contenthash'", 2);
for (const asset of this.compilation.getAssets().filter((a) => this.isOutputAsset(a.name, true)))
{
let idx = -1, varCt = 0, varCt2 = 0;
const chunk = this.fileNameStrip(asset.name, true);
while ((idx = sourceCode.indexOf("__WPW__", idx + 1)) !== -1) {
varCt++;
}
if (varCt > 0)
{
const hash = asset.info.contenthash;
if (this._types_.isString(hash))
{
const regex = new RegExp(`(?:.+?\\.)?__WPW__\\.contentHash(?:\\.|\\[ *")${chunk}(?:" *\\])?`, "gmi");
this.logger.write(` process asset '${chunk}' with hash '${hash}'`, 2);
sourceCode = sourceCode.replace(regex, `"${hash}"`);
idx = -1;
while ((idx = sourceCode.indexOf("__WPW__", idx + 1)) !== -1) {
varCt2++;
}
if (varCt2 !== 0) {
this.logger.warn(` unable to set ${varCt2} contenthash placeholders for '${chunk}', hash ${hash}`)
break;
}
rCt += varCt;
this.logger.write(` found and replaced '${varCt}' placeholders`, 1);
}
else {
throw new Error(`unable to process ${varCt} placeholder variable(s), contenthash not a string`)
}
}
}
l.write(` completed ${rCt} replacement for 'contenthash'`, 2);
l.write(" completed placeholder variable replacement for " + file, 1);
return sourceCode;
}
}
module.exports = WpwRuntimeVarsPlugin.create;