Webpack
五大模块
Entry
入口,webpack打包的入口,默认为src文件夹下的index.js
Output
出口,打包完成后输出的位置和命名
Loader
翻译官,webpack只能识别js和json文件,所以处理css,less,img等内容需要用到loader将其转换为webpack能识别的内容
Plugins
插件,将打包的东西按照自己的想法进行打包,比如说压缩等
Mode
模式,分为development开发模式和production生产模式,分别针对的是开发的时候自己打包用的和上线的时候线上优化的
小结:执行顺序为:
1. 先看mode是哪种模式
2. 然后Entry找到入口,然后找到对应的内容
3. 如果有css或其他webpack识别不了的东西就用loader将其转换
4. 然后根据plugins将其进行压缩
5. 最后到将打包好的文件进行output导出
基础配置
前提
:node版本10以上
- npm init -y生成package.json
- npm install webpack webpack-cli
- 创建src文件夹,且创建index.js作为入口
- 创建build文件夹,且创建index.html作为引入打包文件的地方==(因为最初如果不配置的话,打包后的文件是在build里面webpack自己默认生成个main.js需要自己手动引入到index.html中的)==
- 运行指令 webpack ./src/index.js -o ./build/build.js --mode=development
小结:
webpack ./src/index.js -o ./build/build.js --mode=development
指令意思为
webpack 以./src/index.js作为入口
-o 是-output的缩写,也就是出口
以./build/build.js作为出口,默认为main.js
打包环境为development(开发环境)
初始化文件目录
build
|-build.js 打包后才有这个build.js文件
node.modules
src
|-index.js
package-lock.json
package.json
作用:
举例: index.js里面import $ from 'jquery'
本来直接使用import浏览器会报错,但是经过webpack打包后,使用main.js就不会报错,而且可以实现我们想要的效果,但是此时还不能打包其他的,只能打包js和json,因为还没有配置
webpack配置文件,webpack.config.js
- 作用:是webpack的自定义配置文件,可以指定webpack做哪些事情
- 文件位置:和src同级
- 所有的构建工具都是基于node.js运行的,所以我们
在webpack.config.js里
面使用commonjs规范导入导出
const path =require('path');
const htmlPlugin=require('html-webpack-plugin');
module.exports={
entry:'./src/index.js',
output:{
filename:'bb.js',
path:path.resolve(__dirname,'build')
},
module:{},
plugins:[
new htmlPlugin()
],
mode:'develpoment'
}
loader 翻译官
- lodaer配置
放在module这个对象中的rules数组里面
,每一个配置都为一个对象
- 一个对象包括test和use,
test是匹配文件,使用正则
,
test
也可以换成exclude
,test表示的是处理匹配到的所有
,exclude表示的是处理除了匹配到的以外所有
- test和exclude可以同时存在
use是转化规则,是数组,use的执行顺序是从后到前,是有依赖关系的,里面放的是loader
,如果只有一项可以写成字符串
- enforce :true/false true为优先执行
options
是对象,(独属于图片的
)做一些配置项的,默认没有,可以不写
module:{
rules:[
前提:npm i css-loader style-loader -D
{test:/\.css$/,use:['style-loader','css-loader']},
前提 npm i style-loader less-loader css-loader -D
{test:/\.css$/,use:['style-loader','css-loader','less-loader']},
前提 npm i url-loader file-loader -D
{test:/\.(jpg|png|gif)$/,loader:'url-loader',options:{
limit:8*1024
}},
可以用于处理icon
{exclude:/.(css|js|html)$/,loader:'file-loader'}
]
},
图片打包中的options的一些可选配置项
limit : 8*1024 图片小于8kb转为base64
name : hash[10].[ext] 压缩后的图片名字用10个字,ext是他原本的后缀名,默认压缩完的图片名很长
esModule : true/false 开启或关闭es6Module模式
outputPath : 地址 将打包好的文件输出到指定文件夹,是字符串类型的地址,相对于build根目录的
常见问题
- url-loader和html-loader一起使用有冲突
- 原因是url-loader默认使用es6模块解析,而html-module引入图片采用的是commonjs,es6Module在
- 解决方法:关闭url-loader里的es6模块化,让其使用commonjs规范解析
module.exports = {
module: {
rules: [{
test: '/\.(png|jpg|jpeg)$/',
use: 'url-loader',
options:{
limit:8*1024,
esModule:false
}
},
{
test: '/\.html$/',
use: 'html-loader'
}
]
}
}
eslint
- 作用:语法检测
- 检测范围:只检测自己写的代码,第三方库是不检查的
- 下载依赖 yarn add eslint-config-airbnb-base eslint-plugin-import eslint eslint-loader -D
- 设置检查规则:
- package.json中eslintConfig中设置想要的语法规范
- 打包的时候,红色的是错误,黄色的是警告
> package.json
extends后写要继承的语法规范
"eslintConfig": {
"extends":"airbnb-base"
}
modules.exports={
rules:[
{
test:/\.js$/,
exclude:/node_modules/,
loader:'eslint-loader',
options:{
fix:true
}
}]
}
js兼容性处理
- 处理js兼容
- yarn add -D babel-loader @babel/core @babel/preset-env webpack
版本冲突
- babel-loader8对应babel 7
- babel-loader7对应babel6
默认只能转换基础语法,没有全部将es6转换为es5或更低版本
- yarn add @babel/polyfill -D
- 这个使用就不是在loader里了,直接在index.js中import即可
- 缺陷,虽然会把所有的都处理,但是有一些我们没有用到,他也将方法一并打包,会很大
import '@babel/polyfill';
首先需要将全局的注释掉
yarn add babel-loader @babel/preset-env @babel/preset-env @babel/polyfill core-js -D
module.exports={
module:{
rules:[
{
test: /\.js$/,
exclude: /node-modules/,
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
useBuiltIns:'usage',
corejs:{
version:3
},
targets:{
chrome:'60',
firefox:'60',
ie:'9',
safari:'10'
}
}]
]
},
}]}
}
plugins
作用
:通过一些插件处理东西,不同插件有不同的作用
用法
:都是通过先require导入,然后在plugins这个数组内,new的方式执行
html-webpack-plugin
- 作用: 实现自动生成html,并且引入打包文件
- 可选配置项,
注意
,如果使用配置项,new的时候就要在()内传个对象
- template:'文件地址' 作用是以文件地址中的html为模板生成新的html
前提: npm i html-webpack-plugin -D
const htmlPlugin=require('html-wenpack-plugin');
module.exports={
plugins:[
new htmlPlugin()
],
}
mini-css-extract-plugin插件
作用
:实现css的提取
,单独把css提取成文件,用link标签引入
好处
:正常loader使用的是style-loader打包在js中然后通过js添加style标签实现,会出现白屏效果
注意
:特殊之处在于,不仅需要在plugins里new
,还要在loader里使用名字.loader
,且注意,不能与style-loader同时使用
- new的时候可以传配置项
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}]
}
}
前提: npm install mini-css-extract-plugin -D
const miniPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [miniPlugin.loader, 'css-loader']
}]
},
plugins: [
new miniPlugin({
filename:'css/built.css'
})
]
}
optimize-css-assets-webpack-plugin压缩css
- 作用:压缩css
- 正常下载是npm i optimize-css-assets-webpack-plugin -D,但是下载不了,npm报错,可以使用yarn add -D optimize-css-assets-webpack-plugin
const OptimePlugin=require('optimize-css-assets-webpack-plugin');
module.exports={
plugins:[new OptimePlugin()]
}
css兼容性处理
- 作用:为了适应各个浏览器的兼容,自动给样式加前缀
- 前提:npm install postcss postcss-loader postcss-preset-env -D
- 他会根据package.json里面的browserslist进行兼容css处理
新语法
module: {
rules: [{
test: /\.css$/,
use:[
miniPlugin.loader,
'css-loader',
{
loader:'postcss-loader',
options:{
postcssOptions:{
plugins:[
[
"postcss-preset-env",
]
]
}
}
}
],
},
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
outputPath: 'image'
}
}
]
},
dll 缓存第三方库
- 第三方包是固定的,不需要每次编译都进行打包,这时候就可以使用dll缓存
- 根目录下新建webpack.dll.config.js,添加配置,以jquery举例
const path = require("path");
const webpack = require("webpack");
module.exports = {
mode: "production",
entry: {
jquery: ["jquery"],
},
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dll"),
library: "[name]_[hash]",
},
plugins: [
new webpack.DllPlugin({
name: "[name]_[hash]",
path: path.resolve(__dirname, "dll/manifest.json"),
}),
],
};
- 配置执行命令,
"dll" : "webpack --config webpack.dll.config.js"
- 在webpack.config.js中使用webpack自带插件
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, "dll/manifest.json"),
}),
- 执行顺序,先执行npm run dll,将缓存打包出来,后续再执行npm run build命令时,明显加快
- 注意:因为dll文件是单独打包,所以需要手动引入
devServer 服务,实现自动化
- 作用:我们每一次更新代码都需要npm run build,显然很麻烦,这个时候就可以使用devServer来帮我们处理
- npm install webpack-dev-server -D
- 启动的需要npx webpack-dev-server 或者在package.json里scripts里自己配置一下就可以用npm run 的方式
- 需要了解的是,他并没有肉眼可见的更新文件,是自己在内存中进行更新的,而npx webpack是输出了文件,这是他们的区别
module.export={
devServer:{
contentBase:path.resolve(__dirname,'build'),
compress:true,
port:3000,
open:true
}
}
遇到问题
版本信息:
"webpack": "^5.36.0",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2"
报错 Error: Cannot find module 'webpack-cli/bin/config-yargs'
原因:webpack-dev-server版本不支持wepack5和webpack4
解决方法: 在package.json里的scripts里加个 "start": "webpack serve --mode development --env development"
执行成功:npm run start
webpack性能优化
HMR热更新
- 场景:只要修改一个文件,webpack会将全部的都进行重新打包,消耗性能,慢
- 作用:一个模块发生改变,只打包改变的
- 更新范围:
- 样式文件可以使用hmr功能,因为style-loader内部实现了,所以开发时候我们会用style-loader
- js文件默认不能使用hmr功能,修改了会全部重新打包,刷新页面,在src/index.js里面配置内容,可以实现单个模块更新,示例在下方
- html文件,默认不可以使用hmr功能,修改后会导致页面不可以热更新,页面也不会刷新
+ 解决方案:修改entry入口,改成数组,将html文件也放入,一般是用不到
const webpack=require('webpack');
module.exports={
entry:['./src/index.js','./index.html'],
plugins: [
new webpack.HotModuleReplacementPlugin()
],
devServer:{
hot:true
}
}
解决js更新会全部重新加载页面问题
./aa.js是要更新的js地址
if(module.hot){
module.hot.accept('./aa.js',function(){
print();
})
热更新失效问题
- 在network里面查看websocket是否加载,不加载的话就讲package.json中的 "browserslist"删除
映射 source-map
- source-map 是一种提供源代码到构建后代码的映射技术,简单说明就是打包后的代码出错了,在指出在源代码中错误的位置,便于修改,因为打包后的代码是一个js,很难以观察
- 使用的时候是devtool:'值'
- 有七个值,特点不同,可以两两组合使用
devtool 有几个值
外联
source-map 提示错误信息和源代码的错误位置
hidden-source-map 不能追踪源代码错误,只提示打包后代码错误位置
nosources-source-map 提示错误信息和源代码文件位置,但是没有提示源代码内错误内容具体行数
cheap-source-map 提示错误信息和源代码的错误位置,但是他是按照行来提示的,简单来说就是,如果两行代码没有换行,后面的报错,他会把前面的也标红
cheap-module-map 提示错误信息和源代码的错误位置,module会将loader和source map加入,也就是会将第三方库的错误抛出来
内联
inline-source-map 提示错误信息和源代码的错误位置
eval-source-map 提示错误信息和源代码的错误位置
内联和外联的区别:1.外联是在外部生成js文件,内联是在html中书写,2. 内联构建速度更快
开发环境要求的是:速度快,调试方便
速度(eval>inline>cheap)
eval-cheap-souce-map
eval-source-map
调试友好
souce-map
cheap-module-souce-map
cheap-souce-map
可以结合出最优方案
eval-source-map / eval-cheap-module-source-map
生产环境:源代码要隐蔽,调试要不要方便
内联是嵌入在html内,所以体积会变大,生产环境不用
nosources-source-map 全部隐藏
hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
最优方案
source-map / cheap-module-source-map
oneOf
- 应用场景:比如说css、less我们都配了loader,但是打包的时候他每个都要匹配一次,可是我们不用每个都loader都匹配一次,只匹配一个就好了
- 作用:让一些loader在这个范围内只匹配一个
- 注意:一些后缀相同,且是连带关系的不要这样用,比如说js既要走eslint还要走es6转es5,那只走一个显然有违初衷
module.exports = {
module: {
rules: [
{
oneOf: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader','less-loader']
}
]
}
]
}
}
三种hash
hash
- 每次webpack打包都会生成一个唯一的hash,可以理解为这次打包行动的编号
- 问题:用这个的话,一旦从新打包,所有缓存都失效,因为hash变了,意味着使用hash的文件名也改变了,会重新请求
chunkhash
- 根据chunk生成的hash值,如果打包来源于同一个chuank,那hash值就相同
- 问题:js和css的hash还是相同的,因为正常项目是在js里引入css的,
chunk是根据entry产生的(可能不太标准)
,可以理解为一个入口一个chuank
,所以重新打包缓存还是会失效
chuank产生的途径
- entry入口,
- 如果是
数组或字符串
,那只会产生一个
chunk
- 如果是
对象
,那对象内几个键值对
,就产生几个
chunk
- 异步加载模块
- 代码分割
contenthash
- 根据文件内容产生的hash,也可以理解为每个人独有一份,只有自己改变了,才会生成一个新的唯一标识
缓存
js走缓存
- 在options内添加cacheDirectory: true
- 避免js文件重复走babel编译,应该走的只有修改的js
- 第一次以后才会执行缓存
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.js$/,
exclude: /node-modules/,
loader: 'babel-loader',
options: {
cacheDirectory: true
},
}
]
},
文件缓存
output: {
filename: 'bb.[contenthash].js',
path: path.resolve(__dirname, 'build')
},
plugins: [
new miniPlugin({
filename:'css/build.[contenthash:10].css'
})
],
tree shaking 树摇
作用
:英语直译树摇
,简单来说就是我们下载的第三方库很多,但是有一些用不到,将整个项目比作一颗树的话,用到的就是绿叶,没用到的就是灰叶,那我们需要摇晃大树将灰色枝叶去掉,达到修建枝叶的效果,也就是去除无用代码
- 前提:
- 必须使用es6模块化
- mode为production
css丢失问题
- package.json 中配置了"sideEffects":false意思是所有代码都可以进行tree shaking
- 那么他可能会将css等其他没有用到的文件去掉
- 解决方法:配置"sideEffects"
"sideEffects":["*.css"]
多入口/多页面
- name代表的就是entry的key
- 会打包成两个js
module.exports={
entry:{
index:'./src/js/index.js',
login:'./src/js/login.js'
},
output:{
filename:'js/[name].[contenthash:10].js',
path:resolve(__dirname,'build')
}
}
code split 代码分割
- 简述:webpack打包的话,会默认将js合并,一个入口合并成一个js,代码分割可以将引入的第三方模块抽离出来,多入口时候,同一第三方库在多处导入,默认会在每个入口js合并一次,因为分属不同模块,但是代码分割后,抽离出来,就只将公有第三方模块抽离一次,然后入口js自动引入
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
},
}
- 如果想将自己的某个js单独打包,且命名的话导入的时候就是用import ''
- /webpackChunkName:'aa'/'中的aa是自定义名字,可以不写
import ('./aa');
懒加载和预加载
- 懒加载就是触发条件才加载,懒加载可以将import放在异步函数里面
- 预加载就是提前把资源加载好,等到触发条件,直接走缓存,实施方法是在异步函数里面的import中添加webpackPrefetch:true
- 预加载与正常直接导入加载的区别,正常加载是并行加载,预加载是异步加载,可以理解为等其他的全部加载完,预加载的资源才开始加载
btn.onclick=function (){
import ('./test')
}
常见问题
babel提示babel-core版本不对,需要安装7却安装了6版本,而且npm i babel@7 安装不了
- 解决方法: npm i babel-core babel-loader@7.1.5 babel-plugin-transform-runtime -D
配置命令dev为webpack-dev-server后执行报错 Cannot find module 'webpack-cli/bin/config-yargs'
- 解决方法:将dev的命令改为 webpack server
自定义node服务,不使用devserver
- npm i express webpack-dev-middleware
- 配置server.js
const express = require("express");
const webpack = require("webpack");
const webpackDevMiddleware = require("webpack-dev-middleware");
const path = require("path");
const app = express();
const config = require("./build/webpack.config.js");
const compiler = webpack(config);
const UPLOAD_DIR = path.resolve(__dirname, "target");
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
})
);
app.get("/page/*", (req, res) => {
res.sendFile(path.resolve("./dist/index.html"));
});
app.get("/Proxy/*", (req, res) => {
res.send({
status: 0,
data: [{ a: 1 }, { a: 2 }],
});
});
app.listen(3000, function () {
console.log("🚀3000🚀");
});
- 在package.json中配置命令
"server": "nodemon server.js"
- 执行
npm run server
即可
开启热更新
- npm i webpack-hot-middleware
- 在server.js中添加内容
const webpackHotMiddleware = require("webpack-hot-middleware");
app.use(
webpackHotMiddleware(compiler, {
log: false,
heartbeat: 2000,
})
);
entry: [
"webpack-hot-middleware/client?path=/__webpack_hmr&noInfo=true&reload=true",
"./src/index.tsx",
],
new webpack.HotModuleReplacementPlugin(),
- 在主入口文件添加内容,通常是src下的index.jsx,看自己具体情况
if (module.hot) {
module.hot.accept();
}