In this paper, some technical optimizations in the development of small program are introduced. This project adopts MPVUE framework, but the optimization technical points proposed in this paper are still of reference significance for small program projects using other frameworks.

background

Fang Di small program is a new house information services small program. As the most important C-end product of Sohu focus, Pfangdi carries a lot of business functions. Excluding the embedded H5 page, there are more than 150 small program pages. As the functionality was iterated, the project became bloated and faced many technical problems. On the one hand, development and construction are increasingly time-consuming, affecting development efficiency; On the other hand, as the packaging volume becomes larger, the startup time becomes longer, affecting the user experience.

In view of the above problems, we have carried out a series of optimizations: through partial compilation, improve the compilation efficiency during development; Optimize packaging speed through Webpack Loader; Reduce package volume…… through subcontracting dependency optimization and static resource optimization

Optimization point 1: subcontracting depends on optimization

Subcontracting depends on optimization, which is not a proper term, but I define it according to my personal understanding. It basically means reasonable subcontracting of pages and packaging of common modules into reasonable locations. First introduce the subcontracting mechanism of small program.

Small program subcontracting mechanism

Wechat mini program provides subcontracting mechanism, developers can subcontract the project page according to needs. After subcontracting is adopted, wechat will load resources according to the user’s visit page on demand, so as to optimize the download time for the first startup of the small program. Please read the official document for details. Here are some important rules:

  • Users from the main package page to open small program, just download the main package resources;
  • When the user opens the small program from the subcontract A page, the user needs to download the main package and subcontract A resources.
  • Subcontractor A can only refer to subcontractor A and resources in the main package, but cannot refer to resources in subcontractor B.
  • The main package cannot reference resources within the subcontract;
  • TabBar pages must be in the main package;
  • Main package: 2MB for single subcontract, 20MB for whole package;

What’s the best way to pack it

So, according to the above rules, what makes more sense to pack?

  • The main package should be as compact as possible, containing only TabBar pages and high-frequency access pages;
  • If module1 is referenced by both main package and subpackage A, then Module1 should be plugged into the main package (no doubt).
  • If Module1 is referenced only by pages within Subcontract A, module1 should be packaged inside subcontract A (optimization emphasis);
    • Most developers may think that only subcontracted pages can be placed in a subcontracted directory, but any resource can be placed in a subcontracted directory and can be referenced by subcontracted pages.
    • Some people think that public dependencies can only be placed in the main package, but in fact, public dependencies within a subcontract can also be placed in the subcontract’s own directory.
    • The above two points can be tested and verified by yourself, which is the key point that we can optimize;
  • If module1 is referenced by subcontractors A and B at the same time and is not referenced by the main package, you can send Module1 to the main package or subpackages (depending on the situation).

Existing problems before optimization

Through webpack-bundle-Analyzer analysis of our project, we can find that there are two tim-wx.js and three webim_wx.js in the packaging result, and there are multiple public modules in vender.js that only reference subpackages, which is obviously unreasonable.

Mpvue uses webpack version 3.11.0 and extracts the common code using the CommonsChunkPlugin plugin (WebPack4 has replaced CommonsChunkPlugin with SplitChunksPlugin). The configuration items are as follows:

// build/webpack.prod.config.js
new webpack.optimize.CommonsChunkPlugin({
  name: 'common/vendor'.minChunks: function (module, count) {
    // any required modules inside node_modules are extracted to vendor
    return (
    	module.resource &&
    	/\.js$/.test(module.resource) &&
    	module.resource.indexOf('node_modules') > =0
    ) || count > 2}})Copy the code

Based on the preceding configuration rules, the following problems occur:

  • If an NPM module is referenced only on a subcontracted page, the module will still be included in the main package.
  • If moduleA is referenced more than twice in a subpackage, moduleA will still be included in the main package.
  • If moduleA is referenced twice in a subcontract, moduleA will be entered into various pages in the subcontract, so that modules shared in the subcontract are not removed, increasing the volume of the code package.

How to optimize

First of all, we should make clear the objective of optimization: package the public modules internally dependent on subcontracting to the subcontracting directory, and reduce the volume of the main package common/ Vendor.js. Let’s take a look at the workflow of CommonsChunkPlugin before tuning:

Webpack takes main.js and main.js of each page as its packaging entry, loads resources through various loaders, and extracts the common module to common/ Vendor.js under the main package through CommonsChunkPlugin. Our optimization is mainly to modify the above process. Firstly, the CommonsChunkPlugin is used to extract each subcontracted public module to each subcontracted directory, and then the global public dependency is extracted. The process is roughly as follows:

The specific code is:

