Tips | Lose weight by 50%-70%, Ctrip Taro mini program size reduction plan

About the Author

Can, a front-end developer at Ctrip, is currently engaged in mini program development and has a strong interest in compilation and packaging technology and mini program cross-platform solutions.

I. Overview

At present, our team's small program is developed using the Taro cross-end solution React framework. Based on the existing style solution, a large amount of style code redundancy will be generated after compilation and packaging, accounting for a large proportion of the compiled product of the project.

After analyzing the compiled style code, we found that redundant code is mainly reflected in two aspects:

  • The project style file uses a large number of parent-child selectors as scopes for style isolation. After compilation, there are a lot of repeated and redundant class names. For example, in the following SCSS file style code, .box .item is repeated three times redundantly after compilation.

// 编译前代码
.box {
  .item {
    .item1 {}
    .item2 {}
    .item3 {}
    .item4 {}
  }
}
// 编译后代码
.box .item .item1 {}
.box .item .item2 {}
.box .item .item3 {}
.box .item .item4 {}
  • A large number of attribute values ​​are repeated redundantly in the style code. For example, the most commonly used display: flex attribute value may have hundreds or thousands of redundant duplicates in the project, and after the Autoprefixer plug-in is turned on for compatibility, display: flex will become display:-webkit-flex; display: -ms-flexbox;display:flex; makes the redundancy of style file attribute values ​​more serious.

In response to the above problems encountered by the Taro project's React framework applet, this article will introduce a new style solution. This solution adopts the grammatical requirements of the cssModules style scheme without changing the existing development experience, and uses the convenience of the Taro plug-in to provide corresponding solutions, thereby "slimming" the product. The final style file can be slimmed down by 50% - 70%, further easing the limitations of the official package size and facilitating rapid business development.

2. Brief introduction to cssModules

This article's style plan learns the basic principles of cssModules to resolve style conflicts, and improves on this basis to achieve the purpose of reducing the size of style files. Therefore, before formally understanding this solution, this article first uses the example code using the cssModules solution on Taro's official website as an example to briefly understand its grammatical requirements and principles.

2.1 Grammar requirements

After cssModules is enabled in the configuration, according to the syntax requirements, there are two files, index.module.scss and index.js, in the Taro project. The file codes are as follows. By default, cssModules enables partial custom mode conversion. Only style files containing .module. in their file names will be converted by cssModules. In the following index.module.scss style file, we use parent-child selectors and class selectors normally. However, in the index.js file, the className assignment is no longer a string, but a certain Key of the Object exported by the SCSS file. This Key is the name of the class selector in the SCSS file.

import React, { Component } from 'react'
import { View, Text } from '@tarojs/components'
import styles from './index.module.scss'


export default class Index extends Component {
  render() {
    return (
      <View className={styles.test}>
        <Text className={styles.txt}>Hello world!</Text>
      </View>
    )
  }
}
.test {
  color: red;
  .txt {
    font-size: 36px;
  }
}

2.2 Principle

After the Taro project turns on the cssModules configuration, the css-loader that implements the cssModules specification will be used to process SCSS and other style files when compiling and packaging. It will first process the class selector in the original SCSS file, hash the class name to get a new class name such as index-module__test___Bm2J6, generate new style code and output it to the final index.wxss, while saving the original class name and hash The mapping relationship of the new class name after processing. Afterwards, it will compile the original SCSS file index.module.scss into a mapping object that exports the original class name and the hashed new class name. When the JS file is running, it can obtain the hashed new class name through the mapping object, ensuring that the file class name will not conflict with similar names of other style files, thereby solving the style conflict problem. The following is a compiled code example. styles.test will become index-module__test___Bm2J6 at runtime.

// index.module.scss
export default ({"test":"index-module__test___Bm2J6","txt":"index-module__txt___nIysk"});
// index.wxss
.index-module__test___Bm2J6 {
  color: red;
}
.index-module__test___Bm2J6 .index-module__txt___nIysk {
  font-size: 36rpx;
}

3. Introduction to the principle of the scheme

3.1 Basic principles

3.1.1 Analysis of current style file size

Before formally introducing how this article's plan is to reduce the size of style files, this article uses the following two regular rules to match the two core components ClassName and PropertyValue of all style files in the packaged product, and perform statistical analysis of Size.

