Package Exports
- @startupjs/babel-plugin-react-css-modules
- @startupjs/babel-plugin-react-css-modules/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@startupjs/babel-plugin-react-css-modules) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Babel Plugin: React CSS Modules
This Babel plugin for React transforms styleName attribute of
JSX components into className using compile-time CSS Modules resolution,
allowing for a cleaner use of CSS Modules in React.
Content
Usage Examples
Assuming style.css in the following examples is compiled as CSS Module.
Without this plugin
import S from './styles.css';
export default function Component() {
return (
<div className={S.container}>
<h1 className={S.title}>Example</div>
<p styleName={S.text}>Sample text paragraph.</p>
<p className={`${S.text} ${S.special}`}>
Sample text paragraph with special style.
</p>
</div>
);
}With this plugin
import './styles.css';
export default function Component() {
return (
<div styleName="container">
<h1 styleName="title">Example</div>
<p styleName="text">Sample text paragraph.</p>
<p styleName="text special">
Sample text paragraph with special style.
</p>
</div>
);
}With this plugin and multiple stylesheets
Assuming:
- Styles
container,title, andtextare defined instyles-01.css. - Style
specialis defined instyles-02.css. - The plugin's
autoResolveMultipleImportsoption is enabled (default).
import './styles-01.css';
import './styles-02.css';
export default function Component() {
return (
<div styleName="container">
<h1 styleName="title">Example</div>
<p styleName="text">Sample text paragraph.</p>
<p styleName="text special">
Sample text paragraph with special style.
</p>
</div>
);
}If both files, styles-01.css and styles-02.css contain styles with the same
names, thus making auto resolution impossible, this plugin allows explicit
stylesheet prefixes:
import S1 from './styles-01.css';
import S2 from './styles-02.css';
export default function Component() {
return (
<div styleName="S1.container">
<h1 styleName="S1.title">Example</div>
<p styleName="S1.text">Sample text paragraph.</p>
<p styleName="S1.text S2.special">
Sample text paragraph with special style.
</p>
</div>
);
}With this plugin and runtime resolution
import './styles-01.css';
import './styles-02.css';
export default function Component({ special }) {
let textStyle = 'text';
if (special) textStyle += ' special';
return (
<div styleName="container">
<h1 styleName="title">Example</div>
<p styleName={textStyle}>Sample text paragraph.</p>
<p styleName={textStyle}>
Sample text paragraph with special style.
</p>
</div>
);
}In the case when the exact style value is not known at the compile time, like in
this example, the plugin will inject necessary code to correctly resolve the
styleName at runtime (which is somewhat less performant, but otherwise works
fine).
Installation
The core CSS Modules functionality should be enabled and configured elsewhere in your React project:
- With Create React App see Adding a CSS Modules Stylesheet.
- With bare Webpack see
modulesoption ofcss-loader. - With other frameworks refer to their documentation.
Install this plugin as a direct dependency (in edge-cases not allowing for a compile-time
styleNameresolution, the plugin falls back to the runtime resolution).npm install --save @dr.pogodin/babel-plugin-react-css-modulesInstall Webpack at least as a dev dependency:
npm install --save-dev webpackAdd the plugin to Babel configuration:
{ "plugins": [ ["@dr.pogodin/react-css-modules", { // The default localIdentName in "css-loader" is "[hash:base64]", // it is highly-recommended to explicitly specify the same value // both here, and in "css-loader" options, including the hash length // (the last digit in the template below). "generateScopedName": "[hash:base64:6]" // See below for all valid options. }] ] }
The
generateScopedNameoption value MUST matchlocalIdentNameoption ofcss-loaderto ensure both Webpack and this plugin generate matching class names. The same goes for other options impacting class names (e.g. the default length of hashes generated by Webpack, which is used if you don't specify the hash length explicitly inlocalIdentNamehash placeholders), and also the actuals version of this plugin andcss-loader(seecss-loadercompatibility).Optional.
css-loaderis known for eventual minor updates in their default class name generation logic that require counterpart upgrades of this plugin to keep it compatible. They denied to expose the default class name generator for re-used by 3rd party libraries, and suggested to rely ongetLocalIdentoption if unwanted class name changes due tocss-loaderupdates are a problem for a particular project.To alleviate this issue, this plugin provides stable default implementation for
getLocalIdentfunction (taken from a selected earlier version ofcss-loader). Consider to use it:Within Webpack Config
const { getLocalIdent } = require('@dr.pogodin/babel-plugin-react-css-modules/utils'); const cssLoaderOptions = { modules: { getLocalIdent, localIdentName: '[path]___[name]__[local]___[hash:base64:6]' } };
Within Babel Config
const { generateScopedNameFactory } = require('@dr.pogodin/babel-plugin-react-css-modules/utils'); module.exports = { plugins: [ ["@dr.pogodin/react-css-modules", { generateScopedName: // The classname template MUST match "localIdentName" option value // you passed to "css-loader". generateScopedNameFactory("[path]___[name]__[local]___[hash:base64:6]"), }] ] };
In addition to the standard class name template placeholders mentioned in
css-loaderdocumentation the version ofgetLocalIdent()andgenerateScopedName()provided by this plugin also support[package]placeholder. If used, it looks up from CSS file for the closestpackage.jsonfile, takes the package name from it, and inserts it into the class name (this is useful for CSS bundling for libraries).
React Native
If you'd like to get this working in React Native, you're going to have to allow
custom import extensions, via a rn-cli.config.js file:
module.exports = {
getAssetExts() {
return ["scss"];
}
}Remember, also, that the bundler caches things like plugins and presets. If you
want to change your .babelrc (to add this plugin) then you'll want to add the
--reset-cache flag to the end of the package command.
Configuration
Plugin Options
These are valid plugin options. All are optional, but the overall configuration
should be compatible with that of css-loader, thus defaults may not work for
you.
context- string - Must match webpackcontextconfiguration.css-loaderinheritscontextvalues from webpack. Other CSS module implementations might use different context resolution logic. Defaultsprocess.cwd().exclude- string - A RegExp that will exclude otherwise included files e.g., to exclude all styles from node_modules:exclude: 'node_modules'.filetypes- Configurate syntax loaders like sugarss, LESS and SCSS, and extra plugins for them.generateScopedName- function | string - Allows to customize the exactstyleNametoclassNameconversion algorithm. For details see Generating scoped names. Defaults[path]___[name]__[local]___[hash:base64:5].removeImport- boolean - Remove the matching style import. This option is used to enable server-side rendering. Defaults false.webpackHotModuleReloading- boolean - Enables hot reloading of CSS in webpack. Defaults false.handleMissingStyleName- string - Determines what should be done for undefined CSS modules (using astyleNamefor which there is no CSS module defined). Valid values:"throw","warn","ignore". Setting this option to"ignore"is equivalent to settingerrorWhenNotFound: falsein react-css-modules. Defaults"throw".attributeNames- Custom Attribute Mappingskip- boolean - Whether to apply plugin if no matchingattributeNamesfound in the file. Defaults false.autoResolveMultipleImports- boolean Allow multiple anonymous imports ifstyleNameis only in one of them. Defaults true.
Configurate syntax loaders
To add support for different CSS syntaxes (e.g. SCSS), perform the following two steps:
Add the postcss syntax loader as a development dependency:
npm install postcss-scss --save-dev
Add a
filetypessyntax mapping to the Babel plugin configuration. For example for SCSS:"filetypes": { ".scss": { "syntax": "postcss-scss" } }
And optionally specify extra plugins:
"filetypes": { ".scss": { "syntax": "postcss-scss", "plugins": [ "postcss-nested" ] } }
NOTE:
postcss-nestedis added as an extra plugin for demonstration purposes only. It's not needed withpostcss-scssbecause SCSS already supports nesting.Postcss plugins can have options specified by wrapping the name and an options object in an array inside your config:
"plugins": [ ["postcss-import-sync2", { "path": ["src/styles", "shared/styles"] }], "postcss-nested" ]
Custom Attribute Mapping
You can set your own attribute mapping rules using the attributeNames option.
It's an object, where keys are source attribute names and values are destination attribute names.
For example, the
<NavLink>
component from React Router
has an activeClassName attribute to accept an additional class name. You can
set "attributeNames": { "activeStyleName": "activeClassName" } to transform it.
The default styleName -> className transformation will not be affected
by an attributeNames value without a styleName key. Of course you can use
{ "styleName": "somethingOther" } to change it, or use { "styleName": null }
to disable it.
Under the hood
How does it work?
This plugin does the following:
- Builds index of all stylesheet imports per file (imports of files with
.cssor.scssextension). - Uses postcss to parse the matching CSS files into a lookup of CSS module references.
- Iterates through all JSX element declarations.
- Parses the
styleNameattribute value into anonymous and named CSS module references. - Finds the CSS class name matching the CSS module reference:
- If
styleNamevalue is a string literal, generates a string literal value. - If
styleNamevalue is ajSXExpressionContainer, uses a helper function (getClassName) to construct theclassNamevalue at the runtime.
- If
- Removes the
styleNameattribute from the element. - Appends the resulting
classNameto the existingclassNamevalue (createsclassNameattribute if one does not exist).
Project history
This plugin is an up-to-date, well-maintained fork of the original
babel-plugin-react-css-modules:
- It generates class names matching current
css-loaderversions (seecss-loadercompatibility for details). - All dependencies are upgraded to the latest versions.
- Follow-up maintenance and improvements are performed as necessary.
The original babel-plugin-react-css-modules plugin is largely abandoned by
its author since March 2019. When an year later updates of css-loader and
Webpack broke dependant projects, with no reaction from
babel-plugin-react-css-modules author on emerging issue reports in GitHub,
I (birdofpreyru) created this fork to ensure stability of my own projects
relying on it.
I am banned from commenting in the original project repo since I tried a little self-promo, trying to encourage people to switch over to my fork. If you read this, consider to spread the word to encourage more users to move to this fork.
Migration from babel-plugin-react-css-modules
Prefix plugin name in your Babel config by
@dr.pogodin/scope, i.e.:@dr.pogodin/babel-plugin-react-css-modulesor@dr.pogodin/react-css-moudlesinstead ofbabel-plugin-react-css-modulesorreact-css-modules.Be sure to have
webpackinstalled (it is a must-to-have peer dependency of this plugin starting fromv6.2.0).
css-loader compatibility
css-loader versions |
this plugin versions |
|---|---|
6.5.0 ÷ 6.5.1 (latest) |
6.5.1 ÷ 6.5.4 (latest) |
6.4.0 |
6.4.0 ÷ 6.4.1 |
6.0.0 ÷ 6.3.0 |
6.2.1 ÷ 6.3.1 |
5.2.5 ÷ 5.2.7 |
6.1.1 |
5.2.4 |
6.1.0 |
5.1.3 ÷ 5.2.3 |
6.0.11/6.1.0(1) |
5.0.0 ÷ 5.1.2 |
6.0.7 ÷ 6.0.11 |
4.2.0 ÷ 4.3.0 |
6.0.3 ÷ 6.0.6 |
<= 3.6.0 |
original plugin |
1) There might be some corner-case differences in class name transformation between these versions of css-loader and this plugin, but most probably they won't break compatibility for most users.