Skip to content

Commit

Permalink
feat: Support named exports
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Updates the declaration template.

This is a followup on seek-oss#38

The `exports =` declaration was added in e7342df but removed in
908d491 due some issue in babel which I can't reproduce. Maybe that
has been fixed downstream in the meantime.

Due to microsoft/TypeScript#40594 we cannot export these names directly
since class names might not be valid JavaScript identifiers, even though
they are valid exported names. When that TypeScript bug is resolved this
can be changed to export the names directly instead of using `export =`.
The problem with `export =` is that it will let you do `import * as css
from ...` in addition to `import css from ...` even though only
`import *` will work.
  • Loading branch information
laverdet committed Oct 24, 2023
1 parent bb4892a commit 525cd6d
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 14 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -58,6 +58,10 @@ Instead of emitting new TypeScript declarations, this will throw an error if a g

This workflow is similar to using the [Prettier](https://github.com/prettier/prettier) [`--list-different` option](https://prettier.io/docs/en/cli.html#list-different).

### Named Exports

If using the `namedExports` option of `css-loader` then you can enable the same option in this loader. This can improve tree shaking and reduce bundled JavaScript size by dropping the original class names.

## With Thanks

This package borrows heavily from [typings-for-css-modules-loader](https://github.com/Jimdo/typings-for-css-modules-loader).
Expand Down
41 changes: 27 additions & 14 deletions index.js
Expand Up @@ -7,6 +7,7 @@ const bannerMessage =
'// This file is automatically generated.\n// Please do not change this file!';

const cssModuleExport = 'export const cssExports: CssExports;\nexport default cssExports;\n';
const cssNamedModuleExport = 'export const cssExports: CssExports;\nexport = cssExports;\n';

const getNoDeclarationFileError = ({ filename }) =>
new Error(
Expand Down Expand Up @@ -64,39 +65,51 @@ const makeFileHandlers = filename => ({
fs.writeFile(filename, content, { encoding: 'utf-8' }, handler)
});

const extractLocalExports = (content) => {
let localExports = content.split('exports.locals')[1];
function* extractLocalExports(content) {
let localExports = content.split('exports.locals = {')[1];
if (!localExports) {
localExports = content.split('___CSS_LOADER_EXPORT___.locals')[1];
localExports = content.split('___CSS_LOADER_EXPORT___.locals = {')[1];
}
if (localExports) {
// // Exports
// ___CSS_LOADER_EXPORT___.locals = {
// "class": `class__file`,
const keyRegex = /"([^\\"]+)":/g;
let match;
while ((match = keyRegex.exec(localExports))) {
yield match[1];
}
} else {
// export { _1 as "class" };
const keyRegex = /export { [_a-z0-9]+ as ("[^\\"]+") }/g;
while ((match = keyRegex.exec(content))) {
yield JSON.parse(match[1]);
}
}
return localExports;
}

module.exports = function(content, ...rest) {
const { failed, success } = makeDoneHandlers(this.async(), content, rest);

const filename = this.resourcePath;
const { mode = 'emit' } = loaderUtils.getOptions(this) || {};
const { mode = 'emit', namedExports = false } = loaderUtils.getOptions(this) || {};
if (!validModes.includes(mode)) {
return failed(new Error(`Invalid mode option: ${mode}`));
}

const cssModuleInterfaceFilename = filenameToTypingsFilename(filename);
const { read, write } = makeFileHandlers(cssModuleInterfaceFilename);

const keyRegex = /"([^\\"]+)":/g;
let match;
const cssModuleKeys = [];

const localExports = extractLocalExports(content);

while ((match = keyRegex.exec(localExports))) {
if (cssModuleKeys.indexOf(match[1]) < 0) {
cssModuleKeys.push(match[1]);
for (const key of extractLocalExports(content)) {
// Do you really need this existence check?
if (cssModuleKeys.indexOf(key) < 0) {
cssModuleKeys.push(key);
}
}

const cssModuleDefinition = `${bannerMessage}\n${cssModuleToInterface(cssModuleKeys)}\n${cssModuleExport}`;
const exportsDeclaration = namedExports ? cssNamedModuleExport : cssModuleExport;
const cssModuleDefinition = `${bannerMessage}\n${cssModuleToInterface(cssModuleKeys)}\n${exportsDeclaration}`;

if (mode === 'verify') {
read((err, fileContents) => {
Expand Down

0 comments on commit 525cd6d

Please sign in to comment.