02 【开发服务器 资源模块】
02 【开发服务器 资源模块】
1.开发服务器&自动化
webpack-dev-server 可用于快速开发应用程序。请查阅 开发指南 开始使用。
当前页面记录了影响 webpack-dev-server
(简写: dev-server
) version >= 4.0.0 配置的选项。
Warning
webpack-dev-server v4.0.0+
要求node >= v12.13.0
、webpack >= v4.37.0
(但是我们推荐使用webpack >= v5.0.0
)和webpack-cli >= v4.7.0
。
devServer:开发服务器,用来自动化(自动编译,自动打开浏览器,自动刷新浏览器)
特点:只会在内存中编译打包,不会有任何输出(不会在项目中新建一个文件夹)
1.1 下载包
pnpm i webpack-dev-server -D
1.2 配置
1.2.1 基础配置
在 webpack.config.js
文件中进行配置
module.exports = {
...
// 热更新
devServer: {
static: path.resolve(__dirname, './dist'), // 默认是把/dist目录当作web服务的根目录
host: "localhost", // 启动服务器域名
port: "3000", // 启动服务器端口号
open: true, // 是否自动打开浏览器
compress: true, //可选择开启gzips压缩功能,对应静态资源请求的响应头里的Content-Encoding: gzip
hot:true, // 启用热模块功能
liveReload: true, // 启用热加载功能
},
}
为了方便,我们配置一下工程的脚本命令,在package.json的scripts里。
{
//...
"scripts": {
//...
"dev": "webpack serve --mode development"
}
}
注意!如果您需要指定配置文件的路径,请在命令的后面添加 --config [path], 比如:
webpack serve --mode development --config webpack.config.js
上述是一个基本的示例,我们可以根据自己的需求定制化devServer的参数对象,比 如添加响应头,开启代理来解决跨域问题, http2, https等功能。
1.2.2 添加响应头
有些场景需求下,我们需要为所有响应添加headers,来对资源的请求和响应打入 标志,以便做一些安全防范,或者方便发生异常后做请求的链路追踪。比如:
// webpack-config
module.exports = {
//...
devServer: {
// 在所有响应中添加首部内容
headers: {
'X-Fast-Id': 'p3fdg42njghm34gi9ukj',
},
},
};
这时,在浏览器中右键检查(或者使用f12快捷键),在Network一栏查看任意资源访 问,我们发现响应头里成功打入了一个FastId。
1.2.3 开启代理
我们打包出的 js bundle 里有时会含有一些对特定接口的网络请求(ajax/fetch). 要注意,此时客户端地址是在 http://localhost:3000/ 下,假设我们的接口来自 http://localhost:4001/ ,那么毫无疑问,此时控制台里会报错并提示你跨域。 如何解决这个问题? 在开发环境下,我们可以使用devServer自带的proxy功 能:
module.exports = {
//...
devServer: {
proxy: {
'/api': 'http://localhost:4001',
},
},
};
现在,对 /api/users 的请求会将请求代理到 http://localhost:4001/api/users 。 如 果不希望传递/api,则需要重写路径:
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://localhost:4001',
pathRewrite: { '^/api': '' },
},
},
},
};
默认情况下,将不接受在 HTTPS 上运行且证书无效的后端服务器。 如果需要,可以 这样修改配置:
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'https://other-server.example.com',
secure: false,
},
},
},
}
1.2.4 http
如果想让我们的本地http服务变成https服务,我们只需要这样配置:
module.exports = {
//...
devServer: {
https: true, // https//localhost...
},
}
注意,此时我们访问http://localhost:port 是无法访问我们的服务的,我们需要在地 址栏里加前缀:https: 注意:由于默认配置使用的是自签名证书,所以有得浏览器会 告诉你是不安全的,但我们依然可以继续访问它。 当然我们也可以提供自己的证书 ——如果有的话:
module.exports = {
devServer: {
https: {
cacert: './server.pem',
pfx: './server.pfx',
key: './server.key',
cert: './server.crt',
passphrase: 'webpack-dev-server',
requestCert: true,
},
},
};
1.2.5 http2
如果想要配置http2,那么直接设置:
devServer: {
http2: true, // https//localhost...
},
即可,http2默认自带https自签名证书,当然我们仍然可以通过https配置项来使用 自己的证书。
1.2.6 historyApiFallback
如果我们的应用是个SPA(单页面应用),当路由到/some时(可以直接在地址栏里 输入),会发现此时刷新页面后,控制台会报错。
GET http://localhost:3000/some 404 (Not Found)
此时打开network,刷新并查看,就会发现问题所在———浏览器把这个路由当作了 静态资源地址去请求,然而我们并没有打包出/some这样的资源,所以这个访问无疑 是404的。 如何解决它? 这种时候,我们可以通过配置来提供页面代替任何404的静 态资源响应:
module.exports = {
//...
devServer: {
// 任意的 404 响应都被替代为 index.html
// 基于node connect-history-api-fallback包实现,主要应用于history路由刷新页面404的场景
historyApiFallback: true,
},
}
此时重启服务刷新后发现请求变成了index.html。 当然,在多数业务场景下,我们 需要根据不同的访问路径定制替代的页面,这种情况下,我们可以使用rewrites这个 配置项。 类似这样:
module.exports = {
//...
devServer: {
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html' },
{ from: /^\/subpage/, to: '/views/subpage.html' },
{ from: /./, to: '/views/404.html' },
],
},
},
};
1.2.7 开发服务器主机
如果你在开发环境中起了一个devserve服务,并期望你的同事能访问到它,你只 需要配置:
module.exports = {
//...
devServer: {
host: '0.0.0.0',
},
};
这时候,如果你的同事跟你处在同一局域网下,就可以通过局域网ip来访问你的服务啦。
启动服务的时候有个ipv4的网络地址,可以通过那个访问
1.2.8 progress
在浏览器中以百分比显示编译进度。
module.exports = {
//...
devServer: {
client: {
progress: true,
},
},
};
1.2.9 overlay
boolean = true` `object: { errors boolean = true, warnings boolean = true }
当出现编译错误或警告时,在浏览器中显示全屏覆盖。
module.exports = {
//...
devServer: {
client: {
overlay: true,
},
},
};
如果你只想显示错误信息:
module.exports = {
//...
devServer: {
client: {
overlay: {
errors: true,
warnings: false,
},
},
},
};
1.3 模块热替与热加载
模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
- 保留在完全重新加载页面期间丢失的应用程序状态。
- 只更新变更内容,以节省宝贵的开发时间。
- 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
启用 webpack 的 热模块替换 特性,需要配置devServer.hot参数:
module.exports = {
//...
devServer: {
hot: true,
},
};
此时我们实现了基本的模块热替换功能。
HMR 加载样式 如果你配置了style-loader,那么现在已经同样支持样式文件的 热替换功能了。
module.exports={ module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'], }, ], }, }
如果已经通过 HotModuleReplacementPlugin
启用了模块热替换(Hot Module Replacement),则它的接口将被暴露在 module.hot
属性下面。通常,用户先要检查这个接口是否可访问,然后再开始使用它。举个例子,你可以这样 accept
一个更新的模块:
if (module.hot) {
// 需要使用热替换的js模块
module.hot.accept('./input.js', function() {
// 使用更新过的 library 模块执行某些操作...
})
}
热加载(文件更新时,自动刷新我们的服务和页面)
新版的webpack-dev-server 默认已经开启了热加载的功能。 它对应的参数是devServer.liveReload,默认为 true。 注意,如果想要关掉它,要将liveReload设置为false的同时,也要关掉 hot
module.exports = {
//...
devServer: {
liveReload: false, //默认为true,即开启热更新功能。
},
};
1.4 启动
npx webpack serve(webpack-cli: 4.x)
;npx webpack-dev-server(webpack-cli 3.x)
;
2.资源模块Asset Modules
官方说明:https://webpack.docschina.org/guides/asset-modules
该方法需将资源在 JS 中通过 import 进行导入或css中进行导入
// js 文件导入
import 命名 from '资源路径'
// css 文件引用
.box {
background-image: url('资源路径');
}
资源模块类型
- asset/resource:发送一个单独的文件并导出 URL
- asset/inline:导出一个资源的 Data URI ( base64 )
- asset/source:导出资源的源代码
- asset:在导出一个资源的 Data URI 和发送一个单独的文件之间自动进行选择
2.1 resource
module.exports = {
output: {
path: path.resolve(__dirname, "dist"),
filename: "js/bundle.js",
assetModuleFilename: '[contenthash][ext][query]'
},
...
module: {
rules: [
{
// 监听资源文件
test: /\.png$/i,
// 设置资源类型
type: 'asset/resource',
generator: {
// 将图片文件输出到 static/imgs 目录中
// 将图片文件命名 [hash:8][ext][query]
// contenthash: 根据内容生成hash值 或者 [hash:8]: hash值取8位
// [ext]: 使用之前的文件扩展名
// [query]: 添加之前的query参数
filename: "[hash:8][ext][query]",
}
}
]
}
}
generator中自定义的输出文件名优先级大于output中的assetModuleFilename。
运行指令
npx webpack
此时输出文件目录:
![image-20220802183005730](https://i0.hdslb.com/bfs/album/16dbd02a9aaa66d38cfeb09edd90daf8abb65516.png)
展示在页面
index.js
import imgsrc from './assets/img-1.png'
const img = document.createElement('img')
img.src = imgsrc
document.body.appendChild(img)
打开浏览器
![image-20220802182942299](https://i0.hdslb.com/bfs/album/dbc3b47934f99d767c030c4ac8753115ee93c308.png)
2.2 inline
module.exports = {
...
module: {
rules: [
{
// 监听资源文件
test: /\.svg$/i,
// 设置资源类型
type: 'asset/inline'
}
]
}
}
展示在页面
index.js
import logoSvg from './assets/webpack-logo.svg'
const img2 = document.createElement('img')
img2.style.cssText = 'width: 600px; height: 200px'
img2.src = logoSvg
document.body.appendChild(img2)
打开浏览器:
![image-20220802183156821](https://i0.hdslb.com/bfs/album/9a096dc804006e1f9ca38e81ad644b1ae9ffff1a.png)
可见, .svg
文件都将作为 data URI 注入到 bundle 中。
2.3 source
module.exports = {
...
module: {
rules: [
{
// 监听资源文件
test: /\.txt$/i,
// 设置资源类型
type: 'source'
}
]
}
}
展示在页面
index.js
import exampleTxt from './assets/example.txt'
const block = document.createElement('div')
block.style.cssText = 'width: 200px; height: 200px; background: aliceblue'
block.textContent = exampleTxt
document.body.appendChild(block)
打开浏览器:
![image-20220802183416499](https://i0.hdslb.com/bfs/album/6c7209704789904e17081b6715d19931f738f4c2.png)
所有 .txt
文件将原样注入到 bundle.js
中。
2.4 asset
module.exports = {
...
module: {
rules: [
{
// 监听资源文件
test: /\.jpg$/i,
// 设置资源类型
type: 'asset',
// 默认为4kb
// 小于设置的大小则转为 64 位图,否则转 URL
parser: {
dataUrlCondition: {
maxSize: 4 * 1024, // 4kb
maxSize: 4 * 1024 * 1024// 4mb
}
},
generator: {
// 生成资源名称
filename: 'assets/images/[name][ext]'
}
}
]
}
}
展示在页面
index.js
import jpgMap from './assets/qianfeng-sem.jpg'
const img3 = document.createElement('img')
img3.style.cssText = 'width: 600px; height: 240px; display: block'
img3.src = jpgMap
document.body.appendChild(img3)
打开浏览器:
![image-20220802185641061](https://i0.hdslb.com/bfs/album/e265febef611636aa3240877f41c5ad6e04a8222.png)
![image-20220802185651259](https://i0.hdslb.com/bfs/album/7112d84bd06c940292d95a4f5347d9f28a069aee.png)
发现当前的.jpg
文件被打包成了单独的文件,因为此文件大小超过了 4kb
。