Note: In this article, as in the .txt .tit {color: #red;} CssRule code, ClassName refers to txt and tit, and PropertyValue refers to color:#red;.

const classNamePattern = /(?<=\.)[A-Za-z0-9\-_]+(?=\s|{|:)/g // 匹配 ClassName 如 .txt {color: #red;}中的txt
const cssPropertyPattern = /(?<=\{)[^}]+(?=})/g // 匹配PropertyValue, 如 .txt {color: #red;}中  中括号之间的所有内容 color: #red;

The figure below shows the composition size analysis of the style files of the entire compiled and packaged applet project. From this figure, we can find that among all the style files packaged and compiled by our project, ClassName occupies about one-fifth of the space, while PropertyValue occupies seven-tenths of the space. The rest of the space may be as follows Spaces and pseudo-classes exist and will not be considered in this article.

c1f4589a7941e3b0359335b1731185f1.png

3.1.2 Solution

From the previous section, we can know that there are two main parts of the core content in a style file, one is ClassName, and the other is PropertyValue. The style scheme of this article processes these two parts separately to achieve the goal of saving Size.

1) Reduce the length of ClassName

The core is to replace the original ClassName with a shorter and unique ClassName. While resolving style conflicts, it also saves Size by reducing the length of ClassName. When we use cssModules, usually like the sample code when introducing cssModules in Chapter 2, the ClassName is hashed to ensure uniqueness, but the length of the ClassName after hashing becomes longer, which is not suitable for us. Target to reduce style code size.

This solution starts with the shortest character and gradually increases it to generate a unique ClassName for the entire project, thereby ensuring uniqueness while keeping the length of the ClassName as short as possible. For example, the first parsed ClassName is replaced with -a, the second parsed ClassName is replaced with -b, the 52nd parsed ClassName is replaced with -Z, and the 53rd parsed ClassName is replaced with -aa. The - in front of ClassName is used to prevent newly generated class names from conflicting with unconverted class names. In addition, the newly generated ClassName needs to comply with the rules. This plug-in algorithm first takes one character in prevString, and all subsequent characters can take any character in charString.

const prevString = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' // 52个字符数
const charString = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_' // 64个字符数

Some people may worry that as more and more ClassNames are declared in the entire project, the incrementally generated ClassNames will become longer and longer, which will lead to the total ClassName being too long. Through the above algorithm, counting the - added at the beginning, when the three-character class name is used up, 52 * 64 = 3328 ClassNames can be replaced, and 52 * 64 * 64 = 212992 are needed to reach the four-character length. className. The newly generated ClassName does not exceed four characters, which can meet the needs of most projects. Before using this style scheme, you can search the magnitude of the ClassName in your own project.

2) Reduce PropertyValue

Through the above analysis, we can find that the part that actually occupies the largest size of the style file is the PropertyValue. Therefore, reducing the PropertyValue is the core method for this style plan to save a lot of Size. In fact, many of the style attribute values ​​we use during development are repeated, such as display:flex, the most commonly used layout attribute during development. Each time this attribute is used, a new copy needs to be written, and for the sake of compatibility, the Autoprefixer plug-in is enabled, display:flex will become display:-webkit-flex;display:-ms-flexbox;display:flex;, which makes The size of the style file becomes larger. This plug-in reduces the PropertyValue by reusing the PropertyValue as much as possible.

This plug-in will split the PropertyValue of the CssRule in the style file that only uses the class selector, and each split will generate a new PropertyValue ClassName. As shown in the following sample code, only the class selector CssRule txt is split into two PropertyValue ClassNames of _a and _b. If the class-only selector CssRule is used for subsequent splitting, _a or _b will be reused directly if there is the same PropertyValue.

// 原代码
.txt { display: flex;flex: 1; }
// 处理后的代码
._a {display: -webkit-flex; display: -ms-flexbox;display: flex;}
._b {-webkit-flex: 1;-ms-flex: 1;flex: 1;}

Corresponding mapping processing is also required in js files that use the cssModules style writing method. The babel plug-in is used to perform conversion processing during compilation to determine the reference relationship of the css file and replace it. The sample code is as follows.

// 原代码
import styles from './index.module.scss'
Index = () => {
  return <View className={styles.txt} />
}
// 处理后的代码
import './index.module.scss'
Index = () => {
  return <View className="_a _b" />
}

This style scheme splits the PropertyValue of the CssRule that only uses the class selector into a new PropertyValue ClassName. The PropertyValue ClassName can be directly reused wherever subsequent splitting is performed, thereby greatly reducing the size occupied by repeated and redundant PropertyValues. .

3) Plug-in processing process

The above two sections have introduced two core size reduction schemes. This section gives a more comprehensive example to introduce how this plug-in uses the above two schemes to process and transform style files and JS files during compilation. There are mainly the following two steps.

