小程式分包(原生+mpvue)

a897830933發表於2018-08-10

小程式如何分包

背景

2017.01.09 小程式上線時,官方限制了程式碼包不能超過1MB大小,目的是出於對小程式啟動速度的考慮,希望使用者在使用任何一款小程式時,都能獲得一種“秒開”體驗。然而,1MB 的大小也限制了小程式功能的擴充套件,小程式業務的發展可能需要更大的體積。

那麼,能否有一種方案,在增加小程式包大小的同時,也能保持不錯的啟動速度呢?

為了解決這個矛盾點,官方推出了「分包載入」這個技術方案。

分包載入的介紹

在構建小程式分包專案時,構建會輸出一個或多個功能的分包,其中每個分包小程式必定含有一個主包,所謂的主包,即放置預設啟動頁面/TabBar 頁面,以及一些所有分包都需用到公共資源/JS 指令碼,而分包則是根據開發者的配置進行劃分。

在小程式啟動時,預設會下載主包並啟動主包內頁面,如果使用者需要開啟分包內某個頁面,客戶端會把對應分包下載下來,下載完成後再進行展示。

優點:

  • 對開發者而言,能使小程式有更大的程式碼體積,承載更多的功能與服務
  • 對使用者而言,可以更快地開啟小程式,同時在不影響啟動速度前提下使用更多功能

限制:

  • 整個小程式所有分包大小不超過 8M
  • 單個分包/主包大小不能超過 2M

分包載入的配置

假設支援分包的小程式目錄結構如下:

├── app.js
├── app.json
├── app.wxss
├── packageA
│   └── pages
│       ├── cat
│       └── dog
├── packageB
│   └── pages
│       ├── apple
│       └── banana
├── pages
│   ├── index
│   └── logs
└── utils

複製程式碼

開發者通過在 app.json subPackages 欄位宣告專案分包結構:

{
  "pages":[
    "pages/index",
    "pages/logs"
  ],
  "subPackages": [
    {
      "root": "packageA",
      "pages": [
        "pages/cat",
        "pages/dog"
      ]
    }, {
      "root": "packageB",
      "pages": [
        "pages/apple",
        "pages/banana"
      ]
    }
  ]
}

複製程式碼

###分包原則

  • 宣告 subPackages 後,將按 subPackages 配置路徑進行打包,subPackages 配置路徑外的目錄將被打包到 app(主包) 中
  • app(主包)也可以有自己的 pages(即最外層的 pages 欄位
  • subPackage 的根目錄不能是另外一個 subPackage 內的子目錄
  • 首頁的 TAB 頁面必須在 app(主包)內

引用原則

  • packageA 無法 require packageB JS 檔案,但可以 require app、自己 package 內的 JS 檔案
  • packageA 無法 import packageB 的 template,但可以 require app、自己 package 內的 template
  • packageA 無法使用 packageB 的資源,但可以使用 app、自己 package 內的資源

使用mpvue如何分包?

package.json修改

  • 升級: "mpvue-loader": "^1.1.2-rc.4" "webpack-mpvue-asset-plugin": "^0.1.1"
  • 新增: "relative": "^3.0.2"

注意事項

  • 1.1.2-rc.5 修復 slot 檔案路徑生成錯誤的問題
  • 1.1.x 版本還不是很穩定,對穩定性要求較高的專案建議暫時使用 1.0.x 版本

src/main.js

刪除 config

-export default {
--  // 這個欄位走 app.json
-  config: {
-    // 頁面前帶有 ^ 符號的,會被編譯成首頁,其他頁面可以選填,我們會自動把 webpack entry 裡面的入口頁面加進去
-    pages: ['pages/logs/main', '^pages/index/main'],
-    window: {
-      backgroundTextStyle: 'light',
-      navigationBarBackgroundColor: '#fff',
-      navigationBarTitleText: 'WeChat',
-      navigationBarTextStyle: 'black'
-    }
-  }
-}

複製程式碼

src/main.json(新增檔案與main.js同級)

將原 js 中的 config 遷移到 main.json 檔案中

注意:分包應和主包在同一根目錄下

main.json

+{
+ "pages": [
+   "pages/index/main",
+   "pages/logs/main"
+  ],
+  "subPackages": [
+    {
+      "root": "pages/packageA",
+     "pages": [
+       "counter/main"
+     ]
+   }
+ ],
+ "window": {
+ "backgroundTextStyle": "light",
+ "navigationBarBackgroundColor": "#fff",
+ "navigationBarTitleText": "WeChat",
+ "navigationBarTextStyle": "black"
+ }
}

複製程式碼

webpack 配置配合升級指南

  • build/webpack.base.conf.js
// build/webpack.base.conf.js

+var CopyWebpackPlugin = require('copy-webpack-plugin')
+var relative = require('relative')

 function resolve (dir) {
   return path.join(__dirname, '..', dir)
 }

-function getEntry (rootSrc, pattern) {
-  var files = glob.sync(path.resolve(rootSrc, pattern))
-  return files.reduce((res, file) => {
-    var info = path.parse(file)
-    var key = info.dir.slice(rootSrc.length + 1) + '/' + info.name
-    res[key] = path.resolve(file)
-    return res
-  }, {})
+function getEntry (rootSrc) {
+  var map = {};
+  glob.sync(rootSrc + '/pages/**/main.js')
+  .forEach(file => {
+    var key = relative(rootSrc, file).replace('.js', '');
+    map[key] = file;
+  })
+   return map;
 }

const appEntry = { app: resolve('./src/main.js') }
 const pagesEntry = getEntry(resolve('./src'), 'pages/**/main.js')
 const entry = Object.assign({}, appEntry, pagesEntry)

@@ -108,6 +122,14 @@ module.exports = {
     ]
   },
   plugins: [
-    new MpvuePlugin()
+    new MpvuePlugin(),
+    new CopyWebpackPlugin([{
+      from: '**/*.json',
+      to: 'app.json'
+    }], {
+      context: 'src/'
+    }),
+    new CopyWebpackPlugin([ // 處理 main.json 裡面引用的圖片,不要放程式碼中引用的圖片
+      {
+        from: path.resolve(__dirname, '../static'),
+        to: path.resolve(__dirname, '../dist/static'),
+        ignore: ['.*']
+      }
+    ])
   ]
 }
