Skip to content

Commit f23954c

Browse files
feat: modules.mode callback function (Issue #1063)
1 parent 431f620 commit f23954c

File tree

9 files changed

+260
-32
lines changed

9 files changed

+260
-32
lines changed

src/options.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@
3737
"additionalProperties": false,
3838
"properties": {
3939
"mode": {
40-
"enum": ["local", "global", "pure"]
40+
"anyOf": [
41+
{
42+
"enum": ["local", "global", "pure"]
43+
},
44+
{
45+
"instanceof": "Function"
46+
}
47+
]
4148
},
4249
"localIdentName": {
4350
"type": "string"

src/utils.js

+39-23
Original file line numberDiff line numberDiff line change
@@ -115,25 +115,24 @@ function getModulesPlugins(options, loaderContext) {
115115
modulesOptions = Object.assign({}, modulesOptions, options.modules);
116116
}
117117

118-
return [
119-
modulesValues,
120-
localByDefault({ mode: modulesOptions.mode }),
121-
extractImports(),
122-
modulesScope({
123-
generateScopedName: function generateScopedName(exportName) {
124-
let localIdent = modulesOptions.getLocalIdent(
125-
loaderContext,
126-
modulesOptions.localIdentName,
127-
exportName,
128-
{
129-
context: modulesOptions.context,
130-
hashPrefix: modulesOptions.hashPrefix,
131-
regExp: modulesOptions.localIdentRegExp,
132-
}
133-
);
118+
if (typeof modulesOptions.mode === 'function') {
119+
const modeFromFunction = modulesOptions.mode(loaderContext.resourcePath);
120+
121+
if (modeFromFunction === 'local' || modeFromFunction === 'global') {
122+
modulesOptions.mode = modeFromFunction;
123+
}
124+
}
125+
126+
let plugins = [];
134127

135-
if (!localIdent) {
136-
localIdent = getLocalIdent(
128+
try {
129+
plugins = [
130+
modulesValues,
131+
localByDefault({ mode: modulesOptions.mode }),
132+
extractImports(),
133+
modulesScope({
134+
generateScopedName: function generateScopedName(exportName) {
135+
let localIdent = modulesOptions.getLocalIdent(
137136
loaderContext,
138137
modulesOptions.localIdentName,
139138
exportName,
@@ -143,12 +142,29 @@ function getModulesPlugins(options, loaderContext) {
143142
regExp: modulesOptions.localIdentRegExp,
144143
}
145144
);
146-
}
147145

148-
return localIdent;
149-
},
150-
}),
151-
];
146+
if (!localIdent) {
147+
localIdent = getLocalIdent(
148+
loaderContext,
149+
modulesOptions.localIdentName,
150+
exportName,
151+
{
152+
context: modulesOptions.context,
153+
hashPrefix: modulesOptions.hashPrefix,
154+
regExp: modulesOptions.localIdentRegExp,
155+
}
156+
);
157+
}
158+
159+
return localIdent;
160+
},
161+
}),
162+
];
163+
} catch (error) {
164+
loaderContext.emitError(error);
165+
}
166+
167+
return plugins;
152168
}
153169

154170
function normalizeSourceMap(map) {

test/__snapshots__/modules-option.test.js.snap

+102
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,108 @@ div:not(.\\\\1F600) {
363363
364364
exports[`"modules" option issue #995: warnings 1`] = `Array []`;
365365
366+
exports[`"modules" option issue #1063 throw error: errors 1`] = `
367+
Array [
368+
"ModuleError: Module Error (from \`replaced original path\`):
369+
options.mode must be either \\"global\\", \\"local\\" or \\"pure\\" (default \\"local\\")",
370+
"ModuleError: Module Error (from \`replaced original path\`):
371+
options.mode must be either \\"global\\", \\"local\\" or \\"pure\\" (default \\"local\\")",
372+
]
373+
`;
374+
375+
exports[`"modules" option issue #1063 throw error: module 1`] = `
376+
"// Imports
377+
var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../../src/runtime/api.js\\");
378+
exports = ___CSS_LOADER_API_IMPORT___(false);
379+
// Module
380+
exports.push([module.id, \\".className {\\\\n color: green;\\\\n}\\\\n\\\\n:global(.otherClass) {\\\\n color: blue;\\\\n}\\\\n\\", \\"\\"]);
381+
// Exports
382+
module.exports = exports;
383+
"
384+
`;
385+
386+
exports[`"modules" option issue #1063 throw error: module 2`] = `
387+
"// Imports
388+
var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../../src/runtime/api.js\\");
389+
exports = ___CSS_LOADER_API_IMPORT___(false);
390+
// Module
391+
exports.push([module.id, \\".className {\\\\n color: green;\\\\n}\\\\n\\\\n:local(.otherClass) {\\\\n color: blue;\\\\n}\\\\n\\", \\"\\"]);
392+
// Exports
393+
module.exports = exports;
394+
"
395+
`;
396+
397+
exports[`"modules" option issue #1063 throw error: result 1`] = `
398+
".className {
399+
color: green;
400+
}
401+
402+
:global(.otherClass) {
403+
color: blue;
404+
}
405+
.className {
406+
color: green;
407+
}
408+
409+
:local(.otherClass) {
410+
color: blue;
411+
}
412+
"
413+
`;
414+
415+
exports[`"modules" option issue #1063 throw error: warnings 1`] = `Array []`;
416+
417+
exports[`"modules" option issue #1063: errors 1`] = `Array []`;
418+
419+
exports[`"modules" option issue #1063: module 1`] = `
420+
"// Imports
421+
var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../../src/runtime/api.js\\");
422+
exports = ___CSS_LOADER_API_IMPORT___(false);
423+
// Module
424+
exports.push([module.id, \\"._1iR-jQngOXizCZOLZck-4P {\\\\n color: green;\\\\n}\\\\n\\\\n.otherClass {\\\\n color: blue;\\\\n}\\\\n\\", \\"\\"]);
425+
// Exports
426+
exports.locals = {
427+
\\"className\\": \\"_1iR-jQngOXizCZOLZck-4P\\"
428+
};
429+
module.exports = exports;
430+
"
431+
`;
432+
433+
exports[`"modules" option issue #1063: module 2`] = `
434+
"// Imports
435+
var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../../src/runtime/api.js\\");
436+
exports = ___CSS_LOADER_API_IMPORT___(false);
437+
// Module
438+
exports.push([module.id, \\"._2WZy6LhVz_KGVbdNCAmL6C {\\\\n color: green;\\\\n}\\\\n\\\\n._7mafm73rmgiKuViZtzNmn {\\\\n color: blue;\\\\n}\\\\n\\", \\"\\"]);
439+
// Exports
440+
exports.locals = {
441+
\\"className\\": \\"_2WZy6LhVz_KGVbdNCAmL6C\\",
442+
\\"otherClass\\": \\"_7mafm73rmgiKuViZtzNmn\\"
443+
};
444+
module.exports = exports;
445+
"
446+
`;
447+
448+
exports[`"modules" option issue #1063: result 1`] = `
449+
"._1iR-jQngOXizCZOLZck-4P {
450+
color: green;
451+
}
452+
453+
.otherClass {
454+
color: blue;
455+
}
456+
._2WZy6LhVz_KGVbdNCAmL6C {
457+
color: green;
458+
}
459+
460+
._7mafm73rmgiKuViZtzNmn {
461+
color: blue;
462+
}
463+
"
464+
`;
465+
466+
exports[`"modules" option issue #1063: warnings 1`] = `Array []`;
467+
366468
exports[`"modules" option should avoid unnecessary "require": errors 1`] = `Array []`;
367469
368470
exports[`"modules" option should avoid unnecessary "require": module 1`] = `

test/__snapshots__/validate-options.test.js.snap

+40-8
Original file line numberDiff line numberDiff line change
@@ -86,26 +86,58 @@ exports[`validate options should throw an error on the "modules" option with "{"
8686
8787
exports[`validate options should throw an error on the "modules" option with "{"mode":"globals"}" value 1`] = `
8888
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
89-
- options.modules.mode should be one of these:
90-
\\"local\\" | \\"global\\" | \\"pure\\""
89+
- options.modules should be one of these:
90+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { mode?, localIdentName?, localIdentRegExp?, context?, hashPrefix?, getLocalIdent? }
91+
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
92+
Details:
93+
* options.modules.mode should be one of these:
94+
\\"local\\" | \\"global\\" | \\"pure\\" | function
95+
Details:
96+
* options.modules.mode should be one of these:
97+
\\"local\\" | \\"global\\" | \\"pure\\"
98+
* options.modules.mode should be an instance of function."
9199
`;
92100
93101
exports[`validate options should throw an error on the "modules" option with "{"mode":"locals"}" value 1`] = `
94102
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
95-
- options.modules.mode should be one of these:
96-
\\"local\\" | \\"global\\" | \\"pure\\""
103+
- options.modules should be one of these:
104+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { mode?, localIdentName?, localIdentRegExp?, context?, hashPrefix?, getLocalIdent? }
105+
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
106+
Details:
107+
* options.modules.mode should be one of these:
108+
\\"local\\" | \\"global\\" | \\"pure\\" | function
109+
Details:
110+
* options.modules.mode should be one of these:
111+
\\"local\\" | \\"global\\" | \\"pure\\"
112+
* options.modules.mode should be an instance of function."
97113
`;
98114
99115
exports[`validate options should throw an error on the "modules" option with "{"mode":"pures"}" value 1`] = `
100116
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
101-
- options.modules.mode should be one of these:
102-
\\"local\\" | \\"global\\" | \\"pure\\""
117+
- options.modules should be one of these:
118+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { mode?, localIdentName?, localIdentRegExp?, context?, hashPrefix?, getLocalIdent? }
119+
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
120+
Details:
121+
* options.modules.mode should be one of these:
122+
\\"local\\" | \\"global\\" | \\"pure\\" | function
123+
Details:
124+
* options.modules.mode should be one of these:
125+
\\"local\\" | \\"global\\" | \\"pure\\"
126+
* options.modules.mode should be an instance of function."
103127
`;
104128
105129
exports[`validate options should throw an error on the "modules" option with "{"mode":true}" value 1`] = `
106130
"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
107-
- options.modules.mode should be one of these:
108-
\\"local\\" | \\"global\\" | \\"pure\\""
131+
- options.modules should be one of these:
132+
boolean | \\"local\\" | \\"global\\" | \\"pure\\" | object { mode?, localIdentName?, localIdentRegExp?, context?, hashPrefix?, getLocalIdent? }
133+
-> Enables/Disables CSS Modules and their configuration (https://github.com/webpack-contrib/css-loader#modules).
134+
Details:
135+
* options.modules.mode should be one of these:
136+
\\"local\\" | \\"global\\" | \\"pure\\" | function
137+
Details:
138+
* options.modules.mode should be one of these:
139+
\\"local\\" | \\"global\\" | \\"pure\\"
140+
* options.modules.mode should be an instance of function."
109141
`;
110142
111143
exports[`validate options should throw an error on the "modules" option with "globals" value 1`] = `
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.className {
2+
color: green;
3+
}
4+
5+
:local(.otherClass) {
6+
color: blue;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import css1 from './local.css';
2+
import css2 from './global.css';
3+
4+
__export__ = css1 + css2;
5+
6+
export default css1 + css2;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.className {
2+
color: green;
3+
}
4+
5+
:global(.otherClass) {
6+
color: blue;
7+
}

test/modules-option.test.js

+50
Original file line numberDiff line numberDiff line change
@@ -565,4 +565,54 @@ describe('"modules" option', () => {
565565
expect(getWarnings(stats)).toMatchSnapshot('warnings');
566566
expect(getErrors(stats)).toMatchSnapshot('errors');
567567
});
568+
569+
it('issue #1063', async () => {
570+
const compiler = getCompiler('./modules/issue-1063/issue-1063.js', {
571+
modules: {
572+
mode: (resourcePath) => {
573+
if (resourcePath === 'global.css') {
574+
return 'global';
575+
}
576+
577+
return 'local';
578+
},
579+
},
580+
});
581+
const stats = await compile(compiler);
582+
583+
expect(
584+
getModuleSource('./modules/issue-1063/local.css', stats)
585+
).toMatchSnapshot('module');
586+
expect(
587+
getModuleSource('./modules/issue-1063/global.css', stats)
588+
).toMatchSnapshot('module');
589+
expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
590+
'result'
591+
);
592+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
593+
expect(getErrors(stats)).toMatchSnapshot('errors');
594+
});
595+
596+
it('issue #1063 throw error', async () => {
597+
const compiler = getCompiler('./modules/issue-1063/issue-1063.js', {
598+
modules: {
599+
mode: () => {
600+
return 'not local, global or pure';
601+
},
602+
},
603+
});
604+
const stats = await compile(compiler);
605+
606+
expect(
607+
getModuleSource('./modules/issue-1063/local.css', stats)
608+
).toMatchSnapshot('module');
609+
expect(
610+
getModuleSource('./modules/issue-1063/global.css', stats)
611+
).toMatchSnapshot('module');
612+
expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
613+
'result'
614+
);
615+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
616+
expect(getErrors(stats)).toMatchSnapshot('errors');
617+
});
568618
});

test/validate-options.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('validate options', () => {
2020
{ mode: 'global' },
2121
{ mode: 'local' },
2222
{ mode: 'pure' },
23+
{ mode: () => 'local' },
2324
{ localIdentName: '[path][name]__[local]--[hash:base64:5]' },
2425
{ context: 'context' },
2526
{ hashPrefix: 'hash' },

0 commit comments

Comments
 (0)