Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow named exports to have underscores in names #1209

Merged
merged 3 commits into from Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 19 additions & 7 deletions src/utils.js
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
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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