https://github.com/mrxu007/webpack-project.git
(生产环境相对于开发环境做了优化,后期上线对照着修改即可,建议下载开发环境使用)
https://github.com/mrxu007/webpack-production-base.git
webpack性能优化 | |
---|---|
开发环境性能优化 | |
生产环境性能优化 | ------------- |
优化打包构建速度 | 优化代码调试 |
---|---|
HMR | source-map |
优化打包构建速度 | 优化代码运行的性能 |
---|---|
oneOf | 缓存(hash-chunkhash-contenthash) |
babel缓存 | tree shaking |
多进程打包 | code split |
externals | 懒加载/预加载 |
dll | PWA技术 |
项目逐渐庞大,修改某个类型文件导致整个项目重新加载(非常消耗资源),引入热模块更新
webpack.config.js配置
devServer: {//本地运行服务器配置
contentBase: resolve(__dirname, 'dist'),//解析文件地址
compress: true,//打开gzip压缩
port: 3000,//端口号
// open: true,//自动打开浏览器
//开启HMR: 热模块替换:一个模块发生变化,只会重新打包这个模块(而不是打包所有模块)
hot: true
}
样式文件:默认可以使用HMR功能
样式文件必须搭配style-loader,因为该loader内部实现了HMR功能
{//处理css
test: /\.css$/,
use:[
'style-loader',
//抽离css功能请在生产模式再开启,不然会与HMR功能冲突
// MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
require('postcss-preset-env')
//帮助postcss-loader去找package.json文件中的browserlist兼容主流浏览器配置
]
}
}
]
}
js文件:默认不能使用HMR功能
需要判断然后按需引入,处理非入口js文件
入口文件index.js配置
if (module.hot) {
module.hot.accept('./print.js', () => {
print();
});
}
html文件:默认不能使用HMR功能
HMR功能的开启,会导致原本html文件热更新失效
解决:
将html文件并入主入口文件
webpack.config.js配置
entry: ['./src/js/index.js', './src/index.html'],//文件入口
[inline-|hidden-|eval-]
[nosources-]
[cheap-[module-]] source-map
具体查看webpack官方文档
内联:构建速度更快,打包体积大
inline
eval
外联:
source-map
hidden-
nosources-
cheap-
cheap-module-
推荐使用source-map
使一个loader只匹配一种文件类型,提升打包构建速度
eslint-loader babel-loader处理的都是js文件,需将eslint提到oneof的外面。
webpack.config.js配置
rules: [
{//elint处理js}
{
oneOf: [
{//es6转esx5 },
{//处理文字图片 },
{//处理html中的img图片 },
{//处理import导入的图片 },
{//处理styl文件 },
{//处理less },
{//处理css }
]
}
]
HMR功能基于devserver的,生产环境下没有devserver
让第二次打包构建更快
将编译js代码进行缓存
{//es6转esx5
test: /\.js$/,
exclude: /node_modules/,//依赖库不需要进行转换
include: resolve(__dirname, 'src'),
use: {
loader: 'babel-loader',
options: {
"presets": [
'@babel/preset-env'
],
//开启babel缓存
//第二次构建时,会读取之前的缓存,提高速度
cacheDirectory: true
}
}
},
配合babel-loader
npm i thread-loader -D
{//es6转esx5
test: /\.js$/,
exclude: /node_modules/,//依赖库不需要进行转换
include: resolve(__dirname, 'src'),
use: [
/*
开启多线程打包
进程启动打包为600ms,进程通讯也要开销。
只有构建时间比较长,才需要多进程打包
*/
{
loader: 'thread-loader',
options: {
workers: 2//开启2个
}
},
{
loader: 'babel-loader',
options: {
"presets": [
[
'@babel/preset-env',
{
//按需加载
useBuiltIns: 'usage',
//指定core-js版本
corejs: {
version: 3
},
//指定兼容性做到那些版本浏览器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
],
//开启babel缓存
//第二次构建时,会读取之前的缓存,提高速度
cacheDirectory: true
}
}
]
},
cdn引入的库不进行打包
externals: {//externals用于不打包由外部连接引入的第三方库例如JQuery,vue等
jquery: 'jquery'
},
将node_modules里面的库单独进行打包
配合optimization
webpack.config.js
const webpack = require('webpack');
plugins: [
new webpack.DllReferencePlugin({//告诉webpack那些库不参与打包,使用时查找映射文件里的库
manifest: resolve(__dirname, 'dll/manifest.json')
}),
]
optimization: {
//1.将/node_modules/的文件单独打包成一个chunk最终输出
//2.也可以分析多入口文件中引入相同的库,文件,单独抽离出来,避免了重复打包
splitChunks : {
chunks: 'all'
}
}
webpack.dll.js
/**
* 使用dll技术,对某些库(第三方库:jquery、react、vue)进行单独打包
* 当运行webpack命令是,默认查找的是webpack.config.js
* --> webpack --config webpack.dll.js
*/
const {resolve} = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
//最终打包生成[name] --> jquery
//['jquery'] --> 要打包的库是jquery
jquery: ['jquery']
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'dll'),
library: '[name]_[hash]'//打包的库向外暴露出去的内容叫什么名字
},
plugins: [
//打包生成一个manifast.json ---> 提供的是库的映射文件
new webpack.DllPlugin({
name: '[name]_[hash]',//映射的库的名称
path: resolve(__dirname, 'dll/manifest.json')
})
],
mode: 'production'
}
文件资源缓存优化
后台强制缓存
问题:如果发生重大bug进行修复,重新构建打包,
强制缓存期间,新文件的修改不会重新请求
解决办法:
hash: 给css,js文件后面加上hash值,每次webpack构建时会新生成一个唯一的hash值
缺点:
不管你是否改变文件,重新构建就会生成新的hash值,导致缓存失效
chunkhash: 根据chunk生成的hash值,
如果打包来源于同一个chunk,hash值就一样。
缺点:
① 改变某一个类型文件,构建打包会导致hash变化,导致缓存失效
② 因为一般来说所有的文件都是通过入口文件引入,所以都同属于同一个chunk。
contenthash:根据文件的内容生成hash值,不同文件,生成的hash值就不同
webapck自带树摇 (配置side避免树摇将重要文件清除)
自动清理应用程序中没有使用的代码
前提:1.必须使用es6模块化 2.开启production
作用:减少打包体积
问题:
可能会把其他css / @babel/polyfill 等没有使用的,清除
解决:
package.json配置
"sideEffects": ['*.css', ' * .less',等等]
code_split(代码分割)
将打包的一个chunk分割成多个chunk,有利于并行加载,开启多线程。
第一种
多入口文件:有几个入口就会输出几个打包文件
第二种
optimization优化
optimization: {
//1.但入口文件:将/node_modules/的文件单独打包成一个chunk最终输出
//2.多入口文件:自动分析引入相同的库、文件,单独抽离出来,避免重复打包
splitChunks : {
chunks: 'all'
}
}
②按需加载的需求
1>对js进行分割处理
分割入口引入中的js文件
使用import高级语法:import会返回一个promise对象
/**
* 编写js代码,分割通过入口文件引入的文件单独打包成chunk
* promise语法实现异步导入
* 可以给个标识,容易区分分割后文件
*/
import(/* webpackChunkName:"test" */'./test')
.then(({ mul }) => {
// console.log('文件打包成功');
console.log(mul(10, 10));
}).catch(() => {
console.log('文件打包失败');
});
但是import属于高级语法,eslint并不识别
报错:error Parsing error: Unexpected token import
解决:
npm i babel-eslint -D
.eslintrc配置文件
{
"extends": "airbnb-base",
"parser": "babel-eslint",//对import按需加载高级语法处理,要不然会报错
"env": {
"amd": true,
"es6": true,
"browser": true,
"node": false
},
"rules": {
//未定义的变量不能使用
"no-undef": 0,
//一行结束后面不要有空格
"no-trailing-spaces": 1,
//强制驼峰命名法
"camelcase": 2,
//不能使用console
"no-console": "off"
}
}
不需要你加载,只有我需要的时候才去请求资源,文件也不会重复请求,自动读取缓存
思路:代码分割的思路,将import代码分割,放在异步得环境中,实现懒加载
缺点: 当我们需要用的资源体积较大时,用户等待时间比较长,适用于请求小的资源文件
入口文件index.js配置
$('.lazy_loading').click(() => {
import(/* webpackChunkName: 'test' */ './test')
.then(({ mul }) => {
console.log(mul(4, 5));
}).catch(() => {
console.log('文件加载失败');
});
});
网页优先加载正常资源,当浏览器空闲时,偷偷加载预加载文件。
正常加载可以认为时并行加载啊
(同一个时间加载多个文件,容易造成堵塞)
缺点:兼容性不强
$('.lazy_loading').click(() => {
import(/* webpackChunkName: 'test',webpackPrefetch: true */ './test')
.then(({ mul }) => {
console.log(mul(4, 5));
}).catch(() => {
console.log('文件加载失败');
});
});
二者区别:
懒加载:当文件需要时才加载~
预加载prefetch: 会在使用之前,加载好资源文件
processive web application 渐进式网路访问程序(离线技术)
npm i workbox-webpack-plugin -D
webpack.config.js配置
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
new WorkboxWebpackPlugin.GenerateSW({ //离线缓存技术
// 帮助serviceworker快速启动
// 删除旧的serviceworker
// 生成serviceWorker文件
clientsClaim: true,
skipWaiting:true
})
入口文件index.js配置
// 注册serviceWorker (离线可访问技术):淘宝等大厂都采用了PWA技术
// 处理兼容性问题
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(() => {
console.log('注册成功');
}).catch(() => {
console.log('注册失败');
});
});
npm i express -D
文件根目录配置:
server.js
/*
启动服务器
node server.js
*/
const express = require('express');
const app = express();
//将dist文件下的东西暴露出去
app.use(express.static('dist', {
//当我们代码上线,我们的资源都要做缓存处理,
//用户第二次访问的时候,直接走访缓存。不需要花太多时间,速度非常快
//但是强制缓存有缺点,当我们代码在强缓存期间遇到紧急bug时,修复bug重新打包构建,
//但是资源还在强制缓存期间,新代码就不会被请求
//先去了解浏览器的缓存机制:强缓存,协商缓存
//解决办法给资源名称做处理,给随机hash值
maxage: 1000 * 3600//强制缓存1个小时
}));
app.listen(3000);