From 098a268770ca59d99e12e5e4e3a8527af17fc121 Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Fri, 5 Apr 2019 07:38:36 +1300 Subject: [PATCH 1/7] feat: publicPath can now be a function Also added schema validation for publicPath. --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/loader.js | 8 ++++++++ src/options.json | 19 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 src/options.json diff --git a/README.md b/README.md index beb5620a..72c21d77 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,13 @@ npm install --save-dev mini-css-extract-plugin ### Configuration +#### `publicPath` + +Type: `String|Function` +Default: the `publicPath` in `webpackOptions.output` + +Specifies a custom public path for the target file(s). + #### Minimal example **webpack.config.js** @@ -74,6 +81,45 @@ module.exports = { } ``` +#### `publicPath` function example + +**webpack.config.js** + +```js +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +module.exports = { + plugins: [ + new MiniCssExtractPlugin({ + // Options similar to the same options in webpackOptions.output + // both options are optional + filename: "[name].css", + chunkFilename: "[id].css" + }) + ], + module: { + rules: [ + { + test: /\.css$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + options: { + publicPath: (resourcePath, context) => { + // publicPath is the relative path of the resource to the context + // e.g. for ./css/admin/main.css the publicPath will be ../../ + // while for ./css/main.css the publicPath will be ../ + return path.relative(path.dirname(resourcePath), context) + '/' + }, + } + }, + "css-loader" + ] + } + ] + } +} +``` + #### Advanced configuration example This plugin should be used only on `production` builds without `style-loader` in the loaders chain, especially if you want to have HMR in `development`. diff --git a/src/loader.js b/src/loader.js index 9801d590..ebad3695 100644 --- a/src/loader.js +++ b/src/loader.js @@ -6,6 +6,9 @@ import NodeTargetPlugin from 'webpack/lib/node/NodeTargetPlugin'; import LibraryTemplatePlugin from 'webpack/lib/LibraryTemplatePlugin'; import SingleEntryPlugin from 'webpack/lib/SingleEntryPlugin'; import LimitChunkCountPlugin from 'webpack/lib/optimize/LimitChunkCountPlugin'; +import validateOptions from 'schema-utils'; + +import schema from './options.json'; const MODULE_TYPE = 'css/mini-extract'; const pluginName = 'mini-css-extract-plugin'; @@ -29,12 +32,17 @@ const findModuleById = (modules, id) => { export function pitch(request) { const query = loaderUtils.getOptions(this) || {}; + + validateOptions(schema, query, 'Mini CSS Extract Plugin Loader'); + 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 + : typeof query.publicPath === 'function' + ? query.publicPath(this.resourcePath, this.rootContext) : this._compilation.outputOptions.publicPath; const outputOptions = { filename: childFilename, diff --git a/src/options.json b/src/options.json new file mode 100644 index 00000000..1c6eef18 --- /dev/null +++ b/src/options.json @@ -0,0 +1,19 @@ +{ + "additionalProperties": true, + "properties": { + "publicPath": { + "anyOf": [ + { + "type": "string" + }, + { + "instanceof": "Function" + } + ] + } + }, + "errorMessages": { + "publicPath": "should be {String} or {Function} (https://github.com/webpack-contrib/mini-css-extract-plugin#publicpath)" + }, + "type": "object" + } From 5db65d2eef51e620121382c4602d180688eb063f Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Fri, 5 Apr 2019 20:56:20 +1300 Subject: [PATCH 2/7] test: publicPath function --- .../function-publicpath/expected/main.css | 2 ++ test/cases/function-publicpath/index.js | 1 + .../function-publicpath/nested/style.css | 1 + test/cases/function-publicpath/react.svg | 1 + .../function-publicpath/webpack.config.js | 35 +++++++++++++++++++ 5 files changed, 40 insertions(+) create mode 100644 test/cases/function-publicpath/expected/main.css create mode 100644 test/cases/function-publicpath/index.js create mode 100644 test/cases/function-publicpath/nested/style.css create mode 100644 test/cases/function-publicpath/react.svg create mode 100644 test/cases/function-publicpath/webpack.config.js diff --git a/test/cases/function-publicpath/expected/main.css b/test/cases/function-publicpath/expected/main.css new file mode 100644 index 00000000..aff4e2be --- /dev/null +++ b/test/cases/function-publicpath/expected/main.css @@ -0,0 +1,2 @@ +body { background: red; background-image: url(../cd0bb358c45b584743d8ce4991777c42.svg); } + diff --git a/test/cases/function-publicpath/index.js b/test/cases/function-publicpath/index.js new file mode 100644 index 00000000..d4e71361 --- /dev/null +++ b/test/cases/function-publicpath/index.js @@ -0,0 +1 @@ +import './nested/style.css'; diff --git a/test/cases/function-publicpath/nested/style.css b/test/cases/function-publicpath/nested/style.css new file mode 100644 index 00000000..c6261084 --- /dev/null +++ b/test/cases/function-publicpath/nested/style.css @@ -0,0 +1 @@ +body { background: red; background-image: url(../react.svg); } diff --git a/test/cases/function-publicpath/react.svg b/test/cases/function-publicpath/react.svg new file mode 100644 index 00000000..5b3b22a4 --- /dev/null +++ b/test/cases/function-publicpath/react.svg @@ -0,0 +1 @@ +logo-on-dark-bg \ No newline at end of file diff --git a/test/cases/function-publicpath/webpack.config.js b/test/cases/function-publicpath/webpack.config.js new file mode 100644 index 00000000..30b243df --- /dev/null +++ b/test/cases/function-publicpath/webpack.config.js @@ -0,0 +1,35 @@ +const Self = require('../../../'); +const path = require('path') + +module.exports = { + entry: './index.js', + module: { + rules: [ + { + test: /\.css$/, + use: [ + { + loader: Self.loader, + options: { + publicPath: (resourcePath, context) => path.relative(path.dirname(resourcePath), context) + '/', + } + }, + 'css-loader', + ], + }, { + test: /\.(svg|png)$/, + use: [{ + loader: 'file-loader', + options: { + filename: '[name].[ext]' + } + }] + } + ], + }, + plugins: [ + new Self({ + filename: '[name].css', + }), + ], +}; From fab158dbcbd1d477846fca2fb3a5ff44d88b0731 Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Sat, 6 Apr 2019 06:49:52 +1300 Subject: [PATCH 3/7] fix: lack of trailing slash on publicPath string option --- src/loader.js | 4 ++- .../expected/main.css | 2 ++ test/cases/publicpath-trailing-slash/index.js | 1 + .../cases/publicpath-trailing-slash/react.svg | 1 + .../cases/publicpath-trailing-slash/style.css | 1 + .../webpack.config.js | 34 +++++++++++++++++++ 6 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 test/cases/publicpath-trailing-slash/expected/main.css create mode 100644 test/cases/publicpath-trailing-slash/index.js create mode 100644 test/cases/publicpath-trailing-slash/react.svg create mode 100644 test/cases/publicpath-trailing-slash/style.css create mode 100644 test/cases/publicpath-trailing-slash/webpack.config.js diff --git a/src/loader.js b/src/loader.js index ebad3695..5eafa7ab 100644 --- a/src/loader.js +++ b/src/loader.js @@ -40,7 +40,9 @@ export function pitch(request) { const childFilename = '*'; // eslint-disable-line no-path-concat const publicPath = typeof query.publicPath === 'string' - ? query.publicPath + ? query.publicPath.endsWith('/') + ? query.publicPath + : `${query.publicPath}/` : typeof query.publicPath === 'function' ? query.publicPath(this.resourcePath, this.rootContext) : this._compilation.outputOptions.publicPath; diff --git a/test/cases/publicpath-trailing-slash/expected/main.css b/test/cases/publicpath-trailing-slash/expected/main.css new file mode 100644 index 00000000..6073c14a --- /dev/null +++ b/test/cases/publicpath-trailing-slash/expected/main.css @@ -0,0 +1,2 @@ +body { background: red; background-image: url(/static/img/cd0bb358c45b584743d8ce4991777c42.svg); } + diff --git a/test/cases/publicpath-trailing-slash/index.js b/test/cases/publicpath-trailing-slash/index.js new file mode 100644 index 00000000..aa3357bf --- /dev/null +++ b/test/cases/publicpath-trailing-slash/index.js @@ -0,0 +1 @@ +import './style.css'; diff --git a/test/cases/publicpath-trailing-slash/react.svg b/test/cases/publicpath-trailing-slash/react.svg new file mode 100644 index 00000000..5b3b22a4 --- /dev/null +++ b/test/cases/publicpath-trailing-slash/react.svg @@ -0,0 +1 @@ +logo-on-dark-bg \ No newline at end of file diff --git a/test/cases/publicpath-trailing-slash/style.css b/test/cases/publicpath-trailing-slash/style.css new file mode 100644 index 00000000..edcbc24a --- /dev/null +++ b/test/cases/publicpath-trailing-slash/style.css @@ -0,0 +1 @@ +body { background: red; background-image: url(./react.svg); } diff --git a/test/cases/publicpath-trailing-slash/webpack.config.js b/test/cases/publicpath-trailing-slash/webpack.config.js new file mode 100644 index 00000000..32180f0d --- /dev/null +++ b/test/cases/publicpath-trailing-slash/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', + }), + ], +}; From a9985d869e7551c82c53fe51ceb4d6e0a8038bab Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Sat, 6 Apr 2019 07:08:49 +1300 Subject: [PATCH 4/7] chore: rename function-publicpath test Make it consistent with new publicpath-trailing-slash test --- .../expected/main.css | 0 test/cases/{function-publicpath => publicpath-function}/index.js | 0 .../{function-publicpath => publicpath-function}/nested/style.css | 0 test/cases/{function-publicpath => publicpath-function}/react.svg | 0 .../webpack.config.js | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename test/cases/{function-publicpath => publicpath-function}/expected/main.css (100%) rename test/cases/{function-publicpath => publicpath-function}/index.js (100%) rename test/cases/{function-publicpath => publicpath-function}/nested/style.css (100%) rename test/cases/{function-publicpath => publicpath-function}/react.svg (100%) rename test/cases/{function-publicpath => publicpath-function}/webpack.config.js (100%) diff --git a/test/cases/function-publicpath/expected/main.css b/test/cases/publicpath-function/expected/main.css similarity index 100% rename from test/cases/function-publicpath/expected/main.css rename to test/cases/publicpath-function/expected/main.css diff --git a/test/cases/function-publicpath/index.js b/test/cases/publicpath-function/index.js similarity index 100% rename from test/cases/function-publicpath/index.js rename to test/cases/publicpath-function/index.js diff --git a/test/cases/function-publicpath/nested/style.css b/test/cases/publicpath-function/nested/style.css similarity index 100% rename from test/cases/function-publicpath/nested/style.css rename to test/cases/publicpath-function/nested/style.css diff --git a/test/cases/function-publicpath/react.svg b/test/cases/publicpath-function/react.svg similarity index 100% rename from test/cases/function-publicpath/react.svg rename to test/cases/publicpath-function/react.svg diff --git a/test/cases/function-publicpath/webpack.config.js b/test/cases/publicpath-function/webpack.config.js similarity index 100% rename from test/cases/function-publicpath/webpack.config.js rename to test/cases/publicpath-function/webpack.config.js From 0f7601276303829fc33dc4d73907e4025f8e7193 Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Sat, 6 Apr 2019 07:10:13 +1300 Subject: [PATCH 5/7] feat: test case checker supports nested directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to test output into nested directories, such as I’m about to add for the publicpath-function test. --- test/TestCases.test.js | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/test/TestCases.test.js b/test/TestCases.test.js index ee4cd52c..10a49bf0 100644 --- a/test/TestCases.test.js +++ b/test/TestCases.test.js @@ -59,18 +59,33 @@ describe('TestCases', () => { ); 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' - ); - expect(actualContent).toEqual(content); + + function compareDirectory(actual, expected) { + for (const file of fs.readdirSync(expected, { + withFileTypes: true, + })) { + if (file.isFile()) { + const content = fs.readFileSync( + path.resolve(expected, file.name), + 'utf-8' + ); + const actualContent = fs.readFileSync( + path.resolve(actual, file.name), + 'utf-8' + ); + expect(actualContent).toEqual(content); + } else if (file.isDirectory()) { + compareDirectory( + path.resolve(actual, file.name), + path.resolve(expected, file.name) + ); + } + } } + + const expectedDirectory = path.resolve(directoryForCase, 'expected'); + compareDirectory(outputDirectoryForCase, expectedDirectory); + done(); }); }, 10000); From 9ef1de00b58d6a4a759def6ee0617116127a05d7 Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Sat, 6 Apr 2019 07:14:08 +1300 Subject: [PATCH 6/7] fix: publicpath-function test demonstrates purpose With output of the CSS into a nested folder the computation of the relative publicPath makes sense. --- .../expected/{main.css => nested/style.css} | 0 test/cases/publicpath-function/index.js | 1 - test/cases/publicpath-function/webpack.config.js | 6 +++++- 3 files changed, 5 insertions(+), 2 deletions(-) rename test/cases/publicpath-function/expected/{main.css => nested/style.css} (100%) delete mode 100644 test/cases/publicpath-function/index.js diff --git a/test/cases/publicpath-function/expected/main.css b/test/cases/publicpath-function/expected/nested/style.css similarity index 100% rename from test/cases/publicpath-function/expected/main.css rename to test/cases/publicpath-function/expected/nested/style.css diff --git a/test/cases/publicpath-function/index.js b/test/cases/publicpath-function/index.js deleted file mode 100644 index d4e71361..00000000 --- a/test/cases/publicpath-function/index.js +++ /dev/null @@ -1 +0,0 @@ -import './nested/style.css'; diff --git a/test/cases/publicpath-function/webpack.config.js b/test/cases/publicpath-function/webpack.config.js index 30b243df..948b9db6 100644 --- a/test/cases/publicpath-function/webpack.config.js +++ b/test/cases/publicpath-function/webpack.config.js @@ -2,7 +2,10 @@ const Self = require('../../../'); const path = require('path') module.exports = { - entry: './index.js', + entry: { + // Specific CSS entry point, with output to a nested folder + 'nested/style': './nested/style.css', + }, module: { rules: [ { @@ -11,6 +14,7 @@ module.exports = { { loader: Self.loader, options: { + // Compute publicPath relative to the CSS output publicPath: (resourcePath, context) => path.relative(path.dirname(resourcePath), context) + '/', } }, From 1b691abf23ef62c45888eba50687b4e282baca65 Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Sat, 6 Apr 2019 07:27:56 +1300 Subject: [PATCH 7/7] fix: publicpath-function test demonstrates variable publicPath Added a second entry point / output at different nesting demonstrating the application of the publicPath function --- test/cases/publicpath-function/expected/nested/again/style.css | 2 ++ test/cases/publicpath-function/nested/again/style.css | 1 + test/cases/publicpath-function/webpack.config.js | 2 ++ 3 files changed, 5 insertions(+) create mode 100644 test/cases/publicpath-function/expected/nested/again/style.css create mode 100644 test/cases/publicpath-function/nested/again/style.css diff --git a/test/cases/publicpath-function/expected/nested/again/style.css b/test/cases/publicpath-function/expected/nested/again/style.css new file mode 100644 index 00000000..7fbc7c53 --- /dev/null +++ b/test/cases/publicpath-function/expected/nested/again/style.css @@ -0,0 +1,2 @@ +body { background: green; background-image: url(../../cd0bb358c45b584743d8ce4991777c42.svg); } + diff --git a/test/cases/publicpath-function/nested/again/style.css b/test/cases/publicpath-function/nested/again/style.css new file mode 100644 index 00000000..c21a33c8 --- /dev/null +++ b/test/cases/publicpath-function/nested/again/style.css @@ -0,0 +1 @@ +body { background: green; background-image: url(../../react.svg); } diff --git a/test/cases/publicpath-function/webpack.config.js b/test/cases/publicpath-function/webpack.config.js index 948b9db6..e7154f5e 100644 --- a/test/cases/publicpath-function/webpack.config.js +++ b/test/cases/publicpath-function/webpack.config.js @@ -5,6 +5,8 @@ module.exports = { entry: { // Specific CSS entry point, with output to a nested folder 'nested/style': './nested/style.css', + // Note that relative nesting of output is the same as that of the input + 'nested/again/style': './nested/again/style.css', }, module: { rules: [