前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >干货 | 耗时缩短2/3,Taro编译打包优化实践

干货 | 耗时缩短2/3,Taro编译打包优化实践

作者头像
携程技术
发布2021-12-01 20:22:15
2.8K2
发布2021-12-01 20:22:15
举报
文章被收录于专栏:携程技术携程技术

一、背景

随着项目越来越大,编译的耗时也在默默地不断增加。无论是开发阶段还是生产集成,编译耗时都成为了一个不容小觑的痛点。

我们的项目由微信原生迁移至Taro,先后经历了约5年的持续开发迭代,项目编译后代码接近12M。在日常开发阶段执行构建命令,只是编译打包开发相关的部分文件时,耗时近1分钟。在生产环境下执行构建命令,编译打包项目中所有文件,长达10分钟。此外,随着基建部分、单个复杂页面功能越来越多,代码量也越来越大,会导致主包或者一些分包的大小超过2M,这将使得微信开发者工具的二维码预览功能无法使用,开发体验非常糟糕。

针对上述问题,我们尝试优化Taro编译打包工作。本文分为以下三个部分。

1)了解Taro内置的Webpack配置,使用webpack-chain提供的方法链式修改配置。

2)编写Taro插件,将Taro编译打包耗时缩短至三分之一。

3)编写Taro插件,解决分包过大无法进行二维码预览的问题。

二、 Taro内置的webpack配置

我们知道Taro编译打包的工作是由webpack来完成的,既然想要优化打包速度,首先要知道Taro是如何调用webpack进行打包的,同时也要了解其内置的webpack配置是怎样的。

通过阅读Taro源码后可以知道,Taro是在@tarojs/mini-runner/dis/index.js文件中,调用了webpack进行打包。我们可以着重关注该文件中的build函数,代码如下。该函数接受两个参数,appPath和config,appPath是当前项目的目录,参数config就是我们编写的Taro配置。在调用webpack前,Taro会处理webpackConfig,包括将Taro内置的webpack配置进去,以及将用户在Taro配置文件中的webpackChain配置进去。

代码语言:javascript
复制
export default async function build (appPath: string, config: IBuildConfig): Promise<webpack.Stats> {
  const mode = config.mode

  /** process config.sass options */
  const newConfig = await makeConfig(config)

  /** initialized chain */
  const webpackChain = buildConf(appPath, mode, newConfig)

  /** customized chain */
  await customizeChain(webpackChain, newConfig.modifyWebpackChain, newConfig.webpackChain)

  if (typeof newConfig.onWebpackChainReady === 'function') {
    newConfig.onWebpackChainReady(webpackChain)
  }

  /** webpack config */
  const webpackConfig: webpack.Configuration = webpackChain.toConfig()

  return new Promise<webpack.Stats>((resolve, reject) => {
    const compiler = webpack(webpackConfig) //调用webpack
    const onBuildFinish = newConfig.onBuildFinish
    let prerender: Prerender

    const onFinish = function (error, stats: webpack.Stats | null) {
      ...
    }

    const callback = async (err: Error, stats: webpack.Stats) => {
      ...
    }

    if (newConfig.isWatch) {
      bindDevLogger(compiler)
      compiler.watch({
        aggregateTimeout: 300,
        poll: ?developer/article/1909226/undefined
      }, callback)
    } else {
      bindProdLogger(compiler)
      compiler.run(callback)
    }
  })
}

定位到了webpack位置,那么让我们来看看Taro最终生成的webpack配置是怎样的呢?需要注意的是在开发和生产环境下,内置的webpack配置是有差别的,比如在生产环境下,才会调用terser-webpack-plugin进行文件压缩处理。我们用的是vscode代码编辑器,在调用webpack位置前,debugger打断点,同时使用console命令输出变量webpackConfig,即最终生成的webpack配置。在vscode自带的命令行工具DEBUG CONSOLE,可以非常方便的点击展开对象属性,查看Taro生成的webpack配置。这里展示下,在development环境下,Taro内置的webpack配置,如下图。

这些都是常见的webpack配置,我们主要关注两部分的内容,一是module中配置的rules,配置各种loader来处理匹配的对应的文件,例如常见的处理scss文件和jsx文件。二是plugins中配置的TaroMiniPlugin插件,该插件是Taro内置的,主要负责了将代码编译打包成小程序代码的工作。

现在了解了Taro中的webpack配置,接下来该考虑的是如何去修改该配置,来帮助我们优化编译打包。这里Taro提供了webpack-chain机制,webpack配置本质是一个对象,创建修改比较麻烦。webpack-chain就是提供链式的 API 来创建和修改webpack 配置。具体用法可以看官方github,上面提供了大量的案例用于学习。

https://github.com/Yatoo2018/webpack-chain/tree/zh-cmn-Hans

三、速度优化 -- 耗时缩短至三分之一

我们已经了解了Taro生成的webpack配置,也掌握了修改这些配置的方法,接下来就是考虑修改webpack配置,来优化编译打包速度。我们引入了speed-measure-webpack-plugin,该插件可以统计出编译打包过程中,plugin和loader的耗时情况,可以帮助我们明确优化方向。

