First, the pain point of traditional class

With the popularity of MVVM front-end frameworks supporting componentization such as React and Vue, the technical solution of writing CSS directly in JS is becoming more and more accepted.

Why do front-end developers prefer these CSS-in-JS solutions? I think the key reasons are as follows:

  1. At the beginning of the design of CSS, the consideration of “componentization” is incomplete. CSS works directly on the global, not directly on the internal style of a component.
  2. There are a lot of “component styles change with data” scenarios in our front-end components, but traditional CSS is not equipped to handle this scenario.
  3. There are some specifications that can be used to circumvent the problem, but it is too cumbersome to use and not conducive to cross-team writing.

For example, a form component that follows the BEM specification would look like this:

1 <style> 2 .form { } 3 .form--theme-xmas { } 4 .form--simple { } 5 .form__input { } 6 .form__submit { } 7 .form__submit--disabled { } 8 </style> 9 <form class="form form--theme-xmas form--simple"> 10 <input class="form__input"  type="text" /> 11 <input class="form__submit form__submit--disabled" type="submit" /> 12 </form>Copy the code

It’s too complicated! If this were business code (business code, mind you), the rest of the team would be devastated to read it. Of course, if you are maintaining basic components, it is important to follow the BEM’s “Blocks, Elements, and modifiers.”

Write CSS in React

2-1. ClassName with specification constraints

Use naming conventions (such as the BEM specification) to constrain classnames, such as the following:

1  // style.css
2  .form {
3    background-color: white;
4  }
5  .form__input {
6    color: black;
7  }
8 
9  import './stype.css'
10 const App = props => {
11   return (
12     <form className="form">
13       <input class="form__input" type="text" />
14     </form>
15   )
16 }
Copy the code

This approach is more suitable for basic component library development for the following reasons:

  1. Using a component library developed by class, the business side can be easily overridden by component styles.
  2. The base component library is usually developed by a dedicated team with uniform naming conventions.
  3. Using the most basic class can effectively reduce the size of the component library.

2-2, the inline styling

1  const App = props => {
2    return (
3      <div style={{color: "red"}}>123</div>
4    )
5  }
Copy the code

This is the JSX syntax’s built-in style setting method, rendering inline styles, which has the benefit of using global variables in style (although CSS preprocessing languages like LESS actually support this). Also, if you just want to adjust the margin of the component, this is the least amount of code to write.

2-3, CSS-loader (CSS Module)

Css-loader with WebPack can specify the scope of the style when packaging the project. For example, we can package it like this:

1 // webpack config 2 module.exports = { 3 module: { 4 loaders: [ 5 { 6 test: /\.css$/, 7 loader: 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' 8 }, 9 ] 10 }, 11 ... 12}Copy the code
1 // App.css 2 .app { 3 background-color: red; 4 } 5 .form-item{ 6 color: red; 7}Copy the code
1  import styles from './App.css';
2  const App = props => {
3    return (
4      <div className={style.app}>123</div>
5      <div className={style['form-6  item']}>456</div>
6    )
7  }

Copy the code

So the dot app is going to compile to. App__app___hash. This approach uses webPack implementation to apply the CSS within the component only to the styling within the component, which is a better solution than writing inline STYLING directly.

But using style[‘form-item’] for className values (and we use the “-” symbol when we write separate CSS files), I think many developers would be embarrassed…

In addition, although WebPack supports “-” and hump conversion, but in practical development, if facing a more style component, using “-” in the CSS file and then using the hump in the JS component also has some understanding cost.

2-4, CSS – in – js

As the name suggests, csS-in-JS is a technique for writing CSS directly in JS. It is also the official recommended solution for writing CSS in React, available at github.com/MicheleBert… There are more than 60 csS-in-JS related packages in this repository. The following describes the CSS-IN-JS solution using emotion as an example:

1 import {CSS, JSX} from '@emotion/core' 2 const color = 'white' 3 It's called after the template string has been processed, 5 // We can use this function to manipulate the template string before printing the final result https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals 7 const App = props => { 8 return ( 9 <div 10 className={css` 11 padding: 32px; 12 background-color: hotpink; 13 font-size: 24px; 14 border-radius: 4px; 15 `} 16 > 17 This is test. 18 </div> 19 ) 20 }Copy the code

In the development of business code, because there are many maintenance personnel and not fixed, and the code size will gradually increase, can not guarantee that CSS will not cross influence, so we can not only through the specification to restrain, but through csS-in-JS solutions to solve the problem of CSS cross influence.

Third, CSS-IN-JS scheme comparison

We chose github.com/MicheleBert… Compare several CSS-in-JS schemes that support comprehensive functions and have many monthly downloads in the warehouse (in fact, there is not much difference between them in use, mainly because there are some differences in implementation principles and supported features)