複製程式碼
  • build/webpack.dev.conf.js

修改生成檔案的路徑,讓生成的檔案路徑可以放在原來的 page 下面

module.exports = merge(baseWebpackConfig, {
   devtool: '#source-map',
   output: {
     path: config.build.assetsRoot,
-    filename: utils.assetsPath('js/[name].js'),
-    chunkFilename: utils.assetsPath('js/[id].js')
+    filename: utils.assetsPath('[name].js'),
+    chunkFilename: utils.assetsPath('[id].js')
   },
   plugins: [
     new webpack.DefinePlugin({
@@ -42,8 +42,8 @@ module.exports = merge(baseWebpackConfig, {
     // copy from ./webpack.prod.conf.js
     // extract css into its own file
     new ExtractTextPlugin({
-      filename: utils.assetsPath('css/[name].wxss')
+      filename: utils.assetsPath('[name].wxss')
     }),
@@ -53,7 +53,7 @@ module.exports = merge(baseWebpackConfig, {
       }
     }),
     new webpack.optimize.CommonsChunkPlugin({
-      name: 'vendor',
+      name: 'common/vendor',
       minChunks: function (module, count) {
         // any required modules inside node_modules are extracted to vendor
         return (
@@ -64,17 +64,9 @@ module.exports = merge(baseWebpackConfig, {
       }
     }),
     new webpack.optimize.CommonsChunkPlugin({
-      name: 'manifest',
-      chunks: ['vendor']
+      name: 'common/manifest',
+      chunks: ['common/vendor']
     }),
-    // copy custom static assets
-    new CopyWebpackPlugin([
-      {
-        from: path.resolve(__dirname, '../static'),
-        to: config.build.assetsSubDirectory,
-        ignore: ['.*']
-      }
-    ]),

複製程式碼
  • build/webpack.prod.conf.js

同 build/webpack.dev.conf.js 一樣

@@ -24,10 +24,10 @@ var webpackConfig = merge(baseWebpackConfig, {
   devtool: config.build.productionSourceMap ? '#source-map' : false,
   output: {
     path: config.build.assetsRoot,
-    filename: utils.assetsPath('js/[name].js'),
-    chunkFilename: utils.assetsPath('js/[id].js')
+    filename: utils.assetsPath('[name].js'),
+    chunkFilename: utils.assetsPath('[id].js')
   },
   plugins: [
     // http://vuejs.github.io/vue-loader/en/workflow/production.html
@@ -39,8 +39,8 @@ var webpackConfig = merge(baseWebpackConfig, {
     }),
     // extract css into its own file
     new ExtractTextPlugin({
-      // filename: utils.assetsPath('css/[name].[contenthash].css')
-      filename: utils.assetsPath('css/[name].wxss')
+      // filename: utils.assetsPath('[name].[contenthash].css')
+      filename: utils.assetsPath('[name].wxss')
     }),
     // Compress extracted CSS. We are using this plugin so that possible
     // duplicated CSS from different components can be deduped.
@@ -72,7 +72,7 @@ var webpackConfig = merge(baseWebpackConfig, {
     new webpack.HashedModuleIdsPlugin(),
     // split vendor js into its own file
     new webpack.optimize.CommonsChunkPlugin({
-      name: 'vendor',
+      name: 'common/vendor',
       minChunks: function (module, count) {
         // any required modules inside node_modules are extracted to vendor
         return (
@@ -85,17 +85,9 @@ var webpackConfig = merge(baseWebpackConfig, {
     // extract webpack runtime and module manifest to its own file in order to
     // prevent vendor hash from being updated whenever app bundle is updated
     new webpack.optimize.CommonsChunkPlugin({
-      name: 'manifest',
-      chunks: ['vendor']
-    }),
+      name: 'common/manifest',
+      chunks: ['common/vendor']
+    })
-    // copy custom static assets
-    new CopyWebpackPlugin([
-      {
-        from: path.resolve(__dirname, '../static'),
-        to: config.build.assetsSubDirectory,
-        ignore: ['.*']
-      }
-    ])
   ]
 })
複製程式碼
  • config/index.js
module.exports = {
     env: require('./prod.env'),
     index: path.resolve(__dirname, '../dist/index.html'),
     assetsRoot: path.resolve(__dirname, '../dist'),
-    assetsSubDirectory: 'static', // 不將資源聚合放在 static 目錄下
+    assetsSubDirectory: '',
     assetsPublicPath: '/',
     productionSourceMap: false,
     // Gzip off by default as many popular static hosts such as
@@ -26,7 +26,7 @@ module.exports = {
     port: 8080,
     // 在小程式開發者工具中不需要自動開啟瀏覽器
     autoOpenBrowser: false,
-    assetsSubDirectory: 'static', // 不將資源聚合放在 static 目錄下
+    assetsSubDirectory: '',
     assetsPublicPath: '/',
     proxyTable: {},
     // CSS Sourcemaps off by default because relative paths are "buggy"

複製程式碼

相關文章