diff --git a/.travis.yml b/.travis.yml index b4267b3e..0e114d3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,17 @@ node_js: - stable - lts/* - 6.9 +env: + - WEBPACK_CORE=4 + - WEBPACK_CORE=beta +jobs: + exclude: + - node_js: 6.9 + env: WEBPACK_CORE=beta before_install: - stty columns 120 install: - travis_retry npm install --ignore-scripts + - travis_retry npm install "webpack@$WEBPACK_CORE" --ignore-scripts script: - travis_retry npm test diff --git a/examples/appcache/dist/webpack-5/0714810ae3fb211173e2964249507195.png b/examples/appcache/dist/webpack-5/0714810ae3fb211173e2964249507195.png new file mode 100644 index 00000000..d71b3d78 Binary files /dev/null and b/examples/appcache/dist/webpack-5/0714810ae3fb211173e2964249507195.png differ diff --git a/examples/appcache/dist/webpack-5/bundle.js b/examples/appcache/dist/webpack-5/bundle.js new file mode 100644 index 00000000..9ae17651 --- /dev/null +++ b/examples/appcache/dist/webpack-5/bundle.js @@ -0,0 +1,47 @@ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 636: +/***/ (() => { + +// extracted by mini-css-extract-plugin + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ if(__webpack_module_cache__[moduleId]) { +/******/ return __webpack_module_cache__[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /************************************************************************/ +(() => { +__webpack_require__(636); +var h1 = document.createElement('h1'); +h1.innerHTML = 'Hello world!'; +document.body.appendChild(h1); + +})(); + +/******/ })() +; \ No newline at end of file diff --git a/examples/appcache/dist/webpack-5/index.html b/examples/appcache/dist/webpack-5/index.html new file mode 100644 index 00000000..3a2aa9e5 --- /dev/null +++ b/examples/appcache/dist/webpack-5/index.html @@ -0,0 +1 @@ +Example template \ No newline at end of file diff --git a/examples/appcache/dist/webpack-5/manifest.appcache b/examples/appcache/dist/webpack-5/manifest.appcache new file mode 100644 index 00000000..d9dc95c2 --- /dev/null +++ b/examples/appcache/dist/webpack-5/manifest.appcache @@ -0,0 +1,9 @@ +CACHE MANIFEST +# f509954c60c2fd048c91 + +0714810ae3fb211173e2964249507195.png +styles.css +bundle.js + +NETWORK: +* diff --git a/examples/appcache/dist/webpack-5/styles.css b/examples/appcache/dist/webpack-5/styles.css new file mode 100644 index 00000000..e86486ba --- /dev/null +++ b/examples/appcache/dist/webpack-5/styles.css @@ -0,0 +1,3 @@ +body { + background: snow; +} diff --git a/examples/appcache/webpack.config.js b/examples/appcache/webpack.config.js index 980ebfd5..449a6cf9 100755 --- a/examples/appcache/webpack.config.js +++ b/examples/appcache/webpack.config.js @@ -16,7 +16,7 @@ module.exports = { rules: [ { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] }, { test: /\.png$/, loader: 'file-loader' }, - { test: /\.html$/, loader: 'html-loader?-removeOptionalTags' } + { test: /\.html$/, loader: 'html-loader', options: { removeOptionalTags: false } } ] }, plugins: [ diff --git a/examples/build-examples.js b/examples/build-examples.js index b1deff37..0e45b0bd 100644 --- a/examples/build-examples.js +++ b/examples/build-examples.js @@ -20,7 +20,7 @@ examples.forEach(function (exampleName) { var configFile = path.join(examplePath, 'webpack.config.js'); var config = require(configFile); - if (webpackMajorVersion === '4') { + if (Number(webpackMajorVersion) >= 4) { config.plugins.unshift(new webpack.LoaderOptionsPlugin({ options: { context: process.cwd() // or the same value as `context` diff --git a/examples/chunk-optimization/dist/webpack-5/73.js b/examples/chunk-optimization/dist/webpack-5/73.js new file mode 100644 index 00000000..5e01f279 --- /dev/null +++ b/examples/chunk-optimization/dist/webpack-5/73.js @@ -0,0 +1,40 @@ +(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[73],{ + +/***/ 173: +/***/ ((module, exports, __webpack_require__) => { + +exports = module.exports = __webpack_require__(609)(false); +// Module +exports.push([module.id, "body {\n background: snow;\n}", ""]); + + + +/***/ }), + +/***/ 73: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + +var content = __webpack_require__(173); + +if(typeof content === 'string') content = [[module.id, content, '']]; + +var transform; +var insertInto; + + + +var options = {"hmr":true} + +options.transform = transform +options.insertInto = undefined; + +var update = __webpack_require__(379)(content, options); + +if(content.locals) module.exports = content.locals; + +if(false) {} + +/***/ }) + +}]); \ No newline at end of file diff --git a/examples/chunk-optimization/dist/webpack-5/805.js b/examples/chunk-optimization/dist/webpack-5/805.js new file mode 100644 index 00000000..66796e1b --- /dev/null +++ b/examples/chunk-optimization/dist/webpack-5/805.js @@ -0,0 +1,596 @@ +(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[805],{ + +/***/ 609: +/***/ ((module) => { + +"use strict"; + + +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +// css base code, injected by the css-loader +module.exports = function (useSourceMap) { + var list = []; // return the list of modules as css string + + list.toString = function toString() { + return this.map(function (item) { + var content = cssWithMappingToString(item, useSourceMap); + + if (item[2]) { + return '@media ' + item[2] + '{' + content + '}'; + } else { + return content; + } + }).join(''); + }; // import a list of modules into the list + + + list.i = function (modules, mediaQuery) { + if (typeof modules === 'string') { + modules = [[null, modules, '']]; + } + + var alreadyImportedModules = {}; + + for (var i = 0; i < this.length; i++) { + var id = this[i][0]; + + if (id != null) { + alreadyImportedModules[id] = true; + } + } + + for (i = 0; i < modules.length; i++) { + var item = modules[i]; // skip already imported module + // this implementation is not 100% perfect for weird media query combinations + // when a module is imported multiple times with different media queries. + // I hope this will never occur (Hey this way we have smaller bundles) + + if (item[0] == null || !alreadyImportedModules[item[0]]) { + if (mediaQuery && !item[2]) { + item[2] = mediaQuery; + } else if (mediaQuery) { + item[2] = '(' + item[2] + ') and (' + mediaQuery + ')'; + } + + list.push(item); + } + } + }; + + return list; +}; + +function cssWithMappingToString(item, useSourceMap) { + var content = item[1] || ''; + var cssMapping = item[3]; + + if (!cssMapping) { + return content; + } + + if (useSourceMap && typeof btoa === 'function') { + var sourceMapping = toComment(cssMapping); + var sourceURLs = cssMapping.sources.map(function (source) { + return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */'; + }); + return [content].concat(sourceURLs).concat([sourceMapping]).join('\n'); + } + + return [content].join('\n'); +} // Adapted from convert-source-map (MIT) + + +function toComment(sourceMap) { + // eslint-disable-next-line no-undef + var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))); + var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64; + return '/*# ' + data + ' */'; +} + +/***/ }), + +/***/ 379: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +var stylesInDom = {}; + +var memoize = function (fn) { + var memo; + + return function () { + if (typeof memo === "undefined") memo = fn.apply(this, arguments); + return memo; + }; +}; + +var isOldIE = memoize(function () { + // Test for IE <= 9 as proposed by Browserhacks + // @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805 + // Tests for existence of standard globals is to allow style-loader + // to operate correctly into non-standard environments + // @see https://github.com/webpack-contrib/style-loader/issues/177 + return window && document && document.all && !window.atob; +}); + +var getTarget = function (target, parent) { + if (parent){ + return parent.querySelector(target); + } + return document.querySelector(target); +}; + +var getElement = (function (fn) { + var memo = {}; + + return function(target, parent) { + // If passing function in options, then use it for resolve "head" element. + // Useful for Shadow Root style i.e + // { + // insertInto: function () { return document.querySelector("#foo").shadowRoot } + // } + if (typeof target === 'function') { + return target(); + } + if (typeof memo[target] === "undefined") { + var styleTarget = getTarget.call(this, target, parent); + // Special case to return head of iframe instead of iframe itself + if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) { + try { + // This will throw an exception if access to iframe is blocked + // due to cross-origin restrictions + styleTarget = styleTarget.contentDocument.head; + } catch(e) { + styleTarget = null; + } + } + memo[target] = styleTarget; + } + return memo[target] + }; +})(); + +var singleton = null; +var singletonCounter = 0; +var stylesInsertedAtTop = []; + +var fixUrls = __webpack_require__(657); + +module.exports = function(list, options) { + if (typeof DEBUG !== "undefined" && DEBUG) { + if (typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment"); + } + + options = options || {}; + + options.attrs = typeof options.attrs === "object" ? options.attrs : {}; + + // Force single-tag solution on IE6-9, which has a hard limit on the # of \ No newline at end of file diff --git a/examples/inline/dist/webpack-5/styles.css b/examples/inline/dist/webpack-5/styles.css new file mode 100644 index 00000000..e86486ba --- /dev/null +++ b/examples/inline/dist/webpack-5/styles.css @@ -0,0 +1,3 @@ +body { + background: snow; +} diff --git a/examples/javascript-advanced/dist/webpack-5/0714810ae3fb211173e2964249507195.png b/examples/javascript-advanced/dist/webpack-5/0714810ae3fb211173e2964249507195.png new file mode 100644 index 00000000..d71b3d78 Binary files /dev/null and b/examples/javascript-advanced/dist/webpack-5/0714810ae3fb211173e2964249507195.png differ diff --git a/examples/javascript-advanced/dist/webpack-5/bundle.js b/examples/javascript-advanced/dist/webpack-5/bundle.js new file mode 100644 index 00000000..3e1c0ed3 --- /dev/null +++ b/examples/javascript-advanced/dist/webpack-5/bundle.js @@ -0,0 +1,59 @@ +/* + * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). + * This devtool is not neither made for production nor for readable output files. + * It uses "eval()" calls to create a separate source file in the browser devtools. + * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) + * or disable the default devtool with "devtool: false". + * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). + */ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 184: +/***/ ((module) => { + +"use strict"; +eval("// This file is used for frontend and backend\n\n\n// If compiled by the html-webpack-plugin\n// HTML_WEBPACK_PLUGIN is set to true:\nvar backend = typeof HTML_WEBPACK_PLUGIN !== 'undefined';\n\nmodule.exports = function () {\n return 'Hello World from ' + (backend ? 'backend' : 'frontend');\n};\n\n\n//# sourceURL=webpack:///./universial.js?"); + +/***/ }), + +/***/ 636: +/***/ (() => { + +eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./main.css?"); + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ if(__webpack_module_cache__[moduleId]) { +/******/ return __webpack_module_cache__[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /************************************************************************/ +(() => { +eval("__webpack_require__(636);\n\nvar universal = __webpack_require__(184);\nvar h1 = document.createElement('h1');\nh1.innerHTML = universal();\n\ndocument.body.appendChild(h1);\n\n\n//# sourceURL=webpack:///./example.js?"); +})(); + +/******/ })() +; \ No newline at end of file diff --git a/examples/javascript-advanced/dist/webpack-5/index.html b/examples/javascript-advanced/dist/webpack-5/index.html new file mode 100644 index 00000000..2ec60f39 --- /dev/null +++ b/examples/javascript-advanced/dist/webpack-5/index.html @@ -0,0 +1 @@ +Webpack AppHello World from backend -

Partial

\ No newline at end of file diff --git a/examples/javascript-advanced/dist/webpack-5/styles.css b/examples/javascript-advanced/dist/webpack-5/styles.css new file mode 100644 index 00000000..e86486ba --- /dev/null +++ b/examples/javascript-advanced/dist/webpack-5/styles.css @@ -0,0 +1,3 @@ +body { + background: snow; +} diff --git a/examples/javascript/dist/webpack-5/0714810ae3fb211173e2964249507195.png b/examples/javascript/dist/webpack-5/0714810ae3fb211173e2964249507195.png new file mode 100644 index 00000000..d71b3d78 Binary files /dev/null and b/examples/javascript/dist/webpack-5/0714810ae3fb211173e2964249507195.png differ diff --git a/examples/javascript/dist/webpack-5/bundle.js b/examples/javascript/dist/webpack-5/bundle.js new file mode 100644 index 00000000..3e1c0ed3 --- /dev/null +++ b/examples/javascript/dist/webpack-5/bundle.js @@ -0,0 +1,59 @@ +/* + * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). + * This devtool is not neither made for production nor for readable output files. + * It uses "eval()" calls to create a separate source file in the browser devtools. + * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) + * or disable the default devtool with "devtool: false". + * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). + */ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 184: +/***/ ((module) => { + +"use strict"; +eval("// This file is used for frontend and backend\n\n\n// If compiled by the html-webpack-plugin\n// HTML_WEBPACK_PLUGIN is set to true:\nvar backend = typeof HTML_WEBPACK_PLUGIN !== 'undefined';\n\nmodule.exports = function () {\n return 'Hello World from ' + (backend ? 'backend' : 'frontend');\n};\n\n\n//# sourceURL=webpack:///./universial.js?"); + +/***/ }), + +/***/ 636: +/***/ (() => { + +eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./main.css?"); + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ if(__webpack_module_cache__[moduleId]) { +/******/ return __webpack_module_cache__[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /************************************************************************/ +(() => { +eval("__webpack_require__(636);\n\nvar universal = __webpack_require__(184);\nvar h1 = document.createElement('h1');\nh1.innerHTML = universal();\n\ndocument.body.appendChild(h1);\n\n\n//# sourceURL=webpack:///./example.js?"); +})(); + +/******/ })() +; \ No newline at end of file diff --git a/examples/javascript/dist/webpack-5/index.html b/examples/javascript/dist/webpack-5/index.html new file mode 100644 index 00000000..62334917 --- /dev/null +++ b/examples/javascript/dist/webpack-5/index.html @@ -0,0 +1 @@ +Hello World from backend2020-03-30T16:36:46.399Z

Partial

\ No newline at end of file diff --git a/examples/javascript/dist/webpack-5/styles.css b/examples/javascript/dist/webpack-5/styles.css new file mode 100644 index 00000000..e86486ba --- /dev/null +++ b/examples/javascript/dist/webpack-5/styles.css @@ -0,0 +1,3 @@ +body { + background: snow; +} diff --git a/examples/pug-loader/dist/webpack-5/bundle.js b/examples/pug-loader/dist/webpack-5/bundle.js new file mode 100644 index 00000000..11ae57a6 --- /dev/null +++ b/examples/pug-loader/dist/webpack-5/bundle.js @@ -0,0 +1,333 @@ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 636: +/***/ (() => { + +// extracted by mini-css-extract-plugin + +/***/ }), + +/***/ 656: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var pug = __webpack_require__(79); + +function template(locals) {var pug_html = "", pug_mixins = {}, pug_interp;;var locals_for_with = (locals || {});(function (time) {pug_html = pug_html + "\u003C!-- this partial is used for frontend and backend--\u003E\u003Cdiv class=\"time\"\u003E \u003Cb\u003ECurrent time\u003C\u002Fb\u003E\u003Cp\u003E" + (pug.escape(null == (pug_interp = time.toISOString()) ? "" : pug_interp)) + "\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cimg src=\"#{require('.\u002Flogo.png')}\"\u003E";}.call(this,"time" in locals_for_with?locals_for_with.time:typeof time!=="undefined"?time:undefined));;return pug_html;}; +module.exports = template; + +/***/ }), + +/***/ 79: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; + + +var pug_has_own_property = Object.prototype.hasOwnProperty; + +/** + * Merge two attribute objects giving precedence + * to values in object `b`. Classes are special-cased + * allowing for arrays and merging/joining appropriately + * resulting in a string. + * + * @param {Object} a + * @param {Object} b + * @return {Object} a + * @api private + */ + +exports.merge = pug_merge; +function pug_merge(a, b) { + if (arguments.length === 1) { + var attrs = a[0]; + for (var i = 1; i < a.length; i++) { + attrs = pug_merge(attrs, a[i]); + } + return attrs; + } + + for (var key in b) { + if (key === 'class') { + var valA = a[key] || []; + a[key] = (Array.isArray(valA) ? valA : [valA]).concat(b[key] || []); + } else if (key === 'style') { + var valA = pug_style(a[key]); + valA = valA && valA[valA.length - 1] !== ';' ? valA + ';' : valA; + var valB = pug_style(b[key]); + valB = valB && valB[valB.length - 1] !== ';' ? valB + ';' : valB; + a[key] = valA + valB; + } else { + a[key] = b[key]; + } + } + + return a; +}; + +/** + * Process array, object, or string as a string of classes delimited by a space. + * + * If `val` is an array, all members of it and its subarrays are counted as + * classes. If `escaping` is an array, then whether or not the item in `val` is + * escaped depends on the corresponding item in `escaping`. If `escaping` is + * not an array, no escaping is done. + * + * If `val` is an object, all the keys whose value is truthy are counted as + * classes. No escaping is done. + * + * If `val` is a string, it is counted as a class. No escaping is done. + * + * @param {(Array.|Object.|string)} val + * @param {?Array.} escaping + * @return {String} + */ +exports.classes = pug_classes; +function pug_classes_array(val, escaping) { + var classString = '', className, padding = '', escapeEnabled = Array.isArray(escaping); + for (var i = 0; i < val.length; i++) { + className = pug_classes(val[i]); + if (!className) continue; + escapeEnabled && escaping[i] && (className = pug_escape(className)); + classString = classString + padding + className; + padding = ' '; + } + return classString; +} +function pug_classes_object(val) { + var classString = '', padding = ''; + for (var key in val) { + if (key && val[key] && pug_has_own_property.call(val, key)) { + classString = classString + padding + key; + padding = ' '; + } + } + return classString; +} +function pug_classes(val, escaping) { + if (Array.isArray(val)) { + return pug_classes_array(val, escaping); + } else if (val && typeof val === 'object') { + return pug_classes_object(val); + } else { + return val || ''; + } +} + +/** + * Convert object or string to a string of CSS styles delimited by a semicolon. + * + * @param {(Object.|string)} val + * @return {String} + */ + +exports.style = pug_style; +function pug_style(val) { + if (!val) return ''; + if (typeof val === 'object') { + var out = ''; + for (var style in val) { + /* istanbul ignore else */ + if (pug_has_own_property.call(val, style)) { + out = out + style + ':' + val[style] + ';'; + } + } + return out; + } else { + return val + ''; + } +}; + +/** + * Render the given attribute. + * + * @param {String} key + * @param {String} val + * @param {Boolean} escaped + * @param {Boolean} terse + * @return {String} + */ +exports.attr = pug_attr; +function pug_attr(key, val, escaped, terse) { + if (val === false || val == null || !val && (key === 'class' || key === 'style')) { + return ''; + } + if (val === true) { + return ' ' + (terse ? key : key + '="' + key + '"'); + } + var type = typeof val; + if ((type === 'object' || type === 'function') && typeof val.toJSON === 'function') { + val = val.toJSON(); + } + if (typeof val !== 'string') { + val = JSON.stringify(val); + if (!escaped && val.indexOf('"') !== -1) { + return ' ' + key + '=\'' + val.replace(/'/g, ''') + '\''; + } + } + if (escaped) val = pug_escape(val); + return ' ' + key + '="' + val + '"'; +}; + +/** + * Render the given attributes object. + * + * @param {Object} obj + * @param {Object} terse whether to use HTML5 terse boolean attributes + * @return {String} + */ +exports.attrs = pug_attrs; +function pug_attrs(obj, terse){ + var attrs = ''; + + for (var key in obj) { + if (pug_has_own_property.call(obj, key)) { + var val = obj[key]; + + if ('class' === key) { + val = pug_classes(val); + attrs = pug_attr(key, val, false, terse) + attrs; + continue; + } + if ('style' === key) { + val = pug_style(val); + } + attrs += pug_attr(key, val, false, terse); + } + } + + return attrs; +}; + +/** + * Escape the given string of `html`. + * + * @param {String} html + * @return {String} + * @api private + */ + +var pug_match_html = /["&<>]/; +exports.escape = pug_escape; +function pug_escape(_html){ + var html = '' + _html; + var regexResult = pug_match_html.exec(html); + if (!regexResult) return _html; + + var result = ''; + var i, lastIndex, escape; + for (i = regexResult.index, lastIndex = 0; i < html.length; i++) { + switch (html.charCodeAt(i)) { + case 34: escape = '"'; break; + case 38: escape = '&'; break; + case 60: escape = '<'; break; + case 62: escape = '>'; break; + default: continue; + } + if (lastIndex !== i) result += html.substring(lastIndex, i); + lastIndex = i + 1; + result += escape; + } + if (lastIndex !== i) return result + html.substring(lastIndex, i); + else return result; +}; + +/** + * Re-throw the given `err` in context to the + * the pug in `filename` at the given `lineno`. + * + * @param {Error} err + * @param {String} filename + * @param {String} lineno + * @param {String} str original source + * @api private + */ + +exports.rethrow = pug_rethrow; +function pug_rethrow(err, filename, lineno, str){ + if (!(err instanceof Error)) throw err; + if ((typeof window != 'undefined' || !filename) && !str) { + err.message += ' on line ' + lineno; + throw err; + } + try { + str = str || __webpack_require__(993).readFileSync(filename, 'utf8') + } catch (ex) { + pug_rethrow(err, null, lineno) + } + var context = 3 + , lines = str.split('\n') + , start = Math.max(lineno - context, 0) + , end = Math.min(lines.length, lineno + context); + + // Error context + var context = lines.slice(start, end).map(function(line, i){ + var curr = i + start + 1; + return (curr == lineno ? ' > ' : ' ') + + curr + + '| ' + + line; + }).join('\n'); + + // Alter exception message + err.path = filename; + err.message = (filename || 'Pug') + ':' + lineno + + '\n' + context + '\n\n' + err.message; + throw err; +}; + + +/***/ }), + +/***/ 993: +/***/ (() => { + +/* (ignored) */ + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ if(__webpack_module_cache__[moduleId]) { +/******/ return __webpack_module_cache__[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /************************************************************************/ +(() => { +"use strict"; + +__webpack_require__(636); +// Use the same template for the frontend code +var template = __webpack_require__(656); + +setInterval(function () { + var div = document.getElementById('main'); + div.innerHTML = template({ time: new Date() }); + div.style.color = 'navy'; +}, 1000); + +})(); + +/******/ })() +; \ No newline at end of file diff --git a/examples/pug-loader/dist/webpack-5/favicon.ico b/examples/pug-loader/dist/webpack-5/favicon.ico new file mode 100644 index 00000000..be74abd6 Binary files /dev/null and b/examples/pug-loader/dist/webpack-5/favicon.ico differ diff --git a/examples/pug-loader/dist/webpack-5/index.html b/examples/pug-loader/dist/webpack-5/index.html new file mode 100644 index 00000000..0620dc7e --- /dev/null +++ b/examples/pug-loader/dist/webpack-5/index.html @@ -0,0 +1 @@ +pug demo
Current time

1998-12-31T23:00:00.000Z

\ No newline at end of file diff --git a/examples/pug-loader/dist/webpack-5/styles.css b/examples/pug-loader/dist/webpack-5/styles.css new file mode 100644 index 00000000..e86486ba --- /dev/null +++ b/examples/pug-loader/dist/webpack-5/styles.css @@ -0,0 +1,3 @@ +body { + background: snow; +} diff --git a/examples/sort-manually/dist/webpack-5/0714810ae3fb211173e2964249507195.png b/examples/sort-manually/dist/webpack-5/0714810ae3fb211173e2964249507195.png new file mode 100644 index 00000000..d71b3d78 Binary files /dev/null and b/examples/sort-manually/dist/webpack-5/0714810ae3fb211173e2964249507195.png differ diff --git a/examples/sort-manually/dist/webpack-5/a.js b/examples/sort-manually/dist/webpack-5/a.js new file mode 100644 index 00000000..dbfd0573 --- /dev/null +++ b/examples/sort-manually/dist/webpack-5/a.js @@ -0,0 +1,47 @@ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 636: +/***/ (() => { + +// extracted by mini-css-extract-plugin + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ if(__webpack_module_cache__[moduleId]) { +/******/ return __webpack_module_cache__[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /************************************************************************/ +(() => { +__webpack_require__(636); +var h1 = document.createElement('h1'); +h1.innerHTML = 'a!'; +document.body.appendChild(h1); + +})(); + +/******/ })() +; \ No newline at end of file diff --git a/examples/sort-manually/dist/webpack-5/b.js b/examples/sort-manually/dist/webpack-5/b.js new file mode 100644 index 00000000..7bf528af --- /dev/null +++ b/examples/sort-manually/dist/webpack-5/b.js @@ -0,0 +1,8 @@ +/******/ (() => { // webpackBootstrap +/******/ /************************************************************************/ +var h1 = document.createElement('h1'); +h1.innerHTML = 'b!'; +document.body.appendChild(h1); + +/******/ })() +; \ No newline at end of file diff --git a/examples/sort-manually/dist/webpack-5/c.js b/examples/sort-manually/dist/webpack-5/c.js new file mode 100644 index 00000000..82852a6f --- /dev/null +++ b/examples/sort-manually/dist/webpack-5/c.js @@ -0,0 +1,8 @@ +/******/ (() => { // webpackBootstrap +/******/ /************************************************************************/ +var h1 = document.createElement('h1'); +h1.innerHTML = 'c!'; +document.body.appendChild(h1); + +/******/ })() +; \ No newline at end of file diff --git a/examples/sort-manually/dist/webpack-5/d.js b/examples/sort-manually/dist/webpack-5/d.js new file mode 100644 index 00000000..cfd11c66 --- /dev/null +++ b/examples/sort-manually/dist/webpack-5/d.js @@ -0,0 +1,8 @@ +/******/ (() => { // webpackBootstrap +/******/ /************************************************************************/ +var h1 = document.createElement('h1'); +h1.innerHTML = 'd!'; +document.body.appendChild(h1); + +/******/ })() +; \ No newline at end of file diff --git a/examples/sort-manually/dist/webpack-5/e.js b/examples/sort-manually/dist/webpack-5/e.js new file mode 100644 index 00000000..01886f02 --- /dev/null +++ b/examples/sort-manually/dist/webpack-5/e.js @@ -0,0 +1,8 @@ +/******/ (() => { // webpackBootstrap +/******/ /************************************************************************/ +var h1 = document.createElement('h1'); +h1.innerHTML = 'e!'; +document.body.appendChild(h1); + +/******/ })() +; \ No newline at end of file diff --git a/examples/sort-manually/dist/webpack-5/first-file.html b/examples/sort-manually/dist/webpack-5/first-file.html new file mode 100644 index 00000000..f251a0f8 --- /dev/null +++ b/examples/sort-manually/dist/webpack-5/first-file.html @@ -0,0 +1 @@ +Example template \ No newline at end of file diff --git a/examples/sort-manually/dist/webpack-5/second-file.html b/examples/sort-manually/dist/webpack-5/second-file.html new file mode 100644 index 00000000..ba8f8673 --- /dev/null +++ b/examples/sort-manually/dist/webpack-5/second-file.html @@ -0,0 +1 @@ +Example template \ No newline at end of file diff --git a/examples/sort-manually/dist/webpack-5/styles.css b/examples/sort-manually/dist/webpack-5/styles.css new file mode 100644 index 00000000..e86486ba --- /dev/null +++ b/examples/sort-manually/dist/webpack-5/styles.css @@ -0,0 +1,3 @@ +body { + background: snow; +} diff --git a/examples/template-parameters/dist/webpack-5/bundle.js b/examples/template-parameters/dist/webpack-5/bundle.js new file mode 100644 index 00000000..a81d487b --- /dev/null +++ b/examples/template-parameters/dist/webpack-5/bundle.js @@ -0,0 +1,7 @@ +/******/ (() => { // webpackBootstrap +var h1 = document.createElement('h1'); +h1.innerHTML = 'Hello world!'; +document.body.appendChild(h1); + +/******/ })() +; \ No newline at end of file diff --git a/examples/template-parameters/dist/webpack-5/index.html b/examples/template-parameters/dist/webpack-5/index.html new file mode 100644 index 00000000..2ac7ecd2 --- /dev/null +++ b/examples/template-parameters/dist/webpack-5/index.html @@ -0,0 +1 @@ +bar \ No newline at end of file diff --git a/index.js b/index.js index 7758eeb6..ed6961b5 100644 --- a/index.js +++ b/index.js @@ -16,10 +16,10 @@ const fs = require('fs'); const _ = require('lodash'); const path = require('path'); const loaderUtils = require('loader-utils'); +const { CachedChildCompilation } = require('./lib/cached-child-compiler'); const { createHtmlTagObject, htmlTagObjectToString } = require('./lib/html-tags'); -const childCompiler = require('./lib/compiler.js'); const prettyError = require('./lib/errors.js'); const chunkSorter = require('./lib/chunksorter.js'); const getHtmlWebpackPluginHooks = require('./lib/hooks.js').getHtmlWebpackPluginHooks; @@ -74,10 +74,6 @@ class HtmlWebpackPlugin { // Instance variables to keep caching information // for multiple builds this.childCompilerHash = undefined; - /** - * @type {string | undefined} - */ - this.childCompilationOutputName = undefined; this.assetJson = undefined; this.hash = undefined; this.version = HtmlWebpackPlugin.version; @@ -89,12 +85,15 @@ class HtmlWebpackPlugin { */ apply (compiler) { const self = this; - let isCompilationCached = false; - /** @type Promise */ - let compilationPromise; this.options.template = this.getFullTemplatePath(this.options.template, compiler.context); + // 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 const filename = this.options.filename; @@ -128,75 +127,42 @@ class HtmlWebpackPlugin { }; } - // Clear the cache once a new HtmlWebpackPlugin is added - childCompiler.clearCache(compiler); - - // Register all HtmlWebpackPlugins instances at the child compiler - compiler.hooks.thisCompilation.tap('HtmlWebpackPlugin', (compilation) => { - // Clear the cache if the child compiler is outdated - if (childCompiler.hasOutDatedTemplateCache(compilation)) { - childCompiler.clearCache(compiler); - } - // Add this instances template to the child compiler - childCompiler.addTemplateToCompiler(compiler, this.options.template); - // Add file dependencies of child compiler to parent compiler - // to keep them watched even if we get the result from the cache - compilation.hooks.additionalChunkAssets.tap('HtmlWebpackPlugin', () => { - const childCompilerDependencies = childCompiler.getFileDependencies(compiler); - childCompilerDependencies.forEach(fileDependency => { - compilation.compilationDependencies.add(fileDependency); - }); - }); - }); - - compiler.hooks.make.tapAsync('HtmlWebpackPlugin', (compilation, callback) => { - // Compile the template (queued) - compilationPromise = childCompiler.compileTemplate(self.options.template, self.options.filename, compilation) - .catch(err => { - compilation.errors.push(prettyError(err, compiler.context).toString()); - return { - content: self.options.showErrors ? prettyError(err, compiler.context).toJsonHtml() : 'ERROR', - outputName: self.options.filename, - hash: '' - }; - }) - .then(compilationResult => { - // If the compilation change didnt change the cache is valid - isCompilationCached = Boolean(compilationResult.hash) && self.childCompilerHash === compilationResult.hash; - self.childCompilerHash = compilationResult.hash; - self.childCompilationOutputName = compilationResult.outputName; - callback(); - return compilationResult.content; - }); - }); - compiler.hooks.emit.tapAsync('HtmlWebpackPlugin', /** * Hook into the webpack emit phase * @param {WebpackCompilation} compilation - * @param {() => void} callback + * @param {(err?: Error) => void} callback */ (compilation, callback) => { // Get all entry point names for this html file const entryNames = Array.from(compilation.entrypoints.keys()); const filteredEntryNames = self.filterChunks(entryNames, self.options.chunks, self.options.excludeChunks); const sortedEntryNames = self.sortEntryChunks(filteredEntryNames, this.options.chunksSortMode, compilation); - const childCompilationOutputName = self.childCompilationOutputName; - if (childCompilationOutputName === undefined) { - throw new Error('Did not receive child compilation result'); + const templateResult = this.options.templateContent + ? { mainCompilationHash: compilation.hash } + : childCompilerPlugin.getCompilationEntryResult(this.options.template); + + this.childCompilerHash = templateResult.mainCompilationHash; + + if ('error' in templateResult) { + compilation.errors.push(prettyError(templateResult.error, compiler.context).toString()); } + const childCompilationOutputName = compilation.mainTemplate.getAssetPath(this.options.filename, 'compiledEntry' in templateResult ? { + hash: templateResult.compiledEntry.hash, + chunk: templateResult.compiledEntry.entry + } : { + hash: templateResult.mainCompilationHash + }); + + // If the child compilation was not executed during a previous main compile run + // it is a cached result + const isCompilationCached = templateResult.mainCompilationHash !== compilation.hash; + // Turn the entry point names into file paths const assets = self.htmlWebpackPluginAssets(compilation, childCompilationOutputName, sortedEntryNames); - // If this is a hot update compilation, move on! - // This solves a problem where an `index.html` file is generated for hot-update js files - // It only happens in Webpack 2, where hot updates are emitted separately before the full bundle - if (self.isHotUpdateCompilation(assets)) { - return callback(); - } - // If the template and the assets did not change we don't have to emit the html const assetJson = JSON.stringify(self.getAssetFiles(assets)); if (isCompilationCached && self.options.cache && assetJson === self.assetJson) { @@ -249,15 +215,20 @@ class HtmlWebpackPlugin { }); // Turn the compiled tempalte into a nodejs function or into a nodejs string - const templateEvaluationPromise = compilationPromise - .then(compiledTemplate => { + const templateEvaluationPromise = Promise.resolve() + .then(() => { + if ('error' in templateResult) { + return self.options.showErrors ? prettyError(templateResult.error, compiler.context).toHtml() : 'ERROR'; + } // Allow to use a custom function / string instead if (self.options.templateContent !== false) { return self.options.templateContent; } // Once everything is compiled evaluate the html factory // and replace it with its content - return self.evaluateCompilationResult(compilation, compiledTemplate); + return ('compiledEntry' in templateResult) + ? self.evaluateCompilationResult(compilation, templateResult.compiledEntry.content) + : Promise.reject(new Error('Child compilation contained no compiledEntry')); }); const templateExectutionPromise = Promise.all([assetsPromise, assetTagGroupsPromise, templateEvaluationPromise]) diff --git a/lib/cached-child-compiler.js b/lib/cached-child-compiler.js new file mode 100644 index 00000000..e7444005 --- /dev/null +++ b/lib/cached-child-compiler.js @@ -0,0 +1,379 @@ +// @ts-check +/** + * @file + * Helper plugin manages the cached state of the child compilation + * + * To optimize performance the child compilation is running asyncronously. + * Therefore it needs to be started in the compiler.make phase and ends after + * the compilation.afterCompile phase. + * + * To prevent bugs from blocked hooks there is no promise or event based api + * for this plugin. + * + * Example usage: + * + * ```js + const childCompilerPlugin = new PersistentChildCompilerPlugin(); + childCompilerPlugin.addEntry('./src/index.js'); + compiler.hooks.afterCompile.tapAsync('MyPlugin', (compilation, callback) => { + console.log(childCompilerPlugin.getCompilationResult()['./src/index.js'])); + return true; + }); + * ``` + */ + +// Import types +/** @typedef {import("webpack/lib/Compiler.js")} WebpackCompiler */ +/** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */ +/** @typedef {{hash: string, entry: any, content: string }} ChildCompilationResultEntry */ +/** @typedef {import("./webpack4/file-watcher-api").Snapshot} Snapshot */ +/** @typedef {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} FileDependencies */ +/** @typedef {{ + dependencies: FileDependencies, + compiledEntries: {[entryName: string]: ChildCompilationResultEntry} +} | { + dependencies: FileDependencies, + error: Error +}} ChildCompilationResult */ +'use strict'; + +const { HtmlWebpackChildCompiler } = require('./child-compiler'); +const fileWatcherApi = require('./file-watcher-api'); + +/** + * This plugin is a singleton for performance reasons. + * To keep track if a plugin does already exist for the compiler they are cached + * in this map + * @type {WeakMap}} + */ +const compilerMap = new WeakMap(); + +class CachedChildCompilation { + /** + * @param {WebpackCompiler} compiler + */ + constructor (compiler) { + /** + * @private + * @type {WebpackCompiler} + */ + this.compiler = compiler; + // Create a singlton instance for the compiler + // if there is none + if (compilerMap.has(compiler)) { + return; + } + const persistentChildCompilerSingletonPlugin = new PersistentChildCompilerSingletonPlugin(); + compilerMap.set(compiler, persistentChildCompilerSingletonPlugin); + persistentChildCompilerSingletonPlugin.apply(compiler); + } + + /** + * apply is called by the webpack main compiler during the start phase + * @param {string} entry + */ + addEntry (entry) { + const persistentChildCompilerSingletonPlugin = compilerMap.get(this.compiler); + if (!persistentChildCompilerSingletonPlugin) { + throw new Error( + 'PersistentChildCompilerSingletonPlugin instance not found.' + ); + } + persistentChildCompilerSingletonPlugin.addEntry(entry); + } + + getCompilationResult () { + const persistentChildCompilerSingletonPlugin = compilerMap.get(this.compiler); + if (!persistentChildCompilerSingletonPlugin) { + throw new Error( + 'PersistentChildCompilerSingletonPlugin instance not found.' + ); + } + return persistentChildCompilerSingletonPlugin.getLatestResult(); + } + + /** + * Returns the result for the given entry + * @param {string} entry + * @returns { + | { mainCompilationHash: string, error: Error } + | { mainCompilationHash: string, compiledEntry: ChildCompilationResultEntry } + } + */ + getCompilationEntryResult (entry) { + const latestResult = this.getCompilationResult(); + const compilationResult = latestResult.compilationResult; + return 'error' in compilationResult ? { + mainCompilationHash: latestResult.mainCompilationHash, + error: compilationResult.error + } : { + mainCompilationHash: latestResult.mainCompilationHash, + compiledEntry: compilationResult.compiledEntries[entry] + }; + } +} + +class PersistentChildCompilerSingletonPlugin { + constructor () { + /** + * @private + * @type { + | { + isCompiling: false, + isVerifyingCache: false, + entries: string[], + compiledEntries: string[], + mainCompilationHash: string, + compilationResult: ChildCompilationResult + } + | Readonly<{ + isCompiling: false, + isVerifyingCache: true, + entries: string[], + previousEntries: string[], + previousResult: ChildCompilationResult + }> + | Readonly <{ + isVerifyingCache: false, + isCompiling: true, + entries: string[], + }> + } the internal compilation state */ + this.compilationState = { + isCompiling: false, + isVerifyingCache: false, + entries: [], + compiledEntries: [], + mainCompilationHash: 'initial', + compilationResult: { + dependencies: { + fileDependencies: [], + contextDependencies: [], + missingDependencies: [] + }, + compiledEntries: {} + } + }; + } + + /** + * apply is called by the webpack main compiler during the start phase + * @param {WebpackCompiler} compiler + */ + apply (compiler) { + /** @type Promise */ + let childCompilationResultPromise = Promise.resolve({ + dependencies: { + fileDependencies: [], + contextDependencies: [], + missingDependencies: [] + }, + compiledEntries: {} + }); + /** + * The main compilation hash which will only be updated + * if the childCompiler changes + */ + let mainCompilationHashOfLastChildRecompile = ''; + /** @typedef{Snapshot|undefined} */ + let previousFileSystemSnapshot; + let compilationStartTime = new Date().getTime(); + + compiler.hooks.make.tapAsync( + 'PersistentChildCompilerSingletonPlugin', + (mainCompilation, callback) => { + if (this.compilationState.isCompiling || this.compilationState.isVerifyingCache) { + return callback(new Error('Child compilation has already started')); + } + + // Update the time to the current compile start time + compilationStartTime = new Date().getTime(); + + // The compilation starts - adding new templates is now not possible anymore + this.compilationState = { + isCompiling: false, + isVerifyingCache: true, + previousEntries: this.compilationState.compiledEntries, + previousResult: this.compilationState.compilationResult, + entries: this.compilationState.entries + }; + + // Validate cache: + const isCacheValidPromise = this.isCacheValid(previousFileSystemSnapshot, mainCompilation); + + let cachedResult = childCompilationResultPromise; + childCompilationResultPromise = isCacheValidPromise.then((isCacheValid) => { + // Reuse cache + if (isCacheValid) { + return cachedResult; + } + // Start the compilation + const compiledEntriesPromise = this.compileEntries( + mainCompilation, + this.compilationState.entries + ); + // Update snapshot as soon as we know the filedependencies + // this might possibly cause bugs if files were changed inbetween + // compilation start and snapshot creation + compiledEntriesPromise.then((childCompilationResult) => { + return fileWatcherApi.createSnapshot(childCompilationResult.dependencies, mainCompilation, compilationStartTime); + }).then((snapshot) => { + previousFileSystemSnapshot = snapshot; + }); + return compiledEntriesPromise; + }); + + // Add files to compilation which needs to be watched: + mainCompilation.hooks.optimizeTree.tapAsync( + 'PersistentChildCompilerSingletonPlugin', + (chunks, modules, callback) => { + const handleCompilationDonePromise = childCompilationResultPromise.then( + childCompilationResult => { + this.watchFiles( + mainCompilation, + childCompilationResult.dependencies + ); + }); + handleCompilationDonePromise.then(() => callback(null, chunks, modules), callback); + } + ); + + // Store the final compilation once the main compilation hash is known + mainCompilation.hooks.additionalAssets.tapAsync( + 'PersistentChildCompilerSingletonPlugin', + (callback) => { + const didRecompilePromise = Promise.all([childCompilationResultPromise, cachedResult]).then( + ([childCompilationResult, cachedResult]) => { + // Update if childCompilation changed + return (cachedResult !== childCompilationResult); + } + ); + + const handleCompilationDonePromise = Promise.all([childCompilationResultPromise, didRecompilePromise]).then( + ([childCompilationResult, didRecompile]) => { + // Update hash and snapshot if childCompilation changed + if (didRecompile) { + mainCompilationHashOfLastChildRecompile = mainCompilation.hash; + } + this.compilationState = { + isCompiling: false, + isVerifyingCache: false, + entries: this.compilationState.entries, + compiledEntries: this.compilationState.entries, + compilationResult: childCompilationResult, + mainCompilationHash: mainCompilationHashOfLastChildRecompile + }; + }); + handleCompilationDonePromise.then(() => callback(null), callback); + } + ); + + // Continue compilation: + callback(null); + } + ); + } + + /** + * Add a new entry to the next compile run + * @param {string} entry + */ + addEntry (entry) { + if (this.compilationState.isCompiling || this.compilationState.isVerifyingCache) { + throw new Error( + 'The child compiler has already started to compile. ' + + "Please add entries before the main compiler 'make' phase has started or " + + 'after the compilation is done.' + ); + } + if (this.compilationState.entries.indexOf(entry) === -1) { + this.compilationState.entries = [...this.compilationState.entries, entry]; + } + } + + getLatestResult () { + if (this.compilationState.isCompiling || this.compilationState.isVerifyingCache) { + throw new Error( + 'The child compiler is not done compiling. ' + + "Please access the result after the compiler 'make' phase has started or " + + 'after the compilation is done.' + ); + } + return { + mainCompilationHash: this.compilationState.mainCompilationHash, + compilationResult: this.compilationState.compilationResult + }; + } + + /** + * Verify that the cache is still valid + * @private + * @param {Snapshot | undefined} snapshot + * @param {WebpackCompilation} mainCompilation + * @returns {Promise} + */ + isCacheValid (snapshot, mainCompilation) { + if (!this.compilationState.isVerifyingCache) { + return Promise.reject(new Error('Cache validation can only be done right before the compilation starts')); + } + // If there are no entries we don't need a new child compilation + if (this.compilationState.entries.length === 0) { + return Promise.resolve(true); + } + // If there are new entries the cache is invalid + if (this.compilationState.entries !== this.compilationState.previousEntries) { + return Promise.resolve(false); + } + // Mark the cache as invalid if there is no snapshot + if (!snapshot) { + return Promise.resolve(false); + } + return fileWatcherApi.isSnapShotValid(snapshot, mainCompilation); + } + + /** + * Start to compile all templates + * + * @private + * @param {WebpackCompilation} mainCompilation + * @param {string[]} entries + * @returns {Promise} + */ + compileEntries (mainCompilation, entries) { + const compiler = new HtmlWebpackChildCompiler(entries); + return compiler.compileTemplates(mainCompilation).then((result) => { + return { + // The compiled sources to render the content + compiledEntries: result, + // The file dependencies to find out if a + // recompilation is required + dependencies: compiler.fileDependencies, + // The main compilation hash can be used to find out + // if this compilation was done during the current compilation + mainCompilationHash: mainCompilation.hash + }; + }, error => ({ + // The compiled sources to render the content + error, + // The file dependencies to find out if a + // recompilation is required + dependencies: compiler.fileDependencies, + // The main compilation hash can be used to find out + // if this compilation was done during the current compilation + mainCompilationHash: mainCompilation.hash + })); + } + + /** + * @private + * @param {WebpackCompilation} mainCompilation + * @param {FileDependencies} files + */ + watchFiles (mainCompilation, files) { + fileWatcherApi.watchFiles(mainCompilation, files); + } +} + +module.exports = { + CachedChildCompilation +}; diff --git a/lib/child-compiler.js b/lib/child-compiler.js new file mode 100644 index 00000000..8dd4ab44 --- /dev/null +++ b/lib/child-compiler.js @@ -0,0 +1,183 @@ +// @ts-check +/** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */ +/** @typedef {import("webpack/lib/Compiler.js")} WebpackCompiler */ +/** @typedef {import("webpack/lib/Chunk.js")} WebpackChunk */ +'use strict'; +/** + * @file + * This file uses webpack to compile a template with a child compiler. + * + * [TEMPLATE] -> [JAVASCRIPT] + * + */ +'use strict'; +const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin'); +const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin'); +const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin'); +const LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin'); +const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin'); + +/** + * The HtmlWebpackChildCompiler is a helper to allow resusing one childCompiler + * for multile HtmlWebpackPlugin instances to improve the compilation performance. + */ +class HtmlWebpackChildCompiler { + /** + * + * @param {string[]} templates + */ + constructor (templates) { + /** + * @type {string[]} templateIds + * The template array will allow us to keep track which input generated which output + */ + this.templates = templates; + /** + * @type {Promise<{[templatePath: string]: { content: string, hash: string, entry: WebpackChunk }}>} + */ + this.compilationPromise; // eslint-disable-line + /** + * @type {number} + */ + this.compilationStartedTimestamp; // eslint-disable-line + /** + * @type {number} + */ + this.compilationEndedTimestamp; // eslint-disable-line + /** + * All file dependencies of the child compiler + * @type {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} + */ + this.fileDependencies = { fileDependencies: [], contextDependencies: [], missingDependencies: [] }; + } + + /** + * Returns true if the childCompiler is currently compiling + * @retuns {boolean} + */ + isCompiling () { + return !this.didCompile() && this.compilationStartedTimestamp !== undefined; + } + + /** + * Returns true if the childCOmpiler is done compiling + */ + didCompile () { + return this.compilationEndedTimestamp !== undefined; + } + + /** + * This function will start the template compilation + * once it is started no more templates can be added + * + * @param {WebpackCompilation} mainCompilation + * @returns {Promise<{[templatePath: string]: { content: string, hash: string, entry: WebpackChunk }}>} + */ + compileTemplates (mainCompilation) { + // To prevent multiple compilations for the same template + // the compilation is cached in a promise. + // If it already exists return + if (this.compilationPromise) { + return this.compilationPromise; + } + + // The entry file is just an empty helper as the dynamic template + // require is added in "loader.js" + const outputOptions = { + filename: '__child-[name]', + publicPath: mainCompilation.outputOptions.publicPath + }; + const compilerName = 'HtmlWebpackCompiler'; + // Create an additional child compiler which takes the template + // and turns it into an Node.JS html factory. + // This allows us to use loaders during the compilation + const childCompiler = mainCompilation.createChildCompiler(compilerName, outputOptions); + // The file path context which webpack uses to resolve all relative files to + childCompiler.context = mainCompilation.compiler.context; + // Compile the template to nodejs javascript + new NodeTemplatePlugin(outputOptions).apply(childCompiler); + new NodeTargetPlugin().apply(childCompiler); + new LibraryTemplatePlugin('HTML_WEBPACK_PLUGIN_RESULT', 'var').apply(childCompiler); + new LoaderTargetPlugin('node').apply(childCompiler); + + // Add all templates + this.templates.forEach((template, index) => { + new SingleEntryPlugin(childCompiler.context, template, `HtmlWebpackPlugin_${index}`).apply(childCompiler); + }); + + this.compilationStartedTimestamp = new Date().getTime(); + this.compilationPromise = new Promise((resolve, reject) => { + childCompiler.runAsChild((err, entries, childCompilation) => { + // Extract templates + const compiledTemplates = entries + ? extractHelperFilesFromCompilation(mainCompilation, childCompilation, outputOptions.filename, entries) + : []; + // Extract file dependencies + if (entries) { + this.fileDependencies = { fileDependencies: Array.from(childCompilation.fileDependencies), contextDependencies: Array.from(childCompilation.contextDependencies), missingDependencies: Array.from(childCompilation.missingDependencies) }; + } + // Reject the promise if the childCompilation contains error + if (childCompilation && childCompilation.errors && childCompilation.errors.length) { + const errorDetails = childCompilation.errors.map(error => error.message + (error.error ? ':\n' + error.error : '')).join('\n'); + reject(new Error('Child compilation failed:\n' + errorDetails)); + return; + } + // Reject if the error object contains errors + if (err) { + reject(err); + return; + } + /** + * @type {{[templatePath: string]: { content: string, hash: string, entry: WebpackChunk }}} + */ + const result = {}; + compiledTemplates.forEach((templateSource, entryIndex) => { + // The compiledTemplates are generated from the entries added in + // the addTemplate function. + // Therefore the array index of this.templates should be the as entryIndex. + result[this.templates[entryIndex]] = { + content: templateSource, + hash: childCompilation.hash, + entry: entries[entryIndex] + }; + }); + this.compilationEndedTimestamp = new Date().getTime(); + resolve(result); + }); + }); + + return this.compilationPromise; + } +} + +/** + * The webpack child compilation will create files as a side effect. + * This function will extract them and clean them up so they won't be written to disk. + * + * Returns the source code of the compiled templates as string + * + * @returns Array + */ +function extractHelperFilesFromCompilation (mainCompilation, childCompilation, filename, childEntryChunks) { + const helperAssetNames = childEntryChunks.map((entryChunk, index) => { + return mainCompilation.mainTemplate.getAssetPath(filename, { + hash: childCompilation.hash, + chunk: entryChunk, + name: `HtmlWebpackPlugin_${index}` + }); + }); + + helperAssetNames.forEach((helperFileName) => { + delete mainCompilation.assets[helperFileName]; + }); + + const helperContents = helperAssetNames.map((helperFileName) => { + return childCompilation.assets[helperFileName].source(); + }); + + return helperContents; +} + +module.exports = { + HtmlWebpackChildCompiler +}; diff --git a/lib/compiler.js b/lib/compiler.js deleted file mode 100644 index a13abf3d..00000000 --- a/lib/compiler.js +++ /dev/null @@ -1,356 +0,0 @@ -// @ts-check -/** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */ -/** @typedef {import("webpack/lib/Compiler.js")} WebpackCompiler */ -/** @typedef {import("webpack/lib/Chunk.js")} WebpackChunk */ -'use strict'; -/** - * @file - * This file uses webpack to compile a template with a child compiler. - * - * [TEMPLATE] -> [JAVASCRIPT] - * - */ -'use strict'; -const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin'); -const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin'); -const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin'); -const LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin'); -const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin'); - -/** - * The HtmlWebpackChildCompiler is a helper to allow resusing one childCompiler - * for multile HtmlWebpackPlugin instances to improve the compilation performance. - */ -class HtmlWebpackChildCompiler { - constructor () { - /** - * @type {string[]} templateIds - * The template array will allow us to keep track which input generated which output - */ - this.templates = []; - /** - * @type {Promise<{[templatePath: string]: { content: string, hash: string, entry: WebpackChunk }}>} - */ - this.compilationPromise; // eslint-disable-line - /** - * @type {number} - */ - this.compilationStartedTimestamp; // eslint-disable-line - /** - * @type {number} - */ - this.compilationEndedTimestamp; // eslint-disable-line - /** - * All file dependencies of the child compiler - * @type {string[]} - */ - this.fileDependencies = []; - } - - /** - * Add a templatePath to the child compiler - * The given template will be compiled by `compileTemplates` - * @param {string} template - The webpack path to the template e.g. `'!!html-loader!index.html'` - * @returns {boolean} true if the template is new - */ - addTemplate (template) { - const templateId = this.templates.indexOf(template); - // Don't add the template to the compiler if a similar template was already added - if (templateId !== -1) { - return false; - } - // A child compiler can compile only once - // throw an error if a new template is added after the compilation started - if (this.isCompiling()) { - throw new Error('New templates can only be added before `compileTemplates` was called.'); - } - // Add the template to the childCompiler - this.templates.push(template); - // Mark the cache invalid - return true; - } - - /** - * Returns true if the childCompiler is currently compiling - * @retuns {boolean} - */ - isCompiling () { - return !this.didCompile() && this.compilationStartedTimestamp !== undefined; - } - - /** - * Returns true if the childCOmpiler is done compiling - */ - didCompile () { - return this.compilationEndedTimestamp !== undefined; - } - - /** - * This function will start the template compilation - * once it is started no more templates can be added - * - * @param {WebpackCompilation} mainCompilation - * @returns {Promise<{[templatePath: string]: { content: string, hash: string, entry: WebpackChunk }}>} - */ - compileTemplates (mainCompilation) { - // To prevent multiple compilations for the same template - // the compilation is cached in a promise. - // If it already exists return - if (this.compilationPromise) { - return this.compilationPromise; - } - - // The entry file is just an empty helper as the dynamic template - // require is added in "loader.js" - const outputOptions = { - filename: '__child-[name]', - publicPath: mainCompilation.outputOptions.publicPath - }; - const compilerName = 'HtmlWebpackCompiler'; - // Create an additional child compiler which takes the template - // and turns it into an Node.JS html factory. - // This allows us to use loaders during the compilation - const childCompiler = mainCompilation.createChildCompiler(compilerName, outputOptions); - // The file path context which webpack uses to resolve all relative files to - childCompiler.context = mainCompilation.compiler.context; - // Compile the template to nodejs javascript - new NodeTemplatePlugin(outputOptions).apply(childCompiler); - new NodeTargetPlugin().apply(childCompiler); - new LibraryTemplatePlugin('HTML_WEBPACK_PLUGIN_RESULT', 'var').apply(childCompiler); - new LoaderTargetPlugin('node').apply(childCompiler); - - // Add all templates - this.templates.forEach((template, index) => { - new SingleEntryPlugin(childCompiler.context, template, `HtmlWebpackPlugin_${index}`).apply(childCompiler); - }); - - this.compilationStartedTimestamp = new Date().getTime(); - this.compilationPromise = new Promise((resolve, reject) => { - childCompiler.runAsChild((err, entries, childCompilation) => { - // Extract templates - const compiledTemplates = entries - ? extractHelperFilesFromCompilation(mainCompilation, childCompilation, outputOptions.filename, entries) - : []; - // Extract file dependencies - if (entries) { - this.fileDependencies = Array.from(childCompilation.fileDependencies); - } - // Reject the promise if the childCompilation contains error - if (childCompilation && childCompilation.errors && childCompilation.errors.length) { - const errorDetails = childCompilation.errors.map(error => error.message + (error.error ? ':\n' + error.error : '')).join('\n'); - reject(new Error('Child compilation failed:\n' + errorDetails)); - return; - } - // Reject if the error object contains errors - if (err) { - reject(err); - return; - } - /** - * @type {{[templatePath: string]: { content: string, hash: string, entry: WebpackChunk }}} - */ - const result = {}; - compiledTemplates.forEach((templateSource, entryIndex) => { - // The compiledTemplates are generated from the entries added in - // the addTemplate function. - // Therefore the array index of this.templates should be the as entryIndex. - result[this.templates[entryIndex]] = { - content: templateSource, - hash: childCompilation.hash, - entry: entries[entryIndex] - }; - }); - this.compilationEndedTimestamp = new Date().getTime(); - resolve(result); - }); - }); - - return this.compilationPromise; - } -} - -/** - * The webpack child compilation will create files as a side effect. - * This function will extract them and clean them up so they won't be written to disk. - * - * Returns the source code of the compiled templates as string - * - * @returns Array - */ -function extractHelperFilesFromCompilation (mainCompilation, childCompilation, filename, childEntryChunks) { - const helperAssetNames = childEntryChunks.map((entryChunk, index) => { - return mainCompilation.mainTemplate.getAssetPath(filename, { - hash: childCompilation.hash, - chunk: entryChunk, - name: `HtmlWebpackPlugin_${index}` - }); - }); - - helperAssetNames.forEach((helperFileName) => { - delete mainCompilation.assets[helperFileName]; - }); - - const helperContents = helperAssetNames.map((helperFileName) => { - return childCompilation.assets[helperFileName].source(); - }); - - return helperContents; -} - -/** - * @type {WeakMap}} - */ -const childCompilerCache = new WeakMap(); - -/** - * Get child compiler from cache or a new child compiler for the given mainCompilation - * - * @param {WebpackCompiler} mainCompiler - */ -function getChildCompiler (mainCompiler) { - const cachedChildCompiler = childCompilerCache.get(mainCompiler); - if (cachedChildCompiler) { - return cachedChildCompiler; - } - const newCompiler = new HtmlWebpackChildCompiler(); - childCompilerCache.set(mainCompiler, newCompiler); - return newCompiler; -} - -/** - * Remove the childCompiler from the cache - * - * @param {WebpackCompiler} mainCompiler - */ -function clearCache (mainCompiler) { - const childCompiler = getChildCompiler(mainCompiler); - // If this childCompiler was already used - // remove the entire childCompiler from the cache - if (childCompiler.isCompiling() || childCompiler.didCompile()) { - childCompilerCache.delete(mainCompiler); - } -} - -/** - * Register a template for the current main compiler - * @param {WebpackCompiler} mainCompiler - * @param {string} templatePath - */ -function addTemplateToCompiler (mainCompiler, templatePath) { - const childCompiler = getChildCompiler(mainCompiler); - const isNew = childCompiler.addTemplate(templatePath); - if (isNew) { - clearCache(mainCompiler); - } -} - -/** - * Starts the compilation for all templates. - * This has to be called once all templates where added. - * - * If this function is called multiple times it will use a cache inside - * the childCompiler - * - * @param {string} templatePath - * @param {string} outputFilename - * @param {WebpackCompilation} mainCompilation - */ -function compileTemplate (templatePath, outputFilename, mainCompilation) { - const childCompiler = getChildCompiler(mainCompilation.compiler); - return childCompiler.compileTemplates(mainCompilation).then((compiledTemplates) => { - if (!compiledTemplates[templatePath]) console.log(Object.keys(compiledTemplates), templatePath); - const compiledTemplate = compiledTemplates[templatePath]; - // Replace [hash] placeholders in filename - const outputName = mainCompilation.mainTemplate.getAssetPath(outputFilename, { - hash: compiledTemplate.hash, - chunk: compiledTemplate.entry - }); - return { - // Hash of the template entry point - hash: compiledTemplate.hash, - // Output name - outputName: outputName, - // Compiled code - content: compiledTemplate.content - }; - }); -} - -/** - * Return all file dependencies of the last child compilation - * - * @param {WebpackCompiler} compiler - * @returns {Array} - */ -function getFileDependencies (compiler) { - const childCompiler = getChildCompiler(compiler); - return childCompiler.fileDependencies; -} - -/** - * @type {WeakMap>}} - */ -const hasOutdatedCompilationDependenciesMap = new WeakMap(); -/** - * Returns `true` if the file dependencies of the current childCompiler - * for the given mainCompilation are outdated. - * - * Uses the `hasOutdatedCompilationDependenciesMap` cache if possible. - * - * @param {WebpackCompilation} mainCompilation - * @returns {boolean} - */ -function hasOutDatedTemplateCache (mainCompilation) { - const childCompiler = getChildCompiler(mainCompilation.compiler); - /** - * @type {WeakMap|undefined} - */ - let hasOutdatedChildCompilerDependenciesMap = hasOutdatedCompilationDependenciesMap.get(mainCompilation); - // Create map for childCompiler if none exist - if (!hasOutdatedChildCompilerDependenciesMap) { - hasOutdatedChildCompilerDependenciesMap = new WeakMap(); - hasOutdatedCompilationDependenciesMap.set(mainCompilation, hasOutdatedChildCompilerDependenciesMap); - } - // Try to get the `checkChildCompilerCache` result from cache - let isOutdated = hasOutdatedChildCompilerDependenciesMap.get(childCompiler); - if (isOutdated !== undefined) { - return isOutdated; - } - // If `checkChildCompilerCache` has never been called for the given - // `mainCompilation` and `childCompiler` combination call it: - isOutdated = isChildCompilerCacheOutdated(mainCompilation, childCompiler); - hasOutdatedChildCompilerDependenciesMap.set(childCompiler, isOutdated); - return isOutdated; -} - -/** - * Returns `true` if the file dependencies of the given childCompiler are outdated. - * - * @param {WebpackCompilation} mainCompilation - * @param {HtmlWebpackChildCompiler} childCompiler - * @returns {boolean} - */ -function isChildCompilerCacheOutdated (mainCompilation, childCompiler) { - // If the compilation was never run there is no invalid cache - if (!childCompiler.compilationStartedTimestamp) { - return false; - } - // Check if any dependent file was changed after the last compilation - const fileTimestamps = mainCompilation.fileTimestamps; - const isCacheOutOfDate = childCompiler.fileDependencies.some((fileDependency) => { - const timestamp = fileTimestamps.get(fileDependency); - // If the timestamp is not known the file is new - // If the timestamp is larger then the file has changed - // Otherwise the file is still the same - return !timestamp || timestamp > childCompiler.compilationStartedTimestamp; - }); - return isCacheOutOfDate; -} - -module.exports = { - addTemplateToCompiler, - compileTemplate, - hasOutDatedTemplateCache, - clearCache, - getFileDependencies -}; diff --git a/lib/file-watcher-api.js b/lib/file-watcher-api.js new file mode 100644 index 00000000..83c78fe8 --- /dev/null +++ b/lib/file-watcher-api.js @@ -0,0 +1,14 @@ +// @ts-check +/** + * To use the availble webpack core api + * we have to use different child compilers + * depending on the used webpack version + */ +const webpackMajorVersion = Number(require('webpack/package.json').version.split('.')[0]); + +// Typescript hack to test only the webpack 4 code +/** @type {import('./webpack4/file-watcher-api')} */ +module.exports = webpackMajorVersion === 4 + ? require('./webpack4/file-watcher-api.js') + // Hack to ignore './webpack5/file-watcher-api.js' from typescript: + : require('./webpack' + 5 + '/file-watcher-api.js'); diff --git a/lib/webpack4/file-watcher-api.js b/lib/webpack4/file-watcher-api.js new file mode 100644 index 00000000..e36a798c --- /dev/null +++ b/lib/webpack4/file-watcher-api.js @@ -0,0 +1,64 @@ +/** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */ +/** @typedef {{timestamp: number, fileDependencies: string[]}} Snapshot */ +'use strict'; + +/** + * + * @param {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} fileDependencies + * @param {WebpackCompilation} compilation + * @param {number} startTime + */ +function createSnapshot (fileDependencies, compilation, startTime) { + const flatDependencies = []; + Object.keys(fileDependencies).forEach((depencyTypes) => { + fileDependencies[depencyTypes].forEach(fileDependency => { + flatDependencies.push(fileDependency); + }); + }); + return { + fileDependencies: flatDependencies, + timestamp: startTime + }; +} + +/** + * Returns true if the files inside this snapshot + * have not been changed + * + * @param {Snapshot} snapshot + * @param {WebpackCompilation} compilation + * @returns {Promise} + */ +function isSnapShotValid (snapshot, compilation) { + // Check if any dependent file was changed after the last compilation + const fileTimestamps = compilation.fileTimestamps; + const isCacheOutOfDate = snapshot.fileDependencies.some((fileDependency) => { + const timestamp = fileTimestamps.get(fileDependency); + // If the timestamp is not known the file is new + // If the timestamp is larger then the file has changed + // Otherwise the file is still the same + return !timestamp || timestamp > snapshot.timestamp; + }); + return Promise.resolve(!isCacheOutOfDate); +} + +/** + * Ensure that the files keep watched for changes + * and will trigger a recompile + * + * @param {WebpackCompilation} mainCompilation + * @param {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} fileDependencies + */ +function watchFiles (mainCompilation, fileDependencies) { + Object.keys(fileDependencies).forEach((depencyTypes) => { + fileDependencies[depencyTypes].forEach(fileDependency => { + mainCompilation.compilationDependencies.add(fileDependency); + }); + }); +} + +module.exports = { + createSnapshot, + isSnapShotValid, + watchFiles +}; diff --git a/lib/webpack5/file-watcher-api.js b/lib/webpack5/file-watcher-api.js new file mode 100644 index 00000000..1a7c0b9e --- /dev/null +++ b/lib/webpack5/file-watcher-api.js @@ -0,0 +1,71 @@ +/** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */ +/** @typedef {import("webpack/lib/FileSystemInfo").Snapshot} Snapshot */ +'use strict'; + +/** + * + * @param {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} fileDependencies + * @param {WebpackCompilation} mainCompilation + * @param {number} startTime + */ +function createSnapshot (fileDependencies, mainCompilation, startTime) { + return new Promise((resolve, reject) => { + mainCompilation.fileSystemInfo.createSnapshot( + startTime, + fileDependencies.fileDependencies, + fileDependencies.contextDependencies, + fileDependencies.missingDependencies, + null, + (err, snapshot) => { + if (err) { + return reject(err); + } + resolve(snapshot); + } + ); + }); +} + +/** + * Returns true if the files inside this snapshot + * have not been changed + * + * @param {Snapshot} snapshot + * @param {WebpackCompilation} compilation + * @returns {Promise} + */ +function isSnapShotValid (snapshot, mainCompilation) { + return new Promise((resolve, reject) => { + mainCompilation.fileSystemInfo.checkSnapshotValid( + snapshot, + (err, isValid) => { + if (err) { + reject(err); + } + resolve(isValid); + } + ); + }); +} + +/** + * Ensure that the files keep watched for changes + * and will trigger a recompile + * + * @param {WebpackCompilation} mainCompilation + * @param {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} fileDependencies + */ +function watchFiles (mainCompilation, fileDependencies) { + Object.keys(fileDependencies).forEach((depencyTypes) => { + fileDependencies[depencyTypes].forEach(fileDependency => { + mainCompilation.fileDependencies.add(fileDependency); + mainCompilation[depencyTypes].add(fileDependency); + }); + }); +} + +module.exports = { + createSnapshot, + isSnapShotValid, + watchFiles +}; diff --git a/package.json b/package.json index c0784656..12e12482 100644 --- a/package.json +++ b/package.json @@ -42,12 +42,12 @@ "pug": "2.0.3", "pug-loader": "2.4.0", "rimraf": "2.6.3", - "semistandard": "13.0.1", + "semistandard": "^13.0.1", "standard-version": "5.0.2", "style-loader": "0.23.1", - "typescript": "3.5.2", + "typescript": "3.8.3", "webpack": "4.35.2", - "webpack-recompilation-simulator": "3.0.0" + "webpack-recompilation-simulator": "3.2.0" }, "dependencies": { "@types/html-minifier-terser": "^5.0.0", diff --git a/spec/basic.spec.js b/spec/basic.spec.js index 769fa0ae..63c4b185 100644 --- a/spec/basic.spec.js +++ b/spec/basic.spec.js @@ -405,7 +405,7 @@ describe('HtmlWebpackPlugin', () => { it('works with source maps', done => { testHtmlPlugin({ mode: 'development', - devtool: 'sourcemap', + devtool: 'source-map', entry: path.join(__dirname, 'fixtures/index.js'), output: { path: OUTPUT_DIR, @@ -1044,7 +1044,7 @@ describe('HtmlWebpackPlugin', () => { let eventFired = false; const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync('HtmlWebpackPluginTest', (object, callback) => { expect(Object.keys(object.assetTags)).toEqual(['scripts', 'styles', 'meta']); eventFired = true; @@ -1078,7 +1078,7 @@ describe('HtmlWebpackPlugin', () => { it('allows events to add a no-value attribute', done => { const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync('HtmlWebpackPluginTest', (pluginArgs, callback) => { pluginArgs.assetTags.scripts = pluginArgs.assetTags.scripts.map(tag => { if (tag.tagName === 'script') { @@ -1112,7 +1112,7 @@ describe('HtmlWebpackPlugin', () => { it('allows events to remove an attribute by setting it to false', done => { const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync('HtmlWebpackPluginTest', (pluginArgs, callback) => { pluginArgs.assetTags.scripts = pluginArgs.assetTags.scripts.map(tag => { if (tag.tagName === 'script') { @@ -1147,7 +1147,7 @@ describe('HtmlWebpackPlugin', () => { let eventFired = false; const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).afterTemplateExecution.tapAsync('HtmlWebpackPluginTest', (object, callback) => { eventFired = true; callback(); @@ -1181,7 +1181,7 @@ describe('HtmlWebpackPlugin', () => { let eventFired = false; const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('HtmlWebpackPluginTest', (object, callback) => { eventFired = true; callback(); @@ -1214,7 +1214,7 @@ describe('HtmlWebpackPlugin', () => { let eventFired = false; const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).afterEmit.tapAsync('HtmlWebpackPluginTest', (object, callback) => { eventFired = true; callback(); @@ -1245,7 +1245,7 @@ describe('HtmlWebpackPlugin', () => { let eventFired = false; const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('HtmlWebpackPluginTest', (object, callback) => { eventFired = true; object.html += 'Injected by plugin'; @@ -1280,7 +1280,7 @@ describe('HtmlWebpackPlugin', () => { let hookNames; const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { hookNames = Object.keys(HtmlWebpackPlugin.getHooks(compilation)).sort(); }); } @@ -1318,7 +1318,7 @@ describe('HtmlWebpackPlugin', () => { let eventFiredForSecondPlugin = false; const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('HtmlWebpackPluginTest', (object, callback) => { eventFiredForFirstPlugin = true; object.html += 'Injected by first plugin'; @@ -1329,7 +1329,7 @@ describe('HtmlWebpackPlugin', () => { }; const secondExamplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('HtmlWebpackPluginTest', (object, callback) => { eventFiredForSecondPlugin = true; object.html += ' Injected by second plugin'; @@ -1367,7 +1367,7 @@ describe('HtmlWebpackPlugin', () => { let eventFiredForSecondPlugin = false; const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('HtmlWebpackPluginTest', (object, callback) => { eventFiredForFirstPlugin = true; const result = _.extend(object, { @@ -1380,7 +1380,7 @@ describe('HtmlWebpackPlugin', () => { }; const secondExamplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('HtmlWebpackPluginTest', (object, callback) => { eventFiredForSecondPlugin = true; object.html += ' Injected by second plugin'; @@ -1418,7 +1418,7 @@ describe('HtmlWebpackPlugin', () => { let eventFiredForSecondPlugin = false; const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('HtmlWebpackPluginTest', (object, callback) => { eventFiredForFirstPlugin = true; const result = _.extend(object, { @@ -1431,7 +1431,7 @@ describe('HtmlWebpackPlugin', () => { }; const secondExamplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('HtmlWebpackPluginTest', (object, callback) => { eventFiredForSecondPlugin = true; const result = _.extend(object, { @@ -1468,7 +1468,7 @@ describe('HtmlWebpackPlugin', () => { let eventFired = false; const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).afterTemplateExecution.tapAsync('HtmlWebpackPluginTest', (object, callback) => { eventFired = true; object.bodyTags.push(HtmlWebpackPlugin.createHtmlTagObject('script', { src: 'funky-script.js' })); @@ -1504,7 +1504,7 @@ describe('HtmlWebpackPlugin', () => { let eventFired = false; const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync('HtmlWebpackPluginTest', (object, callback) => { eventFired = true; object.assets.js.push('funky-script.js'); @@ -1548,7 +1548,7 @@ describe('HtmlWebpackPlugin', () => { let hookLength = 0; const examplePlugin = { apply: function (compiler) { - compiler.plugin('compilation', compilation => { + compiler.hooks.compilation.tap('HtmlWebpackPlugin', compilation => { const hooks = HtmlWebpackPlugin.getHooks(compilation); hookLength = hooks.length; // Hook into all hooks @@ -1937,7 +1937,10 @@ describe('HtmlWebpackPlugin', () => { template: path.join(__dirname, 'fixtures/non-existing-template.html') }) ] - }, ['Child compilation failed:\n Entry module not found:'], null, done, true); + }, [Number(webpackMajorVersion) >= 5 + ? 'Child compilation failed:\n Module not found:' + : 'Child compilation failed:\n Entry module not found:' + ], null, done, true); }); it('should sort the chunks in auto mode', done => { diff --git a/spec/caching.spec.js b/spec/caching.spec.js index bbb293cd..f7cbc030 100644 --- a/spec/caching.spec.js +++ b/spec/caching.spec.js @@ -47,7 +47,7 @@ function setUpCompiler (htmlWebpackPlugin) { function getCompiledModules (statsJson) { const builtModules = statsJson.modules.filter(webpackModule => webpackModule.built).map((webpackModule) => { - return module.userRequest; + return webpackModule.name; }); statsJson.children.forEach((childCompilationStats) => { const builtChildModules = getCompiledModules(childCompilationStats); diff --git a/spec/hot.spec.js b/spec/hot.spec.js index 6a2120de..a2f5e156 100644 --- a/spec/hot.spec.js +++ b/spec/hot.spec.js @@ -81,8 +81,8 @@ describe('HtmlWebpackPluginHMR', () => { const hotUpdateJsFileNames = Object.keys(stats.compilation.assets).filter((fileName) => /\.hot-update\.js$/.test(fileName)); expect(hotUpdateJsFileNames).not.toEqual([]); expect(hotUpdateJsFileNames.length).toEqual(1); - const hotUpdateFileSource = stats.compilation.assets[hotUpdateJsFileNames[0]].source(); - expect(hotUpdateFileSource).not.toEqual(''); + const hotUpdateFileSize = stats.compilation.assets[hotUpdateJsFileNames[0]].size(); + expect(hotUpdateFileSize).not.toEqual(0); }) .then(() => compiler.stopWatching()); }); diff --git a/tsconfig.json b/tsconfig.json index 112ef7cb..dfcd473f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,7 +24,9 @@ /* Module Resolution Options */ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "resolveJsonModule": true, + "skipLibCheck": true }, "types": ["node"], "exclude": [ @@ -32,6 +34,7 @@ "spec", "examples", "dist", - "coverage" + "coverage", + "lib/webpack5/*.js" ] }