如何有效提升快应用(Webpack)编译速度

背景

在开发快应用时,少不了构建操作:npm run build(官方 IDE 集成了这些操作,本质上也是调用一样的方法)。这是因为快应用有自己的 DSL 语法,直接这样写出来,在底层是不认识的,需要把业务代码编译成底层识别的样子(感兴趣的同学,可在 build 文件夹查看编译后的代码产物)。整个过程就跟 Vue 和 React 工程的打包一样。等待的过程总是漫长,当项目越来越大的时候,难免还要花上那么点时间。大家都希望这个过程越快越好。

快应用工程是基于 hap-toolkit 编译打包的。而它的功能,部分是基于 Webpack 开发。所以下面跟大家分享的,关于提升快应用编译速度的方法,同样也适用于基于 Webpack 构建的 Web 应用。

基于插件

不过毕竟是 Webpack 上的封装,所以不是所有方法都可以用上。现在 hap-toolkit 支持一些自定义配置了,可以使用部分 loader 和 plugin。详情请查看 ToolKit 项目配置 里面有自定义 webpack plugin 的代码示例。我们这里要讲的就是使用 hard-source-webpack-plugin 插件来为编译加速。

对于做过 Webpack 性能优化的同学,可能有用到过 HardSourceWebpackPlugin 插件,用于为模块提供中间缓存步骤。它能明显提升第二次构建速度:

HardSourceWebpackPlugin is a plugin for webpack to provide an intermediate caching step for modules. In order to see results, you'll need to run webpack twice with this plugin: the first build will take the normal amount of time. The second build will be significantly faster.

安装

yarn add --dev hard-source-webpack-plugin
// OR
npm i hard-source-webpack-plugin --save-dev

使用

在项目根目录下增加 quickapp.config.js 文件,做如下代码配置:

const HardSourceWebpackPlugin = require("hard-source-webpack-plugin");
module.exports = {
  webpack: {
    plugins: [new HardSourceWebpackPlugin()],
  },
};

如上,一点简单的配置,即可轻松使用。当然,也可以通过添加参数,来“量身”定制。下面拿快应用官方 Sample 来看一下。

参数介绍

首先,完全的第一次编译,会花费较长时间。这是因为编译过程需要依赖到 babel 模块,在 webpack 里它化身为 babel-loader,编译过程需要解析成 AST,再转换成我们要的输出格式(对该部分知识感兴趣的同学可自行查阅)。这一过程极为耗时,所以作为一个有担当的依赖,它也应该有自己的缓存。
这一配置已经在 hap-toolkit 写好了。可以在项目目录下node_modules/.cache/babel-loader看到 babel 缓存。

使用 hard-source-webpack-plugin 插件,编译速度会有显著提高。
在上述存放缓存的地点,可以发现多了个 hard-source 的文件夹,里面正是存放着插件生成的缓存。再看看一些参数的介绍,我们直接在代码上以注释的形式展示:

new HardSourceWebpackPlugin({
  // 缓存存放的地址,以 webpack 的执行目录加该字段拼接而成,一般都是存放于项目目录下
  // 也可以写成绝对路径,存放到别的地方
  cacheDirectory: "node_modules/.cache/hard-source/[confighash]",
  // 缓存文件夹的名字生成方式,这里的值对于上方的 configHash
  configHash: function (webpackConfig) {
    return require("node-object-hash")({ sort: false }).hash(webpackConfig);
  },
  // 环境hash,其实就是监听依赖有没有更改,有的话也更新缓存
  // 一般 files 填一个 package-lock.json 也够了
  environmentHash: {
    root: process.cwd(),
    directories: [],
    files: ["package-lock.json", "yarn.lock"],
  },
  cachePrune: {
    // 缓存的存在时间,默认为两天
    maxAge: 2 * 24 * 60 * 60 * 1000,
    // 缓存的最大容量,默认为 50 MB
    sizeThreshold: 50 * 1024 * 1024,
  },
});

