UNPKG

19 kBMarkdownView Raw
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
12Transforms `styleName` to `className` using compile time [CSS module](#css-modules) resolution.
13
14In 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
44In the context of React, CSS Modules look like this:
45
46```js
47import React from 'react';
48import styles from './table.css';
49
50export 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
63Rendering 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
75and a corresponding CSS file that matches those CSS classes.
76
77Awesome!
78
79However, 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
89import React from 'react';
90import './table.css';
91
92export 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
105Using `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
128The 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
149View the [./benchmark](./benchmark).
150
151Run the benchmark:
152
153```bash
154git clone git@github.com:gajus/babel-plugin-react-css-modules.git
155cd ./babel-plugin-react-css-modules
156npm install
157npm run build
158cd ./benchmark
159npm install
160NODE_ENV=production ./test
161```
162
163## How does it work?
164
1651. Builds index of all stylesheet imports per file (imports of files with `.css` or `.scss` extension).
1661. Uses [postcss](https://github.com/postcss/postcss) to parse the matching CSS files.
1671. Iterates through all [JSX](https://facebook.github.io/react/docs/jsx-in-depth.html) element declarations.
1681. Parses the `styleName` attribute value into anonymous and named CSS module references.
1691. 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.
1721. Removes the `styleName` attribute from the element.
1731. Appends the resulting `className` to the existing `className` value (creates `className` attribute if one does not exist).
174
175## Configuration
176
177Configure 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
205Missing 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
213type FiletypeOptionsType = {|
214 +syntax: string,
215 +plugins?: $ReadOnlyArray<string | $ReadOnlyArray<[string, mixed]>>
216|};
217
218type FiletypesConfigurationType = {
219 [key: string]: FiletypeOptionsType
220};
221
222type GenerateScopedNameType = (localName: string, resourcePath: string) => string;
223
224type GenerateScopedNameConfigurationType = GenerateScopedNameType | string;
225
226type AttributeNameMapType = {
227 [key: string]: string
228};
229
230```
231
232### Configurate syntax loaders
233
234To add support for different CSS syntaxes (e.g. SCSS), perform the following two steps:
235
2361. 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
2422. 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
279You can set your own attribute mapping rules using the `attributeNames` option.
280
281It's an object, where keys are source attribute names and values are destination attribute names.
282
283For example, the [&lt;NavLink&gt;](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
285The 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
289When `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
292npm install babel-plugin-react-css-modules --save
293```
294
295
296### React Native
297
298If 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
301module.exports = {
302 getAssetExts() {
303 return ["scss"];
304 }
305}
306```
307
308Remember, 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
313git clone git@github.com:gajus/babel-plugin-react-css-modules.git
314cd ./babel-plugin-react-css-modules/demo
315npm install
316npm start
317```
318
319```bash
320open http://localhost:8080/
321```
322
323## Conventions
324
325### Anonymous reference
326
327Anonymous reference can be used when there is only one stylesheet import.
328
329Format: `CSS module name`.
330
331Example:
332
333```js
334import './foo1.css';
335
336// Imports "a" CSS module from ./foo1.css.
337<div styleName="a"></div>;
338```
339
340### Named reference
341
342Named reference is used to refer to a specific stylesheet import.
343
344Format: `[name of the import].[CSS module name]`.
345
346Example:
347
348```js
349import foo from './foo1.css';
350import 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
363When `styleName` is a literal string value, `babel-plugin-react-css-modules` resolves the value of `className` at the compile time.
364
365Input:
366
367```js
368import './bar.css';
369
370<div styleName="a"></div>;
371
372```
373
374Output:
375
376```js
377import './bar.css';
378
379<div className="bar___a"></div>;
380
381```
382
383### Named `styleName` resolution
384
385When 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
390Input:
391
392```js
393import foo from './foo1.css';
394import bar from './bar1.css';
395
396<div styleName="foo.a"></div>;
397<div styleName="bar.a"></div>;
398```
399
400Output:
401
402```js
403import foo from './foo1.css';
404import 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
413When 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
415Input:
416
417```js
418import './bar.css';
419
420<div styleName={Math.random() > .5 ? 'a' : 'b'}></div>;
421
422```
423
424Output:
425
426```js
427import _getClassName from 'babel-plugin-react-css-modules/dist/browser/getClassName';
428import foo from './bar.css';
429
430const _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
455Follow 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
462If 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
474This 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
480To 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.