package star gzip size feature
styled-components 17306 12.5 kB Automatic Vendor Prefixing, Pseudo Classes, Media Queries
emotion 4101 5.92 kB (core) Automatic Vendor Prefixing, Pseudo Classes, Media Queries, Styles As Object Literals, Extract CSS File
radium 6372 23.3 kB Automatic Vendor Prefixing, Pseudo Classes, Media Queries, Styles As Object Literals
aphrodite 4175 7.23 kB Automatic Vendor Prefixing, Pseudo Classes, Media Queries, Styles As Object Literals, Extract CSS File
jss 5900 6.73 kB Automatic Vendor Prefixing, Pseudo Classes, Media Queries, Styles As Object Literals, Extract CSS File

In terms of size: Emotion is the smallest.

Styled – Components Has the most stars and is relatively well-documented in terms of the technology ecosystem (and popularity).

Emotion, Aphrodite, and JSS support the most features.

As such, newcomers can try styled components, while emotion is a good choice.

Our team actually started front-end development using React + Emotion a long time ago. The main reason for choosing the Emotion was that it had the most comprehensive features and the smallest size in the CSS-IN-JS scheme available at the time.

Emotion is one of the few CSS-in-JS frameworks that supports Source-Map.

Iv. Introduction to emotion

4-1 emotion Effect

First let’s look at what emotion does. Here’s a React component that uses emotion:

1  import React from 'react';
2  import { css } from 'emotion'
3  const color = 'white'
4  function App() {
5    return (
6      <div className={css`
7        padding: 32px;
8        background-color: hotpink;
9        font-size: 24px;
10       border-radius: 4px;
11       &:hover {
12         color: ${color};
13       }
14     `}>
15       This is emotion test
16     </div>
17   );
18 }
19 export default App;
Copy the code

Here is the rendered HTML:

1  <html lang="en">
2    <head>
3      <title>React App</title>
4      <style data-emotion="css">
5        .css-czz5zq {
6          padding: 32px;
7          background-color: hotpink;
8          font-size: 24px;
9          border-radius: 4px;
10      }
11    </style>
12    <style data-emotion="css">
13      .css-czz5zq:hover {
14        color: white;
15      }
16    </style>
17  </head>
18  <body>
19    <div id="root">
20      <div class="css-czz5zq">This is React.js test</div>
21    </div>
22   </body>
23 </html>
Copy the code

We can see that emotion actually does three things:

  1. Writes the style to the template string and passes it in as a parametercssMethods.
  2. Generate the class name from the template string and fill in the component’sclass="xxxx"In the.
  3. Place the generated class name and the class content in<style>Tag, and then put it in the HEAD of the HTML file.

4-2. Initialize emotion

First, we can see that the createEmotion method in the create-emotion package is called when the emotion is instantiated (i.e., when we import {CSS} from ’emotion’ in the component). The main purpose of this method is to initialize the emotion cache (used to generate the style and put it in , more on that later) and initialize some common methods, including the CSS method that we use most often.

