Css-loader: Interoperability across tools and support plain JS modules imports

Created on 28 Feb 2020  路  5Comments  路  Source: webpack-contrib/css-loader

  • Operating System: n/a
  • Node Version: n/a
  • NPM Version: n/a
  • webpack Version: n/a
  • css-loader Version: n/a

Feature Proposal

The feature I'm proposing is essentially to maintain and use a consistent API when exporting and handling CSS modules at all stages of the common webpack+css_modules workflow.

Currently, there is a disparity between the APIs exposed by a CSS Module and used by the webpack tools when it is compiled by css-loader, style-loader, mini-css-extract-plugin (and legacy extract-text-webpack-plugin).

In more detail, these are the exported APIs.

css-loader exports and expects this. The other tools expect this API from a CSS Module.

{
  locals: { /* CSS local values and classes */ }
}

All other tools export this. However they expect the API above when handling modules (in order to re-export the locals)

{ /* CSS local values and classes */ }

I was affected by this problem while working on my company's UI library which is implemented as a set of loosely coupled packages -- many of which have their own CSS modules. I wanted to facilitate the import of deeply nested CSS files by introducing JS module proxies such as (Context: We set importLoaders option to false to allow css-loader to import the js file.)

in my-package/styles.css.js

module.exports = require('./path/to/deeply/nested/styles.css');

In order to support imports in CSS files that would change as such

.consumer-class {
- composes: someClass from '~my-package/path/to/deeply/nested/styles.css';
+ composes: someClass from '~my-package/styles.css';
}

Introducing the patch above would cause a compile-time error. The problem is that the file inmy-package/path/to/deeply/nested/styles.css would go through the whole compilation pipeline before it is exported by my-package/styles.css.js (which will include an extract rule [style-loader or mini-css-extract-plugin]). Because of this, what gets exported by my-package/styles.css.js is

{
  /* CSS local classes (from my-package/path/to/deeply/nested/styles.css)  */
}

And, when css-loader tries to use someClass from my-package/styles.css.js to compose .consumer-class, it will look for cssModule.locals.someClass, which is not available as I demonstrated above.

Feature Use Case

Maintaining and using a consistent API across all stages of the compilation process will unlock the use-case I described above (creating dumb JS module proxies to CSS exports). However, it will also facilitate other cases - such as making truly interoperable JS modules. For example, it would be helpful to do the following:

In interoperable-dynamic-colors.js

const colors = {
    primary: 'red',
    secondary: 'blue'
}

if (process.env.SOME_FEATURE_FLAG) {
  colors.primary = 'pink';
}

module.exports = colors;

Then be able to consume this from a JS module as such

import colors from './interoperable-dynamic-colors.js';

useColorsSomehow(colors);

AND be able to consume this from a CSS module as such

@value ( primary ) from './interoperable-dynamic-colors.js';

.myClass {
  color: primary;
}

Currernt workarounds

In order to do what I demonstrated above - we have to re-export the module via locals so that css-loader allows the reference to be resolved:

In interoperable-dynamic-colors.js

module.exports = colors;
module.exports.locals = module.exports;

Or, simply ONLY use the locals export as

In interoperable-dynamic-colors.js

module.exports.locals = colors;

And changing the JS code consuming this module as such

- import colors from './interoperable-dynamic-colors.js';
+import { locals as colors } from './interoperable-dynamic-colors.js';

useColorsSomehow(colors);

Conclusion

While there are workarounds for the use cases I enumerated, they encourage 2 things that have the potential to be "bad"

  1. Developers need to write code that is aware of the internals handling of CSS modules (awareness of locals property)
  2. Developers are restricted from using common code patterns when handling dependencies (such as the JS module proxy which is incredibly common in packages)

Without diving further into the internals of css-loader, I think it would be relatively simple to collapse the local values to the top-level of the exported object (and require all tools to use this API).

Additionally, I volunteer myself as a contributor if the webpack team is receptive to this proposal.

Most helpful comment

@gaaamii I think for historical reasons - endlessly :smile: The only thing that can happen - we move them from css-loader to own plugin

All 5 comments

Sorry, it is out of scope CSS modules spec, In the near future we want to deprecate CSS modules, there are a lot of other solutions - BEM, CSS-in-JS, CSS-in-JS without runtime https://github.com/callstack/linaria, future CSS module (https://github.com/w3c/webcomponents/issues/759), do not confuse with current CSS modules, Web Components and shadow DOM.

CSS modules is maintenance stage (only fixes), it is really old technology and very controversial. We will support them for a while so that all developers can migrate, but no new features, sorry. You can write an own loader based on the current module code.

馃憤 Thanks for the response @evilebottnawi. I will avoid this pattern for now and promote a migration away from CSS modules within my teams.

@evilebottnawi
How long will CSS Modules be maintained?
I'm thinking to migrate from CSS Modules to another way. But we need rough schedule because our project is big... 馃槶

@gaaamii I think for historical reasons - endlessly :smile: The only thing that can happen - we move them from css-loader to own plugin

Thanks for your quick reply 馃槃
I had thought we had to migrate soon. But if maintenance of CSS Modules feature continues for so long, we will rethink the necessity of the migration. Thank you.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

felipecarrillo100 picture felipecarrillo100  路  3Comments

heldrida picture heldrida  路  4Comments

Naspo88 picture Naspo88  路  3Comments

danielgomonea picture danielgomonea  路  3Comments

camsong picture camsong  路  3Comments