前言
webpack 功能
打包:将不同类型资源按模块处理打包
静态:打包最终产出静态资源
模块:webpack 支持不同规范的模块开发
需要 webpack 的场景
当使用模块化开发时,尽管我们可以通过 type=module
的方式来使用 ES6 的模块化语法,但如果存在其他人使用了 CommonJS
模块化语法,就会出现问题。
webpack 工作模式
Webpack
并不会打包未使用的文件,只有在入口文件以及相关引入文件内明确使用后才会进行打包。
基本使用
全局安装
全局安装 webpack
和 webpack-cli
1 | npm i webpack webpack-cli -g |
局部安装
全局安装的方式并不能保证不同环境下安装的 webpack
版本是否一致,因此一般建议 局部安装 Webpack。
1 | npm i webpack webpack-cli -D |
打包运行
1 | npx webpack |
webpack 默认情况下会将项目根目录下的 src -> index.js
打包至 dist -> main.js
打包配置
webpack
的默认行为往往不能满足我们的需求,因此可以通过两种方式来定制化 webpack
的行为。
命令参数
可以通过给命令添加参数的方式来修改默认行为。例如如下命令指定了入口文件为 ./src/main.js
,打包生成的目录为 ./build
。
1 | npx webpack --entry ./src/main.js --output-path ./build |
配置文件
在项目根目录下新建文件,取名为 webpack.config.js
。
配置文件编写格式如下。
1 | const { resolve } = require( "path" ); |
与命令行不同的是,output -> path
必须为 绝对路径,详细内容在后面讲解配置文件时会描述。
使用下面命令开始打包,此时不需要跟随参数。
1 | npx webpack |
修改配置文件名
配置文件名是允许修改的,需要对打包指令进行部分修改。这里以 test.webpack.js
为例。
1 | npx webpack --config test.webpack.js |
基本属性
这部分梳理下 webpack.config.js
的基本配置属性。
占位符
webpack 在多个名称配置的地方均可以配置占位符,这些占位符如下
[ext]
:扩展名[name]
:文件名[hash]
:结合文件内容以 md4 算法生成的 128 位哈希值[contentHash]
:在这里是少有的与 hash 完全一致的情况[hash:<length>]
:指定 hash 的长度
context
基础目录,绝对路径,用于从配置中解析 entry 和 loader。
默认使用 Node.js 进程的当前工作目录,但建议在配置中手动声明,可以使该打包配置独立于当前目录。
1 | module.exports = { |
context
基础目录,绝对路径,用于从配置中解析入口点(entry point)和 加载器(loader)。
默认使用 Node.js 进程的当前工作目录。因此即使 webpack 配置文件并未放置在项目根目录,也仍会按照工作目录查找 entry
配置中的相对路径。
entry
配置入口文件,可以为一个字符串、数组、对象或是函数。
当路径指定为相对路径时,此时相对于配置 context
所在的路径。
字符串
此时将会对指定的文件作为入口文件打包,默认输出为 main.js
。
1 | module.exports = { |
数组
此时会将数组内的多个文件一起打包,统一默认输出到 main.js
。
1 | module.exports = { |
对象
当为对象时,每个属性对应的文件将会被分别打包,默认输出的文件名即属性名。
1 | module.exports = { |
此时打包目录中将会分别出现 index.js
和 global.js
两个文件。
而每个属性的值同样可以是字符串、数组或对象,当值为字符串或数组时,表现方式与上文同理。而为对象时,则可配置多个属性,如下:
1 | module.exports = { |
各属性释义如下:
- import:目标入口文件
- filename:指定输出的文件名称,该配置会覆盖
output -> filename
,允许使用占位符。 - dependOn: 当前入口所依赖的入口,必须先于该入口加载(暂未搞明白)
还有其他属性:chunkLoading
、asyncChunks
、layer
暂未搞明白,就先不写了。
函数
当值为函数时,将会在每次 make 事件中被调用。
1 | module.exports = { |
函数可以与前三种方式结合使用。
1 | module.exports = { |
允许接受一个异步函数,可以从远程动态获取相关配置
1 | module.exports = { |
output
该属性最低要求为一个包含 filename
属性的对象。
path
打包的内容所输出到的路径,必须为绝对路径。
filename
决定每个输出的名称,将写入到 output.path
指定的目录下
1 | module.exports = { |
当存在多个入口起点时,就不再应该使用静态名称,而是使用标识符的方式为多个起点指定输出名称。
此处的 [name]
为 entry
中对象的键名。
1 | module.exports = { |
该选项同时也可以配置路径,例如如下写法会将文件输出到 output.path
下的 page
目录内。
1 | module.exports = { |
也可以使用函数对该属性进行配置。
1 | module.exports = { |
publicPath
指定按需加载或加载外部资源(如图片、文件等)的基本引用路径,直接讲就是打包之后的 index.html
内部的基本引用路径,值是以 runtime(运行时) 或 loader(载入时) 所创建的每个 URL 为前缀。因此,在多数情况下,此选项的值都会以 / 结束。
一般来讲,打包后的 index.html
对资源引用时,遵循的格式为 domain + publicPath + filename
。
例如对于如下配置:
1 | module.exports = { |
则 index.html
内对打包输出的 js 文件的引用路径为 {domain}:{port}/build/index.js
。
示例:
1 | module.exports = { |
webpack-dev-server
也会默认从 publicPath
为基准,使用它来决定在哪个目录下启用服务,来访问 webpack 输出的文件。
该属性设置为绝对路径例如 /
时,对于 webpack-dev-server
来说一切照常;对于线上环境例如 https://test.marrydream.top/test
目录,将会前往 https://test.marrydream.top/index.js
查找,导致无法找到资源。
而设置为相对路径例如 ./
时,线上环境与本地 file://
没有问题;webpack-dev-server
却无法找到资源。
resolve
用于配置各个模块如何解析。
alias
创建 import
或 require
的别名,来确保模块引入变得更简单,值为一个对象,可配置多个别名。
示例:
1 | module.exports = { |
此时对于 src/assets
目录下的 logo.jpg
,直接按照如下方式引入即可。
1 | import "@/assets/logo.jpg"; |
extensions
当路径未明确指定文件后缀名时,将会按照从左往右的顺序依次尝试 extensions
中所配置的后缀名,默认值为 [".js", ".json", ".wasm"]
。
示例:
1 | module.exports = { |
此时对于同级目录下的 asuka.css
,直接按照如下方式引入即可。
1 | import "./asuka"; |
注:手动设置后将会覆盖掉默认值,建议设置时携带上默认值一起设置,一些第三方包会依赖默认值进行工作,覆盖掉后会造成一些意外错误。
mainFiles
当引入文件的路径结尾是个目录时,将会按照从左往右的顺序依次尝试 mainFiles
中所配置的文件名,自动匹配目录下的对应名称的文件,默认值为 [ "index" ]
。
示例:
1 | module.exports = { |
此时对于同级目录 css
下的 asuka.css
文件,直接按照如下方式引入即可。
1 | import "./css"; |
modules
告诉 webpack 解析模块时应该搜索的目录,当引入路径是个模块时,将会根据配置从左往右进行查找,默认值为 [ "node_modules" ]
。
1 | module.exports = { |
devServer
见下面 webpack-dev-server
。
mode
告知 webpack 使用相应模式的内置优化,存在三种值:
- none:不使用任何默认优化选项
- development:会将
DefinePlugin
中process.env.NODE_ENV
的值设置为development
,模块和 chunk 使用默认有效名称,devtool
将会被设置为eval
。 - production:会将
DefinePlugin
中process.env.NODE_ENV
的值设置为production
,模块和 chunk 使用混淆名称,并进行代码压缩。
当未配置时,将会使用可能有效的 NODE_ENV
值作为 mode
值。若仍找不到任何值,则默认设置为 production
。
若希望根据 webpack.config.js
中的 mode
变量更改打包行为,则应将配置导出为函数。
1 | module.exports = (env, argv) => { |
devtool
控制是否生成,以及如何生成 source map。
devtool 选项在内部添加
SourceMapDevToolPlugin
/EvalSourceMapDevToolPlugin
插件,可以通过直接使用这两个插来做到更多定制化才做。但切勿同时使用 devtool 与 这两个插件,那将会使得插件被应用两次。
source map
Source Map 就是一个信息文件,里面储存着位置信息,即存储着代码压缩混淆前后的对应关系。此时当代码出错时,浏览器将会根据原始代码定位错误,而非压缩后的代码。
当开启 source map 时,最终输出结果中,除了输出文件 index.js 外(此处举例为 index.js),还会包含一个 index.js.map
文件,该文件就是 source map 文件。
不过为了防止原始代码通过 source map 的方式暴露给他人,因此经常见到项目中仅在 development
环境下开启 source map,此时在 production
环境下打包的文件不会包含 source map 文件。
注:需在对应浏览器中开启 source map 功能,该功能才可以正常使用。绝大多数浏览器默认情况下是开启的,例如 chrome 的设置位置位于
f12 -> setting -> Preferences -> Enable JavaScript source maps/Enable CSS source maps
。
工作流程
根据源代码,生成 source map 文件,而后浏览器开启 source map 功能后
可能的值
更多配置方式详见官网 Devtool。
eval
当 mode
为 development
时,默认为该值。此时对于打包生成的文件可以准确定位报错位置(但报错信息存在问题,错误指向 eval),而 webpack-dev-server 下则无法正常工作。
在开发环境下为推荐性能最佳配置方式。
source-map
vue 脚手架开发环境下所给出的配置方式,能够实现源代码映射需求。
此时打包结果中同时存在后缀为 .map
的 source map 文件,且在输出文件的结尾处以注释形式存在 sourceMappingURL
字段指向对应的 .map
文件。
使用高质量 SourceMap 进行生产构建的推荐选择。
eval-source-map
该方式的打包结果中不存在后缀为 .map
的 source map 文件,此时输出文件中的 sourceMappingURL
指向了一个 base64 路径,被以注释形式放在了 eval
方法内。
使用高质量 SourceMap 进行开发构建的推荐选择。
inline-source-map
同样不会在打包结果中出现 .map
文件,且 sourceMappingURL
指向一个 base64 路径,但被放置的位置与 source-map
相同,在文件的结尾处。
打包单个文件时推荐使用的方式。
其他属性值
devtool 的值是存在固定模式的,按照如下顺序和模式即可: [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
。
- hidden:存在
.map
文件,但不会自动加载至浏览器内。 - cheap:此时为性能优化版本,报错信息中仅显示到行,不会具体到列。
- module:会展示未经 loader 处理的原始代码,该字段不会单独出现。
loader
主要用于在读取某一个特性类型时,对其进行转换。
为什么需要 loader
Webpack
默认只会处理 js
和 json
模块,其他模块均不识别。loader 可以将这些模块转换为 Webpack
可识别的模块。
loader 的使用方式
Webpack 5
已经不再支持 cli
的方式来使用 loader,这里仅说明 行内 loader 与 配置文件 两种方式。
行内 loader
以 css 为例,在引入的路径按以下格式在前面添加 loader
1 | // import "loader1!loader2!文件路径" |
配置文件
webpack.config.js 中:
1 | module.exports = { |
对于不需要进行 options 配置且仅有一个 loader 时,可以按照如下方式简写
1 | module.exports = { |
当有多个 loader 但其中某个 loader 不需要进行 options 配置时,可以如下方式简写
1 | module.exports = { |
注意事项
loader 严格按照编写的顺序依次加载,先加载的 loader 会将处理结果传递给下一个 loader。因此需要加载多个 loader 时应严格遵循 loader 之间的先后顺序,否则会报错。
顺序:从右往左、从下往上。
解析 css
基本的 css 代码解析需要用到两个 loader:css-loader
和 style-loader
,作用分别如下:
css-loader
:仅用来将 css 解析为 webpack 识别的语法,不会让 css 代码在页面中生效。style-loader
:在当前的页面上生成一个style
标签,将处理好的内容添加为标签内容。
先安装两个 loader
1 | npm i css-loader style-loader -D |
修改配置文件如下,从上面可知应该先加载 css-loader
然后加载 style-loader
。
1 | module.exports = { |
解析 less 与 sass
less
less-loader
的工作是将 less 代码解析为 css 代码,而在这个过程中其实用到了 less 来执行这项工作,因此也需要同时安装 less
。
1 | npm i css-loader style-loader less-loader less -D |
根据 less-loader
的工作原理我们不难理解,loader 的加载顺序应该为 less-loader
- css-loader
- style-loader
。
1 | module.exports = { |
sass
sass-loader
与 less-loader
工作类似,而他同样也内置需要一个名为 node-sass
的依赖来进行将 sass
代码解析为 css
的工作。
1 | npm i css-loader style-loader sass-loader node-sass -D |
而配置方法也参考 less 的配置理解。
1 | module.exports = { |
postcss 兼容处理
postcss 的详细讲解可以参考 Postcss及其使用,这里不再赘述。
css-loader 的 importLoaders 属性
当前存在两个 css 文件,如下
1 | /* native.css */ |
在配置了 postcss 的前提下打包后发现,最终生成的代码中并没有添加浏览器前缀。
1 | <style>.title { user-select: none; }</style> |
这是因为 postcss-loader
并不能对 @import
、@media
、url()
语法进行处理,尽管在其之后的 css-loader
可以识别并处理这些代码。但 loader 并不能反向把代码重新丢给 postcss-loader
。 因此导致并没有经过兼容处理的代码被放置到了网页上。
因此 css-loader
为我们提供了 importLoaders
属性用以解决该现象。配置方法如下
1 | module.exports = { |
这里的 importLoaders: 1
的意思为:当 css-loader 处理内容时,发现了 @import
等引入代码,将会把代码丢回前 1 个 loader(这里为 postcss-loader
) 再次进行处理。
解析文件
对于图片等资源,webpack 需要接触 file-loader
来进行解析。
1 | npm i file-loader -D |
不难理解,可以对 webpack 进行如下配置
1 | module.exports = { |
两种加载图片方式
此时存在两种情况:<img />
标签与 css url()
引入图片。下面区分讨论
打包预览后若提示
Automatic publicPath is not supported in this browser
,则需在output
中配置:publicPath
为打包导出的文件夹路径名。
img 标签
这里有如下的 js 代码:
1 | function packImg() { |
打包预览后发现实际的路径为:<img src="[object Module]">
。
原因是在 webpack 5 中,file-loader
使用 esModule
导出,这就导致了使用 require
引入图片时,得到的并非是图片资源,而是一个 { default: xxx }
格式的对象,资源数据被包裹在了 default
属性内。
此时有三种解决方案:
1、手动调用 default 属性
1 | img.src = require( "../imgs/test.jpg" ).default; |
2、使用 es 模块的方式引入
1 | import imgSrc from "../imgs/test.jpg"; |
3、修改 webpack loader 通用属性 esModule
在 webpack 的各个 loader 中存在一个通用属性 esModule
,表示是否将导出的内容转化为 esModule
,默认为 true
。在 file-loader
中将其改为 false
即可。
1 | module.exports = { |
url() 方式
css-loader
可以解析 url()
,解析的过程其实是将 url()
直接转为 require
语法。结合上面的内容我们可以得知,默认情况下 require
的导入方式为 esModule
,得到的内容不正确导致图片无法渲染。
不过鉴于 css 代码中无法使用 上文中的第 1、2 种方式,因此可以通过给 css-loader
配置 esModule: false
来解决这个问题。
1 | module.exports = { |
导出配置
默认情况下导出的文件名是非常混乱的,我们可以通过在 options
中对 name
属性配置占位符的方式来定制化输出文件的名称。
同时,也可以通过配置 outputPath
来定制化输出文件夹路径。
例如,可以通过下面的配置,使图片被打包在 img
文件夹下,以 文件名.6位hash值.扩展名 的格式命名。
1 | module.exports = { |
在上面的 outputPath 其实也可以省略,直接用反斜杠 /
拼接在 name
前面也可以生效,即 name: "img/[name].[hash:6].[ext]"
url-loader
url-loader
与 file-loader
配置基本相同。
1 | npm i url-loader -D |
默认情况下两者的区别为:
- file-loader:将要打包的图片资源拷贝到打包输出目录,并把图片的路径返回
- url-loader: 将要打包的图片资源以 base64 的方式加载到代码中,不会拷贝到输出目录。
url-loader
工作方式的利弊非常明显,base64
的打包方式可以有效地减少请求图片资源的次数,但对于一些大型图片会导致数据量过大而加载缓慢。
limit 属性
其实在 url-loader
内部也可以调用 file-loader
,通过一个 limit
属性来决定图片大小超过多大时改为 file-loader
对图片进行打包。
1 | module.exports = { |
上述配置,对于小于 25kb 的图片使用 base64
进行处理,反之使用 file-loader
进行拷贝打包处理。
url-loader
并不内置file-loader
,因此当涉及到调用file-loader
时,若未对其进行安装,则会报错。通常建议使用url-loader
时同时安装file-loader
。
babel 兼容性处理
babel 的详细讲解可以参考 Babel及其使用,这里不再赘述。
ts-loader
ts-loader
内置 typescript
的依赖来进行将 TypeScript
代码解析为 JavaScript
的工作,并在解析之前先进行语法的校验。
1 | npm i ts-loader typescript -D |
配置方法如下:
1 | module.exports = { |
也可以只使用
babel-loader
进行处理,babel
存在专门针对 TypeScript 的预设,各有优缺点,详见 Babel及其使用
Vue
vue-loader 在 16.x 之后为支持 vue3 的版本,若需支持 vue2,则应使用 16.x 以下的版本。
vue-loader 默认实现了 HMR 的热更新
1 | module.exports = { |
在 vue-loader@15 之前,不需要我们去处理 vue-loader-plugin
,在之后则需要我们自己手动的去加载这个插件。
vue-loader-plugin
已在 vue-loader 中默认安装,手动引入即可
1 | const VueLoaderPlugin = require( "vue-loader/lib/plugin" ); |
asset module type
在 webpack 5 之前,处理图片等资源需要用到 file-loader
或 url-loader
,但在 webpack 5
之后则可以直接使用内置的 asset 资源模块(asset module type)。
在 asset module type
中有几类常见的配置选项。
- asset/resource:file-loader 的实现,可以把资源拷贝到指定的目录
- asset/inline:url-loader 的实现,把相应的资源以 base64 的方式添加到行内。
- asset/source:raw-loader 不常用
- asset:通过配置参数来动态的决定该使用
asset/resource
还是asset/inline
对 webpack 进行配置时,可以通过 type
属性来定义当前匹配要使用的类型。例如,下面的操作可以替代默认情况下 file-loader
的使用。
1 | module.exports = { |
asset/resource
对于 asset/resource
有两种配置输出路径方法。
全局配置
可以通过修改 output -> assetModuleFilename
来修改输出的路径,占位符格式与 file-loader
的导出配置基本一致,不同的是这里的 [ext]
会自动包含 .
,不需要手动添加。
1 | module.exports = { |
该方式为所有的资源配置了相同的导出配置,会导致例如 字体 等文件也被打包在 img
目录下,不建议使用。
局部配置
使用 generator
来针对性的对规则进行配置。
1 | module.exports = { |
asset/inline
直接配置 type
即可,无需其他任何配置。
1 | module.exports = { |
asset
通过 asset
可以通过配置 parser -> dataUrlCondition -> maxSize
动态的决定使用 asset/resource
还是 asset/inline
。
1 | module.exports = { |
处理图标字体
对于图标字体,我们不需要让其进行 base64
处理,只需要直接被拷贝到 font
文件夹即可,因此可以如下方式配置。
1 | module.exports = { |
webpack 插件
webpack 在打包过程中其实也拥有自己的生命周期,而插件可以贯穿打包的整个生命周期,并自由的选择在什么时候执行什么操作。例如打包时自动清除导出目录、打包过程对代码进行压缩、定义全局变量等。相较 loader
,插件可以做更多的事情。
插件需要被配置在 plugins
属性下,以数组的形式,如下
1 | module.exports = { |
每一个插件本质上是一个 class
,因此使用时直接 new XXX( ...arg )
即可。
下面讲述部分插件的使用。
clean-webpack-plugin
可用于在每次打包发布时自动清理掉打包输出目录中的旧文件。
1 | npm i clean-webpack-plugin -D |
使用方式如下,详细配置参数参考 项目地址。
1 | const { CleanWebpackPlugin } = require( "clean-webpack-plugin" ); |
DefinePlugin
DefinePlugin 是一个 webpack 内置插件,用来全局定义变量。由于是内置插件,所以无需安装。
1 | const { DefinePlugin } = require( "webpack" ); |
以键值对的形式写入插件的对象参数内即可生效。但需要注意的是,DefinePlugin
会把值原封不动的放置到全局,因此这里如果希望结果是字符串,应当为 "'./'"
而非 "./"
。
html-webpack-plugin
在此之前有我们手动的新建 html
并修改标题、引入js,使用此插件后将会在打包时自动的生成 html
并填充标题、加载js。
1 | npm i html-webpack-plugin -D |
默认情况下,生成的 html 标题为 Webpack App
,并根据 output -> publicPath
导入打包的 js。可通过 title
等配置进行定制化修改,详见 项目地址。
1 | const HtmlWebpackPlugin = require( "html-webpack-plugin" ); |
自定义 html 模板
由于默认的模板常常不能满足我们的需求(例如 Vue
的 html 中需要存在 <div id="app"></div>
),因此需要自定义 html 模板。
新建 public
文件夹(该文件夹规范上讲一般不参与打包而是直接拷贝到输出目录内),新建 index.html
内容如下:
1 |
|
为插件参数对象中提供 template
属性,来指定模板路径。并通过上面所讲的 DefinePlugin
来添加全局属性 BASE_URL
。
1 | const HtmlWebpackPlugin = require( "html-webpack-plugin" ); |
copy-webpack-plugin
希望将部分文件直接拷贝到打包目录。
1 | npm i copy-webpack-plugin -D |
插件参数对象中存在一个 patterns
属性,值为对象数组,用来对多个文件设置拷贝。
1 | const CopyWebpackPlugin = require( "copy-webpack-plugin" ); |
对上面几个属性做一下说明:
form
: 要被拷贝的目录/文件路径。to
:可选,拷贝至打包目录下的指定目录/路径,如这里就是build/icon/
。不配置时默认情况下会将form
目录下的内容拷贝到打包根目录下即build
。globOptions
:ignore
: 忽略待拷贝目录下的文件列表,如要拷贝public
目录时,由于里面还存放着html-webpack-plugin
插件所需的index.html
并
已经对其做出了拷贝处理,此时若不忽略该文件,将会控制台打印报错。其中这里的**/
为在form
指定的目录下查找的意思,不可遗漏。
webpack-dev-server
在平常开发时,通常希望当代码做出改动时,webpack 会自动打包输出并自动更新预览页面,有两种实现方式。
为了解决这个需求,我们可以使用 watch
字段监听代码改动并自动打包,对打包输出的 index.html
使用 live server 方式打开预览。
命令行方式:
1 | npx webpck --watch |
配置文件方式:
1 | module.export = { |
该方式有如下几点不足:
- 当一个文件变化后,所有的文件都会重新进行打包编译。
- 每次编译打包后都会进行文件读写(例如
clean-webpack-plugin
导致的清除打包目录操作)。 - liver server 更新策略是刷新整个页面,我们期望他只是局部刷新所改动的部分。
此时便可以使用 webpack
提供的 webpack-dev-server
来实现该需求。
使用方式
webpack-dev-server
可以搭建一个可热更新的本地的服务器来提高开发效率。
1 | npm i webpack-dev-server -D |
使用如下命令,将会启动一个占用 8080
端口的服务器,且代码改动后将会自动打包。
1 | # 会查找默认的 webpack.config.js,可同样使用 --config 进行自行指定 |
值得注意的是,运行后并没有产生打包目录。该插件其实是把数据都存放在了内存中进行处理。
相关配置项
所有和 webpack-dev-server
有关的配置均可以在 devServer
中进行配置。
如果遇到问题,导航到
/webpack-dev-server
路线将显示提供文件的位置。例如,http://localhost:20715/webpack-dev-server
。
这一部分配置参考 DevServer,这里提供几个常用的配置。
port
指定开启服务的端口,默认 auto
,即默认 8080
,当端口冲突时会尝试替换为其他 prot
。
open
设置为 true
时,服务启动后自动打开浏览器,默认 false
。可以传递 string object [string, object]
。
1 | module.exports = { |
可以传递一个字符串,用于启动时,在浏览器打开指定页面。值为数组且包含多项时。将会同时打开这些页面。
1 | module.exports = { |
proxy
在开发中常会遇到请求接口的跨域问题,尽管后端可以通过 cors
进行跨域配置,但让后端来专门为开发服务器放行似乎有些多此一举。由于 webpack-dev-server
本身就开启了一个服务,因此我们可以让这个服务去帮助我们请求另一个服务端的数据,从而解决跨域问题。这就是 proxy
属性所做的事情。
例如我们希望能通过如下的代码来做到请求 https://api-kozakura.marrydream.top/common/sao_admin/v1/user/page?p=0&s=16
的效果。
1 | // 这里其实等同于请求 localhost:post/sao/user/page?p=0&s=16 |
对 webpack.config.js 中进行如下配置即可实现该效果:
1 | module.exports = { |
其中键值 /sao
是一个标识,表示仅对以 /sao
开头的请求地址进行代理。
下面对 proxy 的各个属性做出解释
target
代理指向目标,会替换当前主机地址为指定地址。例如在上面的案例中配置该属性后实际请求地址为 https://api-kozakura.marrydream.top/sao/user/page?p=0&s=16
。
pathRewrite
对代理后的地址进行重写。例如从 target 的解释中可以发现,导向的地址并不是我们所需要的地址。上面案例中通过重写将 /sao
替换为 /common/sao_admin/v1
。
changeOrigin
默认情况下,代理时保留主机标头的来源,即请求头中的 HOST
依然为本机地址(例如 localhost:xxxx
)。此时对于一些对来源进行了限制处理的 api 会出现无法请求的情况。
设置为 true
后,后端 api 的视角下将会变为自己请求自己。(由于无法改变浏览器的行为,因此此时浏览器中的 HOST 依然显示为 localhost
,但实际已经起作用了)
context
上面的案例中仅对单个标识进行了代理,如果想对多个标识进行同样的代理处理,可以使用 context
属性,且 proxy
改为数组写法。
1 | module.exports = { |
context 还可以采用函数写法,如下写法将会对所有的请求启用代理。
1 | module.exports = { |
secure
默认情况下,将不接受使用无效证书在 HTTPS 上运行的后端服务器。可以通过设置 secure
为 false
来覆盖该行为。
compress
开启服务端的 gzip
压缩,默认 true
。
webpack-dev-middleware
webpack-dev-server 内部使用了 webpack-dev-middleware
包,该包是一个容器,可以把 webpack 处理后的文件传递给一个服务器。可以单独使用,以便进行更多自定义的设置来实现更多需求。
1 | npm i webpack-dev-middleware |
下面简单实现一下 webpack-dev-server
:
1 | /* server.js */ |
使用 node server
启动,可以得到与 webpack-dev-server
类似的效果。
热更新 与 HMR
热更新,即保存后自动刷新页面,可在对 devServer
进行如下配置:
1 | module.exports = { |
开启后控制台将会打印这样一段文字,并在发生更改时自动刷新页面。
1 | [HMR] Waiting for update signal from WDS... |
若设置了 hot: true
后热更新无效果,可能是 mode: development
与 .browserslistrc
冲突,设置 target
屏蔽即可。
1 | module.exports = { |
但此时为全局热更新,即刷新整个页面。我们希望它可以实现局部热更新,此时仅更新改动的部分,不会影响其他部分组件;控制台会保留之前的数据,并打印修改后的输出。
可在入口文件下进行如下配置:
1 | if( module.hot ) { |
配置后,每次 babel.js
内的代码发生变动时,将只更新发生变动的部分。
区分环境打包
可以通过追加 --env
的 flag 来指定打包时的变量。
在 package.json
中新增两条 script:
1 | { |
此处
NODE_ENV
与对应的值可自定义。
将 webpack 配置文件改写为函数形式:
1 | module.exports = ( env ) => { |
执行 npm run dev
进行打包,可以得到打印结果:
1 | { WEBPACK_BUNDLE: true, WEBPACK_BUILD: true, NODE_ENV: 'development' } |
显而易见,我们可以通过设置自定义变量参数,来区分环境的对配置文件进行配置。
合并多个配置文件
为了更直观的针对不同环境进行配置,可以将配置文件拆分为三个,分别为 webpack.common.js
、webpack.prod.js
和 webpack.dev.js
,放置到 config
文件夹下。
对 package.json
中的 scripts
进行修改:
1 | { |
webpack.prod.js
和 webpack.dev.js
中分别抛出各自的配置文件:
1 | module.exports = { |
在 webpack.common.js
中进行统一处理,此处使用了 webpack 提供的 merge
方法来合并配置:
1 | const { merge } = require( "webpack-merge" ); |