Taro减少小程序主包体积大小的几种方式

为何要写这个文章

想必使用Taro开发过偏大型小程序应用的同窗常常会遇到这么个问题:小程序的主包体积超过了2M了,没办法预览、发布。javascript

微信小程序官网给出的包限制:java

目前小程序分包大小有如下限制:node

  • 整个小程序全部分包大小不超过 20M
  • 单个分包/主包大小不能超过 2M

为何使用Taro开发那么容易主包就超了,是框架不行仍是我使用方式错了?呃呃呃...webpack

相信使用过Taro开发的同窗都知道,咱们写的代码最终都会通过webpack里的插件SplitChunksPlugin去作chunks的拆分,Taro对这个插件的完整的默认配置能够看这里git

...
common: {
  name: config.isBuildPlugin ? 'plugin/common' : 'common',
  minChunks: 2,
  priority: 1
}
...
复制代码

只要被两个chunk引用的文件,就被打包到主包的common,而咱们的分包的每一个页面打包完后都是一个独立的chunk,那就是只要分包里有两个页面引用了同一个文件,这个文件就会被打包到common.js,这明显不是咱们想要的结果。github

了解了来龙去脉,咱们天然要解决它了,这也是我写这篇文章的缘由,但愿给有这个问题的同窗们一些本身的经验和建议。web

接下去我主要介绍下一些比较有效的减少主包体积的几种方式,会涉及到Taro2x以及3x,像一些常规的方法:如减小本地图片的使用,按需引入组件,删除无用代码等等这些这里不介绍了。小程序

减少主包体积方法:

1、使用框架自己提供的optimizeMainPackage

module.exports = {
  // ...
  mini: {
    // ...
    optimizeMainPackage: {
      enable: true
    }
  }
}
复制代码

这个特性3.2.9版本开始支持,但这个版本在window下会报错,在3.2.12才修复。可是目前还有几个问题,微信小程序

  1. 在开发环境下会报错某个文件找不到,具体缘由能够自行去看这个issue,目前好像还未修复。解决方法:微信

    • issue里有comment提出将暂存的sub-common移到项目外,避免被ide监测,这个须要去改node_modules下的@tarojs/mini-runner/dist/plugins/MiniSplitChunksPlugin.js文件。

    • 只在生产环境下才开启这个功能,在config/prod.js下加上面的配置,对于比较懒得同窗能够先这样,相信官方会很快修复这个bug。

  2. Cannot read property 'getCacheGroup' of null,能够在上面那个issue找到解决方法。

2、模仿optimizeMainPackage

有人的taro版本3.2.9版本如下,暂时没法升级到3.2.9版本,怎么办?其实方法一说到底就是一个webpack插件,那咱们把找一个支持optimizeMainPackage的taro项目,复制node_modules/@tarojs/mini-runner/dist/plugins/MiniSplitChunksPlugin.js,放到你本身的项目下做为本地插件,经过webpackChain导入插件不就好了吗。

这种方式在taro2x和taro3x配置略有不一样,下面我分别介绍:

taro3x

taro3x很简单,作以下配置就能够了。

// config/index.js
const MiniSplitChunksPlugin = require('你copy下来的代码的文件地址');
...
webpackChain: function (chain) {
  chain.plugin('optimizeMainPackage')
  	// 这里加before,是由于该插件必须在miniPlugin前面执行
    .use(MiniSplitChunksPlugin, [{ exclude: true }]).before('miniPlugin');
}
...
复制代码
taro2x

taro2x就比较麻烦了,说下思路:

  1. 要修改MiniSplitChunksPlugin.js文件,由于他使用的不少tarojs/helper的不少辅助方法在taro2是没有,因此咱们须要去补充上。
  2. 须要新增一个app.config.js文件,插件会去读取这个文件的主包/分包的配置,由于taro3已经改为使用了app.config.js做为路由的配置文件了。

这个方式总体改动较大,并且每新加一个页面还要配置app.jsapp.config.js两个地方,比较麻烦。因为配置下来的代码比较多,我就不在这里贴了,有须要的可把项目clone下来本身看。

感兴趣、有能力的同窗也能够本身去改插件的代码,改为直接去app.tsx去读取配置,就不须要用到app.config.js

3、修改splitChunks

说实话有了上面两种方法,基本能够知足各类状况了。有人会以为对于taro2.x使用上诉方法过于麻烦,这里也提供了另外一种方法供参考。这是官方提供的能力addChunkPages,要先经过webpack配置optimization.splitChunks 来单独抽离分包的公共文件,而后经过 mini.addChunkPages 为分包页面配置引入分包的公共文件。

举个列子,有以下目录结构:

└── src
    ├── page  					# 主包
    │   ├── index
    │   		└── index.tsx
    └── subpackage				# 分包
        ├── page1
        		├── index.tsx		# 引用了utils.ts
        ├── page2
        		├── index.tsx		# 引用了utils.ts
      	├── utils.ts
复制代码

咱们但愿utils只被打包到subpackage,咱们能够这样作:

