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

相關文章
相關標籤/搜索