Navigation Menu

Skip to content

Commit

Permalink
feat: allow the exportLocalsConvention option can be a function use…
Browse files Browse the repository at this point in the history
…ful for named export (#1351)
  • Loading branch information
cap-Bernardito committed Jul 19, 2021
1 parent cc0c1b4 commit 3c4b357
Show file tree
Hide file tree
Showing 11 changed files with 666 additions and 30 deletions.
84 changes: 83 additions & 1 deletion README.md
Expand Up @@ -1031,6 +1031,9 @@ module.exports = {
};
```

To set a custom name for namedExport, can use [`exportLocalsConvention`](#exportLocalsConvention) option as a function.
Example below in the [`examples`](#examples) section.

##### `exportGlobals`

Type: `Boolean`
Expand Down Expand Up @@ -1060,11 +1063,13 @@ module.exports = {

##### `exportLocalsConvention`

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

Style of exported class names.

###### `String`

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`.
Expand Down Expand Up @@ -1110,6 +1115,58 @@ module.exports = {
};
```

###### `Function`

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
loader: "css-loader",
options: {
modules: {
exportLocalsConvention: function (name) {
return name.replace(/-/g, "_");
},
},
},
},
],
},
};
```

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
loader: "css-loader",
options: {
modules: {
exportLocalsConvention: function (name) {
return [
name.replace(/-/g, "_"),
// dashesCamelCase
name.replace(/-+(\w)/g, (match, firstLetter) =>
firstLetter.toUpperCase()
),
];
},
},
},
},
],
},
};
```

##### `exportOnlyLocals`

Type: `Boolean`
Expand Down Expand Up @@ -1434,6 +1491,31 @@ module.exports = {
};
```

### Named export with custom export names

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
loader: "css-loader",
options: {
modules: {
namedExport: true,
exportLocalsConvention: function (name) {
return name.replace(/-/g, "_");
},
},
},
},
],
},
};
```

### Separating `Interoperable CSS`-only and `CSS Module` features

The following setup is an example of allowing `Interoperable CSS` features only (such as `:import` and `:export`) without using further `CSS Module` functionality by setting `mode` option for all files that do not match `*.module.scss` naming convention. This is for reference as having `ICSS` features applied to all files was default `css-loader` behavior before v4.
Expand Down
12 changes: 11 additions & 1 deletion src/index.js
Expand Up @@ -216,7 +216,17 @@ export default async function loader(content, map, meta) {
}

const importCode = getImportCode(imports, options);
const moduleCode = getModuleCode(result, api, replacements, options, this);

let moduleCode;

try {
moduleCode = getModuleCode(result, api, replacements, options, this);
} catch (error) {
callback(error);

return;
}

const exportCode = getExportCode(
exports,
replacements,
Expand Down
19 changes: 13 additions & 6 deletions src/options.json
Expand Up @@ -145,12 +145,19 @@
"exportLocalsConvention": {
"description": "Style of exported classnames.",
"link": "https://github.com/webpack-contrib/css-loader#localsconvention",
"enum": [
"asIs",
"camelCase",
"camelCaseOnly",
"dashes",
"dashesOnly"
"anyOf": [
{
"enum": [
"asIs",
"camelCase",
"camelCaseOnly",
"dashes",
"dashesOnly"
]
},
{
"instanceof": "Function"
}
]
},
"exportOnlyLocals": {
Expand Down
51 changes: 33 additions & 18 deletions src/utils.js
Expand Up @@ -485,6 +485,12 @@ function getFilter(filter, resourcePath) {
}

function getValidLocalName(localName, exportLocalsConvention) {
if (typeof exportLocalsConvention === "function") {
const result = exportLocalsConvention(localName);

return Array.isArray(result) ? result[0] : result;
}

if (exportLocalsConvention === "dashesOnly") {
return dashesCamelCase(localName);
}
Expand Down Expand Up @@ -588,6 +594,7 @@ function getModulesOptions(rawOptions, loaderContext) {
}

if (
typeof modulesOptions.exportLocalsConvention === "string" &&
modulesOptions.exportLocalsConvention !== "camelCaseOnly" &&
modulesOptions.exportLocalsConvention !== "dashesOnly"
) {
Expand Down Expand Up @@ -957,42 +964,50 @@ function getExportCode(exports, replacements, needToUseIcssPlugin, options) {

let localsCode = "";

const addExportToLocalsCode = (name, value) => {
if (options.modules.namedExport) {
localsCode += `export var ${name} = ${JSON.stringify(value)};\n`;
} else {
if (localsCode) {
localsCode += `,\n`;
}
const addExportToLocalsCode = (names, value) => {
const normalizedNames = Array.isArray(names)
? new Set(names)
: new Set([names]);

localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`;
for (const name of normalizedNames) {
if (options.modules.namedExport) {
localsCode += `export var ${name} = ${JSON.stringify(value)};\n`;
} else {
if (localsCode) {
localsCode += `,\n`;
}

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

for (const { name, value } of exports) {
if (typeof options.modules.exportLocalsConvention === "function") {
addExportToLocalsCode(
options.modules.exportLocalsConvention(name),
value
);

// eslint-disable-next-line no-continue
continue;
}

switch (options.modules.exportLocalsConvention) {
case "camelCase": {
addExportToLocalsCode(name, value);

const modifiedName = camelCase(name);

if (modifiedName !== name) {
addExportToLocalsCode(modifiedName, value);
}
addExportToLocalsCode([name, modifiedName], value);
break;
}
case "camelCaseOnly": {
addExportToLocalsCode(camelCase(name), value);
break;
}
case "dashes": {
addExportToLocalsCode(name, value);

const modifiedName = dashesCamelCase(name);

if (modifiedName !== name) {
addExportToLocalsCode(modifiedName, value);
}
addExportToLocalsCode([name, modifiedName], value);
break;
}
case "dashesOnly": {
Expand Down

0 comments on commit 3c4b357

Please sign in to comment.