技能点
打包流程图
graph TD
入口index.js --> 寻找依赖模块eg:elementUi
寻找依赖模块eg:elementUi --> chunk块
入口index.js --> 寻找依赖模块eg:less
寻找依赖模块eg:less --> chunk块
chunk块 --> less打包成css
chunk块 --> js打包成js
less打包成css --> 最后打包成一个bundle
js打包成js --> 最后打包成一个bundle
webpack 5个核心概念
graph TD
webpack --> entry
webpack --> output
webpack --> Loader
webpack --> plugin
webpack --> mode
1.webpack基础知识点
1.build打包时相关说明
插件下载webpack和webpack-cli
"scripts": {
//表示webpack会以./src/index.js为入口开始打包 打包后输出到./build/build.js,整体打包环境为development
"build":"webpack ./src/index.js -o ./build/build.js --mode=development"
},
2.loader处理图片相关问题
//处理图片资源
//问题:处理不了html中的img
{
test:/\.(jpg|png|gif)$/,
//使用一个loader
//下载 url-laoder file-loader
loader:'url-loader',
options:{
//图片小于8kb,就会被base64处理
//优点:减少请求数量(减轻服务器压力)
//缺点:图片体积会更大(文件请求速度更慢)
limit:8*1024,
//问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是common.js
//解析式会出问题:[object Module]
//解决:关闭url-loader的es6模块化,使用common.js
esModule:false,
//给图片重命名
//[hash:10]取图片hash的前10位
//[ext]去文件原来扩展名
name:'[hash:10].[ext]',
outputPath:'imgs',//表示输出到imgs目录下
}
}
//解决方式:
{
test:/\.html$/,
//处理html文件的img图片(负责引入img,从而被url-laoder进行处理)
loader:'html-loader'
}
3.打包其他资源
//打包其他资源(除了css/js/html 等资源 以外的资源)
{
//排除css/js/html等资源
exclude:/\.(css|js|html)$/,
laoder:'file-loader'
}
4.webpack-dev-server
//开发服务器 devServer 用来自动化(自动编译,自动打开浏览器,自动刷新浏览器)
//特点:指挥在内存中打包,不会有任何输出
//配置1
devServer:{
contentBase:path.resolve(__dirname,'build'),//项目构建后的路径
compress:true,//启动zip压缩
port:3000,
open:true,//自动打开浏览器
}
//配置2
devServer: {
static: {
//给出静态资源文件的路径
directory: path.join(__dirname, "dist"),
},
compress: true, //启动压缩
port: 9000, //端口号
hot: true, //热启动
},
5.构建环境的介绍
- 开发模式中
代码自动编译,自动打开浏览器,自动刷新,让代码本地调试提高开发效率 - 生产模式中(
让代码要快,平稳)
(1) css-->js做分离
(2) 代码的压缩
(3) 样式和JS兼容性问题
6.对css进行剥离 mini-css-extract-plugin
7. css兼容性处理
//webpack.config.js中
//postcss-loader 默认是生产环境的设置
//如果在development模式下生效 则需设置环境变量 process.enb.NODE_ENV = 'development'
// 兼容性postcss-loader 要放在less-loader上面 注意顺序
module:{
rules:[
{
test:/\.css$/,
use:[
MiniCssExtractPlugin.loader,//剥离css时使用的loader
'css-loader',
//1.css兼容性处理:postcss --> postcss-loader postcss-preset-env
//2.postcss-preset-env帮postcss找到packge.json中browserslist里面的配置,
//通过配置加载指定的css兼容性样式
//使用loader的默认配置直接这样写
//'postcss-loader'
//需要修改loader配置则需要下面方式 以对象格式
{
loader:'postcss-loader',
options:{
ident:'postcss',
plugins:()=>{
//postcss插件
require('postcss-preset-env')()
}
}
},
'less-loader'
]
}
]
}
//在package.json中
{
"browserslist":{
//开发模式下 需要在webpack.config.js中设置环境变量
// process.env.NODE_ENV = 'development'
"development":[
"last 1 chrome version",//表示兼容最近的chrome版本
"last 1 firefox version",//表示兼容最近的firefox版本
"last 1 safari version",//表示兼容最近的safari版本
],
//生产环境下
// process.env.NODE_ENV = 'production'
"production":[
">0.2%",//表示兼容大于99.8%的浏览器
"not dead",//不要已经死的浏览器 eg:ie10
"not op_mini all" //表示不要 op_mini 这种浏览器
]
}
}
8.压缩css(optimize-css-assets-webpack-plugin)
9.语法检查 eslint
//使用airbnb模式
//需要安装
//yarn add eslint eslint-loader -D
//yarn add eslint-config-airbnb-base eslint-plugin-import -D
//在webpack.config.js中
rules: [
{
test: /\.js$/,
exclude: /node_modules/, // 对node_modules不检查
enforce:'pre',//表示优先执行
loader: "eslint-loader",
options: {
// 自动修复eslint错误
fix: true,
},
},
],
//在 .eslintrc.js或packge.json中
"eslintConfig": {
"extends":"airbnb-base"
}
10.js兼容性处理
注意:使用corejs后就不需要使用第二种@babel/polyfill 了
//使用 1,3两步即下面的配置 做兼容性处理比较好
rules: [
//js兼容性处理 babel-loader @babel/core @babel/preset-env
//! 1.基本js的兼容性处理 @babel/preset-env (问题:只能转换基本语法,如promise不能转换)
//! 2.全部js的兼容性处理 @babel/polyfill (问题:我只需要解决部分兼容性处理,但是将所有兼容性代码全部引入,体积太大)
//!3.需要按需加载 做兼容性处理 --> corejS (用的多)
// 安装 > yarn add core-js -D
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
//指示babel做怎样的兼容性处理
presets: [
[
"@babel/preset-env",
//使用core.js
{
//按需加载
useBuiltIns: "usage",
//指定core-js版本
corejs: {
version: 3,
},
//指定兼容性做到那个版本浏览器
targets: {
chrome: "60",
firefox: "60",
ie: "9",
safari: "10",
edge: "17",
},
},
],
],
},
},
],
//@babel/polyfill的使用方式 在index.js中直接引入即可
import "@babel/polyfill";
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("zhixing");
resolve();
}, 1000);
});
11.html和js的压缩
1.js压缩只需要设置 mode:'production'//生产模式
2.js压缩2(有兼容性,但是多进程快)
const WebpackParallelUglifyPlugin = require("webpack-parallel-uglify-plugin");
plugins:[
new WebpackParallelUglifyPlugin({
uglifyJS: {
output: {
// 最紧凑的输出
beautify: false,
// 删除所有的注释
comments: false,
},
warnings: false,
compress: {
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
},
},
}),
]
3.html压缩
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "./public/index.html"),
//html压缩加以下配置
minify:{
collapseWhitespace:true,//移除空格
removeComments:true//移除注释
}
})
],
12.生产环境基本配置
注意:以下几条
- 先执行eslint-loader再执行babel-loader
graph TD
生产环境配置总览 --> css处理
生产环境配置总览 --> js处理
生产环境配置总览 --> 图片处理
生产环境配置总览 --> html处理
生产环境配置总览 --> 其他文件处理
graph TD
css处理 --> .css
css处理 --> .less
css处理 --> .scss
css处理 --> postcss-loader兼容性处理
css处理 --> 压缩css
graph TD
js处理 --> eslint检查eslint-loader(用的比较多airbnb检查)
js处理 --> js兼容性处理balbel-loader
js处理 --> js压缩(设置mode:production)
graph TD
其他文件处理 --> 使用exclude排除不需要处理的文件
其他文件处理 --> 使用file-loader
wenpack优化配置
graph TD
wenpack优化流程 --> 开发环境性能优化
开发环境性能优化 -->构建速度优化
构建速度优化 --> HMR
开发环境性能优化 -->代码调试优化
代码调试优化 --> source-map
wenpack优化流程 --> 生产环境性能优化
生产环境性能优化 -->构建速度优化.
构建速度优化. --> oneOf
构建速度优化. --> babel缓存
构建速度优化. --> 多进程打包
生产环境性能优化 -->代码运行性能优化
代码运行性能优化 ---> 文件缓存contenthash
代码运行性能优化 ---> treeshaking
代码运行性能优化 ---> 代码分割
代码运行性能优化 ---> 懒加载/预加载
代码运行性能优化 ---> pwa
代码运行性能优化 ---> externals
代码运行性能优化 ---> dll
HRM功能(js需要手动加支持HRM功能的代码)
注意:HRM功能只能对非入口js文件
/*
HRM: hot-module-replacement 热模块替换
作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块)极大提升构建速度
使用方式:在devServer开启HRM功能
1.样式文件:可以使用HRM功能,因为style-loader内部实现了(所以开发模式使用style-loader,生产模式使用css分割loader)
2.js文件:默认不能使用HRM功能 --> 需要修改js,添加支持HRM功能的代码
3.HTML文件:默认不能使用HRM功能,同时会导致问题:html文件不能热更新了 -(一般html不考虑做HRM功能,因为只有一个文件)
*/
devServer: {
//开启HRM功能
hot: true,
},
//js文件支持HRM功能
//eg:建一个print.js文件导出print函数
//在index.js中
import print from './print'
if(module.hot){
//一旦module.hot为true 说明开启了HRM功能,--> 让HRM功能代码生效
module.hot.accept('./print.js',()=>{
//方法会监听print.js文件的变化,一旦变化,其他模块不会重新打包
//会执行回调函数
print();
})
}
2. source-map(提供后见后代码映射关系,构建代码出错,通过映射可以找到源代码错误)
//内联和外部的区别:外部生成了文件,内联没有。内联构建速度更快
//0. source-map //外部 (错误代码的准确信息,源代码的位置)
1.inline-source-map //嵌到js中 内联(只生成一个内联的source-map)(错误代码的准确信息,源代码的位置)
2.hidden-source-map //外部 (错误代码的错误原因,不提示源代码的位置)
3.eval-source-map //内联(每个文件都生成对应的source-map,都在eval)
4.nosources--source-map //外部 (错误代码的准确信息,但没有任何源代码的位置)
5.cleap-source-map //外部 (错误信息只能精确到行)
6.cleap-module-source-map //外部
- 速度快:eval>inline>cheap>eval-cheap-source-map>eval-source-map
- 调试友好:source-map>cleap-module-source-map
开发环境:速度快,调试友好 综上所述用eval-source-map生产环境:源代码隐藏,内联代码体积增大所以要排除综上所述用source-map
3.oneOf优化
module: {
rules: [
{
test: /\.js$/,
enforce:'pre',//表示优先执行
loader:'eslint-loader',
},
//使用oneOf以下loader只会匹配一个
//注意:不能有两个配置处理同一种类型文件(如下babel-loader和eslint-loader都有处理.js文件)
//所以把同时处理.js文件的 eslint-loader提出去先执行,再执行oneOf里面的类型文件
oneOf:[
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, "css-loader",'less-loader'],
},
{
test: /\.js$/,
loader:'babel-loader',
},
]
],
},
4.缓存优化
- babel缓存优化(设置
cacheDirectory:true)
优点:让第二次打包构建速度更快
假设有100个文件 babel做翻译时只有一个文件变,那其中99个文件应该保持不变直接使用缓存
module:{
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
//指示babel做怎样的兼容性处理
presets: [
[
"@babel/preset-env",
//使用core.js
{
//按需加载
useBuiltIns: "usage",
//指定core-js版本
corejs: {
version: 3,
},
//指定兼容性做到那个版本浏览器
targets: {
chrome: "60",
ie: "9",
},
},
],
],
//开启babel缓存
//第二次构建时,会读取之前的缓存
cacheDirectory:true
},
},
]
}
- 文件资源缓存(
contenthash让代码上线运行缓存更好用)
(1)通过hash值设置不同文件名来处理文件缓存
hash:每次webpack构建时都会生成唯一的hash值
问题:因为js和css同时使用一个hash值,如果从新打包会导致所有缓存失效
chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
问题:js和css hash值还是一样的
contenthash:根据文件内容生成hash值,不同文件hash值一定不一样
优点:比如css和js只有改变的文件才会生成新的hash值文件名
//1.通过给打包后文件设置hash值来处理缓存问题
output:{
//filename:'bundle.[hash:10].js',
filename:'bundle.[contenthash:10].js',
path:resolve(__dirname,'./src/index.js')
}
plugins:[
//对css文件取hash值处理缓存
new MiniCssExtractPlugin({
//filename:'css/index.[hash:10].css'
filename:'css/index.[contenthash:10].css'
})
]
14.treeshaking(去除业务中没有使用的代码)
- 前提:
- 使用esModule模块化
- 设置mode:profuction
- 在package.json中设置
//"sideEffexts":false 所有代码都可以进行tree shaking
//问题:可能会把css / @babel/polyfill(没有用到的副作用文件)干掉
//"sideEffexts":["*.css","*.less"]表示css和less文件不会进行treeshaking (文件不会被干掉)
{
"sideEffexts":["*.css"]
}
15.代码分割(code split)
//第一种 多入口方式
module.exports = {
entry: {
index: path.join(__dirname, "./src/index.js"),
tree: path.join(__dirname, "./src/tree.js"),
},
output: {
path: path.resolve(__dirname, "./dist"),
filename: "js/[name].[contenthash:10].js",
},
}
//第二种 使用splitChunks (工程化里面有详细配置)
optimization:{
splitChunks:{
chunks: "all",
}
}
// 通过js代码,让某个文件被单独打包成一个chunk
//import 动态导入语法:能让某个文件单独打包
import("./tree")
.then((result) => {
console.log("文件加载成功" + result);
})
.catch(() => {
console.log("文件加载失败");
});
16.js文件懒加载和预加载
//通过import异步方式 点击或某个动作的时候再加载此文件
//1.懒加载 当文件需要使用时才加载
//2.预加载(兼容性差,慎用) prefetch 会在使用之前,提前加载js文件 (等其他资源加载完毕,浏览器空闲了再偷偷加载)
//3.正常加载 可认为并行加载 (同一时间加载多个文件)
document.onclick = function (){
import(/*webpackChunkName:'test',webpackPrefetch:true*/"./tree").then(({ mul }) => {
console.log("文件加载成功" + mul(3, 12));
});
}
17.PWA渐进式网络开发应用程序(让文件离线也能访问)
注意:使用es6语法时要用babel-loader处理
/*
PWA:渐进式网络开发应用程序`(离线也能访问)
workbox --> yarn add workbox-webpack-plugin -D
问题:eslint 不认识 window navigator全局变量
解决:需要修改package.json中.eslintConfig配置
"env":{
"browser":true//支持浏览器全局变量 要支持node则设置node:true
}
*/
//1.在webpack.config.js中
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
module.exports = {
plugins:[
//帮我们生成一个serviceWorker配置文件
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true, //帮助serviceWorker快速启动
skipWaiting: true, //删除旧的 serviceWorker
}),
]
}
//在入口的js文件中
// 注册serviceWorker
//处理兼容性
if('serviceWorker' in navigator){//判断'serviceWorker' 在没在navigator上
window.addEventListener('load',()=>{//等全局资源加载好 再去注册serviceWorker
navigator.serviceWorker.register('/service-worker.js').then(()=>{
console.log('sw注册成功')
}).catch(()={
console.log('sw注册失败')
})
})
}
18.多进程打包
进程启动大概为600ms。进程通信也有开销。只有工作消耗事件比较长 才需要多进程打包
//1.安装 yarn add thread-loader -D
//thread-loader 要给babel-laoder用时 为以下配置
//当给其他loader类型文件用时,同理放在其他loader左面,最后执行
//在webpack.config.js中
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
//开启多进程打包
{
loader: "thread-loader",
options: {
workers: 2, //进程为2
},
},
{
loader: "babel-loader",
options: {},
},
],
},
],
19.externals(防止将某些包打包到bundle中) 不需要打包
注意:当忽略掉某些包后 要用cdn或本地方式通过script标签将这些忽略的包引入在index.html中
//webpack.config.js
module.exports = {
externals:{
//拒绝Jquery被打包进来
jquery:'jQuery'
}
}
20.dll(动态连接库) 需要打包一次
作用:和externals差不多提取要打包的库,防止重复打包
- 建一个webpack.dll.js (npm 打包执行一次此文件)
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
// dll文件存放的目录
const dllPath = 'public/vendor';
module.exports = {
entry: {
// 需要提取的库文件
vendor: [
'vue',
'vue-router',
'vuex',
'axios',
'vue-gemini-scrollbar',
'element-ui',
'less'
],
chartVendor: ['p-charts', 'less']
},
output: {
path: path.join(__dirname, dllPath),
filename: '[name].dll.js',
// vendor.dll.js中暴露出的全局变量名
// 保持与 webpack.DllPlugin 中名称一致
library: '[name]_[hash]' //打包库暴露出去的内容叫什么名字
},
plugins: [
// 清除之前的dll文件
new CleanWebpackPlugin(['*.*'], {
root: path.join(__dirname, dllPath)
}),
// 设置环境变量
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: 'production'
}
}),
// manifest.json 描述动态链接库包含了哪些内容
new webpack.DllPlugin({
path: path.join(__dirname, dllPath, '[name]-manifest.json'),
// 保持与 output.library 中名称一致
name: '[name]_[hash]',
context: process.cwd()
})
]
};
执行webpack.dll.js打包后的文件目录结构
- 在webpack.config.js中
//1.安装 yarn add add-asset-html-webpack-plugin -D
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
const webpack = require('webpack')
module.exports = {
plugins:[
//告诉webpack哪些库不参与打包,同时使用时名称也得变
new webpack.DllReferencePlugin({
context: process.cwd(),
//manifest: require('./public/vendor/vendor-manifest.json')
manifest:path.resolve(__dirname,'./public/vendor/vendor-manifest.json')
}),
//告诉webpack哪些库不参与打包,同时使用时名称也得变
new webpack.DllReferencePlugin({
context: process.cwd(),
//manifest: require('./public/vendor/chartVendor-manifest.json')
manifest:path.resolve(__dirname,'./public/vendor/chartVendor-manifest.json')
}),
//将某个文件打包输出去,并在html中自动引入该资源
new AddAssetHtmlPlugin({
// dll文件位置
//filepath: path.resolve(__dirname, './public/vendor/*.js'),
filepath: path.resolve(__dirname, "./public/vendor/vendor.dll.js"),
// dll 引用路径
publicPath: './vendor',
// dll最终输出的目录
outputPath: './vendor'
}),
]
}
webpack配置详解
1.entry
//单入口 默认打包文件名 main.js
entry:path.join(__dirname, "./src/index.js")
//多入口
entry:{
index:path.join(__dirname, "./src/index.js"),
add:path.join(__dirname, "./src/index.js"),
indexMore:['jquery','vue','lodash'],//表示将多个文件打包成一个入口
}
2.output
output:{
filename:'js/[name].js',//文件名称(指定名称+目录)
path:path.resolve(__dirname,'build'),//输出文件公共目录
//所有资源引入公共路径前缀 --> 'imgs/a.jpg' -->转成 '/imgs/a.jpg'
publicPath:'/',
chunkFilename:'js/[name]_chunk.js',//非入口chunk的名称
library:'[name]',//暴露打包库文件的名称供使用
//libraryTarget:'window',//变量名添加到window下 browser(浏览器)
//libraryTarget:'global',//变量名添加到global下 node端
libraryTarget:'commonjs',//表示后面以commonjs语法引入
}
3.module
module:{
rules:[
{
test:/\.css$/,
//多个loader使用use
use:['style-loader','css-laoder']
},
{
test:/\.js$/,
//排除node_modules下的js文件
exclude:/node_modules/,
//只检查src下的js文件
include:path.resolve(__dirname,'src'),
enforce:'pre',//pre优先执行 post延后执行 不写中间执行
//单个laoder使用laoder
laoder:'eslint-loader'
options:{}
},
{
//只会生效一个
oneOf:[]
}
]
}
4.resolve 解析模块规则
resolve:{
//配置解析模块路径别名:优点简写路径 缺点写路径没有提示
alias:{
_c:path.resolve(__dirname,'src/commonent'),
_a:''
},
//配置省略文件路径后缀名的规则
//不写文件名后缀时 先找.js再找.json没有再找.css
//缺点:文件名如果一致就会有问题
extensions:['.js','.json','.css'],
//告诉webapck解析模块去找那个目录
modules:[path.resolve(__dirname,'../node_modules','node_modules')]
}
5.devServer
devserver:{
//运行代码目录
//contenBase:path.resolve(__dirname,"build"),
//或 运行代码目录
static:{
directory:path.resolve(__dirname,"build")
},
//监视ContentBase目录下所有文件,一旦文件变化就会reload
watchContentBase:{
ignored:/node_modules/,//不监视node_modules
},
compress:true,//启动压缩
port:3000,//启动端口号
host:'localhsot',//指定域名
open:true,//打开浏览器
hot:true,//开启HMR
//不需要启动服务器日志信息
clientLogLevel:'none',
quiet:true,//除了一些基本信息,其他内容不显示
overlay:false,//如果出错不要全屏提示
//服务器代理 --> 解决开发环境跨域问题
proxy:{
//一旦devServer(5000)服务器收到/api/xx/的请求,就会把请求转发到另外一个服务器(3000)
'/api':{
target:'http://localhost:3000',
//发送请求时,请求路径从写 将/api/xx 转换成 /xx (去掉/api)
pathRewrite:{
'^/api':''
}
}
}
}
6.optimization
optimization: {
splitChunks: {
chunks: 'all'
// 默认值,可以不写~
},
// 将当前模块的记录其他模块的 hash 单独打包为一个文件 runtime
// 解决:修改 a 文件导致 b 文件的 contenthash 变化
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
},
minimizer: [
// 配置生产环境的压缩方案:js 和 css
new TerserWebpackPlugin({
// 开启缓存
cache: true,
// 开启多进程打包
parallel: true,
// 启动 source-map
sourceMap: true
})
]
}
webpack5与webpack4的区别
- webpack5会默认入口和输出目录
entry和output可以不写 - webpack5会自动
treeshaking功能更强大,打包的包更小 - webpack5中commonJS也能进行
treeshaking - 更多详情见