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.
134 lines (116 loc) • 4.27 kB
JavaScript
const { readFileSync } = require('fs');
const path = require('path');
const { loadModule } = require('../../../Common/FileUtils');
const { stringifyJSON } = require('../../Utils');
const MarkdownFilter = require('../../PreprocessorFilters/markdown');
// replace the partial file and data to load nested included template via the Webpack loader
// include("./file.html") => require("./file.eta")({...locals, ...{}})
// include('./file.html', { name: 'Siri' }) => require('./file.eta')({...locals, ...{name: 'Siri'}})
const includeRegexp = /include\((.+?)(?:\)|,\s*{(.+?)}\))/g;
// node module name
const moduleName = 'ejs';
/**
* Require CommonJS or JSON file in EJS template.
*
* @param {string} file
* @param {string} dir
* @return {*}
*/
const requireFile = (file, dir) => {
const fullFilePath = path.join(dir, file);
return file.endsWith('.json') ? JSON.parse(readFileSync(fullFilePath, 'utf-8')) : require(fullFilePath);
};
/**
* Transform the raw template source to a template function or HTML.
*
* @param {BundlerPluginLoaderContext} loaderContext
* @param {{}} options
* @return {{compile: (function(string, {resourcePath: string, data?: {}}): *), render: (function(string, {resourcePath: string, data?: {}}): *), export: (function(string, {data: {}}): string)}}
*/
const preprocessor = (loaderContext, options) => {
const Ejs = loadModule(moduleName);
const { rootContext } = loaderContext;
const markdownFilterOptions = {
highlight: {
use: {
module: 'prismjs',
options: {
verbose: true, // display loaded dependencies
},
},
},
};
Ejs.fileLoader = (file) => {
const source = readFileSync(file, 'utf-8');
if (file.toLocaleLowerCase().endsWith('.md')) {
return MarkdownFilter.getInstance(markdownFilterOptions).apply(source);
}
return source;
};
return {
/**
* Unique preprocessor ID as the module name.
*/
id: moduleName,
/**
* Render template into HTML.
* Called for rendering of template defined as entry point.
*
* @param {string} source The template source code.
* @param {string} resourcePath
* @param {{}} data
* @return {string}
*/
render(source, { resourcePath, data = {} }) {
const contextPath = path.dirname(resourcePath);
data.require = (file) => requireFile(file, contextPath);
return Ejs.render(source, data, {
async: false,
root: rootContext, // root path for includes with an absolute path (e.g., /file.html)
...options,
filename: resourcePath, // allow including a partial relative to the template
});
},
/**
* Compile template into template function.
* Called when a template is loaded in JS in `compile` mode.
*
* @param {string} source The template source code.
* @param {string} resourcePath
* @param {{}} data
* @return {string}
*/
compile(source, { resourcePath, data = {} }) {
return Ejs.compile(source, {
compileDebug: false,
root: rootContext,
...options,
client: true,
async: false,
filename: resourcePath, // allow including a partial relative to the template
context: data,
})
.toString()
.replaceAll(includeRegexp, `require($1)({...locals, ...{$2}})`);
},
/**
* Export the compiled template function contained resolved source asset files.
* Note: this method is required for `compile` mode.
*
* @param {string} templateFunction The source code of the template function.
* @param {{}} data The object with external variables passed in template from data option.
* @return {string} The exported template function.
*/
export(templateFunction, { data }) {
// the name of template function in generated code
const exportFunctionName = 'anonymous';
const exportCode = 'module.exports=';
return `${templateFunction};
var data = ${stringifyJSON(data)};
var template = (context) => ${exportFunctionName}(Object.assign({}, data, context));
${exportCode}template;`;
},
};
};
module.exports = preprocessor;
module.exports.test = /\.(html|ejs)$/i;