123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- // @ts-check
- /** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */
- /** @typedef {import("webpack/lib/Compiler.js")} WebpackCompiler */
- /** @typedef {import("webpack/lib/Chunk.js")} WebpackChunk */
- 'use strict';
- /**
- * @file
- * This file uses webpack to compile a template with a child compiler.
- *
- * [TEMPLATE] -> [JAVASCRIPT]
- *
- */
- 'use strict';
- const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
- const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
- const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
- const LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin');
- const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
- /**
- * The HtmlWebpackChildCompiler is a helper to allow reusing one childCompiler
- * for multiple HtmlWebpackPlugin instances to improve the compilation performance.
- */
- class HtmlWebpackChildCompiler {
- /**
- *
- * @param {string[]} templates
- */
- constructor (templates) {
- /**
- * @type {string[]} templateIds
- * The template array will allow us to keep track which input generated which output
- */
- this.templates = templates;
- /**
- * @type {Promise<{[templatePath: string]: { content: string, hash: string, entry: WebpackChunk }}>}
- */
- this.compilationPromise; // eslint-disable-line
- /**
- * @type {number}
- */
- this.compilationStartedTimestamp; // eslint-disable-line
- /**
- * @type {number}
- */
- this.compilationEndedTimestamp; // eslint-disable-line
- /**
- * All file dependencies of the child compiler
- * @type {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}}
- */
- this.fileDependencies = { fileDependencies: [], contextDependencies: [], missingDependencies: [] };
- }
- /**
- * Returns true if the childCompiler is currently compiling
- * @returns {boolean}
- */
- isCompiling () {
- return !this.didCompile() && this.compilationStartedTimestamp !== undefined;
- }
- /**
- * Returns true if the childCompiler is done compiling
- */
- didCompile () {
- return this.compilationEndedTimestamp !== undefined;
- }
- /**
- * This function will start the template compilation
- * once it is started no more templates can be added
- *
- * @param {WebpackCompilation} mainCompilation
- * @returns {Promise<{[templatePath: string]: { content: string, hash: string, entry: WebpackChunk }}>}
- */
- compileTemplates (mainCompilation) {
- // To prevent multiple compilations for the same template
- // the compilation is cached in a promise.
- // If it already exists return
- if (this.compilationPromise) {
- return this.compilationPromise;
- }
- // The entry file is just an empty helper as the dynamic template
- // require is added in "loader.js"
- const outputOptions = {
- filename: '__child-[name]',
- publicPath: mainCompilation.outputOptions.publicPath
- };
- const compilerName = 'HtmlWebpackCompiler';
- // Create an additional child compiler which takes the template
- // and turns it into an Node.JS html factory.
- // This allows us to use loaders during the compilation
- const childCompiler = mainCompilation.createChildCompiler(compilerName, outputOptions);
- // The file path context which webpack uses to resolve all relative files to
- childCompiler.context = mainCompilation.compiler.context;
- // Compile the template to nodejs javascript
- new NodeTemplatePlugin(outputOptions).apply(childCompiler);
- new NodeTargetPlugin().apply(childCompiler);
- new LibraryTemplatePlugin('HTML_WEBPACK_PLUGIN_RESULT', 'var').apply(childCompiler);
- new LoaderTargetPlugin('node').apply(childCompiler);
- // Add all templates
- this.templates.forEach((template, index) => {
- new SingleEntryPlugin(childCompiler.context, template, `HtmlWebpackPlugin_${index}`).apply(childCompiler);
- });
- this.compilationStartedTimestamp = new Date().getTime();
- this.compilationPromise = new Promise((resolve, reject) => {
- childCompiler.runAsChild((err, entries, childCompilation) => {
- // Extract templates
- const compiledTemplates = entries
- ? extractHelperFilesFromCompilation(mainCompilation, childCompilation, outputOptions.filename, entries)
- : [];
- // Extract file dependencies
- if (entries) {
- this.fileDependencies = { fileDependencies: Array.from(childCompilation.fileDependencies), contextDependencies: Array.from(childCompilation.contextDependencies), missingDependencies: Array.from(childCompilation.missingDependencies) };
- }
- // Reject the promise if the childCompilation contains error
- if (childCompilation && childCompilation.errors && childCompilation.errors.length) {
- const errorDetails = childCompilation.errors.map(error => {
- let message = error.message;
- if (error.error) {
- message += ':\n' + error.error;
- }
- if (error.stack) {
- message += '\n' + error.stack;
- }
- return message;
- }).join('\n');
- reject(new Error('Child compilation failed:\n' + errorDetails));
- return;
- }
- // Reject if the error object contains errors
- if (err) {
- reject(err);
- return;
- }
- /**
- * @type {{[templatePath: string]: { content: string, hash: string, entry: WebpackChunk }}}
- */
- const result = {};
- compiledTemplates.forEach((templateSource, entryIndex) => {
- // The compiledTemplates are generated from the entries added in
- // the addTemplate function.
- // Therefore the array index of this.templates should be the as entryIndex.
- result[this.templates[entryIndex]] = {
- content: templateSource,
- hash: childCompilation.hash,
- entry: entries[entryIndex]
- };
- });
- this.compilationEndedTimestamp = new Date().getTime();
- resolve(result);
- });
- });
- return this.compilationPromise;
- }
- }
- /**
- * The webpack child compilation will create files as a side effect.
- * This function will extract them and clean them up so they won't be written to disk.
- *
- * Returns the source code of the compiled templates as string
- *
- * @returns Array<string>
- */
- function extractHelperFilesFromCompilation (mainCompilation, childCompilation, filename, childEntryChunks) {
- const webpackMajorVersion = Number(require('webpack/package.json').version.split('.')[0]);
- const helperAssetNames = childEntryChunks.map((entryChunk, index) => {
- const entryConfig = {
- hash: childCompilation.hash,
- chunk: entryChunk,
- name: `HtmlWebpackPlugin_${index}`
- };
- return webpackMajorVersion === 4
- ? mainCompilation.mainTemplate.getAssetPath(filename, entryConfig)
- : mainCompilation.getAssetPath(filename, entryConfig);
- });
- helperAssetNames.forEach((helperFileName) => {
- delete mainCompilation.assets[helperFileName];
- });
- const helperContents = helperAssetNames.map((helperFileName) => {
- return childCompilation.assets[helperFileName].source();
- });
- return helperContents;
- }
- module.exports = {
- HtmlWebpackChildCompiler
- };
|