diff --git a/.gitattributes b/.gitattributes index b07091b9..9d37df8f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ package-lock.json -diff * text=auto -bin/* eol=lf \ No newline at end of file +test/cases/* eol=lf +bin/* eol=lf diff --git a/README.md b/README.md index 87c73feb..d1a6fca2 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,11 @@ When inlining CSS `data-href` must be used. +#### Long Term Caching + +For long term caching use `filename: "[contenthash].css"`. Optionally add `[name]`. + + [npm]: https://img.shields.io/npm/v/mini-css-extract-plugin.svg [npm-url]: https://npmjs.com/package/mini-css-extract-plugin diff --git a/package.json b/package.json index 22fcd3f1..a9b4b1d3 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "nsp": "^3.1.0", "pre-commit": "^1.2.2", "standard-version": "^4.3.0", - "webpack": "^4.1.0", + "webpack": "^4.3.0", "webpack-cli": "^2.0.13", "webpack-defaults": "^1.6.0", "webpack-dev-server": "^3.1.1" @@ -56,7 +56,7 @@ "node": ">= 6.11.5" }, "peerDependencies": { - "webpack": "^4.1.0" + "webpack": "^4.3.0" }, "pre-commit": "lint-staged", "lint-staged": { diff --git a/src/index.js b/src/index.js index 3ecc998c..210b5abc 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,7 @@ import webpack from 'webpack'; import sources from 'webpack-sources'; const { ConcatSource, SourceMapSource, OriginalSource } = sources; -const { Template } = webpack; +const { Template, util: { createHash } } = webpack; const NS = path.dirname(fs.realpathSync(__filename)); @@ -57,7 +57,7 @@ class CssModule extends webpack.Module { nameForCondition() { const resource = this._identifier.split('!').pop(); const idx = resource.indexOf('?'); - if (idx >= 0) return resource.substr(0, idx); + if (idx >= 0) return resource.substring(0, idx); return resource; } @@ -66,6 +66,13 @@ class CssModule extends webpack.Module { this.buildMeta = {}; callback(); } + + updateHash(hash) { + super.updateHash(hash); + hash.update(this.content); + hash.update(this.media || ''); + hash.update(JSON.stringify(this.sourceMap || '')); + } } class CssModuleFactory { @@ -121,6 +128,7 @@ class MiniCssExtractPlugin { filenameTemplate: this.options.filename, pathOptions: { chunk, + contentHashType: NS, }, identifier: `mini-css-extract-plugin.${chunk.id}`, }); @@ -134,11 +142,26 @@ class MiniCssExtractPlugin { filenameTemplate: this.options.chunkFilename, pathOptions: { chunk, + contentHashType: NS, }, identifier: `mini-css-extract-plugin.${chunk.id}`, }); } }); + 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 === NS) { + m.updateHash(hash); + } + } + const { contentHash } = chunk; + contentHash[NS] = hash + .digest(hashDigest) + .substring(0, hashDigestLength); + }); const { mainTemplate } = compilation; mainTemplate.hooks.localVars.tap( pluginName, @@ -178,13 +201,35 @@ class MiniCssExtractPlugin { const shortChunkHashMap = Object.create(null); for (const chunkId of Object.keys(chunkMaps.hash)) { if (typeof chunkMaps.hash[chunkId] === 'string') { - shortChunkHashMap[chunkId] = chunkMaps.hash[chunkId].substr(0, length); + shortChunkHashMap[chunkId] = chunkMaps.hash[chunkId].substring(0, length); } } return `" + ${JSON.stringify(shortChunkHashMap)}[chunkId] + "`; }, + contentHash: { + [NS]: `" + ${JSON.stringify( + chunkMaps.contentHash[NS], + )}[chunkId] + "`, + }, + contentHashWithLength: { + [NS]: (length) => { + const shortContentHashMap = {}; + const contentHash = chunkMaps.contentHash[NS]; + 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: NS, }, ); return Template.asString([ diff --git a/test/TestCases.test.js b/test/TestCases.test.js index 53c7b842..bb6ecc91 100644 --- a/test/TestCases.test.js +++ b/test/TestCases.test.js @@ -13,13 +13,16 @@ describe('TestCases', () => { const outputDirectoryForCase = path.resolve(outputDirectory, directory); // eslint-disable-next-line import/no-dynamic-require, global-require const webpackConfig = require(path.resolve(directoryForCase, 'webpack.config.js')); - webpack(Object.assign({ - mode: 'none', - context: directoryForCase, - output: { - path: outputDirectoryForCase, - }, - }, webpackConfig), (err, stats) => { + for (const config of [].concat(webpackConfig)) { + Object.assign(config, { + mode: 'none', + context: directoryForCase, + output: Object.assign({ + path: outputDirectoryForCase, + }, config.output), + }, config); + } + webpack(webpackConfig, (err, stats) => { if (err) { done(err); return; diff --git a/test/cases/contenthash/expected/1.main.eaf8d14ae542d0855a7d.css b/test/cases/contenthash/expected/1.main.eaf8d14ae542d0855a7d.css new file mode 100644 index 00000000..aea53e43 --- /dev/null +++ b/test/cases/contenthash/expected/1.main.eaf8d14ae542d0855a7d.css @@ -0,0 +1,2 @@ +body { background: red; } + diff --git a/test/cases/contenthash/expected/2.main.ae0f5d087b9f049f864a.css b/test/cases/contenthash/expected/2.main.ae0f5d087b9f049f864a.css new file mode 100644 index 00000000..1a6052d7 --- /dev/null +++ b/test/cases/contenthash/expected/2.main.ae0f5d087b9f049f864a.css @@ -0,0 +1,2 @@ +body { background: green; } + diff --git a/test/cases/contenthash/index.js b/test/cases/contenthash/index.js new file mode 100644 index 00000000..aa3357bf --- /dev/null +++ b/test/cases/contenthash/index.js @@ -0,0 +1 @@ +import './style.css'; diff --git a/test/cases/contenthash/style1.css b/test/cases/contenthash/style1.css new file mode 100644 index 00000000..31fc5b8a --- /dev/null +++ b/test/cases/contenthash/style1.css @@ -0,0 +1 @@ +body { background: red; } diff --git a/test/cases/contenthash/style2.css b/test/cases/contenthash/style2.css new file mode 100644 index 00000000..56af6df5 --- /dev/null +++ b/test/cases/contenthash/style2.css @@ -0,0 +1 @@ +body { background: green; } diff --git a/test/cases/contenthash/webpack.config.js b/test/cases/contenthash/webpack.config.js new file mode 100644 index 00000000..213bcc8d --- /dev/null +++ b/test/cases/contenthash/webpack.config.js @@ -0,0 +1,29 @@ +const Self = require('../../../'); + +module.exports = [1, 2].map(n => ({ + entry: './index.js', + module: { + rules: [ + { + test: /\.css$/, + use: [ + Self.loader, + 'css-loader', + ], + }, + ], + }, + output: { + filename: `${n}.[name].js` + }, + resolve: { + alias: { + './style.css': `./style${n}.css` + } + }, + plugins: [ + new Self({ + filename: `${n}.[name].[contenthash].css`, + }), + ], +})); diff --git a/test/cases/js-hash/expected/style.41bf047ed4fa005a3e24.js b/test/cases/js-hash/expected/style.41bf047ed4fa005a3e24.js new file mode 100644 index 00000000..21f472ef --- /dev/null +++ b/test/cases/js-hash/expected/style.41bf047ed4fa005a3e24.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.44c5db65e2b053b76abc.fde1566f2c635787864b.css b/test/cases/js-hash/expected/style.44c5db65e2b053b76abc.fde1566f2c635787864b.css new file mode 100644 index 00000000..39afb1fb --- /dev/null +++ b/test/cases/js-hash/expected/style.44c5db65e2b053b76abc.fde1566f2c635787864b.css @@ -0,0 +1 @@ +.wX52cuPepLZcpDx5S3yYO { background: red; } diff --git a/test/cases/js-hash/expected/style.fadeeffaa38bf4f2edd8.802f6f3e865dd4e83d8e.css b/test/cases/js-hash/expected/style.fadeeffaa38bf4f2edd8.802f6f3e865dd4e83d8e.css new file mode 100644 index 00000000..f557e02c --- /dev/null +++ b/test/cases/js-hash/expected/style.fadeeffaa38bf4f2edd8.802f6f3e865dd4e83d8e.css @@ -0,0 +1 @@ +.wX52cuPepLZcpDx5S3yYO { background: green; } diff --git a/test/cases/js-hash/index.js b/test/cases/js-hash/index.js new file mode 100644 index 00000000..00b23fa4 --- /dev/null +++ b/test/cases/js-hash/index.js @@ -0,0 +1 @@ +import(/* webpackChunkName: "style" */'./style.css'); diff --git a/test/cases/js-hash/loader.js b/test/cases/js-hash/loader.js new file mode 100644 index 00000000..219e4f57 --- /dev/null +++ b/test/cases/js-hash/loader.js @@ -0,0 +1,4 @@ +module.exports = function(source) { + const { number } = this.query; + return source.split(/\r?\n/)[number-1]; +}; diff --git a/test/cases/js-hash/style.css b/test/cases/js-hash/style.css new file mode 100644 index 00000000..3176c972 --- /dev/null +++ b/test/cases/js-hash/style.css @@ -0,0 +1,2 @@ +.a { background: red; } +.a { background: green; } diff --git a/test/cases/js-hash/webpack.config.js b/test/cases/js-hash/webpack.config.js new file mode 100644 index 00000000..de241c13 --- /dev/null +++ b/test/cases/js-hash/webpack.config.js @@ -0,0 +1,36 @@ +const Self = require('../../../'); + +module.exports = [1, 2].map(n => ({ + entry: './index.js', + module: { + rules: [ + { + test: /\.css$/, + use: [ + Self.loader, + { + loader: 'css-loader', + options: { + modules: true, + }, + }, + { + loader: './loader', + ident: 'my-loader', + options: { + number: n + } + } + ], + }, + ], + }, + output: { + filename: `[name].[contenthash].js` + }, + plugins: [ + new Self({ + filename: `[name].[contenthash].[chunkhash].css`, + }), + ], +})); diff --git a/yarn.lock b/yarn.lock index c60330c0..5bc3d99c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1327,7 +1327,7 @@ cac@^3.0.3: suffix "^0.1.0" text-table "^0.2.0" -cacache@^10.0.1: +cacache@^10.0.4: version "10.0.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460" dependencies: @@ -6922,7 +6922,7 @@ sax@^1.2.4, sax@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" -schema-utils@^0.4.2: +schema-utils@^0.4.2, schema-utils@^0.4.5: version "0.4.5" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e" dependencies: @@ -7724,13 +7724,13 @@ uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" -uglifyjs-webpack-plugin@^1.1.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.2.tgz#e7516d4367afdb715c3847841eb46f94c45ca2b9" +uglifyjs-webpack-plugin@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.4.tgz#5eec941b2e9b8538be0a20fc6eda25b14c7c1043" dependencies: - cacache "^10.0.1" + cacache "^10.0.4" find-cache-dir "^1.0.0" - schema-utils "^0.4.2" + schema-utils "^0.4.5" serialize-javascript "^1.4.0" source-map "^0.6.1" uglify-es "^3.3.4" @@ -8125,9 +8125,9 @@ webpack-sources@^1.0.1, webpack-sources@^1.1.0: source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.1.0.tgz#91b6862e56eb3b18b79bb10b51866987ff10d2d6" +webpack@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.3.0.tgz#0b0c1e211311b3995dd25aed47ab46ea658be070" dependencies: acorn "^5.0.0" acorn-dynamic-import "^3.0.0" @@ -8145,7 +8145,7 @@ webpack@^4.1.0: node-libs-browser "^2.0.0" schema-utils "^0.4.2" tapable "^1.0.0" - uglifyjs-webpack-plugin "^1.1.1" + uglifyjs-webpack-plugin "^1.2.4" watchpack "^1.5.0" webpack-sources "^1.0.1"