1 import createEmotion from 'create-emotion' 2 export const { 3 injectGlobal, 4 keyframes, 5 css, 6 cache, 7 //... 8 } = createEmotion() 9 ``` 10 ```ts 11 let createEmotion = (options: *): Emotion => {12 // Generate Emotion cache 13 Let cache = createCache(options) 14 // Use common CSS 15 let CSS = (... args) => { 16 let serialized = serializeStyles(args, cache.registered, undefined) 17 insertStyles(cache, serialized, False) 18 return '${cache.key}-${serialized. Name}' 19} 20 // For CSS animation 21 let keyframes = (... args) => { 22 let serialized = serializeStyles(args, cache.registered) 23 let animation = `animation-${serialized.name}` 24 insertWithoutScoping(cache, { 25 name: serialized.name, 26 styles: Styles}} '27}) 28 return animation 29} 30 31 // Register global variables 32 let injectGlobal = (... args) => { 33 let serialized = serializeStyles(args, cache.registered) 34 insertWithoutScoping(cache, serialized) 35 } 36 return { 37 css, 38 injectGlobal, 39 keyframes, 40 cache, 41 //... 43 42}}Copy the code

4-3, emotion cache

The emotion cache is used to cache registered styles, that is, styles that have been put into the head. When the cache is generated, a CSS precompiler called Stylis is used to compile the serialized styles we pass in, and it also generates the Insert style method.

1  let createCache = (options?: Options): EmotionCache => {
2    if (options === undefined) options = {}
3    let key = options.key || 'css'
4    let stylisOptions
5    if (options.prefix !== undefined) {
6      stylisOptions = {
7        prefix: options.prefix
8      }
9    }
10   let stylis = new Stylis(stylisOptions)
11   let inserted = {}
12   let container: HTMLElement
13   if (isBrowser) {
14     container = options.container || document.head
15   }
16   let insert: (
17     selector: string,
18     serialized: SerializedStyles,
19     sheet: StyleSheet,
20     shouldCache: boolean
21   ) => string | void
22   if (isBrowser) {
23     stylis.use(options.stylisPlugins)(ruleSheet)
24     insert = (
25       selector: string,
26       serialized: SerializedStyles,
27       sheet: StyleSheet,
28       shouldCache: boolean
29     ): void => {
30       let name = serialized.name
31       Sheet.current = sheet
32       stylis(selector, serialized.styles)  // 该方法会在对应的selector中添加对应的styles
33       if (shouldCache) {
34         cache.inserted[name] = true
35       }
36     }
37   }
38   const cache: EmotionCache = {
39     key,
40     sheet: new StyleSheet({
41       key,
42       container,
43       nonce: options.nonce,
44       speedy: options.speedy
45     }),
46     nonce: options.nonce,
47     inserted,
48     registered: {},
49     insert
50   }
51   return cache
52 }
Copy the code

4-4. Emotion THE CSS method

This is the most important method in emotion. It actually calls the serializeStyles method to handle the arguments in the CSS method, inserts them into the HTML file using the insertStyles method, and returns the class name. Then we use

in the component to point to the appropriate style.

1 let css = (... args) => { 2 let serialized = serializeStyles(args, cache.registered, undefined) 3 insertStyles(cache, serialized, false) 4 return `${cache.key}-${serialized.name}` 5 }Copy the code

The serializeStyles method is a more complex method whose main purpose is to process the parameters passed in the CSS method and generate a serialized class.

1 export const serializeStyles = function( 2 args: Array<Interpolation>, 3 registered: RegisteredCache | void, 4 mergedProps: void | Object 5 ): SerializedStyles {6 // If only one argument is passed, return 7 if (8 args. Length === 1&&9 typeof args[0] === 'object' &&10 args[0]! == null && 11 args[0].styles ! == undefined 12) {13 return args[0] 14} 15 16 You will need to merge these style 17 let stringMode = true 18 let styles = '19 let strings = args [0] 20 if (strings = = null | | strings.raw === undefined) { 21 stringMode = false 22 styles += handleInterpolation(mergedProps, registered, strings, false) 23 } else { 24 styles += strings[0] 25 } 26 // we start at 1 since we've already handled the first arg 27 for (let i = 1; i < args.length; i++) { 28 styles += handleInterpolation( 29 mergedProps, 30 registered, 31 args[i], 32 styles.charCodeAt(styles.length - 1) === 46 33 ) 34 if (stringMode) { 35 styles += strings[i] 36 } 37 } 38 // using a  global regex with .exec is stateful so lastIndex has to be reset each time 39 labelPattern.lastIndex = 0 40 let identifierName = '' 41 let match 42 while ((match = labelPattern.exec(styles)) ! == null) { 43 identifierName += 44 '-' + 45 match[1] 46 } 47 let name = hashString(styles) + identifierName 48 return { 49 name, 50 styles 51} 52} 55 function handleInterpolation(55 mergedProps: void | Object, 56 registered: RegisteredCache | void, 57 interpolation: Interpolation, 58 couldBeSelectorInterpolation: boolean 59 ): string | number { 60 // ... 61} 62Copy the code

InsertStyles is a simple method that reads the cache to see if the style has been inserted. If not, it calls the cache insert method and inserts the style into the head.

1 export const insertStyles = ( 2 cache: EmotionCache, 3 serialized: SerializedStyles, 4 isStringTag: boolean 5 ) => { 6 let className = `${cache.key}-${serialized.name}` 7 if (cache.inserted[serialized.name] === undefined) { 8 let current = serialized 9 do { 10 let maybeStyles = cache.insert( 11 `.${className}`, 12 current, 13 cache.sheet, 14 true 15 ) 16 current = current.next 17 } while (current ! == undefined) 18 } 19 }Copy the code

5. Some conclusions

In general, if you are developing basic components, using “spec constrained” native CSS (such as BEM compliant CSS) or a preprocessor language such as LESS is appropriate to minimize the size of the component library and provide style coverage for business parties.

If it is for business development, I personally recommend CSS-in-JS, because it can not only write CSS directly in components, but also directly use JS variables in components, which can effectively solve the problem of “component style changes with data”. In addition, in business development, due to the rapid iteration speed and relatively large developer mobility, there is a certain risk that we directly use the specification to constrain CSS. When the project scale gradually increases, the code readability will be poor, and CSS will influence each other.

In addition, CSS Module is also a good solution in business development, but most front-end developers will use “-” to name the style, but in the component can only use style[‘form-item’] way to reference, I personally do not like this style of writing.

However, no single technology can solve all problems, and choosing the most appropriate technology for different scenarios is the best solution. If your team is not using any of these techniques, and you encounter the “pain points of traditional CSS in component development” described in this article, I recommend trying CSS-in-JS or CSS Modules, depending on which is more acceptable to your team members. If you are already using CSS-in-JS or CSS Modules, continue to use them, which cover scenarios for existing component development.

Write at the end

Mainstream front-end frameworks like Vue and Angular provide additional handling for CSS scoping, such as Vue scoped. React leaves CSS scoping entirely up to the community, resulting in a variety of CSS-in-JS frameworks. Personally, I still find vue SFC scoped the most delicious.

1  <style scoped>
2  .example {
3    color: red;
4  }
5  </style>
6  <template>
7    <div class="example">hi</div>
8  </template>
Copy the code

Author: shadowings – zy