The solution to the excessive packaging volume of small programs

a background

Subpackage is a solution similar to web asynchronous introduction given by the applet. It puts some unnecessary pages into the subpackage, and then downloads the subpackage when jumping to the corresponding page, thus effectively reducing the volume of the main package. .
Project background:
The company's small program project uses taro to achieve one code and multiple terminals. The public library and basic library are placed in the main package, which causes the volume of the main package to exceed 2M and cannot be previewed locally. This time is to record the analysis process and solution ideas of package volume optimization.

1. At present, the subcontracting of small programs has the following restrictions:

  • The size of all subpackages of the entire applet should not exceed 20M
  • The size of a single subpackage/main package does not exceed 2M

2. What is the main package and sub-package

The applet declares the subpackage structure in the subpackages field of app.json:
the original project path:

├── app.js
├── app.json
├── app.wxss
├── packageA
│   └── pages
│       ├── cat
│       └── dog
├── packageB
│   └── pages
│       ├── apple
│       └── banana
├── pages
│   ├── index
│   └── logs
└── utils

Subcontracting:

{
    
    
  "pages":[
    "pages/index",
    "pages/logs"
  ],
  "subpackages": [
    {
    
    
      "root": "packageA",
      "pages": [
        "pages/cat",
        "pages/dog"
      ]
    }, {
    
    
      "root": "packageB",
      "name": "pack2",
      "pages": [
        "pages/apple",
        "pages/banana"
      ]
    }
  ]
}

illustrate:

field type illustrate
root String subpackage root directory
name String Subpackage alias, can be used when subpackage pre-download
pages StringArray Subpackage page path, relative to subpackage root directory
independent Boolean Whether the subcontract is an independent subcontract

The packaging principle after using subcontracting:

  • After subpackages are declared, they will be packaged according to the subpackages configuration path, and directories outside the subpackages configuration path will be packaged into the main package
  • The root directory of a subpackage cannot be a subdirectory within another subpackage
  • The tabBar page must be inside the main package

That is to say, the main package is used to store the startup page/tabBar page, as well as public resources and js scripts, while the subpackages are divided according to the configuration of the developer. When the applet is started, the main package will be downloaded by default and the pages in the main package will be launched. When the user enters a page in the subpackage, the client will download the corresponding subpackage and display it after the download is complete.

Dependency analysis of the second main package

After the project is packaged, click on Developer Tools -> Details -> Basic Information -> Local Code -> Code Dependency Analysis to see the size of the main package and each subpackage.
Please add a picture description

It can be seen that the volume of the main package has exceeded 2M, so the package volume must be optimized, otherwise it cannot be previewed and published locally.
But it doesn’t work just to see the large size of the vendors.js file. We need to know which files under vendors.js take up the most volume, so that we can optimize it better. This requires the help of some other tools, such as a webpack plug-in such as webpack-bundle-analyzer, for auxiliary analysis. It can intuitively analyze what is included in the packaged files, the size ratio, module inclusion relationship, dependencies, and whether the files are duplicated. , what is the size after compression and so on.

1.webpack-bundle-analyzer

(1) Introduction
The taro version of this project is 3.1.4, and taro uses webpack as its internal packaging system. Sometimes we use require syntax or import default syntax in business code, and webpack cannot provide us with the effect of tree-shaking. At this time, we need the webpack-bundle-analyzer plug-in, which will open a visual chart page in the browser and tell us the volume of each package referenced.

(2) configuration

// 引入依赖
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

const config = {
    
    
  ...
  mini: {
    
    
    webpackChain (chain, webpack) {
    
    
      chain.plugin('analyzer').use(BundleAnalyzerPlugin)
    }
  }
}

After compiling, we can see the specific package diagram:
Please add a picture description

Start to analyze the above picture:

  • In vendors.js, node_modules accounts for the largest proportion, and taro-ui/dist also accounts for a large proportion of node_modules. This part requires a careful analysis of the reasons.
  • At the same time, @wallet/taro-cashier accounts for a large proportion. Considering that this is a component provided by other teams in the company, the team has been notified to optimize the package size.
  • In the remaining packages, we can see that bn.js is large in size and is packaged repeatedly.

Three problem solving

About which files will be packaged into vendors.js and why taro projects have repeated packaging issues.
When developing small programs, the taro compiler relies on the SplitChunksPlugin plug-in to extract public components. By default, the main package and the public libraries that sub-packages depend on will be packaged into the root directory vendors.js file (there is an exception, when there is only one page in the subpackage that depends on the public library, it will be packaged into the subpackage and depend on the source code of the page), which directly affects the size of the main package of the applet, and it is easy to exceed 2M limit size.

As long as the files referenced by two chunks are packaged into the common of the main package, and each page of the subpackage is an independent chunk after packaging, that is, as long as there are two pages in the subpackage that refer to the same file , this file will be packaged into common.js.

1. The problem of repeated packaging of bn.js

Method: configure path alias

