Skip to content

Commit 4806f02

Browse files
committed
feat(options.filename): add ability to transform filename
1 parent 44d00ea commit 4806f02

File tree

8 files changed

+152
-68
lines changed

8 files changed

+152
-68
lines changed

src/index.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const pluginName = 'mini-css-extract-plugin';
1616
const REGEXP_CHUNKHASH = /\[chunkhash(?::(\d+))?\]/i;
1717
const REGEXP_CONTENTHASH = /\[contenthash(?::(\d+))?\]/i;
1818
const REGEXP_NAME = /\[name\]/i;
19+
const REGEXP_PLACEHOLDERS = /\[(name|id|chunkhash)\]/g;
20+
const DEFAULT_FILENAME = '[name].css';
1921

2022
class CssDependency extends webpack.Dependency {
2123
constructor(
@@ -121,23 +123,19 @@ class MiniCssExtractPlugin {
121123
constructor(options) {
122124
this.options = Object.assign(
123125
{
124-
filename: '[name].css',
126+
filename: DEFAULT_FILENAME,
125127
},
126128
options
127129
);
128130

129131
if (!this.options.chunkFilename) {
130132
const { filename } = this.options;
131-
const hasName = filename.includes('[name]');
132-
const hasId = filename.includes('[id]');
133-
const hasChunkHash = filename.includes('[chunkhash]');
134-
135133
// Anything changing depending on chunk is fine
136-
if (hasChunkHash || hasName || hasId) {
134+
if (typeof filename === 'string' && REGEXP_PLACEHOLDERS.test(filename)) {
137135
this.options.chunkFilename = filename;
138136
} else {
139137
// Elsewise prefix '[id].' in front of the basename to make it changing
140-
this.options.chunkFilename = filename.replace(
138+
this.options.chunkFilename = DEFAULT_FILENAME.replace(
141139
/(^|\/)([^/]*(?:\?|$))/,
142140
'$1[id].$2'
143141
);
@@ -187,6 +185,23 @@ class MiniCssExtractPlugin {
187185
const renderedModules = Array.from(chunk.modulesIterable).filter(
188186
(module) => module.type === MODULE_TYPE
189187
);
188+
const { filename } = this.options;
189+
let filenameTemplate = filename;
190+
if (typeof filename === 'function') {
191+
const {
192+
hash: chunkhash,
193+
contentHash: contenthash,
194+
name,
195+
id,
196+
} = chunk;
197+
198+
filenameTemplate = filename({
199+
chunkhash,
200+
contenthash,
201+
name,
202+
id,
203+
});
204+
}
190205

191206
if (renderedModules.length > 0) {
192207
result.push({
@@ -197,7 +212,7 @@ class MiniCssExtractPlugin {
197212
renderedModules,
198213
compilation.runtimeTemplate.requestShortener
199214
),
200-
filenameTemplate: this.options.filename,
215+
filenameTemplate,
201216
pathOptions: {
202217
chunk,
203218
contentHashType: MODULE_TYPE,

test/TestCases.test.js

Lines changed: 91 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -32,70 +32,101 @@ describe('TestCases', () => {
3232
for (const directory of fs.readdirSync(casesDirectory)) {
3333
if (!/^(\.|_)/.test(directory)) {
3434
// eslint-disable-next-line no-loop-func
35-
it(`${directory} should compile to the expected result`, (done) => {
36-
const directoryForCase = path.resolve(casesDirectory, directory);
37-
const outputDirectoryForCase = path.resolve(outputDirectory, directory);
38-
// eslint-disable-next-line import/no-dynamic-require, global-require
39-
const webpackConfig = require(path.resolve(
40-
directoryForCase,
41-
'webpack.config.js'
42-
));
43-
44-
for (const config of [].concat(webpackConfig)) {
45-
Object.assign(
46-
config,
47-
{
48-
mode: 'none',
49-
context: directoryForCase,
50-
output: Object.assign(
51-
{
52-
path: outputDirectoryForCase,
53-
},
54-
config.output
55-
),
56-
},
57-
config
35+
it(
36+
`${directory} should compile to the expected result`,
37+
(done) => {
38+
const directoryForCase = path.resolve(casesDirectory, directory);
39+
const outputDirectoryForCase = path.resolve(
40+
outputDirectory,
41+
directory
5842
);
59-
}
60-
61-
webpack(webpackConfig, (err, stats) => {
62-
if (err) {
63-
done(err);
64-
return;
65-
}
66-
67-
done();
68-
69-
// eslint-disable-next-line no-console
70-
console.log(
71-
stats.toString({
72-
context: path.resolve(__dirname, '..'),
73-
chunks: true,
74-
chunkModules: true,
75-
modules: false,
76-
})
77-
);
78-
79-
if (stats.hasErrors()) {
80-
done(
81-
new Error(
82-
stats.toString({
83-
context: path.resolve(__dirname, '..'),
84-
errorDetails: true,
85-
})
86-
)
43+
// eslint-disable-next-line import/no-dynamic-require, global-require
44+
const webpackConfig = require(path.resolve(
45+
directoryForCase,
46+
'webpack.config.js'
47+
));
48+
for (const config of [].concat(webpackConfig)) {
49+
Object.assign(
50+
config,
51+
{
52+
mode: 'none',
53+
context: directoryForCase,
54+
output: Object.assign(
55+
{
56+
path: outputDirectoryForCase,
57+
},
58+
config.output
59+
),
60+
},
61+
config
8762
);
88-
89-
return;
9063
}
64+
webpack(webpackConfig, (err, stats) => {
65+
if (err) {
66+
done(err);
67+
return;
68+
}
69+
done();
70+
// eslint-disable-next-line no-console
71+
console.log(
72+
stats.toString({
73+
context: path.resolve(__dirname, '..'),
74+
chunks: true,
75+
chunkModules: true,
76+
modules: false,
77+
})
78+
);
79+
if (stats.hasErrors()) {
80+
done(
81+
new Error(
82+
stats.toString({
83+
context: path.resolve(__dirname, '..'),
84+
errorDetails: true,
85+
})
86+
)
87+
);
88+
return;
89+
}
90+
const expectedDirectory = path.resolve(
91+
directoryForCase,
92+
'expected'
93+
);
9194

92-
const expectedDirectory = path.resolve(directoryForCase, 'expected');
93-
94-
compareDirectory(outputDirectoryForCase, expectedDirectory);
95-
96-
done();
97-
});
98-
}, 10000);
95+
for (const file of walkSync(expectedDirectory)) {
96+
const expectedContent = fs.readFileSync(
97+
path.resolve(expectedDirectory, path.basename(file)),
98+
'utf-8'
99+
);
100+
101+
const actualContent = fs.readFileSync(
102+
path.resolve(outputDirectoryForCase, file),
103+
'utf-8'
104+
);
105+
106+
expect(actualContent).toEqual(expectedContent);
107+
}
108+
done();
109+
});
110+
},
111+
10000
112+
);
99113
}
100114
}
101115
});
116+
117+
/**
118+
* Synchronously traverse directory of files
119+
* @param {String} dir
120+
* @returns {String} // path to file or directory
121+
*/
122+
function* walkSync(dir) {
123+
for (const file of fs.readdirSync(dir)) {
124+
const pathToFile = path.join(dir, file);
125+
const isDirectory = fs.statSync(pathToFile).isDirectory();
126+
if (isDirectory) {
127+
yield* walkSync(pathToFile);
128+
} else {
129+
yield pathToFile;
130+
}
131+
}
132+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
body { background: red; }
2+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
body { background: green; }
2+

test/cases/filename/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './style.css';

test/cases/filename/style1.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
body { background: red; }

test/cases/filename/style2.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
body { background: green; }
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const Self = require('../../../');
2+
3+
module.exports = [1, 2].map((n) => {
4+
return {
5+
entry: {
6+
'demo/js/main': './index.js',
7+
},
8+
module: {
9+
rules: [
10+
{
11+
test: /\.css$/,
12+
use: [Self.loader, 'css-loader'],
13+
},
14+
],
15+
},
16+
output: {
17+
filename: '[name].[chunkhash:8].js',
18+
},
19+
resolve: {
20+
alias: {
21+
'./style.css': `./style${n}.css`,
22+
},
23+
},
24+
plugins: [
25+
new Self({
26+
filename: ({ name, chunkhash }) =>
27+
`${name.replace('/js/', '/css/')}.${chunkhash.substring(0, 8)}.css`,
28+
}),
29+
],
30+
};
31+
});

0 commit comments

Comments
 (0)