小程式如何分包
背景
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"
複製程式碼