将speed-measure-webpack-plugin配置好后,执行构建命令,输出结果如下图。

图中数据显示在plugins中,TaroMiniPlugin耗时严重,这个是Taro内置的webpack插件,Taro的绝大多数编译打包工作都是配置在这里的进行的,例如获取配置内容、处理分包和tabbar、读取小程序配置的页面添加dependencies数组中进行后续处理、生成小程序相关文件等。次之耗时严重的就是TerserPlugin,该插件主要进行压缩文件工作。

而在loaders耗时统计中,babel-loader耗时两分半,sass-loader耗时两分钟,这两者耗时最为严重。这两者也是导致TaroMiniPlugin耗时如此严重的主要原因。因为该插件,会将小程序页面、组件等文件,通过webpack的compilation.addEntry添加到入口文件中,后续会执行webpack中一个完整的compliation阶段,在这个过程中会调用配置好的loader进行处理。当然也会调用babel-loader和scss-loader进行处理 js文件或者scss文件,这就严重拖慢了TaroMiniPlugin速度,导致统计出来该插件耗时严重。

因此优化这两loader,也就相当于优化了TaroMiniPlugin。这里主要使用了两种优化策略:多核和缓存。

1)多核

这里是采用了官方推荐的thread-loader,可以将非常消耗资源的 loaders 转存到worker pool。根据上述耗时统计,可以知道babel-loader是最耗时的loader,因此将thread-loader放置在babel-loader之前,这样babel-loader就会在一个单独的worker pool中运行,从而提高编译效率。

清楚了优化方法,就该考虑如何配置到webpack中。简单来说,就是利用Taro插件化机制提供的modifyWebpackChain钩子,采用webpack-chain提供的方法,链式修改webpack配置。

具体做法是,首先想办法删除Taro中内置的babel-loader,我们可以回头查看Taro内置的webpack配置,发现处理babel-loader的那条具名规则为'script',如下图,然后使用webpack-chain语法规则删除该条具名规则即可。

最后,通过webpack-chain提供的merge方法,重新配置处理js文件的babel-loader,同时在babel-loader之前引入thread-loader。这样就完成了。

代码语言:javascript
复制
ctx.modifyWebpackChain(args => {
  const chain = args.chain
  chain.module.rules.delete('script') // 删除Taro中配置的babel-loader
  chain.merge({ // 重新配置babel-loader
    module: {
      rule: {
        script: {
          test: /\.[tj]sx?$/i,
          use: {
            threadLoader: {
              loader: 'thread-loader', // 多核构建
            },
            babelLoader: {
              loader: 'babel-loader',
              options: {
                cacheDirectory: true, // 开启babel-loader缓存
              },
            },
          },
        },
      },
    }
  })
})

目前引入的thread-loader只处理babel-loader,我尝试过去用其处理css-loader,但是失败了。因为thread-loader的限制,可见issue,目前仍未解决。 Cannot read property 'outputOptions' of ?developer/article/1909226/undefined #66

2)缓存

缓存优化策略也是针对这两部分进行,一是使用cache-loader缓存用于处理scss文件的loaders,二是babel-loader,设置参数cacheDirectory为true,开启babel-loader缓存。

在使用cache-loader缓存时,额外注意的是,需要将cache-loader放置在css-loader之前,mini-css-extract-plugin之后。实践中发现,放置在mini-css-extract-plugin/loader之前,是无法有效缓存生成的文件。

具体做法类似上面,主要是查看Taro内置的webpack配置,然后使用webpack-chain语法,定位到对应的位置,最后调用before方法,插入到css-loader之前。

代码语言:javascript
复制
// 通过webpack-chain方法,将cache-loader放置在css-loader之前,mini-css-extract-plugin之后
chain.module.rule('scss').oneOf('0').use('cacheLoader').loader('cache-loader').before('1')
chain.module.rule('scss').oneOf('1').use('cacheLoader').loader('cache-loader').before('1')

注意: 缓存默认是保存在node_moduls/.cache中,如下图。因此在使用执行编译打包命令时,需要注意当前的打包环境是否能够将缓存保留下来,否则缓存配置无法带来速度优化效果。

值得一提的是,看上图我们可以发现,terser-webpack-plugin也是开启了缓存的。我们再回头看下,下图是Taro中配置的参数。我们可以发现cache和parallel都为true,分别是开启了缓存以及并行编译。

小结

我们已经将上述优化方案,写成了Taro插件,放在了npm上,大家可以很方便的使用。总的来说,本插件是利用了Taro插件化机制暴露出来的modifyWebpackChain钩子,采用webpack-chain方法,链式修改webpack配置。将多核和缓存优化策略配置到Taro的webpack中,来提升编译打包速度。本案例中,优化前3m9s,优化后56.8s,可以将编译打包耗时缩短至三分之一左右。

