html-bundler-webpack-plugin
Version:
Generates complete single-page or multi-page website from source assets. Built-in support for Markdown, Eta, EJS, Handlebars, Nunjucks, Pug. Alternative to html-webpack-plugin.
223 lines (191 loc) • 6.03 kB
JavaScript
const fs = require('fs');
const path = require('path');
const { reset, green, cyan, cyanBright, yellow, white, whiteBright, redBright } = require('ansis');
const Config = require('../../Common/Config');
const { pluginLabel } = Config.get();
const PluginError = new Set();
class PluginException extends Error {
/**
* @param {string} message The plugin error message.
* @param {Error?} error The original error.
*/
constructor(message, error) {
message = `\n${reset.whiteBright.bgRedBright` ${pluginLabel} `} ${whiteBright(message)}\n`;
if (error && error.stack) {
message += error.stack;
}
super();
this.name = this.constructor.name;
this.message = message;
PluginError.add(this);
}
}
/**
* @param {string} dir
* @throws {Error}
*/
const optionEntryPathException = (dir) => {
const message =
`The plugin option ${green`entry`} must be a relative or an absolute path to the entry directory.\nThe directory is invalid or not found:\n` +
cyanBright(dir);
throw new PluginException(message);
};
/**
* @param {Object} config
* @param {string|null} type
* @param {Array<string>} availableTypes
* @throws {Error}
*/
const optionPreloadAsException = (config, type, availableTypes) => {
const replacer = (key, value) => {
if (value instanceof RegExp) {
return value.toString();
}
return value;
};
const configString = JSON.stringify(config, replacer, ' ');
let message = '';
if (type && availableTypes.indexOf(type) < 0) {
message =
`Invalid value of the ${green`'as'`} property in the plugin option ${green`preload`}:\n` +
configString +
'\n' +
`The value must be one of the following: ` +
JSON.stringify(availableTypes) +
'\n';
} else {
message = `Missing the ${green`'as'`} property in the plugin option ${green`preload`}:\n` + configString + '\n';
}
throw new PluginException(message);
};
/**
* @param {string} file The unresolved file can be absolute or relative.
* @param {string} issuer The absolute issuer file of unresolved file.
* @param {string} rootContext The absolute path to project files.
* @param {object} pluginOption The instance of the plugin Option.
* @throws {Error}
*/
const resolveException = (file, issuer, rootContext, pluginOption) => {
let isExistsFile = true;
issuer = path.relative(rootContext, issuer);
if (path.isAbsolute(file)) {
isExistsFile = fs.existsSync(file);
}
let message = `Can't resolve ${yellow(file)} in the file ${cyan(issuer)}`;
if (!isExistsFile) {
message = `File ${yellow(file)} not found in ${cyan(issuer)}`;
} else if (/\.css$/.test(file) && file.includes('??ruleSet')) {
message +=
`\nThe handling of ${yellow`@import at-rules in CSS`} is not supported. Disable the 'import' option in 'css-loader':\n` +
white`
{
test: /\.css$/i,
use: [
{
loader: 'css-loader',
options: {
import: false, ${white`// disable @import at-rules handling`}
},
},
],
},`;
} else if (pluginOption.isStyle(file) && hasSplitChunksCacheGroups(pluginOption.webpackOptions)) {
message += `\n
${whiteBright.bgGreen` Tip `}
Add the ${white`'splitChunks.cacheGroups.{cacheGroup}.test'`} option as a RegExp to each cache group to split only script files, excluding styles.
For example:
optimization: {
splitChunks: {
minSize: 1000,
cacheGroups: {
default: {
test: /.+\.(js|ts)$/, ${white`// split only scripts, excluding style files`}
name: 'common',
chunks: 'all',
},
vendors: {
test: /[\\/]node_modules[\\/].+\.(js|ts)$/, ${white`// split only scripts, excluding style files`}
name: 'vendor',
chunks: 'all',
},
},
},
},
`;
}
throw new PluginException(message);
};
/**
* Detect whether Webpack config contains `splitChunks.cacheGroups` option.
*
* @param webpackOptions
* @return {boolean}
*/
const hasSplitChunksCacheGroups = (webpackOptions) => {
return webpackOptions?.optimization?.splitChunks?.cacheGroups != null;
};
/**
* @param {Error} error
* @param {string} sourceFile
* @throws {Error}
*/
const executeFunctionException = (error, sourceFile) => {
const message = `Failed to execute the function.\nSource file: '${cyan(sourceFile)}'`;
throw new PluginException(message, error);
};
/**
* @param {Error} error
* @param {TemplateInfo} info
* @throws {Error}
*/
const postprocessException = (error, info) => {
const message = `Postprocess failed.\nSource file: ${cyan(info.sourceFile)}.`;
throw new PluginException(message, error);
};
/**
* @param {Error} error
* @param {string} file
* @throws {Error}
*/
const beforeEmitException = (error, file) => {
const message = `Before emit failed. Check you 'beforeEmit' option.\nSource file: ${cyan(file)}.`;
throw new PluginException(message, error);
};
/**
* @param {Error} error
* @return {PluginException} Return the error for promise.
*/
const afterEmitException = (error) => {
const message = `After emit failed. Check you 'afterEmit' option.`;
return new PluginException(message, error);
};
/**
* @throws {Error}
*/
const missingCrossOriginForIntegrityException = () => {
const message = `When using the ${yellow`'integrity'`} plugin option, must be specified the Webpack option ${yellow`'output.crossOriginLoading'`}.`;
throw new PluginException(message);
};
/**
* @param {string} sourceFile
* @throws {Error}
*/
const noHeadException = (sourceFile) => {
const message =
`The imported style can't be injected in HTML. The template not contains ${yellow`<head>`} tag.\n` +
`The template: ${cyan(sourceFile)}`;
throw new PluginException(message, null);
};
module.exports = {
PluginError,
PluginException,
optionEntryPathException,
optionPreloadAsException,
resolveException,
executeFunctionException,
postprocessException,
beforeEmitException,
afterEmitException,
missingCrossOriginForIntegrityException,
noHeadException,
};