source: frontend/node_modules/html-webpack-plugin/index.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 48.2 KB
Line 
1// @ts-check
2"use strict";
3
4const promisify = require("util").promisify;
5
6const vm = require("vm");
7const fs = require("fs");
8const path = require("path");
9const { CachedChildCompilation } = require("./lib/cached-child-compiler");
10
11const {
12 createHtmlTagObject,
13 htmlTagObjectToString,
14 HtmlTagArray,
15} = require("./lib/html-tags");
16const prettyError = require("./lib/errors.js");
17const chunkSorter = require("./lib/chunksorter.js");
18const { AsyncSeriesWaterfallHook } = require("tapable");
19
20/** @typedef {import("./typings").HtmlTagObject} HtmlTagObject */
21/** @typedef {import("./typings").Options} HtmlWebpackOptions */
22/** @typedef {import("./typings").ProcessedOptions} ProcessedHtmlWebpackOptions */
23/** @typedef {import("./typings").TemplateParameter} TemplateParameter */
24/** @typedef {import("webpack").Compiler} Compiler */
25/** @typedef {import("webpack").Compilation} Compilation */
26/** @typedef {Required<Compilation["outputOptions"]["publicPath"]>} PublicPath */
27/** @typedef {ReturnType<Compiler["getInfrastructureLogger"]>} Logger */
28/** @typedef {Compilation["entrypoints"] extends Map<string, infer I> ? I : never} Entrypoint */
29/** @typedef {Array<{ name: string, source: import('webpack').sources.Source, info?: import('webpack').AssetInfo }>} PreviousEmittedAssets */
30/** @typedef {{ publicPath: string, js: Array<string>, css: Array<string>, manifest?: string, favicon?: string }} AssetsInformationByGroups */
31/** @typedef {import("./typings").Hooks} HtmlWebpackPluginHooks */
32/**
33 * @type {WeakMap<Compilation, HtmlWebpackPluginHooks>}}
34 */
35const compilationHooksMap = new WeakMap();
36
37class HtmlWebpackPlugin {
38 // The following is the API definition for all available hooks
39 // For the TypeScript definition, see the Hooks type in typings.d.ts
40 /**
41 beforeAssetTagGeneration:
42 AsyncSeriesWaterfallHook<{
43 assets: {
44 publicPath: string,
45 js: Array<string>,
46 css: Array<string>,
47 favicon?: string | undefined,
48 manifest?: string | undefined
49 },
50 outputName: string,
51 plugin: HtmlWebpackPlugin
52 }>,
53 alterAssetTags:
54 AsyncSeriesWaterfallHook<{
55 assetTags: {
56 scripts: Array<HtmlTagObject>,
57 styles: Array<HtmlTagObject>,
58 meta: Array<HtmlTagObject>,
59 },
60 publicPath: string,
61 outputName: string,
62 plugin: HtmlWebpackPlugin
63 }>,
64 alterAssetTagGroups:
65 AsyncSeriesWaterfallHook<{
66 headTags: Array<HtmlTagObject | HtmlTagObject>,
67 bodyTags: Array<HtmlTagObject | HtmlTagObject>,
68 publicPath: string,
69 outputName: string,
70 plugin: HtmlWebpackPlugin
71 }>,
72 afterTemplateExecution:
73 AsyncSeriesWaterfallHook<{
74 html: string,
75 headTags: Array<HtmlTagObject | HtmlTagObject>,
76 bodyTags: Array<HtmlTagObject | HtmlTagObject>,
77 outputName: string,
78 plugin: HtmlWebpackPlugin,
79 }>,
80 beforeEmit:
81 AsyncSeriesWaterfallHook<{
82 html: string,
83 outputName: string,
84 plugin: HtmlWebpackPlugin,
85 }>,
86 afterEmit:
87 AsyncSeriesWaterfallHook<{
88 outputName: string,
89 plugin: HtmlWebpackPlugin
90 }>
91 */
92
93 /**
94 * Returns all public hooks of the html webpack plugin for the given compilation
95 *
96 * @param {Compilation} compilation
97 * @returns {HtmlWebpackPluginHooks}
98 */
99 static getCompilationHooks(compilation) {
100 let hooks = compilationHooksMap.get(compilation);
101
102 if (!hooks) {
103 hooks = {
104 beforeAssetTagGeneration: new AsyncSeriesWaterfallHook(["pluginArgs"]),
105 alterAssetTags: new AsyncSeriesWaterfallHook(["pluginArgs"]),
106 alterAssetTagGroups: new AsyncSeriesWaterfallHook(["pluginArgs"]),
107 afterTemplateExecution: new AsyncSeriesWaterfallHook(["pluginArgs"]),
108 beforeEmit: new AsyncSeriesWaterfallHook(["pluginArgs"]),
109 afterEmit: new AsyncSeriesWaterfallHook(["pluginArgs"]),
110 };
111 compilationHooksMap.set(compilation, hooks);
112 }
113
114 return hooks;
115 }
116
117 /**
118 * @param {HtmlWebpackOptions} [options]
119 */
120 constructor(options) {
121 /** @type {HtmlWebpackOptions} */
122 // TODO remove me in the next major release
123 this.userOptions = options || {};
124 this.version = HtmlWebpackPlugin.version;
125
126 // Default options
127 /** @type {ProcessedHtmlWebpackOptions} */
128 const defaultOptions = {
129 template: "auto",
130 templateContent: false,
131 templateParameters: templateParametersGenerator,
132 filename: "index.html",
133 publicPath:
134 this.userOptions.publicPath === undefined
135 ? "auto"
136 : this.userOptions.publicPath,
137 hash: false,
138 inject: this.userOptions.scriptLoading === "blocking" ? "body" : "head",
139 scriptLoading: "defer",
140 compile: true,
141 favicon: false,
142 minify: "auto",
143 cache: true,
144 showErrors: true,
145 chunks: "all",
146 excludeChunks: [],
147 chunksSortMode: "auto",
148 meta: {},
149 base: false,
150 title: "Webpack App",
151 xhtml: false,
152 };
153
154 /** @type {ProcessedHtmlWebpackOptions} */
155 this.options = Object.assign(defaultOptions, this.userOptions);
156 }
157
158 /**
159 *
160 * @param {Compiler} compiler
161 * @returns {void}
162 */
163 apply(compiler) {
164 this.logger = compiler.getInfrastructureLogger("HtmlWebpackPlugin");
165
166 const options = this.options;
167
168 options.template = this.getTemplatePath(
169 this.options.template,
170 compiler.context,
171 );
172
173 // Assert correct option spelling
174 if (
175 options.scriptLoading !== "defer" &&
176 options.scriptLoading !== "blocking" &&
177 options.scriptLoading !== "module" &&
178 options.scriptLoading !== "systemjs-module"
179 ) {
180 /** @type {Logger} */
181 (this.logger).error(
182 'The "scriptLoading" option need to be set to "defer", "blocking" or "module" or "systemjs-module"',
183 );
184 }
185
186 if (
187 options.inject !== true &&
188 options.inject !== false &&
189 options.inject !== "head" &&
190 options.inject !== "body"
191 ) {
192 /** @type {Logger} */
193 (this.logger).error(
194 'The `inject` option needs to be set to true, false, "head" or "body',
195 );
196 }
197
198 if (
199 this.options.templateParameters !== false &&
200 typeof this.options.templateParameters !== "function" &&
201 typeof this.options.templateParameters !== "object"
202 ) {
203 /** @type {Logger} */
204 (this.logger).error(
205 "The `templateParameters` has to be either a function or an object or false",
206 );
207 }
208
209 // Default metaOptions if no template is provided
210 if (
211 !this.userOptions.template &&
212 options.templateContent === false &&
213 options.meta
214 ) {
215 options.meta = Object.assign(
216 {},
217 options.meta,
218 {
219 // TODO remove in the next major release
220 // From https://developer.mozilla.org/en-US/docs/Mozilla/Mobile/Viewport_meta_tag
221 viewport: "width=device-width, initial-scale=1",
222 },
223 this.userOptions.meta,
224 );
225 }
226
227 // entryName to fileName conversion function
228 const userOptionFilename =
229 this.userOptions.filename || this.options.filename;
230 const filenameFunction =
231 typeof userOptionFilename === "function"
232 ? userOptionFilename
233 : // Replace '[name]' with entry name
234 (entryName) => userOptionFilename.replace(/\[name\]/g, entryName);
235
236 /** output filenames for the given entry names */
237 const entryNames = Object.keys(compiler.options.entry);
238 const outputFileNames = new Set(
239 (entryNames.length ? entryNames : ["main"]).map(filenameFunction),
240 );
241
242 // Hook all options into the webpack compiler
243 outputFileNames.forEach((outputFileName) => {
244 // Instance variables to keep caching information for multiple builds
245 const assetJson = { value: undefined };
246 /**
247 * store the previous generated asset to emit them even if the content did not change
248 * to support watch mode for third party plugins like the clean-webpack-plugin or the compression plugin
249 * @type {PreviousEmittedAssets}
250 */
251 const previousEmittedAssets = [];
252
253 // Inject child compiler plugin
254 const childCompilerPlugin = new CachedChildCompilation(compiler);
255
256 if (!this.options.templateContent) {
257 childCompilerPlugin.addEntry(this.options.template);
258 }
259
260 // convert absolute filename into relative so that webpack can
261 // generate it at correct location
262 let filename = outputFileName;
263
264 if (path.resolve(filename) === path.normalize(filename)) {
265 const outputPath =
266 /** @type {string} - Once initialized the path is always a string */ (
267 compiler.options.output.path
268 );
269
270 filename = path.relative(outputPath, filename);
271 }
272
273 compiler.hooks.thisCompilation.tap(
274 "HtmlWebpackPlugin",
275 /**
276 * Hook into the webpack compilation
277 * @param {Compilation} compilation
278 */
279 (compilation) => {
280 compilation.hooks.processAssets.tapAsync(
281 {
282 name: "HtmlWebpackPlugin",
283 stage:
284 /**
285 * Generate the html after minification and dev tooling is done
286 */
287 compiler.webpack.Compilation
288 .PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE,
289 },
290 /**
291 * Hook into the process assets hook
292 * @param {any} _
293 * @param {(err?: Error) => void} callback
294 */
295 (_, callback) => {
296 this.generateHTML(
297 compiler,
298 compilation,
299 filename,
300 childCompilerPlugin,
301 previousEmittedAssets,
302 assetJson,
303 callback,
304 );
305 },
306 );
307 },
308 );
309 });
310 }
311
312 /**
313 * Helper to return the absolute template path with a fallback loader
314 *
315 * @private
316 * @param {string} template The path to the template e.g. './index.html'
317 * @param {string} context The webpack base resolution path for relative paths e.g. process.cwd()
318 */
319 getTemplatePath(template, context) {
320 if (template === "auto") {
321 template = path.resolve(context, "src/index.ejs");
322 if (!fs.existsSync(template)) {
323 template = path.join(__dirname, "default_index.ejs");
324 }
325 }
326
327 // If the template doesn't use a loader use the lodash template loader
328 if (template.indexOf("!") === -1) {
329 template =
330 require.resolve("./lib/loader.js") +
331 "!" +
332 path.resolve(context, template);
333 }
334
335 // Resolve template path
336 return template.replace(
337 /([!])([^/\\][^!?]+|[^/\\!?])($|\?[^!?\n]+$)/,
338 (match, prefix, filepath, postfix) =>
339 prefix + path.resolve(filepath) + postfix,
340 );
341 }
342
343 /**
344 * Return all chunks from the compilation result which match the exclude and include filters
345 *
346 * @private
347 * @param {any} chunks
348 * @param {string[]|'all'} includedChunks
349 * @param {string[]} excludedChunks
350 */
351 filterEntryChunks(chunks, includedChunks, excludedChunks) {
352 return chunks.filter((chunkName) => {
353 // Skip if the chunks should be filtered and the given chunk was not added explicity
354 if (
355 Array.isArray(includedChunks) &&
356 includedChunks.indexOf(chunkName) === -1
357 ) {
358 return false;
359 }
360
361 // Skip if the chunks should be filtered and the given chunk was excluded explicity
362 if (
363 Array.isArray(excludedChunks) &&
364 excludedChunks.indexOf(chunkName) !== -1
365 ) {
366 return false;
367 }
368
369 // Add otherwise
370 return true;
371 });
372 }
373
374 /**
375 * Helper to sort chunks
376 *
377 * @private
378 * @param {string[]} entryNames
379 * @param {string|((entryNameA: string, entryNameB: string) => number)} sortMode
380 * @param {Compilation} compilation
381 */
382 sortEntryChunks(entryNames, sortMode, compilation) {
383 // Custom function
384 if (typeof sortMode === "function") {
385 return entryNames.sort(sortMode);
386 }
387 // Check if the given sort mode is a valid chunkSorter sort mode
388 if (typeof chunkSorter[sortMode] !== "undefined") {
389 return chunkSorter[sortMode](entryNames, compilation, this.options);
390 }
391 throw new Error('"' + sortMode + '" is not a valid chunk sort mode');
392 }
393
394 /**
395 * Encode each path component using `encodeURIComponent` as files can contain characters
396 * which needs special encoding in URLs like `+ `.
397 *
398 * Valid filesystem characters which need to be encoded for urls:
399 *
400 * # pound, % percent, & ampersand, { left curly bracket, } right curly bracket,
401 * \ back slash, < left angle bracket, > right angle bracket, * asterisk, ? question mark,
402 * blank spaces, $ dollar sign, ! exclamation point, ' single quotes, " double quotes,
403 * : colon, @ at sign, + plus sign, ` backtick, | pipe, = equal sign
404 *
405 * However the query string must not be encoded:
406 *
407 * fo:demonstration-path/very fancy+name.js?path=/home?value=abc&value=def#zzz
408 * ^ ^ ^ ^ ^ ^ ^ ^^ ^ ^ ^ ^ ^
409 * | | | | | | | || | | | | |
410 * encoded | | encoded | | || | | | | |
411 * ignored ignored ignored ignored ignored
412 *
413 * @private
414 * @param {string} filePath
415 */
416 urlencodePath(filePath) {
417 // People use the filepath in quite unexpected ways.
418 // Try to extract the first querystring of the url:
419 //
420 // some+path/demo.html?value=abc?def
421 //
422 const queryStringStart = filePath.indexOf("?");
423 const urlPath =
424 queryStringStart === -1 ? filePath : filePath.substr(0, queryStringStart);
425 const queryString = filePath.substr(urlPath.length);
426 // Encode all parts except '/' which are not part of the querystring:
427 const encodedUrlPath = urlPath.split("/").map(encodeURIComponent).join("/");
428 return encodedUrlPath + queryString;
429 }
430
431 /**
432 * Appends a cache busting hash to the query string of the url
433 * E.g. http://localhost:8080/ -> http://localhost:8080/?50c9096ba6183fd728eeb065a26ec175
434 *
435 * @private
436 * @param {string | undefined} url
437 * @param {string} hash
438 */
439 appendHash(url, hash) {
440 if (!url) {
441 return url;
442 }
443
444 return url + (url.indexOf("?") === -1 ? "?" : "&") + hash;
445 }
446
447 /**
448 * Generate the relative or absolute base url to reference images, css, and javascript files
449 * from within the html file - the publicPath
450 *
451 * @private
452 * @param {Compilation} compilation
453 * @param {string} filename
454 * @param {string | 'auto'} customPublicPath
455 * @returns {string}
456 */
457 getPublicPath(compilation, filename, customPublicPath) {
458 /**
459 * @type {string} the configured public path to the asset root
460 * if a path publicPath is set in the current webpack config use it otherwise
461 * fallback to a relative path
462 */
463 const webpackPublicPath = compilation.getAssetPath(
464 /** @type {NonNullable<Compilation["outputOptions"]["publicPath"]>} */ (
465 compilation.outputOptions.publicPath
466 ),
467 { hash: compilation.hash },
468 );
469 // Webpack 5 introduced "auto" as default value
470 const isPublicPathDefined = webpackPublicPath !== "auto";
471
472 let publicPath =
473 // If the html-webpack-plugin options contain a custom public path unset it
474 customPublicPath !== "auto"
475 ? customPublicPath
476 : isPublicPathDefined
477 ? // If a hard coded public path exists use it
478 webpackPublicPath
479 : // If no public path was set get a relative url path
480 path
481 .relative(
482 path.resolve(
483 /** @type {string} */ (compilation.options.output.path),
484 path.dirname(filename),
485 ),
486 /** @type {string} */ (compilation.options.output.path),
487 )
488 .split(path.sep)
489 .join("/");
490
491 if (publicPath.length && publicPath.substr(-1, 1) !== "/") {
492 publicPath += "/";
493 }
494
495 return publicPath;
496 }
497
498 /**
499 * The getAssetsForHTML extracts the asset information of a webpack compilation for all given entry names.
500 *
501 * @private
502 * @param {Compilation} compilation
503 * @param {string} outputName
504 * @param {string[]} entryNames
505 * @returns {AssetsInformationByGroups}
506 */
507 getAssetsInformationByGroups(compilation, outputName, entryNames) {
508 /** The public path used inside the html file */
509 const publicPath = this.getPublicPath(
510 compilation,
511 outputName,
512 this.options.publicPath,
513 );
514 /**
515 * @type {AssetsInformationByGroups}
516 */
517 const assets = {
518 // The public path
519 publicPath,
520 // Will contain all js and mjs files
521 js: [],
522 // Will contain all css files
523 css: [],
524 // Will contain the html5 appcache manifest files if it exists
525 manifest: Object.keys(compilation.assets).find(
526 (assetFile) => path.extname(assetFile) === ".appcache",
527 ),
528 // Favicon
529 favicon: undefined,
530 };
531
532 // Append a hash for cache busting
533 if (this.options.hash && assets.manifest) {
534 assets.manifest = this.appendHash(
535 assets.manifest,
536 /** @type {string} */ (compilation.hash),
537 );
538 }
539
540 // Extract paths to .js, .mjs and .css files from the current compilation
541 const entryPointPublicPathMap = {};
542 const extensionRegexp = /\.(css|js|mjs)(\?|$)/;
543
544 for (let i = 0; i < entryNames.length; i++) {
545 const entryName = entryNames[i];
546 /** entryPointUnfilteredFiles - also includes hot module update files */
547 const entryPointUnfilteredFiles = /** @type {Entrypoint} */ (
548 compilation.entrypoints.get(entryName)
549 ).getFiles();
550 const entryPointFiles = entryPointUnfilteredFiles.filter((chunkFile) => {
551 const asset = compilation.getAsset(chunkFile);
552
553 if (!asset) {
554 return true;
555 }
556
557 // Prevent hot-module files from being included:
558 const assetMetaInformation = asset.info || {};
559
560 return !(
561 assetMetaInformation.hotModuleReplacement ||
562 assetMetaInformation.development
563 );
564 });
565 // Prepend the publicPath and append the hash depending on the
566 // webpack.output.publicPath and hashOptions
567 // E.g. bundle.js -> /bundle.js?hash
568 const entryPointPublicPaths = entryPointFiles.map((chunkFile) => {
569 const entryPointPublicPath = publicPath + this.urlencodePath(chunkFile);
570 return this.options.hash
571 ? this.appendHash(
572 entryPointPublicPath,
573 /** @type {string} */ (compilation.hash),
574 )
575 : entryPointPublicPath;
576 });
577
578 entryPointPublicPaths.forEach((entryPointPublicPath) => {
579 const extMatch = extensionRegexp.exec(
580 /** @type {string} */ (entryPointPublicPath),
581 );
582
583 // Skip if the public path is not a .css, .mjs or .js file
584 if (!extMatch) {
585 return;
586 }
587
588 // Skip if this file is already known
589 // (e.g. because of common chunk optimizations)
590 if (entryPointPublicPathMap[entryPointPublicPath]) {
591 return;
592 }
593
594 entryPointPublicPathMap[entryPointPublicPath] = true;
595
596 // ext will contain .js or .css, because .mjs recognizes as .js
597 const ext = extMatch[1] === "mjs" ? "js" : extMatch[1];
598
599 assets[ext].push(entryPointPublicPath);
600 });
601 }
602
603 return assets;
604 }
605
606 /**
607 * Once webpack is done with compiling the template into a NodeJS code this function
608 * evaluates it to generate the html result
609 *
610 * The evaluateCompilationResult is only a class function to allow spying during testing.
611 * Please change that in a further refactoring
612 *
613 * @param {string} source
614 * @param {string} publicPath
615 * @param {string} templateFilename
616 * @returns {Promise<string | (() => string | Promise<string>)>}
617 */
618 evaluateCompilationResult(source, publicPath, templateFilename) {
619 if (!source) {
620 return Promise.reject(
621 new Error("The child compilation didn't provide a result"),
622 );
623 }
624
625 // The LibraryTemplatePlugin stores the template result in a local variable.
626 // By adding it to the end the value gets extracted during evaluation
627 if (source.indexOf("HTML_WEBPACK_PLUGIN_RESULT") >= 0) {
628 source += ";\nHTML_WEBPACK_PLUGIN_RESULT";
629 }
630
631 const templateWithoutLoaders = templateFilename
632 .replace(/^.+!/, "")
633 .replace(/\?.+$/, "");
634 const globalClone = Object.create(
635 Object.getPrototypeOf(global),
636 Object.getOwnPropertyDescriptors(global),
637 );
638 // Presence of `eval` and `Function` breaks template's explicit `eval` call
639 // Ref: https://github.com/nodejs/help/issues/2880
640 delete globalClone.eval;
641 delete globalClone.Function;
642 // Not using `...global` as it throws when localStorage is not explicitly enabled in Node 25+
643 // Provide a CommonJS-style `module`/`exports` pair so templates compiled as CommonJS
644 // (e.g. Rspack's child compilation output, which wraps the result in `module.exports = ...`)
645 // can assign to them instead of failing with `module is not defined`.
646 const sandboxModule = { exports: {} };
647 const vmContext = vm.createContext(
648 Object.assign(globalClone, {
649 HTML_WEBPACK_PLUGIN: true,
650 // Copying nonstandard globals like `require` explicitly as they may be absent from `global`
651 require: require,
652 module: sandboxModule,
653 exports: sandboxModule.exports,
654 htmlWebpackPluginPublicPath: publicPath,
655 __filename: templateWithoutLoaders,
656 __dirname: path.dirname(templateWithoutLoaders),
657 }),
658 );
659
660 const vmScript = new vm.Script(source, {
661 filename: templateWithoutLoaders,
662 });
663
664 // Evaluate code and cast to string
665 let newSource;
666
667 try {
668 newSource = vmScript.runInContext(vmContext);
669 } catch (e) {
670 return Promise.reject(e);
671 }
672
673 if (
674 typeof newSource === "object" &&
675 newSource.__esModule &&
676 newSource.default !== undefined
677 ) {
678 newSource = newSource.default;
679 }
680
681 return typeof newSource === "string" || typeof newSource === "function"
682 ? Promise.resolve(newSource)
683 : Promise.reject(
684 new Error(
685 'The loader "' + templateWithoutLoaders + "\" didn't return html.",
686 ),
687 );
688 }
689
690 /**
691 * Add toString methods for easier rendering inside the template
692 *
693 * @private
694 * @param {Array<HtmlTagObject>} assetTagGroup
695 * @returns {Array<HtmlTagObject>}
696 */
697 prepareAssetTagGroupForRendering(assetTagGroup) {
698 const xhtml = this.options.xhtml;
699 return HtmlTagArray.from(
700 assetTagGroup.map((assetTag) => {
701 const copiedAssetTag = Object.assign({}, assetTag);
702 copiedAssetTag.toString = function () {
703 return htmlTagObjectToString(this, xhtml);
704 };
705 return copiedAssetTag;
706 }),
707 );
708 }
709
710 /**
711 * Generate the template parameters for the template function
712 *
713 * @private
714 * @param {Compilation} compilation
715 * @param {AssetsInformationByGroups} assetsInformationByGroups
716 * @param {{
717 headTags: HtmlTagObject[],
718 bodyTags: HtmlTagObject[]
719 }} assetTags
720 * @returns {Promise<{[key: any]: any}>}
721 */
722 getTemplateParameters(compilation, assetsInformationByGroups, assetTags) {
723 const templateParameters = this.options.templateParameters;
724
725 if (templateParameters === false) {
726 return Promise.resolve({});
727 }
728
729 if (
730 typeof templateParameters !== "function" &&
731 typeof templateParameters !== "object"
732 ) {
733 throw new Error(
734 "templateParameters has to be either a function or an object",
735 );
736 }
737
738 const templateParameterFunction =
739 typeof templateParameters === "function"
740 ? // A custom function can overwrite the entire template parameter preparation
741 templateParameters
742 : // If the template parameters is an object merge it with the default values
743 (compilation, assetsInformationByGroups, assetTags, options) =>
744 Object.assign(
745 {},
746 templateParametersGenerator(
747 compilation,
748 assetsInformationByGroups,
749 assetTags,
750 options,
751 ),
752 templateParameters,
753 );
754 const preparedAssetTags = {
755 headTags: this.prepareAssetTagGroupForRendering(assetTags.headTags),
756 bodyTags: this.prepareAssetTagGroupForRendering(assetTags.bodyTags),
757 };
758 return Promise.resolve().then(() =>
759 templateParameterFunction(
760 compilation,
761 assetsInformationByGroups,
762 preparedAssetTags,
763 this.options,
764 ),
765 );
766 }
767
768 /**
769 * This function renders the actual html by executing the template function
770 *
771 * @private
772 * @param {(templateParameters) => string | Promise<string>} templateFunction
773 * @param {AssetsInformationByGroups} assetsInformationByGroups
774 * @param {{
775 headTags: HtmlTagObject[],
776 bodyTags: HtmlTagObject[]
777 }} assetTags
778 * @param {Compilation} compilation
779 * @returns Promise<string>
780 */
781 executeTemplate(
782 templateFunction,
783 assetsInformationByGroups,
784 assetTags,
785 compilation,
786 ) {
787 // Template processing
788 const templateParamsPromise = this.getTemplateParameters(
789 compilation,
790 assetsInformationByGroups,
791 assetTags,
792 );
793
794 return templateParamsPromise.then((templateParams) => {
795 try {
796 // If html is a promise return the promise
797 // If html is a string turn it into a promise
798 return templateFunction(templateParams);
799 } catch (e) {
800 // @ts-ignore
801 compilation.errors.push(new Error("Template execution failed: " + e));
802 return Promise.reject(e);
803 }
804 });
805 }
806
807 /**
808 * Html Post processing
809 *
810 * @private
811 * @param {Compiler} compiler The compiler instance
812 * @param {any} originalHtml The input html
813 * @param {AssetsInformationByGroups} assetsInformationByGroups
814 * @param {{headTags: HtmlTagObject[], bodyTags: HtmlTagObject[]}} assetTags The asset tags to inject
815 * @returns {Promise<string>}
816 */
817 postProcessHtml(
818 compiler,
819 originalHtml,
820 assetsInformationByGroups,
821 assetTags,
822 ) {
823 let html = originalHtml;
824
825 if (typeof html !== "string") {
826 return Promise.reject(
827 new Error(
828 "Expected html to be a string but got " + JSON.stringify(html),
829 ),
830 );
831 }
832
833 if (this.options.inject) {
834 const htmlRegExp = /(<html[^>]*>)/i;
835 const headRegExp = /(<\/head\s*>)/i;
836 const bodyRegExp = /(<\/body\s*>)/i;
837 const metaViewportRegExp = /<meta[^>]+name=["']viewport["'][^>]*>/i;
838 const body = assetTags.bodyTags.map((assetTagObject) =>
839 htmlTagObjectToString(assetTagObject, this.options.xhtml),
840 );
841 const head = assetTags.headTags
842 .filter((item) => {
843 if (
844 item.tagName === "meta" &&
845 item.attributes &&
846 item.attributes.name === "viewport" &&
847 metaViewportRegExp.test(html)
848 ) {
849 return false;
850 }
851
852 return true;
853 })
854 .map((assetTagObject) =>
855 htmlTagObjectToString(assetTagObject, this.options.xhtml),
856 );
857
858 if (body.length) {
859 if (bodyRegExp.test(html)) {
860 // Append assets to body element
861 html = html.replace(bodyRegExp, (match) => body.join("") + match);
862 } else {
863 // Append scripts to the end of the file if no <body> element exists:
864 html += body.join("");
865 }
866 }
867
868 if (head.length) {
869 // Create a head tag if none exists
870 if (!headRegExp.test(html)) {
871 if (!htmlRegExp.test(html)) {
872 html = "<head></head>" + html;
873 } else {
874 html = html.replace(htmlRegExp, (match) => match + "<head></head>");
875 }
876 }
877
878 // Append assets to head element
879 html = html.replace(headRegExp, (match) => head.join("") + match);
880 }
881
882 // Inject manifest into the opening html tag
883 if (assetsInformationByGroups.manifest) {
884 html = html.replace(/(<html[^>]*)(>)/i, (match, start, end) => {
885 // Append the manifest only if no manifest was specified
886 if (/\smanifest\s*=/.test(match)) {
887 return match;
888 }
889 return (
890 start +
891 ' manifest="' +
892 assetsInformationByGroups.manifest +
893 '"' +
894 end
895 );
896 });
897 }
898 }
899
900 // TODO avoid this logic and use https://github.com/webpack-contrib/html-minimizer-webpack-plugin under the hood in the next major version
901 // Check if webpack is running in production mode
902 // @see https://github.com/webpack/webpack/blob/3366421f1784c449f415cda5930a8e445086f688/lib/WebpackOptionsDefaulter.js#L12-L14
903 const isProductionLikeMode =
904 compiler.options.mode === "production" || !compiler.options.mode;
905 const needMinify =
906 this.options.minify === true ||
907 typeof this.options.minify === "object" ||
908 (this.options.minify === "auto" && isProductionLikeMode);
909
910 if (!needMinify) {
911 return Promise.resolve(html);
912 }
913
914 const minifyOptions =
915 typeof this.options.minify === "object"
916 ? this.options.minify
917 : {
918 // https://www.npmjs.com/package/html-minifier-terser#options-quick-reference
919 collapseWhitespace: true,
920 keepClosingSlash: true,
921 removeComments: true,
922 removeRedundantAttributes: true,
923 removeScriptTypeAttributes: true,
924 removeStyleLinkTypeAttributes: true,
925 useShortDoctype: true,
926 };
927
928 try {
929 html = require("html-minifier-terser").minify(html, minifyOptions);
930 } catch (e) {
931 const isParseError = String(e.message).indexOf("Parse Error") === 0;
932
933 if (isParseError) {
934 e.message =
935 "html-webpack-plugin could not minify the generated output.\n" +
936 "In production mode the html minification is enabled by default.\n" +
937 "If you are not generating a valid html output please disable it manually.\n" +
938 "You can do so by adding the following setting to your HtmlWebpackPlugin config:\n|\n|" +
939 " minify: false\n|\n" +
940 "See https://github.com/jantimon/html-webpack-plugin#options for details.\n\n" +
941 "For parser dedicated bugs please create an issue here:\n" +
942 "https://danielruf.github.io/html-minifier-terser/" +
943 "\n" +
944 e.message;
945 }
946
947 return Promise.reject(e);
948 }
949
950 return Promise.resolve(html);
951 }
952
953 /**
954 * Helper to return a sorted unique array of all asset files out of the asset object
955 * @private
956 */
957 getAssetFiles(assets) {
958 const files = [
959 ...new Set(
960 Object.keys(assets)
961 .filter((assetType) => assetType !== "chunks" && assets[assetType])
962 .reduce((files, assetType) => files.concat(assets[assetType]), []),
963 ),
964 ];
965 files.sort();
966 return files;
967 }
968
969 /**
970 * Converts a favicon file from disk to a webpack resource and returns the url to the resource
971 *
972 * @private
973 * @param {Compiler} compiler
974 * @param {string|false} favicon
975 * @param {Compilation} compilation
976 * @param {string} publicPath
977 * @param {PreviousEmittedAssets} previousEmittedAssets
978 * @returns {Promise<string|undefined>}
979 */
980 generateFavicon(
981 compiler,
982 favicon,
983 compilation,
984 publicPath,
985 previousEmittedAssets,
986 ) {
987 if (!favicon) {
988 return Promise.resolve(undefined);
989 }
990
991 const filename = path.resolve(compilation.compiler.context, favicon);
992
993 return promisify(compilation.inputFileSystem.readFile)(filename)
994 .then((buf) => {
995 const source = new compiler.webpack.sources.RawSource(
996 /** @type {string | Buffer} */ (buf),
997 false,
998 );
999 const name = path.basename(filename);
1000
1001 compilation.fileDependencies.add(filename);
1002 compilation.emitAsset(name, source);
1003 previousEmittedAssets.push({ name, source });
1004
1005 const faviconPath = publicPath + name;
1006
1007 if (this.options.hash) {
1008 return this.appendHash(
1009 faviconPath,
1010 /** @type {string} */ (compilation.hash),
1011 );
1012 }
1013
1014 return faviconPath;
1015 })
1016 .catch(() =>
1017 Promise.reject(
1018 new Error("HtmlWebpackPlugin: could not load file " + filename),
1019 ),
1020 );
1021 }
1022
1023 /**
1024 * Generate all tags script for the given file paths
1025 *
1026 * @private
1027 * @param {Array<string>} jsAssets
1028 * @returns {Array<HtmlTagObject>}
1029 */
1030 generatedScriptTags(jsAssets) {
1031 // @ts-ignore
1032 return jsAssets.map((src) => {
1033 const attributes = {};
1034
1035 if (this.options.scriptLoading === "defer") {
1036 attributes.defer = true;
1037 } else if (this.options.scriptLoading === "module") {
1038 attributes.type = "module";
1039 } else if (this.options.scriptLoading === "systemjs-module") {
1040 attributes.type = "systemjs-module";
1041 }
1042
1043 attributes.src = src;
1044
1045 return {
1046 tagName: "script",
1047 voidTag: false,
1048 meta: { plugin: "html-webpack-plugin" },
1049 attributes,
1050 };
1051 });
1052 }
1053
1054 /**
1055 * Generate all style tags for the given file paths
1056 *
1057 * @private
1058 * @param {Array<string>} cssAssets
1059 * @returns {Array<HtmlTagObject>}
1060 */
1061 generateStyleTags(cssAssets) {
1062 return cssAssets.map((styleAsset) => ({
1063 tagName: "link",
1064 voidTag: true,
1065 meta: { plugin: "html-webpack-plugin" },
1066 attributes: {
1067 href: styleAsset,
1068 rel: "stylesheet",
1069 },
1070 }));
1071 }
1072
1073 /**
1074 * Generate an optional base tag
1075 *
1076 * @param {string | {[attributeName: string]: string}} base
1077 * @returns {Array<HtmlTagObject>}
1078 */
1079 generateBaseTag(base) {
1080 return [
1081 {
1082 tagName: "base",
1083 voidTag: true,
1084 meta: { plugin: "html-webpack-plugin" },
1085 // attributes e.g. { href:"http://example.com/page.html" target:"_blank" }
1086 attributes:
1087 typeof base === "string"
1088 ? {
1089 href: base,
1090 }
1091 : base,
1092 },
1093 ];
1094 }
1095
1096 /**
1097 * Generate all meta tags for the given meta configuration
1098 *
1099 * @private
1100 * @param {false | {[name: string]: false | string | {[attributeName: string]: string|boolean}}} metaOptions
1101 * @returns {Array<HtmlTagObject>}
1102 */
1103 generatedMetaTags(metaOptions) {
1104 if (metaOptions === false) {
1105 return [];
1106 }
1107
1108 // Make tags self-closing in case of xhtml
1109 // Turn { "viewport" : "width=500, initial-scale=1" } into
1110 // [{ name:"viewport" content:"width=500, initial-scale=1" }]
1111 const metaTagAttributeObjects = Object.keys(metaOptions)
1112 .map((metaName) => {
1113 const metaTagContent = metaOptions[metaName];
1114 return typeof metaTagContent === "string"
1115 ? {
1116 name: metaName,
1117 content: metaTagContent,
1118 }
1119 : metaTagContent;
1120 })
1121 .filter((attribute) => attribute !== false);
1122
1123 // Turn [{ name:"viewport" content:"width=500, initial-scale=1" }] into
1124 // the html-webpack-plugin tag structure
1125 return metaTagAttributeObjects.map((metaTagAttributes) => {
1126 if (metaTagAttributes === false) {
1127 throw new Error("Invalid meta tag");
1128 }
1129 return {
1130 tagName: "meta",
1131 voidTag: true,
1132 meta: { plugin: "html-webpack-plugin" },
1133 attributes: metaTagAttributes,
1134 };
1135 });
1136 }
1137
1138 /**
1139 * Generate a favicon tag for the given file path
1140 *
1141 * @private
1142 * @param {string} favicon
1143 * @returns {Array<HtmlTagObject>}
1144 */
1145 generateFaviconTag(favicon) {
1146 return [
1147 {
1148 tagName: "link",
1149 voidTag: true,
1150 meta: { plugin: "html-webpack-plugin" },
1151 attributes: {
1152 rel: "icon",
1153 href: favicon,
1154 },
1155 },
1156 ];
1157 }
1158
1159 /**
1160 * Group assets to head and body tags
1161 *
1162 * @param {{
1163 scripts: Array<HtmlTagObject>;
1164 styles: Array<HtmlTagObject>;
1165 meta: Array<HtmlTagObject>;
1166 }} assetTags
1167 * @param {"body" | "head"} scriptTarget
1168 * @returns {{
1169 headTags: Array<HtmlTagObject>;
1170 bodyTags: Array<HtmlTagObject>;
1171 }}
1172 */
1173 groupAssetsByElements(assetTags, scriptTarget) {
1174 /** @type {{ headTags: Array<HtmlTagObject>; bodyTags: Array<HtmlTagObject>; }} */
1175 const result = {
1176 headTags: [...assetTags.meta, ...assetTags.styles],
1177 bodyTags: [],
1178 };
1179
1180 // Add script tags to head or body depending on
1181 // the htmlPluginOptions
1182 if (scriptTarget === "body") {
1183 result.bodyTags.push(...assetTags.scripts);
1184 } else {
1185 // If script loading is blocking add the scripts to the end of the head
1186 // If script loading is non-blocking add the scripts in front of the css files
1187 const insertPosition =
1188 this.options.scriptLoading === "blocking"
1189 ? result.headTags.length
1190 : assetTags.meta.length;
1191
1192 result.headTags.splice(insertPosition, 0, ...assetTags.scripts);
1193 }
1194
1195 return result;
1196 }
1197
1198 /**
1199 * Replace [contenthash] in filename
1200 *
1201 * @see https://survivejs.com/webpack/optimizing/adding-hashes-to-filenames/
1202 *
1203 * @private
1204 * @param {Compiler} compiler
1205 * @param {string} filename
1206 * @param {string|Buffer} fileContent
1207 * @param {Compilation} compilation
1208 * @returns {{ path: string, info: {} }}
1209 */
1210 replacePlaceholdersInFilename(compiler, filename, fileContent, compilation) {
1211 if (/\[\\*([\w:]+)\\*\]/i.test(filename) === false) {
1212 return { path: filename, info: {} };
1213 }
1214
1215 const hash = compiler.webpack.util.createHash(
1216 compilation.outputOptions.hashFunction,
1217 );
1218
1219 hash.update(fileContent);
1220
1221 if (compilation.outputOptions.hashSalt) {
1222 hash.update(compilation.outputOptions.hashSalt);
1223 }
1224
1225 const contentHash = /** @type {string} */ (
1226 hash
1227 .digest(compilation.outputOptions.hashDigest)
1228 .slice(0, compilation.outputOptions.hashDigestLength)
1229 );
1230
1231 return compilation.getPathWithInfo(filename, {
1232 contentHash,
1233 chunk: {
1234 hash: contentHash,
1235 // @ts-ignore
1236 contentHash,
1237 },
1238 });
1239 }
1240
1241 /**
1242 * Function to generate HTML file.
1243 *
1244 * @private
1245 * @param {Compiler} compiler
1246 * @param {Compilation} compilation
1247 * @param {string} outputName
1248 * @param {CachedChildCompilation} childCompilerPlugin
1249 * @param {PreviousEmittedAssets} previousEmittedAssets
1250 * @param {{ value: string | undefined }} assetJson
1251 * @param {(err?: Error) => void} callback
1252 */
1253 generateHTML(
1254 compiler,
1255 compilation,
1256 outputName,
1257 childCompilerPlugin,
1258 previousEmittedAssets,
1259 assetJson,
1260 callback,
1261 ) {
1262 // Get all entry point names for this html file
1263 const entryNames = Array.from(compilation.entrypoints.keys());
1264 const filteredEntryNames = this.filterEntryChunks(
1265 entryNames,
1266 this.options.chunks,
1267 this.options.excludeChunks,
1268 );
1269 const sortedEntryNames = this.sortEntryChunks(
1270 filteredEntryNames,
1271 this.options.chunksSortMode,
1272 compilation,
1273 );
1274 const templateResult = this.options.templateContent
1275 ? { mainCompilationHash: compilation.hash }
1276 : childCompilerPlugin.getCompilationEntryResult(this.options.template);
1277
1278 if ("error" in templateResult) {
1279 compilation.errors.push(
1280 new Error(
1281 prettyError(templateResult.error, compiler.context).toString(),
1282 ),
1283 );
1284 }
1285
1286 // If the child compilation was not executed during a previous main compile run
1287 // it is a cached result
1288 const isCompilationCached =
1289 templateResult.mainCompilationHash !== compilation.hash;
1290 /** Generated file paths from the entry point names */
1291 const assetsInformationByGroups = this.getAssetsInformationByGroups(
1292 compilation,
1293 outputName,
1294 sortedEntryNames,
1295 );
1296 // If the template and the assets did not change we don't have to emit the html
1297 const newAssetJson = JSON.stringify(
1298 this.getAssetFiles(assetsInformationByGroups),
1299 );
1300
1301 if (
1302 isCompilationCached &&
1303 this.options.cache &&
1304 assetJson.value === newAssetJson
1305 ) {
1306 previousEmittedAssets.forEach(({ name, source, info }) => {
1307 compilation.emitAsset(name, source, info);
1308 });
1309 return callback();
1310 } else {
1311 previousEmittedAssets.length = 0;
1312 assetJson.value = newAssetJson;
1313 }
1314
1315 // The html-webpack plugin uses a object representation for the html-tags which will be injected
1316 // to allow altering them more easily
1317 // Just before they are converted a third-party-plugin author might change the order and content
1318 const assetsPromise = this.generateFavicon(
1319 compiler,
1320 this.options.favicon,
1321 compilation,
1322 assetsInformationByGroups.publicPath,
1323 previousEmittedAssets,
1324 ).then((faviconPath) => {
1325 assetsInformationByGroups.favicon = faviconPath;
1326 return HtmlWebpackPlugin.getCompilationHooks(
1327 compilation,
1328 ).beforeAssetTagGeneration.promise({
1329 assets: assetsInformationByGroups,
1330 outputName,
1331 plugin: this,
1332 });
1333 });
1334
1335 // Turn the js and css paths into grouped HtmlTagObjects
1336 const assetTagGroupsPromise = assetsPromise
1337 // And allow third-party-plugin authors to reorder and change the assetTags before they are grouped
1338 .then(({ assets }) =>
1339 HtmlWebpackPlugin.getCompilationHooks(
1340 compilation,
1341 ).alterAssetTags.promise({
1342 assetTags: {
1343 scripts: this.generatedScriptTags(assets.js),
1344 styles: this.generateStyleTags(assets.css),
1345 meta: [
1346 ...(this.options.base !== false
1347 ? this.generateBaseTag(this.options.base)
1348 : []),
1349 ...this.generatedMetaTags(this.options.meta),
1350 ...(assets.favicon
1351 ? this.generateFaviconTag(assets.favicon)
1352 : []),
1353 ],
1354 },
1355 outputName,
1356 publicPath: assetsInformationByGroups.publicPath,
1357 plugin: this,
1358 }),
1359 )
1360 .then(({ assetTags }) => {
1361 // Inject scripts to body unless it set explicitly to head
1362 const scriptTarget =
1363 this.options.inject === "head" ||
1364 (this.options.inject !== "body" &&
1365 this.options.scriptLoading !== "blocking")
1366 ? "head"
1367 : "body";
1368 // Group assets to `head` and `body` tag arrays
1369 const assetGroups = this.groupAssetsByElements(assetTags, scriptTarget);
1370 // Allow third-party-plugin authors to reorder and change the assetTags once they are grouped
1371 return HtmlWebpackPlugin.getCompilationHooks(
1372 compilation,
1373 ).alterAssetTagGroups.promise({
1374 headTags: assetGroups.headTags,
1375 bodyTags: assetGroups.bodyTags,
1376 outputName,
1377 publicPath: assetsInformationByGroups.publicPath,
1378 plugin: this,
1379 });
1380 });
1381
1382 // Turn the compiled template into a nodejs function or into a nodejs string
1383 const templateEvaluationPromise = Promise.resolve().then(() => {
1384 if ("error" in templateResult) {
1385 return this.options.showErrors
1386 ? prettyError(templateResult.error, compiler.context).toHtml()
1387 : "ERROR";
1388 }
1389
1390 // Allow to use a custom function / string instead
1391 if (this.options.templateContent !== false) {
1392 return this.options.templateContent;
1393 }
1394
1395 // Once everything is compiled evaluate the html factory and replace it with its content
1396 if ("compiledEntry" in templateResult) {
1397 const compiledEntry = templateResult.compiledEntry;
1398 const assets = compiledEntry.assets;
1399
1400 // Store assets from child compiler to re-emit them later
1401 for (const name in assets) {
1402 previousEmittedAssets.push({
1403 name,
1404 source: assets[name].source,
1405 info: assets[name].info,
1406 });
1407 }
1408
1409 return this.evaluateCompilationResult(
1410 compiledEntry.content,
1411 assetsInformationByGroups.publicPath,
1412 this.options.template,
1413 );
1414 }
1415
1416 return Promise.reject(
1417 new Error("Child compilation contained no compiledEntry"),
1418 );
1419 });
1420 const templateExecutionPromise = Promise.all([
1421 assetsPromise,
1422 assetTagGroupsPromise,
1423 templateEvaluationPromise,
1424 ])
1425 // Execute the template
1426 .then(([assetsHookResult, assetTags, compilationResult]) =>
1427 typeof compilationResult !== "function"
1428 ? compilationResult
1429 : this.executeTemplate(
1430 compilationResult,
1431 assetsHookResult.assets,
1432 { headTags: assetTags.headTags, bodyTags: assetTags.bodyTags },
1433 compilation,
1434 ),
1435 );
1436
1437 const injectedHtmlPromise = Promise.all([
1438 assetTagGroupsPromise,
1439 templateExecutionPromise,
1440 ])
1441 // Allow plugins to change the html before assets are injected
1442 .then(([assetTags, html]) => {
1443 const pluginArgs = {
1444 html,
1445 headTags: assetTags.headTags,
1446 bodyTags: assetTags.bodyTags,
1447 plugin: this,
1448 outputName,
1449 };
1450 return HtmlWebpackPlugin.getCompilationHooks(
1451 compilation,
1452 ).afterTemplateExecution.promise(pluginArgs);
1453 })
1454 .then(({ html, headTags, bodyTags }) => {
1455 return this.postProcessHtml(compiler, html, assetsInformationByGroups, {
1456 headTags,
1457 bodyTags,
1458 });
1459 });
1460
1461 const emitHtmlPromise = injectedHtmlPromise
1462 // Allow plugins to change the html after assets are injected
1463 .then((html) => {
1464 const pluginArgs = { html, plugin: this, outputName };
1465 return HtmlWebpackPlugin.getCompilationHooks(compilation)
1466 .beforeEmit.promise(pluginArgs)
1467 .then((result) => result.html);
1468 })
1469 .catch((err) => {
1470 // In case anything went wrong the promise is resolved
1471 // with the error message and an error is logged
1472 compilation.errors.push(
1473 new Error(prettyError(err, compiler.context).toString()),
1474 );
1475 return this.options.showErrors
1476 ? prettyError(err, compiler.context).toHtml()
1477 : "ERROR";
1478 })
1479 .then((html) => {
1480 const filename = outputName.replace(
1481 /\[templatehash([^\]]*)\]/g,
1482 require("util").deprecate(
1483 (match, options) => `[contenthash${options}]`,
1484 "[templatehash] is now [contenthash]",
1485 ),
1486 );
1487 const replacedFilename = this.replacePlaceholdersInFilename(
1488 compiler,
1489 filename,
1490 html,
1491 compilation,
1492 );
1493 const source = new compiler.webpack.sources.RawSource(html, false);
1494
1495 // Add the evaluated html code to the webpack assets
1496 compilation.emitAsset(
1497 replacedFilename.path,
1498 source,
1499 replacedFilename.info,
1500 );
1501 previousEmittedAssets.push({ name: replacedFilename.path, source });
1502
1503 return replacedFilename.path;
1504 })
1505 .then((finalOutputName) =>
1506 HtmlWebpackPlugin.getCompilationHooks(compilation)
1507 .afterEmit.promise({
1508 outputName: finalOutputName,
1509 plugin: this,
1510 })
1511 .catch((err) => {
1512 /** @type {Logger} */
1513 (this.logger).error(err);
1514 return null;
1515 })
1516 .then(() => null),
1517 );
1518
1519 // Once all files are added to the webpack compilation
1520 // let the webpack compiler continue
1521 emitHtmlPromise.then(() => {
1522 callback();
1523 });
1524 }
1525}
1526
1527/**
1528 * The default for options.templateParameter
1529 * Generate the template parameters
1530 *
1531 * Generate the template parameters for the template function
1532 * @param {Compilation} compilation
1533 * @param {AssetsInformationByGroups} assets
1534 * @param {{
1535 headTags: HtmlTagObject[],
1536 bodyTags: HtmlTagObject[]
1537 }} assetTags
1538 * @param {ProcessedHtmlWebpackOptions} options
1539 * @returns {TemplateParameter}
1540 */
1541function templateParametersGenerator(compilation, assets, assetTags, options) {
1542 return {
1543 compilation: compilation,
1544 webpackConfig: compilation.options,
1545 htmlWebpackPlugin: {
1546 tags: assetTags,
1547 files: assets,
1548 options: options,
1549 },
1550 };
1551}
1552
1553// Statics:
1554/**
1555 * The major version number of this plugin
1556 */
1557HtmlWebpackPlugin.version = 5;
1558
1559/**
1560 * A static helper to get the hooks for this plugin
1561 *
1562 * Usage: HtmlWebpackPlugin.getHooks(compilation).HOOK_NAME.tapAsync('YourPluginName', () => { ... });
1563 */
1564// TODO remove me in the next major release in favor getCompilationHooks
1565HtmlWebpackPlugin.getHooks = HtmlWebpackPlugin.getCompilationHooks;
1566HtmlWebpackPlugin.createHtmlTagObject = createHtmlTagObject;
1567
1568module.exports = HtmlWebpackPlugin;
Note: See TracBrowser for help on using the repository browser.