// build/utils.js
exports.getSubPackageCommonChunkPlugins = function () {
  const appJson = require('.. /src/app.json')
  const subPackages = {}
  if (appJson.subPackages) {
    appJson.subPackages.forEach(package= > {
      subPackages[package.root] = []
      package.pages.forEach(subPage= > subPackages[package.root].push(path.join(package.root, subPage)))
    })
  }
  return Object.keys(subPackages).map(sub= >
    new webpack.optimize.CommonsChunkPlugin({
      name: path.join(sub, 'common'),
      chunks: subPackages[sub],
      minChunks: 2,}}))// build/webpack.prod.config.js
var utils = require('./utils')
var subPackageCommonChunkPlugins = utils.getSubPackageCommonChunkPlugins()

// webpack plugins
plugins: [
  ...subPackageCommonChunkPlugins,
  new webpack.optimize.CommonsChunkPlugin({
    name: 'common/vendor'.minChunks: function (module, count) {
      // You can customize configurations for different modules for reference only
      return (
        module.resource &&
        /\.js$/.test(module.resource) &&
        module.resource.indexOf('node_modules') > =0
        && count > 1
      ) || count > 2}})]Copy the code

The optimization effect

Through the subcontracting dependency optimization, we extracted the common modules in the subcontracting to the subcontracting, which effectively reduced the volume of the main package and each subcontracting code.

Optimal point Volume before optimization (KB) Optimized volume (KB) The effect
The main package The whole package The main package The whole package The main package The whole package
Subcontracting dependency optimization 1940 7144 1809 6523 – 131. – 621.

Optimization point 2: Webpack Loader optimization

Optimal point

  • Enable babel-loader cache;
  • Check whether each loader is necessary (for example, eslint-loader is required in production);
  • Optimized the verification rules (include and exclude) for loader files.
    • Do not process unnecessary files, such as third-party packages;
    • Narrow the file query scope, for example, specify processing SRC directory;
    • Delete unnecessary verification rules, such as the verification of font files and audio and video files.

The optimization effect

Optimal point Time before optimization Time after optimization The effect
1 Eslint-loader does not handle third-party dependencies 122 116 – 6
2 Delete the eslint-Loader in the production environment 116 95 – 21
3 Delete url-loader verification of font files, audio and video files 95 94 – 1
4 Babel-loader does not handle third-party dependencies 94 84 – 10
5 Babel-loader enables local cache 84 80 4 –

Note: The experimental machine is MacBook Pro 2020 (1.4ghz quad-core I5/16 GB), and the time unit is second.

Optimization point 3: static resource optimization

Existing problems

  • Put more static pictures locally in the early stage of the project;
  • When images are converted to base64 strings, they tend to grow in size (about 30%);
  • If the same local image is referenced more than once in CSS, there will be multiple copies of the base64 string after compilation and packaging.
  • Junk code: there are base64 string constants in the project JS file instead of images;

To optimize the

  • Static resources such as pictures and fonts should be uploaded to CDN as far as possible to avoid the involvement of static resources in packaging and compilation, so as to improve compilation efficiency and reduce code volume.
  • Delete base64 string constants;

The optimization effect

Optimal point Volume before optimization (KB) Optimized volume (KB) The effect
The main package The whole package The main package The whole package The main package The whole package
1 Static resource optimization 1809 6523 1607 6175 – 202. – 348.

Optimization point 4: Change the WebPack entry

Existing problems

  • The default entry of mpvue is main.js and /pages/**/main.js. Webpack packs all pages in the SRC /pages/ directory.
  • When you want to take a page offline, you must remove the page’s response code, otherwise the page will still be included in the final package even if it is not configured in app.json.

To optimize the

Modify Entry to the page configured in app.json. This is done by traversing pages in app.json and pages in subPackages as the entry to the package.

// build/webpack.base.conf.js
const rootSrc = path.join(__dirname, '.. '.'./src')
const appJson = require('.. /src/app.json')
const pages = JSON.parse(JSON.stringify(appJson.pages))
if (appJson.subPackages) {
    appJson.subPackages.forEach(subPackage= > {
        subPackage.pages.forEach(page= > {
            pages.push(subPackage.root + page)
        })
    })
}
const pagesEntry = {}
pages.forEach(page= > {
    pagesEntry[page] = `${rootSrc}/${page}.js`
})
const appEntry = { app: path.join(__dirname, '.. '.'./src/main.js')}const entry = Object.assign({}, appEntry, pagesEntry)

module.exports = {
  entry,
  output: {
      path: config.build.assetsRoot,
      jsonpFunction: 'webpackJsonpMpvue'.filename: '[name].js'.publicPath: process.env.NODE_ENV === 'production'
          ? config.build.assetsPublicPath
          : config.dev.assetsPublicPath
  }
  ......
}
Copy the code

What is the advantage

  • More in line with the small program logic;
  • When a page is offline, you can only modify the app.json configuration file without deleting the code, which is convenient and fast.

Optimization point 5: Partial compilation

