Skip to content

Commit

Permalink
refactor: named export (#1125)
Browse files Browse the repository at this point in the history
  • Loading branch information
evilebottnawi authored Jul 22, 2020
1 parent 01e8c76 commit 791fff3
Show file tree
Hide file tree
Showing 17 changed files with 327 additions and 258 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -844,9 +844,10 @@ Type: `Boolean`
Default: `false`

Enables/disables ES modules named export for locals.
Names of locals are converted to camelCase.

> i It is not allowed to use JavaScript reserved words in css class names
> ⚠ Names of locals are converted to camelcase, i.e. the `exportLocalsConvention` option has `camelCaseOnly` value by default.
> ⚠ It is not allowed to use JavaScript reserved words in css class names.
**styles.css**

Expand Down Expand Up @@ -920,12 +921,14 @@ module.exports = {
##### `exportlocalsConvention`

Type: `String`
Default: `'asIs'`
Default: based on the `modules.namedExport` option value, if `true` - `camelCaseOnly`, otherwise `asIs`

Style of exported class names.

By default, the exported JSON keys mirror the class names (i.e `asIs` value).

> ⚠ Only `camelCaseOnly` value allowed if you set the `namedExport` value to `true`.
| Name | Type | Description |
| :-------------------: | :--------: | :----------------------------------------------------------------------------------------------- |
| **`'asIs'`** | `{String}` | Class names will be exported as is. |
Expand Down
14 changes: 11 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,17 @@ export default async function loader(content, map, meta) {
});

const plugins = [];
const options = normalizeOptions(rawOptions, this);
const callback = this.async();

let options;

try {
options = normalizeOptions(rawOptions, this);
} catch (error) {
callback(error);

return;
}

if (shouldUseModulesPlugins(options)) {
const icssResolver = this.getResolve({
Expand Down Expand Up @@ -117,8 +127,6 @@ export default async function loader(content, map, meta) {
}
}

const callback = this.async();

let result;

try {
Expand Down
61 changes: 33 additions & 28 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ function getModulesOptions(rawOptions, loaderContext) {
return false;
}
}

if (
rawOptions.modules.namedExport === true &&
typeof rawOptions.modules.exportLocalsConvention === 'undefined'
) {
modulesOptions.exportLocalsConvention = 'camelCaseOnly';
}
}

modulesOptions = { ...modulesOptions, ...(rawOptions.modules || {}) };
Expand All @@ -172,12 +179,18 @@ function getModulesOptions(rawOptions, loaderContext) {
modulesOptions.mode = modulesOptions.mode(loaderContext.resourcePath);
}

if (modulesOptions.namedExport === true && rawOptions.esModule === false) {
loaderContext.emitError(
new Error(
'`Options.module.namedExport` cannot be used without `options.esModule`'
)
);
if (modulesOptions.namedExport === true) {
if (rawOptions.esModule === false) {
throw new Error(
'The "modules.namedExport" option requires the "esModules" option to be enabled'
);
}

if (modulesOptions.exportLocalsConvention !== 'camelCaseOnly') {
throw new Error(
'The "modules.namedExport" option requires the "modules.exportLocalsConvention" option to be "camelCaseOnly"'
);
}
}

return modulesOptions;
Expand Down Expand Up @@ -411,19 +424,18 @@ function dashesCamelCase(str) {
function getExportCode(exports, icssReplacements, options) {
let code = '';
let localsCode = '';
let namedCode = '';

const addExportToLocalsCode = (name, value) => {
if (localsCode) {
localsCode += `,\n`;
}

localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`;

if (options.modules.namedExport) {
namedCode += `export const ${camelCase(name)} = ${JSON.stringify(
localsCode += `export const ${camelCase(name)} = ${JSON.stringify(
value
)};\n`;
} else {
if (localsCode) {
localsCode += `,\n`;
}

localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`;
}
};

Expand Down Expand Up @@ -467,25 +479,18 @@ function getExportCode(exports, icssReplacements, options) {
for (const replacement of icssReplacements) {
const { replacementName, importName, localName } = replacement;

localsCode = localsCode.replace(
new RegExp(replacementName, 'g'),
() => `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
);

if (options.modules.namedExport) {
namedCode = namedCode.replace(
new RegExp(replacementName, 'g'),
() =>
`" + ${importName}_NAMED___[${JSON.stringify(
localsCode = localsCode.replace(new RegExp(replacementName, 'g'), () =>
options.modules.namedExport
? `" + ${importName}_NAMED___[${JSON.stringify(
camelCase(localName)
)}] + "`
);
}
: `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
);
}

if (localsCode) {
code += namedCode
? `${namedCode}\n`
code += options.modules.namedExport
? `${localsCode}`
: `___CSS_LOADER_EXPORT___.locals = {\n${localsCode}\n};\n`;
}

Expand Down
136 changes: 0 additions & 136 deletions test/__snapshots__/esModule-option.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,68 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`"esModule" option should emit error when class has unsupported name: errors 1`] = `
Array [
"ModuleParseError: Module parse failed: Unexpected keyword 'class' (7:13)
File was processed with these loaders:",
]
`;

exports[`"esModule" option should emit error when class has unsupported name: warnings 1`] = `Array []`;

exports[`"esModule" option should emit error when namedExport true && esModule false: errors 1`] = `
Array [
"ModuleError: Module Error (from \`replaced original path\`):
\`Options.module.namedExport\` cannot be used without \`options.esModule\`",
]
`;

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

exports[`"esModule" option should work js template with "namedExport" option: module 1`] = `
"// Imports
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\";
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
// Module
___CSS_LOADER_EXPORT___.push([module.id, \\".header-baz {\\\\n color: red;\\\\n}\\\\n\\\\n.body {\\\\n color: coral;\\\\n}\\\\n\\\\n.footer {\\\\n color: blue;\\\\n}\\\\n\\", \\"\\"]);
// Exports
export const headerBaz = \\"header-baz\\";
export const body = \\"body\\";
export const footer = \\"footer\\";
export default ___CSS_LOADER_EXPORT___;
"
`;

exports[`"esModule" option should work js template with "namedExport" option: result 1`] = `
Object {
"css": Array [
Array [
"./es-module/named/template/index.css",
".header-baz {
color: red;
}
.body {
color: coral;
}
.footer {
color: blue;
}
",
"",
],
],
"html": "
<div class=\\"header-baz\\">
<div class=\\"body\\">
<div class=\\"footer\\">
",
}
`;
exports[`"esModule" option should work js template with "namedExport" option: warnings 1`] = `Array []`;
exports[`"esModule" option should work when not specified: errors 1`] = `Array []`;

exports[`"esModule" option should work when not specified: module 1`] = `
Expand Down Expand Up @@ -109,79 +46,6 @@ Array [

exports[`"esModule" option should work when not specified: warnings 1`] = `Array []`;

exports[`"esModule" option should work with "namedExport" option with nested import: errors 1`] = `Array []`;
exports[`"esModule" option should work with "namedExport" option with nested import: 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]!../../../modules/composes/values.css\\";
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
___CSS_LOADER_EXPORT___.i(___CSS_LOADER_ICSS_IMPORT_0___, \\"\\", true);
// Module
___CSS_LOADER_EXPORT___.push([module.id, \\"._1yYSY3W2VgnkKdMmuxCIL1 {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"vDef\\"] + \\";\\\\n}\\\\n\\", \\"\\"]);
// Exports
export const vDef = \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0____NAMED___[\\"vDef\\"] + \\"\\";
export const ghi = \\"_1yYSY3W2VgnkKdMmuxCIL1\\";
export default ___CSS_LOADER_EXPORT___;
"
`;
exports[`"esModule" option should work with "namedExport" option with nested import: result 1`] = `
Array [
Array [
"../../src/index.js?[ident]!./modules/composes/values.css",
"
",
"",
],
Array [
"./es-module/named/nested/index.css",
"._1yYSY3W2VgnkKdMmuxCIL1 {
color: red;
}
",
"",
],
]
`;
exports[`"esModule" option should work with "namedExport" option with nested import: warnings 1`] = `Array []`;
exports[`"esModule" option should work with "namedExport" option: errors 1`] = `Array []`;
exports[`"esModule" option should work with "namedExport" option: module 1`] = `
"// Imports
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../../src/runtime/api.js\\";
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
// Module
___CSS_LOADER_EXPORT___.push([module.id, \\"._3yAjbn27wJ9GVH2M-pj-hs {\\\\n color: red;\\\\n}\\\\n\\\\n.bar {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]);
// Exports
export const barBaz = \\"_3yAjbn27wJ9GVH2M-pj-hs\\";
export default ___CSS_LOADER_EXPORT___;
"
`;
exports[`"esModule" option should work with "namedExport" option: result 1`] = `
Array [
Array [
"./es-module/named/base/index.css",
"._3yAjbn27wJ9GVH2M-pj-hs {
color: red;
}
.bar {
color: red;
}
",
"",
],
]
`;
exports[`"esModule" option should work with "namedExport" option: warnings 1`] = `Array []`;
exports[`"esModule" option should work with a value equal to "false": errors 1`] = `Array []`;

exports[`"esModule" option should work with a value equal to "false": module 1`] = `
Expand Down
Loading

0 comments on commit 791fff3

Please sign in to comment.