Skip to content

Commit 7b1425a

Browse files
karlvrevilebottnawi
authored andcommitted
feat: publicPath can be a function (#373)
1 parent 272910c commit 7b1425a

File tree

15 files changed

+189
-12
lines changed

15 files changed

+189
-12
lines changed

README.md

+46
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ npm install --save-dev mini-css-extract-plugin
3838

3939
### Configuration
4040

41+
#### `publicPath`
42+
43+
Type: `String|Function`
44+
Default: the `publicPath` in `webpackOptions.output`
45+
46+
Specifies a custom public path for the target file(s).
47+
4148
#### Minimal example
4249

4350
**webpack.config.js**
@@ -74,6 +81,45 @@ module.exports = {
7481
}
7582
```
7683

84+
#### `publicPath` function example
85+
86+
**webpack.config.js**
87+
88+
```js
89+
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
90+
module.exports = {
91+
plugins: [
92+
new MiniCssExtractPlugin({
93+
// Options similar to the same options in webpackOptions.output
94+
// both options are optional
95+
filename: "[name].css",
96+
chunkFilename: "[id].css"
97+
})
98+
],
99+
module: {
100+
rules: [
101+
{
102+
test: /\.css$/,
103+
use: [
104+
{
105+
loader: MiniCssExtractPlugin.loader,
106+
options: {
107+
publicPath: (resourcePath, context) => {
108+
// publicPath is the relative path of the resource to the context
109+
// e.g. for ./css/admin/main.css the publicPath will be ../../
110+
// while for ./css/main.css the publicPath will be ../
111+
return path.relative(path.dirname(resourcePath), context) + '/'
112+
},
113+
}
114+
},
115+
"css-loader"
116+
]
117+
}
118+
]
119+
}
120+
}
121+
```
122+
77123
#### Advanced configuration example
78124

79125
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`.

src/loader.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import NodeTargetPlugin from 'webpack/lib/node/NodeTargetPlugin';
66
import LibraryTemplatePlugin from 'webpack/lib/LibraryTemplatePlugin';
77
import SingleEntryPlugin from 'webpack/lib/SingleEntryPlugin';
88
import LimitChunkCountPlugin from 'webpack/lib/optimize/LimitChunkCountPlugin';
9+
import validateOptions from 'schema-utils';
10+
11+
import schema from './options.json';
912

1013
const MODULE_TYPE = 'css/mini-extract';
1114
const pluginName = 'mini-css-extract-plugin';
@@ -29,12 +32,19 @@ const findModuleById = (modules, id) => {
2932

3033
export function pitch(request) {
3134
const query = loaderUtils.getOptions(this) || {};
35+
36+
validateOptions(schema, query, 'Mini CSS Extract Plugin Loader');
37+
3238
const loaders = this.loaders.slice(this.loaderIndex + 1);
3339
this.addDependency(this.resourcePath);
3440
const childFilename = '*'; // eslint-disable-line no-path-concat
3541
const publicPath =
3642
typeof query.publicPath === 'string'
37-
? query.publicPath
43+
? query.publicPath.endsWith('/')
44+
? query.publicPath
45+
: `${query.publicPath}/`
46+
: typeof query.publicPath === 'function'
47+
? query.publicPath(this.resourcePath, this.rootContext)
3848
: this._compilation.outputOptions.publicPath;
3949
const outputOptions = {
4050
filename: childFilename,

src/options.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"additionalProperties": true,
3+
"properties": {
4+
"publicPath": {
5+
"anyOf": [
6+
{
7+
"type": "string"
8+
},
9+
{
10+
"instanceof": "Function"
11+
}
12+
]
13+
}
14+
},
15+
"errorMessages": {
16+
"publicPath": "should be {String} or {Function} (https://github.com/webpack-contrib/mini-css-extract-plugin#publicpath)"
17+
},
18+
"type": "object"
19+
}

test/TestCases.test.js

+26-11
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,33 @@ describe('TestCases', () => {
5959
);
6060
return;
6161
}
62-
const expectedDirectory = path.resolve(directoryForCase, 'expected');
63-
for (const file of fs.readdirSync(expectedDirectory)) {
64-
const content = fs.readFileSync(
65-
path.resolve(expectedDirectory, file),
66-
'utf-8'
67-
);
68-
const actualContent = fs.readFileSync(
69-
path.resolve(outputDirectoryForCase, file),
70-
'utf-8'
71-
);
72-
expect(actualContent).toEqual(content);
62+
63+
function compareDirectory(actual, expected) {
64+
for (const file of fs.readdirSync(expected, {
65+
withFileTypes: true,
66+
})) {
67+
if (file.isFile()) {
68+
const content = fs.readFileSync(
69+
path.resolve(expected, file.name),
70+
'utf-8'
71+
);
72+
const actualContent = fs.readFileSync(
73+
path.resolve(actual, file.name),
74+
'utf-8'
75+
);
76+
expect(actualContent).toEqual(content);
77+
} else if (file.isDirectory()) {
78+
compareDirectory(
79+
path.resolve(actual, file.name),
80+
path.resolve(expected, file.name)
81+
);
82+
}
83+
}
7384
}
85+
86+
const expectedDirectory = path.resolve(directoryForCase, 'expected');
87+
compareDirectory(outputDirectoryForCase, expectedDirectory);
88+
7489
done();
7590
});
7691
}, 10000);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
body { background: green; background-image: url(../../cd0bb358c45b584743d8ce4991777c42.svg); }
2+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
body { background: red; background-image: url(../cd0bb358c45b584743d8ce4991777c42.svg); }
2+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
body { background: green; background-image: url(../../react.svg); }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
body { background: red; background-image: url(../react.svg); }
+1
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const Self = require('../../../');
2+
const path = require('path')
3+
4+
module.exports = {
5+
entry: {
6+
// Specific CSS entry point, with output to a nested folder
7+
'nested/style': './nested/style.css',
8+
// Note that relative nesting of output is the same as that of the input
9+
'nested/again/style': './nested/again/style.css',
10+
},
11+
module: {
12+
rules: [
13+
{
14+
test: /\.css$/,
15+
use: [
16+
{
17+
loader: Self.loader,
18+
options: {
19+
// Compute publicPath relative to the CSS output
20+
publicPath: (resourcePath, context) => path.relative(path.dirname(resourcePath), context) + '/',
21+
}
22+
},
23+
'css-loader',
24+
],
25+
}, {
26+
test: /\.(svg|png)$/,
27+
use: [{
28+
loader: 'file-loader',
29+
options: {
30+
filename: '[name].[ext]'
31+
}
32+
}]
33+
}
34+
],
35+
},
36+
plugins: [
37+
new Self({
38+
filename: '[name].css',
39+
}),
40+
],
41+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
body { background: red; background-image: url(/static/img/cd0bb358c45b584743d8ce4991777c42.svg); }
2+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './style.css';
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
body { background: red; background-image: url(./react.svg); }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const Self = require('../../../');
2+
3+
module.exports = {
4+
entry: './index.js',
5+
module: {
6+
rules: [
7+
{
8+
test: /\.css$/,
9+
use: [
10+
{
11+
loader: Self.loader,
12+
options: {
13+
publicPath: '/static/img'
14+
}
15+
},
16+
'css-loader',
17+
],
18+
}, {
19+
test: /\.(svg|png)$/,
20+
use: [{
21+
loader: 'file-loader',
22+
options: {
23+
filename: '[name].[ext]'
24+
}
25+
}]
26+
}
27+
],
28+
},
29+
plugins: [
30+
new Self({
31+
filename: '[name].css',
32+
}),
33+
],
34+
};

0 commit comments

Comments
 (0)