想必使用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,像一些常规的方法:如减小本地图片的使用,按需引入组件,删除无用代码等等这些这里不介绍了。小程序
module.exports = {
// ...
mini: {
// ...
optimizeMainPackage: {
enable: true
}
}
}
复制代码
这个特性3.2.9版本开始支持,但这个版本在window下会报错,在3.2.12才修复。可是目前还有几个问题,微信小程序
在开发环境下会报错某个文件找不到,具体缘由能够自行去看这个issue,目前好像还未修复。解决方法:微信
issue里有comment提出将暂存的sub-common移到项目外,避免被ide监测,这个须要去改node_modules下的@tarojs/mini-runner/dist/plugins/MiniSplitChunksPlugin.js
文件。
只在生产环境下才开启这个功能,在config/prod.js下加上面的配置,对于比较懒得同窗能够先这样,相信官方会很快修复这个bug。
报Cannot read property 'getCacheGroup' of null
,能够在上面那个issue找到解决方法。
有人的taro版本3.2.9版本如下,暂时没法升级到3.2.9版本,怎么办?其实方法一说到底就是一个webpack插件,那咱们把找一个支持optimizeMainPackage的taro项目,复制node_modules/@tarojs/mini-runner/dist/plugins/MiniSplitChunksPlugin.js
,放到你本身的项目下做为本地插件,经过webpackChain导入插件不就好了吗。
这种方式在taro2x和taro3x配置略有不一样,下面我分别介绍:
taro3x很简单,作以下配置就能够了。
// config/index.js
const MiniSplitChunksPlugin = require('你copy下来的代码的文件地址');
...
webpackChain: function (chain) {
chain.plugin('optimizeMainPackage')
// 这里加before,是由于该插件必须在miniPlugin前面执行
.use(MiniSplitChunksPlugin, [{ exclude: true }]).before('miniPlugin');
}
...
复制代码
taro2x就比较麻烦了,说下思路:
MiniSplitChunksPlugin.js
文件,由于他使用的不少tarojs/helper
的不少辅助方法在taro2是没有,因此咱们须要去补充上。app.config.js
文件,插件会去读取这个文件的主包/分包的配置,由于taro3已经改为使用了app.config.js做为路由的配置文件了。这个方式总体改动较大,并且每新加一个页面还要配置app.js
和app.config.js
两个地方,比较麻烦。因为配置下来的代码比较多,我就不在这里贴了,有须要的可把项目clone下来本身看。
感兴趣、有能力的同窗也能够本身去改插件的代码,改为直接去app.tsx去读取配置,就不须要用到app.config.js
。
说实话有了上面两种方法,基本能够知足各类状况了。有人会以为对于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
文件下了。
相信有不少人,在接触并使用taro的时候,已经有一个使用原生代码开发的一个小程序了,并且短期并不能废弃,因此就出现了使用taro开发,打包完成后复制代码到主包中,做为分包。
咱们能够参考这种方式,分包是打包完成后复制到主包的,因此也就不会出现分包的模块被打包到主包的common.js中了,也算是一个减少主包体积的方式了。
建立一个空的小程序环境(使用taro打包出来的小程序不行,具体缘由还不清楚),做为主包使用。你的主包可能提供一些公共变量、方法供各个分包使用,能够在小程序的App实例上挂载。
// app.js
import sdk from "./sdk"
App({
...
SDK: sdk,
...
})
// 在分包中引用
const app = Taro.getApp();
app.SDK.request();
复制代码
使用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;
复制代码
taro官网说是在taro3.0.25+提供了这种混合开发的能力,具体能够自行看文档,讲的很详细。
以上就是我能总结出来的使用taro减少主包体积的几种方式,能够根据本身的业务场景选择一种适合你的。
因为我的能力和时间有限,并非每一个方法都研究透了,并且也不敢保证,通过个人魔改的代码后不会出问题。此篇主要目的是提供我的对taro减少包体积的一个思路。若是各位大佬有更好的方法或发现什么错误,欢迎指出。