Existing problems

As project pages continue to increase, project compilation becomes slower and slower. In the development stage, the waiting time for executing the NPM run dev command becomes longer and longer, which becomes the biggest pain point in development and seriously affects the development efficiency and experience.

Part of the compilation

Partial compilation is the most effective and direct way to quickly improve compilation efficiency. Partial compilation means compiling only the pages needed for development, not all of the project’s pages. Our whole project package has about 150 pages, while each development may involve only a few pages. Partial compilation can greatly improve compilation efficiency and perfectly solve the compilation efficiency problem in the development stage.

Partial compilation is also simpler:

  1. Create a new copy of the app.json file and rename it app.dev. Json, simply configure the pages involved in the development in app.dev.
  2. Add environment variables with cross-envonly, e.g."dev:only": "rimraf ./dist && cross-env only=true node build/dev-server.js ", when only is true, change the webpack entry to the page configured in app.dev.json as the packaging entry;
  3. Copy the contents of app.dev. Json to app.json in dist via webpack-dev-middleware-hard-disk.

With these changes, the NPM run dev:only command can be used to compile only part of the page, thus improving the compilation speed.

// build/webpack.base.conf.js
const isOnly = process.env.only
const rootSrc = path.join(__dirname, '.. '.'./src')
const appJson = isOnly ? require('.. /src/app.dev.json') : require('.. /src/app.json')
const pages = JSON.parse(JSON.stringify(appJson.pages))
if (appJson.subPackages) {
    appJson.subPackages.forEach(subPackage= > {
        subPackage.pages.forEach(page= > {
            pages.push(subPackage.root + page)
        })
    })
}
const pagesEntry = {}
pages.forEach(page= > {
    pagesEntry[page] = `${rootSrc}/${page}.js`
})
const appEntry = { app: path.join(__dirname, '.. '.'./src/main.js')}const entry = Object.assign({}, appEntry, pagesEntry)


// build/dev-server.js
var webpack = require('webpack')
var webpackConfig = require('./webpack.dev.conf')
var express = require('express')
var app = express()

var compiler = webpack(webpackConfig, function() {
    if (process.env.only) {
        const appJson = require(path.resolve(__dirname, '.. /src//app.dev.json'))
        fs.writeFileSync(path.resolve(__dirname, '.. /dist/app.json'), JSON.stringify(appJson))
    }
})

module.exports = new Promise((resolve, reject) = > {
    var server = app.listen('8080'.'localhost')
    // for applet file saving mechanism
    require('webpack-dev-middleware-hard-disk')(compiler, {
        publicPath: webpackConfig.output.publicPath,
        quiet: true
    })
    resolve({
        ready: Promise.resolve(),
        close: () = > {
            server.close()
        }
    })
})
Copy the code

Other optimization points

  • Check whether third-party dependencies can be streamlined, such as echarts with custom versions that remove unwanted drawing functions;
  • Check whether there are redundant files after packaging: the small program will package all files in the dist directory and upload them, so the redundant files should be deleted.
  • Check the statis directory and delete the unreferenced files;

conclusion

Through subcontracting dependency optimization and static resource optimization, the packing volume is reduced by about 13%. Through webpack Loader optimization, the packaging speed is increased by about 40%; Through the modification of Webpack entry, partial compilation, effectively improve the development efficiency.

Optimal point Time before optimization Time after optimization The effect
1 Eslint-loader does not handle third-party dependencies 122 116 – 6
2 Delete the eslint-Loader in the production environment 116 95 – 21
3 Delete url-loader verification of font files, audio and video files 95 94 – 1
4 Babel-loader does not handle third-party dependencies 94 84 – 10
5 Babel-loader enables local cache 84 80 4 –
6 Subcontracting dependency optimization, IM SDK optimization 80 74 – 6
A combined Packaging time reduced by 48s, speed increased by about 40%
Optimal point Volume before optimization (KB) Optimized volume (KB) The effect
The main package The whole package The main package The whole package The main package The whole package
1 Subcontracting dependency optimization, IM SDK optimization 1940 7144 1809 6523 – 131. – 621.
2 Static resource optimization 1809 6523 1607 6175 – 202. – 348.
A combined Main package decreased by 17%333kb), whole package decreased by 13.6%(969kb)

The optimization points mentioned in this paper have certain reference significance for other small program projects. The mpvue applets framework packages all public dependencies into the main package by default, which causes the main package to be too large, and even often leads to the size of the main package exceeding the 2MB limit of the applets, which is very unreasonable. The subcontract dependency optimization proposed by us can effectively solve this problem. Partial compilation is the most direct and effective way to improve compilation efficiency, and it can be applied to any other project. And WebpacLK Loader optimization, static resource optimization is more common front-end project optimization means.

The above content only represents personal understanding, welcome to discuss and correct the deficiencies.