addChunkPages(pages, pagesNames) {
  pagesNames.forEach(pagename => {
    if (/subpackage\//.test(pagename)) {
      pages.set(pagename, ['subpackage/subpackage-common']);
    }
  });
},
 webpackChain: function (chain) {
   chain.merge({
     optimization: {
       splitChunks: {
         cacheGroups: {
           subpackage: {
             name: 'subpackage/subpackage-common',
             minChunks: 2,
             test: (module, chunks) => {
               return /\/subpackage\//.test(module.resource)
             },
             priority: 200
           }
         }
       }
     }
   })
 }
复制代码

打包完成后,utils.ts就被打包到subpackage/subpackage-common.js文件下了。

4、混合开发

相信有不少人,在接触并使用taro的时候,已经有一个使用原生代码开发的一个小程序了,并且短期并不能废弃,因此就出现了使用taro开发,打包完成后复制代码到主包中,做为分包。

咱们能够参考这种方式,分包是打包完成后复制到主包的,因此也就不会出现分包的模块被打包到主包的common.js中了,也算是一个减少主包体积的方式了。

taro2x
  1. 建立一个空的小程序环境(使用taro打包出来的小程序不行,具体缘由还不清楚),做为主包使用。你的主包可能提供一些公共变量、方法供各个分包使用,能够在小程序的App实例上挂载。

    // app.js
    import sdk from "./sdk"
    App({
    	...
      SDK: sdk,
      ...
    })
    
    // 在分包中引用
    const app = Taro.getApp();
    app.SDK.request();
    复制代码
  2. 使用taro建立你的分包,因为直接做为分包,致使该taro打包后的app.js文件不会被执行,咱们只能手动将其一些依赖引入到每一个page,这里不能直接去引入app.js文件的缘由是会致使分包的app对象会覆盖主包的app对象。

    // config/index.js
    mini: {
      ...
      outputRoot: '你在主包要放分包的地址',
      // 对每一个分包的页面都导入app.js
      addChunkPages(pages, pagesNames) {
        pagesNames.forEach(page => {
         	// 根据本身实际状况配,可能会没有common、vendors,能够直接复制打包后app.js头部的require
            pages.set(page, ['runtime', 'common', 'vendors'])
          });
        },
      ...
    }
    复制代码

    有人可能会以为这样还得本身去判断是否有这个文件比较麻烦,我这边也提供了一个webpack插件帮你自动添加(借鉴了taro的plugin-indie插件的代码),

    // config/index.js
    ...
    webpackChain(chain, webpack) {
      chain.plugin('AutoRequirePlugin').use(new AutoRequirePlugin())
    },
    ...
    复制代码
    // webpack插件
    const { ConcatSource } = require('webpack-sources')
    const path = require('path');
    
    class AutoRequirePlugin {
      constructor(event) {
        this.needRequireChunkNames = ['runtime', 'common', 'vendors', 'taro'];
      }
    
      addRequireToSource(id, modules, needRequireChunks) {
        const source = new ConcatSource()
        needRequireChunks.forEach(chunk => {
          source.add(`require(${JSON.stringify(this.promoteRelativePath(path.relative(id, chunk.name)))});\n`)
        })
        source.add('\n')
        source.add(modules)
        source.add(';')
        return source
      }
    
      promoteRelativePath(fPath) {
        const fPathArr = fPath.split(path.sep);
        let dotCount = 0;
        fPathArr.forEach(item => {
          if (item.indexOf('..') >= 0) {
            dotCount++;
          }
        });
        if (dotCount === 1) {
          fPathArr.splice(0, 1, '.');
          return fPathArr.join('/');
        }
        if (dotCount > 1) {
          fPathArr.splice(0, 1);
          return fPathArr.join('/');
        }
        return normalizePath(fPath);
      }
    
      getIdOrName(chunk) {
        if (typeof chunk.id === 'string') {
          return chunk.id
        }
        return chunk.name
      }
    
      apply(compiler) {
        compiler.hooks.make.tap('AutoRequirePlugin', (compilation) => {
          let needRequireChunks = [];
          compilation.hooks.afterOptimizeChunks.tap('afterOptimizeChunks', (chunks) => {
            needRequireChunks = chunks.filter(chunk => this.needRequireChunkNames.includes(chunk.name)).reverse();
          })
          compilation.chunkTemplate.hooks.renderWithEntry.tap('renderWithEntry', (modules, chunk) => {
            if (!chunk.entryModule) return
            const entryModule = chunk.entryModule.rootModule ? chunk.entryModule.rootModule : chunk.entryModule
            const { miniType } = entryModule
            const id = this.getIdOrName(chunk)
            if (miniType === 'PAGE' || miniType === 'STATIC') {
              return this.addRequireToSource(id, modules, needRequireChunks)
            }
          })
        })
      }
    }
    
    module.exports = AutoRequirePlugin; 
    复制代码
taro3x

taro官网说是在taro3.0.25+提供了这种混合开发的能力,具体能够自行看文档,讲的很详细。

总结

以上就是我能总结出来的使用taro减少主包体积的几种方式,能够根据本身的业务场景选择一种适合你的。

因为我的能力和时间有限,并非每一个方法都研究透了,并且也不敢保证,通过个人魔改的代码后不会出问题。此篇主要目的是提供我的对taro减少包体积的一个思路。若是各位大佬有更好的方法或发现什么错误,欢迎指出。

相关文章
相关标签/搜索