vue-cli3
版本的發佈距今已通過了大半年,先後迭代了50多個版本,終於趨於穩定;這裏不得不得感嘆vue開源團隊對vue技術棧的傾力貢獻,使得vue社區的前端工程化實踐又向前邁了一大步。相比vue-cli2
版本的'大鍋混',三版本的插件系統卓識使人驚豔了一把,所以組內也在第一時間遷移了vue-cli3
,本文算是對插件系統的一次探索與學習,也算是一次拋磚引玉,期待後面繼續更新推出優秀的插件並將開發插件的經驗總結開源出來。html
關於模塊預編譯,網上的教程及webpack配置攻略很是多,沒有經驗的讀者可參考webpack dllPlugin。在前端項目迭代到中後期或者依賴第三方模塊體積較大時,模塊預編譯可有效提高webpack
構建速度,但不一樣項目須要預編譯的模塊不一樣,以及配置細節也不一樣,因此藉助vue-cli3
封裝成vue-plugin-dll
插件,將構建邏輯封裝在插件內部,對外開放預編譯的配置項,這樣可使前端開發更專一於業務。前端
注意:本文封裝的
vue-cli-plugin-dll
未發佈到npm中,僅提供了開發插件的思路和總結。vue
webpack.dllPlugin
本質是將大量複用模塊且不會頻繁更新的庫進行預編譯,且只須要編譯一次,編譯完成後產出指定文件(能夠稱爲動態連接庫)。在以後的構建過程當中不會再對這些模塊進行編譯,而是直接使用DllReferencePlugin來引用動態連接庫的代碼,所以能夠提升構建速度。通常能夠將第三方模塊進行預編譯,如 vue、vue-router、vuex
等,只要這些依賴模塊不更新,就不須要再從新編譯。webpack
在封裝vue-cli-plugin-dll
插件以前,須要探索一下模塊預編譯對前端項目的影響有多大。 這裏實驗對比了兩個項目:ios
vue-cli3
構建的初始化項目。vue-cli3
構建且依賴其餘三方庫的工程。開發環境,未預先運行dll腳本進行預編譯git
構建次數 | 第一次 | 第二次 | 第三次 | 第四次 | 平均用時 |
---|---|---|---|---|---|
vue-init | 2997ms | 3561ms | 2867ms | 2935ms | 3078ms |
sellgoods | 21449ms | 16601ms | 22480ms | 22600ms | 20782ms |
生產環境,未預先運行dll腳本進行預編譯github
構建次數 | 第一次 | 第二次 | 第三次 | 第四次 | 平均用時 | 構建包大小 |
---|---|---|---|---|---|---|
vue-init | 3736ms | 3713ms | 3647ms | 3800ms | 3724ms | 122.99 KB |
sellgoods | 52.09s | 38.77s | 39.78s | 47.82s | 44.615s | 2.54 MB |
其中files
指定了須要提早預編譯的模塊list
web
// vue-init 預編譯列表
files: [
'vue/dist/vue.runtime.esm.js',
'vue-router',
'vuex'
]
// sellgoods 預編譯列表
files: [
'vue/dist/vue.runtime.esm.js',
'vue-router',
'vuex',
'axios',
'element-ui',
'nprogress',
'qs',
'resize-observer-polyfill',
'lodash'
]
複製代碼
開發環境,運行dll腳本提早預編譯vue-router
構建次數 | 第一次 | 第二次 | 第三次 | 第四次 | 平均用時 |
---|---|---|---|---|---|
vue-init | 2723ms | 2849ms | 2799ms | 2774ms | 2786ms |
sellgoods | 16115ms | 16432ms | 16479ms | 15131ms | 16039ms |
生產環境,運行dll腳本提早預編譯vuex
構建次數 | 第一次 | 第二次 | 第三次 | 第四次 | 平均用時 | 構建包大小 |
---|---|---|---|---|---|---|
vue-init | 3057ms | 2936ms | 3708ms | 2877ms | 3144ms | 25.06 KB |
sellgoods | 27.93s | 27.60s | 27.72s | 27.10s | 27.58s | 1.51 MB |
實際上,影響webpack構建速度的因素存在不少,好比硬件設施、webpack配置是否合理、代碼分割策略等等。這裏只針對預編譯(建立動態軟鏈)這一種狀況的優化作了分析。
同時爲告終果的可行性分析,這裏剔除了異常數據,僅對優化與未優化兩種結果的數據進行對比來進行討論。
從生產環境的構建時間能夠看到:
從生產環境的構建時間能夠看到:
注:構建產物減小不意味着瀏覽器加載資源變少,而是減小的部分被提早預編譯,以script標籤形式在index.html中引入。
結論:
針對同一工程的不一樣環境下而言,預編譯對生產環境的構建提高速度明顯
從vue-init和sellgoods兩者的生產環境與開發環境進行對比能夠看到,不考慮硬件設施和其它因素影響的狀況下,生產環境下的效率提高要比開發環境提高效率高出一倍左右。
預編譯的模塊體積越大,構建提高效率越高
將sellgoods與vue-init進行橫向比較,vue-init項目是腳手架的初始項目,只添加了vue、vue-router、vuex等依賴庫;而sellgoods項目已進行到中後期,相對於vue-init而言,代碼量及依賴的庫要多不少,其中以element-ui
最爲明顯。從結果能夠看到,sellgoods不管是生產環境仍是開發環境下,預編譯對構建效率的提高都要比vue-init明顯。
實際上,webpack.dllPlugin
配置門檻很低,但沒有必要在每一個工程中配置一遍,或者將底層配置開放給業務人員。這裏選擇了封裝vue-cli-plugin-dll
插件併發布到內網npm
源中,供其餘項目自由引用,下面詳細介紹若是一步步開放vue-cli3
插件。
插件開發文檔可見:vue插件開發指南
├── generator
├ └── index.js
├── service
├ ├── base.js
├ └── dll.js
├── index.js
└── package.json
複製代碼
generator
const { red, green } = require('chalk');
module.exports = (api, options, rootOptions) => {
api.extendPackage({
scripts: {
dll: 'vue-cli-service dll'
},
vue: {
pluginOptions: {
dll: {
// 文件名
entry: 'vendor',
// 文件輸出路徑
filePath: './public/vendor',
// 預編譯包
files: ['vue/dist/vue.runtime.esm.js', 'vue-router', 'vuex'],
// 是否保留歷史編譯記錄
noCache: true
}
}
}
});
};
複製代碼
generator
對外暴露一個函數,對內接受一個api工具類(GeneratorAPI)負責對工程作偏好設置。這裏咱們藉助extendPackage
方法向package.json
文件注入dll
指令,以及dll
插件的初始化配置。若是創建項目的時候勾選了useConfigFiles
,那麼vue
屬性下的配置將會被注入到vue.config.js
文件中。
module.exports = (api, ops) => {
require('./service/base')(api, ops);
require('./service/dll')(api, ops);
};
module.exports.defaultModes = {
dll: 'production'
};
複製代碼
service
也對外暴露一個函數,並接受api工具類(PluginAPI)負責對webpack做更新配置。 這裏咱們將webpack配置進行解耦,base
配置公共webpack
邏輯,建立動態軟鏈;而dll
負責預編譯模塊邏輯。
const { red, green } = require('chalk');
module.exports = (api, ops = {}) => {
api.registerCommand(
'dll',
{
description: '第三方模塊預編譯',
usage: 'vue-cli-service dll'
},
async args => {
const Config = require('webpack-chain');
const webpack = require('webpack');
const fs = require('fs-extra');
const path = require('path');
const {
log,
done,
logWithSpinner,
stopSpinner
} = require('@vue/cli-shared-utils');
logWithSpinner(green('Building dll files to public vendor'));
const config = new Config();
const pluginOptions = ops.pluginOptions || {};
const root = api.getCwd();
const dllConfig = pluginOptions.dll;
if (!dllConfig) {
log();
log(red('缺失dll文件配置'));
log();
process.exit(0);
}
function resolve(dir) {
return path.resolve(root, dir);
}
function hasVendor(filePath) {
return fs.existsSync(resolve(filePath));
}
// 默認打到public/vendor文件夾裏
const {
entry = 'vendor',
filePath = `./public/${entry}`,
files,
noCache = true
} = dllConfig;
if (files.length) {
files.forEach(oneOf => config.entry(entry).add(oneOf));
}
config.output
.path(resolve(filePath))
.filename('[name].dll.[hash:8].js')
.library('[name]_[hash]')
.end();
if (noCache) {
// 清空vendor緩存
config.when(hasVendor(filePath), () => {
fs.removeSync(resolve(filePath));
});
}
config
.plugin('DllPlugin')
.use(require('webpack/lib/DllPlugin'), [
{
name: '[name]_[hash]',
path: path.join(root, filePath, '[name]-manifest.json'),
context: root
}
])
.end();
const result = config.toConfig();
webpack(result, (err, stats) => {
stopSpinner(false);
if (err) {
log();
log(red(err));
log();
return false;
}
done(green('Build complete'));
});
}
);
};
複製代碼
這裏藉助registerCommand
方法註冊dll
指令,與generator
中擴展的腳本先後呼應,在dll
方法中,核心使用webpack/lib/DllPlugin
插件預編譯模塊,併產生緩存文件,供其餘環境配置使用。
module.exports = (api, ops) => {
const webpack = require('webpack');
const path = require('path');
const fs = require('fs');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const root = api.getCwd();
function resolve(dir) {
return path.resolve(root, dir);
}
if (ops && ops.pluginOptions) {
const { entry = 'vendor', filePath = `./public/${entry}` } =
ops.pluginOptions.dll || {};
const outputPath = path.basename(filePath) || entry;
if (fs.existsSync(path.join(filePath, `${entry}-manifest.json`))) {
api.configureWebpack(config => {
config.plugins.push(
new webpack.DllReferencePlugin({
context: root,
manifest: require(resolve(`${filePath}/${entry}-manifest.json`))
}),
new AddAssetHtmlPlugin({
filepath: resolve(`${filePath}/*.js`),
publicPath: `./${outputPath}`,
outputPath: `./${outputPath}`
})
);
});
}
}
};
複製代碼
在插件安裝完畢以後,運行yarn dll
指令,便可將預編譯的包及緩存打到public/vendor
目錄下,這時還需爲其餘環境(如開發和生產環境)配置動態軟鏈,忽略預編譯模塊的構建。在base.js
中藉助configureWebpack
方法將建立動態軟鏈的配置更新到最終版的webpack
配置中(也可以使用chainWebpack
)。
至此,一個初步的vue-cli-plugin-dll
插件開發完畢,具有了預編譯模塊的功能,但仍有不少的不足,好比未開放預編譯模塊的loader
或者plugin
定製功能等,這裏僅是一次插件封裝的嘗試。
最後:歡迎大牛或者有經驗的前端從業人員對本文有誤內容不吝指導。
轉載請註明出處,十分感謝!