简介
webpack
强大的功能主要还是依赖于 loader
与 plugin
机制
基本使用
-
全局安装:
npm i webpack webpack-cli -g
-
在项目目录下
npm init -y
-
在项目目录下新建
public/
目录 -
在
public/
目录下新建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Webpack Sample Project</title>
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
- 在项目目录下新建
app/
目录 - 在
app/
目录下新建main.js
文件
const greeter = require('./greeter')
document.querySelector('#root').appendChild(greeter())
- 在
app/
目录下新建greeter.js
文件
module.exports = function() {
var greet = document.createElement('div')
greet.textContent = 'Hi there and greetings!'
return greet
}
- 在项目目录下新建
webpack.config.js
文件
const path = require('path')
module.exports = {
entry: './app/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'public')
},
mode: 'production'
}
- 在命令行中,切换到项目目录下。输入
webpack
,即可在public/
目录下生成bundle.js
文件
基本配置
入口entry
一个 webpack
工程,首先至少要有一个入口
// 一个入口
module.exports = {
entry: './index.js'
}
// 多个入口
module.exports = {
entry: {
pageA: './pageA.js',
pageB: './pageB.js'
}
}
// 也可以是数组(实际工程中很少使用这种方式)
module.exports = {
entry: ['./pageA.js', './pageB.js']
}
// 或者
module.exports = {
entry: {
main: ['./pageA.js', './pageB.js']
}
}
出口output
编译构建后,真正被浏览器加载的资源文件则是出口文件
出口的相关配置定义了输出文件的路径与文件名
path
配置决定了最终打包后输出资源的文件路径,必须为系统绝对路径
// 单出口
module.exports = {
output: {
filename: 'output.js', // 文件名
path: __dirname + './dist' // 文件输出路径,必须为系统绝对路径
}
}
// 多个出口 - 对应多个入口
module.exports = {
entry: {
pageA: './a.js',
pageB: './b.js'
},
output: {
// 会输出 /dist/pageA.js /dist/pageB.js
// 占位符 [name] 为 chunkName。在此处则即是 entry 中多入口配置对象的key值,即pageA 与 pageB
filename: '[name].js',
path: __dirname + './dist'
}
}
如果使用了 html
插件,那 webpack
会智能的判断生成 html
与 chunk
的相对路径,再以 script
方式引入
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
pageA: './a.js',
pageB: './b.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist',
publicPath: 'https://xxx.com' // html 中引用的 js 资源从 pageA.js 变为 https://xxx.com/pageA.js
},
plugins: [
new HtmlWebpackPlugin()
]
}
模式mode
目前已有的模式:production
、development
、none
默认:production
- 设置为
production
与development
时会同时默认设置process.env.NODE_ENV
为production
或development
- 设置
none
时,webpack
不做任何附加操作
module.exports = {
// ...
mode: 'development'
}
devtool
devtool选项 | 配置结果 |
---|---|
source-map |
在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map ,但是它会减慢打包速度 |
cheap-module-source-map |
在一个单独的文件中生成一个不带列映射的map ,不带列映射提高了打包速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便 |
eval-source-map |
使用eval 打包源文件模块,在同一个文件中生成干净的完整的source map 。这个选项可以在不影响构建速度的前提下生成完整的sourcemap ,但是对打包后输出的JS 文件的执行具有性能和安全的隐患。在开发阶段这是一个非常好的选项,在生产阶段则一定不要启用这个选项 |
cheap-module-eval-source-map |
这是在打包文件时最快的生成source map 的方法,生成的Source Map 会和打包后的JavaScript 文件同行显示,没有列映射,和eval-source-map 选项具有相似的缺点 |
模块module
webpack
是一个模块打包器
webpack
自身不可能穷举处理所有的相关文件。于是就采用了 loader
方式
module
的主要配置项为 rules
。这是一个数组配置,rules
中的每一项 rule
即配置了如何去处理一个模块
const path = require('path')
module.exports = {
// ...
module: {
rules: [
{
// 正则,匹配的对象是 引用模块的绝对路径
test: /\.(js)$/,
// use: 'babel-loader',
// ps:babel 的配置我们更多是以 .babelrc 配置文件的方式存在项目根目录
use: {
loader: 'babel-loader',
options: {
// 根据目标浏览器自动转换为相应 es5 代码
presets: ['@babel/preset-env']
}
},
// 过滤 node_modules 目录
exclude: path.resolve(process.cwd(), './node_modules'),
// 只匹配 src 目录
include: path.resolve(process.cwd(), './src')
}
]
}
}
一个模块文件需要转化多次,需要多个 loader
。比如一个 css
文件
先通过 css-loader
解释 css
文件内的 @import
和 url()
,
最后通过 style-loader
将 css
以 <style>
标签插入至 dom
中
module.exports = {
// ...
module: {
rules: [
{
test: /\.(css|less)$/,
use: [
'style-loader',
// 'css-loader',
// 如果 css-loader 还需要增加配置
{
loader: 'css-loader',
options: {
// 启用 sourceMap
sourceMap: true
}
}
]
}
]
}
}
解析resolve
这个配置决定如何去解析模块。比如:是否需要缓存此模块;是否自动识别扩展文件名等
module.exports = {
// ...
resolve: {
// 别名
alias: {
// import utils from '../../../utils'
// 可以写成 import utils from '#/utils'
'#': path.resolve(process.cwd(), './src')
}
}
}
设置别名后,编辑器没有办法自动匹配文件路径了。
比如 vscode
,可以设置 vscode/jsconfig.json
文件
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"#/*": ["src/*"]
}
}
}
统计信息stats
- 当我们执行
webpack
命令时,终端中会打印非常多的信息- 大部分配置都是默认开启,实际需求中只要关闭几个不必要的配置即可
- 如果是开发环境,启动了
webpack-dev-server
,那需要devServer
对象中配置
比如生产环境可以配置
module.exports = {
// ...
stats: {
warnings: false, // 取消警告信息
children: false, // 取消子级信息
modules: false, // 取消模块构建信息
entrypoints: false// 不显示入口起点
}
}
常用loader
loader
主要负责对于某些文件的处理与转化
babel-loader
将最新标准的代码转成当下浏览器可执行的 js
代码
主要是以 .babelrc
配置文件的方式存在项目根目录
css相关loaders
less-loader/sass-loader
:less
/sass
预处理postcss-loader
:css
样式后处理工具。css
压缩、合并、自动兼容浏览器等功能利器css-loader
:解释css
文件内的@import
和url()
;可开启css-module
style-loader
:将css
以style
标签插入dom
中css-hot-loader
:css
热更新loader
,线上环境时勿加,会引起js文件contentHash每次都不同
file-loader
对于图片这样的静态资源,我们在代码中引入时,常以当前文件为基准,引入其相对路径下的图片。
而当我们访问 html
时,这个相对路径其实是基于 html
此时的路径的,故而会导致引入路径错误
file-loader
可以自动的识别 webpack
配置,打包资源图片,修复引入路径,进而保证资源引入正确。同时也支持修改输出后文件的路径与文件名、携带 hash
值等功能
url-loader
基本功能同 file-loader
在它基础上,可以设置一个 limit
配置项,意义为文件的体积大小,单位为字节。对于小于此大小的文件,会转化成 base64
的数据,替换 url
引入
对于小图片等资源常用这样的操作,好处是减少资源的请求次数;
或者在某些场景下,保证图片在 html
加载或渲染时就能展示,不需要再发起请求
常用plugin
plugin
是作用于 webpack
命令执行的整个生命周期的
html-webpack-plugin
只要是 web
应用的工程,必须要有 html
文件或者其他 html
模板文件
html-webpack-plugin
主要功能就是可以根据项目中的 html
模板(没有也行),生成想要的 html
文件,并插入我们构建出来的 js
与 css
等资源
对于构建多页应用来说,每需要一个 html
文件,就需增加一个 html-webpack-plugin
,并引入相应的 chunk
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
pageA: './a.js',
pageB: './b.js'
},
output: {
filename: '[name].js',
path: _dirname + '/dist'
},
plugins: [
new HtmlWebpackPlugin({
filename: 'pageA.html',
chunks: ['pageA']
}),
new HtmlWebpackPlugin({
filename: 'pageB.html',
chunks: ['pageB']
})
]
}
mini-css-extract-plugin
抽离 css
文件。不要以 style
标签的形式插入至 dom
中
如果引入了本插件,那么就需要在 css
文件的相关 loaders
中需要使用 MiniCssExtractPlugin.loader
替换 style-loader
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
// ...
plugins: [
new MiniCssExtractPlugin({
// 生产环境需要hash可配置为 [name].[contenthash].css
fileName: '[name].css'
})
],
module: {
rules: [
{
test: /\.(css)$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
}
}
HotModuleReplacementPlugin
模块热替换(
HMR - Hot Module Replacement
)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面
- 保留在完全重新加载页面时丢失的应用程序状态。
- 只更新变更内容,以节省宝贵的开发时间。
- 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。
webpack-dev-server
中需要设置hot
为true
。style-loader
支持热替换,但是上小节中为了抽离css
文件引入的MiniCssExtractPlugin.loader
暂时未支持热替换,故而需要开发环境时采用style-loader
,或者引入css-hot-loader
。react
工程需要实现组件热替换的话,需引入react-hot-loader
。vue-loader
已经实现了HMR
,无需要增加其他loader
。
uglifyjs-webpack-plugin
实现代码压缩、混淆、tree-shaking
的
webpack
也已内置
commons-chunk-plugin
抽离多个入口chunk
内的公共代码
在webpack@4
中,已使用 optimization.splitChunks
来实现这样的功能。其内部实现基于内置的 SplitChunksPlugin
webpack-bundle-analyzer
提供可视化的界面,以用来分析webpack
构建后的打包情况
建议工程项目都可以添加,才能清晰掌握自己工程项目具体加载了哪些代码,进而做相应的打包优化
开发配置
webpack-dev-server
主要就是启动了一个静态服务,让开发者可以方便的预览自己工程构建的webApp
host
:{string}
服务的host
,默认为localhost
,如果有需要非本机访问的需求(如想通过手机访问页面),可配成0.0.0.0
port
:{number}
服务端口号。不配置时,默认为8080
,如若端口不可用,会自增寻找可用端口。但手动配置且端口号不可用时,不会自动寻找其他端口号disableHostCheck
:{boolean}
默认为false
。如果有绑定域名访问的需要的话,需设置为true
allowedHosts
:{array}
可以自己配置允许的hosts
白名单。将指定的域名添加至配置项中,即可通过该域名访问本服务contentBase
:{boolean|string|array}
静态服务器的内容目录地址。或者说,就是访问服务器的ip/域名时,对应访问的工程里的文件夹index
:{string}
主页文件名,默认index.html
hot
:{boolean}
启动webpack
的模块热替换。即不需要刷新页面则更新代码。不过这需要插入热更新插件historyApiFallback
:{boolean}
依赖于HTML5 history API
,如果设置为true
,所有的跳转将指向index.html
clientLogLevel
:{string}
默认为info
,可选为none
,error
,warning
stats
:同统计信息[stats]
const path = require('path')
module.exports = {
// ...
devServer: {
// webpack-dev-server 访问的是内存中文件,此时文件并未真正输出到 dist 目录下
contentBase: path.join(__dirname, 'dist'),
// contentBase: [path.join(__dirname, 'dist'), path.join(__dirname, 'www')],
index: 'home.html'
}
}
构建配置
生产环境特殊的构建配置
尽量减小构建包的体积
optimization
webpack@4
中尽量将这些插件内置,并将其配置化,主要收敛在 optimization
这个配置项中
minimize
:当mode
配置为production
时,minimize
值为true
。若设为false
时取消引入uglifyjs-webpack-plugin
minimizer
:配置执行代码压缩的工具,默认为uglifyjs-webpack-plugin
splitChunks
:即是SplitChunkPlugin
的配置。用处是可以按一定规则将工程中代码提取一部分到一个新的chunk
文件runtimeChunk
:webpack
打包代码后,为了控制模块的依赖与加载,必不可少的需要往工程项目中加入webpack
提供的相应执行代码。这部分代码会因模块的增删改变化而变化。故而可以通过配置runtimeChunk
,将这部分运行时代码抽离出来。runtimeChunk
默认为false
,可以配置为{ name: 'manifest' }
,设为对象,指定name
即可
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true, // 多进程压缩
sourceMap: true
}),
new OptimizeCSSAssetsPlugin({}) // 优化压缩css
]
}
}
splitChunks
常见场景
- 把不同页面公共的代码抽离到一个
commonChunk
- 把外部依赖包单独抽离到一个
vendor
- 把React/Vue 等项目必引的库单独抽离成
dll
splitChunks
基本配置
name
: 分离出的chunk
名字,默认为true
,chunk name
会自动生成。maxInitialRequests
: 最大可分割出来的chunk
数。maxAsyncRequests
: 最大可分割出来的异步chunk
数(按需加载时使用)。cacheGroups
: 这个配置项是分割代码的核心配置,这是一个对象,key
为chunk
的唯一识别key
,value
为chunk
的具体分割规则配置,如下:
name
: 分离出的chunk
的名字,若未设置且splitChunks.name
为true
时,以chunk
的key
为name
。
minChunks
: 抽离公共代码时,该公共代码最少被几个chunk
引用了。
test
: 模块匹配的规则
priority
: 分离规则优先级。有时候一个模块可能被多个规则匹配到,设置优先级可以让某个规则分离的chunk
具有更高匹配模块的优先级。
发表评论