module.exports = {
    
    
  alias: {
    
    
    'bn.js': path.resolve(process.cwd(), 'node_modules', 'bn.js')
  },
}

Repackage and found that bn.js is only packaged once:
Please add a picture description

2. The taro-ui/dist package is too large

Only a few files in the project use taro-ui components, and this is how they are specifically referenced:

//引用单个ui组件
import {
    
     AtButton } from 'taro-ui'

//全局引入样式(css中)
@import "~taro-ui/dist/style/index.scss";

But when checking the style, I found that the style is referenced twice globally:
Please add a picture description

Delete one of them and package again:
Please add a picture description
Compared with the first app.wxss, it has reduced by more than 300k.

And when packaging, taro-ui has been fully packaged, and webpack does not tree-shake unreferenced components, that is to say, the official on-demand import is actually not possible.
After reading the relevant information and the solution to this problem on github:
taro-ui packaging problem optimization

GitHub's solution for importing taro-ui on demand

The simplest solution is adopted first, and the alias is added:

alias: {
    
    
   'taro-ui$': 'taro-ui/lib/index',
},

In this way, related components in taro-ui/lib/index can be directly loaded, and those not loaded will be optimized.
Please add a picture description

After doing so, kill taro-ui/dist directly. But in fact, it is not enough to only import js on demand, and also want to import styles on demand, because the app.wxss and common.wxss of the project are still very large, so the second solution is considered.

The second option is the option in Link 1.

cnpm i babel-plugin-import --save-dev

Configure the following in babel.config.js:

const {
    
     includes } = require("lodash");
module.exports = {
    
    
  plugins: [
    '@babel/plugin-proposal-optional-chaining',
    ["import", {
    
    
      libraryName: "taro-ui",
      customName: (name, file) => {
    
    
        const nameSection = name.split('-')
        if (nameSection.length === 4) {
    
    
          // 子组件的路径跟主组件一样
          nameSection.pop()
        }
        // 指定组件做路径映射
        const pathMap = {
    
    
          'tabs/pane': 'tabs-pane',
          'modal-action': 'modal/action',
          'modal-content': 'modal/content',
          'modal-header': 'modal/header'
        }
        const path = nameSection.slice(1).join('-')
        return `taro-ui/lib/components/${
      
      pathMap[path] || path}`
      },
      style: (name) => {
    
    
        if (includes(name, '/modal')) {
    
    
          return 'taro-ui/dist/style/components/modal.scss'
        }

        const wholePath = name.split('/')
        const compName = wholePath[wholePath.length - 1]
        const fix = {
    
    
          'tabs-pane': 'tabs',
          // 2、或者在这里写映射,这里正好跟上面的映射相反
          // 'modal/action': 'modal',
          // 'modal/header': 'modal',
          // 'modal/content': 'modal',
        }[compName]
        return `taro-ui/dist/style/components/${
      
      fix || compName}.scss`
      }
    }]
  ],
  presets: [
    ['taro', {
    
    
      framework: 'react',
      ts: true,
      hot: false // 处理h5 babel运行报错 https://github.com/NervJS/taro/releases?after=v3.1.1
    }]
  ]
}

Remove globally referenced css styles:

// @import "~taro-ui/dist/style/index.scss";   

Repackaging:
Please add a picture description
You can see that app.wxss and common.wxss are both small in size.
Note:
Since the path of taro-ui is very unstructured, the subcomponents in the component may need to be additionally referenced. In this case, code maintenance will be more troublesome. Therefore, if you want to add taro components that did not exist before to the project in the future, you must Additional configuration is required. And if the taro-ui version is upgraded, it means that we may need to continuously optimize the configuration according to the official version, which is a huge workload and of little significance.

3. Other solutions

(1) Use optimizeMainPackage, terser-webpack-plugin and miniSplitChunksPlugin plug-ins
This part of the optimization plan can be viewed here: https://taro-docs.jd.com/taro/docs/mini-split-chunks-plugin
The specific configuration is :

const TerserPlugin = require("terser-webpack-plugin");
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const MiniSplitChunksPlugin = require('mini-split-chunks-plugin')
const config = {
    
    
    mini: {
    
     
        optimizeMainPackage: {
    
    
          enable: true
        },
        webpackChain (chain, webpack) {
    
    
          process.env.TARO_ENV === 'weapp' && chain.plugin('optimizeMainPackage').use(MiniSplitChunksPlugin).before('miniPlugin')
          chain.plugin('analyzer').use(BundleAnalyzerPlugin);
          chain.merge({
    
    
            plugin: {
    
    
              terse: {
    
    
                plugin: TerserPlugin,
                args: [
                  {
    
    
                    minify: TerserPlugin.swcMinify,
                    terserOptions: {
    
    
                      compress: true,
                    },
                  }
                ]
              }
            },
          })
        }
       },
    }
}

(2) Replace some larger components
such as moment.js with day.js

The above is the whole process of small package size optimization. This time, the main package has been reduced from 2.54M to 1.76M.

Guess you like

Origin blog.csdn.net/LittleMoon_lyy/article/details/125348190