Author: 微笑向暖html
vue-cli3
版本的发布距今已通过了大半年,先后迭代了50多个版本,终于趋于稳定;这里不得不得感叹vue开源团队对vue技术栈的倾力贡献,使得vue社区的前端工程化实践又向前迈了一大步。相比vue-cli2
版本的'大锅混',三版本的插件系统卓识使人惊艳了一把,所以组内也在第一时间迁移了vue-cli3
,本文算是对插件系统的一次探索与学习,也算是一次抛砖引玉,期待后面继续更新推出优秀的插件并将开发插件的经验总结开源出来。前端
关于模块预编译,网上的教程及webpack配置攻略很是多,没有经验的读者可参考webpack dllPlugin。在前端项目迭代到中后期或者依赖第三方模块体积较大时,模块预编译可有效提高webpack
构建速度,但不一样项目须要预编译的模块不一样,以及配置细节也不一样,因此借助vue-cli3
封装成vue-plugin-dll
插件,将构建逻辑封装在插件内部,对外开放预编译的配置项,这样可使前端开发更专一于业务。vue
注意:本文封装的
vue-cli-plugin-dll
未发布到npm中,仅提供了开发插件的思路和总结。webpack
webpack.dllPlugin
本质是将大量复用模块且不会频繁更新的库进行预编译,且只须要编译一次,编译完成后产出指定文件(能够称为动态连接库)。在以后的构建过程当中不会再对这些模块进行编译,而是直接使用DllReferencePlugin来引用动态连接库的代码,所以能够提升构建速度。通常能够将第三方模块进行预编译,如 vue、vue-router、vuex
等,只要这些依赖模块不更新,就不须要再从新编译。ios
在封装vue-cli-plugin-dll
插件以前,须要探索一下模块预编译对前端项目的影响有多大。 这里实验对比了两个项目:git
vue-cli3
构建的初始化项目。vue-cli3
构建且依赖其余三方库的工程。开发环境,未预先运行dll脚本进行预编译github
构建次数 | 第一次 | 第二次 | 第三次 | 第四次 | 平均用时 |
---|---|---|---|---|---|
vue-init | 2997ms | 3561ms | 2867ms | 2935ms | 3078ms |
sellgoods | 21449ms | 16601ms | 22480ms | 22600ms | 20782ms |
生产环境,未预先运行dll脚本进行预编译web
构建次数 | 第一次 | 第二次 | 第三次 | 第四次 | 平均用时 | 构建包大小 |
---|---|---|---|---|---|---|
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
vue-router
// 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脚本提早预编译vuex
构建次数 | 第一次 | 第二次 | 第三次 | 第四次 | 平均用时 |
---|---|---|---|---|---|
vue-init | 2723ms | 2849ms | 2799ms | 2774ms | 2786ms |
sellgoods | 16115ms | 16432ms | 16479ms | 15131ms | 16039ms |
生产环境,运行dll脚本提早预编译
构建次数 | 第一次 | 第二次 | 第三次 | 第四次 | 平均用时 | 构建包大小 |
---|---|---|---|---|---|---|
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
定制功能等,这里仅是一次插件封装的尝试。
转载请注明出处,十分感谢!