需要注意下,如果用官方 IDE 来打开项目,由于 IDE 的运行路径,与项目路径不一致,会导致报错,需要设置 environmentHash 来修正:

new HardSourceWebpackPlugin({
  environmentHash: {
    root: __dirname,
  },
});

基于文件操作

一 移除 source-map

source-map,简而言之,就是编译后的代码与源代码的一个映射。在进行代码调试时候,执行环境运行的是编译后的代码,但可以看到对应的源代码;最常见的就是浏览器中,通过打断点,定位到源代码去,这样就可以发现源代码出现的问题。

通过阅读 Webpack Devtool 文档,可以知道,source-map 构建也需要时间;且跟还原度也有关系,越精确越耗时间,尤其是值为 source-map 模式。

hap-toolkit 默认设置,release 就是设置为 “none”;debug 模式由于要考虑到还原度,选择了 “cheap-eval-source-map”。那么,如果平时无需 source-map,可以直接将其设置为 “none”,即可进一步加快速度。同时,也会带来问题,即调试之时,无法精确对应源代码。您可以有两种方式,来关闭 source-map

  1. 通过修改 quickapp.config.js 配置:
module.exports = {
  cli: {
    devtool: "none",
  },
};
  1. 通过命令行
// 此方法,在 IDE 中不适用
npm run build -- --devtool none

这个对于速度有小幅提升。这个虽然效果不如缓存提升得那么明显,但是胜在作用于每次打包,对于首次打包也是有效的。

二 减少编译代码

我们知道,编译速度跟代码量也有关系,那么减少“需要编译的文件”,当然就可以加快速度。开发时候,通常都是一个或者几个相关页面来开发。所以开发过程中可以只编译当前需要的页面。
hap-toolkit 收集页面是通过 manifest.json 的 router.page 配置,而不是通过工程的 src 下具体文件。所以可以先去掉不相干的页面配置,而不需要去除实际文件。
最简单情况可以只留下单个页面,对比起好几个甚至十几个页面的编译速度,提速效果还是相当明显的。

三 加快文件搜索

代码量对编译速度有影响,自然的,寻找需要编译的文件也要花时间的。
我们在代码中对文件的引用,写了很多相对路径,而且也习惯不写文件后缀。webpack 没有那么神可以洞察开发者内心的诉求,而是根据配置好的搜索条件,不断地循环查找。
webpack 的 resolve 配置就是干这个活的。

我们摘取其中实用的来说,先上代码,在快应用中 quickapp.config.js 配置:

const path = require("path");
module.exports = {
  webpack: {
    resolve: {
      alias: {
        "@src": path.resolve(__dirname, "src"),
      },
      modules: ["./src/components"],
    },
  },
};

1、resolve.modules:配置 Webpack 去哪些目录下寻找第三方模块,默认配置是去 node_modules 目录下寻找。
有时做了一个公用组件库,给不同的地方调用。在不同地方的引入,就有可能导致这个路径会很长,如 import '../../../components/button'。这时可以利用 modules 配置项优化,假如组件库在 ./src/components 目录下,就可以给 modules 增加'./src/components'这样一个地址,供 webpack 快速命中资源位置。

2、resolve.alias: 给路径起别名。当代码中使用了众多相对路径,不仅我们去找这个文件费劲,webpack 也是需要时间去解析。所以直接配置好绝对路径,在代码中用别名代替冗长的路径。于编译解析,于代码书写,都是利好,两开花!

3、resolve.extensions: 则是告知 webpack 获取哪些类型的文件。比如:['js', 'ux'],当我们引用文件是这样写 import util from './util',在定位到目录之后,就开始按照 util.js -> util.ux 的顺序去寻找该文件。
也就是这个字段配置作用是方便了代码的书写,但是增加 webpack 的搜索时间(故此字段仅为介绍,无需增加配置)。所以如果代码中精确到完整文件名,在大工程多引用文件数的情况下,也是能节省一笔可观的时间消耗哟。

随着工具的升级,及开放自定义程度的提高,整体编译速度也会不断提升,敬请期待。