Skip to content

Commit f96a110

Browse files
committedApr 4, 2024
feat: namedExports works fine with any exportLocalsConvention value
1 parent 9852aa6 commit f96a110

File tree

5 files changed

+4279
-3992
lines changed

5 files changed

+4279
-3992
lines changed
 

‎README.md

+2-7
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,7 @@ Enables/disables ES modules named export for locals.
11441144

11451145
> **Warning**
11461146
>
1147-
> It is not allowed to use JavaScript reserved words in css class names unless `exportLocalsConvention` is `"as-is"`.
1147+
> It is not allowed to use the `default` reserved word in css classes.
11481148
11491149
**styles.css**
11501150

@@ -1247,12 +1247,7 @@ Default: Depends on the value of the `modules.namedExport` option, if `true` - `
12471247
>
12481248
> Names of locals are converted to camelcase when the named export is `false`, i.e. the `exportLocalsConvention` option has
12491249
> `camelCaseOnly` value by default. You can set this back to any other valid option but selectors
1250-
> which are not valid JavaScript identifiers may run into problems which do not implement the entire
1251-
> modules specification.
1252-
1253-
> **Warning**
1254-
>
1255-
> **You need to disable `modules.namedExport` if you want to use `'camel-case'` or `'dashes'` value.**
1250+
> which are not valid JavaScript identifiers may run into problems which do not implement the entire modules specification.
12561251
12571252
Style of exported class names.
12581253

‎src/utils.js

+74-28
Original file line numberDiff line numberDiff line change
@@ -550,16 +550,12 @@ function getModulesOptions(rawOptions, esModule, exportType, loaderContext) {
550550
namedExport,
551551
};
552552

553-
let exportLocalsConventionType;
554-
555553
if (typeof modulesOptions.exportLocalsConvention === "string") {
556-
exportLocalsConventionType = modulesOptions.exportLocalsConvention;
554+
// eslint-disable-next-line no-shadow
555+
const { exportLocalsConvention } = modulesOptions;
557556

558-
modulesOptions.useExportsAs =
559-
exportLocalsConventionType === "as-is" ||
560-
exportLocalsConventionType === "asIs";
561557
modulesOptions.exportLocalsConvention = (name) => {
562-
switch (exportLocalsConventionType) {
558+
switch (exportLocalsConvention) {
563559
case "camel-case":
564560
case "camelCase": {
565561
return [name, camelCase(name)];
@@ -640,26 +636,10 @@ function getModulesOptions(rawOptions, esModule, exportType, loaderContext) {
640636
}
641637
}
642638

643-
if (modulesOptions.namedExport === true) {
644-
if (esModule === false) {
645-
throw new Error(
646-
"The 'modules.namedExport' option requires the 'esModule' option to be enabled",
647-
);
648-
}
649-
650-
/* if (
651-
typeof exportLocalsConventionType === "string" &&
652-
exportLocalsConventionType !== "asIs" &&
653-
exportLocalsConventionType !== "as-is" &&
654-
exportLocalsConventionType !== "camelCaseOnly" &&
655-
exportLocalsConventionType !== "camel-case-only" &&
656-
exportLocalsConventionType !== "dashesOnly" &&
657-
exportLocalsConventionType !== "dashes-only"
658-
) {
659-
throw new Error(
660-
'The "modules.namedExport" option requires the "modules.exportLocalsConvention" option to be "as-is", "camel-case-only" or "dashes-only"',
661-
);
662-
}*/
639+
if (modulesOptions.namedExport === true && esModule === false) {
640+
throw new Error(
641+
"The 'modules.namedExport' option requires the 'esModule' option to be enabled",
642+
);
663643
}
664644

665645
return modulesOptions;
@@ -1123,6 +1103,69 @@ function dashesCamelCase(str) {
11231103
);
11241104
}
11251105

1106+
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u;
1107+
const keywords = new Set([
1108+
"abstract",
1109+
"boolean",
1110+
"break",
1111+
"byte",
1112+
"case",
1113+
"catch",
1114+
"char",
1115+
"class",
1116+
"const",
1117+
"continue",
1118+
"debugger",
1119+
"default",
1120+
"delete",
1121+
"do",
1122+
"double",
1123+
"else",
1124+
"enum",
1125+
"export",
1126+
"extends",
1127+
"false",
1128+
"final",
1129+
"finally",
1130+
"float",
1131+
"for",
1132+
"function",
1133+
"goto",
1134+
"if",
1135+
"implements",
1136+
"import",
1137+
"in",
1138+
"instanceof",
1139+
"int",
1140+
"interface",
1141+
"long",
1142+
"native",
1143+
"new",
1144+
"null",
1145+
"package",
1146+
"private",
1147+
"protected",
1148+
"public",
1149+
"return",
1150+
"short",
1151+
"static",
1152+
"super",
1153+
"switch",
1154+
"synchronized",
1155+
"this",
1156+
"throw",
1157+
"throws",
1158+
"transient",
1159+
"true",
1160+
"try",
1161+
"typeof",
1162+
"var",
1163+
"void",
1164+
"volatile",
1165+
"while",
1166+
"with",
1167+
]);
1168+
11261169
function getExportCode(
11271170
exports,
11281171
replacements,
@@ -1145,10 +1188,13 @@ function getExportCode(
11451188
const serializedValue = isTemplateLiteralSupported
11461189
? convertToTemplateLiteral(value)
11471190
: JSON.stringify(value);
1191+
11481192
if (options.modules.namedExport) {
1149-
if (options.modules.useExportsAs) {
1193+
if (!validIdentifier.test(name) || keywords.has(name)) {
11501194
identifierId += 1;
1195+
11511196
const id = `_${identifierId.toString(16)}`;
1197+
11521198
localsCode += `var ${id} = ${serializedValue};\n`;
11531199
localsCode += `export { ${id} as ${JSON.stringify(name)} };\n`;
11541200
} else {

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

+4,052-3,953
Large diffs are not rendered by default.

‎test/fixtures/modules/localsConvention/localsConvention.css

+12
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,15 @@ a {
2222
.foo_bar {
2323
color: red;
2424
}
25+
26+
.class {
27+
color: red;
28+
}
29+
30+
.fooBarBaz {
31+
color: red;
32+
}
33+
34+
.b {
35+
color: red;
36+
}

‎test/modules-option.test.js

+139-4
Original file line numberDiff line numberDiff line change
@@ -1389,7 +1389,74 @@ describe('"modules" option', () => {
13891389
expect(getErrors(stats)).toMatchSnapshot("errors");
13901390
});
13911391

1392-
it('should work and respect the "localConvention" option with the "camelCase" value', async () => {
1392+
it('should work and respect the `localConvention` option with the "as-is" value', async () => {
1393+
const compiler = getCompiler(
1394+
"./modules/localsConvention/localsConvention.js",
1395+
{
1396+
modules: {
1397+
mode: "local",
1398+
exportLocalsConvention: "as-is",
1399+
},
1400+
},
1401+
);
1402+
const stats = await compile(compiler);
1403+
1404+
expect(
1405+
getModuleSource("./modules/localsConvention/localsConvention.css", stats),
1406+
).toMatchSnapshot("module");
1407+
expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot(
1408+
"result",
1409+
);
1410+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
1411+
expect(getErrors(stats)).toMatchSnapshot("errors");
1412+
});
1413+
1414+
it("should work and respect the `localConvention` option with the `as-is` value and `namedExport` is `false`", async () => {
1415+
const compiler = getCompiler(
1416+
"./modules/localsConvention/localsConvention.js",
1417+
{
1418+
modules: {
1419+
mode: "local",
1420+
exportLocalsConvention: "as-is",
1421+
namedExport: false,
1422+
},
1423+
},
1424+
);
1425+
const stats = await compile(compiler);
1426+
1427+
expect(
1428+
getModuleSource("./modules/localsConvention/localsConvention.css", stats),
1429+
).toMatchSnapshot("module");
1430+
expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot(
1431+
"result",
1432+
);
1433+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
1434+
expect(getErrors(stats)).toMatchSnapshot("errors");
1435+
});
1436+
1437+
it("should work and respect the `localConvention` option with the `camelCase` value", async () => {
1438+
const compiler = getCompiler(
1439+
"./modules/localsConvention/localsConvention.js",
1440+
{
1441+
modules: {
1442+
mode: "local",
1443+
exportLocalsConvention: "camelCase",
1444+
},
1445+
},
1446+
);
1447+
const stats = await compile(compiler);
1448+
1449+
expect(
1450+
getModuleSource("./modules/localsConvention/localsConvention.css", stats),
1451+
).toMatchSnapshot("module");
1452+
expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot(
1453+
"result",
1454+
);
1455+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
1456+
expect(getErrors(stats)).toMatchSnapshot("errors");
1457+
});
1458+
1459+
it("should work and respect the `localConvention` option with the `camelCase` value and `namedExport` false", async () => {
13931460
const compiler = getCompiler(
13941461
"./modules/localsConvention/localsConvention.js",
13951462
{
@@ -1412,7 +1479,7 @@ describe('"modules" option', () => {
14121479
expect(getErrors(stats)).toMatchSnapshot("errors");
14131480
});
14141481

1415-
it('should work and respect the "localConvention" option with the "camel-case-only" value', async () => {
1482+
it("should work and respect the `localConvention` option with the `camel-case-only` value", async () => {
14161483
const compiler = getCompiler(
14171484
"./modules/localsConvention/localsConvention.js",
14181485
{
@@ -1434,7 +1501,52 @@ describe('"modules" option', () => {
14341501
expect(getErrors(stats)).toMatchSnapshot("errors");
14351502
});
14361503

1437-
it('should work and respect the "localConvention" option with the "dashes" value', async () => {
1504+
it("should work and respect the `localConvention` option with the `camel-case-only` value and `namedExport` false", async () => {
1505+
const compiler = getCompiler(
1506+
"./modules/localsConvention/localsConvention.js",
1507+
{
1508+
modules: {
1509+
mode: "local",
1510+
exportLocalsConvention: "camel-case-only",
1511+
namedExport: false,
1512+
},
1513+
},
1514+
);
1515+
const stats = await compile(compiler);
1516+
1517+
expect(
1518+
getModuleSource("./modules/localsConvention/localsConvention.css", stats),
1519+
).toMatchSnapshot("module");
1520+
expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot(
1521+
"result",
1522+
);
1523+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
1524+
expect(getErrors(stats)).toMatchSnapshot("errors");
1525+
});
1526+
1527+
it("should work and respect the `localConvention` option with the `dashes` value", async () => {
1528+
const compiler = getCompiler(
1529+
"./modules/localsConvention/localsConvention.js",
1530+
{
1531+
modules: {
1532+
mode: "local",
1533+
exportLocalsConvention: "dashes",
1534+
},
1535+
},
1536+
);
1537+
const stats = await compile(compiler);
1538+
1539+
expect(
1540+
getModuleSource("./modules/localsConvention/localsConvention.css", stats),
1541+
).toMatchSnapshot("module");
1542+
expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot(
1543+
"result",
1544+
);
1545+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
1546+
expect(getErrors(stats)).toMatchSnapshot("errors");
1547+
});
1548+
1549+
it("should work and respect the `localConvention` option with the `dashes` value and `namedExport` false", async () => {
14381550
const compiler = getCompiler(
14391551
"./modules/localsConvention/localsConvention.js",
14401552
{
@@ -1457,7 +1569,7 @@ describe('"modules" option', () => {
14571569
expect(getErrors(stats)).toMatchSnapshot("errors");
14581570
});
14591571

1460-
it('should work and respect the "localConvention" option with the "dashesOnly" value', async () => {
1572+
it("should work and respect the `localConvention` option with the `dashesOnly` value", async () => {
14611573
const compiler = getCompiler(
14621574
"./modules/localsConvention/localsConvention.js",
14631575
{
@@ -1479,6 +1591,29 @@ describe('"modules" option', () => {
14791591
expect(getErrors(stats)).toMatchSnapshot("errors");
14801592
});
14811593

1594+
it("should work and respect the `localConvention` option with the `dashesOnly` value and `namedExport` false", async () => {
1595+
const compiler = getCompiler(
1596+
"./modules/localsConvention/localsConvention.js",
1597+
{
1598+
modules: {
1599+
mode: "local",
1600+
exportLocalsConvention: "dashesOnly",
1601+
namedExport: false,
1602+
},
1603+
},
1604+
);
1605+
const stats = await compile(compiler);
1606+
1607+
expect(
1608+
getModuleSource("./modules/localsConvention/localsConvention.css", stats),
1609+
).toMatchSnapshot("module");
1610+
expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot(
1611+
"result",
1612+
);
1613+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
1614+
expect(getErrors(stats)).toMatchSnapshot("errors");
1615+
});
1616+
14821617
it('should work and respect the "exportLocalsConvention" option with the "function" type', async () => {
14831618
const compiler = getCompiler(
14841619
"./modules/localsConvention/localsConvention.js",

0 commit comments

Comments
 (0)
Please sign in to comment.