1 | # babel-plugin-react-css-modules
|
2 |
|
3 | [![GitSpo Mentions](https://gitspo.com/badges/gajus/babel-plugin-react-css-modules?style=flat-square)](https://gitspo.com/mentions/gajus/babel-plugin-react-css-modules)
|
4 | [![Travis build status](http://img.shields.io/travis/gajus/babel-plugin-react-css-modules/master.svg?style=flat-square)](https://travis-ci.org/gajus/babel-plugin-react-css-modules)
|
5 | [![NPM version](http://img.shields.io/npm/v/babel-plugin-react-css-modules.svg?style=flat-square)](https://www.npmjs.org/package/babel-plugin-react-css-modules)
|
6 | [![Canonical Code Style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical)
|
7 | [![Gitter](https://img.shields.io/gitter/room/babel-plugin-react-css-modules/Lobby.svg?style=flat-square)](https://gitter.im/babel-plugin-react-css-modules/Lobby)
|
8 | [![Twitter Follow](https://img.shields.io/twitter/follow/kuizinas.svg?style=social&label=Follow)](https://twitter.com/kuizinas)
|
9 |
|
10 | <img src='./.README/babel-plugin-react-css-modules.png' height='150' />
|
11 |
|
12 | Transforms `styleName` to `className` using compile time [CSS module](#css-modules) resolution.
|
13 |
|
14 | In contrast to [`react-css-modules`](https://github.com/gajus/react-css-modules), `babel-plugin-react-css-modules` has a lot smaller performance overhead (0-10% vs +50%; see [Performance](#performance)) and a lot smaller size footprint (less than 2kb vs 17kb react-css-modules + lodash dependency).
|
15 |
|
16 | * [CSS Modules](#css-modules)
|
17 | * [Difference from `react-css-modules`](#difference-from-react-css-modules)
|
18 | * [Performance](#performance)
|
19 | * [How does it work?](#how-does-it-work)
|
20 | * [Conventions](#conventions)
|
21 | * [Anonymous reference](#anonymous-reference)
|
22 | * [Named reference](#named-reference)
|
23 | * [Configuration](#configuration)
|
24 | * [Configurate syntax loaders](#configurate-syntax-loaders)
|
25 | * [Custom Attribute Mapping](#custom-attribute-mapping)
|
26 | * [Installation](#installation)
|
27 | * [React Native](#react-native)
|
28 | * [Demo](#demo)
|
29 | * [Example transpilations](#example-transpilations)
|
30 | * [Anonymous `styleName` resolution](#anonymous-stylename-resolution)
|
31 | * [Named `styleName` resolution](#named-stylename-resolution)
|
32 | * [Runtime `styleName` resolution](#runtime-stylename-resolution)
|
33 | * [Limitations](#limitations)
|
34 | * [Have a question or want to suggest an improvement?](#have-a-question-or-want-to-suggest-an-improvement)
|
35 | * [FAQ](#faq)
|
36 | * [How to migrate from react-css-modules to babel-plugin-react-css-modules?](#how-to-migrate-from-react-css-modules-to-babel-plugin-react-css-modules)
|
37 | * [How to reference multiple CSS modules?](#how-to-reference-multiple-css-modules)
|
38 | * [How to live reload the CSS?](#hot-to-live-reload-the-css)
|
39 |
|
40 | ## CSS Modules
|
41 |
|
42 | [CSS Modules](https://github.com/css-modules/css-modules) are awesome! If you are not familiar with CSS Modules, it is a concept of using a module bundler such as [webpack](http://webpack.github.io/docs/) to load CSS scoped to a particular document. CSS module loader will generate a unique name for each CSS class at the time of loading the CSS document ([Interoperable CSS](https://github.com/css-modules/icss) to be precise). To see CSS Modules in practice, [webpack-demo](https://css-modules.github.io/webpack-demo/).
|
43 |
|
44 | In the context of React, CSS Modules look like this:
|
45 |
|
46 | ```js
|
47 | import React from 'react';
|
48 | import styles from './table.css';
|
49 |
|
50 | export default class Table extends React.Component {
|
51 | render () {
|
52 | return <div className={styles.table}>
|
53 | <div className={styles.row}>
|
54 | <div className={styles.cell}>A0</div>
|
55 | <div className={styles.cell}>B0</div>
|
56 | </div>
|
57 | </div>;
|
58 | }
|
59 | }
|
60 |
|
61 | ```
|
62 |
|
63 | Rendering the component will produce a markup similar to:
|
64 |
|
65 | ```js
|
66 | <div class="table__table___32osj">
|
67 | <div class="table__row___2w27N">
|
68 | <div class="table__cell___1oVw5">A0</div>
|
69 | <div class="table__cell___1oVw5">B0</div>
|
70 | </div>
|
71 | </div>
|
72 |
|
73 | ```
|
74 |
|
75 | and a corresponding CSS file that matches those CSS classes.
|
76 |
|
77 | Awesome!
|
78 |
|
79 | However, there are several disadvantages of using CSS modules this way:
|
80 |
|
81 | * You have to use `camelCase` CSS class names.
|
82 | * You have to use `styles` object whenever constructing a `className`.
|
83 | * Mixing CSS Modules and global CSS classes is cumbersome.
|
84 | * Reference to an undefined CSS Module resolves to `undefined` without a warning.
|
85 |
|
86 | `babel-plugin-react-css-modules` automates loading of CSS Modules using `styleName` property, e.g.
|
87 |
|
88 | ```js
|
89 | import React from 'react';
|
90 | import './table.css';
|
91 |
|
92 | export default class Table extends React.Component {
|
93 | render () {
|
94 | return <div styleName='table'>
|
95 | <div styleName='row'>
|
96 | <div styleName='cell'>A0</div>
|
97 | <div styleName='cell'>B0</div>
|
98 | </div>
|
99 | </div>;
|
100 | }
|
101 | }
|
102 |
|
103 | ```
|
104 |
|
105 | Using `babel-plugin-react-css-modules`:
|
106 |
|
107 | * You are not forced to use the `camelCase` naming convention.
|
108 | * You do not need to refer to the `styles` object every time you use a CSS Module.
|
109 | * There is clear distinction between global CSS and CSS modules, e.g.
|
110 |
|
111 | ```js
|
112 | <div className='global-css' styleName='local-module'></div>
|
113 | ```
|
114 |
|
115 | <!--
|
116 | * You are warned when `styleName` refers to an undefined CSS Module ([`errorWhenNotFound`](#errorwhennotfound) option).
|
117 | * You can enforce use of a single CSS module per `ReactElement` ([`allowMultiple`](#allowmultiple) option).
|
118 | -->
|
119 |
|
120 | ## Difference from `react-css-modules`
|
121 |
|
122 | [`react-css-modules`](https://github.com/gajus/react-css-modules) introduced a convention of using `styleName` attribute to reference [CSS module](https://github.com/css-modules/css-modules). `react-css-modules` is a higher-order [React](https://facebook.github.io/react/) component. It is using the `styleName` value to construct the `className` value at the run-time. This abstraction frees a developer from needing to reference the imported styles object when using CSS modules ([What's the problem?](https://github.com/gajus/react-css-modules#whats-the-problem)). However, this approach has a measurable performance penalty (see [Performance](#performance)).
|
123 |
|
124 | `babel-plugin-react-css-modules` solves the developer experience problem without impacting the performance.
|
125 |
|
126 | ## Performance
|
127 |
|
128 | The important metric here is the "Difference from the base benchmark". "base" is defined as using React with hardcoded `className` values. The lesser the difference, the bigger the performance impact.
|
129 |
|
130 | > Note:
|
131 | > This benchmark suite does not include a scenario when `babel-plugin-react-css-modules` can statically construct a literal value at the build time.
|
132 | > If a literal value of the `className` is constructed at the compile time, the performance is equal to the base benchmark.
|
133 |
|
134 | |Name|Operations per second (relative margin of error)|Sample size|Difference from the base benchmark|
|
135 | |---|---|---|---|
|
136 | |Using `className` (base)|9551 (±1.47%)|587|-0%|
|
137 | |`react-css-modules`|5914 (±2.01%)|363|-61%|
|
138 | |`babel-plugin-react-css-modules` (runtime, anonymous)|9145 (±1.94%)|540|-4%|
|
139 | |`babel-plugin-react-css-modules` (runtime, named)|8786 (±1.59%)|527|-8%|
|
140 |
|
141 | > Platform info:
|
142 | >
|
143 | > * Darwin 16.1.0 x64
|
144 | > * Node.JS 7.1.0
|
145 | > * V8 5.4.500.36
|
146 | > * NODE_ENV=production
|
147 | > * Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz × 8
|
148 |
|
149 | View the [./benchmark](./benchmark).
|
150 |
|
151 | Run the benchmark:
|
152 |
|
153 | ```bash
|
154 | git clone git@github.com:gajus/babel-plugin-react-css-modules.git
|
155 | cd ./babel-plugin-react-css-modules
|
156 | npm install
|
157 | npm run build
|
158 | cd ./benchmark
|
159 | npm install
|
160 | NODE_ENV=production ./test
|
161 | ```
|
162 |
|
163 | ## How does it work?
|
164 |
|
165 | 1. Builds index of all stylesheet imports per file (imports of files with `.css` or `.scss` extension).
|
166 | 1. Uses [postcss](https://github.com/postcss/postcss) to parse the matching CSS files.
|
167 | 1. Iterates through all [JSX](https://facebook.github.io/react/docs/jsx-in-depth.html) element declarations.
|
168 | 1. Parses the `styleName` attribute value into anonymous and named CSS module references.
|
169 | 1. Finds the CSS class name matching the CSS module reference:
|
170 | * If `styleName` value is a string literal, generates a string literal value.
|
171 | * If `styleName` value is a [`jSXExpressionContainer`](https://babeljs.io/docs/en/next/babel-types.html#jsxexpressioncontainer), uses a helper function ([`getClassName`](./src/getClassName.js)) to construct the `className` value at the runtime.
|
172 | 1. Removes the `styleName` attribute from the element.
|
173 | 1. Appends the resulting `className` to the existing `className` value (creates `className` attribute if one does not exist).
|
174 |
|
175 | ## Configuration
|
176 |
|
177 | Configure the options for the plugin within your `.babelrc` as follows:
|
178 |
|
179 | ```json
|
180 | {
|
181 | "plugins": [
|
182 | ["react-css-modules", {
|
183 | "option": "value"
|
184 | }]
|
185 | ]
|
186 | }
|
187 |
|
188 | ```
|
189 |
|
190 | ### Options
|
191 |
|
192 | |Name|Type|Description|Default|
|
193 | |---|---|---|---|
|
194 | |`context`|`string`|Must match webpack [`context`](https://webpack.js.org/configuration/entry-context/#context) configuration. [`css-loader`](https://github.com/webpack/css-loader) inherits `context` values from webpack. Other CSS module implementations might use different context resolution logic.|`process.cwd()`|
|
195 | |`exclude`|`string`|A RegExp that will exclude otherwise included files e.g., to exclude all styles from node_modules `exclude: 'node_modules'`|
|
196 | |`filetypes`|`?FiletypesConfigurationType`|Configure [postcss syntax loaders](https://github.com/postcss/postcss#syntaxes) like sugarss, LESS and SCSS and extra plugins for them. ||
|
197 | |`generateScopedName`|`?GenerateScopedNameConfigurationType`|Refer to [Generating scoped names](https://github.com/css-modules/postcss-modules#generating-scoped-names). If you use this option, make sure it matches the value of `localIdentName` in webpack config. See this [issue](https://github.com/gajus/babel-plugin-react-css-modules/issues/108#issuecomment-334351241) |`[path]___[name]__[local]___[hash:base64:5]`|
|
198 | |`removeImport`|`boolean`|Remove the matching style import. This option is used to enable server-side rendering.|`false`|
|
199 | |`webpackHotModuleReloading`|`boolean`|Enables hot reloading of CSS in webpack|`false`|
|
200 | |`handleMissingStyleName`|`"throw"`, `"warn"`, `"ignore"`|Determines what should be done for undefined CSS modules (using a `styleName` for which there is no CSS module defined). Setting this option to `"ignore"` is equivalent to setting `errorWhenNotFound: false` in [react-css-modules](https://github.com/gajus/react-css-modules#errorwhennotfound). |`"throw"`|
|
201 | |`attributeNames`|`?AttributeNameMapType`|Refer to [Custom Attribute Mapping](#custom-attribute-mapping)|`{"styleName": "className"}`|
|
202 | |`skip`|`boolean`|Whether to apply plugin if no matching `attributeNames` found in the file|`false`|
|
203 | |`autoResolveMultipleImports`|`boolean`|Allow multiple anonymous imports if `styleName` is only in one of them.|`false`|
|
204 |
|
205 | Missing a configuration? [Raise an issue](https://github.com/gajus/babel-plugin-react-css-modules/issues/new?title=New%20configuration:).
|
206 |
|
207 | > Note:
|
208 | > The default configuration should work out of the box with the [css-loader](https://github.com/webpack/css-loader).
|
209 |
|
210 | #### Option types (flow)
|
211 |
|
212 | ```js
|
213 | type FiletypeOptionsType = {|
|
214 | +syntax: string,
|
215 | +plugins?: $ReadOnlyArray<string | $ReadOnlyArray<[string, mixed]>>
|
216 | |};
|
217 |
|
218 | type FiletypesConfigurationType = {
|
219 | [key: string]: FiletypeOptionsType
|
220 | };
|
221 |
|
222 | type GenerateScopedNameType = (localName: string, resourcePath: string) => string;
|
223 |
|
224 | type GenerateScopedNameConfigurationType = GenerateScopedNameType | string;
|
225 |
|
226 | type AttributeNameMapType = {
|
227 | [key: string]: string
|
228 | };
|
229 |
|
230 | ```
|
231 |
|
232 | ### Configurate syntax loaders
|
233 |
|
234 | To add support for different CSS syntaxes (e.g. SCSS), perform the following two steps:
|
235 |
|
236 | 1. Add the [postcss syntax loader](https://github.com/postcss/postcss#syntaxes) as a development dependency:
|
237 |
|
238 | ```bash
|
239 | npm install postcss-scss --save-dev
|
240 | ```
|
241 |
|
242 | 2. Add a filetype syntax mapping to the Babel plugin configuration
|
243 |
|
244 | ```json
|
245 | "filetypes": {
|
246 | ".scss": {
|
247 | "syntax": "postcss-scss"
|
248 | }
|
249 | }
|
250 | ```
|
251 |
|
252 | And optionaly specify extra plugins
|
253 |
|
254 | ```json
|
255 | "filetypes": {
|
256 | ".scss": {
|
257 | "syntax": "postcss-scss",
|
258 | "plugins": [
|
259 | "postcss-nested"
|
260 | ]
|
261 | }
|
262 | }
|
263 | ```
|
264 |
|
265 | Postcss plugins can have options specified by wrapping the name and an options object in an array inside your config
|
266 |
|
267 | ```json
|
268 | "plugins": [
|
269 | ["postcss-import-sync2", {
|
270 | "path": ["src/styles", "shared/styles"]
|
271 | }],
|
272 | "postcss-nested"
|
273 | ]
|
274 | ```
|
275 |
|
276 |
|
277 | ### Custom Attribute Mapping
|
278 |
|
279 | You can set your own attribute mapping rules using the `attributeNames` option.
|
280 |
|
281 | It's an object, where keys are source attribute names and values are destination attribute names.
|
282 |
|
283 | For example, the [<NavLink>](https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/api/NavLink.md) component from [React Router](https://github.com/ReactTraining/react-router) has a `activeClassName` attribute to accept an additional class name. You can set `"attributeNames": { "activeStyleName": "activeClassName" }` to transform it.
|
284 |
|
285 | 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.
|
286 |
|
287 | ## Installation
|
288 |
|
289 | When `babel-plugin-react-css-modules` cannot resolve CSS module at a compile time, it imports a helper function (read [Runtime `styleName` resolution](#runtime-stylename-resolution)). Therefore, you must install `babel-plugin-react-css-modules` as a direct dependency of the project.
|
290 |
|
291 | ```bash
|
292 | npm install babel-plugin-react-css-modules --save
|
293 | ```
|
294 |
|
295 |
|
296 | ### React Native
|
297 |
|
298 | 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:
|
299 |
|
300 | ```js
|
301 | module.exports = {
|
302 | getAssetExts() {
|
303 | return ["scss"];
|
304 | }
|
305 | }
|
306 | ```
|
307 |
|
308 | 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.
|
309 |
|
310 | ### Demo
|
311 |
|
312 | ```bash
|
313 | git clone git@github.com:gajus/babel-plugin-react-css-modules.git
|
314 | cd ./babel-plugin-react-css-modules/demo
|
315 | npm install
|
316 | npm start
|
317 | ```
|
318 |
|
319 | ```bash
|
320 | open http://localhost:8080/
|
321 | ```
|
322 |
|
323 | ## Conventions
|
324 |
|
325 | ### Anonymous reference
|
326 |
|
327 | Anonymous reference can be used when there is only one stylesheet import.
|
328 |
|
329 | Format: `CSS module name`.
|
330 |
|
331 | Example:
|
332 |
|
333 | ```js
|
334 | import './foo1.css';
|
335 |
|
336 | // Imports "a" CSS module from ./foo1.css.
|
337 | <div styleName="a"></div>;
|
338 | ```
|
339 |
|
340 | ### Named reference
|
341 |
|
342 | Named reference is used to refer to a specific stylesheet import.
|
343 |
|
344 | Format: `[name of the import].[CSS module name]`.
|
345 |
|
346 | Example:
|
347 |
|
348 | ```js
|
349 | import foo from './foo1.css';
|
350 | import bar from './bar1.css';
|
351 |
|
352 | // Imports "a" CSS module from ./foo1.css.
|
353 | <div styleName="foo.a"></div>;
|
354 |
|
355 | // Imports "a" CSS module from ./bar1.css.
|
356 | <div styleName="bar.a"></div>;
|
357 | ```
|
358 |
|
359 | ## Example transpilations
|
360 |
|
361 | ### Anonymous `styleName` resolution
|
362 |
|
363 | When `styleName` is a literal string value, `babel-plugin-react-css-modules` resolves the value of `className` at the compile time.
|
364 |
|
365 | Input:
|
366 |
|
367 | ```js
|
368 | import './bar.css';
|
369 |
|
370 | <div styleName="a"></div>;
|
371 |
|
372 | ```
|
373 |
|
374 | Output:
|
375 |
|
376 | ```js
|
377 | import './bar.css';
|
378 |
|
379 | <div className="bar___a"></div>;
|
380 |
|
381 | ```
|
382 |
|
383 | ### Named `styleName` resolution
|
384 |
|
385 | When a file imports multiple stylesheets, you must use a [named reference](#named-reference).
|
386 |
|
387 | > Have suggestions for an alternative behaviour?
|
388 | > [Raise an issue](https://github.com/gajus/babel-plugin-react-css-modules/issues/new?title=Suggestion%20for%20alternative%20handling%20of%20multiple%20stylesheet%20imports) with your suggestion.
|
389 |
|
390 | Input:
|
391 |
|
392 | ```js
|
393 | import foo from './foo1.css';
|
394 | import bar from './bar1.css';
|
395 |
|
396 | <div styleName="foo.a"></div>;
|
397 | <div styleName="bar.a"></div>;
|
398 | ```
|
399 |
|
400 | Output:
|
401 |
|
402 | ```js
|
403 | import foo from './foo1.css';
|
404 | import bar from './bar1.css';
|
405 |
|
406 | <div className="foo___a"></div>;
|
407 | <div className="bar___a"></div>;
|
408 |
|
409 | ```
|
410 |
|
411 | ### Runtime `styleName` resolution
|
412 |
|
413 | When the value of `styleName` cannot be determined at the compile time, `babel-plugin-react-css-modules` inlines all possible styles into the file. It then uses [`getClassName`](https://github.com/gajus/babel-plugin-react-css-modules/blob/master/src/getClassName.js) helper function to resolve the `styleName` value at the runtime.
|
414 |
|
415 | Input:
|
416 |
|
417 | ```js
|
418 | import './bar.css';
|
419 |
|
420 | <div styleName={Math.random() > .5 ? 'a' : 'b'}></div>;
|
421 |
|
422 | ```
|
423 |
|
424 | Output:
|
425 |
|
426 | ```js
|
427 | import _getClassName from 'babel-plugin-react-css-modules/dist/browser/getClassName';
|
428 | import foo from './bar.css';
|
429 |
|
430 | const _styleModuleImportMap = {
|
431 | foo: {
|
432 | a: 'bar__a',
|
433 | b: 'bar__b'
|
434 | }
|
435 | };
|
436 |
|
437 | <div styleName={_getClassName(Math.random() > .5 ? 'a' : 'b', _styleModuleImportMap)}></div>;
|
438 |
|
439 | ```
|
440 |
|
441 | ## Limitations
|
442 |
|
443 | * [Establish a convention for extending the styles object at the runtime](https://github.com/gajus/babel-plugin-react-css-modules/issues/1)
|
444 |
|
445 | ## Have a question or want to suggest an improvement?
|
446 |
|
447 | * Have a technical questions? [Ask on Stack Overflow.](http://stackoverflow.com/questions/ask?tags=babel-plugin-react-css-modules)
|
448 | * Have a feature suggestion or want to report an issue? [Raise an issues.](https://github.com/gajus/babel-plugin-react-css-modules/issues)
|
449 | * Want to say hello to other `babel-plugin-react-css-modules` users? [Chat on Gitter.](https://gitter.im/babel-plugin-react-css-modules)
|
450 |
|
451 | ## FAQ
|
452 |
|
453 | ### How to migrate from react-css-modules to babel-plugin-react-css-modules?
|
454 |
|
455 | Follow the following steps:
|
456 |
|
457 | * Remove `react-css-modules`.
|
458 | * Add `babel-plugin-react-css-modules`.
|
459 | * Configure `.babelrc` (see [Configuration](#configuration)).
|
460 | * Remove all uses of the `cssModules` decorator and/or HoC.
|
461 |
|
462 | If you are still having problems, refer to one of the user submitted guides:
|
463 |
|
464 | * [Porting from react-css-modules to babel-plugin-react-css-modules (with Less)](http://www.jjinux.com/2018/04/javascript-porting-from-react-css.html)
|
465 |
|
466 | ### How to reference multiple CSS modules?
|
467 |
|
468 | `react-css-modules` had an option [`allowMultiple`](https://github.com/gajus/react-css-modules#allowmultiple). `allowMultiple` allows multiple CSS module names in a `styleName` declaration, e.g.
|
469 |
|
470 | ```js
|
471 | <div styleName='foo bar' />
|
472 | ```
|
473 |
|
474 | This behaviour is enabled by default in `babel-plugin-react-css-modules`.
|
475 |
|
476 | ### How to live reload the CSS?
|
477 |
|
478 | `babel-plugin-react-css-modules` utilises webpack [Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/#root) (HMR) to live reload the CSS.
|
479 |
|
480 | To enable live reloading of the CSS:
|
481 |
|
482 | * Enable [`webpackHotModuleReloading`](#configuration) `babel-plugin-react-css-modules` configuration.
|
483 | * Configure `webpack` to use HMR. Use [`--hot`](https://webpack.js.org/configuration/dev-server/#root) option if you are using `webpack-dev-server`.
|
484 | * Use [`style-loader`](https://github.com/webpack/style-loader) to load the style sheets.
|
485 |
|
486 | > Note:
|
487 | >
|
488 | > This enables live reloading of the CSS. To enable HMR of the React components, refer to the [Hot Module Replacement - React](https://webpack.js.org/guides/hot-module-replacement/#other-code-and-frameworks) guide.
|
489 |
|
490 | > Note:
|
491 | >
|
492 | > This is a [webpack](https://webpack.github.io/) specific option. If you are using `babel-plugin-react-css-modules` in a different setup and require CSS live reloading, raise an issue describing your setup.
|