The first step is to split the PropertyValue for the CssRule that only uses the class selector. In the following example code, .box{display:flex} splits out ._a {display: -webkit-flex;display: -ms-flexbox;display: flex;} , and when .item1` `.item2 is subsequently split, ._a is directly reused, reducing the redundancy of PropertyValue duplication.

The second step is to directly replace the CssRule that does not only use the class selector with a globally unique and shorter ClassName. In the following example code, .box .item2{color: red;}, the ClassName in the original selector is directly replaced with the shorter .-a .-b{ color: red;}, and the mapping relationship styles = is added. {box: "_a -a", item1: "_a _b _c", item2: "_a _b _d -b"} and replaced at compile time.

// 原代码
import React from 'react'
import styles from './index.module.scss'


export default Index = () => {


  return <View className={styles.box}>
    <View className={styles.item1}>item1</View>
    <View className={styles.item2}>item2</View>
  </View>
}
// 处理后的代码
import React from 'react'
import './index.module.scss'
// styles = {box: "_a -a", item1: "_a _b _c", item2: "_a _b _d -b"}
export default Index = () => {


  return <View className="_a -a">
    <View className="_a _b _c">item1</View>
    <View className="_a _b _d -b">item2</View>
  </View>
}
// 原index.module.scss代码
.box {
    display: flex;
}
.item1{
    display: flex;
    font-size: 32px;
    color: red;
}
.item2{
    display: flex;
    font-size: 32px;
    color: grey;
}
.box .item2{
    color: red;
}
// 处理后index.module.scss代码
._a {display: -webkit-flex;display: -ms-flexbox;display: flex;}
._b {font-size: 32px;}
._c {color: red;}
._d {color: grey;}
.-a .-b{
    color: red;
}

3.2 Issues that need attention

3.2.1 Properties of the styles object do not support runtime

In the cssModules solution, the style file object introduced in the JS file supports runtime calculated properties, as shown in the following example. This is because the packaged JS file contains a copy of the object data of the mapping relationship between the original ClassName and the hashed new ClassName. Therefore, styles can also map attributes at runtime, but this processing method will cause the size of the js file to increase.

import styles from './index.module.scss'
const Index = () => {
  return <View className={styles['t' + 'xt']} />
}

In order to ensure that the project size is as small as possible, this solution does not use cssModules. This solution will directly replace the original CLassName and the new ClassName after splitting the PropertyValue during compilation, such as directly replacing className={styles.txt} with className="_a _b".

Therefore, the styles object in this solution does not support the txt attribute calculated at runtime in the above sample code. If you need to dynamically adjust the style, there are two solutions. One is to use inline styles directly. The second is to write a new ClassName instead of concatenating it, such as className={value ? styles.txt1 : styles.txt2}}.

3.2.2 Only class selectors do not rely on order to determine priority.

In the above, it was mentioned that only the class selector CssRule will be used to reuse the existing PropertyValue ClassName as much as possible. However, this kind of reuse is flawed. It will cause the order of ClassName to not meet expectations. As shown in the following code, generally speaking, we think that the title color should be gray.

// 原代码
import styles from './index.module.scss'
const Index = () => {
  return <View className={styles.tit1 + ' ' + styles.tit2}>标题</View>
}
// 处理后的代码
import styles from './index.module.scss'
const Index = () => {
  return <View className={'_a' + ' ' + '_b'}>标题</View>
}
// 原代码
.other { color: green; color:red; }
.tit1 { color: red; }
.tit2 { color: green; }
// 处理后的代码
._a {color:green;}
._b {color:red;}

However, after this plug-in reuses the PropertyValue, ._b{color:red;} appears behind ._a{color:green;}. At this time, the color of the title becomes red, which may not be suitable for development. or expectations.

Therefore, it should be noted that when writing ClassName with only class selector CssRule, you cannot rely on the order of class selectors to determine the priority. You can use sibling selectors to raise the priority higher, so that it is not affected by the order. The following code example . This will ensure that the title color must be green.

// 兄弟选择器来提高优先级
.other { color: green; color:red; }
.tit1 { color: red; }
.tit1.tit2 { color: green; }

4. User Guide

4.1 Use

4.1.1 Install plug-ins

This style scheme is integrated in the Taro plug-in taro-plugin-split-class. Install this plug-in. See the source code in the warehouse taro-plugin-split-class .

npm install -D taro-plugin-split-class

4.1.2 Turn off the cssModules function

In the Taro configuration file, make mini.posetcss.cssModules.enable = false and ensure that the cssModules function is turned off, as shown in the following code.

