From 525cd6d7be0d58bcc381de159583873a0347d3a3 Mon Sep 17 00:00:00 2001 From: Marcel Laverdet Date: Tue, 24 Oct 2023 15:49:59 -0400 Subject: [PATCH] feat: Support named exports BREAKING CHANGE: Updates the declaration template. This is a followup on #38 The `exports =` declaration was added in e7342df507 but removed in 908d49159d 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. --- README.md | 4 ++++ index.js | 41 +++++++++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 36fc6c5..44aaf7f 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/index.js b/index.js index c1df2d3..c54d4c8 100644 --- a/index.js +++ b/index.js @@ -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( @@ -64,19 +65,34 @@ 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}`)); } @@ -84,19 +100,16 @@ module.exports = function(content, ...rest) { 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) => {