内容简介:关于模块预编译,网上的教程及webpack配置攻略非常多,没有经验的读者可参考webpack dllPlugin。在前端项目迭代到中后期或者依赖第三方模块体积较大时,模块预编译可有效提升注意:本文封装的
vue-cli3
版本的发布距今已经过了大半年,前后迭代了50多个版本,终于趋于稳定;这里不得不得感叹vue开源团队对vue技术栈的倾力贡献,使得vue社区的前端工程化实践又向前迈了一大步。相比 vue-cli2
版本的'大锅混',三版本的插件系统卓识令人惊艳了一把,因此组内也在第一时间迁移了 vue-cli3
,本文算是对插件系统的一次探索与学习,也算是一次抛砖引玉,期待后面继续更新推出优秀的插件并将开发插件的经验总结开源出来。
插件开发背景
关于模块预编译,网上的教程及webpack配置攻略非常多,没有经验的读者可参考webpack dllPlugin。在前端项目迭代到中后期或者依赖第三方模块体积较大时,模块预编译可有效提升 webpack
构建速度,但不同项目需要预编译的模块不同,以及配置细节也不同,所以借助 vue-cli3
封装成 vue-plugin-dll
插件,将构建逻辑封装在插件内部,对外开放预编译的配置项,这样可以使前端开发更专注于业务。
注意:本文封装的 vue-cli-plugin-dll
未发布到npm中,仅提供了开发插件的思路和总结。
模块预编译原理
webpack.dllPlugin
本质是将大量复用模块且不会频繁更新的库进行预编译,且只需要编译一次,编译完成后产出指定文件(可以称为动态链接库)。在之后的构建过程中不会再对这些模块进行编译,而是直接使用DllReferencePlugin来引用动态链接库的代码,因此可以提高构建速度。一般可以将第三方模块进行预编译,如 vue、vue-router、vuex
等,只要这些依赖模块不更新,就不需要再重新编译。
项目对比
在封装 vue-cli-plugin-dll
插件之前,需要探索一下模块预编译对前端项目的影响有多大。
这里实验对比了两个项目:
vue-cli3 vue-cli3
改造前现状
开发环境,未预先运行dll脚本进行预编译
| 构建次数 | 第一次 | 第二次 | 第三次 | 第四次 | 平均用时 |
|---|---|---|---|---|---|
| vue-init | 2997ms | 3561ms | 2867ms | 2935ms | 3078ms |
| sellgoods | 21449ms | 16601ms | 22480ms | 22600ms | 20782ms |
生产环境,未预先运行dll脚本进行预编译
| 构建次数 | 第一次 | 第二次 | 第三次 | 第四次 | 平均用时 | 构建包大小 |
|---|---|---|---|---|---|---|
| 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-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-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配置是否合理、代码分割策略等等。这里只针对预编译(创建动态软链)这一种情况的优化做了分析。
同时为了结果的可行性分析,这里剔除了异常数据,仅对优化与未优化两种结果的数据进行对比来进行讨论。
从生产环境的构建时间可以看到:
- vue-init:开发环境下构建平均耗时 3078ms;优化后平均耗时2786ms,速度提升10%左右;
- sellgoods:开发环境下构建平均耗时20782ms;优化后平均耗时16039ms,速度提升30%左右。
从生产环境的构建时间可以看到:
- vue-init:生产环境下构建平均耗时3724ms,优化后平均耗时3144ms,构建产出包大小由122.99 KB缩减到25.06 KB,速度提升18%左右。
- sellgoods:生产环境下构建平均耗时44.615s,优化后平均耗时27.58s,构建产出包大小由2.54 MB缩减到1.51 MB,速度提升60%左右。
注:构建产物减少不意味着浏览器加载资源变少,而是减少的部分被提前预编译,以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插件开发指南
1.构建插件目录
├── generator ├ └── index.js ├── service ├ ├── base.js ├ └── dll.js ├── index.js └── package.json 复制代码
2.开发 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
文件中。
3.开发service(index.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
负责预编译模块逻辑。
4.开发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
插件预编译模块,并产生缓存文件,供其他环境配置使用。
5.开发base.js
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
定制功能等,这里仅是一次插件封装的尝试。
最后:欢迎大牛或者有经验的前端从业人员对本文有误内容不吝指导。
转载请注明出处,十分感谢!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- logstash grok filter 插件实战
- 急速 debug 实战三(Node - webpack插件,babel插件,vue源码篇)
- Electron-vue开发实战5——开发插件系统之CLI部分
- 实战生产环境:1.13.3最新版k8s集群部署Heapster插件
- Flutter 实战之从 0 搭建「网易云音乐」App(一):创建项目、添加插件、通用代码
- IDEA 插件:多线程文件下载插件开发
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Building Web Reputation Systems
Randy Farmer、Bryce Glass / Yahoo Press / 2010 / GBP 31.99
What do Amazon's product reviews, eBay's feedback score system, Slashdot's Karma System, and Xbox Live's Achievements have in common? They're all examples of successful reputation systems that enable ......一起来看看 《Building Web Reputation Systems》 这本书的介绍吧!
Markdown 在线编辑器
Markdown 在线编辑器
HEX CMYK 转换工具
HEX CMYK 互转工具