12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598 |
- // @ts-check
- "use strict";
- const promisify = require("util").promisify;
- const vm = require("vm");
- const fs = require("fs");
- const _uniq = require("lodash/uniq");
- const path = require("path");
- const { CachedChildCompilation } = require("./lib/cached-child-compiler");
- const {
- createHtmlTagObject,
- htmlTagObjectToString,
- HtmlTagArray,
- } = require("./lib/html-tags");
- const prettyError = require("./lib/errors.js");
- const chunkSorter = require("./lib/chunksorter.js");
- const { AsyncSeriesWaterfallHook } = require("tapable");
- /** @typedef {import("./typings").HtmlTagObject} HtmlTagObject */
- /** @typedef {import("./typings").Options} HtmlWebpackOptions */
- /** @typedef {import("./typings").ProcessedOptions} ProcessedHtmlWebpackOptions */
- /** @typedef {import("./typings").TemplateParameter} TemplateParameter */
- /** @typedef {import("webpack").Compiler} Compiler */
- /** @typedef {import("webpack").Compilation} Compilation */
- /** @typedef {Required<Compilation["outputOptions"]["publicPath"]>} PublicPath */
- /** @typedef {ReturnType<Compiler["getInfrastructureLogger"]>} Logger */
- /** @typedef {Compilation["entrypoints"] extends Map<string, infer I> ? I : never} Entrypoint */
- /** @typedef {Array<{ name: string, source: import('webpack').sources.Source, info?: import('webpack').AssetInfo }>} PreviousEmittedAssets */
- /** @typedef {{ publicPath: string, js: Array<string>, css: Array<string>, manifest?: string, favicon?: string }} AssetsInformationByGroups */
- /** @typedef {import("./typings").Hooks} HtmlWebpackPluginHooks */
- /**
- * @type {WeakMap<Compilation, HtmlWebpackPluginHooks>}}
- */
- const compilationHooksMap = new WeakMap();
- class HtmlWebpackPlugin {
- // The following is the API definition for all available hooks
- // For the TypeScript definition, see the Hooks type in typings.d.ts
- /**
- beforeAssetTagGeneration:
- AsyncSeriesWaterfallHook<{
- assets: {
- publicPath: string,
- js: Array<string>,
- css: Array<string>,
- favicon?: string | undefined,
- manifest?: string | undefined
- },
- outputName: string,
- plugin: HtmlWebpackPlugin
- }>,
- alterAssetTags:
- AsyncSeriesWaterfallHook<{
- assetTags: {
- scripts: Array<HtmlTagObject>,
- styles: Array<HtmlTagObject>,
- meta: Array<HtmlTagObject>,
- },
- publicPath: string,
- outputName: string,
- plugin: HtmlWebpackPlugin
- }>,
- alterAssetTagGroups:
- AsyncSeriesWaterfallHook<{
- headTags: Array<HtmlTagObject | HtmlTagObject>,
- bodyTags: Array<HtmlTagObject | HtmlTagObject>,
- publicPath: string,
- outputName: string,
- plugin: HtmlWebpackPlugin
- }>,
- afterTemplateExecution:
- AsyncSeriesWaterfallHook<{
- html: string,
- headTags: Array<HtmlTagObject | HtmlTagObject>,
- bodyTags: Array<HtmlTagObject | HtmlTagObject>,
- outputName: string,
- plugin: HtmlWebpackPlugin,
- }>,
- beforeEmit:
- AsyncSeriesWaterfallHook<{
- html: string,
- outputName: string,
- plugin: HtmlWebpackPlugin,
- }>,
- afterEmit:
- AsyncSeriesWaterfallHook<{
- outputName: string,
- plugin: HtmlWebpackPlugin
- }>
- */
- /**
- * Returns all public hooks of the html webpack plugin for the given compilation
- *
- * @param {Compilation} compilation
- * @returns {HtmlWebpackPluginHooks}
- */
- static getCompilationHooks(compilation) {
- let hooks = compilationHooksMap.get(compilation);
- if (!hooks) {
- hooks = {
- beforeAssetTagGeneration: new AsyncSeriesWaterfallHook(["pluginArgs"]),
- alterAssetTags: new AsyncSeriesWaterfallHook(["pluginArgs"]),
- alterAssetTagGroups: new AsyncSeriesWaterfallHook(["pluginArgs"]),
- afterTemplateExecution: new AsyncSeriesWaterfallHook(["pluginArgs"]),
- beforeEmit: new AsyncSeriesWaterfallHook(["pluginArgs"]),
- afterEmit: new AsyncSeriesWaterfallHook(["pluginArgs"]),
- };
- compilationHooksMap.set(compilation, hooks);
- }
- return hooks;
- }
- /**
- * @param {HtmlWebpackOptions} [options]
- */
- constructor(options) {
- /** @type {HtmlWebpackOptions} */
- // TODO remove me in the next major release
- this.userOptions = options || {};
- this.version = HtmlWebpackPlugin.version;
- // Default options
- /** @type {ProcessedHtmlWebpackOptions} */
- const defaultOptions = {
- template: "auto",
- templateContent: false,
- templateParameters: templateParametersGenerator,
- filename: "index.html",
- publicPath:
- this.userOptions.publicPath === undefined
- ? "auto"
- : this.userOptions.publicPath,
- hash: false,
- inject: this.userOptions.scriptLoading === "blocking" ? "body" : "head",
- scriptLoading: "defer",
- compile: true,
- favicon: false,
- minify: "auto",
- cache: true,
- showErrors: true,
- chunks: "all",
- excludeChunks: [],
- chunksSortMode: "auto",
- meta: {},
- base: false,
- title: "Webpack App",
- xhtml: false,
- };
- /** @type {ProcessedHtmlWebpackOptions} */
- this.options = Object.assign(defaultOptions, this.userOptions);
- }
- /**
- *
- * @param {Compiler} compiler
- * @returns {void}
- */
- apply(compiler) {
- this.logger = compiler.getInfrastructureLogger("HtmlWebpackPlugin");
- const options = this.options;
- options.template = this.getTemplatePath(
- this.options.template,
- compiler.context,
- );
- // Assert correct option spelling
- if (
- options.scriptLoading !== "defer" &&
- options.scriptLoading !== "blocking" &&
- options.scriptLoading !== "module" &&
- options.scriptLoading !== "systemjs-module"
- ) {
- /** @type {Logger} */
- (this.logger).error(
- 'The "scriptLoading" option need to be set to "defer", "blocking" or "module" or "systemjs-module"',
- );
- }
- if (
- options.inject !== true &&
- options.inject !== false &&
- options.inject !== "head" &&
- options.inject !== "body"
- ) {
- /** @type {Logger} */
- (this.logger).error(
- 'The `inject` option needs to be set to true, false, "head" or "body',
- );
- }
- if (
- this.options.templateParameters !== false &&
- typeof this.options.templateParameters !== "function" &&
- typeof this.options.templateParameters !== "object"
- ) {
- /** @type {Logger} */
- (this.logger).error(
- "The `templateParameters` has to be either a function or an object or false",
- );
- }
- // Default metaOptions if no template is provided
- if (
- !this.userOptions.template &&
- options.templateContent === false &&
- options.meta
- ) {
- options.meta = Object.assign(
- {},
- options.meta,
- {
- // TODO remove in the next major release
- // From https://developer.mozilla.org/en-US/docs/Mozilla/Mobile/Viewport_meta_tag
- viewport: "width=device-width, initial-scale=1",
- },
- this.userOptions.meta,
- );
- }
- // entryName to fileName conversion function
- const userOptionFilename =
- this.userOptions.filename || this.options.filename;
- const filenameFunction =
- typeof userOptionFilename === "function"
- ? userOptionFilename
- : // Replace '[name]' with entry name
- (entryName) => userOptionFilename.replace(/\[name\]/g, entryName);
- /** output filenames for the given entry names */
- const entryNames = Object.keys(compiler.options.entry);
- const outputFileNames = new Set(
- (entryNames.length ? entryNames : ["main"]).map(filenameFunction),
- );
- // Hook all options into the webpack compiler
- outputFileNames.forEach((outputFileName) => {
- // Instance variables to keep caching information for multiple builds
- const assetJson = { value: undefined };
- /**
- * store the previous generated asset to emit them even if the content did not change
- * to support watch mode for third party plugins like the clean-webpack-plugin or the compression plugin
- * @type {PreviousEmittedAssets}
- */
- const previousEmittedAssets = [];
- // Inject child compiler plugin
- const childCompilerPlugin = new CachedChildCompilation(compiler);
- if (!this.options.templateContent) {
- childCompilerPlugin.addEntry(this.options.template);
- }
- // convert absolute filename into relative so that webpack can
- // generate it at correct location
- let filename = outputFileName;
- if (path.resolve(filename) === path.normalize(filename)) {
- const outputPath =
- /** @type {string} - Once initialized the path is always a string */ (
- compiler.options.output.path
- );
- filename = path.relative(outputPath, filename);
- }
- compiler.hooks.thisCompilation.tap(
- "HtmlWebpackPlugin",
- /**
- * Hook into the webpack compilation
- * @param {Compilation} compilation
- */
- (compilation) => {
- compilation.hooks.processAssets.tapAsync(
- {
- name: "HtmlWebpackPlugin",
- stage:
- /**
- * Generate the html after minification and dev tooling is done
- */
- compiler.webpack.Compilation
- .PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE,
- },
- /**
- * Hook into the process assets hook
- * @param {any} _
- * @param {(err?: Error) => void} callback
- */
- (_, callback) => {
- this.generateHTML(
- compiler,
- compilation,
- filename,
- childCompilerPlugin,
- previousEmittedAssets,
- assetJson,
- callback,
- );
- },
- );
- },
- );
- });
- }
- /**
- * Helper to return the absolute template path with a fallback loader
- *
- * @private
- * @param {string} template The path to the template e.g. './index.html'
- * @param {string} context The webpack base resolution path for relative paths e.g. process.cwd()
- */
- getTemplatePath(template, context) {
- if (template === "auto") {
- template = path.resolve(context, "src/index.ejs");
- if (!fs.existsSync(template)) {
- template = path.join(__dirname, "default_index.ejs");
- }
- }
- // If the template doesn't use a loader use the lodash template loader
- if (template.indexOf("!") === -1) {
- template =
- require.resolve("./lib/loader.js") +
- "!" +
- path.resolve(context, template);
- }
- // Resolve template path
- return template.replace(
- /([!])([^/\\][^!?]+|[^/\\!?])($|\?[^!?\n]+$)/,
- (match, prefix, filepath, postfix) =>
- prefix + path.resolve(filepath) + postfix,
- );
- }
- /**
- * Return all chunks from the compilation result which match the exclude and include filters
- *
- * @private
- * @param {any} chunks
- * @param {string[]|'all'} includedChunks
- * @param {string[]} excludedChunks
- */
- filterEntryChunks(chunks, includedChunks, excludedChunks) {
- return chunks.filter((chunkName) => {
- // Skip if the chunks should be filtered and the given chunk was not added explicity
- if (
- Array.isArray(includedChunks) &&
- includedChunks.indexOf(chunkName) === -1
- ) {
- return false;
- }
- // Skip if the chunks should be filtered and the given chunk was excluded explicity
- if (
- Array.isArray(excludedChunks) &&
- excludedChunks.indexOf(chunkName) !== -1
- ) {
- return false;
- }
- // Add otherwise
- return true;
- });
- }
- /**
- * Helper to sort chunks
- *
- * @private
- * @param {string[]} entryNames
- * @param {string|((entryNameA: string, entryNameB: string) => number)} sortMode
- * @param {Compilation} compilation
- */
- sortEntryChunks(entryNames, sortMode, compilation) {
- // Custom function
- if (typeof sortMode === "function") {
- return entryNames.sort(sortMode);
- }
- // Check if the given sort mode is a valid chunkSorter sort mode
- if (typeof chunkSorter[sortMode] !== "undefined") {
- return chunkSorter[sortMode](entryNames, compilation, this.options);
- }
- throw new Error('"' + sortMode + '" is not a valid chunk sort mode');
- }
- /**
- * Encode each path component using `encodeURIComponent` as files can contain characters
- * which needs special encoding in URLs like `+ `.
- *
- * Valid filesystem characters which need to be encoded for urls:
- *
- * # pound, % percent, & ampersand, { left curly bracket, } right curly bracket,
- * \ back slash, < left angle bracket, > right angle bracket, * asterisk, ? question mark,
- * blank spaces, $ dollar sign, ! exclamation point, ' single quotes, " double quotes,
- * : colon, @ at sign, + plus sign, ` backtick, | pipe, = equal sign
- *
- * However the query string must not be encoded:
- *
- * fo:demonstration-path/very fancy+name.js?path=/home?value=abc&value=def#zzz
- * ^ ^ ^ ^ ^ ^ ^ ^^ ^ ^ ^ ^ ^
- * | | | | | | | || | | | | |
- * encoded | | encoded | | || | | | | |
- * ignored ignored ignored ignored ignored
- *
- * @private
- * @param {string} filePath
- */
- urlencodePath(filePath) {
- // People use the filepath in quite unexpected ways.
- // Try to extract the first querystring of the url:
- //
- // some+path/demo.html?value=abc?def
- //
- const queryStringStart = filePath.indexOf("?");
- const urlPath =
- queryStringStart === -1 ? filePath : filePath.substr(0, queryStringStart);
- const queryString = filePath.substr(urlPath.length);
- // Encode all parts except '/' which are not part of the querystring:
- const encodedUrlPath = urlPath.split("/").map(encodeURIComponent).join("/");
- return encodedUrlPath + queryString;
- }
- /**
- * Appends a cache busting hash to the query string of the url
- * E.g. http://localhost:8080/ -> http://localhost:8080/?50c9096ba6183fd728eeb065a26ec175
- *
- * @private
- * @param {string | undefined} url
- * @param {string} hash
- */
- appendHash(url, hash) {
- if (!url) {
- return url;
- }
- return url + (url.indexOf("?") === -1 ? "?" : "&") + hash;
- }
- /**
- * Generate the relative or absolute base url to reference images, css, and javascript files
- * from within the html file - the publicPath
- *
- * @private
- * @param {Compilation} compilation
- * @param {string} filename
- * @param {string | 'auto'} customPublicPath
- * @returns {string}
- */
- getPublicPath(compilation, filename, customPublicPath) {
- /**
- * @type {string} the configured public path to the asset root
- * if a path publicPath is set in the current webpack config use it otherwise
- * fallback to a relative path
- */
- const webpackPublicPath = compilation.getAssetPath(
- /** @type {NonNullable<Compilation["outputOptions"]["publicPath"]>} */ (
- compilation.outputOptions.publicPath
- ),
- { hash: compilation.hash },
- );
- // Webpack 5 introduced "auto" as default value
- const isPublicPathDefined = webpackPublicPath !== "auto";
- let publicPath =
- // If the html-webpack-plugin options contain a custom public path unset it
- customPublicPath !== "auto"
- ? customPublicPath
- : isPublicPathDefined
- ? // If a hard coded public path exists use it
- webpackPublicPath
- : // If no public path was set get a relative url path
- path
- .relative(
- path.resolve(
- /** @type {string} */ (compilation.options.output.path),
- path.dirname(filename),
- ),
- /** @type {string} */ (compilation.options.output.path),
- )
- .split(path.sep)
- .join("/");
- if (publicPath.length && publicPath.substr(-1, 1) !== "/") {
- publicPath += "/";
- }
- return publicPath;
- }
- /**
- * The getAssetsForHTML extracts the asset information of a webpack compilation for all given entry names.
- *
- * @private
- * @param {Compilation} compilation
- * @param {string} outputName
- * @param {string[]} entryNames
- * @returns {AssetsInformationByGroups}
- */
- getAssetsInformationByGroups(compilation, outputName, entryNames) {
- /** The public path used inside the html file */
- const publicPath = this.getPublicPath(
- compilation,
- outputName,
- this.options.publicPath,
- );
- /**
- * @type {AssetsInformationByGroups}
- */
- const assets = {
- // The public path
- publicPath,
- // Will contain all js and mjs files
- js: [],
- // Will contain all css files
- css: [],
- // Will contain the html5 appcache manifest files if it exists
- manifest: Object.keys(compilation.assets).find(
- (assetFile) => path.extname(assetFile) === ".appcache",
- ),
- // Favicon
- favicon: undefined,
- };
- // Append a hash for cache busting
- if (this.options.hash && assets.manifest) {
- assets.manifest = this.appendHash(
- assets.manifest,
- /** @type {string} */ (compilation.hash),
- );
- }
- // Extract paths to .js, .mjs and .css files from the current compilation
- const entryPointPublicPathMap = {};
- const extensionRegexp = /\.(css|js|mjs)(\?|$)/;
- for (let i = 0; i < entryNames.length; i++) {
- const entryName = entryNames[i];
- /** entryPointUnfilteredFiles - also includes hot module update files */
- const entryPointUnfilteredFiles = /** @type {Entrypoint} */ (
- compilation.entrypoints.get(entryName)
- ).getFiles();
- const entryPointFiles = entryPointUnfilteredFiles.filter((chunkFile) => {
- const asset = compilation.getAsset(chunkFile);
- if (!asset) {
- return true;
- }
- // Prevent hot-module files from being included:
- const assetMetaInformation = asset.info || {};
- return !(
- assetMetaInformation.hotModuleReplacement ||
- assetMetaInformation.development
- );
- });
- // Prepend the publicPath and append the hash depending on the
- // webpack.output.publicPath and hashOptions
- // E.g. bundle.js -> /bundle.js?hash
- const entryPointPublicPaths = entryPointFiles.map((chunkFile) => {
- const entryPointPublicPath = publicPath + this.urlencodePath(chunkFile);
- return this.options.hash
- ? this.appendHash(
- entryPointPublicPath,
- /** @type {string} */ (compilation.hash),
- )
- : entryPointPublicPath;
- });
- entryPointPublicPaths.forEach((entryPointPublicPath) => {
- const extMatch = extensionRegexp.exec(
- /** @type {string} */ (entryPointPublicPath),
- );
- // Skip if the public path is not a .css, .mjs or .js file
- if (!extMatch) {
- return;
- }
- // Skip if this file is already known
- // (e.g. because of common chunk optimizations)
- if (entryPointPublicPathMap[entryPointPublicPath]) {
- return;
- }
- entryPointPublicPathMap[entryPointPublicPath] = true;
- // ext will contain .js or .css, because .mjs recognizes as .js
- const ext = extMatch[1] === "mjs" ? "js" : extMatch[1];
- assets[ext].push(entryPointPublicPath);
- });
- }
- return assets;
- }
- /**
- * Once webpack is done with compiling the template into a NodeJS code this function
- * evaluates it to generate the html result
- *
- * The evaluateCompilationResult is only a class function to allow spying during testing.
- * Please change that in a further refactoring
- *
- * @param {string} source
- * @param {string} publicPath
- * @param {string} templateFilename
- * @returns {Promise<string | (() => string | Promise<string>)>}
- */
- evaluateCompilationResult(source, publicPath, templateFilename) {
- if (!source) {
- return Promise.reject(
- new Error("The child compilation didn't provide a result"),
- );
- }
- // The LibraryTemplatePlugin stores the template result in a local variable.
- // By adding it to the end the value gets extracted during evaluation
- if (source.indexOf("HTML_WEBPACK_PLUGIN_RESULT") >= 0) {
- source += ";\nHTML_WEBPACK_PLUGIN_RESULT";
- }
- const templateWithoutLoaders = templateFilename
- .replace(/^.+!/, "")
- .replace(/\?.+$/, "");
- const vmContext = vm.createContext({
- ...global,
- HTML_WEBPACK_PLUGIN: true,
- require: require,
- htmlWebpackPluginPublicPath: publicPath,
- __filename: templateWithoutLoaders,
- __dirname: path.dirname(templateWithoutLoaders),
- AbortController: global.AbortController,
- AbortSignal: global.AbortSignal,
- Blob: global.Blob,
- Buffer: global.Buffer,
- ByteLengthQueuingStrategy: global.ByteLengthQueuingStrategy,
- BroadcastChannel: global.BroadcastChannel,
- CompressionStream: global.CompressionStream,
- CountQueuingStrategy: global.CountQueuingStrategy,
- Crypto: global.Crypto,
- CryptoKey: global.CryptoKey,
- CustomEvent: global.CustomEvent,
- DecompressionStream: global.DecompressionStream,
- Event: global.Event,
- EventTarget: global.EventTarget,
- File: global.File,
- FormData: global.FormData,
- Headers: global.Headers,
- MessageChannel: global.MessageChannel,
- MessageEvent: global.MessageEvent,
- MessagePort: global.MessagePort,
- PerformanceEntry: global.PerformanceEntry,
- PerformanceMark: global.PerformanceMark,
- PerformanceMeasure: global.PerformanceMeasure,
- PerformanceObserver: global.PerformanceObserver,
- PerformanceObserverEntryList: global.PerformanceObserverEntryList,
- PerformanceResourceTiming: global.PerformanceResourceTiming,
- ReadableByteStreamController: global.ReadableByteStreamController,
- ReadableStream: global.ReadableStream,
- ReadableStreamBYOBReader: global.ReadableStreamBYOBReader,
- ReadableStreamBYOBRequest: global.ReadableStreamBYOBRequest,
- ReadableStreamDefaultController: global.ReadableStreamDefaultController,
- ReadableStreamDefaultReader: global.ReadableStreamDefaultReader,
- Response: global.Response,
- Request: global.Request,
- SubtleCrypto: global.SubtleCrypto,
- DOMException: global.DOMException,
- TextDecoder: global.TextDecoder,
- TextDecoderStream: global.TextDecoderStream,
- TextEncoder: global.TextEncoder,
- TextEncoderStream: global.TextEncoderStream,
- TransformStream: global.TransformStream,
- TransformStreamDefaultController: global.TransformStreamDefaultController,
- URL: global.URL,
- URLSearchParams: global.URLSearchParams,
- WebAssembly: global.WebAssembly,
- WritableStream: global.WritableStream,
- WritableStreamDefaultController: global.WritableStreamDefaultController,
- WritableStreamDefaultWriter: global.WritableStreamDefaultWriter,
- });
- const vmScript = new vm.Script(source, {
- filename: templateWithoutLoaders,
- });
- // Evaluate code and cast to string
- let newSource;
- try {
- newSource = vmScript.runInContext(vmContext);
- } catch (e) {
- return Promise.reject(e);
- }
- if (
- typeof newSource === "object" &&
- newSource.__esModule &&
- newSource.default !== undefined
- ) {
- newSource = newSource.default;
- }
- return typeof newSource === "string" || typeof newSource === "function"
- ? Promise.resolve(newSource)
- : Promise.reject(
- new Error(
- 'The loader "' + templateWithoutLoaders + "\" didn't return html.",
- ),
- );
- }
- /**
- * Add toString methods for easier rendering inside the template
- *
- * @private
- * @param {Array<HtmlTagObject>} assetTagGroup
- * @returns {Array<HtmlTagObject>}
- */
- prepareAssetTagGroupForRendering(assetTagGroup) {
- const xhtml = this.options.xhtml;
- return HtmlTagArray.from(
- assetTagGroup.map((assetTag) => {
- const copiedAssetTag = Object.assign({}, assetTag);
- copiedAssetTag.toString = function () {
- return htmlTagObjectToString(this, xhtml);
- };
- return copiedAssetTag;
- }),
- );
- }
- /**
- * Generate the template parameters for the template function
- *
- * @private
- * @param {Compilation} compilation
- * @param {AssetsInformationByGroups} assetsInformationByGroups
- * @param {{
- headTags: HtmlTagObject[],
- bodyTags: HtmlTagObject[]
- }} assetTags
- * @returns {Promise<{[key: any]: any}>}
- */
- getTemplateParameters(compilation, assetsInformationByGroups, assetTags) {
- const templateParameters = this.options.templateParameters;
- if (templateParameters === false) {
- return Promise.resolve({});
- }
- if (
- typeof templateParameters !== "function" &&
- typeof templateParameters !== "object"
- ) {
- throw new Error(
- "templateParameters has to be either a function or an object",
- );
- }
- const templateParameterFunction =
- typeof templateParameters === "function"
- ? // A custom function can overwrite the entire template parameter preparation
- templateParameters
- : // If the template parameters is an object merge it with the default values
- (compilation, assetsInformationByGroups, assetTags, options) =>
- Object.assign(
- {},
- templateParametersGenerator(
- compilation,
- assetsInformationByGroups,
- assetTags,
- options,
- ),
- templateParameters,
- );
- const preparedAssetTags = {
- headTags: this.prepareAssetTagGroupForRendering(assetTags.headTags),
- bodyTags: this.prepareAssetTagGroupForRendering(assetTags.bodyTags),
- };
- return Promise.resolve().then(() =>
- templateParameterFunction(
- compilation,
- assetsInformationByGroups,
- preparedAssetTags,
- this.options,
- ),
- );
- }
- /**
- * This function renders the actual html by executing the template function
- *
- * @private
- * @param {(templateParameters) => string | Promise<string>} templateFunction
- * @param {AssetsInformationByGroups} assetsInformationByGroups
- * @param {{
- headTags: HtmlTagObject[],
- bodyTags: HtmlTagObject[]
- }} assetTags
- * @param {Compilation} compilation
- * @returns Promise<string>
- */
- executeTemplate(
- templateFunction,
- assetsInformationByGroups,
- assetTags,
- compilation,
- ) {
- // Template processing
- const templateParamsPromise = this.getTemplateParameters(
- compilation,
- assetsInformationByGroups,
- assetTags,
- );
- return templateParamsPromise.then((templateParams) => {
- try {
- // If html is a promise return the promise
- // If html is a string turn it into a promise
- return templateFunction(templateParams);
- } catch (e) {
- // @ts-ignore
- compilation.errors.push(new Error("Template execution failed: " + e));
- return Promise.reject(e);
- }
- });
- }
- /**
- * Html Post processing
- *
- * @private
- * @param {Compiler} compiler The compiler instance
- * @param {any} originalHtml The input html
- * @param {AssetsInformationByGroups} assetsInformationByGroups
- * @param {{headTags: HtmlTagObject[], bodyTags: HtmlTagObject[]}} assetTags The asset tags to inject
- * @returns {Promise<string>}
- */
- postProcessHtml(
- compiler,
- originalHtml,
- assetsInformationByGroups,
- assetTags,
- ) {
- let html = originalHtml;
- if (typeof html !== "string") {
- return Promise.reject(
- new Error(
- "Expected html to be a string but got " + JSON.stringify(html),
- ),
- );
- }
- if (this.options.inject) {
- const htmlRegExp = /(<html[^>]*>)/i;
- const headRegExp = /(<\/head\s*>)/i;
- const bodyRegExp = /(<\/body\s*>)/i;
- const metaViewportRegExp = /<meta[^>]+name=["']viewport["'][^>]*>/i;
- const body = assetTags.bodyTags.map((assetTagObject) =>
- htmlTagObjectToString(assetTagObject, this.options.xhtml),
- );
- const head = assetTags.headTags
- .filter((item) => {
- if (
- item.tagName === "meta" &&
- item.attributes &&
- item.attributes.name === "viewport" &&
- metaViewportRegExp.test(html)
- ) {
- return false;
- }
- return true;
- })
- .map((assetTagObject) =>
- htmlTagObjectToString(assetTagObject, this.options.xhtml),
- );
- if (body.length) {
- if (bodyRegExp.test(html)) {
- // Append assets to body element
- html = html.replace(bodyRegExp, (match) => body.join("") + match);
- } else {
- // Append scripts to the end of the file if no <body> element exists:
- html += body.join("");
- }
- }
- if (head.length) {
- // Create a head tag if none exists
- if (!headRegExp.test(html)) {
- if (!htmlRegExp.test(html)) {
- html = "<head></head>" + html;
- } else {
- html = html.replace(htmlRegExp, (match) => match + "<head></head>");
- }
- }
- // Append assets to head element
- html = html.replace(headRegExp, (match) => head.join("") + match);
- }
- // Inject manifest into the opening html tag
- if (assetsInformationByGroups.manifest) {
- html = html.replace(/(<html[^>]*)(>)/i, (match, start, end) => {
- // Append the manifest only if no manifest was specified
- if (/\smanifest\s*=/.test(match)) {
- return match;
- }
- return (
- start +
- ' manifest="' +
- assetsInformationByGroups.manifest +
- '"' +
- end
- );
- });
- }
- }
- // TODO avoid this logic and use https://github.com/webpack-contrib/html-minimizer-webpack-plugin under the hood in the next major version
- // Check if webpack is running in production mode
- // @see https://github.com/webpack/webpack/blob/3366421f1784c449f415cda5930a8e445086f688/lib/WebpackOptionsDefaulter.js#L12-L14
- const isProductionLikeMode =
- compiler.options.mode === "production" || !compiler.options.mode;
- const needMinify =
- this.options.minify === true ||
- typeof this.options.minify === "object" ||
- (this.options.minify === "auto" && isProductionLikeMode);
- if (!needMinify) {
- return Promise.resolve(html);
- }
- const minifyOptions =
- typeof this.options.minify === "object"
- ? this.options.minify
- : {
- // https://www.npmjs.com/package/html-minifier-terser#options-quick-reference
- collapseWhitespace: true,
- keepClosingSlash: true,
- removeComments: true,
- removeRedundantAttributes: true,
- removeScriptTypeAttributes: true,
- removeStyleLinkTypeAttributes: true,
- useShortDoctype: true,
- };
- try {
- html = require("html-minifier-terser").minify(html, minifyOptions);
- } catch (e) {
- const isParseError = String(e.message).indexOf("Parse Error") === 0;
- if (isParseError) {
- e.message =
- "html-webpack-plugin could not minify the generated output.\n" +
- "In production mode the html minification is enabled by default.\n" +
- "If you are not generating a valid html output please disable it manually.\n" +
- "You can do so by adding the following setting to your HtmlWebpackPlugin config:\n|\n|" +
- " minify: false\n|\n" +
- "See https://github.com/jantimon/html-webpack-plugin#options for details.\n\n" +
- "For parser dedicated bugs please create an issue here:\n" +
- "https://danielruf.github.io/html-minifier-terser/" +
- "\n" +
- e.message;
- }
- return Promise.reject(e);
- }
- return Promise.resolve(html);
- }
- /**
- * Helper to return a sorted unique array of all asset files out of the asset object
- * @private
- */
- getAssetFiles(assets) {
- const files = _uniq(
- Object.keys(assets)
- .filter((assetType) => assetType !== "chunks" && assets[assetType])
- .reduce((files, assetType) => files.concat(assets[assetType]), []),
- );
- files.sort();
- return files;
- }
- /**
- * Converts a favicon file from disk to a webpack resource and returns the url to the resource
- *
- * @private
- * @param {Compiler} compiler
- * @param {string|false} favicon
- * @param {Compilation} compilation
- * @param {string} publicPath
- * @param {PreviousEmittedAssets} previousEmittedAssets
- * @returns {Promise<string|undefined>}
- */
- generateFavicon(
- compiler,
- favicon,
- compilation,
- publicPath,
- previousEmittedAssets,
- ) {
- if (!favicon) {
- return Promise.resolve(undefined);
- }
- const filename = path.resolve(compilation.compiler.context, favicon);
- return promisify(compilation.inputFileSystem.readFile)(filename)
- .then((buf) => {
- const source = new compiler.webpack.sources.RawSource(
- /** @type {string | Buffer} */ (buf),
- false,
- );
- const name = path.basename(filename);
- compilation.fileDependencies.add(filename);
- compilation.emitAsset(name, source);
- previousEmittedAssets.push({ name, source });
- const faviconPath = publicPath + name;
- if (this.options.hash) {
- return this.appendHash(
- faviconPath,
- /** @type {string} */ (compilation.hash),
- );
- }
- return faviconPath;
- })
- .catch(() =>
- Promise.reject(
- new Error("HtmlWebpackPlugin: could not load file " + filename),
- ),
- );
- }
- /**
- * Generate all tags script for the given file paths
- *
- * @private
- * @param {Array<string>} jsAssets
- * @returns {Array<HtmlTagObject>}
- */
- generatedScriptTags(jsAssets) {
- // @ts-ignore
- return jsAssets.map((src) => {
- const attributes = {};
- if (this.options.scriptLoading === "defer") {
- attributes.defer = true;
- } else if (this.options.scriptLoading === "module") {
- attributes.type = "module";
- } else if (this.options.scriptLoading === "systemjs-module") {
- attributes.type = "systemjs-module";
- }
- attributes.src = src;
- return {
- tagName: "script",
- voidTag: false,
- meta: { plugin: "html-webpack-plugin" },
- attributes,
- };
- });
- }
- /**
- * Generate all style tags for the given file paths
- *
- * @private
- * @param {Array<string>} cssAssets
- * @returns {Array<HtmlTagObject>}
- */
- generateStyleTags(cssAssets) {
- return cssAssets.map((styleAsset) => ({
- tagName: "link",
- voidTag: true,
- meta: { plugin: "html-webpack-plugin" },
- attributes: {
- href: styleAsset,
- rel: "stylesheet",
- },
- }));
- }
- /**
- * Generate an optional base tag
- *
- * @param {string | {[attributeName: string]: string}} base
- * @returns {Array<HtmlTagObject>}
- */
- generateBaseTag(base) {
- return [
- {
- tagName: "base",
- voidTag: true,
- meta: { plugin: "html-webpack-plugin" },
- // attributes e.g. { href:"http://example.com/page.html" target:"_blank" }
- attributes:
- typeof base === "string"
- ? {
- href: base,
- }
- : base,
- },
- ];
- }
- /**
- * Generate all meta tags for the given meta configuration
- *
- * @private
- * @param {false | {[name: string]: false | string | {[attributeName: string]: string|boolean}}} metaOptions
- * @returns {Array<HtmlTagObject>}
- */
- generatedMetaTags(metaOptions) {
- if (metaOptions === false) {
- return [];
- }
- // Make tags self-closing in case of xhtml
- // Turn { "viewport" : "width=500, initial-scale=1" } into
- // [{ name:"viewport" content:"width=500, initial-scale=1" }]
- const metaTagAttributeObjects = Object.keys(metaOptions)
- .map((metaName) => {
- const metaTagContent = metaOptions[metaName];
- return typeof metaTagContent === "string"
- ? {
- name: metaName,
- content: metaTagContent,
- }
- : metaTagContent;
- })
- .filter((attribute) => attribute !== false);
- // Turn [{ name:"viewport" content:"width=500, initial-scale=1" }] into
- // the html-webpack-plugin tag structure
- return metaTagAttributeObjects.map((metaTagAttributes) => {
- if (metaTagAttributes === false) {
- throw new Error("Invalid meta tag");
- }
- return {
- tagName: "meta",
- voidTag: true,
- meta: { plugin: "html-webpack-plugin" },
- attributes: metaTagAttributes,
- };
- });
- }
- /**
- * Generate a favicon tag for the given file path
- *
- * @private
- * @param {string} favicon
- * @returns {Array<HtmlTagObject>}
- */
- generateFaviconTag(favicon) {
- return [
- {
- tagName: "link",
- voidTag: true,
- meta: { plugin: "html-webpack-plugin" },
- attributes: {
- rel: "icon",
- href: favicon,
- },
- },
- ];
- }
- /**
- * Group assets to head and body tags
- *
- * @param {{
- scripts: Array<HtmlTagObject>;
- styles: Array<HtmlTagObject>;
- meta: Array<HtmlTagObject>;
- }} assetTags
- * @param {"body" | "head"} scriptTarget
- * @returns {{
- headTags: Array<HtmlTagObject>;
- bodyTags: Array<HtmlTagObject>;
- }}
- */
- groupAssetsByElements(assetTags, scriptTarget) {
- /** @type {{ headTags: Array<HtmlTagObject>; bodyTags: Array<HtmlTagObject>; }} */
- const result = {
- headTags: [...assetTags.meta, ...assetTags.styles],
- bodyTags: [],
- };
- // Add script tags to head or body depending on
- // the htmlPluginOptions
- if (scriptTarget === "body") {
- result.bodyTags.push(...assetTags.scripts);
- } else {
- // If script loading is blocking add the scripts to the end of the head
- // If script loading is non-blocking add the scripts in front of the css files
- const insertPosition =
- this.options.scriptLoading === "blocking"
- ? result.headTags.length
- : assetTags.meta.length;
- result.headTags.splice(insertPosition, 0, ...assetTags.scripts);
- }
- return result;
- }
- /**
- * Replace [contenthash] in filename
- *
- * @see https://survivejs.com/webpack/optimizing/adding-hashes-to-filenames/
- *
- * @private
- * @param {Compiler} compiler
- * @param {string} filename
- * @param {string|Buffer} fileContent
- * @param {Compilation} compilation
- * @returns {{ path: string, info: {} }}
- */
- replacePlaceholdersInFilename(compiler, filename, fileContent, compilation) {
- if (/\[\\*([\w:]+)\\*\]/i.test(filename) === false) {
- return { path: filename, info: {} };
- }
- const hash = compiler.webpack.util.createHash(
- compilation.outputOptions.hashFunction,
- );
- hash.update(fileContent);
- if (compilation.outputOptions.hashSalt) {
- hash.update(compilation.outputOptions.hashSalt);
- }
- const contentHash = /** @type {string} */ (
- hash
- .digest(compilation.outputOptions.hashDigest)
- .slice(0, compilation.outputOptions.hashDigestLength)
- );
- return compilation.getPathWithInfo(filename, {
- contentHash,
- chunk: {
- hash: contentHash,
- // @ts-ignore
- contentHash,
- },
- });
- }
- /**
- * Function to generate HTML file.
- *
- * @private
- * @param {Compiler} compiler
- * @param {Compilation} compilation
- * @param {string} outputName
- * @param {CachedChildCompilation} childCompilerPlugin
- * @param {PreviousEmittedAssets} previousEmittedAssets
- * @param {{ value: string | undefined }} assetJson
- * @param {(err?: Error) => void} callback
- */
- generateHTML(
- compiler,
- compilation,
- outputName,
- childCompilerPlugin,
- previousEmittedAssets,
- assetJson,
- callback,
- ) {
- // Get all entry point names for this html file
- const entryNames = Array.from(compilation.entrypoints.keys());
- const filteredEntryNames = this.filterEntryChunks(
- entryNames,
- this.options.chunks,
- this.options.excludeChunks,
- );
- const sortedEntryNames = this.sortEntryChunks(
- filteredEntryNames,
- this.options.chunksSortMode,
- compilation,
- );
- const templateResult = this.options.templateContent
- ? { mainCompilationHash: compilation.hash }
- : childCompilerPlugin.getCompilationEntryResult(this.options.template);
- if ("error" in templateResult) {
- compilation.errors.push(
- new Error(
- prettyError(templateResult.error, compiler.context).toString(),
- ),
- );
- }
- // If the child compilation was not executed during a previous main compile run
- // it is a cached result
- const isCompilationCached =
- templateResult.mainCompilationHash !== compilation.hash;
- /** Generated file paths from the entry point names */
- const assetsInformationByGroups = this.getAssetsInformationByGroups(
- compilation,
- outputName,
- sortedEntryNames,
- );
- // If the template and the assets did not change we don't have to emit the html
- const newAssetJson = JSON.stringify(
- this.getAssetFiles(assetsInformationByGroups),
- );
- if (
- isCompilationCached &&
- this.options.cache &&
- assetJson.value === newAssetJson
- ) {
- previousEmittedAssets.forEach(({ name, source, info }) => {
- compilation.emitAsset(name, source, info);
- });
- return callback();
- } else {
- previousEmittedAssets.length = 0;
- assetJson.value = newAssetJson;
- }
- // The html-webpack plugin uses a object representation for the html-tags which will be injected
- // to allow altering them more easily
- // Just before they are converted a third-party-plugin author might change the order and content
- const assetsPromise = this.generateFavicon(
- compiler,
- this.options.favicon,
- compilation,
- assetsInformationByGroups.publicPath,
- previousEmittedAssets,
- ).then((faviconPath) => {
- assetsInformationByGroups.favicon = faviconPath;
- return HtmlWebpackPlugin.getCompilationHooks(
- compilation,
- ).beforeAssetTagGeneration.promise({
- assets: assetsInformationByGroups,
- outputName,
- plugin: this,
- });
- });
- // Turn the js and css paths into grouped HtmlTagObjects
- const assetTagGroupsPromise = assetsPromise
- // And allow third-party-plugin authors to reorder and change the assetTags before they are grouped
- .then(({ assets }) =>
- HtmlWebpackPlugin.getCompilationHooks(
- compilation,
- ).alterAssetTags.promise({
- assetTags: {
- scripts: this.generatedScriptTags(assets.js),
- styles: this.generateStyleTags(assets.css),
- meta: [
- ...(this.options.base !== false
- ? this.generateBaseTag(this.options.base)
- : []),
- ...this.generatedMetaTags(this.options.meta),
- ...(assets.favicon
- ? this.generateFaviconTag(assets.favicon)
- : []),
- ],
- },
- outputName,
- publicPath: assetsInformationByGroups.publicPath,
- plugin: this,
- }),
- )
- .then(({ assetTags }) => {
- // Inject scripts to body unless it set explicitly to head
- const scriptTarget =
- this.options.inject === "head" ||
- (this.options.inject !== "body" &&
- this.options.scriptLoading !== "blocking")
- ? "head"
- : "body";
- // Group assets to `head` and `body` tag arrays
- const assetGroups = this.groupAssetsByElements(assetTags, scriptTarget);
- // Allow third-party-plugin authors to reorder and change the assetTags once they are grouped
- return HtmlWebpackPlugin.getCompilationHooks(
- compilation,
- ).alterAssetTagGroups.promise({
- headTags: assetGroups.headTags,
- bodyTags: assetGroups.bodyTags,
- outputName,
- publicPath: assetsInformationByGroups.publicPath,
- plugin: this,
- });
- });
- // Turn the compiled template into a nodejs function or into a nodejs string
- const templateEvaluationPromise = Promise.resolve().then(() => {
- if ("error" in templateResult) {
- return this.options.showErrors
- ? prettyError(templateResult.error, compiler.context).toHtml()
- : "ERROR";
- }
- // Allow to use a custom function / string instead
- if (this.options.templateContent !== false) {
- return this.options.templateContent;
- }
- // Once everything is compiled evaluate the html factory and replace it with its content
- if ("compiledEntry" in templateResult) {
- const compiledEntry = templateResult.compiledEntry;
- const assets = compiledEntry.assets;
- // Store assets from child compiler to re-emit them later
- for (const name in assets) {
- previousEmittedAssets.push({
- name,
- source: assets[name].source,
- info: assets[name].info,
- });
- }
- return this.evaluateCompilationResult(
- compiledEntry.content,
- assetsInformationByGroups.publicPath,
- this.options.template,
- );
- }
- return Promise.reject(
- new Error("Child compilation contained no compiledEntry"),
- );
- });
- const templateExecutionPromise = Promise.all([
- assetsPromise,
- assetTagGroupsPromise,
- templateEvaluationPromise,
- ])
- // Execute the template
- .then(([assetsHookResult, assetTags, compilationResult]) =>
- typeof compilationResult !== "function"
- ? compilationResult
- : this.executeTemplate(
- compilationResult,
- assetsHookResult.assets,
- { headTags: assetTags.headTags, bodyTags: assetTags.bodyTags },
- compilation,
- ),
- );
- const injectedHtmlPromise = Promise.all([
- assetTagGroupsPromise,
- templateExecutionPromise,
- ])
- // Allow plugins to change the html before assets are injected
- .then(([assetTags, html]) => {
- const pluginArgs = {
- html,
- headTags: assetTags.headTags,
- bodyTags: assetTags.bodyTags,
- plugin: this,
- outputName,
- };
- return HtmlWebpackPlugin.getCompilationHooks(
- compilation,
- ).afterTemplateExecution.promise(pluginArgs);
- })
- .then(({ html, headTags, bodyTags }) => {
- return this.postProcessHtml(compiler, html, assetsInformationByGroups, {
- headTags,
- bodyTags,
- });
- });
- const emitHtmlPromise = injectedHtmlPromise
- // Allow plugins to change the html after assets are injected
- .then((html) => {
- const pluginArgs = { html, plugin: this, outputName };
- return HtmlWebpackPlugin.getCompilationHooks(compilation)
- .beforeEmit.promise(pluginArgs)
- .then((result) => result.html);
- })
- .catch((err) => {
- // In case anything went wrong the promise is resolved
- // with the error message and an error is logged
- compilation.errors.push(
- new Error(prettyError(err, compiler.context).toString()),
- );
- return this.options.showErrors
- ? prettyError(err, compiler.context).toHtml()
- : "ERROR";
- })
- .then((html) => {
- const filename = outputName.replace(
- /\[templatehash([^\]]*)\]/g,
- require("util").deprecate(
- (match, options) => `[contenthash${options}]`,
- "[templatehash] is now [contenthash]",
- ),
- );
- const replacedFilename = this.replacePlaceholdersInFilename(
- compiler,
- filename,
- html,
- compilation,
- );
- const source = new compiler.webpack.sources.RawSource(html, false);
- // Add the evaluated html code to the webpack assets
- compilation.emitAsset(
- replacedFilename.path,
- source,
- replacedFilename.info,
- );
- previousEmittedAssets.push({ name: replacedFilename.path, source });
- return replacedFilename.path;
- })
- .then((finalOutputName) =>
- HtmlWebpackPlugin.getCompilationHooks(compilation)
- .afterEmit.promise({
- outputName: finalOutputName,
- plugin: this,
- })
- .catch((err) => {
- /** @type {Logger} */
- (this.logger).error(err);
- return null;
- })
- .then(() => null),
- );
- // Once all files are added to the webpack compilation
- // let the webpack compiler continue
- emitHtmlPromise.then(() => {
- callback();
- });
- }
- }
- /**
- * The default for options.templateParameter
- * Generate the template parameters
- *
- * Generate the template parameters for the template function
- * @param {Compilation} compilation
- * @param {AssetsInformationByGroups} assets
- * @param {{
- headTags: HtmlTagObject[],
- bodyTags: HtmlTagObject[]
- }} assetTags
- * @param {ProcessedHtmlWebpackOptions} options
- * @returns {TemplateParameter}
- */
- function templateParametersGenerator(compilation, assets, assetTags, options) {
- return {
- compilation: compilation,
- webpackConfig: compilation.options,
- htmlWebpackPlugin: {
- tags: assetTags,
- files: assets,
- options: options,
- },
- };
- }
- // Statics:
- /**
- * The major version number of this plugin
- */
- HtmlWebpackPlugin.version = 5;
- /**
- * A static helper to get the hooks for this plugin
- *
- * Usage: HtmlWebpackPlugin.getHooks(compilation).HOOK_NAME.tapAsync('YourPluginName', () => { ... });
- */
- // TODO remove me in the next major release in favor getCompilationHooks
- HtmlWebpackPlugin.getHooks = HtmlWebpackPlugin.getCompilationHooks;
- HtmlWebpackPlugin.createHtmlTagObject = createHtmlTagObject;
- module.exports = HtmlWebpackPlugin;
|