最后看看优化后的耗时统计,可以发现总耗时已经缩短至56.9s,TaroMiniPlugin、babel-loader还有css-loader耗时有着明显的缩短,而配置了缓存的TerserPlugin也从22.8s缩短至13.9s。优化效果还是很显著的。

使用
代码语言:javascript
复制


npm install --save-dev thread-loader cache-loader taro-plugin-compiler-optimization
代码语言:javascript
复制
// 将其配置到taro config.js中的plugins中
// 根目录/config/index.js
plugins: ['taro-plugin-compiler-optimization']

安装好了npm包后,将Taro插件写入到Taro配置中即可。

GitHub:https://github.com/CANntyield/taro-plugin-compiler-optimization Npm:https://www.npmjs.com/package/taro-plugin-compiler-optimization

该npm包,目前只适用Taro3,react、scss技术栈

四、压缩项目文件

微信开发者工具中,如果想要在真机上调试小程序,通常是需要进行二维码预览的。由于微信限制,打包出来的文件,主包、分包文件不能超过2M,否则进行二维码预览无法成功。但是随着项目越来越大,主包文件超过2M是没办法的事情,尤其是通过babel-loader处理后的文件,更是会包含了非常多的注释、过长的变量名等,导致文件过大。

比较简单能够想到的办法是,将跟目前调试目标无关的主包代码手动进行删除,留下入口用于调试。当然这样做也有一些问题,一是每次手动删除会比较麻烦,调试完之后需要自己手动恢复,每次预览都需要重启项目。二是微信限制tabbar最少 2 个、最多 5 个,这就导致存在可能单个tabbar超过2M的情况,这样更是麻烦。

也还有一种解决办法,那就是执行build构建命令,这样就可以启用terser-webpack-plugin压缩文件,将主包文件缩小至2M以下。问题也是很明显的,那就是每次都需要花费大量的时间用于构建打包工作,效率实在是太低了。而且这种情况下,不会监听文件变化,进行模块热替换工作,这种工作效率更是低到令人发指。

思路比较简单,就是在开发环境下,配置webpack,调用terser-webpack-plugin进行压缩。同时配置插件参数,压缩指定文件。

打开微信开发者工具,点开代码依赖分析,如下图。从图中可以看到,主包文件已经超过了2M。其中common.js、taro.js、vendors.js、app.js四个文件明显较大,并且每个Taro项目编译打包后必然生成这四个文件。pages文件夹也高达1.41M,该文件夹是我们配置的tabBar页面,因此该文件夹大小直接受到tabBar页面复杂度的影响。除此之外,其他文件都比较小,可以暂时不考虑进行处理。

我们的目标就是压缩这几个比较大的文件,用起来很简单,首先执行以下命令安装terser-webpack-plugin。

代码语言:javascript
复制
npm install -D terser-webpack-plugin@3.0.5

需要注意的是,terser-webpack-plugin最新版本已经是v5了,这个版本是根据webpack5进行优化的,但是不支持webpack4,因此需要自己额外指定版本,才能使用。这里我选择的是3.0.5,跟Taro中使用的terser-webpack-plugin是同一个版本。其中,传入的参数配置也是跟Taro一样,我们要做的是,将需要进行压缩的文件路径添加到test数组中即可,其中已经默认配置了common.js、taro.js、vendors.js、app.js、pages/homoe/index.js文件。

注意:文件路径是,Taro编译打包后最终生成的文件路径,不是项目中的文件路径。

同样的,我们需要在Taro配置文件plugins中引入该Taro插件,建议在config/dev.js配置文件中引入,只会在开发环境下才会使用到。

代码语言:javascript
复制
// config/dev.js
plugins: [
    path.resolve(__dirname, 'plugins/minifyMainPackage.js'),
]

最后我们来看看压缩后主包的大小,可以发现已经减少至1.42M了,相对于此前的3.45M,压缩了50%左右,可以解决大部分无法进行二维码预览打包的场景了。

使用

点击以下链接,将该Taro插件下载到项目中,修改代码中的test数组,配置想要压缩的文件路径。然后按照Taro插件文件所在路径,在Taro配置中引入即可。

GitHub:https://github.com/CANntyield/taro-plugin-repository/blob/main/minifyMainPackage.js

五、总结

本文主要是解决在使用Taro开发大型项目时经常遇到的两个编译打包相关的问题,在解决问题的过程中,深入源码去了解Taro的编译打包机制和webpack相关机制,检索常用优化相关的解决方案,最终完成了这两个Taro插件。一是用于优化Taro编译打包速度,二是提供了一种解决方案,解决分包过大导致无法使用微信开发者工具进行二维码预览的问题。在文中我都给出了使用方法,大家可以尝试下,如有问题,欢迎指出探讨。

本文参与?腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-25,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 携程技术中心 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体同步曝光计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二、 Taro内置的webpack配置
  • 三、速度优化 -- 耗时缩短至三分之一
    • 小结
      • 使用
      • 四、压缩项目文件
        • 使用
        • 五、总结
        相关产品与服务
        云开发 CloudBase
        云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
        http://www.vxiaotou.com