// config/index.js
{
    mini: {
        postcss: {
            cssModules: {
                enable: false
           }
        }
    }
}

4.1.3 Configure this plug-in

In the Taro configuration file, add this plug-in taro-plugin-split-class to the plugins configuration. This plug-in supports configuring the class name conversion whitelist (the implementation function is similar to: global, see 2.4) classNameWhite. For example, the commonly used iconfont does not need to be converted.

plugins: [
    ['taro-plugin-split-class', {
      classNameWhite: ["iconfont", /^ifont-/]
    }]
]

4.2 Grammar requirements

a. The name of the style file must end with .module.xxx, such as index.module.scss, so that the style file can be converted and processed by this plug-in.

b. In the JS file, introduce the style file as an object and use the class name as the key of the object. As shown in the following code, use className={styles.box} instead of className="box", where box is the class name defined in the style file.

// 如下
import styles from './index.module.scss'
<View className={styles.box}></View>
// 而不是
import './index.module.scss'
<View className="box"></View>

c. This solution supports all selectors including parent-child selectors, pseudo-class selectors, sibling selectors, etc. But please use class-only selectors to locate elements as much as possible. This will make it easier for the plug-in to reuse the PropertyValue as much as possible to better reduce the Size. This solution solves the problem of class name conflicts, so developers do not need to worry about class name conflicts caused by simple class naming.

// 如下仅类选择器的CssRule
.box {
    display: flex;
    flex-direction: column;
    align-items: center;
}
.tit {
    display: flex;
    font-size: 40px;
    color: red;
}
// 而不是父子选择器
.box {
    display: flex;
    flex-direction: column;
    align-items: center;
    .tit {
        display: flex;
        font-size: 40px;
        color: red;
    }
}

d. The special class name remains unchanged

Sometimes we want some special ClassName to remain unchanged. In the JS file, it is enough not to take the class name from styles, as shown in the extra code below.

import styles from './index.module.scss'
<View className={styles.tit + ' extra'}>标题</View>

But in the style file, all ClassNames will be split or compressed by default. In the following code example, extra is processed as -a.

// 原类名
.extra.tit {
color: blue;
}
// 新类名
.-a.-b {
    color: blue;
}

Therefore, a special identifier is needed to let the plug-in know that it does not need to process the ClasName. This solution provides a :global solution similar to cssModules. There are two ways to use it. One is: global(.extra). The wrapped class name will not be replaced.

// 编译前
:global(.extra).tit {
  color: blue;
}
// 编译后
.extra.-a {
    color: blue;
}

Second, if it starts with: global, all subsequent class names will not be replaced.

// 编译前
:global .extra1 .extra2 { color: red;}
// 编译后
.extra1 .extra2 { color: red;}

4.3 Packaging effect display

4.3.1 Development environment

After using this plug-in, the original class names will be replaced or split into shorter and more new class names. The readability of the new class name processed in this way is very poor, and developers cannot easily locate the original class name code. Therefore, in the development environment, [folder_file name_original class name] will be added before shorter and more new class names. Information related to the original class name is retained to facilitate developers to find the original class name. As shown in the code below, the original class name is box. After splitting and shortening by the plug-in, the new class name is _a _g _h -c. Index_indes-module_box is added in front of the new class name. The final displayed complete class name is index_index. -module_box _a _g _h -c .

6a12f3514d49cf45f3211934b9e4e95b.png

4.3.2 Production environment

In a production environment, there is no need to consider the readability of the new class name, so the class name will be completely replaced with the new class name. As shown in the code below, box is directly replaced with _a _g _h -c.

ac11e09dead626ec6983bf524a18cb27.png

5. Program analysis

5.1 Practical results

5.1.1 Comparison before and after page transformation

After using this style plan to transform a page, the size comparison before and after the transformation is as follows. It can be found that the style file has been reduced by 44KB, the size has been reduced by nearly 70%, and the JS file has increased by 2kb.


JS file  style file  sum
 before use 54kb 63kb 117kb
 After use 56kb 19kb 75kb

The file size after compilation before use is as shown below:

9c354aaddecea049e7a16756ed9f98d9.png

The compiled file size after use is as shown below:

c69469fa2484864a8d3f75671b14c10e.png

5.1.2 Horizontal comparison of reconstructed pages

Recently, our project has reconstructed two large order details pages. This section takes the reconstructed code of these two pages as an example to analyze the size before and after compilation and packaging and make a horizontal comparison.

Sort out the following table:


Number of style encoding characters Actual Size after packaging
Order details page 1 that does not use this style plan 3620 86kb
Order details page 2 using this style plan 6615
73kb

