想必使用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減少包體積的一個思路。若是各位大佬有更好的方法或發現什麼錯誤,歡迎指出。