Skip to content

Commit 747d62b

Browse files
feat: allow named exports to have underscores in names (#1209)
1 parent 7bfe85d commit 747d62b

File tree

8 files changed

+222
-8
lines changed

8 files changed

+222
-8
lines changed

src/utils.js

+19-7
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@ function getFilter(filter, resourcePath) {
111111
};
112112
}
113113

114+
function getValidLocalName(localName, exportLocalsConvention) {
115+
if (exportLocalsConvention === 'dashesOnly') {
116+
return dashesCamelCase(localName);
117+
}
118+
119+
return camelCase(localName);
120+
}
121+
114122
const moduleRegExp = /\.module(s)?\.\w+$/i;
115123
const icssRegExp = /\.icss\.\w+$/i;
116124

@@ -203,9 +211,12 @@ function getModulesOptions(rawOptions, loaderContext) {
203211
);
204212
}
205213

206-
if (modulesOptions.exportLocalsConvention !== 'camelCaseOnly') {
214+
if (
215+
modulesOptions.exportLocalsConvention !== 'camelCaseOnly' &&
216+
modulesOptions.exportLocalsConvention !== 'dashesOnly'
217+
) {
207218
throw new Error(
208-
'The "modules.namedExport" option requires the "modules.exportLocalsConvention" option to be "camelCaseOnly"'
219+
'The "modules.namedExport" option requires the "modules.exportLocalsConvention" option to be "camelCaseOnly" or "dashesOnly"'
209220
);
210221
}
211222
}
@@ -516,7 +527,10 @@ function getModuleCode(result, api, replacements, options, loaderContext) {
516527
code = code.replace(new RegExp(replacementName, 'g'), () =>
517528
options.modules.namedExport
518529
? `" + ${importName}_NAMED___[${JSON.stringify(
519-
camelCase(localName)
530+
getValidLocalName(
531+
localName,
532+
options.modules.exportLocalsConvention
533+
)
520534
)}] + "`
521535
: `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
522536
);
@@ -551,9 +565,7 @@ function getExportCode(exports, replacements, options) {
551565

552566
const addExportToLocalsCode = (name, value) => {
553567
if (options.modules.namedExport) {
554-
localsCode += `export const ${camelCase(name)} = ${JSON.stringify(
555-
value
556-
)};\n`;
568+
localsCode += `export const ${name} = ${JSON.stringify(value)};\n`;
557569
} else {
558570
if (localsCode) {
559571
localsCode += `,\n`;
@@ -609,7 +621,7 @@ function getExportCode(exports, replacements, options) {
609621
localsCode = localsCode.replace(new RegExp(replacementName, 'g'), () => {
610622
if (options.modules.namedExport) {
611623
return `" + ${importName}_NAMED___[${JSON.stringify(
612-
camelCase(localName)
624+
getValidLocalName(localName, options.modules.exportLocalsConvention)
613625
)}] + "`;
614626
} else if (options.modules.exportOnlyLocals) {
615627
return `" + ${importName}[${JSON.stringify(localName)}] + "`;

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

+104-1
Original file line numberDiff line numberDiff line change
@@ -1614,7 +1614,7 @@ exports[`"modules" option should throw an error when class has unsupported name
16141614
exports[`"modules" option should throw an error when the "namedExport" is enabled and the "exportLocalsConvention" options has not "camelCaseOnly" value: errors 1`] = `
16151615
Array [
16161616
"ModuleBuildError: Module build failed (from \`replaced original path\`):
1617-
Error: The \\"modules.namedExport\\" option requires the \\"modules.exportLocalsConvention\\" option to be \\"camelCaseOnly\\"",
1617+
Error: The \\"modules.namedExport\\" option requires the \\"modules.exportLocalsConvention\\" option to be \\"camelCaseOnly\\" or \\"dashesOnly\\"",
16181618
]
16191619
`;
16201620

@@ -1629,6 +1629,15 @@ Error: The \\"modules.namedExport\\" option requires the \\"esModules\\" option
16291629

16301630
exports[`"modules" option should throw an error when the "namedExport" option is "true", but the "esModule" is "false": warnings 1`] = `Array []`;
16311631

1632+
exports[`"modules" option should throw error with composes when the "namedExport" is enabled and "exportLocalsConvention" options has invalid value: errors 1`] = `
1633+
Array [
1634+
"ModuleBuildError: Module build failed (from \`replaced original path\`):
1635+
Error: The \\"modules.namedExport\\" option requires the \\"modules.exportLocalsConvention\\" option to be \\"camelCaseOnly\\" or \\"dashesOnly\\"",
1636+
]
1637+
`;
1638+
1639+
exports[`"modules" option should throw error with composes when the "namedExport" is enabled and "exportLocalsConvention" options has invalid value: warnings 1`] = `Array []`;
1640+
16321641
exports[`"modules" option should work and correctly replace escaped symbols: errors 1`] = `Array []`;
16331642

16341643
exports[`"modules" option should work and correctly replace escaped symbols: module 1`] = `
@@ -3719,6 +3728,39 @@ Object {
37193728

37203729
exports[`"modules" option should work js template with "namedExport" option: warnings 1`] = `Array []`;
37213730

3731+
exports[`"modules" option should work when the "namedExport" is enabled and the "exportLocalsConvention" options has "dashesOnly" value: errors 1`] = `Array []`;
3732+
3733+
exports[`"modules" option should work when the "namedExport" is enabled and the "exportLocalsConvention" options has "dashesOnly" value: module 1`] = `
3734+
"// Imports
3735+
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\";
3736+
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]});
3737+
// Module
3738+
___CSS_LOADER_EXPORT___.push([module.id, \\".foo_barBaz {\\\\n color: red;\\\\n}\\\\n\\\\n.bar {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]);
3739+
// Exports
3740+
export const foo_barBaz = \\"foo_barBaz\\";
3741+
export default ___CSS_LOADER_EXPORT___;
3742+
"
3743+
`;
3744+
3745+
exports[`"modules" option should work when the "namedExport" is enabled and the "exportLocalsConvention" options has "dashesOnly" value: result 1`] = `
3746+
Array [
3747+
Array [
3748+
"./modules/namedExport/dashesOnly/index.css",
3749+
".foo_barBaz {
3750+
color: red;
3751+
}
3752+
3753+
.bar {
3754+
color: red;
3755+
}
3756+
",
3757+
"",
3758+
],
3759+
]
3760+
`;
3761+
3762+
exports[`"modules" option should work when the "namedExport" is enabled and the "exportLocalsConvention" options has "dashesOnly" value: warnings 1`] = `Array []`;
3763+
37223764
exports[`"modules" option should work with "exportOnlyLocals" and "esModule" with "false" value options: errors 1`] = `Array []`;
37233765

37243766
exports[`"modules" option should work with "exportOnlyLocals" and "esModule" with "false" value options: module 1`] = `
@@ -12152,6 +12194,67 @@ Array [
1215212194

1215312195
exports[`"modules" option should work with case \`values-10\` (\`modules\` value is \`true)\`: warnings 1`] = `Array []`;
1215412196

12197+
exports[`"modules" option should work with composes when the "namedExport" is enabled and "exportLocalsConvention" options has "dashesOnly" value: errors 1`] = `Array []`;
12198+
12199+
exports[`"modules" option should work with composes when the "namedExport" is enabled and "exportLocalsConvention" options has "dashesOnly" value: module 1`] = `
12200+
"// Imports
12201+
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\";
12202+
import ___CSS_LOADER_ICSS_IMPORT_0___, * as ___CSS_LOADER_ICSS_IMPORT_0____NAMED___ from \\"-!../../../../../src/index.js??[ident]!./values.css\\";
12203+
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]});
12204+
___CSS_LOADER_EXPORT___.i(___CSS_LOADER_ICSS_IMPORT_0___, \\"\\", true);
12205+
// Module
12206+
___CSS_LOADER_EXPORT___.push([module.id, \\"._ghi {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"v_def\\"] + \\";\\\\n}\\\\n\\\\n._my-class {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"sWhite\\"] + \\";\\\\n}\\\\n\\\\n._other {\\\\n display: \\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"mSmall\\"] + \\";\\\\n}\\\\n\\\\n._other-other {\\\\n width: \\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"v_def\\"] + \\";\\\\n}\\\\n\\\\n._green {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"v_otherOther\\"] + \\";\\\\n}\\\\n\\", \\"\\"]);
12207+
// Exports
12208+
export const v_def = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"v_def\\"] + \\"\\";
12209+
export const v_otherOther = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"v_otherOther\\"] + \\"\\";
12210+
export const sWhite = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"sWhite\\"] + \\"\\";
12211+
export const mSmall = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"mSmall\\"] + \\"\\";
12212+
export const ghi = \\"_ghi\\";
12213+
export const myClass = \\"_my-class\\";
12214+
export const other = \\"_other\\";
12215+
export const otherOther = \\"_other-other\\";
12216+
export const green = \\"_green\\";
12217+
export default ___CSS_LOADER_EXPORT___;
12218+
"
12219+
`;
12220+
12221+
exports[`"modules" option should work with composes when the "namedExport" is enabled and "exportLocalsConvention" options has "dashesOnly" value: result 1`] = `
12222+
Array [
12223+
Array [
12224+
"../../src/index.js?[ident]!./modules/namedExport/composes/values.css",
12225+
"
12226+
",
12227+
"",
12228+
],
12229+
Array [
12230+
"./modules/namedExport/composes/composes.css",
12231+
"._ghi {
12232+
color: red;
12233+
}
12234+
12235+
._my-class {
12236+
color: white;
12237+
}
12238+
12239+
._other {
12240+
display: (min-width: 320px);
12241+
}
12242+
12243+
._other-other {
12244+
width: red;
12245+
}
12246+
12247+
._green {
12248+
color: green;
12249+
}
12250+
",
12251+
"",
12252+
],
12253+
]
12254+
`;
12255+
12256+
exports[`"modules" option should work with composes when the "namedExport" is enabled and "exportLocalsConvention" options has "dashesOnly" value: warnings 1`] = `Array []`;
12257+
1215512258
exports[`"modules" option should work with the "[local]" placeholder for the "localIdentName" option: errors 1`] = `Array []`;
1215612259

1215712260
exports[`"modules" option should work with the "[local]" placeholder for the "localIdentName" option: module 1`] = `
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@value v_def from './values.css';
2+
@value v_other-other from './values.css';
3+
@value s-white from './values.css';
4+
@value m-small from './values.css';
5+
6+
.ghi {
7+
color: v_def;
8+
}
9+
10+
.my-class {
11+
color: s-white;
12+
}
13+
14+
.other {
15+
display: m-small;
16+
}
17+
18+
.other-other {
19+
width: v_def;
20+
}
21+
22+
.green {
23+
color: v_other-other;
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import css from './composes.css';
2+
3+
__export__ = css;
4+
5+
export default css;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@value v_def: red;
2+
@value v_other-other: green;
3+
@value s-white: white;
4+
@value m-small: (min-width: 320px);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
:local(.foo_barBaz) {
2+
color: red;
3+
}
4+
5+
:global(.bar) {
6+
color: red;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import css from './index.css';
2+
3+
__export__ = css;
4+
5+
export default css;

test/modules-option.test.js

+54
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,60 @@ describe('"modules" option', () => {
12031203
expect(getErrors(stats)).toMatchSnapshot('errors');
12041204
});
12051205

1206+
it('should work when the "namedExport" is enabled and the "exportLocalsConvention" options has "dashesOnly" value', async () => {
1207+
const compiler = getCompiler('./modules/namedExport/dashesOnly/index.js', {
1208+
modules: {
1209+
localIdentName: '[local]',
1210+
namedExport: true,
1211+
exportLocalsConvention: 'dashesOnly',
1212+
},
1213+
});
1214+
const stats = await compile(compiler);
1215+
1216+
expect(
1217+
getModuleSource('./modules/namedExport/dashesOnly/index.css', stats)
1218+
).toMatchSnapshot('module');
1219+
expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
1220+
'result'
1221+
);
1222+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
1223+
expect(getErrors(stats, true)).toMatchSnapshot('errors');
1224+
});
1225+
1226+
it('should work with composes when the "namedExport" is enabled and "exportLocalsConvention" options has "dashesOnly" value', async () => {
1227+
const compiler = getCompiler('./modules/namedExport/composes/composes.js', {
1228+
modules: {
1229+
localIdentName: '_[local]',
1230+
namedExport: true,
1231+
exportLocalsConvention: 'dashesOnly',
1232+
},
1233+
});
1234+
const stats = await compile(compiler);
1235+
1236+
expect(
1237+
getModuleSource('./modules/namedExport/composes/composes.css', stats)
1238+
).toMatchSnapshot('module');
1239+
expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
1240+
'result'
1241+
);
1242+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
1243+
expect(getErrors(stats)).toMatchSnapshot('errors');
1244+
});
1245+
1246+
it('should throw error with composes when the "namedExport" is enabled and "exportLocalsConvention" options has invalid value', async () => {
1247+
const compiler = getCompiler('./modules/namedExport/composes/composes.js', {
1248+
modules: {
1249+
localIdentName: '_[local]',
1250+
namedExport: true,
1251+
exportLocalsConvention: 'dashes',
1252+
},
1253+
});
1254+
const stats = await compile(compiler);
1255+
1256+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
1257+
expect(getErrors(stats, true)).toMatchSnapshot('errors');
1258+
});
1259+
12061260
it('should throw an error when the "namedExport" option is "true", but the "esModule" is "false"', async () => {
12071261
const compiler = getCompiler('./modules/namedExport/base/index.js', {
12081262
esModule: false,

0 commit comments

Comments
 (0)