diff --git a/.babelrc b/.babelrc index b3bfd2cb..a64dde26 100644 --- a/.babelrc +++ b/.babelrc @@ -1,36 +1,35 @@ { - "presets": [ - [ - "env", - { - "useBuiltIns": true, - "targets": { - "node": "6.11.5", - "browsers": "cover 99.5%" - }, - "exclude": [ - "transform-async-to-generator", - "transform-regenerator" - ] - } - ] - ], - "plugins": [ - [ - "transform-object-rest-spread", - { - "useBuiltIns": true - } - ] - ], - "env": { - "test": { - "presets": [ - "env" - ], - "plugins": [ - "transform-object-rest-spread" - ] - } - } + "presets": [ + [ + "env", + { + "useBuiltIns": true, + "targets": { + "node": "6.9.0" + }, + "exclude": [ + "transform-async-to-generator", + "transform-regenerator" + ] + } + ] + ], + "plugins": [ + [ + "transform-object-rest-spread", + { + "useBuiltIns": true + } + ] + ], + "env": { + "test": { + "presets": [ + "env" + ], + "plugins": [ + "transform-object-rest-spread" + ] + } + } } diff --git a/.eslintrc.js b/.eslintrc.js index 41c10cb5..cdb41323 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,27 +1,16 @@ module.exports = { - parser: 'babel-eslint', - parserOptions: { - ecmaFeatures: { - generators: true, - experimentalObjectRestSpread: true - }, - sourceType: 'module', - allowImportExportEverywhere: false - }, - extends: ['airbnb'], - env: { - 'browser': true, + root: true, + plugins: ['prettier'], + extends: ['@webpack-contrib/eslint-config-webpack'], + globals: { + document: true }, rules: { - 'no-param-reassign': 0, - 'func-names': 0, - 'no-underscore-dangle': 0, - 'no-restricted-syntax': 0, - 'prefer-arrow-callback': 0, - 'prefer-destructuring': 0, - 'array-callback-return': 0, - 'prefer-template': 0, - 'class-methods-use-this': 0, - 'no-plusplus': 0 - } + 'prettier/prettier': [ + 'error', + { singleQuote: true, trailingComma: 'es5', arrowParens: 'always' }, + ], + 'class-methods-use-this': 'off', + 'no-undefined': 'off', + }, }; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..e537c8ad --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "es5", + "arrowParens": "always" +} diff --git a/.travis.yml b/.travis.yml index 07f5c876..0fef0925 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ node_js: script: - echo success after_success: - - npx semantic-release + - npm run semantic-release branches: except: - /^v\d+\.\d+\.\d+$/ diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 00000000..7346e51a --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,32 @@ +/* eslint-disable */ +const Configuration = { + extends: ['@commitlint/config-conventional'], + + rules: { + 'body-leading-blank': [1, 'always'], + 'footer-leading-blank': [1, 'always'], + 'header-max-length': [2, 'always', 72], + 'scope-case': [2, 'always', 'lower-case'], + 'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']], + 'subject-empty': [2, 'never'], + 'subject-full-stop': [2, 'never', '.'], + 'type-case': [2, 'always', 'lower-case'], + 'type-empty': [2, 'never'], + 'type-enum': [2, 'always', [ + 'build', + 'chore', + 'ci', + 'docs', + 'feat', + 'fix', + 'perf', + 'refactor', + 'revert', + 'style', + 'test', + ], + ], + }, +}; + +module.exports = Configuration; diff --git a/jest.config.js b/jest.config.js index db31d51e..13ca2c50 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,13 +1,5 @@ module.exports = { - testPathIgnorePatterns: [ - '/TO_DELETE_test/', - ], - transformIgnorePatterns: [ - '/node_modules/', - '/dist/', - ], - watchPathIgnorePatterns: [ - '/test/js', - ], + testURL: 'http://localhost/', + transformIgnorePatterns: ['/node_modules/', '/dist/'], + watchPathIgnorePatterns: ['/test/js'], }; - diff --git a/package.json b/package.json index 4f141882..72d34c5f 100644 --- a/package.json +++ b/package.json @@ -46,52 +46,48 @@ "ci:coverage": "npm run test:coverage -- --runInBand", "build:example": "npm run build && webpack-dev-server test/manual/src/index.js --open --config test/manual/webpack.config.js", "cm": "git-cz", - "release": "standard-version", - "semantic-release": "semantic-release pre && npm publish && semantic-release post", - "travis": "npm run ci:coverage" + "semantic-release": "npx semantic-release pre && npm publish && npx semantic-release post", + "travis": "npm run ci:coverage", + "defaults": "webpack-defaults" }, "peerDependencies": { - "webpack": "^4.8.3" + "webpack": "^4.4.0" }, "dependencies": { "loader-utils": "^1.1.0", - "lodash": "^4.17.5", - "normalize-url": "^1.9.1", + "lodash": "^4.17.11", + "normalize-url": "^4.1.0", + "schema-utils": "^1.0.0", "webpack-sources": "^1.1.0" }, "devDependencies": { + "@commitlint/cli": "^6.1.3", + "@commitlint/config-conventional": "^6.1.3", "@webpack-contrib/eslint-config-webpack": "^2.0.4", "babel-cli": "^6.26.0", - "babel-eslint": "^7.2.3", "babel-jest": "^22.2.2", - "babel-loader": "^7.1.4", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-polyfill": "^6.26.0", "babel-preset-env": "^1.6.1", - "commitizen": "^2.10.1", "conventional-github-releaser": "^2.0.2", "cross-env": "^5.1.3", "css-loader": "^0.28.10", - "cz-conventional-changelog": "^2.1.0", "del": "^3.0.0", "del-cli": "^1.1.0", - "eslint": "^3.19.0", - "eslint-config-airbnb": "^15.0.1", - "eslint-plugin-flowtype": "^2.32.1", + "eslint": "^4.17.0", "eslint-plugin-import": "^2.8.0", - "eslint-plugin-jsx-a11y": "^5.0.3", "eslint-plugin-prettier": "^2.6.0", - "eslint-plugin-react": "^7.0.1", + "file-loader": "^1.1.11", "husky": "^0.14.3", "jest": "^22.2.2", + "lint-staged": "^6.1.0", "memory-fs": "^0.4.1", - "nsp": "^3.1.0", "pre-commit": "^1.2.2", "prettier": "^1.11.1", - "standard-version": "^4.4.0", "semantic-release": "^6.3.2", - "webpack": "4.8.3", + "webpack": "^4.14.0", "webpack-cli": "^2.0.13", + "webpack-defaults": "^2.3.0", "webpack-dev-server": "^3.1.1" }, "homepage": "http://github.com/faceyspacey/extract-css-chunks-webpack-plugin", diff --git a/src/hotLoader.js b/src/hotLoader.js index c7c0364c..e4e9471b 100644 --- a/src/hotLoader.js +++ b/src/hotLoader.js @@ -1,24 +1,29 @@ const path = require('path'); + const loaderUtils = require('loader-utils'); const defaultOptions = { - fileMap: '{fileName}', - cssModules: true, + fileMap: '{fileName}', }; -module.exports = function (content) { - this.cacheable(); - const options = Object.assign( +module.exports = function(content) { + this.cacheable(); + const options = Object.assign( {}, defaultOptions, - loaderUtils.getOptions(this), + loaderUtils.getOptions(this) ); - const accept = options.cssModules ? '' : 'module.hot.accept(undefined, cssReload);'; - return content + ` + const accept = options.cssModules + ? '' + : 'module.hot.accept(undefined, cssReload);'; + return `${content} if(module.hot) { // ${Date.now()} - var cssReload = require(${loaderUtils.stringifyRequest(this, path.join(__dirname, 'hotModuleReplacement.js'))})(module.id, ${JSON.stringify(options)}); + var cssReload = require(${loaderUtils.stringifyRequest( + this, + path.join(__dirname, 'hotModuleReplacement.js') + )})(module.id, ${JSON.stringify(options)}); module.hot.dispose(cssReload); ${accept}; } diff --git a/src/hotModuleReplacement.js b/src/hotModuleReplacement.js index 9becca5c..0b6aa04c 100644 --- a/src/hotModuleReplacement.js +++ b/src/hotModuleReplacement.js @@ -6,121 +6,128 @@ const debounce = require('lodash/debounce'); const noDocument = typeof document === 'undefined'; const forEach = Array.prototype.forEach; -const noop = function () {}; - -const getCurrentScriptUrl = function (moduleId) { - let src = srcByModuleId[moduleId]; - - if (!src) { - if (document.currentScript) { - src = document.currentScript.src; - } else { - const scripts = document.getElementsByTagName('script'); - const lastScriptTag = scripts[scripts.length - 1]; - - if (lastScriptTag) { - src = lastScriptTag.src; - } +const noop = function() {}; + +const getCurrentScriptUrl = function(moduleId) { + let src = srcByModuleId[moduleId]; + + if (!src) { + if (document.currentScript) { + src = document.currentScript.src; + } else { + const scripts = document.getElementsByTagName('script'); + const lastScriptTag = scripts[scripts.length - 1]; + + if (lastScriptTag) { + src = lastScriptTag.src; + } + } + srcByModuleId[moduleId] = src; } - srcByModuleId[moduleId] = src; - } - - return function (fileMap) { - const splitResult = /([^\\/]+)\.js$/.exec(src); - const filename = splitResult && splitResult[1]; - if (!filename) { - return [src.replace('.js', '.css')]; - } - return fileMap.split(',').map(function (mapRule) { - const reg = new RegExp(filename + '\\.js$', 'g'); - return normalizeUrl(src.replace(reg, mapRule.replace(/{fileName}/g, filename) + '.css'), { stripWWW: false }); - }); - }; + + return function(fileMap) { + const splitResult = /([^\\/]+)\.js$/.exec(src); + const filename = splitResult && splitResult[1]; + if (!filename) { + return [src.replace('.js', '.css')]; + } + return fileMap.split(',').map((mapRule) => { + const reg = new RegExp(`${filename}\\.js$`, 'g'); + return normalizeUrl( + src.replace( + reg, + `${mapRule.replace(/{fileName}/g, filename)}.css` + ), + { stripWWW: false } + ); + }); + }; }; function updateCss(el, url) { - if (!url) { - url = el.href.split('?')[0]; - } - if (el.isLoaded === false) { + if (!url) { + url = el.href.split('?')[0]; + } + if (el.isLoaded === false) { // We seem to be about to replace a css link that hasn't loaded yet. // We're probably changing the same file more than once. - return; - } - if (!url || !(url.indexOf('.css') > -1)) return; + return; + } + if (!url || !(url.indexOf('.css') > -1)) return; - el.visited = true; - const newEl = el.cloneNode(); + el.visited = true; + const newEl = el.cloneNode(); - newEl.isLoaded = false; + newEl.isLoaded = false; - newEl.addEventListener('load', function () { - newEl.isLoaded = true; - el.parentNode.removeChild(el); - }); + newEl.addEventListener('load', () => { + newEl.isLoaded = true; + el.parentNode.removeChild(el); + }); - newEl.addEventListener('error', function () { - newEl.isLoaded = true; - el.parentNode.removeChild(el); - }); + newEl.addEventListener('error', () => { + newEl.isLoaded = true; + el.parentNode.removeChild(el); + }); - newEl.href = url + '?' + Date.now(); - el.parentNode.appendChild(newEl); + newEl.href = `${url}?${Date.now()}`; + el.parentNode.appendChild(newEl); } function getReloadUrl(href, src) { - href = normalizeUrl(href, { stripWWW: false }); - let ret; - src.some(function (url) { - if (href.indexOf(src) > -1) { - ret = url; - } - }); - return ret; + href = normalizeUrl(href, { stripWWW: false }); + let ret; + // eslint-disable-next-line array-callback-return + src.some((url) => { + if (href.indexOf(src) > -1) { + ret = url; + } + }); + return ret; } function reloadStyle(src) { - const elements = document.querySelectorAll('link'); - let loaded = false; + const elements = document.querySelectorAll('link'); + let loaded = false; - forEach.call(elements, function (el) { - if (el.visited === true) return; + forEach.call(elements, (el) => { + if (el.visited === true) return; - const url = getReloadUrl(el.href, src); - if (url) { - updateCss(el, url); - loaded = true; - } - }); + const url = getReloadUrl(el.href, src); + if (url) { + updateCss(el, url); + loaded = true; + } + }); - return loaded; + return loaded; } function reloadAll() { - const elements = document.querySelectorAll('link'); - forEach.call(elements, function (el) { - if (el.visited === true) return; - updateCss(el); - }); + const elements = document.querySelectorAll('link'); + forEach.call(elements, (el) => { + if (el.visited === true) return; + updateCss(el); + }); } -module.exports = function (moduleId, options) { - if (noDocument) { - return noop; - } - - const getScriptSrc = getCurrentScriptUrl(moduleId); - - function update() { - const src = getScriptSrc(options.fileMap); - const reloaded = reloadStyle(src); - if (reloaded && !options.reloadAll) { - console.log('[HMR] css reload %s', src.join(' ')); - } else { - console.log('[HMR] Reload all css'); - reloadAll(); +module.exports = function(moduleId, options) { + if (noDocument) { + return noop; + } + + const getScriptSrc = getCurrentScriptUrl(moduleId); + + function update() { + const src = getScriptSrc(options.fileMap); + const reloaded = reloadStyle(src); + if (reloaded && !options.reloadAll) { + console.log('[HMR] css reload %s', src.join(' ')); + } else { + console.log('[HMR] Reload all css'); + reloadAll(); + } } - } - return debounce(update, 10); + return debounce(update, 10); }; diff --git a/src/index.js b/src/index.js index e2a77449..dfaab46c 100644 --- a/src/index.js +++ b/src/index.js @@ -20,591 +20,708 @@ const REGEXP_CONTENTHASH = /\[contenthash(?::(\d+))?\]/i; const REGEXP_NAME = /\[name\]/i; const isHMR = (compiler) => { - if (compiler && compiler.options) { - if (compiler.options.devServer && compiler.options.devServer.hot) { - return true; - } + if (compiler && compiler.options) { + if (compiler.options.devServer && compiler.options.devServer.hot) { + return true; + } - if (compiler.options.entry) { - const entry = typeof compiler.options.entry === 'function' ? compiler.options.entry() : compiler.options.entry; - const entryString = JSON.stringify(entry); - return entryString.includes('hot') || entryString.includes('hmr'); + if (compiler.options.entry) { + const entry = + typeof compiler.options.entry === 'function' + ? compiler.options.entry() + : compiler.options.entry; + const entryString = JSON.stringify(entry); + return entryString.includes('hot') || entryString.includes('hmr'); + } } - } - return false; + return false; }; class CssDependency extends webpack.Dependency { - constructor( - { identifier, content, media, sourceMap }, - context, - identifierIndex, + 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}`; - } + 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() {} + apply() {} } class CssModule extends webpack.Module { - constructor(dependency) { - super(MODULE_TYPE, dependency.context); - this._identifier = dependency.identifier; - this._identifierIndex = dependency.identifierIndex; - this.content = dependency.content; - this.media = dependency.media; - this.sourceMap = dependency.sourceMap; - } + 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; - } + size() { + return this.content.length; + } - identifier() { - return `css ${this._identifier} ${this._identifierIndex}`; - } + identifier() { + return `css ${this._identifier} ${this._identifierIndex}`; + } - readableIdentifier(requestShortener) { - return `css ${requestShortener.shorten(this._identifier)}${ + 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(JSON.stringify(this.sourceMap || '')); - } + }`; + } + + 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( - { + create( + { dependencies: [dependency], }, - callback, + callback ) { - callback(null, new CssModule(dependency)); - } + callback(null, new CssModule(dependency)); + } } class ExtractCssChunks { - constructor(options) { - this.options = Object.assign({ filename: '[name].css', orderWarning: true }, options); - const { cssModules, reloadAll } = this.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'); - } - } + constructor(options) { + this.options = Object.assign( + { + filename: '[name].css', + }, + options + ); + const { cssModules, reloadAll } = this.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' + ); + } + } - this.hotLoaderObject = Object.assign({ - loader: hotLoader, - options: { - cssModules: false, - reloadAll: false, - }, - }, { - options: { - cssModules, - reloadAll, - }, - }); - } - - apply(compiler) { - try { - const isHOT = this.options.hot ? true : isHMR(compiler); - - if (isHOT && compiler.options.module && compiler.options.module.rules) { - compiler.options.module.rules = this.updateWebpackConfig(compiler.options.module.rules); - } - } catch (e) { - throw new Error(`Something went wrong: contact the author: ${JSON.stringify(e)}`); + this.hotLoaderObject = Object.assign( + { + loader: hotLoader, + options: { + cssModules: false, + reloadAll: false, + }, + }, + { + options: { + cssModules, + reloadAll, + }, + } + ); } - 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( + apply(compiler) { + try { + const isHOT = this.options.hot ? true : isHMR(compiler); + + if ( + isHOT && + compiler.options.module && + compiler.options.module.rules + ) { + compiler.options.module.rules = this.updateWebpackConfig( + compiler.options.module.rules + ); + } + } catch (e) { + throw new Error( + `Something went wrong: contact the author: ${JSON.stringify(e)}` + ); + } + + 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(), + new CssModuleFactory() ); - compilation.dependencyTemplates.set( + compilation.dependencyTemplates.set( CssDependency, - new CssDependencyTemplate(), + new CssDependencyTemplate() ); - compilation.mainTemplate.hooks.renderManifest.tap( + 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: () => + 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, + compilation.runtimeTemplate.requestShortener ), - filenameTemplate: this.options.filename, - pathOptions: { - chunk, - contentHashType: MODULE_TYPE, - }, - identifier: `${pluginName}.${chunk.id}`, - hash: chunk.contentHash[MODULE_TYPE], - }); - } - }, + filenameTemplate: this.options.filename, + pathOptions: { + chunk, + contentHashType: MODULE_TYPE, + }, + identifier: `${pluginName}.${chunk.id}`, + hash: chunk.contentHash[MODULE_TYPE], + }); + } + } ); - compilation.chunkTemplate.hooks.renderManifest.tap( + 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: () => + 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, + compilation.runtimeTemplate.requestShortener ), - filenameTemplate: this.options.chunkFilename, - pathOptions: { - chunk, - contentHashType: MODULE_TYPE, - }, - identifier: `${pluginName}.${chunk.id}`, - hash: chunk.contentHash[MODULE_TYPE], - }); - } - }, + filenameTemplate: this.options.chunkFilename, + pathOptions: { + chunk, + contentHashType: MODULE_TYPE, + }, + identifier: `${pluginName}.${chunk.id}`, + hash: chunk.contentHash[MODULE_TYPE], + }); + } + } ); - compilation.mainTemplate.hooks.hashForChunk.tap( + 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] || {}), + const { chunkFilename } = this.options; + if (REGEXP_CHUNKHASH.test(chunkFilename)) { + hash.update( + JSON.stringify(chunk.getChunkMaps(true).hash) ); - } - if (REGEXP_NAME.test(chunkFilename)) { - hash.update(JSON.stringify(chunk.getChunkMaps(true).name)); - } - }, + } + 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 + 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( + }); + 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 linkHrefPath = mainTemplate.getAssetPath( + 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[ + { + 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 - ].substring(0, length); - } - } - return `" + ${JSON.stringify( - shortChunkHashMap, + ] === 'string' + ) { + shortChunkHashMap[ + chunkId + ] = chunkMaps.hash[ + chunkId + ].substring(0, length); + } + } + return `" + ${JSON.stringify( + shortChunkHashMap )}[chunkId] + "`; - }, - contentHash: { - [MODULE_TYPE]: `" + ${JSON.stringify( - chunkMaps.contentHash[MODULE_TYPE], + }, + 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[ + }, + 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, + ].substring(0, length); + } + } + return `" + ${JSON.stringify( + shortContentHashMap )}[chunkId] + "`; - }, - }, - name: `" + (${JSON.stringify( - chunkMaps.name, + }, + }, + name: `" + (${JSON.stringify( + chunkMaps.name )}[chunkId]||chunkId) + "`, - }, - contentHashType: MODULE_TYPE, - }, + }, + 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" && (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);', - ]), - '}).then(function() {', - Template.indent(['installedCssChunks[chunkId] = 0;']), - '}));', - ]), - '}', - ]); - } - return source; - }, + 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" && (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;', + '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;', + ]), + '}));', + ]), + '}', + ]); + } + return source; + } ); - }); - } - - traverseDepthFirst(root, visit) { - let nodesToVisit = [root]; + }); + } - while (nodesToVisit.length > 0) { - const currentNode = nodesToVisit.shift(); + traverseDepthFirst(root, visit) { + let nodesToVisit = [root]; - if (currentNode !== null && typeof currentNode === 'object') { - const children = Object.values(currentNode); - nodesToVisit = [...children, ...nodesToVisit]; - } + while (nodesToVisit.length > 0) { + const currentNode = nodesToVisit.shift(); - visit(currentNode); - } - } - - updateWebpackConfig(rulez) { - return rulez.reduce((rules, rule) => { - this.traverseDepthFirst(rule, (node) => { - if (node && node.use && Array.isArray(node.use)) { - const isMiniCss = node.use.some((l) => { - const needle = l.loader || l; - if (typeof l === 'function') { - return false; + if (currentNode !== null && typeof currentNode === 'object') { + const children = Object.values(currentNode); + nodesToVisit = [...children, ...nodesToVisit]; } - return needle.includes(pluginName); - }); - if (isMiniCss) { - node.use.unshift(this.hotLoaderObject); - } - } - if (node && node.loader && Array.isArray(node.loader)) { - const isMiniCss = node.loader.some((l) => { - const needle = l.loader || l; - if (typeof l === 'function') { - return false; - } - return needle.includes(pluginName); - }); - if (isMiniCss) { - node.loader.unshift(this.hotLoaderObject); - } + + visit(currentNode); } - }); + } + + updateWebpackConfig(rulez) { + return rulez.reduce((rules, rule) => { + this.traverseDepthFirst(rule, (node) => { + if (node && node.use && Array.isArray(node.use)) { + const isExtractCss = node.use.some((l) => { + const needle = l.loader || l; + if (typeof l === 'function') { + return false; + } + return needle.includes(pluginName); + }); + if (isExtractCss) { + node.use.unshift(this.hotLoaderObject); + } + } + if (node && node.loader && Array.isArray(node.loader)) { + const isExtractCss = node.loader.some((l) => { + const needle = l.loader || l; + if (typeof l === 'function') { + return false; + } + return needle.includes(pluginName); + }); + if (isExtractCss) { + node.loader.unshift(this.hotLoaderObject); + } + } + }); - rules.push(rule); + rules.push(rule); - return rules; - }, []); - } + return rules; + }, []); + } - 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; + 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; } - return obj; - } - renderContentAsset(compilation, chunk, modules, requestShortener) { - let usedModules; + renderContentAsset(compilation, chunk, modules, requestShortener) { + let usedModules; - const [chunkGroup] = chunk.groupsIterable; - if (typeof chunkGroup.getModuleIndex2 === 'function') { + const [chunkGroup] = chunk.groupsIterable; + if (typeof chunkGroup.getModuleIndex2 === 'function') { // Store dependencies for modules - const moduleDependencies = new Map(modules.map(m => [m, new Set()])); + 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 => ({ - 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; - }); + 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(); + usedModules = new Set(); - const unusedModulesFilter = m => !usedModules.has(m); + const unusedModulesFilter = (m) => !usedModules.has(m); - while (usedModules.size < modules.length) { - let success = false; - let bestMatch; - let bestMatchDeps; + while (usedModules.size < modules.length) { + let success = false; + let bestMatch; + let bestMatchDeps; // get first module where dependencies are fulfilled - for (const list of modulesByChunkGroup) { + for (const list of modulesByChunkGroup) { // skip and remove already added modules - while (list.length > 0 && usedModules.has(list[list.length - 1])) { list.pop(); } + 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); + 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); + 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) { + 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) { + 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(); - if (this.options.orderWarning) { - 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 { + const fallbackModule = bestMatch.pop(); + compilation.warnings.push( + new Error( + `chunk ${chunk.name || + chunk.id} [extract-css-chunks-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)) { + 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) { + 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( + 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, - ), + m.sourceMap + ) ); - } else { - source.add( + } else { + source.add( new OriginalSource( m.content, - m.readableIdentifier(requestShortener), - ), + m.readableIdentifier(requestShortener) + ) ); + } + source.add('\n'); + if (m.media) { + source.add('}\n'); + } + } } - source.add('\n'); - if (m.media) { - source.add('}\n'); - } - } + return new ConcatSource(externalsSource, source); } - return new ConcatSource(externalsSource, source); - } } ExtractCssChunks.loader = require.resolve('./loader'); diff --git a/src/loader.js b/src/loader.js index f5d0b97d..94627edd 100644 --- a/src/loader.js +++ b/src/loader.js @@ -11,127 +11,130 @@ const MODULE_TYPE = 'css/extract-chunks'; const pluginName = 'extract-css-chunks-webpack-plugin'; const exec = (loaderContext, code, filename) => { - const module = new NativeModule(filename, loaderContext); - // eslint-disable-line no-underscore-dangle - module.paths = NativeModule._nodeModulePaths(loaderContext.context); - module.filename = filename; - module._compile(code, filename); // eslint-disable-line no-underscore-dangle - return module.exports; + const module = new NativeModule(filename, loaderContext); + module.paths = NativeModule._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; + for (const module of modules) { + if (module.id === id) { + return module; + } } - } - return null; + return null; }; export function pitch(request) { - const query = loaderUtils.getOptions(this) || {}; - const loaders = this.loaders.slice(this.loaderIndex + 1); - this.addDependency(this.resourcePath); - const childFilename = '*'; // eslint-disable-line no-path-concat - const publicPath = + const query = loaderUtils.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( + const outputOptions = { + filename: childFilename, + publicPath, + }; + const childCompiler = this._compilation.createChildCompiler( `${pluginName} ${request}`, - outputOptions, + outputOptions ); - new NodeTemplatePlugin(outputOptions).apply(childCompiler); - new LibraryTemplatePlugin(null, 'commonjs2').apply(childCompiler); - new NodeTargetPlugin().apply(childCompiler); - new SingleEntryPlugin(this.context, `!!${request}`, pluginName).apply( - childCompiler, + new NodeTemplatePlugin(outputOptions).apply(childCompiler); + new LibraryTemplatePlugin(null, 'commonjs2').apply(childCompiler); + new NodeTargetPlugin().apply(childCompiler); + new SingleEntryPlugin(this.context, `!!${request}`, pluginName).apply( + childCompiler ); - new LimitChunkCountPlugin({ maxChunks: 1 }).apply(childCompiler); + new LimitChunkCountPlugin({ 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( + childCompiler.hooks.thisCompilation.tap( `${pluginName} loader`, (compilation) => { - compilation.hooks.normalModuleLoader.tap( + compilation.hooks.normalModuleLoader.tap( `${pluginName} loader`, (loaderContext, module) => { - loaderContext[MODULE_TYPE] = false; // eslint-disable-line no-param-reassign - if (module.request === request) { + 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 => ({ - loader: loader.path, - options: loader.options, - ident: loader.ident, - })); - } - }, + module.loaders = loaders.map((loader) => { + return { + loader: loader.path, + options: loader.options, + ident: loader.ident, + }; + }); + } + } ); - }, + } ); - let source; - childCompiler.hooks.afterCompile.tap(pluginName, (compilation) => { - source = + 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 - }); + 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); + 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)};`; - } + 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); - }); + return callback(null, resultSource); + }); } -export default function () {} +export default function() {} diff --git a/test/TestCases.test.js b/test/TestCases.test.js index 4a133bc1..ee4cd52c 100644 --- a/test/TestCases.test.js +++ b/test/TestCases.test.js @@ -1,7 +1,6 @@ -/* eslint-disable no-restricted-syntax, import/no-dynamic-require, global-require, - no-console, no-undef, no-loop-func */ import fs from 'fs'; import path from 'path'; + import webpack from 'webpack'; describe('TestCases', () => { @@ -9,12 +8,30 @@ describe('TestCases', () => { const outputDirectory = path.resolve(__dirname, 'js'); for (const directory of fs.readdirSync(casesDirectory)) { if (!/^(\.|_)/.test(directory)) { + // eslint-disable-next-line no-loop-func it(`${directory} should compile to the expected result`, (done) => { const directoryForCase = path.resolve(casesDirectory, directory); const outputDirectoryForCase = path.resolve(outputDirectory, directory); - const webpackConfig = require(path.resolve(directoryForCase, 'webpack.config.js')); + // eslint-disable-next-line import/no-dynamic-require, global-require + const webpackConfig = require(path.resolve( + directoryForCase, + 'webpack.config.js' + )); for (const config of [].concat(webpackConfig)) { - Object.assign(config, { mode: 'none', context: directoryForCase, output: Object.assign({ path: outputDirectoryForCase }, config.output) }, config); + Object.assign( + config, + { + mode: 'none', + context: directoryForCase, + output: Object.assign( + { + path: outputDirectoryForCase, + }, + config.output + ), + }, + config + ); } webpack(webpackConfig, (err, stats) => { if (err) { @@ -22,15 +39,36 @@ describe('TestCases', () => { return; } done(); - console.log(stats.toString({ context: path.resolve(__dirname, '..'), chunks: true, chunkModules: true, modules: false })); + // eslint-disable-next-line no-console + console.log( + stats.toString({ + context: path.resolve(__dirname, '..'), + chunks: true, + chunkModules: true, + modules: false, + }) + ); if (stats.hasErrors()) { - done(new Error(stats.toString({ context: path.resolve(__dirname, '..'), errorDetails: true }))); + done( + new Error( + stats.toString({ + context: path.resolve(__dirname, '..'), + errorDetails: true, + }) + ) + ); return; } const expectedDirectory = path.resolve(directoryForCase, 'expected'); for (const file of fs.readdirSync(expectedDirectory)) { - const content = fs.readFileSync(path.resolve(expectedDirectory, file), 'utf-8'); - const actualContent = fs.readFileSync(path.resolve(outputDirectoryForCase, file), 'utf-8'); + const content = fs.readFileSync( + path.resolve(expectedDirectory, file), + 'utf-8' + ); + const actualContent = fs.readFileSync( + path.resolve(outputDirectoryForCase, file), + 'utf-8' + ); expect(actualContent).toEqual(content); } done(); diff --git a/test/TestMemoryFS.test.js b/test/TestMemoryFS.test.js new file mode 100644 index 00000000..635b2aef --- /dev/null +++ b/test/TestMemoryFS.test.js @@ -0,0 +1,40 @@ +import path from 'path'; + +import MemoryFS from 'memory-fs'; +import webpack from 'webpack'; + +const assetsNames = (json) => json.assets.map((asset) => asset.name); + +describe('TestMemoryFS', () => { + it('should preserve asset even if not emitted', (done) => { + const casesDirectory = path.resolve(__dirname, 'cases'); + const directoryForCase = path.resolve(casesDirectory, 'simple-publicpath'); + const webpackConfig = require(path.resolve( + directoryForCase, + 'webpack.config.js' + )); + const compiler = webpack({ + ...webpackConfig, + mode: 'development', + context: directoryForCase, + cache: false, + }); + compiler.outputFileSystem = new MemoryFS(); + compiler.run((err1, stats1) => { + if (err1) { + done(err1); + return; + } + compiler.run((err2, stats2) => { + if (err2) { + done(err2); + return; + } + expect(assetsNames(stats1.toJson())).toEqual( + assetsNames(stats2.toJson()) + ); + done(); + }); + }); + }); +}); diff --git a/test/cases/at-import/a.css b/test/cases/at-import/a.css new file mode 100644 index 00000000..34ce1e9c --- /dev/null +++ b/test/cases/at-import/a.css @@ -0,0 +1,9 @@ +@import './ae.css'; +@import './aa.css'; +@import './ab.css'; +@import './ac.css'; +@import './ad.css'; + +body { + background: red; +} diff --git a/test/cases/at-import/aa.css b/test/cases/at-import/aa.css new file mode 100644 index 00000000..9ad34057 --- /dev/null +++ b/test/cases/at-import/aa.css @@ -0,0 +1,3 @@ +.aa { + background: green; +} diff --git a/test/cases/at-import/ab.css b/test/cases/at-import/ab.css new file mode 100644 index 00000000..a57f2508 --- /dev/null +++ b/test/cases/at-import/ab.css @@ -0,0 +1,3 @@ +.ab { + background: green; +} diff --git a/test/cases/at-import/ac.css b/test/cases/at-import/ac.css new file mode 100644 index 00000000..356d5138 --- /dev/null +++ b/test/cases/at-import/ac.css @@ -0,0 +1,3 @@ +.ac { + background: green; +} diff --git a/test/cases/at-import/ad.css b/test/cases/at-import/ad.css new file mode 100644 index 00000000..1323e35d --- /dev/null +++ b/test/cases/at-import/ad.css @@ -0,0 +1,3 @@ +.ad { + background: green; +} diff --git a/test/cases/at-import/ae.css b/test/cases/at-import/ae.css new file mode 100644 index 00000000..5eca6b0e --- /dev/null +++ b/test/cases/at-import/ae.css @@ -0,0 +1,3 @@ +.ae { + background: green; +} diff --git a/test/cases/at-import/b.css b/test/cases/at-import/b.css new file mode 100644 index 00000000..e300b655 --- /dev/null +++ b/test/cases/at-import/b.css @@ -0,0 +1,6 @@ +@import './ba.css'; +@import './bb.css'; + +body { + background: yellow; +} diff --git a/test/cases/at-import/ba.css b/test/cases/at-import/ba.css new file mode 100644 index 00000000..adb84dbe --- /dev/null +++ b/test/cases/at-import/ba.css @@ -0,0 +1,3 @@ +.ba { + background: green; +} diff --git a/test/cases/at-import/bb.css b/test/cases/at-import/bb.css new file mode 100644 index 00000000..1e09c7ac --- /dev/null +++ b/test/cases/at-import/bb.css @@ -0,0 +1,3 @@ +.bb { + background: green; +} diff --git a/test/cases/at-import/expected/main.css b/test/cases/at-import/expected/main.css new file mode 100644 index 00000000..eb4811b4 --- /dev/null +++ b/test/cases/at-import/expected/main.css @@ -0,0 +1,36 @@ +.ae { + background: green; +} + +.aa { + background: green; +} + +.ab { + background: green; +} + +.ac { + background: green; +} + +.ad { + background: green; +} + +body { + background: red; +} + +.ba { + background: green; +} + +.bb { + background: green; +} + +body { + background: yellow; +} + diff --git a/test/cases/at-import/index.js b/test/cases/at-import/index.js new file mode 100644 index 00000000..a0ec7954 --- /dev/null +++ b/test/cases/at-import/index.js @@ -0,0 +1,2 @@ +import './a.css'; +import './b.css'; diff --git a/test/cases/at-import/webpack.config.js b/test/cases/at-import/webpack.config.js new file mode 100644 index 00000000..a22ebdad --- /dev/null +++ b/test/cases/at-import/webpack.config.js @@ -0,0 +1,21 @@ +const Self = require('../../../'); + +module.exports = { + entry: './index.js', + module: { + rules: [ + { + test: /\.css$/, + use: [ + Self.loader, + 'css-loader', + ], + }, + ], + }, + plugins: [ + new Self({ + filename: '[name].css', + }), + ], +}; diff --git a/test/cases/composes-async/expected/1.css b/test/cases/composes-async/expected/1.css index 2fa9e2cb..23f2a9f7 100644 --- a/test/cases/composes-async/expected/1.css +++ b/test/cases/composes-async/expected/1.css @@ -1,4 +1,4 @@ -.base { - background: blue; +.composed { + background: green; } diff --git a/test/cases/composes-async/expected/2.css b/test/cases/composes-async/expected/2.css new file mode 100644 index 00000000..2fa9e2cb --- /dev/null +++ b/test/cases/composes-async/expected/2.css @@ -0,0 +1,4 @@ +.base { + background: blue; +} + diff --git a/test/cases/composes-async/webpack.config.js b/test/cases/composes-async/webpack.config.js index 33c90863..ef38e7f6 100644 --- a/test/cases/composes-async/webpack.config.js +++ b/test/cases/composes-async/webpack.config.js @@ -11,9 +11,9 @@ module.exports = { { loader: 'css-loader', options: { - localIdentName: '[local]', - }, - }, + localIdentName: '[local]' + } + } ], }, ], @@ -25,10 +25,10 @@ module.exports = { test: /\.css$/, chunks: 'all', minChunks: 2, - enforce: true, - }, - }, - }, + enforce: true + } + } + } }, plugins: [ new Self({ diff --git a/test/cases/contenthash-multiple-entries/entryA.js b/test/cases/contenthash-multiple-entries/entryA.js new file mode 100644 index 00000000..c1845fd4 --- /dev/null +++ b/test/cases/contenthash-multiple-entries/entryA.js @@ -0,0 +1,2 @@ +import "./styleA.css"; +import "./styleB.css"; diff --git a/test/cases/contenthash-multiple-entries/entryB.js b/test/cases/contenthash-multiple-entries/entryB.js new file mode 100644 index 00000000..e5558621 --- /dev/null +++ b/test/cases/contenthash-multiple-entries/entryB.js @@ -0,0 +1 @@ +import "./styleA.css"; diff --git a/test/cases/contenthash-multiple-entries/entryC.js b/test/cases/contenthash-multiple-entries/entryC.js new file mode 100644 index 00000000..e69de29b diff --git a/test/cases/contenthash-multiple-entries/entryD.js b/test/cases/contenthash-multiple-entries/entryD.js new file mode 100644 index 00000000..c1845fd4 --- /dev/null +++ b/test/cases/contenthash-multiple-entries/entryD.js @@ -0,0 +1,2 @@ +import "./styleA.css"; +import "./styleB.css"; diff --git a/test/cases/contenthash-multiple-entries/entryE.js b/test/cases/contenthash-multiple-entries/entryE.js new file mode 100644 index 00000000..11bc2609 --- /dev/null +++ b/test/cases/contenthash-multiple-entries/entryE.js @@ -0,0 +1,2 @@ +import "./styleC.css"; +import "./styleD.css"; diff --git a/test/cases/contenthash-multiple-entries/expected/10adcc6fa8f8a3cf8d8d.css b/test/cases/contenthash-multiple-entries/expected/10adcc6fa8f8a3cf8d8d.css new file mode 100644 index 00000000..7dad92ee --- /dev/null +++ b/test/cases/contenthash-multiple-entries/expected/10adcc6fa8f8a3cf8d8d.css @@ -0,0 +1,4 @@ +.styleA { background: red; } + +.styleB { background: blue; } + diff --git a/test/cases/contenthash-multiple-entries/expected/3023a4e1067adc5b1950.css b/test/cases/contenthash-multiple-entries/expected/3023a4e1067adc5b1950.css new file mode 100644 index 00000000..f9171ff1 --- /dev/null +++ b/test/cases/contenthash-multiple-entries/expected/3023a4e1067adc5b1950.css @@ -0,0 +1,2 @@ +.styleA { background: red; } + diff --git a/test/cases/contenthash-multiple-entries/styleA.css b/test/cases/contenthash-multiple-entries/styleA.css new file mode 100644 index 00000000..84a728d8 --- /dev/null +++ b/test/cases/contenthash-multiple-entries/styleA.css @@ -0,0 +1 @@ +.styleA { background: red; } diff --git a/test/cases/contenthash-multiple-entries/styleB.css b/test/cases/contenthash-multiple-entries/styleB.css new file mode 100644 index 00000000..3662427b --- /dev/null +++ b/test/cases/contenthash-multiple-entries/styleB.css @@ -0,0 +1 @@ +.styleB { background: blue; } diff --git a/test/cases/contenthash-multiple-entries/styleC.css b/test/cases/contenthash-multiple-entries/styleC.css new file mode 100644 index 00000000..84a728d8 --- /dev/null +++ b/test/cases/contenthash-multiple-entries/styleC.css @@ -0,0 +1 @@ +.styleA { background: red; } diff --git a/test/cases/contenthash-multiple-entries/styleD.css b/test/cases/contenthash-multiple-entries/styleD.css new file mode 100644 index 00000000..3662427b --- /dev/null +++ b/test/cases/contenthash-multiple-entries/styleD.css @@ -0,0 +1 @@ +.styleB { background: blue; } diff --git a/test/cases/contenthash-multiple-entries/webpack.config.js b/test/cases/contenthash-multiple-entries/webpack.config.js new file mode 100644 index 00000000..6ea382fc --- /dev/null +++ b/test/cases/contenthash-multiple-entries/webpack.config.js @@ -0,0 +1,30 @@ +const Self = require('../../../'); + +module.exports = { + entry: { + 'entryA': './entryA.js', + 'entryB': './entryB.js', + 'entryC': './entryC.js', + 'entryD': './entryD.js', + 'entryE': './entryE.js', + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + Self.loader, + 'css-loader', + ], + }, + ], + }, + output: { + filename: '[name]-[contenthash].js', + }, + plugins: [ + new Self({ + filename: '[contenthash].css', + }), + ], +}; diff --git a/test/cases/contenthash/expected/1.main.e0d1c5b21d098a0a371f.css b/test/cases/contenthash/expected/1.main.e0d1c5b21d098a0a371f.css new file mode 100644 index 00000000..aea53e43 --- /dev/null +++ b/test/cases/contenthash/expected/1.main.e0d1c5b21d098a0a371f.css @@ -0,0 +1,2 @@ +body { background: red; } + diff --git a/test/cases/contenthash/expected/2.main.9728193f8673cfdafd0e.css b/test/cases/contenthash/expected/2.main.9728193f8673cfdafd0e.css new file mode 100644 index 00000000..1a6052d7 --- /dev/null +++ b/test/cases/contenthash/expected/2.main.9728193f8673cfdafd0e.css @@ -0,0 +1,2 @@ +body { background: green; } + diff --git a/test/cases/contenthash/index.js b/test/cases/contenthash/index.js index 4ec1281a..aa3357bf 100644 --- a/test/cases/contenthash/index.js +++ b/test/cases/contenthash/index.js @@ -1 +1 @@ -import './style.css'; // eslint-disable-line import/no-unresolved +import './style.css'; diff --git a/test/cases/contenthash/webpack.config.js b/test/cases/contenthash/webpack.config.js index 67560582..213bcc8d 100644 --- a/test/cases/contenthash/webpack.config.js +++ b/test/cases/contenthash/webpack.config.js @@ -14,12 +14,12 @@ module.exports = [1, 2].map(n => ({ ], }, output: { - filename: `${n}.[name].js`, + filename: `${n}.[name].js` }, resolve: { alias: { - './style.css': `./style${n}.css`, - }, + './style.css': `./style${n}.css` + } }, plugins: [ new Self({ diff --git a/test/cases/css-import/a.css b/test/cases/css-import/a.css new file mode 100644 index 00000000..31fc5b8a --- /dev/null +++ b/test/cases/css-import/a.css @@ -0,0 +1 @@ +body { background: red; } diff --git a/test/cases/css-import/b.css b/test/cases/css-import/b.css new file mode 100644 index 00000000..11559583 --- /dev/null +++ b/test/cases/css-import/b.css @@ -0,0 +1,5 @@ +.b { background: red; } + +@import url("https://some/external/css"); + +.b { color: yellow; } diff --git a/test/cases/css-import/c.css b/test/cases/css-import/c.css new file mode 100644 index 00000000..9c41dd1f --- /dev/null +++ b/test/cases/css-import/c.css @@ -0,0 +1,5 @@ +.c { background: red; } +@import './a.css'; +@import url("https://some/other/external/css"); + +.c { color: yellow; } diff --git a/test/cases/css-import/expected/main.css b/test/cases/css-import/expected/main.css new file mode 100644 index 00000000..cd5dacee --- /dev/null +++ b/test/cases/css-import/expected/main.css @@ -0,0 +1,14 @@ +@import url(https://some/other/external/css); +@import url(https://some/external/css); +body { background: red; } + +.c { background: red; } + +.c { color: yellow; } + +.b { background: red; } + +.b { color: yellow; } + + + diff --git a/test/cases/css-import/index.css b/test/cases/css-import/index.css new file mode 100644 index 00000000..c03e069d --- /dev/null +++ b/test/cases/css-import/index.css @@ -0,0 +1,2 @@ +@import './c.css'; +@import './b.css'; diff --git a/test/cases/css-import/webpack.config.js b/test/cases/css-import/webpack.config.js new file mode 100644 index 00000000..7442065a --- /dev/null +++ b/test/cases/css-import/webpack.config.js @@ -0,0 +1,21 @@ +const Self = require('../../../'); + +module.exports = { + entry: './index.css', + module: { + rules: [ + { + test: /\.css$/, + use: [ + Self.loader, + 'css-loader', + ], + }, + ], + }, + plugins: [ + new Self({ + filename: '[name].css', + }), + ], +}; diff --git a/test/cases/js-hash/expected/style.11a76e1512072d65b9db.35ee3780b5b76904cef5.css b/test/cases/js-hash/expected/style.11a76e1512072d65b9db.35ee3780b5b76904cef5.css new file mode 100644 index 00000000..39afb1fb --- /dev/null +++ b/test/cases/js-hash/expected/style.11a76e1512072d65b9db.35ee3780b5b76904cef5.css @@ -0,0 +1 @@ +.wX52cuPepLZcpDx5S3yYO { background: red; } diff --git a/test/cases/js-hash/expected/style.25d094fb4a6ef6ef0222.js b/test/cases/js-hash/expected/style.25d094fb4a6ef6ef0222.js new file mode 100644 index 00000000..21f472ef --- /dev/null +++ b/test/cases/js-hash/expected/style.25d094fb4a6ef6ef0222.js @@ -0,0 +1,10 @@ +(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[ +/* 0 */, +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +// extracted by mini-css-extract-plugin +module.exports = {"a":"wX52cuPepLZcpDx5S3yYO"}; + +/***/ }) +]]); \ No newline at end of file diff --git a/test/cases/js-hash/expected/style.822a674590ef68e758b3.ca7d26e87c697b1c7039.css b/test/cases/js-hash/expected/style.822a674590ef68e758b3.ca7d26e87c697b1c7039.css new file mode 100644 index 00000000..f557e02c --- /dev/null +++ b/test/cases/js-hash/expected/style.822a674590ef68e758b3.ca7d26e87c697b1c7039.css @@ -0,0 +1 @@ +.wX52cuPepLZcpDx5S3yYO { background: green; } diff --git a/test/cases/js-hash/loader.js b/test/cases/js-hash/loader.js index dd2063b2..219e4f57 100644 --- a/test/cases/js-hash/loader.js +++ b/test/cases/js-hash/loader.js @@ -1,4 +1,4 @@ -module.exports = function (source) { +module.exports = function(source) { const { number } = this.query; - return source.split(/\r?\n/)[number - 1]; + return source.split(/\r?\n/)[number-1]; }; diff --git a/test/cases/js-hash/webpack.config.js b/test/cases/js-hash/webpack.config.js index 25ad2fda..de241c13 100644 --- a/test/cases/js-hash/webpack.config.js +++ b/test/cases/js-hash/webpack.config.js @@ -18,19 +18,19 @@ module.exports = [1, 2].map(n => ({ loader: './loader', ident: 'my-loader', options: { - number: n, - }, - }, + number: n + } + } ], }, ], }, output: { - filename: '[name].[contenthash].js', + filename: `[name].[contenthash].js` }, plugins: [ new Self({ - filename: '[name].[contenthash].[chunkhash].css', + filename: `[name].[contenthash].[chunkhash].css`, }), ], })); diff --git a/test/cases/multiple-entry/a.css b/test/cases/multiple-entry/a.css new file mode 100644 index 00000000..31fc5b8a --- /dev/null +++ b/test/cases/multiple-entry/a.css @@ -0,0 +1 @@ +body { background: red; } diff --git a/test/cases/multiple-entry/async-one.js b/test/cases/multiple-entry/async-one.js new file mode 100644 index 00000000..882b0824 --- /dev/null +++ b/test/cases/multiple-entry/async-one.js @@ -0,0 +1,2 @@ +import './c.css'; +import './d.css'; diff --git a/test/cases/multiple-entry/async-two.js b/test/cases/multiple-entry/async-two.js new file mode 100644 index 00000000..69733f71 --- /dev/null +++ b/test/cases/multiple-entry/async-two.js @@ -0,0 +1,2 @@ +import './d.css'; +import './c.css'; diff --git a/test/cases/multiple-entry/b.css b/test/cases/multiple-entry/b.css new file mode 100644 index 00000000..56af6df5 --- /dev/null +++ b/test/cases/multiple-entry/b.css @@ -0,0 +1 @@ +body { background: green; } diff --git a/test/cases/multiple-entry/c.css b/test/cases/multiple-entry/c.css new file mode 100644 index 00000000..4e2dfe9a --- /dev/null +++ b/test/cases/multiple-entry/c.css @@ -0,0 +1 @@ +body { background: blue; } diff --git a/test/cases/multiple-entry/d.css b/test/cases/multiple-entry/d.css new file mode 100644 index 00000000..75945584 --- /dev/null +++ b/test/cases/multiple-entry/d.css @@ -0,0 +1 @@ +body { background: yellow; } diff --git a/test/cases/multiple-entry/expected/async-one.css b/test/cases/multiple-entry/expected/async-one.css new file mode 100644 index 00000000..e28fd9a4 --- /dev/null +++ b/test/cases/multiple-entry/expected/async-one.css @@ -0,0 +1,4 @@ +body { background: blue; } + +body { background: yellow; } + diff --git a/test/cases/multiple-entry/expected/async-two.css b/test/cases/multiple-entry/expected/async-two.css new file mode 100644 index 00000000..14d2a2f0 --- /dev/null +++ b/test/cases/multiple-entry/expected/async-two.css @@ -0,0 +1,4 @@ +body { background: yellow; } + +body { background: blue; } + diff --git a/test/cases/multiple-entry/expected/main-one.css b/test/cases/multiple-entry/expected/main-one.css new file mode 100644 index 00000000..f17305d1 --- /dev/null +++ b/test/cases/multiple-entry/expected/main-one.css @@ -0,0 +1,4 @@ +body { background: red; } + +body { background: green; } + diff --git a/test/cases/multiple-entry/expected/main-two.css b/test/cases/multiple-entry/expected/main-two.css new file mode 100644 index 00000000..f49bc882 --- /dev/null +++ b/test/cases/multiple-entry/expected/main-two.css @@ -0,0 +1,4 @@ +body { background: green; } + +body { background: red; } + diff --git a/test/cases/multiple-entry/index-one.js b/test/cases/multiple-entry/index-one.js new file mode 100644 index 00000000..2a31d962 --- /dev/null +++ b/test/cases/multiple-entry/index-one.js @@ -0,0 +1,3 @@ +import './a.css'; +import './b.css'; +import(/* webpackChunkName: 'async-one' */'./async-one'); diff --git a/test/cases/multiple-entry/index-two.js b/test/cases/multiple-entry/index-two.js new file mode 100644 index 00000000..4ce48925 --- /dev/null +++ b/test/cases/multiple-entry/index-two.js @@ -0,0 +1,3 @@ +import(/* webpackChunkName: 'async-two' */'./async-two'); +import './b.css'; +import './a.css'; diff --git a/test/cases/multiple-entry/webpack.config.js b/test/cases/multiple-entry/webpack.config.js new file mode 100644 index 00000000..688637f0 --- /dev/null +++ b/test/cases/multiple-entry/webpack.config.js @@ -0,0 +1,24 @@ +const Self = require('../../../'); + +module.exports = { + entry: { + 'main-one': './index-one.js', + 'main-two': './index-two.js', + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + Self.loader, + 'css-loader', + ], + }, + ], + }, + plugins: [ + new Self({ + filename: '[name].css', + }), + ], +}; diff --git a/test/cases/shared-import/index.js b/test/cases/shared-import/index.js index b1356e18..bd92052b 100644 --- a/test/cases/shared-import/index.js +++ b/test/cases/shared-import/index.js @@ -1,4 +1,4 @@ import './a.css'; import './b.css'; -import('./c.css'); +import("./c.css"); diff --git a/test/cases/simple-async-load-css-fallback/a.css b/test/cases/simple-async-load-css-fallback/a.css new file mode 100644 index 00000000..31fc5b8a --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/a.css @@ -0,0 +1 @@ +body { background: red; } diff --git a/test/cases/simple-async-load-css-fallback/async-one.js b/test/cases/simple-async-load-css-fallback/async-one.js new file mode 100644 index 00000000..882b0824 --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/async-one.js @@ -0,0 +1,2 @@ +import './c.css'; +import './d.css'; diff --git a/test/cases/simple-async-load-css-fallback/async-two.js b/test/cases/simple-async-load-css-fallback/async-two.js new file mode 100644 index 00000000..e83f933c --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/async-two.js @@ -0,0 +1,2 @@ +import './e.css'; +import './f.css'; diff --git a/test/cases/simple-async-load-css-fallback/b.css b/test/cases/simple-async-load-css-fallback/b.css new file mode 100644 index 00000000..56af6df5 --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/b.css @@ -0,0 +1 @@ +body { background: green; } diff --git a/test/cases/simple-async-load-css-fallback/c.css b/test/cases/simple-async-load-css-fallback/c.css new file mode 100644 index 00000000..4e2dfe9a --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/c.css @@ -0,0 +1 @@ +body { background: blue; } diff --git a/test/cases/simple-async-load-css-fallback/d.css b/test/cases/simple-async-load-css-fallback/d.css new file mode 100644 index 00000000..75945584 --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/d.css @@ -0,0 +1 @@ +body { background: yellow; } diff --git a/test/cases/simple-async-load-css-fallback/e.css b/test/cases/simple-async-load-css-fallback/e.css new file mode 100644 index 00000000..484aaf18 --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/e.css @@ -0,0 +1 @@ +body { background: purple; } diff --git a/test/cases/simple-async-load-css-fallback/expected/async-one.css b/test/cases/simple-async-load-css-fallback/expected/async-one.css new file mode 100644 index 00000000..e28fd9a4 --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/expected/async-one.css @@ -0,0 +1,4 @@ +body { background: blue; } + +body { background: yellow; } + diff --git a/test/cases/simple-async-load-css-fallback/expected/async-two.css b/test/cases/simple-async-load-css-fallback/expected/async-two.css new file mode 100644 index 00000000..83c2b1a9 --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/expected/async-two.css @@ -0,0 +1,4 @@ +body { background: purple; } + +body { background: indigo; } + diff --git a/test/cases/simple-async-load-css-fallback/expected/main.css b/test/cases/simple-async-load-css-fallback/expected/main.css new file mode 100644 index 00000000..f17305d1 --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/expected/main.css @@ -0,0 +1,4 @@ +body { background: red; } + +body { background: green; } + diff --git a/test/cases/simple-async-load-css-fallback/f.css b/test/cases/simple-async-load-css-fallback/f.css new file mode 100644 index 00000000..a0f22c15 --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/f.css @@ -0,0 +1 @@ +body { background: indigo; } diff --git a/test/cases/simple-async-load-css-fallback/index.js b/test/cases/simple-async-load-css-fallback/index.js new file mode 100644 index 00000000..4a384fd0 --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/index.js @@ -0,0 +1,5 @@ +import './a.css'; +import './b.css'; +import(/* webpackChunkName: 'async-one' */'./async-one'); +import(/* webpackChunkName: 'async-two' */'./async-two'); + diff --git a/test/cases/simple-async-load-css-fallback/webpack.config.js b/test/cases/simple-async-load-css-fallback/webpack.config.js new file mode 100644 index 00000000..17dc8ead --- /dev/null +++ b/test/cases/simple-async-load-css-fallback/webpack.config.js @@ -0,0 +1,33 @@ +const Self = require('../../../'); + +module.exports = { + entry: { + 'main': './index.js', + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + Self.loader, + 'css-loader', + ], + }, + ], + }, + plugins: [ + function() { + this.hooks.compilation.tap("Test", compilation => { + compilation.hooks.beforeChunkAssets.tap("Test", () => { + for (const chunkGroup of compilation.chunkGroups) { + // remove getModuleIndex2 to enforce using fallback + chunkGroup.getModuleIndex2 = undefined; + } + }); + }) + }, + new Self({ + filename: '[name].css', + }), + ], +}; diff --git a/test/cases/simple-async-load-css/a.css b/test/cases/simple-async-load-css/a.css new file mode 100644 index 00000000..31fc5b8a --- /dev/null +++ b/test/cases/simple-async-load-css/a.css @@ -0,0 +1 @@ +body { background: red; } diff --git a/test/cases/simple-async-load-css/async-one.js b/test/cases/simple-async-load-css/async-one.js new file mode 100644 index 00000000..882b0824 --- /dev/null +++ b/test/cases/simple-async-load-css/async-one.js @@ -0,0 +1,2 @@ +import './c.css'; +import './d.css'; diff --git a/test/cases/simple-async-load-css/async-two.js b/test/cases/simple-async-load-css/async-two.js new file mode 100644 index 00000000..e83f933c --- /dev/null +++ b/test/cases/simple-async-load-css/async-two.js @@ -0,0 +1,2 @@ +import './e.css'; +import './f.css'; diff --git a/test/cases/simple-async-load-css/b.css b/test/cases/simple-async-load-css/b.css new file mode 100644 index 00000000..56af6df5 --- /dev/null +++ b/test/cases/simple-async-load-css/b.css @@ -0,0 +1 @@ +body { background: green; } diff --git a/test/cases/simple-async-load-css/c.css b/test/cases/simple-async-load-css/c.css new file mode 100644 index 00000000..4e2dfe9a --- /dev/null +++ b/test/cases/simple-async-load-css/c.css @@ -0,0 +1 @@ +body { background: blue; } diff --git a/test/cases/simple-async-load-css/d.css b/test/cases/simple-async-load-css/d.css new file mode 100644 index 00000000..75945584 --- /dev/null +++ b/test/cases/simple-async-load-css/d.css @@ -0,0 +1 @@ +body { background: yellow; } diff --git a/test/cases/simple-async-load-css/e.css b/test/cases/simple-async-load-css/e.css new file mode 100644 index 00000000..484aaf18 --- /dev/null +++ b/test/cases/simple-async-load-css/e.css @@ -0,0 +1 @@ +body { background: purple; } diff --git a/test/cases/simple-async-load-css/expected/async-one.css b/test/cases/simple-async-load-css/expected/async-one.css new file mode 100644 index 00000000..e28fd9a4 --- /dev/null +++ b/test/cases/simple-async-load-css/expected/async-one.css @@ -0,0 +1,4 @@ +body { background: blue; } + +body { background: yellow; } + diff --git a/test/cases/simple-async-load-css/expected/async-two.css b/test/cases/simple-async-load-css/expected/async-two.css new file mode 100644 index 00000000..83c2b1a9 --- /dev/null +++ b/test/cases/simple-async-load-css/expected/async-two.css @@ -0,0 +1,4 @@ +body { background: purple; } + +body { background: indigo; } + diff --git a/test/cases/simple-async-load-css/expected/main.css b/test/cases/simple-async-load-css/expected/main.css new file mode 100644 index 00000000..f17305d1 --- /dev/null +++ b/test/cases/simple-async-load-css/expected/main.css @@ -0,0 +1,4 @@ +body { background: red; } + +body { background: green; } + diff --git a/test/cases/simple-async-load-css/f.css b/test/cases/simple-async-load-css/f.css new file mode 100644 index 00000000..a0f22c15 --- /dev/null +++ b/test/cases/simple-async-load-css/f.css @@ -0,0 +1 @@ +body { background: indigo; } diff --git a/test/cases/simple-async-load-css/index.js b/test/cases/simple-async-load-css/index.js new file mode 100644 index 00000000..4a384fd0 --- /dev/null +++ b/test/cases/simple-async-load-css/index.js @@ -0,0 +1,5 @@ +import './a.css'; +import './b.css'; +import(/* webpackChunkName: 'async-one' */'./async-one'); +import(/* webpackChunkName: 'async-two' */'./async-two'); + diff --git a/test/cases/simple-async-load-css/webpack.config.js b/test/cases/simple-async-load-css/webpack.config.js new file mode 100644 index 00000000..2a846242 --- /dev/null +++ b/test/cases/simple-async-load-css/webpack.config.js @@ -0,0 +1,23 @@ +const Self = require('../../../'); + +module.exports = { + entry: { + 'main': './index.js', + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + Self.loader, + 'css-loader', + ], + }, + ], + }, + plugins: [ + new Self({ + filename: '[name].css', + }), + ], +}; diff --git a/test/cases/simple-publicpath/expected/main.css b/test/cases/simple-publicpath/expected/main.css new file mode 100644 index 00000000..6073c14a --- /dev/null +++ b/test/cases/simple-publicpath/expected/main.css @@ -0,0 +1,2 @@ +body { background: red; background-image: url(/static/img/cd0bb358c45b584743d8ce4991777c42.svg); } + diff --git a/test/cases/simple-publicpath/index.js b/test/cases/simple-publicpath/index.js new file mode 100644 index 00000000..aa3357bf --- /dev/null +++ b/test/cases/simple-publicpath/index.js @@ -0,0 +1 @@ +import './style.css'; diff --git a/test/cases/simple-publicpath/react.svg b/test/cases/simple-publicpath/react.svg new file mode 100644 index 00000000..5b3b22a4 --- /dev/null +++ b/test/cases/simple-publicpath/react.svg @@ -0,0 +1 @@ +logo-on-dark-bg \ No newline at end of file diff --git a/test/cases/simple-publicpath/style.css b/test/cases/simple-publicpath/style.css new file mode 100644 index 00000000..edcbc24a --- /dev/null +++ b/test/cases/simple-publicpath/style.css @@ -0,0 +1 @@ +body { background: red; background-image: url(./react.svg); } diff --git a/test/cases/simple-publicpath/webpack.config.js b/test/cases/simple-publicpath/webpack.config.js new file mode 100644 index 00000000..75b7b8b8 --- /dev/null +++ b/test/cases/simple-publicpath/webpack.config.js @@ -0,0 +1,34 @@ +const Self = require('../../../'); + +module.exports = { + entry: './index.js', + module: { + rules: [ + { + test: /\.css$/, + use: [ + { + loader: Self.loader, + options: { + publicPath: '/static/img/' + } + }, + 'css-loader', + ], + }, { + test: /\.(svg|png)$/, + use: [{ + loader: 'file-loader', + options: { + filename: '[name].[ext]' + } + }] + } + ], + }, + plugins: [ + new Self({ + filename: '[name].css', + }), + ], +}; diff --git a/test/cases/source-map/expected/main.css b/test/cases/source-map/expected/main.css new file mode 100644 index 00000000..e7d36a90 --- /dev/null +++ b/test/cases/source-map/expected/main.css @@ -0,0 +1,4 @@ +body { background: red; } + + +/*# sourceMappingURL=main.css.map*/ \ No newline at end of file diff --git a/test/cases/source-map/index.js b/test/cases/source-map/index.js new file mode 100644 index 00000000..aa3357bf --- /dev/null +++ b/test/cases/source-map/index.js @@ -0,0 +1 @@ +import './style.css'; diff --git a/test/cases/source-map/style.css b/test/cases/source-map/style.css new file mode 100644 index 00000000..31fc5b8a --- /dev/null +++ b/test/cases/source-map/style.css @@ -0,0 +1 @@ +body { background: red; } diff --git a/test/cases/source-map/webpack.config.js b/test/cases/source-map/webpack.config.js new file mode 100644 index 00000000..4c8bdad1 --- /dev/null +++ b/test/cases/source-map/webpack.config.js @@ -0,0 +1,32 @@ +const Self = require('../../../'); + +module.exports = { + entry: './index.js', + devtool: "source-map", + module: { + rules: [ + { + test: /\.css$/, + use: [ + { + loader: Self.loader, + options: { + sourceMap: true + } + }, + { + loader: 'css-loader', + options: { + sourceMap: true + } + } + ], + }, + ], + }, + plugins: [ + new Self({ + filename: '[name].css', + }), + ], +}; diff --git a/test/cases/split-chunks-single/a.css b/test/cases/split-chunks-single/a.css new file mode 100644 index 00000000..dd4f97ce --- /dev/null +++ b/test/cases/split-chunks-single/a.css @@ -0,0 +1 @@ +body { content: "a"; } diff --git a/test/cases/split-chunks-single/b.css b/test/cases/split-chunks-single/b.css new file mode 100644 index 00000000..52d18453 --- /dev/null +++ b/test/cases/split-chunks-single/b.css @@ -0,0 +1 @@ +body { content: "b"; } diff --git a/test/cases/split-chunks-single/c.css b/test/cases/split-chunks-single/c.css new file mode 100644 index 00000000..c53028fb --- /dev/null +++ b/test/cases/split-chunks-single/c.css @@ -0,0 +1 @@ +body { content: "c"; } diff --git a/test/cases/split-chunks-single/chunk1.js b/test/cases/split-chunks-single/chunk1.js new file mode 100644 index 00000000..eb558470 --- /dev/null +++ b/test/cases/split-chunks-single/chunk1.js @@ -0,0 +1,2 @@ +import "./c.css"; +import "./d.css"; diff --git a/test/cases/split-chunks-single/chunk2.js b/test/cases/split-chunks-single/chunk2.js new file mode 100644 index 00000000..7254062a --- /dev/null +++ b/test/cases/split-chunks-single/chunk2.js @@ -0,0 +1,2 @@ +import "./d.css"; +import "./h.css"; diff --git a/test/cases/split-chunks-single/d.css b/test/cases/split-chunks-single/d.css new file mode 100644 index 00000000..1b2fc6a8 --- /dev/null +++ b/test/cases/split-chunks-single/d.css @@ -0,0 +1 @@ +body { content: "d"; } diff --git a/test/cases/split-chunks-single/e1.css b/test/cases/split-chunks-single/e1.css new file mode 100644 index 00000000..9028097f --- /dev/null +++ b/test/cases/split-chunks-single/e1.css @@ -0,0 +1 @@ +body { content: "e1"; } diff --git a/test/cases/split-chunks-single/e2.css b/test/cases/split-chunks-single/e2.css new file mode 100644 index 00000000..af21d863 --- /dev/null +++ b/test/cases/split-chunks-single/e2.css @@ -0,0 +1 @@ +body { content: "e2"; } diff --git a/test/cases/split-chunks-single/entry1.js b/test/cases/split-chunks-single/entry1.js new file mode 100644 index 00000000..db2fccba --- /dev/null +++ b/test/cases/split-chunks-single/entry1.js @@ -0,0 +1,6 @@ +import './a.css'; +import './e1.css'; +import './e2.css'; +import './f.css'; +import("./chunk1"); +import("./chunk2"); diff --git a/test/cases/split-chunks-single/entry2.js b/test/cases/split-chunks-single/entry2.js new file mode 100644 index 00000000..a8576c43 --- /dev/null +++ b/test/cases/split-chunks-single/entry2.js @@ -0,0 +1,5 @@ +import './b.css'; +import './e2.css'; +import './e1.css'; +import './g.css'; +import './h.css'; diff --git a/test/cases/split-chunks-single/expected/styles.css b/test/cases/split-chunks-single/expected/styles.css new file mode 100644 index 00000000..1bd8b96d --- /dev/null +++ b/test/cases/split-chunks-single/expected/styles.css @@ -0,0 +1,18 @@ +body { content: "a"; } + +body { content: "b"; } + +body { content: "c"; } + +body { content: "d"; } + +body { content: "e1"; } + +body { content: "e2"; } + +body { content: "f"; } + +body { content: "g"; } + +body { content: "h"; } + diff --git a/test/cases/split-chunks-single/f.css b/test/cases/split-chunks-single/f.css new file mode 100644 index 00000000..08f73240 --- /dev/null +++ b/test/cases/split-chunks-single/f.css @@ -0,0 +1 @@ +body { content: "f"; } diff --git a/test/cases/split-chunks-single/g.css b/test/cases/split-chunks-single/g.css new file mode 100644 index 00000000..098e2a66 --- /dev/null +++ b/test/cases/split-chunks-single/g.css @@ -0,0 +1 @@ +body { content: "g"; } diff --git a/test/cases/split-chunks-single/h.css b/test/cases/split-chunks-single/h.css new file mode 100644 index 00000000..991f82ec --- /dev/null +++ b/test/cases/split-chunks-single/h.css @@ -0,0 +1 @@ +body { content: "h"; } diff --git a/test/cases/split-chunks-single/style.css b/test/cases/split-chunks-single/style.css new file mode 100644 index 00000000..31fc5b8a --- /dev/null +++ b/test/cases/split-chunks-single/style.css @@ -0,0 +1 @@ +body { background: red; } diff --git a/test/cases/split-chunks-single/webpack.config.js b/test/cases/split-chunks-single/webpack.config.js new file mode 100644 index 00000000..9732c2a0 --- /dev/null +++ b/test/cases/split-chunks-single/webpack.config.js @@ -0,0 +1,36 @@ +const Self = require('../../../'); + +module.exports = { + entry: { + entry1: './entry1.js', + entry2: './entry2.js' + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + Self.loader, + 'css-loader', + ], + }, + ], + }, + optimization: { + splitChunks: { + cacheGroups: { + styles: { + name: "styles", + chunks: 'all', + test: /\.css$/, + enforce: true + } + } + } + }, + plugins: [ + new Self({ + filename: '[name].css', + }), + ], +}; diff --git a/test/cases/split-chunks/index.js b/test/cases/split-chunks/index.js index 4bac714b..c83e204a 100644 --- a/test/cases/split-chunks/index.js +++ b/test/cases/split-chunks/index.js @@ -1,3 +1,2 @@ -/* eslint-disable */ import 'bootstrap.css'; import './style.css'; diff --git a/test/cases/split-chunks/webpack.config.js b/test/cases/split-chunks/webpack.config.js index c1b163e1..d276ab14 100644 --- a/test/cases/split-chunks/webpack.config.js +++ b/test/cases/split-chunks/webpack.config.js @@ -19,10 +19,10 @@ module.exports = { cacheGroups: { vendors: { test: /node_modules/, - enforce: true, - }, - }, - }, + enforce: true + } + } + } }, plugins: [ new Self({ diff --git a/test/manual/index.html b/test/manual/index.html index 8f544a27..8158d36a 100644 --- a/test/manual/index.html +++ b/test/manual/index.html @@ -3,7 +3,7 @@ - extract-css-chunks-webpack-plugin testcase + mini-css-extract-plugin testcase