UNPKG

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.

181 lines (157 loc) 5.55 kB
const path = require('path'); const { eachAsync, makeTemplateId, stringifyJSON } = require('../../Utils'); const { loadModule } = require('../../../Common/FileUtils'); // node module name const moduleName = 'twig'; const preprocessor = (loaderContext, options) => { const TwigEngine = loadModule(moduleName, () => require(moduleName)); const Twig = TwigEngine.twig; const rootContext = loaderContext.rootContext || process.cwd(); const async = options?.async === true; const dependencies = new Set(); let template = {}; /** * Resolve absolute paths in token values. * * @param {Object} token The template token. * @return {Promise<void>} */ const resolveDependency = async (token) => { // if the `namespaces` twig option contains not absolute path, then a parsed path is the path relative to root context const filePath = TwigEngine.path.parsePath(template, token.value); let file = path.isAbsolute(filePath) ? filePath : path.resolve(rootContext, filePath); token.value = makeTemplateId(rootContext, file); // fix windows-like path file = file.replace(/\\/g, '/'); dependencies.add(file); loaderContext.addDependency(file); }; /** * Walk through the token tree and resolve include/extends. * * @param {Object} token The template token. * @return {Promise<void>} */ const processToken = async (token) => { if (token.type !== 'logic' || !token.token.type) { return; } switch (token.token.type) { case 'Twig.logic.type.apply': case 'Twig.logic.type.block': case 'Twig.logic.type.if': case 'Twig.logic.type.elseif': case 'Twig.logic.type.else': case 'Twig.logic.type.for': case 'Twig.logic.type.macro': case 'Twig.logic.type.spaceless': case 'Twig.logic.type.setcapture': { await eachAsync(token.token.output, processToken); break; } case 'Twig.logic.type.extends': case 'Twig.logic.type.include': { await eachAsync(token.token.stack, resolveDependency); break; } case 'Twig.logic.type.embed': { await eachAsync(token.token.output, processToken); await eachAsync(token.token.stack, resolveDependency); break; } case 'Twig.logic.type.import': case 'Twig.logic.type.from': if (token.token.expression !== '_self') { await eachAsync(token.token.stack, resolveDependency); } break; } }; TwigEngine.cache(false); 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. * @param {string} resourcePath * @param {{}} data * @return {string} */ render(source, { resourcePath, data = {} }) { const twigOptions = { ...options, async, data: source, }; return Twig(twigOptions).render(data); }, /** * Compile template into template function. * Called when a template is loaded in JS in `compile` mode. * * @param {string} source The template source code. * @param {BundlerPluginLoaderContext} loaderContext * @return {Promise} */ compile(source, loaderContext) { const { resourcePath, data = {} } = loaderContext; return new Promise((resolve, reject) => { const twigOptions = { ...options, async, id: makeTemplateId(rootContext, resourcePath), path: resourcePath, data: source, allowInlineIncludes: true, rethrow: true, }; // set global `template` variable used in an async function defined in global scope template = Twig(twigOptions); eachAsync(template.tokens, processToken).then(() => { const twigOptions = { id: template.id, data: template.tokens, allowInlineIncludes: true, rethrow: true, precompiled: true, }; const precompiledTemplate = JSON.stringify(twigOptions); resolve(precompiledTemplate); }); }); }, /** * Export the compiled template function contained resolved source asset files. * Note: this method is required for `compile` mode. * * @param {string} precompiledTemplate The code of the precompiled template. * @param {{}} data The object with external variables passed in template from data option. * @param {boolean} hot * @return {string} The exported template function. */ export(precompiledTemplate, { data, hot }) { // fix windows-like path const runtimeFile = require.resolve('twig/twig.min.js').replace(/\\/g, '/'); const exportFunctionName = 'templateFn'; const exportCode = 'module.exports='; let loadDependencies = ''; for (let dep of dependencies) { loadDependencies += `require('${dep}');`; } return `${loadDependencies} var Twig = require('${runtimeFile}'); ${hot === true ? `Twig.cache(false);` : ''} var data = ${stringifyJSON(data)}; var template = Twig.twig(${precompiledTemplate}); var ${exportFunctionName} = (context) => template.render(Object.assign({}, data, context)); ${exportCode}${exportFunctionName};`; }, }; }; module.exports = preprocessor; module.exports.test = /\.(html|twig)$/i;