The code organization structures of the two order details pages are similar, so they will be compared horizontally. The number of style encoding characters for order details page 1 that does not adopt this style scheme is 3620, and the actual size after packaging is 86kb. If the order details page 2 does not use this style plan and the number of style encoding characters before packaging is 6615, the actual size after packaging is expected to be 6615 / 3620 \* 86kb = 157kb, but the order details page uses this style plan and the actual size after packaging is 73kb , compared to 157kb, the size is reduced by about 50%.

The following is the order details page 1 that does not use this style plan. The style files in this directory include 50 style files with a total of 3620 characters. The size of the final packaged style file is 86kb.

6ec8008237fe0caa746c5c0bac6e8a00.png

97c6a73aa4f589b9bd1554564ae7f6bd.png

The following is the order details page 2 that uses this style plan. The style files in this directory include 96 style files with a total of 6615 characters. The final packaged style file size is 73kb.

3079c5af711d6a17668b13ae607fe46f.png

ae59f0cf37b3880da5fbe5b2445a0eba.png

5.2 Size reduction effect analysis

The above two practical effects, compared with the original style writing scheme in the project, after using this scheme, the size is mainly saved in the following three aspects.

a. This solution solves the problem of style conflicts. When writing style code, you can no longer use parent-child selectors to isolate style scopes, reducing the redundancy of ancestor selectors. As shown below in the style code using the sass preprocessor, we can find that in the final compiled code, .box .item is redundant three times, and if we continue to add a leaf node .item under .box .item *, .box .item will be redundant once. Therefore, using parent-child selectors in the project to isolate scopes will lead to a large number of redundant ancestor selectors.

// 编译前代码
.box {
  .item {
  .item1 {}
  .item2 {}
  .item3 {}
  .item4 {}
  }
}
// 编译后代码
.box .item .item1 {}
.box .item .item2 {}
.box .item .item3 {}
.box .item .item4 {}

b. Directly shorten the original ClassName to a shorter ClassName, directly reducing the number of characters. This method is more direct, but the optimization effect is limited.

c. This solution splits the CssRule with only the class selector in the style file as much as possible, generates and reuses the PropertyValue ClassName, and reduces the duplication and redundancy of the PropertyValue as much as possible. Although the ClassName in the JS file is replaced with a shorter but more PropertyValue ClassName, there is a certain size increase. For example, in Practice Effect 1, the JS file has grown by 2KB after practice. But compared to the reduction effect on the style file Size, it is negligible.

5.3 Size growth analysis

As the number of style files increases, the size of the style files will grow more slowly for projects using this style plan. This solution requires that only class selectors are used as the main method, and a small number of scenarios use other selectors as a supplement to write style code. As there are more and more style codes in the project, more and more reusable PropertyValue CssRules will be generated by splitting the class selector CssRule through processing by this plug-in. At this time, when a new class-only selector CssRule is used to use a PropertyValue as required, the probability of reuse will be higher. Each reuse with a high probability will save a part of the size, causing the size growth rate of the style file generated after final compilation and packaging to gradually slow down.

6. Summary

For the Taro project React framework applet, this article introduces a new style solution, which is integrated into a Taro plug-in, which can alleviate the redundancy of style code with less change to the existing development experience. question.

This style scheme learns from the syntax rules and principles of the cssModules style scheme to solve the problem of style conflicts. On this basis, it achieves size reduction from two aspects: reducing the length of ClassName and reducing the PropertyValue. The final effect of slimming down the style file is It can reach 50%-70%. This will help alleviate the limitations of the official package size and facilitate the rapid development of business.

7. vscode plug-in recommendation

The basic syntax of this solution is consistent with cssModules, so you can directly use the existing cssModules plug-in to improve the development experience.

7.1 CSS-Modules-transform plug-in

This plug-in supports the quick conversion of the project's existing JS code into cssModules syntax, and one-click replacement of the original class name usage with the class name usage syntax required by the cost plan, such as classname="a1" => className={styles.a1}. It should be noted that one-click replacement only supports non-runtime syntax, and runtime syntax still needs to be replaced manually. It can effectively improve the conversion efficiency of existing style solutions.

7.2 CSS Modules plugin

The CSS Modules plug-in supports auto-completion and type definition to improve development experience.

8. Article reference

[Recommended reading]

ecde59f3bee673e466e727894fa1b892.jpeg

 “Ctrip Technology” public account

  Share, communicate, grow

Guess you like

Origin blog.csdn.net/ctrip_tech/article/details/131587595