Skip to content

Commit

Permalink
feat: publicPath can be a function (#373)
Browse files Browse the repository at this point in the history
  • Loading branch information
karlvr authored and evilebottnawi committed Apr 8, 2019
1 parent 272910c commit 7b1425a
Show file tree
Hide file tree
Showing 15 changed files with 189 additions and 12 deletions.
46 changes: 46 additions & 0 deletions README.md
Expand Up @@ -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**
Expand Down Expand Up @@ -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`.
Expand Down
12 changes: 11 additions & 1 deletion src/loader.js
Expand Up @@ -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';
Expand All @@ -29,12 +32,19 @@ 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
? query.publicPath.endsWith('/')
? query.publicPath
: `${query.publicPath}/`
: typeof query.publicPath === 'function'
? query.publicPath(this.resourcePath, this.rootContext)
: this._compilation.outputOptions.publicPath;
const outputOptions = {
filename: childFilename,
Expand Down
19 changes: 19 additions & 0 deletions 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"
}
37 changes: 26 additions & 11 deletions test/TestCases.test.js
Expand Up @@ -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);
Expand Down
@@ -0,0 +1,2 @@
body { background: green; background-image: url(../../cd0bb358c45b584743d8ce4991777c42.svg); }

2 changes: 2 additions & 0 deletions test/cases/publicpath-function/expected/nested/style.css
@@ -0,0 +1,2 @@
body { background: red; background-image: url(../cd0bb358c45b584743d8ce4991777c42.svg); }

1 change: 1 addition & 0 deletions test/cases/publicpath-function/nested/again/style.css
@@ -0,0 +1 @@
body { background: green; background-image: url(../../react.svg); }
1 change: 1 addition & 0 deletions test/cases/publicpath-function/nested/style.css
@@ -0,0 +1 @@
body { background: red; background-image: url(../react.svg); }
1 change: 1 addition & 0 deletions test/cases/publicpath-function/react.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions test/cases/publicpath-function/webpack.config.js
@@ -0,0 +1,41 @@
const Self = require('../../../');
const path = require('path')

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: [
{
test: /\.css$/,
use: [
{
loader: Self.loader,
options: {
// Compute publicPath relative to the CSS output
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',
}),
],
};
2 changes: 2 additions & 0 deletions test/cases/publicpath-trailing-slash/expected/main.css
@@ -0,0 +1,2 @@
body { background: red; background-image: url(/static/img/cd0bb358c45b584743d8ce4991777c42.svg); }

1 change: 1 addition & 0 deletions test/cases/publicpath-trailing-slash/index.js
@@ -0,0 +1 @@
import './style.css';
1 change: 1 addition & 0 deletions test/cases/publicpath-trailing-slash/react.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/cases/publicpath-trailing-slash/style.css
@@ -0,0 +1 @@
body { background: red; background-image: url(./react.svg); }
34 changes: 34 additions & 0 deletions 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',
}),
],
};

0 comments on commit 7b1425a

Please sign in to comment.