vue-cli3结合webapck优化
1 . 懒加载
(1). js 需要的时候加载
app.vue:
<template>
<div id="app">
<button @click="fn">按钮</button>
</div>
</template>
<script>
export default {
name: "App",
created() {
console.log('index.js 被加载')
},
methods: {
fn() {
import("../src/utils/test.js")
.then(({ mul }) => {
console.log('test.js加载成功:',mul(40, 3));
})
.catch((err) => {
console.log(`test.js加载失败:${err}`);
});
}
},
};
</script>
test.js
console.log('test.js 被加载')
export function mul(a,b){
return a+b
}
点击按钮的时候,才会加载test.js 文件。不会重复加载,当第二次点击按钮会调用缓存。
(2). 路由懒加载
静态引用方式
import KeepAlive from '@/components/KeepAlive'
routes:[ path: '/', name: 'KeepAlive', title: "iframes页面",component: KeepAlive ]
改成这种
{
path: "/",
name: "KeepAlive",
title: "iframes页面",
component: () => import("../component/KeepAlive.vue")// 这种就是赖加载
},
以函数的形式动态引入,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会下载路由组件
补充:有时候需要解析识别import(),配合使用babel-plugin-syntax-dynamic-import
babel-plugin-syntax-dynamic-import 作用:用以解析识别import()动态导入语法---并非转换,而是解析识别
安装:
npm install babel-plugin-syntax-dynamic-import
使用:.babelrc
{
"plugins": ["syntax-dynamic-import"]
}
2. 预加载
提前知道用户未来可能会访问的内容 ,就会把那些内容路由 提前加载。
文件正常加载可以认为是并行加载(同一时间加载多个文件),预加载是等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
在之前的import语法中添加一个参数 webpackPrefetch 表示预加载
document.getElementById('btn').onclick = function () {
// 预加载
import(/* webpackChunkName: 'test', webpackPrefetch: true */ "../src/utils/test.js")
.then(({multi, sum}) => {
console.log(multi, sum)
console.log('multi=', multi(3, 3))
}).catch(() => {
console.log('test文件加载失败!')
})
}
注意:可以关闭预加载的功能
module.exports = {
chainWebpack: config => {
// 移除 prefetch 插件
config.plugins.delete('prefetch')
}
}
(1).懒加载和预加载在实现上面都差不多,但是区别是:
预加载会在使用之前提前加载test文件
懒加载是当文件需要用时才加载
(2). 正常加载和预加载的区别:
正常加载可以认为是并行加载(同一时间加载多个文件)并没有所谓的先后顺序。会造成某一个文件很大但是它是在某些特定条件下才会实现,那么就会造成整体页面加载缓慢。
预加载是等其他资源加载完毕,浏览器空闲了,再偷偷加载资源。(预加载简单来说就是将所有所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。)
(3). 懒加载和预加载的缺点:
懒加载:首次点击按钮才加载文件,但是文件过大就会等待时间过长页面才会有反应,(相当于有一个延迟的效果)用户体验不好。但是第二次点击就会调用缓存,就没问题了。
预加载:会有兼容问题(ie浏览器会有很大的兼容问题),慎用。
3. 使用cdn 预加载, 提前 引入js 文件
一种方式在public/index.html 引入
另一种方式 结合 vue.config.js
vue.config.js
// cdn预加载使用
const externals = {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'axios': 'axios'
}
const cdn = {
// 开发环境
dev: {
css: [
'https://unpkg.com/element-ui/lib/theme-chalk/index.css',
'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css'
],
js: []
},
// 生产环境
build: {
css: [
'https://unpkg.com/element-ui/lib/theme-chalk/index.css',
'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css'
],
js: [
'https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js',
'https://cdn.jsdelivr.net/npm/vue-router@3.0.1/dist/vue-router.min.js',
'https://cdn.jsdelivr.net/npm/vuex@3.0.1/dist/vuex.min.js',
'https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js',
]
}
}
module.exports = {
chainWebpack: config => {
// 使用cdn
config.plugin('html').tap(args => {
if (process.env.NODE_ENV === 'production') {
args[0].cdn = cdn.build
}
if (process.env.NODE_ENV === 'development') {
args[0].cdn = cdn.dev
}
return args
})
},
configureWebpack: config => {
const myConfig = {}
//本地环境 线上环境
if (process.env.NODE_ENV === 'production') {
myConfig.externals = externals
}
if (process.env.NODE_ENV === 'development') {
myConfig.devServer = {
disableHostCheck: true
}
}
return myConfig
}
}
public/index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 使用CDN加速的CSS文件,配置在vue.config.js下 -->
<% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style">
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet">
<% } %>
<!-- 使用CDN加速的JS文件,配置在vue.config.js下 -->
<% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="preload" as="script">
<% } %>
<!-- 测试 -->
<title>vue-cli3</title>
</head>
<body>
<noscript>
<strong>We're sorry but vue-project-demo doesn't work properly without JavaScript enabled. Please enable it
tocontinue.</strong>
</noscript>
<div id="app"></div>
<!-- 使用CDN加速的JS文件,配置在vue.config.js下 -->
<% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
</body>
</html>
4. 去掉注释、去掉console.log
npm i uglifyjs-webpack-plugin
vue.config.js :
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
configureWebpack: config => {
const myConfig = {}
if (process.env.NODE_ENV === 'production') {
myConfig.plugins = []
myConfig.plugins.push(
new UglifyJsPlugin({
uglifyOptions: {
output: {
comments: false, // 去掉注释
},
compress: {
warnings: false,
drop_console: true,
drop_debugger: false,
pure_funcs: ['console.log']//移除console
}
}
})
)
}
}
}
5. Gzip压缩(压缩js、css)
npm i compression-webpack-plugin
const CompressionWebpackPlugin = require('compression-webpack-plugin')
module.exports = {
configureWebpack:{
plugins: [
// 配置compression-webpack-plugin压缩
new CompressionWebpackPlugin({
algorithm: 'gzip',
test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
threshold: 10240,
minRatio: 0.8
})
]
}
}
6. css 不拆分
vuecli 3会默认开启一个css分离插件 ExtractTextPlugin
每一个模块的css文件都会分离出来,整整13个css文件,而我们的首页就请求了4个,花费了不少的资源请求时间,
所以可以在vue.config.js中关闭它
css: {
// 是否使用css分离插件 ExtractTextPlugin
extract: false,
// 开启 CSS source maps?
sourceMap: false,
// css预设器配置项
loaderOptions: {},
// 启用 CSS modules for all css / pre-processor files.
modules: false
},
这样的话,打包出来的dist 就没有css 文件
组件重复打包
二 webpack
识别你的 入口文件,识别你的模块依赖,来打包成一个js文件
1. externals 外部扩展-- 引入cdn
防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖。总就是:防止将某一些包打包到我们最终的bundle里面导致包体积变大【不想通过webapck 打包】
场景:jquery我们希望通过csdn链接引入使用,不希望打包到最后的bundle里面,为了避免打包的时候将jquery最终也给打包了,可以配置externals,禁止将jquery打包。可以参考【使用cdn 预加载, 提前 引入js 文件】
注意: 必须再外网
2. plugins
指webpack 开发 内置的插件【如:compression-webpack-plugin】,而不是其他第三方的插件
3. bundle.js
bundle.js 简单的理解是打包生产的js文件
依赖关系:
因为webpack从entry开始,对每一个 module 都进行处理,碰到 require 之后就跳入到对应的 module 的处理,也就是递归的对这颗依赖树进行处理,这是典型的深度优先遍历的递归解法,而且是先序优先遍历。处理的过程是这样的
处理 main.js,记录入口 [main]
碰到 require(a),记录 [main, a]
进入到 a 模块,碰到语句 require©, 记录下来 [main, a, c]
同理碰到 require(d),记录下来 [main, a, c, d]
返回到 main.js,下一句是 require(‘b’),记录下来 [main, a, c, d, b]
进入模块 b,碰到语句 require(e),记录下来[main, a, c, d, b, e]
返回,结束
4. prefetch(预先加载模块)
预加载prefetch:会在使用之前,提前加载js;
文件正常加载可以认为是并行加载(同一时间加载多个文件),预加载是等其他资源加载完毕,浏览器空闲了,再偷偷加载资源。
兼容性比较差,慎用。
5. HMR(hot module replacement) 模块热替换
作用:一个模块发生改变,只会重新打包这一个模块(而不是打包所有模块),提高webpack构建速度
实现:在devServer配置中增加一个hot属性,值为ture,表示开启HMR功能。HMR是基于dev-server的,生产环境是不需要dev-server。
devServer: {
contentBase: resolve(__dirname, 'build'),
port: 3000,
open: true,
compress: true,
hot: true, // 开启HMR功能
}
注意:
样式文件:可以使用HMR功能,因为style-loader内部实现了HMR功能,会自动的去做。所以我们在开发环境借用style-loader,性能更好,打包速度更快;生产环境需要提取成一个单独的文件,因为上线需要考虑到代码的性能优化。
js文件: 默认不能使用HMR功能,修改完js代码后,整体模块会重新加载
html文件:默认不能使用HMR功能
6. babel缓存 & hash缓存
场景:
当有100个js文件,当修改了其中某一个js文件的时候,不可能再把所有的js文件再编译一遍,会影响构建速度,所以这个时候只要实现修改一个js文件只构建那一个文件,其他的99个文件不动。
那么这个时候可能会想到用热模块替换(HMR),但是HMR是基于dev-server的,生产环境是不需要dev-server。
那么这个时候可以用缓存实现。怎么配置呢?
1、针对js兼容性进行缓存:(babel缓存)
babel-loader的options设置中增加cacheDirectory属性,属性值为true。表示:开启babel缓存,第二次构建时会读取之前的缓存,构建速度会更快一点。
{
test: /\.js$/,
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
2、针对文件资源进行缓存
(1)hash:每次webpack构建时都会生成一个唯一的hash值。(不管文件有没有修改,只要构建就重新生成hash值。)
比如:filename: ‘built.[hash:10].js’
缺点:因为webpack构建js和css是共用一个hash值,如果重新打包会导致所有缓存失效。(只修改js文件,那么css文件也重新构建了)
(2)chunkhash:如果打包来自同一个chunk,那么hash值就一样。
问题:js和css的hash值还是一样的
因为css是在js中被引入的,所以还是同一个hash值,同属于一个chunk.
(3)contenthash:根据文件的内容生成hash值,不同文件的hash值都不一样
缓存作用:
babel缓存:让第二次打包构建速度更快
hash缓存:让代码上线运行缓存更好使用
7. 代码分割 code split
将我们打包输出的一个文件分割为多个文件。(并行加载,速度更快 、按需加载)
主要针对于js代码。
(1)方式一
通过修改entry配置多入口,有几个入口输出有几个bundle
entry: {
index: './src/js/index.js',
test: './src/js/test.js'
}
缺点:修改不太灵活,要改配置。
(2)方式二:optimization
- 单入口应用中
可以将node_modules中代码单独打包成一个chunk最终输出(将第三方的东西打包成一个chunk,自己本身的代码打包成一个chunk) - 多入口应用中
自动分析多入口chunk中有没有公共的文件(公共依赖),如果有会单独打包成一个chunk
optimization: {
splitChunks: {
chunks: 'all'
}
}
(2)方式三:import动态导入语法,
import动态导入语法,能将某个文件单独打包(通过js代码让某个文件被单独打包成一个chunk) (纯粹的单入口)
// 在index.js文件中通过import动态引入test.js文件:
import(/* webpackChunkName: 'test' */'./test')
.then(({multi, sum}) => {
console.log('res=', multi(1, 2), sum(3, 5))
}).catch(() => {
console.log('文件加载失败')
})
8. 多进程打包 thread-loader
场景:js是单线程,同一时间只能干一件事情,如果事情很多,那么就会排队等很久,时间慢。如果同一时间两个/三个进程干这件事情,那么速度就会快很多。
thread-loader :开启多进程打包,一般用在babel-loader后面。
module: {
rules: [
{
test: /\.js$/,
use: [{
loader: 'thread-loader',
options: {
workers: 2
}
},
'babel-loader']
}
]
}
有利有弊:
进程启动大概600ms,进程通信(告诉你某件事情我干好了)也有时间开销。
假设我们花100ms完成的事情,开启多进程打包,那么就会得不偿失。所以多进程打包用在:工 作消耗时间比较长,用直白的话说就是js代码多
补充:
多进程打包一般用在js文件上面,那么有两个loader对js文件进行处理:
eslint-loader:只是对代码进行语法检查,消耗时间可能不是很长
babel-loader:要进行编译,转换,所以时间比较长,所以用在babel-loader里面
9. dll
(1). dll是什么
类似于externals,指使哪些库是不需要打包的。和externals区别在于 dll 会单独的对某些库进行单独打包,将多个库打包成一个chunk。
(2). 有什么意义
正产情况下一个项目下 node_modules 里面的包会打包成一个chunk,但是第三方库很大,打包成一个chunk文件体积过大。所以通过 dll 将所有库进行单独打包成不同的chunk,更加有利于我们的性能优化,提高构建速度。
使用dll技术对某些库(第三方库:jquery,react,vue…)进行单独打包,减少重复打包次数,提高构建速度。
(3). 参考
9. webpack分包及异步加载套路
(1)静态资源图片,字体可以在webpack配置url-loader,file-loader结合这用,以及png,svg,base64这些结合使用,具体可以参考百度搜索
(2)js文件打包,可以使用chunk,进行分模块,并且webpack添加按需加载配置dynamic(比如一个文件超过1M,可以分成多文件,具体拆分规则待讨论)
(3)vue的每个组件中,对于引入的dom对象的使用,事件监听,或者bus的使用,记住,需要销毁,否则占用内存较大,久了容易页面卡顿
(4)可以在路由的加载中进行权限校验,但是复杂的权限,建议提取出来,项目登录时判断+进入主页后判断,并且可以对数据进行持久化,