一、 安装
1.
npm init -y
初始化目录2.
npm i webpack webpack-cli -D
安装webpack。可以执行
npx webpack ./src/main.js --mode=production
或--mode=development
进行打包指定的文件
二、 基本内容
配置
webpack.config.js
,后使用npx webpack
可直接打包 配置css-loader,安装npm i css-loader style-loader -D
,然后在配置文件的module rules中添加一个对象,test填写正则,匹配css文件,use表示要使用的loader。 处理less,npm i less less-loader -D
处理scss,
npm i sass sass-loader -D
处理stylus,
npm i stylus-loader -D
···
sass:不用写大括号和分号
{};
,stly:冒号都不用写
图片资源可以不用配置loader就可以加载,如果要设置小图片转base64,则配置rules,type:'asset',parser等。图片资源输出目录,则配置generator,filename使用绝对路径.
打包前清空dist目录,则在output中配置clean:true
处理字体图标资源,和图片资源类似,不过type配置为asset/resource,以及没有了转化base64的配置,音视频等同理。
js代码格式检查,配置eslint。
- .eslintrc.js配置文件示例
css
module.exports={
parserOptions:{
ecmaVersion:6,
sourceType:'module',
ecmaFeatures:{
jsx:true
}
},
rules:{
semi:'warn'//禁止分号,'off'|0,'warn'|1,'error'|2,分别代表提示的三种等级。
},
extends:[]
}
-
eslint可继承的规则如:
eslint:recommended
,plugin:vue/essential
,react-app
-
安装
npm i eslint eslint-webpack-plugin -D
-
在配置文件中
const ESLintPlugin=require('webpack-eslint-plugin')
,然后在plugins中写详细配置
arduino
new ESLintPlugin({
context:path.resolve(__dirname,'src')//配置要检查的目录
})
- 配置.eslintignore来忽略dist等,主要使用vscode编辑器插件eslint时,在dist目录也会提示报错信息。
js代码兼容,使用babel
- 配置文件babel.config.js或.babelrc.js,后缀写json和不写都是可以的。
- 需要下载的包有babel-loader,@babel/core以及预设包 @babel/preset-env
- 在webpack配置文件中给js配置babel-loader,options可以写在loader下面,也可以单独写在babel配置文件中,主要内容是presets预设
处理html,装包
html-webpack-plugin
,配置中可使用template指定使用的html模板。开发服务器,安装
webpack-dev-server
,并在webpack配置文件中增加devServer配置,通过npx webpack serve
启动服务。
三、分开发模式和生产模式
- 新建config文件夹,将webpack.config.js分成两个文件,一个webpack.dev.js,一个webpack.prod.js
- 注意绝对路径(path.resolve(__dirname,'../src'))
- 开发模式的配置不需要输出路径
- 运行方式
npx webpack serve --config ./config/webpack.dev.js
- 生产模式的配置不需要devServe,mode改为production
- 打包
npx webpack --config ./config/webpack.prod.js
- 指令配置,在package.json中,增加script指令
json
"start":"npm run dev"
"dev":"webpack serve --config ./config/webpack.dev.js",
"build":"webpack --config ./config/webpack.prod.js"
四、
- css文件原本是一同打包进css的,现需要单独打包成css文件,装包
mini-css-extract-plugin
2.在配置文件中引入,并将style-loader改成MiniCssExtractPlugin.loader
,最后在plugins中调用一下,可以指定filename
css兼容性处理
- 使用postcss,需要下载的包有
postcss postcss-loader postcss-preset-env
- 在配置文件的css-loader后面增加配置
css
{
loader:'postcss-loader',
options:{
postcssOptions:{
plugins:[
'postcss-preset-env'
]
}
}
}
- 在package.json中备注需要兼容的浏览器列表
bash
"browserslist":[
"ie >= 8"
## 实际开发中常用的写法:
"last 2 version",
"> 1%",
"not dead"
]
- 可以将css的loader封装成一个方法,在配置文件中
javascript
function getStyleLoader(pre){
return [
MinicssExtraxtPlugin.loader,
"css-loader",
{
loader:'postcss-loader',
options:{
postcssOptions:{
plugins:[
'postcss-preset-env'
]
}
}
},
pre //传入后面使用的其他loader,如sass-loader
].filter(Boolean) //如果pre是undefined,则过滤掉
}
- css压缩,安装
css-minimizer-webpack-plugin
,使用方法可以同其他插件一样,也可以在webpack中新增配置项(1) - html-webpack-plugin本身会压缩html文件
5.高级配置
- sourceMap
开发模式,在webpack配置文件中加入
devtool:'cheap-module-source-map'
生产模式,值为
source-map
提升打包速度
- hotModuleReplacement 仅开发模式
实际上就是在DevServer中加入
hot:true
,也是默认值 对于js,可以在main.js中加入下面代码,对于使用了vue-loader或react-hot-loader的项目,默认已经有这些功能了
arduino
if(module.hot){
// 是否有模块热替换的功能
module.hot.accept('./js/count')
}
- oneOf 开发生产皆可
对于loader,会遍历每一个rule项
使用oneOf,当匹配到一个rule项,就不会继续向下匹配了
css
rules:[
{
oneOf:[...]
}
]
- include、exclude 开发生产皆可
在处理js的rule中写
javascript
{
test:/\.js$/,
// 不能同时用
// exclude:/node_modules/,//表示不处理node_module中的文件
include:path.resolve(__dirname,'../src') ,//表示只处理src里的文件
loader:'babel-loader',
},
- cache
主要是缓存eslint检查和babel编译结果
babel写法,在loader处配置options
arduino
options:{
cacheDirectory:true,//开启babel缓存
cacheCompression:false,//关闭缓存压缩
}
对于eslint,则在插件调用的地方加入参数
lua
cache:true,
cacheLocation:path.resolve(__dirname,'../node_modules/.cache/eslintcache')
- thead 对js多进程打包 开发生产皆可
对js处理的主要是eslint,babel,terser,每个进程启动大概需要600ms时间,建议大项目使用 获取cpu核数
ini
const os=require('os')
const threads=os.cpus().length
需要下载thread-loader包
在babel-loader出,增加loader
css
{
loader:'thread-loader',
options:{
works:threads
}
}
处理terser,需要现在配置文件中引入插件
javascript
const TerserWebpackPlugin=require('terser-webpack-plugin')
...
new TerserWebpackPlugin({
parallel:threads
})
处理eslint,直接在调用插件那里加入参数threads
webpack中可以加入一项optimization:{minimizer:[...内容同plugins,放一些压缩的插件]}
-
tree shaking 一种说法,实际就是只打包引入的文件,如
import {count} from 'count'
,依赖es6的模块化 -
babel会产生很多辅助代码,这里避免产生重复的辅助代码,需要安装包
@babel/plugin-transform-runtime
,然后在写babel-loader 的地方添加options配置项plugins:[@babel/plugin-transform-runtime]
-
图片压缩 (这部分操作报错的原因暂未实际测试过)
需要下载插件
image-minimizer-webpack-plugin
,imagemin
还需要下载的包,压缩方式有有损压缩和无损压缩,下载的包也有所不同
无损: imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo
有损: imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo
然后在webpack配置中引入插件
csharp
const ImageMinimizerPlugin=require('image-minimizer-webpack-plugin')
...
//在plugins中配置
new ImageMinimizerPlugin({
minimizer:{
implementation:ImageMinimizerPlugin.imageminGenerate,
options:{
plugins:[
["gifsicle",{interlaced:true}],
["jpegtran",{progressive:true}],
["optipng",{optimizationLevel:5}],
["svgo",{
plugins:[
"preset-default",
"prefixIds",{
name:"sortAttrs",
params:{
xmlnsOrder:"alphabetical"
}
}
]
}]
]
}
}
})
安装无损或有损的包时可能会报错。。。 安装成功,打包也可能会报错,提示缺少jpegtran.exe和optipng.exe,将对应exe放到nodemodules中对应文件夹后再次打包即可。
多入口,code split
- entry配置为对象,一个模块名对应一个路径,如main:'./main.js',output的filename配置为
[name].js
,则打包后会生成对应名称的js - 对于不同文件引用的公共方法,会都打包一份,如果要将公共方法提出来,则配置optimization的splitChunks
ruby
optimization:{
splitChunks:{
chunks:'all',//对所有模块进行分割
// 一些默认值
// minChunks:1,//至少被引用一次才被分割
//maxAsyncRequests:30,//并行加载的最大文件数,超过则合并至其他文件
// minSize:2000,//分割代码的最小大小
// minRemainingSize:0,//类似minSize,保证提取的文件大小不为0
// maxInitialRequests:30,//入口js最大并行请求数量
// enforceSizeThreshold:50000,//超过50kb强制单独打包
// cacheGroups:{ // 分组,哪些模块要打包到一个组
// defaultVendors:{//组名
// 这里面可以写上面的那些配置,表示覆盖上面的那些配置
// test:/[\\/]node_modules[\\/]/,//需要打包到一起的模块
// priority:-10,//权重,越大越搞
// name:'chunk-react',//打包出来的js文件的名称
// reuseExistingChunk:true,//如果当前chunk已包含从主bundle中拆分出的模块,则被重用,而不是生成新的
// },
//表示把react和antd相关的代码单独打包为一个js文件
react:{
test:/[\\/]node_modules[\\/]react(.*)?[\\/]/,
name:'chunk-react',
priority:40
},
antd:{
test:/[\\/]node_modules[\\/]antd[\\/]/,
name:'chunk-antd',
priority:40
},
// }
// 修改配置
cacheGroups:{
default:{
minSize:0,//我们定义的文件太小了,所以要改到最小的文件体积
minChunks:2,
priority:-20,
reuseExistingChunk:true
}
}
}
}
- 按需加载,动态导入
比如,点击按钮触发的事件不需要一开始就加载,可以点击之后再加载
javascript
ducument.getElementById('btn').onclick=function(){
//import动态导入,会将动态导入的代码文件分割(单独打包为一个文件),在需要使用的时候自动加载
import('./count').then(res=>{
//加载成功
}).catch(err=>{
//加载失败
})
}
单入口
- 只需要配置optimization的splitChunks的
chunks:'all'
- 对于打包后的文件命名,首先需要在import中这样写
import(/* webpackChunkName: "math" */'./js/btn'),then(res=>{
, 然后需要在output中配置chunkFilename:'static/js/[name].js'
统一命名
- output的
filename:'static/js/[name].js'
,chunkFilename:'static/js/[name].chunk.js'
, type:asset的资源命名assetModuleFilename:'static/media/[hash:10][ext][query]'
, 还有css的命名类似
preload,prefetch
- preload立即加载,prefetch空闲时加载,兼容性较低需注意
- 装包
preload-webpack-plugin
,报错则@vue/preload-webpack-plugin
使用插件
php
const PreloadWebpackPlugin=require('@vue/preload-webpack-plugin')
...
new PreloadWebpackPlugin({
rel:'preload',
as:'script'
//如果是prefetch,则只用写rel:'prefetch',不用写as
})
网络缓存,打包后hash值
由于文件1存在对文件2的引用,文件2的文件发生变化,hash也变化,导致文件1不得不变化,为了解决这个问题, 将引用时文件hash单独存放在一个runtime文件,文件1在runtime文件查找文件2的引用名,这样文件1不用变化,实现方法如下
javascript
//在optimization中加入
runtimeChunk:{
name:(entrypoint) => `runtime~${entrypoint.name}.js`
}
然后filename的hash改成contenthash
js兼容问题
- babel能处理箭头函数,点点点运算符等,但无法处理async,promise,includes等,这里使用code-js
- 安装
npm i core-js
, - 全量加载在main.js中
import 'core-js'
- 按需加载,在main.js中
import "core-js/es/promise"
- 自动加载,在babel的配置文件中配置,无需在main.js中引入,注意多了一层中括号,注意这个兼容范围跟package.json中的browserslist有关
lua
presets:[['@babel/preset-env',{
useBuiltIns:'usage',//按需加载自动引入
corejs:3//corejs版本
}]]
pwa 离线访问
- 装包
workbox-webpack-plugin
- 配置文件中,
javascript
const WorkboxPlugin=require('workbox-webpack-plugin')
...
new WorkboxPlugin.GenerateSW({
//这些选项帮助快速启用serviceworkers
//不允许遗留任何旧的
clientsClaim:true,
skipWaiting:true
})
- 需要在main.js中加入代码
javascript
if("serviceWorker" in navigator){
window.addEventListener("load",()=>{
navigator.serviceWorker.register('/service-work.js').then(registration=>{
console.log("SW registered:"+registration)
}).catch(registrationError=>{
console.log("SW registration failed:"+registrationError)
})
})
}
注意打开需要以dist为根服务器地址,不然会找不到资源
这里可以用
npm i serve -g
,然后serve dist
总结
- 提升开发体验
· source map,让开发或上线的代码有更准确的错误提示 - 提升webpack打包构建速度
· HotModuleReplacement,开发时只更新修改的部分
· OneOf,让文件一旦被某个loader处理了,就不继续遍历,提升打包速度
· Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。
· Cache对babel和eslint处理结果进行缓存,让第二次打包速度更快
· thead,多进程处理babel和eslint任务
3.减少代码体积
· Tree Shaking 剔除了没有使用的多余代码,让代码体积更小
· @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小,
· Image Minimizer对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需 要了。本地项目静态图片才需要进行压缩。) - 优化代码运行性能
· code split对代码进行分割成多个js 文件,从而便单个文件体积更小,并行加载js速度更快,并通过 import 动态导入语法进行按需 加载,从而达到需要使用时才加载该资源,不用时不加载资源。
· Preload、prefetch 对代码进行提前加载,等未来需要便用时就能直接使用,从而用户体验更好
· network cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。
· core-js 对js 进行容性处理,让我们代码能运行在低版本浏览器。
· PWA能让代码离线也能访问,从而提升用户体验。
react配置
- eslintrc文件,还需要安装
eslint-config-react-app
css
extends:['react-app'],
parserOptions:{
babelOptions:{
presets:[
//解决页面报错问题
['babel-preset-react-app',false],
"babel-preset-react-app/prod"
]
}
}
- babel-loader,test:/.jsx?$/
- babel需要安装预设包
babel-preset-react-app
,babel.config.js,react-app包含了core-js等,无需多余配置
vbnet
presets:["react-app"]
注意使用babel-preset-react-app时,打包会报错,需要安装
cross-env
,然后配置package.json中的dev指令
json
"dev":"cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js"
- 安装
npm i react react-dom
- 在webpack配置中加入
css
resolve:{
//自动补全文件后缀
extensions:['.jsx','.js','.json']
}
然后可以运行简单的react项目。 6. react的hmr
typescript
devServer中hot:true
npm i -D @pmmmwh/react-refresh-webpack=plugin react-refresh
配置文件中
1.const ReactRefreshPlugin=require('@pmmmwh/react-refresh-webpack=plugin')
2.babel-loader的options中加入
plugins:[
'react-refresh/babel'
],
3.在外层plugins中
new ReactRefreshPlugin()
- react devServer路由刷新404问题,需要在DevServer中加入配置
historyApiFallback:true
生产模式配置
favicon.icon的显示,将public中资源复制到dist,下载插件copy-webpack-plugin
css
const CopyPlugin=require('copy-webpack-plugin')
...
new CopyPlugin({
patterns:[
{
from:path.resolve(__dirname,'../public'),
to:path.resolve(__dirname,'../dist'),
globOptions:{
ignore:["**/index.html"]
}
}
]
})
合并生产和开发配置
在webpack.config.js中使用process.env.NODE_ENV获取当前是production还是development
sql
const isProduction=process.env.NODE_ENV==='production'
1. output路径区分
2. babel-loader的plugins:[
!isProduction&&"react-refresh/babel"
]
3. copy功能,MiniCssExtractPlugin在生产模式用
4.过滤数组用.filter(Boolean)
5. mode,devtool,minimizer
6. webpack配置的minimize:isProduction,表示是否需要压缩,为true的话minimizer选项才生效
使用antd的优化
因为antd使用的less,这里对less-loader做一些改造 在返回样式处理函数中
javascript
const getStyleLoader=(pre)=>{
...
pre&&{
loader:pre,
options:pre==='less-loader'?{
//antd自定义主题色
lessOption:{
modifyVars:{
"@primary-color":"#1da57a",
},
javascriptEnabled:true
}
}:{}
}
}
//注意main.js中应当引入antd的less样式
打包优化,splitChunk中进行分包,以及可以performance:false关闭性能分析,提升打包速度
vue编译
以vue3为例
- 安装vue-loader,并修改配置
javascript
const {VueLoaderPlugin}=require('vue-loader')
...
{
test:/\.vue$/,
loader:'vue-loader'
}
...
new VueLoaderPlugin()
- 安装vue-style-loader,并将style-loader改为vue-style-loader
- eslint配置
vbnet
root:true,
env:{
node:true
},
extends:["plugin:vue/vue3-essential","eslint-recommended"],
parserOptions:{
parser:"@babel/eslint-parser"
}
//需要下载包@babel/eslint-parser,eslint-plugin-vue
- babel配置
perl
presets:["@vue/cli-plugin-babel/preset"]
需要下载包@vue/cli-plugin-babel
- 然后运行会提示vue某环境变量未定义,这里修改一下webpack配置
javascript
const {DefinePlugin}=require("webpack")
...
//cross-env的环境变量是给打包工具使用的
//DefinePlugin的环境变量是给源代码使用的
new DefinePlugin({
__VUE_OPTIONS_API__:true,
__VUE_PROD_DEVTOOLS__:false
}}
resolve:{ extensions:['.vue','.js','.json']}
element-plus
- 按需导入,参照官网配置
- 配置路径别名,在resolve中添加配置
bash
alias:{
"@":path.resolve(__dirname:'../src')
}
原理
loader
- pre:前置loader,normal:普通,inline:内联,post:后置,相同优先级loader从下到上,从右到左执行。
javascript
{
enforce:'pre',//或'post'
test:/\.js$/,
loader:'loader1'
}
- 注意inline loader的写法
javascript
import Styles from 'style-loader!css-loader?modules!./style.css'
import Styles from '!style-loader!css-loader?modules!./style.css'//表示跳过normal loader
import Styles from '-!style-loader!css-loader?modules!./style.css'//表示跳过pre和normal loader
import Styles from '!!style-loader!css-loader?modules!./style.css'//表示跳过pre,post和normal loader
- 写一个loader
loader本质是一个函数
content是文件内容,map sourceMap,meta 别的loader传递的数据
javascript
//loader.js
module.exports=function(content,map,meta){
return content
}
//webpack.config.js
...
loader:'./loader/loader.js'
...
- 4种loader,同步、异步、raw、pitch
javascript
// 同步loader
// module.exports=function (content){
// console.log(content)
// return content
// }
module.exports=function (content,map,meta){
// callback第一个参数表示是否有错误
// 第二个参数是处理后的内容
// 第三个
// 第四个给下一个loader的参数
console.log(content)
//如清除console.log的写法
content.replace(/console\.log\(.*\);?/g,"")
this.callback(null,content,map,meta)
}
===
javascript
// 异步loader
module.exports=function (content,map,meta){
console.log('异步loader')
const callback=this.async()
setTimeout(()=>{
callback(null,content,map,meta)
},1000)
}
======
javascript
// raw loader,接收的content是buffer数据
module.exports=function (content){
console.log(content)
// this.callback(null,content,map,meta)
return content
}
module.exports.raw=true
// 另一种写法
// function loaderName(content){
// return content
// }
// loaderName.raw=true
// module.exports=loaderName
======
javascript
// pitch loader
module.exports=function (content){
console.log(content)
// this.callback(null,content,map,meta)
return content
}
// pitch 方法的执行顺序在loader解析前,
// 如use:['loader1','loader2','loader3'],pitch方法执行顺序loader1>loader2>loader3,然后再执行loader解析
// 如果pitch2有return,则不会执行pitch3,loader2,loader3,只会执行pitch loader2前面的loader1
module.exports.pitch=function(){
}
- 一些loader中的api
this.async异步回调,返回this.callback,const callback=this.async()
this.callback(err,content,sourceMap?,meta?)
this.getOptions(schema),获取loader的option
this.emitFile(name,content,sourceMap)产生一个文件 this.utils.contextify(context,request)返回一个相对路径 this.utils.absolutify(context,request)返回一个绝对路径
javascript
//schema.json:
{
"type":"object",
"properties":{
"author":{
"type":"string"
}
},
"additionalProperties":false
}
//additionalProperties表示不能新增字段
//loader.js
const schema=require('./schema.json')
...
const options=this.getOptions(schema)
console.log(schema.author)
实现简易babel-loader
- 安装
@babel/core @babel/preset-env
- 在webpack中配置对js使用自定义的loader文件
css
{
test:/\.js$/,
loader:'./loaders/babel-loader.js',
options:{
presets:['@babel/preset-env']
}
}
- 新建babel-loader.js文件,babel中的方法可以参考官网
javascript
const schema=require('./schema.json')
const babel=require('@babel/core')
module.exports=function(content){
const callback=this.async()
const options=this.getOptions(schema)
babel.transform(content,options,function(err,result){
if(err){
callback(err)
}else{
callback(null,result.code)
}
})
}
- schema配置
json
{
"type":"object",
"properties":{
"preset":{
"type":"array"
}
},
"additionalProperties":true
}
实现file-loader
- loader写法
javascript
const loaderUtils=require('loader-utils')
module.exports=function(content){
// 1.根据文件内容生成hash值文件名(借助webpack官方提供的处理文件名的方法)
const interpolateName=loaderUtils.interpolateName(this,"[hash].[ext][query]",{
content
})
console.log(interpolateName)
// 2.将文件输出
this.emitFile(interpolateName,content)
// 3.返回module.exports="文件路径(文件名)"
return `module.exports="${interpolateName}"`
}
module.exports.raw=true
javascript
{
test:/\.(png|jpe?g|gif)$/,
loader:'./loaders/file-loader.js',
type:'javascript/auto'//阻止webpack默认的asset去处理这些资源
},
实现style-loader
ini
module.exports=function(content){
//直接使用style-laoder只能处理样式,不能处理样式中引入其他资源的问题
//借助css-loader处理样式中引入其他资源的问题
//问题是css-loader暴露了一段js代码,style-loader需要执行js代码,得到返回值
const script=`
const styleEL=document.createElement('style');
styleEL.innerHTML=${JSON.stringify(content)};
document.head.appendChild(styleEL)
`
return script
}
- 基于以上代码,要处理css-loader处理后的代码,首先注释掉script等内容
kotlin
module.exports.pitch=function(remainingRequest){
// remainingRequest返回两段绝对路径,用!分隔,一个css-loader的,一个css的
// 1.绝对路径处理成两个相对路径
const relativePath= remainingRequest.split('!').map(absolutePath=>{
return this.utils.contextify(this.context,absolutePath)
}).join('!')
console.log(relativePath)
const script=`
import style from "!!${relativePath}"
const styleEL=document.createElement('style');
styleEL.innerHTML=style
document.head.appendChild(styleEL)
`
// 终止后面loader
return script
}
plugin 原理
- webpack打包过程会触发一系列tapable钩子事件
tap 可注册同步和异步钩子
tapAsync 回调异步
tapPromise promise方式异步
- 基本结构
javascript
class Plugin1{
constructor(options){
this.options=options
}
apply(compiler){
console.log('test apply')
compiler.hooks.environment.tap('TestPlugin',()=>{
console.log('test Env')
})
compiler.hooks.make.tap('TestPlugin',(compilation)=>{
debugger;
console.log(compilation)
compilation.hooks.seal.tap('Test Plugin',()=>{
console.log('seal')
})
})
}
}
// node 调试
/**
* "debug":"node --inspect-brk ./node_modules/webpack-cli/bin/cli.js"
* package.json添加debug的命令,代码需要调试的地方加入debugger;浏览器控制台会出现node图标,点击会看到调试数据,可用于观测compilation结构
*/