diff --git a/src/index.js b/src/index.js index 4f584441..0da152bd 100644 --- a/src/index.js +++ b/src/index.js @@ -112,6 +112,7 @@ class MiniCssExtractPlugin { this.options = Object.assign( { filename: '[name].css', + checkCssChunk: true, }, options ); @@ -248,9 +249,60 @@ class MiniCssExtractPlugin { .substring(0, hashDigestLength); }); const { mainTemplate } = compilation; - mainTemplate.hooks.localVars.tap(pluginName, (source, chunk) => { + mainTemplate.hooks.localVars.tap(pluginName, (source, chunk, hash) => { const chunkMap = this.getCssChunkObject(chunk); if (Object.keys(chunkMap).length > 0) { + const chunkMaps = chunk.getChunkMaps(); + const linkHrefPath = mainTemplate.getAssetPath( + JSON.stringify(this.options.chunkFilename), + { + hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`, + hashWithLength: (length) => + `" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`, + chunk: { + id: '" + chunkId + "', + hash: `" + ${JSON.stringify(chunkMaps.hash)}[chunkId] + "`, + hashWithLength(length) { + const shortChunkHashMap = Object.create(null); + for (const chunkId of Object.keys(chunkMaps.hash)) { + if (typeof chunkMaps.hash[chunkId] === 'string') { + shortChunkHashMap[chunkId] = chunkMaps.hash[ + chunkId + ].substring(0, length); + } + } + return `" + ${JSON.stringify( + shortChunkHashMap + )}[chunkId] + "`; + }, + contentHash: { + [MODULE_TYPE]: `" + ${JSON.stringify( + chunkMaps.contentHash[MODULE_TYPE] + )}[chunkId] + "`, + }, + contentHashWithLength: { + [MODULE_TYPE]: (length) => { + const shortContentHashMap = {}; + const contentHash = chunkMaps.contentHash[MODULE_TYPE]; + for (const chunkId of Object.keys(contentHash)) { + if (typeof contentHash[chunkId] === 'string') { + shortContentHashMap[chunkId] = contentHash[ + chunkId + ].substring(0, length); + } + } + return `" + ${JSON.stringify( + shortContentHashMap + )}[chunkId] + "`; + }, + }, + name: `" + (${JSON.stringify( + chunkMaps.name + )}[chunkId]||chunkId) + "`, + }, + contentHashType: MODULE_TYPE, + } + ); return Template.asString([ source, '', @@ -260,120 +312,78 @@ class MiniCssExtractPlugin { chunk.ids.map((id) => `${JSON.stringify(id)}: 0`).join(',\n') ), '}', + '', + 'function cssLinkHref(chunkId) {', + Template.indent([ + `var href = ${linkHrefPath};`, + `var fullhref = ${mainTemplate.requireFn}.p + href;`, + 'return {href: href, fullhref: fullhref};', + ]), + '}', ]); } return source; }); - mainTemplate.hooks.requireEnsure.tap( - pluginName, - (source, chunk, hash) => { - const chunkMap = this.getCssChunkObject(chunk); - if (Object.keys(chunkMap).length > 0) { - const chunkMaps = chunk.getChunkMaps(); - const linkHrefPath = mainTemplate.getAssetPath( - JSON.stringify(this.options.chunkFilename), - { - hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`, - hashWithLength: (length) => - `" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`, - chunk: { - id: '" + chunkId + "', - hash: `" + ${JSON.stringify(chunkMaps.hash)}[chunkId] + "`, - hashWithLength(length) { - const shortChunkHashMap = Object.create(null); - for (const chunkId of Object.keys(chunkMaps.hash)) { - if (typeof chunkMaps.hash[chunkId] === 'string') { - shortChunkHashMap[chunkId] = chunkMaps.hash[ - chunkId - ].substring(0, length); - } - } - return `" + ${JSON.stringify( - shortChunkHashMap - )}[chunkId] + "`; - }, - contentHash: { - [MODULE_TYPE]: `" + ${JSON.stringify( - chunkMaps.contentHash[MODULE_TYPE] - )}[chunkId] + "`, - }, - contentHashWithLength: { - [MODULE_TYPE]: (length) => { - const shortContentHashMap = {}; - const contentHash = chunkMaps.contentHash[MODULE_TYPE]; - for (const chunkId of Object.keys(contentHash)) { - if (typeof contentHash[chunkId] === 'string') { - shortContentHashMap[chunkId] = contentHash[ - chunkId - ].substring(0, length); - } - } - return `" + ${JSON.stringify( - shortContentHashMap - )}[chunkId] + "`; - }, - }, - name: `" + (${JSON.stringify( - chunkMaps.name - )}[chunkId]||chunkId) + "`, - }, - contentHashType: MODULE_TYPE, - } - ); - return Template.asString([ - source, - '', - `// ${pluginName} CSS loading`, - `var cssChunks = ${JSON.stringify(chunkMap)};`, - 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', - 'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {', + mainTemplate.hooks.requireEnsure.tap(pluginName, (source, chunk) => { + const chunkMap = this.getCssChunkObject(chunk); + if (Object.keys(chunkMap).length > 0) { + const checkCssChunk = this.options.checkCssChunk; + return Template.asString([ + source, + '', + `// ${pluginName} CSS loading`, + checkCssChunk ? `var cssChunks = ${JSON.stringify(chunkMap)};` : '', + 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', + `else if(installedCssChunks[chunkId] !== 0${ + checkCssChunk ? ' && cssChunks[chunkId]' : '' + }) {`, + Template.indent([ + 'promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', Template.indent([ - 'promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', + 'var hrefData = cssLinkHref(chunkId);', + 'var href = hrefData.href;', + 'var fullhref = hrefData.fullhref;', + 'var existingLinkTags = document.getElementsByTagName("link");', + 'for(var i = 0; i < existingLinkTags.length; i++) {', + Template.indent([ + 'var tag = existingLinkTags[i];', + 'var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");', + 'if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve();', + ]), + '}', + 'var existingStyleTags = document.getElementsByTagName("style");', + 'for(var i = 0; i < existingStyleTags.length; i++) {', Template.indent([ - `var href = ${linkHrefPath};`, - `var fullhref = ${mainTemplate.requireFn}.p + href;`, - 'var existingLinkTags = document.getElementsByTagName("link");', - 'for(var i = 0; i < existingLinkTags.length; i++) {', - Template.indent([ - 'var tag = existingLinkTags[i];', - 'var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");', - 'if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve();', - ]), - '}', - 'var existingStyleTags = document.getElementsByTagName("style");', - 'for(var i = 0; i < existingStyleTags.length; i++) {', - Template.indent([ - 'var tag = existingStyleTags[i];', - 'var dataHref = tag.getAttribute("data-href");', - 'if(dataHref === href || dataHref === fullhref) return resolve();', - ]), - '}', - 'var linkTag = document.createElement("link");', - 'linkTag.rel = "stylesheet";', - 'linkTag.type = "text/css";', - 'linkTag.onload = resolve;', - 'linkTag.onerror = function(event) {', - Template.indent([ - 'var request = event && event.target && event.target.src || fullhref;', - 'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + request + ")");', - 'err.request = request;', - 'reject(err);', - ]), - '};', - 'linkTag.href = fullhref;', - 'var head = document.getElementsByTagName("head")[0];', - 'head.appendChild(linkTag);', + 'var tag = existingStyleTags[i];', + 'var dataHref = tag.getAttribute("data-href");', + 'if(dataHref === href || dataHref === fullhref) return resolve();', ]), - '}).then(function() {', - Template.indent(['installedCssChunks[chunkId] = 0;']), - '}));', + '}', + 'var linkTag = document.createElement("link");', + 'linkTag.rel = "stylesheet";', + 'linkTag.type = "text/css";', + 'linkTag.onload = resolve;', + 'linkTag.onerror = function(event) {', + Template.indent([ + 'var request = event && event.target && event.target.src || fullhref;', + 'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + request + ")");', + 'err.request = request;', + 'reject(err);', + ]), + '};', + 'linkTag.href = fullhref;', + 'var head = document.getElementsByTagName("head")[0];', + 'head.appendChild(linkTag);', ]), - '}', - ]); - } - return source; + '}).then(function() {', + Template.indent(['installedCssChunks[chunkId] = 0;']), + '}));', + ]), + '}', + ]); } - ); + return source; + }); }); } diff --git a/test/cases/runtime-without-check-csschunks/async.css b/test/cases/runtime-without-check-csschunks/async.css new file mode 100644 index 00000000..a9779cd8 --- /dev/null +++ b/test/cases/runtime-without-check-csschunks/async.css @@ -0,0 +1 @@ +.async { background: blue; } diff --git a/test/cases/runtime-without-check-csschunks/async.js b/test/cases/runtime-without-check-csschunks/async.js new file mode 100644 index 00000000..1a2ee580 --- /dev/null +++ b/test/cases/runtime-without-check-csschunks/async.js @@ -0,0 +1 @@ +import './in-async.css'; diff --git a/test/cases/runtime-without-check-csschunks/expected/1.css b/test/cases/runtime-without-check-csschunks/expected/1.css new file mode 100644 index 00000000..92eec627 --- /dev/null +++ b/test/cases/runtime-without-check-csschunks/expected/1.css @@ -0,0 +1,2 @@ +.in-async { background: green; } + diff --git a/test/cases/runtime-without-check-csschunks/expected/2.css b/test/cases/runtime-without-check-csschunks/expected/2.css new file mode 100644 index 00000000..0a0fb724 --- /dev/null +++ b/test/cases/runtime-without-check-csschunks/expected/2.css @@ -0,0 +1,2 @@ +.async { background: blue; } + diff --git a/test/cases/runtime-without-check-csschunks/expected/main.css b/test/cases/runtime-without-check-csschunks/expected/main.css new file mode 100644 index 00000000..aea53e43 --- /dev/null +++ b/test/cases/runtime-without-check-csschunks/expected/main.css @@ -0,0 +1,2 @@ +body { background: red; } + diff --git a/test/cases/runtime-without-check-csschunks/expected/main.js b/test/cases/runtime-without-check-csschunks/expected/main.js new file mode 100644 index 00000000..49b75af7 --- /dev/null +++ b/test/cases/runtime-without-check-csschunks/expected/main.js @@ -0,0 +1,271 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // install a JSONP callback for chunk loading +/******/ function webpackJsonpCallback(data) { +/******/ var chunkIds = data[0]; +/******/ var moreModules = data[1]; +/******/ +/******/ +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0, resolves = []; +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(installedChunks[chunkId]) { +/******/ resolves.push(installedChunks[chunkId][0]); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ for(moduleId in moreModules) { +/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { +/******/ modules[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(parentJsonpFunction) parentJsonpFunction(data); +/******/ +/******/ while(resolves.length) { +/******/ resolves.shift()(); +/******/ } +/******/ +/******/ }; +/******/ +/******/ +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // object to store loaded CSS chunks +/******/ var installedCssChunks = { +/******/ 0: 0 +/******/ } +/******/ +/******/ function cssLinkHref(chunkId) { +/******/ var href = "" + ({}[chunkId]||chunkId) + ".css"; +/******/ var fullhref = __webpack_require__.p + href; +/******/ return {href: href, fullhref: fullhref}; +/******/ } +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // Promise = chunk loading, 0 = chunk loaded +/******/ var installedChunks = { +/******/ 0: 0 +/******/ }; +/******/ +/******/ +/******/ +/******/ // script path function +/******/ function jsonpScriptSrc(chunkId) { +/******/ return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".js" +/******/ } +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ // This file contains only the entry chunk. +/******/ // The chunk loading function for additional chunks +/******/ __webpack_require__.e = function requireEnsure(chunkId) { +/******/ var promises = []; +/******/ +/******/ +/******/ // mini-css-extract-plugin CSS loading +/******/ +/******/ if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]); +/******/ else if(installedCssChunks[chunkId] !== 0) { +/******/ promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) { +/******/ var hrefData = cssLinkHref(chunkId); +/******/ var href = hrefData.href; +/******/ var fullhref = hrefData.fullhref; +/******/ var existingLinkTags = document.getElementsByTagName("link"); +/******/ for(var i = 0; i < existingLinkTags.length; i++) { +/******/ var tag = existingLinkTags[i]; +/******/ var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href"); +/******/ if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve(); +/******/ } +/******/ var existingStyleTags = document.getElementsByTagName("style"); +/******/ for(var i = 0; i < existingStyleTags.length; i++) { +/******/ var tag = existingStyleTags[i]; +/******/ var dataHref = tag.getAttribute("data-href"); +/******/ if(dataHref === href || dataHref === fullhref) return resolve(); +/******/ } +/******/ var linkTag = document.createElement("link"); +/******/ linkTag.rel = "stylesheet"; +/******/ linkTag.type = "text/css"; +/******/ linkTag.onload = resolve; +/******/ linkTag.onerror = function(event) { +/******/ var request = event && event.target && event.target.src || fullhref; +/******/ var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")"); +/******/ err.request = request; +/******/ reject(err); +/******/ }; +/******/ linkTag.href = fullhref; +/******/ var head = document.getElementsByTagName("head")[0]; +/******/ head.appendChild(linkTag); +/******/ }).then(function() { +/******/ installedCssChunks[chunkId] = 0; +/******/ })); +/******/ } +/******/ +/******/ // JSONP chunk loading for javascript +/******/ +/******/ var installedChunkData = installedChunks[chunkId]; +/******/ if(installedChunkData !== 0) { // 0 means "already installed". +/******/ +/******/ // a Promise means "currently loading". +/******/ if(installedChunkData) { +/******/ promises.push(installedChunkData[2]); +/******/ } else { +/******/ // setup Promise in chunk cache +/******/ var promise = new Promise(function(resolve, reject) { +/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject]; +/******/ }); +/******/ promises.push(installedChunkData[2] = promise); +/******/ +/******/ // start chunk loading +/******/ var head = document.getElementsByTagName('head')[0]; +/******/ var script = document.createElement('script'); +/******/ var onScriptComplete; +/******/ +/******/ script.charset = 'utf-8'; +/******/ script.timeout = 120; +/******/ if (__webpack_require__.nc) { +/******/ script.setAttribute("nonce", __webpack_require__.nc); +/******/ } +/******/ script.src = jsonpScriptSrc(chunkId); +/******/ +/******/ onScriptComplete = function (event) { +/******/ // avoid mem leaks in IE. +/******/ script.onerror = script.onload = null; +/******/ clearTimeout(timeout); +/******/ var chunk = installedChunks[chunkId]; +/******/ if(chunk !== 0) { +/******/ if(chunk) { +/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); +/******/ var realSrc = event && event.target && event.target.src; +/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'); +/******/ error.type = errorType; +/******/ error.request = realSrc; +/******/ chunk[1](error); +/******/ } +/******/ installedChunks[chunkId] = undefined; +/******/ } +/******/ }; +/******/ var timeout = setTimeout(function(){ +/******/ onScriptComplete({ type: 'timeout', target: script }); +/******/ }, 120000); +/******/ script.onerror = script.onload = onScriptComplete; +/******/ head.appendChild(script); +/******/ } +/******/ } +/******/ return Promise.all(promises); +/******/ }; +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // on error function for async loading +/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; +/******/ +/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; +/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); +/******/ jsonpArray.push = webpackJsonpCallback; +/******/ jsonpArray = jsonpArray.slice(); +/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); +/******/ var parentJsonpFunction = oldJsonpFunction; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _main_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); +/* harmony import */ var _main_css__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_main_css__WEBPACK_IMPORTED_MODULE_0__); + + +__webpack_require__.e(/* import() */ 1).then(__webpack_require__.bind(null, 3)); + +__webpack_require__.e(/* import() */ 2).then(__webpack_require__.t.bind(null, 6, 7)); + + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +// extracted by mini-css-extract-plugin + +/***/ }) +/******/ ]); \ No newline at end of file diff --git a/test/cases/runtime-without-check-csschunks/in-async.css b/test/cases/runtime-without-check-csschunks/in-async.css new file mode 100644 index 00000000..85e56aff --- /dev/null +++ b/test/cases/runtime-without-check-csschunks/in-async.css @@ -0,0 +1 @@ +.in-async { background: green; } diff --git a/test/cases/runtime-without-check-csschunks/index.js b/test/cases/runtime-without-check-csschunks/index.js new file mode 100644 index 00000000..010bff00 --- /dev/null +++ b/test/cases/runtime-without-check-csschunks/index.js @@ -0,0 +1,5 @@ +import './main.css'; + +import('./async'); + +import('./async.css'); diff --git a/test/cases/runtime-without-check-csschunks/main.css b/test/cases/runtime-without-check-csschunks/main.css new file mode 100644 index 00000000..31fc5b8a --- /dev/null +++ b/test/cases/runtime-without-check-csschunks/main.css @@ -0,0 +1 @@ +body { background: red; } diff --git a/test/cases/runtime-without-check-csschunks/webpack.config.js b/test/cases/runtime-without-check-csschunks/webpack.config.js new file mode 100644 index 00000000..af706673 --- /dev/null +++ b/test/cases/runtime-without-check-csschunks/webpack.config.js @@ -0,0 +1,22 @@ +const Self = require('../../../'); + +module.exports = { + entry: './index.js', + module: { + rules: [ + { + test: /\.css$/, + use: [ + Self.loader, + 'css-loader', + ], + }, + ], + }, + plugins: [ + new Self({ + filename: '[name].css', + checkCssChunk: false, + }), + ], +};