Skip to content

Commit

Permalink
feat: allow named exports to have underscores in names (#1209)
Browse files Browse the repository at this point in the history
  • Loading branch information
cap-Bernardito authored Oct 13, 2020
1 parent 7bfe85d commit 747d62b
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 8 deletions.
26 changes: 19 additions & 7 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ function getFilter(filter, resourcePath) {
};
}

function getValidLocalName(localName, exportLocalsConvention) {
if (exportLocalsConvention === 'dashesOnly') {
return dashesCamelCase(localName);
}

return camelCase(localName);
}

const moduleRegExp = /\.module(s)?\.\w+$/i;
const icssRegExp = /\.icss\.\w+$/i;

Expand Down Expand Up @@ -203,9 +211,12 @@ function getModulesOptions(rawOptions, loaderContext) {
);
}

if (modulesOptions.exportLocalsConvention !== 'camelCaseOnly') {
if (
modulesOptions.exportLocalsConvention !== 'camelCaseOnly' &&
modulesOptions.exportLocalsConvention !== 'dashesOnly'
) {
throw new Error(
'The "modules.namedExport" option requires the "modules.exportLocalsConvention" option to be "camelCaseOnly"'
'The "modules.namedExport" option requires the "modules.exportLocalsConvention" option to be "camelCaseOnly" or "dashesOnly"'
);
}
}
Expand Down Expand Up @@ -516,7 +527,10 @@ function getModuleCode(result, api, replacements, options, loaderContext) {
code = code.replace(new RegExp(replacementName, 'g'), () =>
options.modules.namedExport
? `" + ${importName}_NAMED___[${JSON.stringify(
camelCase(localName)
getValidLocalName(
localName,
options.modules.exportLocalsConvention
)
)}] + "`
: `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
);
Expand Down Expand Up @@ -551,9 +565,7 @@ function getExportCode(exports, replacements, options) {

const addExportToLocalsCode = (name, value) => {
if (options.modules.namedExport) {
localsCode += `export const ${camelCase(name)} = ${JSON.stringify(
value
)};\n`;
localsCode += `export const ${name} = ${JSON.stringify(value)};\n`;
} else {
if (localsCode) {
localsCode += `,\n`;
Expand Down Expand Up @@ -609,7 +621,7 @@ function getExportCode(exports, replacements, options) {
localsCode = localsCode.replace(new RegExp(replacementName, 'g'), () => {
if (options.modules.namedExport) {
return `" + ${importName}_NAMED___[${JSON.stringify(
camelCase(localName)
getValidLocalName(localName, options.modules.exportLocalsConvention)
)}] + "`;
} else if (options.modules.exportOnlyLocals) {
return `" + ${importName}[${JSON.stringify(localName)}] + "`;
Expand Down
105 changes: 104 additions & 1 deletion test/__snapshots__/modules-option.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1614,7 +1614,7 @@ exports[`"modules" option should throw an error when class has unsupported name
exports[`"modules" option should throw an error when the "namedExport" is enabled and the "exportLocalsConvention" options has not "camelCaseOnly" value: errors 1`] = `
Array [
"ModuleBuildError: Module build failed (from \`replaced original path\`):
Error: The \\"modules.namedExport\\" option requires the \\"modules.exportLocalsConvention\\" option to be \\"camelCaseOnly\\"",
Error: The \\"modules.namedExport\\" option requires the \\"modules.exportLocalsConvention\\" option to be \\"camelCaseOnly\\" or \\"dashesOnly\\"",
]
`;

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

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

exports[`"modules" option should throw error with composes when the "namedExport" is enabled and "exportLocalsConvention" options has invalid value: errors 1`] = `
Array [
"ModuleBuildError: Module build failed (from \`replaced original path\`):
Error: The \\"modules.namedExport\\" option requires the \\"modules.exportLocalsConvention\\" option to be \\"camelCaseOnly\\" or \\"dashesOnly\\"",
]
`;

exports[`"modules" option should throw error with composes when the "namedExport" is enabled and "exportLocalsConvention" options has invalid value: warnings 1`] = `Array []`;

exports[`"modules" option should work and correctly replace escaped symbols: errors 1`] = `Array []`;

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

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

exports[`"modules" option should work when the "namedExport" is enabled and the "exportLocalsConvention" options has "dashesOnly" value: errors 1`] = `Array []`;

exports[`"modules" option should work when the "namedExport" is enabled and the "exportLocalsConvention" options has "dashesOnly" value: module 1`] = `
"// Imports
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\";
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]});
// Module
___CSS_LOADER_EXPORT___.push([module.id, \\".foo_barBaz {\\\\n color: red;\\\\n}\\\\n\\\\n.bar {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]);
// Exports
export const foo_barBaz = \\"foo_barBaz\\";
export default ___CSS_LOADER_EXPORT___;
"
`;

exports[`"modules" option should work when the "namedExport" is enabled and the "exportLocalsConvention" options has "dashesOnly" value: result 1`] = `
Array [
Array [
"./modules/namedExport/dashesOnly/index.css",
".foo_barBaz {
color: red;
}

.bar {
color: red;
}
",
"",
],
]
`;

exports[`"modules" option should work when the "namedExport" is enabled and the "exportLocalsConvention" options has "dashesOnly" value: warnings 1`] = `Array []`;

exports[`"modules" option should work with "exportOnlyLocals" and "esModule" with "false" value options: errors 1`] = `Array []`;

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

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

exports[`"modules" option should work with composes when the "namedExport" is enabled and "exportLocalsConvention" options has "dashesOnly" value: errors 1`] = `Array []`;

exports[`"modules" option should work with composes when the "namedExport" is enabled and "exportLocalsConvention" options has "dashesOnly" value: module 1`] = `
"// Imports
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\";
import ___CSS_LOADER_ICSS_IMPORT_0___, * as ___CSS_LOADER_ICSS_IMPORT_0____NAMED___ from \\"-!../../../../../src/index.js??[ident]!./values.css\\";
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]});
___CSS_LOADER_EXPORT___.i(___CSS_LOADER_ICSS_IMPORT_0___, \\"\\", true);
// Module
___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\\", \\"\\"]);
// Exports
export const v_def = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"v_def\\"] + \\"\\";
export const v_otherOther = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"v_otherOther\\"] + \\"\\";
export const sWhite = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"sWhite\\"] + \\"\\";
export const mSmall = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"mSmall\\"] + \\"\\";
export const ghi = \\"_ghi\\";
export const myClass = \\"_my-class\\";
export const other = \\"_other\\";
export const otherOther = \\"_other-other\\";
export const green = \\"_green\\";
export default ___CSS_LOADER_EXPORT___;
"
`;

exports[`"modules" option should work with composes when the "namedExport" is enabled and "exportLocalsConvention" options has "dashesOnly" value: result 1`] = `
Array [
Array [
"../../src/index.js?[ident]!./modules/namedExport/composes/values.css",
"
",
"",
],
Array [
"./modules/namedExport/composes/composes.css",
"._ghi {
color: red;
}

._my-class {
color: white;
}

._other {
display: (min-width: 320px);
}

._other-other {
width: red;
}

._green {
color: green;
}
",
"",
],
]
`;

exports[`"modules" option should work with composes when the "namedExport" is enabled and "exportLocalsConvention" options has "dashesOnly" value: warnings 1`] = `Array []`;

exports[`"modules" option should work with the "[local]" placeholder for the "localIdentName" option: errors 1`] = `Array []`;

exports[`"modules" option should work with the "[local]" placeholder for the "localIdentName" option: module 1`] = `
Expand Down
24 changes: 24 additions & 0 deletions test/fixtures/modules/namedExport/composes/composes.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@value v_def from './values.css';
@value v_other-other from './values.css';
@value s-white from './values.css';
@value m-small from './values.css';

