index.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. // @ts-check
  2. 'use strict';
  3. // use Polyfill for util.promisify in node versions < v8
  4. const promisify = require('util.promisify');
  5. // Import types
  6. /* eslint-disable */
  7. /// <reference path="./typings.d.ts" />
  8. /* eslint-enable */
  9. /** @typedef {import("webpack/lib/Compiler.js")} WebpackCompiler */
  10. /** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */
  11. const vm = require('vm');
  12. const fs = require('fs');
  13. const _ = require('lodash');
  14. const path = require('path');
  15. const htmlTagObjectToString = require('./lib/html-tags').htmlTagObjectToString;
  16. const childCompiler = require('./lib/compiler.js');
  17. const prettyError = require('./lib/errors.js');
  18. const chunkSorter = require('./lib/chunksorter.js');
  19. const getHtmlWebpackPluginHooks = require('./lib/hooks.js').getHtmlWebpackPluginHooks;
  20. const getHtmlWebpackPluginHook = require('./lib/hooks.js').getHtmlWebpackPluginHook;
  21. const fsStatAsync = promisify(fs.stat);
  22. const fsReadFileAsync = promisify(fs.readFile);
  23. class HtmlWebpackPlugin {
  24. /**
  25. * @param {Partial<HtmlWebpackPluginOptions>} options
  26. */
  27. constructor (options) {
  28. // Default options
  29. /**
  30. * @type {HtmlWebpackPluginOptions}
  31. */
  32. this.options = Object.assign({
  33. template: path.join(__dirname, 'default_index.ejs'),
  34. templateContent: false,
  35. templateParameters: templateParametersGenerator,
  36. filename: 'index.html',
  37. hash: false,
  38. inject: true,
  39. compile: true,
  40. favicon: false,
  41. minify: false,
  42. cache: true,
  43. showErrors: true,
  44. chunks: 'all',
  45. excludeChunks: [],
  46. chunksSortMode: 'auto',
  47. meta: {},
  48. title: 'Webpack App',
  49. xhtml: false
  50. }, options);
  51. // Instance variables to keep caching information
  52. // for multiple builds
  53. this.childCompilerHash = undefined;
  54. this.childCompilationOutputName = undefined;
  55. this.assetJson = undefined;
  56. this.hash = undefined;
  57. /**
  58. * The major version number of this plugin
  59. */
  60. this.version = 4;
  61. }
  62. /**
  63. * apply is called by the webpack main compiler during the start phase
  64. * @param {WebpackCompiler} compiler
  65. */
  66. apply (compiler) {
  67. const self = this;
  68. let isCompilationCached = false;
  69. let compilationPromise;
  70. this.options.template = this.getFullTemplatePath(this.options.template, compiler.context);
  71. // convert absolute filename into relative so that webpack can
  72. // generate it at correct location
  73. const filename = this.options.filename;
  74. if (path.resolve(filename) === path.normalize(filename)) {
  75. this.options.filename = path.relative(compiler.options.output.path, filename);
  76. }
  77. // setup hooks for third party plugins
  78. compiler.hooks.compilation.tap('HtmlWebpackPluginHooks', getHtmlWebpackPluginHooks);
  79. compiler.hooks.make.tapAsync('HtmlWebpackPlugin', (compilation, callback) => {
  80. // Compile the template (queued)
  81. compilationPromise = childCompiler.compileTemplate(self.options.template, compiler.context, self.options.filename, compilation)
  82. .catch(err => {
  83. compilation.errors.push(prettyError(err, compiler.context).toString());
  84. return {
  85. content: self.options.showErrors ? prettyError(err, compiler.context).toJsonHtml() : 'ERROR',
  86. outputName: self.options.filename
  87. };
  88. })
  89. .then(compilationResult => {
  90. // If the compilation change didnt change the cache is valid
  91. isCompilationCached = compilationResult.hash && self.childCompilerHash === compilationResult.hash;
  92. self.childCompilerHash = compilationResult.hash;
  93. self.childCompilationOutputName = compilationResult.outputName;
  94. callback();
  95. return compilationResult.content;
  96. });
  97. });
  98. compiler.hooks.emit.tapAsync('HtmlWebpackPlugin',
  99. /**
  100. * Hook into the webpack emit phase
  101. * @param {WebpackCompilation} compilation
  102. * @param {() => void} callback
  103. */
  104. (compilation, callback) => {
  105. // Get all entry point names for this html file
  106. const entryNames = Array.from(compilation.entrypoints.keys());
  107. const filteredEntryNames = self.filterChunks(entryNames, self.options.chunks, self.options.excludeChunks);
  108. const sortedEntryNames = self.sortEntryChunks(filteredEntryNames, this.options.chunksSortMode, compilation);
  109. // Turn the entry point names into file paths
  110. const assets = self.htmlWebpackPluginAssets(compilation, sortedEntryNames);
  111. // If this is a hot update compilation, move on!
  112. // This solves a problem where an `index.html` file is generated for hot-update js files
  113. // It only happens in Webpack 2, where hot updates are emitted separately before the full bundle
  114. if (self.isHotUpdateCompilation(assets)) {
  115. return callback();
  116. }
  117. // If the template and the assets did not change we don't have to emit the html
  118. const assetJson = JSON.stringify(self.getAssetFiles(assets));
  119. if (isCompilationCached && self.options.cache && assetJson === self.assetJson) {
  120. return callback();
  121. } else {
  122. self.assetJson = assetJson;
  123. }
  124. Promise.resolve()
  125. // Favicon
  126. .then(() => {
  127. if (self.options.favicon) {
  128. return self.addFileToAssets(self.options.favicon, compilation)
  129. .then(faviconBasename => {
  130. let publicPath = compilation.mainTemplate.getPublicPath({hash: compilation.hash}) || '';
  131. if (publicPath && publicPath.substr(-1) !== '/') {
  132. publicPath += '/';
  133. }
  134. assets.favicon = publicPath + faviconBasename;
  135. });
  136. }
  137. })
  138. // Wait for the compilation to finish
  139. .then(() => compilationPromise)
  140. .then(compiledTemplate => {
  141. // Allow to use a custom function / string instead
  142. if (self.options.templateContent !== false) {
  143. return self.options.templateContent;
  144. }
  145. // Once everything is compiled evaluate the html factory
  146. // and replace it with its content
  147. return self.evaluateCompilationResult(compilation, compiledTemplate);
  148. })
  149. // Allow plugins to make changes to the assets before invoking the template
  150. // This only makes sense to use if `inject` is `false`
  151. .then(compilationResult => getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginBeforeHtmlGeneration').promise({
  152. assets: assets,
  153. outputName: self.childCompilationOutputName,
  154. plugin: self
  155. })
  156. .then(() => compilationResult))
  157. // Execute the template
  158. .then(compilationResult => typeof compilationResult !== 'function'
  159. ? compilationResult
  160. : self.executeTemplate(compilationResult, assets, compilation))
  161. // Allow plugins to change the html before assets are injected
  162. .then(html => {
  163. const pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName};
  164. return getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginBeforeHtmlProcessing').promise(pluginArgs);
  165. })
  166. .then(result => {
  167. const html = result.html;
  168. const assets = result.assets;
  169. // Prepare script and link tags
  170. const assetTags = self.generateHtmlTagObjects(assets);
  171. const pluginArgs = {head: assetTags.head, body: assetTags.body, plugin: self, outputName: self.childCompilationOutputName};
  172. // Allow plugins to change the assetTag definitions
  173. return getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginAlterAssetTags').promise(pluginArgs)
  174. .then(result => self.postProcessHtml(html, assets, { body: result.body, head: result.head })
  175. .then(html => _.extend(result, {html: html, assets: assets})));
  176. })
  177. // Allow plugins to change the html after assets are injected
  178. .then(result => {
  179. const html = result.html;
  180. const assets = result.assets;
  181. const pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName};
  182. return getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginAfterHtmlProcessing').promise(pluginArgs)
  183. .then(result => result.html);
  184. })
  185. .catch(err => {
  186. // In case anything went wrong the promise is resolved
  187. // with the error message and an error is logged
  188. compilation.errors.push(prettyError(err, compiler.context).toString());
  189. // Prevent caching
  190. self.hash = null;
  191. return self.options.showErrors ? prettyError(err, compiler.context).toHtml() : 'ERROR';
  192. })
  193. .then(html => {
  194. // Replace the compilation result with the evaluated html code
  195. compilation.assets[self.childCompilationOutputName] = {
  196. source: () => html,
  197. size: () => html.length
  198. };
  199. })
  200. .then(() => getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginAfterEmit').promise({
  201. html: compilation.assets[self.childCompilationOutputName],
  202. outputName: self.childCompilationOutputName,
  203. plugin: self
  204. }).catch(err => {
  205. console.error(err);
  206. return null;
  207. }).then(() => null))
  208. // Let webpack continue with it
  209. .then(() => {
  210. callback();
  211. });
  212. });
  213. }
  214. /**
  215. * Evaluates the child compilation result
  216. * Returns a promise
  217. */
  218. evaluateCompilationResult (compilation, source) {
  219. if (!source) {
  220. return Promise.reject('The child compilation didn\'t provide a result');
  221. }
  222. // The LibraryTemplatePlugin stores the template result in a local variable.
  223. // To extract the result during the evaluation this part has to be removed.
  224. source = source.replace('var HTML_WEBPACK_PLUGIN_RESULT =', '');
  225. const template = this.options.template.replace(/^.+!/, '').replace(/\?.+$/, '');
  226. const vmContext = vm.createContext(_.extend({HTML_WEBPACK_PLUGIN: true, require: require}, global));
  227. const vmScript = new vm.Script(source, {filename: template});
  228. // Evaluate code and cast to string
  229. let newSource;
  230. try {
  231. newSource = vmScript.runInContext(vmContext);
  232. } catch (e) {
  233. return Promise.reject(e);
  234. }
  235. if (typeof newSource === 'object' && newSource.__esModule && newSource.default) {
  236. newSource = newSource.default;
  237. }
  238. return typeof newSource === 'string' || typeof newSource === 'function'
  239. ? Promise.resolve(newSource)
  240. : Promise.reject('The loader "' + this.options.template + '" didn\'t return html.');
  241. }
  242. /**
  243. * Generate the template parameters for the template function
  244. * @param {WebpackCompilation} compilation
  245. *
  246. */
  247. getTemplateParameters (compilation, assets) {
  248. if (typeof this.options.templateParameters === 'function') {
  249. return this.options.templateParameters(compilation, assets, this.options);
  250. }
  251. if (typeof this.options.templateParameters === 'object') {
  252. return this.options.templateParameters;
  253. }
  254. return {};
  255. }
  256. /**
  257. * Html post processing
  258. *
  259. * @returns Promise<string>
  260. */
  261. executeTemplate (templateFunction, assets, compilation) {
  262. // Template processing
  263. const templateParams = this.getTemplateParameters(compilation, assets);
  264. let html = '';
  265. try {
  266. html = templateFunction(templateParams);
  267. } catch (e) {
  268. compilation.errors.push(new Error('Template execution failed: ' + e));
  269. return Promise.reject(e);
  270. }
  271. // If html is a promise return the promise
  272. // If html is a string turn it into a promise
  273. return Promise.resolve().then(() => html);
  274. }
  275. /**
  276. * Html post processing
  277. *
  278. * Returns a promise
  279. */
  280. postProcessHtml (html, assets, assetTags) {
  281. if (typeof html !== 'string') {
  282. return Promise.reject('Expected html to be a string but got ' + JSON.stringify(html));
  283. }
  284. return Promise.resolve()
  285. // Inject
  286. .then(() => {
  287. if (this.options.inject) {
  288. return this.injectAssetsIntoHtml(html, assets, assetTags);
  289. } else {
  290. return html;
  291. }
  292. })
  293. // Minify
  294. .then(html => {
  295. if (this.options.minify) {
  296. const minify = require('html-minifier').minify;
  297. return minify(html, this.options.minify === true ? {} : this.options.minify);
  298. }
  299. return html;
  300. });
  301. }
  302. /*
  303. * Pushes the content of the given filename to the compilation assets
  304. * @param {string} filename
  305. * @param {WebpackCompilation} compilation
  306. */
  307. addFileToAssets (filename, compilation) {
  308. filename = path.resolve(compilation.compiler.context, filename);
  309. return Promise.all([
  310. fsStatAsync(filename),
  311. fsReadFileAsync(filename)
  312. ])
  313. .then(([size, source]) => {
  314. return {
  315. size,
  316. source
  317. };
  318. })
  319. .catch(() => Promise.reject(new Error('HtmlWebpackPlugin: could not load file ' + filename)))
  320. .then(results => {
  321. const basename = path.basename(filename);
  322. compilation.fileDependencies.add(filename);
  323. compilation.assets[basename] = {
  324. source: () => results.source,
  325. size: () => results.size.size
  326. };
  327. return basename;
  328. });
  329. }
  330. /**
  331. * Helper to sort chunks
  332. * @param {string[]} entryNames
  333. * @param {string|((entryNameA: string, entryNameB: string) => number)} sortMode
  334. * @param {WebpackCompilation} compilation
  335. */
  336. sortEntryChunks (entryNames, sortMode, compilation) {
  337. // Custom function
  338. if (typeof sortMode === 'function') {
  339. return entryNames.sort(sortMode);
  340. }
  341. // Check if the given sort mode is a valid chunkSorter sort mode
  342. if (typeof chunkSorter[sortMode] !== 'undefined') {
  343. return chunkSorter[sortMode](entryNames, compilation, this.options);
  344. }
  345. throw new Error('"' + sortMode + '" is not a valid chunk sort mode');
  346. }
  347. /**
  348. * Return all chunks from the compilation result which match the exclude and include filters
  349. * @param {any} chunks
  350. * @param {string[]|'all'} includedChunks
  351. * @param {string[]} excludedChunks
  352. */
  353. filterChunks (chunks, includedChunks, excludedChunks) {
  354. return chunks.filter(chunkName => {
  355. // Skip if the chunks should be filtered and the given chunk was not added explicity
  356. if (Array.isArray(includedChunks) && includedChunks.indexOf(chunkName) === -1) {
  357. return false;
  358. }
  359. // Skip if the chunks should be filtered and the given chunk was excluded explicity
  360. if (Array.isArray(excludedChunks) && excludedChunks.indexOf(chunkName) !== -1) {
  361. return false;
  362. }
  363. // Add otherwise
  364. return true;
  365. });
  366. }
  367. isHotUpdateCompilation (assets) {
  368. return assets.js.length && assets.js.every(name => /\.hot-update\.js$/.test(name));
  369. }
  370. /**
  371. * The htmlWebpackPluginAssets extracts the asset information of a webpack compilation
  372. * for all given entry names
  373. * @param {WebpackCompilation} compilation
  374. * @param {string[]} entryNames
  375. * @returns {{
  376. publicPath: string,
  377. js: Array<{entryName: string, path: string}>,
  378. css: Array<{entryName: string, path: string}>,
  379. manifest?: string,
  380. favicon?: string
  381. }}
  382. */
  383. htmlWebpackPluginAssets (compilation, entryNames) {
  384. const compilationHash = compilation.hash;
  385. /**
  386. * @type {string} the configured public path to the asset root
  387. * if a publicPath is set in the current webpack config use it otherwise
  388. * fallback to a realtive path
  389. */
  390. let publicPath = typeof compilation.options.output.publicPath !== 'undefined'
  391. // If a hard coded public path exists use it
  392. ? compilation.mainTemplate.getPublicPath({hash: compilationHash})
  393. // If no public path was set get a relative url path
  394. : path.relative(path.resolve(compilation.options.output.path, path.dirname(this.childCompilationOutputName)), compilation.options.output.path)
  395. .split(path.sep).join('/');
  396. if (publicPath.length && publicPath.substr(-1, 1) !== '/') {
  397. publicPath += '/';
  398. }
  399. /**
  400. * @type {{
  401. publicPath: string,
  402. js: Array<{entryName: string, path: string}>,
  403. css: Array<{entryName: string, path: string}>,
  404. manifest?: string,
  405. favicon?: string
  406. }}
  407. */
  408. const assets = {
  409. // The public path
  410. publicPath: publicPath,
  411. // Will contain all js files
  412. js: [],
  413. // Will contain all css files
  414. css: [],
  415. // Will contain the html5 appcache manifest files if it exists
  416. manifest: Object.keys(compilation.assets).find(assetFile => path.extname(assetFile) === '.appcache'),
  417. // Favicon
  418. favicon: undefined
  419. };
  420. // Append a hash for cache busting
  421. if (this.options.hash && assets.manifest) {
  422. assets.manifest = this.appendHash(assets.manifest, compilationHash);
  423. }
  424. // Extract paths to .js and .css files from the current compilation
  425. const extensionRegexp = /\.(css|js)(\?|$)/;
  426. for (let i = 0; i < entryNames.length; i++) {
  427. const entryName = entryNames[i];
  428. const entryPointFiles = compilation.entrypoints.get(entryName).getFiles();
  429. // Prepend the publicPath and append the hash depending on the
  430. // webpack.output.publicPath and hashOptions
  431. // E.g. bundle.js -> /bundle.js?hash
  432. const entryPointPublicPaths = entryPointFiles
  433. .map(chunkFile => {
  434. const entryPointPublicPath = publicPath + chunkFile;
  435. return this.options.hash
  436. ? this.appendHash(entryPointPublicPath, compilationHash)
  437. : entryPointPublicPath;
  438. });
  439. entryPointPublicPaths.forEach((entryPointPublicPaths) => {
  440. const extMatch = extensionRegexp.exec(entryPointPublicPaths);
  441. // Skip if the public path is not a .css or .js file
  442. if (!extMatch) {
  443. return;
  444. }
  445. // ext will contain .js or .css
  446. const ext = extMatch[1];
  447. assets[ext].push({
  448. entryName: entryName,
  449. path: entryPointPublicPaths
  450. });
  451. });
  452. }
  453. return assets;
  454. }
  455. /**
  456. * Generate meta tags
  457. * @returns {HtmlTagObject[]}
  458. */
  459. getMetaTags () {
  460. const metaOptions = this.options.meta;
  461. if (metaOptions === false) {
  462. return [];
  463. }
  464. // Make tags self-closing in case of xhtml
  465. // Turn { "viewport" : "width=500, initial-scale=1" } into
  466. // [{ name:"viewport" content:"width=500, initial-scale=1" }]
  467. const metaTagAttributeObjects = Object.keys(metaOptions).map((metaName) => {
  468. const metaTagContent = metaOptions[metaName];
  469. return (typeof metaTagContent === 'string') ? {
  470. name: metaName,
  471. content: metaTagContent
  472. } : metaTagContent;
  473. });
  474. // Turn [{ name:"viewport" content:"width=500, initial-scale=1" }] into
  475. // the html-webpack-plugin tag structure
  476. return metaTagAttributeObjects.map((metaTagAttributes) => {
  477. return {
  478. tagName: 'meta',
  479. voidTag: true,
  480. attributes: metaTagAttributes
  481. };
  482. });
  483. }
  484. /**
  485. * Turns the given asset information into tag object representations
  486. * which is seperated into head and body
  487. *
  488. * @param {{
  489. js: {entryName: string, path: string}[],
  490. css: {entryName: string, path: string}[],
  491. favicon?: string
  492. }} assets
  493. *
  494. * @returns {{
  495. head: HtmlTagObject[],
  496. body: HtmlTagObject[]
  497. }}
  498. */
  499. generateHtmlTagObjects (assets) {
  500. // Turn script files into script tags
  501. const scripts = assets.js.map(scriptAsset => ({
  502. tagName: 'script',
  503. voidTag: false,
  504. attributes: {
  505. src: scriptAsset.path
  506. }
  507. }));
  508. // Turn css files into link tags
  509. const styles = assets.css.map(styleAsset => ({
  510. tagName: 'link',
  511. voidTag: true,
  512. attributes: {
  513. href: styleAsset.path,
  514. rel: 'stylesheet'
  515. }
  516. }));
  517. // Injection targets
  518. let head = this.getMetaTags();
  519. let body = [];
  520. // If there is a favicon present, add it to the head
  521. if (assets.favicon) {
  522. head.push({
  523. tagName: 'link',
  524. voidTag: true,
  525. attributes: {
  526. rel: 'shortcut icon',
  527. href: assets.favicon
  528. }
  529. });
  530. }
  531. // Add styles to the head
  532. head = head.concat(styles);
  533. // Add scripts to body or head
  534. if (this.options.inject === 'head') {
  535. head = head.concat(scripts);
  536. } else {
  537. body = body.concat(scripts);
  538. }
  539. return {head: head, body: body};
  540. }
  541. /**
  542. * Injects the assets into the given html string
  543. *
  544. * @param {string} html
  545. * @param {any} assets
  546. * The input html
  547. * @param {{
  548. head: HtmlTagObject[],
  549. body: HtmlTagObject[]
  550. }} assetTags
  551. * The asset tags to inject
  552. *
  553. * @returns {string}
  554. */
  555. injectAssetsIntoHtml (html, assets, assetTags) {
  556. const htmlRegExp = /(<html[^>]*>)/i;
  557. const headRegExp = /(<\/head\s*>)/i;
  558. const bodyRegExp = /(<\/body\s*>)/i;
  559. const body = assetTags.body.map((assetTagObject) => htmlTagObjectToString(assetTagObject, this.options.xhtml));
  560. const head = assetTags.head.map((assetTagObject) => htmlTagObjectToString(assetTagObject, this.options.xhtml));
  561. if (body.length) {
  562. if (bodyRegExp.test(html)) {
  563. // Append assets to body element
  564. html = html.replace(bodyRegExp, match => body.join('') + match);
  565. } else {
  566. // Append scripts to the end of the file if no <body> element exists:
  567. html += body.join('');
  568. }
  569. }
  570. if (head.length) {
  571. // Create a head tag if none exists
  572. if (!headRegExp.test(html)) {
  573. if (!htmlRegExp.test(html)) {
  574. html = '<head></head>' + html;
  575. } else {
  576. html = html.replace(htmlRegExp, match => match + '<head></head>');
  577. }
  578. }
  579. // Append assets to head element
  580. html = html.replace(headRegExp, match => head.join('') + match);
  581. }
  582. // Inject manifest into the opening html tag
  583. if (assets.manifest) {
  584. html = html.replace(/(<html[^>]*)(>)/i, (match, start, end) => {
  585. // Append the manifest only if no manifest was specified
  586. if (/\smanifest\s*=/.test(match)) {
  587. return match;
  588. }
  589. return start + ' manifest="' + assets.manifest + '"' + end;
  590. });
  591. }
  592. return html;
  593. }
  594. /**
  595. * Appends a cache busting hash to the query string of the url
  596. * E.g. http://localhost:8080/ -> http://localhost:8080/?50c9096ba6183fd728eeb065a26ec175
  597. * @param {string} url
  598. * @param {string} hash
  599. */
  600. appendHash (url, hash) {
  601. if (!url) {
  602. return url;
  603. }
  604. return url + (url.indexOf('?') === -1 ? '?' : '&') + hash;
  605. }
  606. /**
  607. * Helper to return the absolute template path with a fallback loader
  608. * @param {string} template
  609. * The path to the tempalate e.g. './index.html'
  610. * @param {string} context
  611. * The webpack base resolution path for relative paths e.g. process.cwd()
  612. */
  613. getFullTemplatePath (template, context) {
  614. // If the template doesn't use a loader use the lodash template loader
  615. if (template.indexOf('!') === -1) {
  616. template = require.resolve('./lib/loader.js') + '!' + path.resolve(context, template);
  617. }
  618. // Resolve template path
  619. return template.replace(
  620. /([!])([^/\\][^!?]+|[^/\\!?])($|\?[^!?\n]+$)/,
  621. (match, prefix, filepath, postfix) => prefix + path.resolve(filepath) + postfix);
  622. }
  623. /**
  624. * Helper to return a sorted unique array of all asset files out of the
  625. * asset object
  626. */
  627. getAssetFiles (assets) {
  628. const files = _.uniq(Object.keys(assets).filter(assetType => assetType !== 'chunks' && assets[assetType]).reduce((files, assetType) => files.concat(assets[assetType]), []));
  629. files.sort();
  630. return files;
  631. }
  632. }
  633. /**
  634. * The default for options.templateParameter
  635. * Generate the template parameters
  636. */
  637. function templateParametersGenerator (compilation, assets, options) {
  638. return {
  639. compilation: compilation,
  640. webpackConfig: compilation.options,
  641. htmlWebpackPlugin: {
  642. files: assets,
  643. options: options
  644. }
  645. };
  646. }
  647. module.exports = HtmlWebpackPlugin;