From e7d8c7e4990ef3d48471991dbb3ae06f377286f9 Mon Sep 17 00:00:00 2001 From: jgillespie Date: Tue, 22 Jan 2019 11:34:07 -0700 Subject: [PATCH 01/16] rel=preload first pass --- src/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 3e2a9001..78f1ac26 100644 --- a/src/index.js +++ b/src/index.js @@ -340,7 +340,7 @@ class MiniCssExtractPlugin { 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();', + 'if((tag.rel === "stylesheet" || tag.rel === "preload") && (dataHref === href || dataHref === fullhref)) return resolve();', ]), '}', 'var existingStyleTags = document.getElementsByTagName("style");', @@ -352,8 +352,11 @@ class MiniCssExtractPlugin { ]), '}', 'var linkTag = document.createElement("link");', - 'linkTag.rel = "stylesheet";', - 'linkTag.type = "text/css";', + //JG: EDIT + //'linkTag.type = "text/css";', + //'linkTag.rel = "stylesheet";', + 'linkTag.rel = "preload";', + 'linkTag.as = "style";', 'linkTag.onload = resolve;', 'linkTag.onerror = function(event) {', Template.indent([ From 9f56caa4c80358bef2c5d5bbb7a9801579a0f560 Mon Sep 17 00:00:00 2001 From: jgillespie Date: Tue, 22 Jan 2019 11:38:53 -0700 Subject: [PATCH 02/16] Append preloaded links to bottom --- src/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 78f1ac26..7d77443d 100644 --- a/src/index.js +++ b/src/index.js @@ -384,7 +384,14 @@ class MiniCssExtractPlugin { 'head.appendChild(linkTag);', ]), '}).then(function() {', - Template.indent(['installedCssChunks[chunkId] = 0;']), + Template.indent([ + 'var execLinkTag = document.createElement("link");', + 'execLinkTag.rel = "stylesheet";', + `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, + 'var body = document.getElementsByTagName("body")[0];', + 'body.appendChild(execLinkTag);', + 'installedCssChunks[chunkId] = 0;' + ]), '}));', ]), '}', From 8e3f0f094c1deac8734753aa2c884f110e6f945c Mon Sep 17 00:00:00 2001 From: jgillespie Date: Tue, 22 Jan 2019 11:55:19 -0700 Subject: [PATCH 03/16] Small changes --- src/index.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/index.js b/src/index.js index 7d77443d..f877a022 100644 --- a/src/index.js +++ b/src/index.js @@ -352,9 +352,6 @@ class MiniCssExtractPlugin { ]), '}', 'var linkTag = document.createElement("link");', - //JG: EDIT - //'linkTag.type = "text/css";', - //'linkTag.rel = "stylesheet";', 'linkTag.rel = "preload";', 'linkTag.as = "style";', 'linkTag.onload = resolve;', @@ -386,10 +383,10 @@ class MiniCssExtractPlugin { '}).then(function() {', Template.indent([ 'var execLinkTag = document.createElement("link");', - 'execLinkTag.rel = "stylesheet";', `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, - 'var body = document.getElementsByTagName("body")[0];', - 'body.appendChild(execLinkTag);', + 'execLinkTag.rel = "stylesheet";', + 'execLinkTag.type = "text/css";', + 'document.body.appendChild(execLinkTag);', 'installedCssChunks[chunkId] = 0;' ]), '}));', From 4087a1ae4a965e217abac6198efef7b62dba957f Mon Sep 17 00:00:00 2001 From: jgillespie Date: Tue, 22 Jan 2019 12:26:55 -0700 Subject: [PATCH 04/16] Build dist --- .gitignore | 1 - dist/cjs.js | 3 + dist/index.js | 392 ++++++++++++++++++++++++++++++++++++++++++++++ dist/loader.js | 152 ++++++++++++++++++ package-lock.json | 28 +++- 5 files changed, 568 insertions(+), 8 deletions(-) create mode 100644 dist/cjs.js create mode 100644 dist/index.js create mode 100644 dist/loader.js diff --git a/.gitignore b/.gitignore index b70b127e..4ff034f1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ logs npm-debug.log* .eslintcache /coverage -/dist /test/js /local /reports diff --git a/dist/cjs.js b/dist/cjs.js new file mode 100644 index 00000000..61cc3574 --- /dev/null +++ b/dist/cjs.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./index').default; \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 00000000..57342e95 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,392 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _webpack = require('webpack'); + +var _webpack2 = _interopRequireDefault(_webpack); + +var _webpackSources = require('webpack-sources'); + +var _webpackSources2 = _interopRequireDefault(_webpackSources); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const { ConcatSource, SourceMapSource, OriginalSource } = _webpackSources2.default; +const { + Template, + util: { createHash } +} = _webpack2.default; + +const MODULE_TYPE = 'css/mini-extract'; + +const pluginName = 'mini-css-extract-plugin'; + +const REGEXP_CHUNKHASH = /\[chunkhash(?::(\d+))?\]/i; +const REGEXP_CONTENTHASH = /\[contenthash(?::(\d+))?\]/i; +const REGEXP_NAME = /\[name\]/i; + +class CssDependency extends _webpack2.default.Dependency { + constructor({ identifier, content, media, sourceMap }, context, identifierIndex) { + super(); + this.identifier = identifier; + this.identifierIndex = identifierIndex; + this.content = content; + this.media = media; + this.sourceMap = sourceMap; + this.context = context; + } + + getResourceIdentifier() { + return `css-module-${this.identifier}-${this.identifierIndex}`; + } +} + +class CssDependencyTemplate { + apply() {} +} + +class CssModule extends _webpack2.default.Module { + constructor(dependency) { + super(MODULE_TYPE, dependency.context); + this.id = ''; + this._identifier = dependency.identifier; + this._identifierIndex = dependency.identifierIndex; + this.content = dependency.content; + this.media = dependency.media; + this.sourceMap = dependency.sourceMap; + } + + // no source() so webpack doesn't do add stuff to the bundle + + size() { + return this.content.length; + } + + identifier() { + return `css ${this._identifier} ${this._identifierIndex}`; + } + + readableIdentifier(requestShortener) { + return `css ${requestShortener.shorten(this._identifier)}${this._identifierIndex ? ` (${this._identifierIndex})` : ''}`; + } + + nameForCondition() { + const resource = this._identifier.split('!').pop(); + const idx = resource.indexOf('?'); + if (idx >= 0) return resource.substring(0, idx); + return resource; + } + + updateCacheModule(module) { + this.content = module.content; + this.media = module.media; + this.sourceMap = module.sourceMap; + } + + needRebuild() { + return true; + } + + build(options, compilation, resolver, fileSystem, callback) { + this.buildInfo = {}; + this.buildMeta = {}; + callback(); + } + + updateHash(hash) { + super.updateHash(hash); + hash.update(this.content); + hash.update(this.media || ''); + hash.update(this.sourceMap ? JSON.stringify(this.sourceMap) : ''); + } +} + +class CssModuleFactory { + create({ + dependencies: [dependency] + }, callback) { + callback(null, new CssModule(dependency)); + } +} + +class MiniCssExtractPlugin { + constructor(options) { + this.options = Object.assign({ + filename: '[name].css' + }, options); + if (!this.options.chunkFilename) { + const { filename } = this.options; + const hasName = filename.includes('[name]'); + const hasId = filename.includes('[id]'); + const hasChunkHash = filename.includes('[chunkhash]'); + // Anything changing depending on chunk is fine + if (hasChunkHash || hasName || hasId) { + this.options.chunkFilename = filename; + } else { + // Elsewise prefix '[id].' in front of the basename to make it changing + this.options.chunkFilename = filename.replace(/(^|\/)([^/]*(?:\?|$))/, '$1[id].$2'); + } + } + } + + apply(compiler) { + compiler.hooks.thisCompilation.tap(pluginName, compilation => { + compilation.hooks.normalModuleLoader.tap(pluginName, (lc, m) => { + const loaderContext = lc; + const module = m; + loaderContext[MODULE_TYPE] = content => { + if (!Array.isArray(content) && content != null) { + throw new Error(`Exported value was not extracted as an array: ${JSON.stringify(content)}`); + } + + const identifierCountMap = new Map(); + for (const line of content) { + const count = identifierCountMap.get(line.identifier) || 0; + module.addDependency(new CssDependency(line, m.context, count)); + identifierCountMap.set(line.identifier, count + 1); + } + }; + }); + compilation.dependencyFactories.set(CssDependency, new CssModuleFactory()); + compilation.dependencyTemplates.set(CssDependency, new CssDependencyTemplate()); + compilation.mainTemplate.hooks.renderManifest.tap(pluginName, (result, { chunk }) => { + const renderedModules = Array.from(chunk.modulesIterable).filter(module => module.type === MODULE_TYPE); + if (renderedModules.length > 0) { + result.push({ + render: () => this.renderContentAsset(compilation, chunk, renderedModules, compilation.runtimeTemplate.requestShortener), + filenameTemplate: this.options.filename, + pathOptions: { + chunk, + contentHashType: MODULE_TYPE + }, + identifier: `${pluginName}.${chunk.id}`, + hash: chunk.contentHash[MODULE_TYPE] + }); + } + }); + compilation.chunkTemplate.hooks.renderManifest.tap(pluginName, (result, { chunk }) => { + const renderedModules = Array.from(chunk.modulesIterable).filter(module => module.type === MODULE_TYPE); + if (renderedModules.length > 0) { + result.push({ + render: () => this.renderContentAsset(compilation, chunk, renderedModules, compilation.runtimeTemplate.requestShortener), + filenameTemplate: this.options.chunkFilename, + pathOptions: { + chunk, + contentHashType: MODULE_TYPE + }, + identifier: `${pluginName}.${chunk.id}`, + hash: chunk.contentHash[MODULE_TYPE] + }); + } + }); + compilation.mainTemplate.hooks.hashForChunk.tap(pluginName, (hash, chunk) => { + const { chunkFilename } = this.options; + if (REGEXP_CHUNKHASH.test(chunkFilename)) { + hash.update(JSON.stringify(chunk.getChunkMaps(true).hash)); + } + if (REGEXP_CONTENTHASH.test(chunkFilename)) { + hash.update(JSON.stringify(chunk.getChunkMaps(true).contentHash[MODULE_TYPE] || {})); + } + if (REGEXP_NAME.test(chunkFilename)) { + hash.update(JSON.stringify(chunk.getChunkMaps(true).name)); + } + }); + compilation.hooks.contentHash.tap(pluginName, chunk => { + const { outputOptions } = compilation; + const { hashFunction, hashDigest, hashDigestLength } = outputOptions; + const hash = createHash(hashFunction); + for (const m of chunk.modulesIterable) { + if (m.type === MODULE_TYPE) { + m.updateHash(hash); + } + } + const { contentHash } = chunk; + contentHash[MODULE_TYPE] = hash.digest(hashDigest).substring(0, hashDigestLength); + }); + const { mainTemplate } = compilation; + mainTemplate.hooks.localVars.tap(pluginName, (source, chunk) => { + const chunkMap = this.getCssChunkObject(chunk); + if (Object.keys(chunkMap).length > 0) { + return Template.asString([source, '', '// object to store loaded CSS chunks', 'var installedCssChunks = {', Template.indent(chunk.ids.map(id => `${JSON.stringify(id)}: 0`).join(',\n')), '}']); + } + 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 { crossOriginLoading } = mainTemplate.outputOptions; + 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]) {', Template.indent(['promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', 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" || tag.rel === "preload") && (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 = "preload";', 'linkTag.as = "style";', '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;', 'delete installedCssChunks[chunkId]', 'linkTag.parentNode.removeChild(linkTag)', 'reject(err);']), '};', 'linkTag.href = fullhref;', crossOriginLoading ? Template.asString([`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`, Template.indent(`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), '}']) : '', 'var head = document.getElementsByTagName("head")[0];', 'head.appendChild(linkTag);']), '}).then(function() {', Template.indent(['var execLinkTag = document.createElement("link");', `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, 'execLinkTag.rel = "stylesheet";', 'execLinkTag.type = "text/css";', 'document.body.appendChild(execLinkTag);', 'installedCssChunks[chunkId] = 0;']), '}));']), '}']); + } + return source; + }); + }); + } + + getCssChunkObject(mainChunk) { + const obj = {}; + for (const chunk of mainChunk.getAllAsyncChunks()) { + for (const module of chunk.modulesIterable) { + if (module.type === MODULE_TYPE) { + obj[chunk.id] = 1; + break; + } + } + } + return obj; + } + + renderContentAsset(compilation, chunk, modules, requestShortener) { + let usedModules; + + const [chunkGroup] = chunk.groupsIterable; + if (typeof chunkGroup.getModuleIndex2 === 'function') { + // Store dependencies for modules + const moduleDependencies = new Map(modules.map(m => [m, new Set()])); + + // Get ordered list of modules per chunk group + // This loop also gathers dependencies from the ordered lists + // Lists are in reverse order to allow to use Array.pop() + const modulesByChunkGroup = Array.from(chunk.groupsIterable, cg => { + const sortedModules = modules.map(m => { + return { + module: m, + index: cg.getModuleIndex2(m) + }; + }).filter(item => item.index !== undefined).sort((a, b) => b.index - a.index).map(item => item.module); + for (let i = 0; i < sortedModules.length; i++) { + const set = moduleDependencies.get(sortedModules[i]); + for (let j = i + 1; j < sortedModules.length; j++) { + set.add(sortedModules[j]); + } + } + + return sortedModules; + }); + + // set with already included modules in correct order + usedModules = new Set(); + + const unusedModulesFilter = m => !usedModules.has(m); + + while (usedModules.size < modules.length) { + let success = false; + let bestMatch; + let bestMatchDeps; + // get first module where dependencies are fulfilled + for (const list of modulesByChunkGroup) { + // skip and remove already added modules + while (list.length > 0 && usedModules.has(list[list.length - 1])) list.pop(); + + // skip empty lists + if (list.length !== 0) { + const module = list[list.length - 1]; + const deps = moduleDependencies.get(module); + // determine dependencies that are not yet included + const failedDeps = Array.from(deps).filter(unusedModulesFilter); + + // store best match for fallback behavior + if (!bestMatchDeps || bestMatchDeps.length > failedDeps.length) { + bestMatch = list; + bestMatchDeps = failedDeps; + } + if (failedDeps.length === 0) { + // use this module and remove it from list + usedModules.add(list.pop()); + success = true; + break; + } + } + } + + if (!success) { + // no module found => there is a conflict + // use list with fewest failed deps + // and emit a warning + const fallbackModule = bestMatch.pop(); + compilation.warnings.push(new Error(`chunk ${chunk.name || chunk.id} [mini-css-extract-plugin]\n` + 'Conflicting order between:\n' + ` * ${fallbackModule.readableIdentifier(requestShortener)}\n` + `${bestMatchDeps.map(m => ` * ${m.readableIdentifier(requestShortener)}`).join('\n')}`)); + usedModules.add(fallbackModule); + } + } + } else { + // fallback for older webpack versions + // (to avoid a breaking change) + // TODO remove this in next mayor version + // and increase minimum webpack version to 4.12.0 + modules.sort((a, b) => a.index2 - b.index2); + usedModules = modules; + } + const source = new ConcatSource(); + const externalsSource = new ConcatSource(); + for (const m of usedModules) { + if (/^@import url/.test(m.content)) { + // HACK for IE + // http://stackoverflow.com/a/14676665/1458162 + let { content } = m; + if (m.media) { + // insert media into the @import + // this is rar + // TODO improve this and parse the CSS to support multiple medias + content = content.replace(/;|\s*$/, m.media); + } + externalsSource.add(content); + externalsSource.add('\n'); + } else { + if (m.media) { + source.add(`@media ${m.media} {\n`); + } + if (m.sourceMap) { + source.add(new SourceMapSource(m.content, m.readableIdentifier(requestShortener), m.sourceMap)); + } else { + source.add(new OriginalSource(m.content, m.readableIdentifier(requestShortener))); + } + source.add('\n'); + if (m.media) { + source.add('}\n'); + } + } + } + return new ConcatSource(externalsSource, source); + } +} + +MiniCssExtractPlugin.loader = require.resolve('./loader'); + +exports.default = MiniCssExtractPlugin; \ No newline at end of file diff --git a/dist/loader.js b/dist/loader.js new file mode 100644 index 00000000..2a566577 --- /dev/null +++ b/dist/loader.js @@ -0,0 +1,152 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.pitch = pitch; + +exports.default = function () {}; + +var _module = require('module'); + +var _module2 = _interopRequireDefault(_module); + +var _loaderUtils = require('loader-utils'); + +var _loaderUtils2 = _interopRequireDefault(_loaderUtils); + +var _NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin'); + +var _NodeTemplatePlugin2 = _interopRequireDefault(_NodeTemplatePlugin); + +var _NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin'); + +var _NodeTargetPlugin2 = _interopRequireDefault(_NodeTargetPlugin); + +var _LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin'); + +var _LibraryTemplatePlugin2 = _interopRequireDefault(_LibraryTemplatePlugin); + +var _SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin'); + +var _SingleEntryPlugin2 = _interopRequireDefault(_SingleEntryPlugin); + +var _LimitChunkCountPlugin = require('webpack/lib/optimize/LimitChunkCountPlugin'); + +var _LimitChunkCountPlugin2 = _interopRequireDefault(_LimitChunkCountPlugin); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const MODULE_TYPE = 'css/mini-extract'; +const pluginName = 'mini-css-extract-plugin'; + +const exec = (loaderContext, code, filename) => { + const module = new _module2.default(filename, loaderContext); + module.paths = _module2.default._nodeModulePaths(loaderContext.context); // eslint-disable-line no-underscore-dangle + module.filename = filename; + module._compile(code, filename); // eslint-disable-line no-underscore-dangle + return module.exports; +}; + +const findModuleById = (modules, id) => { + for (const module of modules) { + if (module.id === id) { + return module; + } + } + return null; +}; + +function pitch(request) { + const query = _loaderUtils2.default.getOptions(this) || {}; + const loaders = this.loaders.slice(this.loaderIndex + 1); + this.addDependency(this.resourcePath); + const childFilename = '*'; // eslint-disable-line no-path-concat + const publicPath = typeof query.publicPath === 'string' ? query.publicPath : this._compilation.outputOptions.publicPath; + const outputOptions = { + filename: childFilename, + publicPath + }; + const childCompiler = this._compilation.createChildCompiler(`${pluginName} ${request}`, outputOptions); + new _NodeTemplatePlugin2.default(outputOptions).apply(childCompiler); + new _LibraryTemplatePlugin2.default(null, 'commonjs2').apply(childCompiler); + new _NodeTargetPlugin2.default().apply(childCompiler); + new _SingleEntryPlugin2.default(this.context, `!!${request}`, pluginName).apply(childCompiler); + new _LimitChunkCountPlugin2.default({ maxChunks: 1 }).apply(childCompiler); + // We set loaderContext[MODULE_TYPE] = false to indicate we already in + // a child compiler so we don't spawn another child compilers from there. + childCompiler.hooks.thisCompilation.tap(`${pluginName} loader`, compilation => { + compilation.hooks.normalModuleLoader.tap(`${pluginName} loader`, (loaderContext, module) => { + loaderContext.emitFile = this.emitFile; + loaderContext[MODULE_TYPE] = false; // eslint-disable-line no-param-reassign + if (module.request === request) { + // eslint-disable-next-line no-param-reassign + module.loaders = loaders.map(loader => { + return { + loader: loader.path, + options: loader.options, + ident: loader.ident + }; + }); + } + }); + }); + + let source; + childCompiler.hooks.afterCompile.tap(pluginName, compilation => { + source = compilation.assets[childFilename] && compilation.assets[childFilename].source(); + + // Remove all chunk assets + compilation.chunks.forEach(chunk => { + chunk.files.forEach(file => { + delete compilation.assets[file]; // eslint-disable-line no-param-reassign + }); + }); + }); + + const callback = this.async(); + childCompiler.runAsChild((err, entries, compilation) => { + if (err) return callback(err); + + if (compilation.errors.length > 0) { + return callback(compilation.errors[0]); + } + compilation.fileDependencies.forEach(dep => { + this.addDependency(dep); + }, this); + compilation.contextDependencies.forEach(dep => { + this.addContextDependency(dep); + }, this); + if (!source) { + return callback(new Error("Didn't get a result from child compiler")); + } + let text; + let locals; + try { + text = exec(this, source, request); + locals = text && text.locals; + if (!Array.isArray(text)) { + text = [[null, text]]; + } else { + text = text.map(line => { + const module = findModuleById(compilation.modules, line[0]); + return { + identifier: module.identifier(), + content: line[1], + media: line[2], + sourceMap: line[3] + }; + }); + } + this[MODULE_TYPE](text); + } catch (e) { + return callback(e); + } + let resultSource = `// extracted by ${pluginName}`; + if (locals && typeof resultSource !== 'undefined') { + resultSource += `\nmodule.exports = ${JSON.stringify(locals)};`; + } + + return callback(null, resultSource); + }); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d3a4647e..8ee7caa0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5832,12 +5832,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5852,17 +5854,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5979,7 +5984,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5991,6 +5997,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6005,6 +6012,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6012,12 +6020,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -6036,6 +6046,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -6116,7 +6127,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -6128,6 +6140,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -6249,6 +6262,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", From de6194592dcc7eddd790577b3e874f837688bb95 Mon Sep 17 00:00:00 2001 From: jgillespie Date: Wed, 23 Jan 2019 08:13:23 -0700 Subject: [PATCH 05/16] SupportsPreload incorporated --- dist/index.js | 2 +- src/index.js | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/dist/index.js b/dist/index.js index 57342e95..138a40b4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -253,7 +253,7 @@ class MiniCssExtractPlugin { }, 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]) {', Template.indent(['promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', 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" || tag.rel === "preload") && (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 = "preload";', 'linkTag.as = "style";', '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;', 'delete installedCssChunks[chunkId]', 'linkTag.parentNode.removeChild(linkTag)', 'reject(err);']), '};', 'linkTag.href = fullhref;', crossOriginLoading ? Template.asString([`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`, Template.indent(`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), '}']) : '', 'var head = document.getElementsByTagName("head")[0];', 'head.appendChild(linkTag);']), '}).then(function() {', Template.indent(['var execLinkTag = document.createElement("link");', `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, 'execLinkTag.rel = "stylesheet";', 'execLinkTag.type = "text/css";', 'document.body.appendChild(execLinkTag);', 'installedCssChunks[chunkId] = 0;']), '}));']), '}']); + return Template.asString([source, '', `// ${pluginName} CSS loading`, 'var supportsPreload = (function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());', `var cssChunks = ${JSON.stringify(chunkMap)};`, 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', 'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {', Template.indent(['promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', 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" || tag.rel === "preload") && (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 = "supportsPreload ? "preload": "stylesheet";', 'console.log("supportsPreload: ", supportsPreload);', 'supportsPreload ? linkTag.as = "style" : 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;', 'delete installedCssChunks[chunkId]', 'linkTag.parentNode.removeChild(linkTag)', 'reject(err);']), '};', 'linkTag.href = fullhref;', crossOriginLoading ? Template.asString([`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`, Template.indent(`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), '}']) : '', 'var head = document.getElementsByTagName("head")[0];', 'head.appendChild(linkTag);']), '}).then(function() {', Template.indent(['installedCssChunks[chunkId] = 0;', 'if(supportsPreload) {', Template.indent(['var execLinkTag = document.createElement("link");', `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, 'execLinkTag.rel = "stylesheet";', 'execLinkTag.type = "text/css";', 'document.body.appendChild(execLinkTag);']), '}']), '}));']), '}']); } return source; }); diff --git a/src/index.js b/src/index.js index f877a022..8a0c5d49 100644 --- a/src/index.js +++ b/src/index.js @@ -327,6 +327,7 @@ class MiniCssExtractPlugin { source, '', `// ${pluginName} CSS loading`, + 'var supportsPreload = (function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());', `var cssChunks = ${JSON.stringify(chunkMap)};`, 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', 'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {', @@ -352,8 +353,9 @@ class MiniCssExtractPlugin { ]), '}', 'var linkTag = document.createElement("link");', - 'linkTag.rel = "preload";', - 'linkTag.as = "style";', + 'linkTag.rel = "supportsPreload ? "preload": "stylesheet";', + 'console.log("supportsPreload: ", supportsPreload);', + 'supportsPreload ? linkTag.as = "style" : linkTag.type = "text/css";', 'linkTag.onload = resolve;', 'linkTag.onerror = function(event) {', Template.indent([ @@ -382,12 +384,18 @@ class MiniCssExtractPlugin { ]), '}).then(function() {', Template.indent([ - 'var execLinkTag = document.createElement("link");', - `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, - 'execLinkTag.rel = "stylesheet";', - 'execLinkTag.type = "text/css";', - 'document.body.appendChild(execLinkTag);', - 'installedCssChunks[chunkId] = 0;' + 'installedCssChunks[chunkId] = 0;', + 'if(supportsPreload) {', + Template.indent([ + 'var execLinkTag = document.createElement("link");', + `execLinkTag.href = ${ + mainTemplate.requireFn + }.p + ${linkHrefPath};`, + 'execLinkTag.rel = "stylesheet";', + 'execLinkTag.type = "text/css";', + 'document.body.appendChild(execLinkTag);', + ]), + '}', ]), '}));', ]), From 15b959db27b4a012c97b8f8f9bade0c41222d457 Mon Sep 17 00:00:00 2001 From: jgillespie Date: Wed, 23 Jan 2019 14:19:38 -0700 Subject: [PATCH 06/16] Remove console --- src/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.js b/src/index.js index 8a0c5d49..e9ebc639 100644 --- a/src/index.js +++ b/src/index.js @@ -354,7 +354,6 @@ class MiniCssExtractPlugin { '}', 'var linkTag = document.createElement("link");', 'linkTag.rel = "supportsPreload ? "preload": "stylesheet";', - 'console.log("supportsPreload: ", supportsPreload);', 'supportsPreload ? linkTag.as = "style" : linkTag.type = "text/css";', 'linkTag.onload = resolve;', 'linkTag.onerror = function(event) {', From 7751da2db459c9c4b336282a8ed21e9c81e5c327 Mon Sep 17 00:00:00 2001 From: jgillespie Date: Wed, 23 Jan 2019 14:22:29 -0700 Subject: [PATCH 07/16] rebuild dist --- dist/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/index.js b/dist/index.js index 138a40b4..40866762 100644 --- a/dist/index.js +++ b/dist/index.js @@ -253,7 +253,7 @@ class MiniCssExtractPlugin { }, contentHashType: MODULE_TYPE }); - return Template.asString([source, '', `// ${pluginName} CSS loading`, 'var supportsPreload = (function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());', `var cssChunks = ${JSON.stringify(chunkMap)};`, 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', 'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {', Template.indent(['promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', 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" || tag.rel === "preload") && (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 = "supportsPreload ? "preload": "stylesheet";', 'console.log("supportsPreload: ", supportsPreload);', 'supportsPreload ? linkTag.as = "style" : 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;', 'delete installedCssChunks[chunkId]', 'linkTag.parentNode.removeChild(linkTag)', 'reject(err);']), '};', 'linkTag.href = fullhref;', crossOriginLoading ? Template.asString([`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`, Template.indent(`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), '}']) : '', 'var head = document.getElementsByTagName("head")[0];', 'head.appendChild(linkTag);']), '}).then(function() {', Template.indent(['installedCssChunks[chunkId] = 0;', 'if(supportsPreload) {', Template.indent(['var execLinkTag = document.createElement("link");', `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, 'execLinkTag.rel = "stylesheet";', 'execLinkTag.type = "text/css";', 'document.body.appendChild(execLinkTag);']), '}']), '}));']), '}']); + return Template.asString([source, '', `// ${pluginName} CSS loading`, 'var supportsPreload = (function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());', `var cssChunks = ${JSON.stringify(chunkMap)};`, 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', 'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {', Template.indent(['promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', 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" || tag.rel === "preload") && (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 = "supportsPreload ? "preload": "stylesheet";', 'supportsPreload ? linkTag.as = "style" : 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;', 'delete installedCssChunks[chunkId]', 'linkTag.parentNode.removeChild(linkTag)', 'reject(err);']), '};', 'linkTag.href = fullhref;', crossOriginLoading ? Template.asString([`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`, Template.indent(`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), '}']) : '', 'var head = document.getElementsByTagName("head")[0];', 'head.appendChild(linkTag);']), '}).then(function() {', Template.indent(['installedCssChunks[chunkId] = 0;', 'if(supportsPreload) {', Template.indent(['var execLinkTag = document.createElement("link");', `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, 'execLinkTag.rel = "stylesheet";', 'execLinkTag.type = "text/css";', 'document.body.appendChild(execLinkTag);']), '}']), '}));']), '}']); } return source; }); From a076864b97d2974f0ddcd0d885bc21effec261b6 Mon Sep 17 00:00:00 2001 From: jgillespie Date: Thu, 24 Jan 2019 08:59:59 -0700 Subject: [PATCH 08/16] Fixed extra quotes in JS injection --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index e9ebc639..c0042d55 100644 --- a/src/index.js +++ b/src/index.js @@ -353,7 +353,7 @@ class MiniCssExtractPlugin { ]), '}', 'var linkTag = document.createElement("link");', - 'linkTag.rel = "supportsPreload ? "preload": "stylesheet";', + 'linkTag.rel = supportsPreload ? "preload": "stylesheet";', 'supportsPreload ? linkTag.as = "style" : linkTag.type = "text/css";', 'linkTag.onload = resolve;', 'linkTag.onerror = function(event) {', From bdd45a0d0f709b08a0f873bfa568c5c75e23cdcc Mon Sep 17 00:00:00 2001 From: jgillespie Date: Thu, 24 Jan 2019 09:02:09 -0700 Subject: [PATCH 09/16] Regenerate dist --- dist/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/index.js b/dist/index.js index 40866762..e6e185af 100644 --- a/dist/index.js +++ b/dist/index.js @@ -253,7 +253,7 @@ class MiniCssExtractPlugin { }, contentHashType: MODULE_TYPE }); - return Template.asString([source, '', `// ${pluginName} CSS loading`, 'var supportsPreload = (function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());', `var cssChunks = ${JSON.stringify(chunkMap)};`, 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', 'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {', Template.indent(['promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', 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" || tag.rel === "preload") && (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 = "supportsPreload ? "preload": "stylesheet";', 'supportsPreload ? linkTag.as = "style" : 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;', 'delete installedCssChunks[chunkId]', 'linkTag.parentNode.removeChild(linkTag)', 'reject(err);']), '};', 'linkTag.href = fullhref;', crossOriginLoading ? Template.asString([`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`, Template.indent(`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), '}']) : '', 'var head = document.getElementsByTagName("head")[0];', 'head.appendChild(linkTag);']), '}).then(function() {', Template.indent(['installedCssChunks[chunkId] = 0;', 'if(supportsPreload) {', Template.indent(['var execLinkTag = document.createElement("link");', `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, 'execLinkTag.rel = "stylesheet";', 'execLinkTag.type = "text/css";', 'document.body.appendChild(execLinkTag);']), '}']), '}));']), '}']); + return Template.asString([source, '', `// ${pluginName} CSS loading`, 'var supportsPreload = (function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());', `var cssChunks = ${JSON.stringify(chunkMap)};`, 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', 'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {', Template.indent(['promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', 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" || tag.rel === "preload") && (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 = supportsPreload ? "preload": "stylesheet";', 'supportsPreload ? linkTag.as = "style" : 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;', 'delete installedCssChunks[chunkId]', 'linkTag.parentNode.removeChild(linkTag)', 'reject(err);']), '};', 'linkTag.href = fullhref;', crossOriginLoading ? Template.asString([`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`, Template.indent(`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), '}']) : '', 'var head = document.getElementsByTagName("head")[0];', 'head.appendChild(linkTag);']), '}).then(function() {', Template.indent(['installedCssChunks[chunkId] = 0;', 'if(supportsPreload) {', Template.indent(['var execLinkTag = document.createElement("link");', `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, 'execLinkTag.rel = "stylesheet";', 'execLinkTag.type = "text/css";', 'document.body.appendChild(execLinkTag);']), '}']), '}));']), '}']); } return source; }); From 727b2a4bddfe941fba73891d6412e24e06868efe Mon Sep 17 00:00:00 2001 From: jgillespie Date: Thu, 24 Jan 2019 10:35:49 -0700 Subject: [PATCH 10/16] feat: add rel preload support - useRelPreload option --- dist/index.js | 7 +++++-- src/index.js | 7 ++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/dist/index.js b/dist/index.js index e6e185af..972ec8ca 100644 --- a/dist/index.js +++ b/dist/index.js @@ -115,7 +115,8 @@ class CssModuleFactory { class MiniCssExtractPlugin { constructor(options) { this.options = Object.assign({ - filename: '[name].css' + filename: '[name].css', + useRelPreload: false }, options); if (!this.options.chunkFilename) { const { filename } = this.options; @@ -253,7 +254,9 @@ class MiniCssExtractPlugin { }, contentHashType: MODULE_TYPE }); - return Template.asString([source, '', `// ${pluginName} CSS loading`, 'var supportsPreload = (function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());', `var cssChunks = ${JSON.stringify(chunkMap)};`, 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', 'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {', Template.indent(['promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', 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" || tag.rel === "preload") && (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 = supportsPreload ? "preload": "stylesheet";', 'supportsPreload ? linkTag.as = "style" : 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;', 'delete installedCssChunks[chunkId]', 'linkTag.parentNode.removeChild(linkTag)', 'reject(err);']), '};', 'linkTag.href = fullhref;', crossOriginLoading ? Template.asString([`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`, Template.indent(`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), '}']) : '', 'var head = document.getElementsByTagName("head")[0];', 'head.appendChild(linkTag);']), '}).then(function() {', Template.indent(['installedCssChunks[chunkId] = 0;', 'if(supportsPreload) {', Template.indent(['var execLinkTag = document.createElement("link");', `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, 'execLinkTag.rel = "stylesheet";', 'execLinkTag.type = "text/css";', 'document.body.appendChild(execLinkTag);']), '}']), '}));']), '}']); + + const supportsPreload = this.options.useRelPreload ? '(function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());' : 'false;'; + return Template.asString([source, '', `// ${pluginName} CSS loading`, `var supportsPreload = ${supportsPreload}`, `var cssChunks = ${JSON.stringify(chunkMap)};`, 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', 'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {', Template.indent(['promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', 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" || tag.rel === "preload") && (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 = supportsPreload ? "preload": "stylesheet";', 'supportsPreload ? linkTag.as = "style" : 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;', 'delete installedCssChunks[chunkId]', 'linkTag.parentNode.removeChild(linkTag)', 'reject(err);']), '};', 'linkTag.href = fullhref;', crossOriginLoading ? Template.asString([`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`, Template.indent(`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), '}']) : '', 'var head = document.getElementsByTagName("head")[0];', 'head.appendChild(linkTag);']), '}).then(function() {', Template.indent(['installedCssChunks[chunkId] = 0;', 'if(supportsPreload) {', Template.indent(['var execLinkTag = document.createElement("link");', `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, 'execLinkTag.rel = "stylesheet";', 'execLinkTag.type = "text/css";', 'document.body.appendChild(execLinkTag);']), '}']), '}));']), '}']); } return source; }); diff --git a/src/index.js b/src/index.js index c0042d55..8e0287ec 100644 --- a/src/index.js +++ b/src/index.js @@ -113,6 +113,7 @@ class MiniCssExtractPlugin { this.options = Object.assign( { filename: '[name].css', + useRelPreload: false, }, options ); @@ -323,11 +324,15 @@ class MiniCssExtractPlugin { contentHashType: MODULE_TYPE, } ); + + const supportsPreload = this.options.useRelPreload + ? '(function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());' + : 'false;'; return Template.asString([ source, '', `// ${pluginName} CSS loading`, - 'var supportsPreload = (function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());', + `var supportsPreload = ${supportsPreload}`, `var cssChunks = ${JSON.stringify(chunkMap)};`, 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', 'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {', From b2dde786d7e1010c3a48232105daf28ed332b666 Mon Sep 17 00:00:00 2001 From: jgillespie Date: Thu, 24 Jan 2019 10:52:55 -0700 Subject: [PATCH 11/16] feat: removed modified files for pull request --- .gitignore | 1 + dist/cjs.js | 3 - dist/index.js | 395 ------------------------------------------------- dist/loader.js | 152 ------------------- 4 files changed, 1 insertion(+), 550 deletions(-) delete mode 100644 dist/cjs.js delete mode 100644 dist/index.js delete mode 100644 dist/loader.js diff --git a/.gitignore b/.gitignore index 4ff034f1..b70b127e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ logs npm-debug.log* .eslintcache /coverage +/dist /test/js /local /reports diff --git a/dist/cjs.js b/dist/cjs.js deleted file mode 100644 index 61cc3574..00000000 --- a/dist/cjs.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('./index').default; \ No newline at end of file diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index 972ec8ca..00000000 --- a/dist/index.js +++ /dev/null @@ -1,395 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _webpack = require('webpack'); - -var _webpack2 = _interopRequireDefault(_webpack); - -var _webpackSources = require('webpack-sources'); - -var _webpackSources2 = _interopRequireDefault(_webpackSources); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -const { ConcatSource, SourceMapSource, OriginalSource } = _webpackSources2.default; -const { - Template, - util: { createHash } -} = _webpack2.default; - -const MODULE_TYPE = 'css/mini-extract'; - -const pluginName = 'mini-css-extract-plugin'; - -const REGEXP_CHUNKHASH = /\[chunkhash(?::(\d+))?\]/i; -const REGEXP_CONTENTHASH = /\[contenthash(?::(\d+))?\]/i; -const REGEXP_NAME = /\[name\]/i; - -class CssDependency extends _webpack2.default.Dependency { - constructor({ identifier, content, media, sourceMap }, context, identifierIndex) { - super(); - this.identifier = identifier; - this.identifierIndex = identifierIndex; - this.content = content; - this.media = media; - this.sourceMap = sourceMap; - this.context = context; - } - - getResourceIdentifier() { - return `css-module-${this.identifier}-${this.identifierIndex}`; - } -} - -class CssDependencyTemplate { - apply() {} -} - -class CssModule extends _webpack2.default.Module { - constructor(dependency) { - super(MODULE_TYPE, dependency.context); - this.id = ''; - this._identifier = dependency.identifier; - this._identifierIndex = dependency.identifierIndex; - this.content = dependency.content; - this.media = dependency.media; - this.sourceMap = dependency.sourceMap; - } - - // no source() so webpack doesn't do add stuff to the bundle - - size() { - return this.content.length; - } - - identifier() { - return `css ${this._identifier} ${this._identifierIndex}`; - } - - readableIdentifier(requestShortener) { - return `css ${requestShortener.shorten(this._identifier)}${this._identifierIndex ? ` (${this._identifierIndex})` : ''}`; - } - - nameForCondition() { - const resource = this._identifier.split('!').pop(); - const idx = resource.indexOf('?'); - if (idx >= 0) return resource.substring(0, idx); - return resource; - } - - updateCacheModule(module) { - this.content = module.content; - this.media = module.media; - this.sourceMap = module.sourceMap; - } - - needRebuild() { - return true; - } - - build(options, compilation, resolver, fileSystem, callback) { - this.buildInfo = {}; - this.buildMeta = {}; - callback(); - } - - updateHash(hash) { - super.updateHash(hash); - hash.update(this.content); - hash.update(this.media || ''); - hash.update(this.sourceMap ? JSON.stringify(this.sourceMap) : ''); - } -} - -class CssModuleFactory { - create({ - dependencies: [dependency] - }, callback) { - callback(null, new CssModule(dependency)); - } -} - -class MiniCssExtractPlugin { - constructor(options) { - this.options = Object.assign({ - filename: '[name].css', - useRelPreload: false - }, options); - if (!this.options.chunkFilename) { - const { filename } = this.options; - const hasName = filename.includes('[name]'); - const hasId = filename.includes('[id]'); - const hasChunkHash = filename.includes('[chunkhash]'); - // Anything changing depending on chunk is fine - if (hasChunkHash || hasName || hasId) { - this.options.chunkFilename = filename; - } else { - // Elsewise prefix '[id].' in front of the basename to make it changing - this.options.chunkFilename = filename.replace(/(^|\/)([^/]*(?:\?|$))/, '$1[id].$2'); - } - } - } - - apply(compiler) { - compiler.hooks.thisCompilation.tap(pluginName, compilation => { - compilation.hooks.normalModuleLoader.tap(pluginName, (lc, m) => { - const loaderContext = lc; - const module = m; - loaderContext[MODULE_TYPE] = content => { - if (!Array.isArray(content) && content != null) { - throw new Error(`Exported value was not extracted as an array: ${JSON.stringify(content)}`); - } - - const identifierCountMap = new Map(); - for (const line of content) { - const count = identifierCountMap.get(line.identifier) || 0; - module.addDependency(new CssDependency(line, m.context, count)); - identifierCountMap.set(line.identifier, count + 1); - } - }; - }); - compilation.dependencyFactories.set(CssDependency, new CssModuleFactory()); - compilation.dependencyTemplates.set(CssDependency, new CssDependencyTemplate()); - compilation.mainTemplate.hooks.renderManifest.tap(pluginName, (result, { chunk }) => { - const renderedModules = Array.from(chunk.modulesIterable).filter(module => module.type === MODULE_TYPE); - if (renderedModules.length > 0) { - result.push({ - render: () => this.renderContentAsset(compilation, chunk, renderedModules, compilation.runtimeTemplate.requestShortener), - filenameTemplate: this.options.filename, - pathOptions: { - chunk, - contentHashType: MODULE_TYPE - }, - identifier: `${pluginName}.${chunk.id}`, - hash: chunk.contentHash[MODULE_TYPE] - }); - } - }); - compilation.chunkTemplate.hooks.renderManifest.tap(pluginName, (result, { chunk }) => { - const renderedModules = Array.from(chunk.modulesIterable).filter(module => module.type === MODULE_TYPE); - if (renderedModules.length > 0) { - result.push({ - render: () => this.renderContentAsset(compilation, chunk, renderedModules, compilation.runtimeTemplate.requestShortener), - filenameTemplate: this.options.chunkFilename, - pathOptions: { - chunk, - contentHashType: MODULE_TYPE - }, - identifier: `${pluginName}.${chunk.id}`, - hash: chunk.contentHash[MODULE_TYPE] - }); - } - }); - compilation.mainTemplate.hooks.hashForChunk.tap(pluginName, (hash, chunk) => { - const { chunkFilename } = this.options; - if (REGEXP_CHUNKHASH.test(chunkFilename)) { - hash.update(JSON.stringify(chunk.getChunkMaps(true).hash)); - } - if (REGEXP_CONTENTHASH.test(chunkFilename)) { - hash.update(JSON.stringify(chunk.getChunkMaps(true).contentHash[MODULE_TYPE] || {})); - } - if (REGEXP_NAME.test(chunkFilename)) { - hash.update(JSON.stringify(chunk.getChunkMaps(true).name)); - } - }); - compilation.hooks.contentHash.tap(pluginName, chunk => { - const { outputOptions } = compilation; - const { hashFunction, hashDigest, hashDigestLength } = outputOptions; - const hash = createHash(hashFunction); - for (const m of chunk.modulesIterable) { - if (m.type === MODULE_TYPE) { - m.updateHash(hash); - } - } - const { contentHash } = chunk; - contentHash[MODULE_TYPE] = hash.digest(hashDigest).substring(0, hashDigestLength); - }); - const { mainTemplate } = compilation; - mainTemplate.hooks.localVars.tap(pluginName, (source, chunk) => { - const chunkMap = this.getCssChunkObject(chunk); - if (Object.keys(chunkMap).length > 0) { - return Template.asString([source, '', '// object to store loaded CSS chunks', 'var installedCssChunks = {', Template.indent(chunk.ids.map(id => `${JSON.stringify(id)}: 0`).join(',\n')), '}']); - } - 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 { crossOriginLoading } = mainTemplate.outputOptions; - 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 - }); - - const supportsPreload = this.options.useRelPreload ? '(function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());' : 'false;'; - return Template.asString([source, '', `// ${pluginName} CSS loading`, `var supportsPreload = ${supportsPreload}`, `var cssChunks = ${JSON.stringify(chunkMap)};`, 'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);', 'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {', Template.indent(['promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {', 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" || tag.rel === "preload") && (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 = supportsPreload ? "preload": "stylesheet";', 'supportsPreload ? linkTag.as = "style" : 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;', 'delete installedCssChunks[chunkId]', 'linkTag.parentNode.removeChild(linkTag)', 'reject(err);']), '};', 'linkTag.href = fullhref;', crossOriginLoading ? Template.asString([`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`, Template.indent(`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), '}']) : '', 'var head = document.getElementsByTagName("head")[0];', 'head.appendChild(linkTag);']), '}).then(function() {', Template.indent(['installedCssChunks[chunkId] = 0;', 'if(supportsPreload) {', Template.indent(['var execLinkTag = document.createElement("link");', `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, 'execLinkTag.rel = "stylesheet";', 'execLinkTag.type = "text/css";', 'document.body.appendChild(execLinkTag);']), '}']), '}));']), '}']); - } - return source; - }); - }); - } - - getCssChunkObject(mainChunk) { - const obj = {}; - for (const chunk of mainChunk.getAllAsyncChunks()) { - for (const module of chunk.modulesIterable) { - if (module.type === MODULE_TYPE) { - obj[chunk.id] = 1; - break; - } - } - } - return obj; - } - - renderContentAsset(compilation, chunk, modules, requestShortener) { - let usedModules; - - const [chunkGroup] = chunk.groupsIterable; - if (typeof chunkGroup.getModuleIndex2 === 'function') { - // Store dependencies for modules - const moduleDependencies = new Map(modules.map(m => [m, new Set()])); - - // Get ordered list of modules per chunk group - // This loop also gathers dependencies from the ordered lists - // Lists are in reverse order to allow to use Array.pop() - const modulesByChunkGroup = Array.from(chunk.groupsIterable, cg => { - const sortedModules = modules.map(m => { - return { - module: m, - index: cg.getModuleIndex2(m) - }; - }).filter(item => item.index !== undefined).sort((a, b) => b.index - a.index).map(item => item.module); - for (let i = 0; i < sortedModules.length; i++) { - const set = moduleDependencies.get(sortedModules[i]); - for (let j = i + 1; j < sortedModules.length; j++) { - set.add(sortedModules[j]); - } - } - - return sortedModules; - }); - - // set with already included modules in correct order - usedModules = new Set(); - - const unusedModulesFilter = m => !usedModules.has(m); - - while (usedModules.size < modules.length) { - let success = false; - let bestMatch; - let bestMatchDeps; - // get first module where dependencies are fulfilled - for (const list of modulesByChunkGroup) { - // skip and remove already added modules - while (list.length > 0 && usedModules.has(list[list.length - 1])) list.pop(); - - // skip empty lists - if (list.length !== 0) { - const module = list[list.length - 1]; - const deps = moduleDependencies.get(module); - // determine dependencies that are not yet included - const failedDeps = Array.from(deps).filter(unusedModulesFilter); - - // store best match for fallback behavior - if (!bestMatchDeps || bestMatchDeps.length > failedDeps.length) { - bestMatch = list; - bestMatchDeps = failedDeps; - } - if (failedDeps.length === 0) { - // use this module and remove it from list - usedModules.add(list.pop()); - success = true; - break; - } - } - } - - if (!success) { - // no module found => there is a conflict - // use list with fewest failed deps - // and emit a warning - const fallbackModule = bestMatch.pop(); - compilation.warnings.push(new Error(`chunk ${chunk.name || chunk.id} [mini-css-extract-plugin]\n` + 'Conflicting order between:\n' + ` * ${fallbackModule.readableIdentifier(requestShortener)}\n` + `${bestMatchDeps.map(m => ` * ${m.readableIdentifier(requestShortener)}`).join('\n')}`)); - usedModules.add(fallbackModule); - } - } - } else { - // fallback for older webpack versions - // (to avoid a breaking change) - // TODO remove this in next mayor version - // and increase minimum webpack version to 4.12.0 - modules.sort((a, b) => a.index2 - b.index2); - usedModules = modules; - } - const source = new ConcatSource(); - const externalsSource = new ConcatSource(); - for (const m of usedModules) { - if (/^@import url/.test(m.content)) { - // HACK for IE - // http://stackoverflow.com/a/14676665/1458162 - let { content } = m; - if (m.media) { - // insert media into the @import - // this is rar - // TODO improve this and parse the CSS to support multiple medias - content = content.replace(/;|\s*$/, m.media); - } - externalsSource.add(content); - externalsSource.add('\n'); - } else { - if (m.media) { - source.add(`@media ${m.media} {\n`); - } - if (m.sourceMap) { - source.add(new SourceMapSource(m.content, m.readableIdentifier(requestShortener), m.sourceMap)); - } else { - source.add(new OriginalSource(m.content, m.readableIdentifier(requestShortener))); - } - source.add('\n'); - if (m.media) { - source.add('}\n'); - } - } - } - return new ConcatSource(externalsSource, source); - } -} - -MiniCssExtractPlugin.loader = require.resolve('./loader'); - -exports.default = MiniCssExtractPlugin; \ No newline at end of file diff --git a/dist/loader.js b/dist/loader.js deleted file mode 100644 index 2a566577..00000000 --- a/dist/loader.js +++ /dev/null @@ -1,152 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.pitch = pitch; - -exports.default = function () {}; - -var _module = require('module'); - -var _module2 = _interopRequireDefault(_module); - -var _loaderUtils = require('loader-utils'); - -var _loaderUtils2 = _interopRequireDefault(_loaderUtils); - -var _NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin'); - -var _NodeTemplatePlugin2 = _interopRequireDefault(_NodeTemplatePlugin); - -var _NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin'); - -var _NodeTargetPlugin2 = _interopRequireDefault(_NodeTargetPlugin); - -var _LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin'); - -var _LibraryTemplatePlugin2 = _interopRequireDefault(_LibraryTemplatePlugin); - -var _SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin'); - -var _SingleEntryPlugin2 = _interopRequireDefault(_SingleEntryPlugin); - -var _LimitChunkCountPlugin = require('webpack/lib/optimize/LimitChunkCountPlugin'); - -var _LimitChunkCountPlugin2 = _interopRequireDefault(_LimitChunkCountPlugin); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -const MODULE_TYPE = 'css/mini-extract'; -const pluginName = 'mini-css-extract-plugin'; - -const exec = (loaderContext, code, filename) => { - const module = new _module2.default(filename, loaderContext); - module.paths = _module2.default._nodeModulePaths(loaderContext.context); // eslint-disable-line no-underscore-dangle - module.filename = filename; - module._compile(code, filename); // eslint-disable-line no-underscore-dangle - return module.exports; -}; - -const findModuleById = (modules, id) => { - for (const module of modules) { - if (module.id === id) { - return module; - } - } - return null; -}; - -function pitch(request) { - const query = _loaderUtils2.default.getOptions(this) || {}; - const loaders = this.loaders.slice(this.loaderIndex + 1); - this.addDependency(this.resourcePath); - const childFilename = '*'; // eslint-disable-line no-path-concat - const publicPath = typeof query.publicPath === 'string' ? query.publicPath : this._compilation.outputOptions.publicPath; - const outputOptions = { - filename: childFilename, - publicPath - }; - const childCompiler = this._compilation.createChildCompiler(`${pluginName} ${request}`, outputOptions); - new _NodeTemplatePlugin2.default(outputOptions).apply(childCompiler); - new _LibraryTemplatePlugin2.default(null, 'commonjs2').apply(childCompiler); - new _NodeTargetPlugin2.default().apply(childCompiler); - new _SingleEntryPlugin2.default(this.context, `!!${request}`, pluginName).apply(childCompiler); - new _LimitChunkCountPlugin2.default({ maxChunks: 1 }).apply(childCompiler); - // We set loaderContext[MODULE_TYPE] = false to indicate we already in - // a child compiler so we don't spawn another child compilers from there. - childCompiler.hooks.thisCompilation.tap(`${pluginName} loader`, compilation => { - compilation.hooks.normalModuleLoader.tap(`${pluginName} loader`, (loaderContext, module) => { - loaderContext.emitFile = this.emitFile; - loaderContext[MODULE_TYPE] = false; // eslint-disable-line no-param-reassign - if (module.request === request) { - // eslint-disable-next-line no-param-reassign - module.loaders = loaders.map(loader => { - return { - loader: loader.path, - options: loader.options, - ident: loader.ident - }; - }); - } - }); - }); - - let source; - childCompiler.hooks.afterCompile.tap(pluginName, compilation => { - source = compilation.assets[childFilename] && compilation.assets[childFilename].source(); - - // Remove all chunk assets - compilation.chunks.forEach(chunk => { - chunk.files.forEach(file => { - delete compilation.assets[file]; // eslint-disable-line no-param-reassign - }); - }); - }); - - const callback = this.async(); - childCompiler.runAsChild((err, entries, compilation) => { - if (err) return callback(err); - - if (compilation.errors.length > 0) { - return callback(compilation.errors[0]); - } - compilation.fileDependencies.forEach(dep => { - this.addDependency(dep); - }, this); - compilation.contextDependencies.forEach(dep => { - this.addContextDependency(dep); - }, this); - if (!source) { - return callback(new Error("Didn't get a result from child compiler")); - } - let text; - let locals; - try { - text = exec(this, source, request); - locals = text && text.locals; - if (!Array.isArray(text)) { - text = [[null, text]]; - } else { - text = text.map(line => { - const module = findModuleById(compilation.modules, line[0]); - return { - identifier: module.identifier(), - content: line[1], - media: line[2], - sourceMap: line[3] - }; - }); - } - this[MODULE_TYPE](text); - } catch (e) { - return callback(e); - } - let resultSource = `// extracted by ${pluginName}`; - if (locals && typeof resultSource !== 'undefined') { - resultSource += `\nmodule.exports = ${JSON.stringify(locals)};`; - } - - return callback(null, resultSource); - }); -} \ No newline at end of file From 11a96b6df7986c89342cecde0763b64e962b8161 Mon Sep 17 00:00:00 2001 From: jgillespie Date: Thu, 24 Jan 2019 10:56:31 -0700 Subject: [PATCH 12/16] feat: removed modified files for pull request --- package-lock.json | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ee7caa0..d3a4647e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5832,14 +5832,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5854,20 +5852,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5984,8 +5979,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5997,7 +5991,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6012,7 +6005,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6020,14 +6012,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -6046,7 +6036,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -6127,8 +6116,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -6140,7 +6128,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -6262,7 +6249,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", From fe35279f3a392150c03f0635c4617d3c71192770 Mon Sep 17 00:00:00 2001 From: jgillespie Date: Mon, 28 Jan 2019 08:14:30 -0700 Subject: [PATCH 13/16] feat: remove useRelPreload option and make default behavior if supported --- src/index.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 8e0287ec..bdaab69f 100644 --- a/src/index.js +++ b/src/index.js @@ -113,7 +113,6 @@ class MiniCssExtractPlugin { this.options = Object.assign( { filename: '[name].css', - useRelPreload: false, }, options ); @@ -325,9 +324,8 @@ class MiniCssExtractPlugin { } ); - const supportsPreload = this.options.useRelPreload - ? '(function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());' - : 'false;'; + const supportsPreload = + '(function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());'; return Template.asString([ source, '', From 04605a75b36bea1a7584cf8f00d948a7305d272b Mon Sep 17 00:00:00 2001 From: jgillespie Date: Mon, 28 Jan 2019 08:33:39 -0700 Subject: [PATCH 14/16] feat: add in new behavior to documentation --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 072cb1ff..51fcac94 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,14 @@ module.exports = { For long term caching use `filename: "[contenthash].css"`. Optionally add `[name]`. +#### rel="preload" support + +On browsers that support ``, then CSS links will be preloaded by default. +This is both to improve page load performance, and addresess Chrome Lighthouse SEO performance audits requiring the use of rel="preload" with asynchronous chunks. + +-[Lighthouse article](https://developers.google.com/web/tools/lighthouse/audits/preload) + + ### Media Query Plugin If you'd like to extract the media queries from the extracted CSS (so mobile users don't need to load desktop or tablet specific CSS anymore) you should use one of the following plugins: From 880f3873cfd9f53aec49c25c64bf4e78cbfee40d Mon Sep 17 00:00:00 2001 From: jgillespie Date: Mon, 13 Apr 2020 11:09:54 -0600 Subject: [PATCH 15/16] style: small fixes to get lint:prettier to pass --- README.md | 2 +- src/index.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index edb9c928..ba286d08 100644 --- a/README.md +++ b/README.md @@ -532,7 +532,7 @@ For long term caching use `filename: "[contenthash].css"`. Optionally add `[name #### rel="preload" support On browsers that support ``, then CSS links will be preloaded by default. -This is both to improve page load performance, and addresess Chrome Lighthouse SEO performance audits requiring the use of rel="preload" with asynchronous chunks. +This is both to improve page load performance, and addresess Chrome Lighthouse SEO performance audits requiring the use of rel="preload" with asynchronous chunks. -[Lighthouse article](https://developers.google.com/web/tools/lighthouse/audits/preload) diff --git a/src/index.js b/src/index.js index e004d435..37050b6d 100644 --- a/src/index.js +++ b/src/index.js @@ -388,9 +388,7 @@ class MiniCssExtractPlugin { 'if(supportsPreload) {', Template.indent([ 'var execLinkTag = document.createElement("link");', - `execLinkTag.href = ${ - mainTemplate.requireFn - }.p + ${linkHrefPath};`, + `execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`, 'execLinkTag.rel = "stylesheet";', 'execLinkTag.type = "text/css";', 'document.body.appendChild(execLinkTag);', From d2c5202ed7ee925cb6f28ee5bf94ee375a673788 Mon Sep 17 00:00:00 2001 From: jgillespie Date: Mon, 13 Apr 2020 15:14:24 -0600 Subject: [PATCH 16/16] test: tests and jest snapshots updated to reflect rel preload as default --- test/HMR.test.js | 3 ++- test/__snapshots__/HMR.test.js.snap | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/test/HMR.test.js b/test/HMR.test.js index be56e01d..da0203e7 100644 --- a/test/HMR.test.js +++ b/test/HMR.test.js @@ -27,7 +27,8 @@ describe('HMR', () => { jest.spyOn(Date, 'now').mockImplementation(() => 1479427200000); - document.head.innerHTML = ''; + document.head.innerHTML = + ''; document.body.innerHTML = ''; }); diff --git a/test/__snapshots__/HMR.test.js.snap b/test/__snapshots__/HMR.test.js.snap index 269260d0..7d1f41a1 100644 --- a/test/__snapshots__/HMR.test.js.snap +++ b/test/__snapshots__/HMR.test.js.snap @@ -2,7 +2,7 @@ exports[`HMR should handle error event 1`] = `"[HMR] css reload %s"`; -exports[`HMR should handle error event 2`] = `""`; +exports[`HMR should handle error event 2`] = `""`; exports[`HMR should reloads with # link href 1`] = `"[HMR] css reload %s"`; @@ -18,7 +18,7 @@ exports[`HMR should reloads with link without href 2`] = `""`; +exports[`HMR should reloads with locals 2`] = `""`; exports[`HMR should reloads with non http/https link href 1`] = `"[HMR] css reload %s"`; @@ -26,14 +26,14 @@ exports[`HMR should reloads with non http/https link href 2`] = `""`; +exports[`HMR should reloads with reloadAll option 2`] = `""`; exports[`HMR should works 1`] = `"[HMR] css reload %s"`; -exports[`HMR should works 2`] = `""`; +exports[`HMR should works 2`] = `""`; exports[`HMR should works with multiple updates 1`] = `"[HMR] css reload %s"`; -exports[`HMR should works with multiple updates 2`] = `""`; +exports[`HMR should works with multiple updates 2`] = `""`; -exports[`HMR should works with multiple updates 3`] = `""`; +exports[`HMR should works with multiple updates 3`] = `""`;