.ghi {
color: v_def;
}

.my-class {
color: s-white;
}

.other {
display: m-small;
}

.other-other {
width: v_def;
}

.green {
color: v_other-other;
}
5 changes: 5 additions & 0 deletions test/fixtures/modules/namedExport/composes/composes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import css from './composes.css';

__export__ = css;

export default css;
4 changes: 4 additions & 0 deletions test/fixtures/modules/namedExport/composes/values.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@value v_def: red;
@value v_other-other: green;
@value s-white: white;
@value m-small: (min-width: 320px);
7 changes: 7 additions & 0 deletions test/fixtures/modules/namedExport/dashesOnly/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
:local(.foo_barBaz) {
color: red;
}

:global(.bar) {
color: red;
}
5 changes: 5 additions & 0 deletions test/fixtures/modules/namedExport/dashesOnly/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import css from './index.css';

__export__ = css;

export default css;
54 changes: 54 additions & 0 deletions test/modules-option.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,60 @@ describe('"modules" option', () => {
expect(getErrors(stats)).toMatchSnapshot('errors');
});

it('should work when the "namedExport" is enabled and the "exportLocalsConvention" options has "dashesOnly" value', async () => {
const compiler = getCompiler('./modules/namedExport/dashesOnly/index.js', {
modules: {
localIdentName: '[local]',
namedExport: true,
exportLocalsConvention: 'dashesOnly',
},
});
const stats = await compile(compiler);

expect(
getModuleSource('./modules/namedExport/dashesOnly/index.css', stats)
).toMatchSnapshot('module');
expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
'result'
);
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats, true)).toMatchSnapshot('errors');
});

it('should work with composes when the "namedExport" is enabled and "exportLocalsConvention" options has "dashesOnly" value', async () => {
const compiler = getCompiler('./modules/namedExport/composes/composes.js', {
modules: {
localIdentName: '_[local]',
namedExport: true,
exportLocalsConvention: 'dashesOnly',
},
});
const stats = await compile(compiler);

expect(
getModuleSource('./modules/namedExport/composes/composes.css', stats)
).toMatchSnapshot('module');
expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
'result'
);
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});

it('should throw error with composes when the "namedExport" is enabled and "exportLocalsConvention" options has invalid value', async () => {
const compiler = getCompiler('./modules/namedExport/composes/composes.js', {
modules: {
localIdentName: '_[local]',
namedExport: true,
exportLocalsConvention: 'dashes',
},
});
const stats = await compile(compiler);

expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats, true)).toMatchSnapshot('errors');
});

it('should throw an error when the "namedExport" option is "true", but the "esModule" is "false"', async () => {
const compiler = getCompiler('./modules/namedExport/base/index.js', {
esModule: false,
Expand Down

0 comments on commit 747d62b

Please sign in to comment.