Webpack 是什么
webpack
的核心目的是打包,即把源代码一个一个的 js
文件,打包汇总为一个总文件 bundle.js
。
基本配置包括mode
指定打包模式,entry
指定打包入口,output
指定打包输出目录。
另外,由于 webpack
默认只能打包js
文件,如果需要打包其他类型的文件,就需要配置loader
。
plugins
用于配置插件,插件可用于执行更广泛的任务,包括打包优化和压缩、或者重新定义环境中的变量。
webpack 和 vite 的区别
Vite
是一个轻量的、快速的构建工具。核心是由于浏览器支持 es module
,所以在开发环境下避免了打包成 js bunlde
,浏览器请求时,按需为浏览器提供es module
文件,浏览器获取 ES Module
文件会直接执行,极大的提高了启动速度和热更新速度。
Vite
和 Webpack
的区别:
Webpack
支持的模块化规范是ES Modules
、CommonJS
、AMD Modules
;Webpack
在开发环境:通过webpack-dev-server
托管打包好的模块;Webpack
在生产环境通过Webpack
构建。Vite
支持的模块化规范是ES Module
;Vite
在开发环境下直接使用ES Module
,托管给浏览器去执行;Vite
在生产环境通过rollup
构建
基本配置
初始化项目
javascript
npm init -y
使用 webpack
- 安装
webpack、webpack-cli
javascript
yarn add webpack webpack-cli --save
- 在项目下新建配置文件
webpack.config.js
- 默认依据
webpack.config.js
进行打包 - 如果使用别的名字,需要在通过
--config
指定配置文件
javascript
const path = require('path');
module.exports = {
mode: 'development', // 指定打包模式 development / production
output: {},
module: {},
plugins: []
};
- 打包命令
javascript
// 默认使用 webpack.config.js
npx webpack
javascript
// 指定配置文件
npx webpack --config webpack.dev.js
webpack 配置
基本五要素
mode
- 指定打包方式:
development / production
。
javascript
module.exports = {
mode: 'development' // 开发模式
};
javascript
module.exports = {
mode: 'production' // 生产模式,默认会压缩 js
};
entry
- 指定打包的入口文件。
单入口
- 不配置
entry
- 默认打包方式是单入口
- 默认打包入口文件是
/src/index.js
- 配置单入口
- 打包形成一个
chunk
,输出一个bundle
文件 - 默认打包后的
chunk
名字叫main.js
,想要修改输出文件名称需要在output
配置
javascript
// 单入口:字符串形式
const path = require('path');
module.exports = {
entry: path.join(__dirname, 'src/index.js') // 指定打包入口文件
};
多入口
配置多入口:对象形式 / 数组形式。
javascript
// 例如有两个文件
// /src/index.js
function add(a, b) {
return a + b;
}
export default add;
javascript
// /src/count.js
function count(a, b) {
return a - b;
}
export default count;
- 对象形式配置多入口
javascript
// 对象形式配置多入口
// 有几个入口文件就形成几个 chunk,输出几个 bundle 文件
// bundle 默认名称是对应的 key
module.exports = {
entry: {
index: path.join(__dirname, 'src/index.js'),
other: path.join(__dirname, 'src/count.js')
},
// 指定打包后的代码输出目录
// output: {
// filename: 'bundle.[name].[contenthash:8].js', // [name] 对应 entry 的 key
// path: path.join(__dirname, 'dist') // 输出的文件目录
// },
};
- 数组形式配置多入口
javascript
// 数组形式配置多入口
// 所有文件最终只会形成一个 chunk,输出文件只有一个 bundle 文件
// 会将后面的文件内容打包到第一个文件中,bundle 文件默认名字 main.js
module.exports = {
entry: [path.join(__dirname, 'src/index.js'), path.join(__dirname, 'src/count.js']
};
// 这时打包后只会输出一个 bundle 文件,会默认将 /src/count.js 打包到 /src/inde.js 中
javascript
// 一般不使用数组形式
// 唯一用处是解决:在开启 HMR 功能后,html 文件的热加载功能失效 的问题
// 开启 HMR 功能后,在 entry 入口添加 html 文件,就能解决 html 文件热加载失效的问题
module.exports = {
// 注意这里引入了 index.html 后,需要配置 html-loader 才能处理 html 文件
entry: [path.join(__dirname, 'src/index.js'), path.join(__dirname, 'src/index.html')]
};
output
指定打包后的文件输出目录。
- 不配置
output
:默认所有打包后的资源都输出到更目录下的dist
文件夹中
javascript
// 默认所有打包后的资源都输出到更目录下的 dist 文件夹中
module.exports = {
output: {}
};
- 配置
output
:将打包后的资源都输出到指定目录下
javascript
const path = require('path');
// 对象形式
module.exports = {
output: {
// path 配置公共目录,指定所有资源文件的输出目录
// 如果不配置,默认是输出到 dist 目录
path: path.resolve(__dirname, '/build'), // __dirname 是项目根目录的绝对路径
// 指定输出到公共目录下的资源名称(文件名 + 目录)
// 如果不配置: 默认输出文件名是 main.js, 输出结果是 /build/main.js
filename: 'js/bundle.js', // 输出为 /build/js/bundle.js
// filename: 'bundle.[name].js', // [name] 对应 entry 多入口的 key;
// filename: 'bundle.[contenthash:8].js', // 打包代码时加上 hash 戳可以命中缓存
// publicPath: 所有资源引入公共路径的前缀
// 不配置则 <script src="built.js"></script>
// 配置则 <script src="/built.js"></script> 会从项目根目录去寻找
publicPath: '/',
// 自动清空上次打包的内容
// 在打包前将 path 整个目录内容清空,再进行打包
clean:true
}
};
javascript
// 上面的 filename 配置是只输出一个 bundle 文件的情况
// 如果需要输出多个 bundle 文件 这里的 [name] 对应的就是多入口配置的 key
module.exports = {
entry: {
build: path.join(__dirname, 'src/index.js'),
other: path.join(__dirname, 'src/count.js')
},
output: {
path: path.resolve(__dirname, '/build'),
filename: 'js/[name].js' // 输出结果 /build/js/build.js 和 /build/js/other.js
}
};
module
module
里一般用于配置 loader
。
loader
是用于帮助 webpack
去处理非 js
文件的,因为 webpack
只能打包js
文件。
javascript
// module 对象套 rules 数组,在数组中配置 loader
module.exports = {
module: {
rules: [
// 使用单个loader的写法
{
test: /\.js$/, // 需要检测的文件,正则表达式
loader: 'babel-loader', // loader 名称
options: {
// 给 loader 指定一些特殊配置
}
},
{
test: /\.js$/,
// 如果一种文件需要被多个 loader 执行,必须要指定先后执行顺序
// enforce 指定优先执行 pre
enforce: 'pre',
exclude: /node_moduls/,
loader: 'eslint-loader', // loader 名称
options: {}
},
// 需要使用多个 loader
{
test: /\.css$/,
// 数组写多个 loader,调用方式是从后往前,先调用的需要写到后面
use: [
'style-loader',
{
loader: 'css-loader',
options: {
// 指定特殊配置
}
}
]
}
// 其它属性
{
test: /\.css$/,
// 默认是匹配所有目录
// include 指定要匹配的目录, 多个就用数组形式
include: [/asset/, /global/],
// exclude 指定要排除的目录
// 单个就直接写, 目录都用 // 包裹, 多个就用数组形式
exclude: /mode_modules/,
use: [
'style-loader',
'css-loader'
]
}
]
}
};
plugin
plugins
用于配置插件。
插件可用于执行更广泛的任务,包括打包优化、压缩文件、重新定义环境中的变量。
javascript
// 首先将需要的插件引入
const HtmlWebpackPlugin = require('html-webpack-plugin');
// plugins 是数组形式配置
module.exports = {
plugins: [
// 调用插件
new HtmlWebpackPlugin()
]
};
开发环境配置
devServer
用于开发环境下的配置,可修改端口号,自动打开浏览器,设置代理,开启 HMR,方便开发者的开发工作。
- 安装
webpack-dev-server
javascript
yarn add webpack webpack-dev-server -D
- 配置
devServer
javascript
const path = require('path');
module.exports = {
devServer: {
port: 3000, // 修改端口号
progress: true, // 显示打包的进度条
contentBase: path.join(__dirname, 'dist'), // 指定运行代码的目录,输出在内存中,看不到
open: true, // 自动打开浏览器
compress: true, // 启动 gzip 压缩
// 设置代理
proxy: {
// 将本地 /api/xxx 代理到 localhost:3000/api/xxx
'/api': 'http://localhost:3000',
// 将本地 /api2/xxx 代理到 localhost:3000/xxx
'/api2': {
target: 'http://localhost:3000',
pathRewrite: {
'/api2': ''
}
}
}
}
};
配置热更新 HMR
- 自动刷新:整个网页全部刷新,速度较慢;状态会丢失
- 热更新:新代码生效,网页不刷新,状态不丢失
javascript
// 自动刷新 (一般不需要配置自动刷新)
module.export = {
watch: true, // 开启监听,默认为false
// 注意,开启监听之后,webpack-dev-server 会自动开启刷新浏览器
// 监听配置
watchOptions: {
ignored: /node_modules/,// 忽略哪些文件
// 监听到变化发生后会等 300ms 再去执行动作,防止文件更新太快导致重新编译频率太高
aggregateTimeout:300,// 默认为 300MS
// 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
poll: 1000 // 默认每隔 1000 毫秒询问一次
}
};
javascript
// 开启 HMR
module.export = {
devServer: {
hot: true
}
};
HMR 原理
webpack-dev-server
会创建两个服务:express
服务(提供静态资源) 和 socket
服务(服务器可以主动发送文件到客户端)。
express server
负责直接提供静态资源的服务:打包后的资源直接被浏览器请求和解析。HMR Socket Server
,是一个socket
的长连接,建立连接后双方可以通信,服务器可以直接发送文件到客户端。而http
请求必须要浏览器主动发起请求。
当服务器监听到对应的模块发生变化时,会生成两个文件。 manifest.json
文件记录更新的位置信息等配置信息,update chunk
.js
文件记录实际更新的具体内容。通过长连接,可以直接将这两个文件主动发送给浏览器,浏览器拿到两个新的文件后,通过 HMR runtime
机制(webpack
在打包的时候提供),加载这两个文件,并且针对修改的模块进行更新。
resolve
alias
alias
属性配置路径别名。
文件目录隔的比较远就可以用路径别名来代替相对路径。比如存在目录:/src/pages/monitor/container.jsx
和 /src/common/utils/i18n.js
。container.jsx
文件需要引用 i18n.js
文件,通过以下配置后,引入时就可以缩短路径名称 import xxx from '@utils/i18n.js';
javascript
module.exports = {
resolve: {
alias: {
'@utils': path.resolve(__dirname, 'src/common/utils')
}
}
};
extentions
extentions
配置省略文件路径后缀名。
配置后在引入该文件的时候,可以不用写后缀名。
javascript
module.exports = {
resolve: {
// 配置省略文件路径后缀名
// 默认是省略 .js 和 .json
extensions: ['.js', '.json', '.jsx']
}
};
modules
告诉 webpack
解析模块应该去找哪个目录。默认是 node_modules
。
javascript
module.exports = {
resolve: {
// 告诉 webpack 解析模块是去找哪个目录
// 默认是 node_modules,但是会在目录中一层一层的找,先找 js 当前目录,找不到就往上一层找,直到找到
modules: 'node_modules'
}
};
javascript
const path = require('path);
module.exports = {
resolve: {
// 通过 resove 指定去哪一层找
// 一般后面还要加上 node_modules,如果指定目录找不到,就再一层一层找
modules: [path.resolve(__dirname, '../../node_modules'), 'node_modules']
}
};