UNPKG

15.8 kBMarkdownView Raw
1# babel-plugin-react-css-modules
2
3[![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)
4[![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)
5[![Canonical Code Style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical)
6[![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)
7[![Twitter Follow](https://img.shields.io/twitter/follow/kuizinas.svg?style=social&label=Follow)](https://twitter.com/kuizinas)
8
9<img src='./.README/babel-plugin-react-css-modules.png' height='150' />
10
11Transforms `styleName` to `className` using compile time [CSS module](#css-modules) resolution.
12
13In 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).
14
15* [CSS Modules](#css-modules)
16* [Difference from `react-css-modules`](#difference-from-react-css-modules)
17* [Performance](#performance)
18* [How does it work?](#how-does-it-work)
19* [Conventions](#conventions)
20 * [Anonymous reference](#anonymous-reference)
21 * [Named reference](#named-reference)
22* [Configuration](#configuration)
23 * [Configurate syntax loaders](#configurate-syntax-loaders)
24* [Installation](#installation)
25 * [Demo](#demo)
26* [Example transpilations](#example-transpilations)
27 * [Anonymous `styleName` resolution](#anonymous-stylename-resolution)
28 * [Named `styleName` resolution](#named-stylename-resolution)
29 * [Runtime `styleName` resolution](#runtime-stylename-resolution)
30* [Limitations](#limitations)
31* [Have a question or want to suggest an improvement?](#have-a-question-or-want-to-suggest-an-improvement)
32* [FAQ](#faq)
33 * [How to reference multiple CSS modules?](#how-to-reference-multiple-css-modules)
34 * [How to live reload the CSS?](#hot-to-live-reload-the-css)
35
36## CSS Modules
37
38[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/).
39
40In the context of React, CSS Modules look like this:
41
42```js
43import React from 'react';
44import styles from './table.css';
45
46export default class Table extends React.Component {
47 render () {
48 return <div className={styles.table}>
49 <div className={styles.row}>
50 <div className={styles.cell}>A0</div>
51 <div className={styles.cell}>B0</div>
52 </div>
53 </div>;
54 }
55}
56
57```
58
59Rendering the component will produce a markup similar to:
60
61```js
62<div class="table__table___32osj">
63 <div class="table__row___2w27N">
64 <div class="table__cell___1oVw5">A0</div>
65 <div class="table__cell___1oVw5">B0</div>
66 </div>
67</div>
68
69```
70
71and a corresponding CSS file that matches those CSS classes.
72
73Awesome!
74
75However, there are several several disadvantages of using CSS modules this way:
76
77* You have to use `camelCase` CSS class names.
78* You have to use `styles` object whenever constructing a `className`.
79* Mixing CSS Modules and global CSS classes is cumbersome.
80* Reference to an undefined CSS Module resolves to `undefined` without a warning.
81
82`babel-plugin-react-css-modules` automates loading of CSS Modules using `styleName` property, e.g.
83
84```js
85import React from 'react';
86import './table.css';
87
88export default class Table extends React.Component {
89 render () {
90 return <div styleName='table'>
91 <div styleName='row'>
92 <div styleName='cell'>A0</div>
93 <div styleName='cell'>B0</div>
94 </div>
95 </div>;
96 }
97}
98
99```
100
101Using `babel-plugin-react-css-modules`:
102
103* You are not forced to use the `camelCase` naming convention.
104* You do not need to refer to the `styles` object every time you use a CSS Module.
105* There is clear distinction between global CSS and CSS modules, e.g.
106
107 ```js
108 <div className='global-css' styleName='local-module'></div>
109 ```
110
111<!--
112* You are warned when `styleName` refers to an undefined CSS Module ([`errorWhenNotFound`](#errorwhennotfound) option).
113* You can enforce use of a single CSS module per `ReactElement` ([`allowMultiple`](#allowmultiple) option).
114-->
115
116## Difference from `react-css-modules`
117
118[`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)).
119
120`babel-plugin-react-css-modules` solves the developer experience problem without impacting the performance.
121
122## Performance
123
124The 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.
125
126> Note:
127> This benchmark suite does not include a scenario when `babel-plugin-react-css-modules` can statically construct a literal value at the build time.
128> If a literal value of the `className` is constructed at the compile time, the performance is equal to the base benchmark.
129
130|Name|Operations per second (relative margin of error)|Sample size|Difference from the base benchmark|
131|---|---|---|---|
132|Using `className` (base)|9551 (±1.47%)|587|-0%|
133|`react-css-modules`|5914 (±2.01%)|363|-61%|
134|`babel-plugin-react-css-modules` (runtime, anonymous)|9145 (±1.94%)|540|-4%|
135|`babel-plugin-react-css-modules` (runtime, named)|8786 (±1.59%)|527|-8%|
136
137> Platform info:
138>
139> * Darwin 16.1.0 x64
140> * Node.JS 7.1.0
141> * V8 5.4.500.36
142> * NODE_ENV=production
143> * Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz × 8
144
145View the [./benchmark](./benchmark).
146
147Run the benchmark:
148
149```bash
150git clone git@github.com:gajus/babel-plugin-react-css-modules.git
151cd ./babel-plugin-react-css-modules
152npm install
153npm run build
154cd ./benchmark
155npm install
156NODE_ENV=production ./test
157```
158
159## How does it work?
160
1611. Builds index of all stylesheet imports per file (imports of files with `.css` or `.scss` extension).
1621. Uses [postcss](https://github.com/postcss/postcss) to parse the matching CSS files.
1631. Iterates through all [JSX](https://facebook.github.io/react/docs/jsx-in-depth.html) element declarations.
1641. Parses the `styleName` attribute value into anonymous and named CSS module references.
1651. Finds the CSS class name matching the CSS module reference:
166 * If `styleName` value is a string literal, generates a string literal value.
167 * If `styleName` value is a [`jSXExpressionContainer`](https://github.com/babel/babel/tree/master/packages/babel-types#jsxexpressioncontainer), uses a helper function ([`getClassName`](./src/getClassName.js)) to construct the `className` value at the runtime.
1681. Removes the `styleName` attribute from the element.
1691. Appends the resulting `className` to the existing `className` value (creates `className` attribute if one does not exist).
170
171## Configuration
172
173Configure the options for the plugin within your `.babelrc` as follows:
174
175```json
176{
177 "plugins": [
178 ["react-css-modules", {
179 "option": "value"
180 }]
181 ]
182}
183
184```
185
186### Options
187
188|Name|Type|Description|Default|
189|---|---|---|---|
190|`context`|`string`|Must match webpack [`context`](https://webpack.github.io/docs/configuration.html#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()`|
191|`exclude`|`string`|A RegExp that will exclude otherwise included files e.g., to exclude all styles from node_modules `exclude: 'node_modules'`|
192|`filetypes`|`?FiletypesConfigurationType`|Configure [postcss syntax loaders](https://github.com/postcss/postcss#syntaxes) like sugerss, LESS and SCSS and extra plugins for them. ||
193|`generateScopedName`|`?GenerateScopedNameConfigurationType`|Refer to [Generating scoped names](https://github.com/css-modules/postcss-modules#generating-scoped-names)|`[path]___[name]__[local]___[hash:base64:5]`|
194|`removeImport`|`boolean`|Remove the matching style import. This option is used to enable server-side rendering.|`false`|
195|`webpackHotModuleReloading`|`boolean`|Enables hot reloading of CSS in webpack|`false`|
196|`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"`|
197
198Missing a configuration? [Raise an issue](https://github.com/gajus/babel-plugin-react-css-modules/issues/new?title=New%20configuration:).
199
200> Note:
201> The default configuration should work out of the box with the [css-loader](https://github.com/webpack/css-loader).
202
203#### Option types (flow)
204
205```js
206type FiletypeOptionsType = {|
207 +syntax: string,
208 +plugins?: $ReadOnlyArray<string>
209|};
210
211type FiletypesConfigurationType = {
212 [key: string]: FiletypeOptionsType
213};
214
215type GenerateScopedNameType = (localName: string, resourcePath: string) => string;
216
217type GenerateScopedNameConfigurationType = GenerateScopedNameType | string;
218
219```
220
221### Configurate syntax loaders
222
223To add support for different CSS syntaxes (e.g. SCSS), perform the following two steps:
224
2251. Add the [postcss syntax loader](https://github.com/postcss/postcss#syntaxes) as a development dependency:
226
227 ```bash
228 npm install postcss-scss --save-dev
229 ```
230
2312. Add a filetype syntax mapping to the Babel plugin configuration
232
233 ```json
234 "filetypes": {
235 ".scss": {
236 "syntax": "postcss-scss"
237 }
238 }
239 ```
240
241 And optionaly specify extra plugins
242
243 ```json
244 "filetypes": {
245 ".scss": {
246 "syntax": "postcss-scss",
247 "plugins": ["postcss-nested"]
248 }
249 }
250 ```
251
252## Installation
253
254When `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.
255
256```bash
257npm install babel-plugin-react-css-modules --save
258```
259
260### Demo
261
262```bash
263git clone git@github.com:gajus/babel-plugin-react-css-modules.git
264cd ./babel-plugin-react-css-modules/demo
265npm install
266webpack-dev-server
267```
268
269```bash
270open http://localhost:8080/
271```
272
273## Conventions
274
275### Anonymous reference
276
277Anonymous reference can be used when there is only one stylesheet import.
278
279Format: `CSS module name`.
280
281Example:
282
283```js
284import './foo1.css';
285
286// Imports "a" CSS module from ./foo1.css.
287<div styleName="a"></div>;
288```
289
290### Named reference
291
292Named reference is used to refer to a specific stylesheet import.
293
294Format: `[name of the import].[CSS module name]`.
295
296Example:
297
298```js
299import foo from './foo1.css';
300import bar from './bar1.css';
301
302// Imports "a" CSS module from ./foo1.css.
303<div styleName="foo.a"></div>;
304
305// Imports "a" CSS module from ./bar1.css.
306<div styleName="bar.a"></div>;
307```
308
309## Example transpilations
310
311### Anonymous `styleName` resolution
312
313When `styleName` is a literal string value, `babel-plugin-react-css-modules` resolves the value of `className` at the compile time.
314
315Input:
316
317```js
318import './bar.css';
319
320<div styleName="a"></div>;
321
322```
323
324Output:
325
326```js
327import './bar.css';
328
329<div className="bar___a"></div>;
330
331```
332
333### Named `styleName` resolution
334
335When a file imports multiple stylesheets, you must use a [named reference](#named-reference).
336
337> Have suggestions for an alternative behaviour?
338> [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.
339
340Input:
341
342```js
343import foo from './foo1.css';
344import bar from './bar1.css';
345
346<div styleName="foo.a"></div>;
347<div styleName="bar.a"></div>;
348```
349
350Output:
351
352```js
353import foo from './foo1.css';
354import bar from './bar1.css';
355
356<div className="foo___a"></div>;
357<div className="bar___a"></div>;
358
359```
360
361### Runtime `styleName` resolution
362
363When 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.
364
365Input:
366
367```js
368import './bar.css';
369
370<div styleName={Math.random() > .5 ? 'a' : 'b'}></div>;
371
372```
373
374Output:
375
376```js
377import _getClassName from 'babel-plugin-react-css-modules/dist/browser/getClassName';
378import foo from './bar.css';
379
380const _styleModuleImportMap = {
381 foo: {
382 a: 'bar__a',
383 b: 'bar__b'
384 }
385};
386
387<div styleName={_getClassName(Math.random() > .5 ? 'a' : 'b', _styleModuleImportMap)}></div>;
388
389```
390
391## Limitations
392
393* [Establish a convention for extending the styles object at the runtime](https://github.com/gajus/babel-plugin-react-css-modules/issues/1)
394
395## Have a question or want to suggest an improvement?
396
397* Have a technical questions? [Ask on Stack Overflow.](http://stackoverflow.com/questions/ask?tags=babel-plugin-react-css-modules)
398* Have a feature suggestion or want to report an issue? [Raise an issues.](https://github.com/gajus/babel-plugin-react-css-modules/issues)
399* Want to say hello to other `babel-plugin-react-css-modules` users? [Chat on Gitter.](https://gitter.im/babel-plugin-react-css-modules)
400
401## FAQ
402
403### How to reference multiple CSS modules?
404
405`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.
406
407```js
408<div styleName='foo bar' />
409```
410
411This behaviour is enabled by default in `babel-plugin-react-css-modules`.
412
413## How to live reload the CSS?
414
415`babel-plugin-react-css-modules` utilises webpack [Hot Module Replacement](https://webpack.github.io/docs/hot-module-replacement.html) (HMR) to live reload the CSS.
416
417To enable live reloading of the CSS:
418
419* Enable [`webpackHotModuleReloading`](#configuration) `babel-plugin-react-css-modules` configuration.
420* Configure `webpack` to use HMR. Use [`--hot`](https://webpack.github.io/docs/webpack-dev-server.html) option if you are using `webpack-dev-server`.
421* Use [`style-loader`](https://github.com/webpack/style-loader) to load the style sheets.
422
423> Note:
424>
425> 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/hmr-react/) guide.
426
427> Note:
428>
429> 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.