Open maicFir opened 2 years ago
在webpack中构建本地服务,最重要的一个插件webpack-dev-server,我们俗称WDS,它承担起了在开发环境模块热加载、本地服务、接口代理等非常重要的功能。
webpack
webpack-dev-server
WDS
模块热加载
本地服务
接口代理
本文是笔者对wds的一些理解和认识,希望在项目中有所帮助。
wds
正文开始...
在阅读本文之前,本文会大概从下几个方面去了解wds
1、了解wds是什么
2、wds在 webpack 中如何使用
3、项目中使用wds是怎么样的
4、关于配置devServer的一些常用配置,代理等
devServer
5、wds如何实现模块热加载原理
顾名思义,这是一个在开发环境下的使用的本地服务,它承担了一个提供前端静态服务的作用
开发环境
首先我们快速搭建一个项目,创建一个项目webpack-07-wds执行npm init -y,然后安装基础支持的插件
webpack-07-wds
npm init -y
npm i webpack webpack-cli html-webpack-plugin webpack-dev-server -D
创建一个webpack.config.js
webpack.config.js
const path = require('path'); const htmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'js/[name].js' }, plugins: [ new htmlWebpackPlugin({ template: './public/index.html' }) ] };
在根目录下创建public,新建html文件
public
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>webpack-for-dev-server</title> </head> <body> <div id="app"></div> </body> </html>
我们在入口文件写入一段简单的代码
// index (() => { const appDom = document.getElementById('app'); appDom.innerHTML = 'hello webpack for wds'; })();
我们已经准备好了内容,现在需要启动wds,因此我们需要在在package.json中启动服务
package.json
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack server" },
执行npm run start
npm run start
万事大吉,原来就是一行命令就可以了
但是这行命令的背后实际上有webpack-cli帮我们做了一下事情,实际上在.bin目录下,当你执行该命令时,webpack就会启用告知webpack-dev-server开启服务,通过webpack根据webpack.config.js的配置信息进行compiler,然后再交给webpack-dev-server处理
webpack-cli
.bin
compiler
参考官方文档webpack-dev-server
根目录新建server.js
server.js
// server.js const webpack = require('webpack'); const webpackDevServer = require('webpack-dev-server'); const webpackConfig = require('./webpack.config.js'); // webpack处理入口配置相关文件 const compiler = webpack(webpackConfig); // devServer的相关配置 const devServerOption = { port: 8081, static: { directory: path.join(__dirname, 'public') }, compress: true // 开启gizps压缩public中的html }; const server = new webpackDevServer(devServerOption, compiler); const startServer = async () => { console.log('server is start'); await server.start(); }; startServer();
终端执行node server.js或者在package.json中配置,执行npm run server
node server.js
npm run server
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack server", "server": "node ./server.js" }
打开页面http://localhost:8081地址,发现是ok的
http://localhost:8081
ok
我们注意到可以使用webpack server启动服务,这个主要是webpack-cli的命令server
webpack server
关于在命令行中设置对应的环境,在以前项目中可能你会看到,process.env.NODE_ENV这样的设置
process.env.NODE_ENV
你可以在cli命令中配置,注意只能在最前面设置,不能像以下这种方式设置webpack server NODE_ENV=test NODE_API=api,不然会无效
cli
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "NODE_ENV=test NODE_API=api webpack server", "server": "node ./server.js" },
在webpack.config.js中就可以看到设置的参数
// webpack.config.js const path = require('path'); const htmlWebpackPlugin = require('html-webpack-plugin'); console.log(process.env.NODE_ENV, process.env.NODE_API); // test api module.exports = { entry: './src/index.js', mode: 'development', output: { path: path.resolve(__dirname, 'dist'), filename: 'js/[name].js' }, plugins: [ new htmlWebpackPlugin({ template: './public/index.html' }) ] };
你可以设置--node-env xxx环境参数来指定环境变量
--node-env xxx
"start:test": "webpack server --node-env test",
更多参数设置参考官方cli
我们上述是用一个server.js,通过命令行方式,调用webpack-dev-serverAPI 方式去启动一个本地的静态服务,但是实际上,在webpack中直接在配置devServer接口中配置就行。
了解几个常用的配置
port
client
progress
historyApiFallback
hot
module.hot.accept('xxx/xxx')
module.exports = { ... devServer: { port: '9000', client: { progress: true, // 启用gizp overlay: { errors: true, // 如果有错误会有蒙层 warnings: false, }, webSocketURL: { hostname: '0.0.0.0', pathname: '/ws', port: 8080, protocol: 'ws', } }, historyApiFallback: true, // 使用路由模式为history时,必须设置这个,要不然前端刷新会直接404页面 hot: true, // 模块热加载,对应模块须配合module.hot.accept('xxx/xxx.js')指定模块热加载 open: true, // 当服务启动时默认自动直接打开浏览器,可以指定打开哪个页面 } }
proxy 这是项目中接触最多一点,也是初学者配置代理时常最令人头疼的事情,实际上proxy本质就是将本地的接口路由前缀代理到目标服务器环境,可以同时代理多个不同环境,具体参考以下
proxy
接口路由前缀
... module.exports = { ... devServer: { ... proxy: { '/j': { target: 'https://movie.douban.com', // 代理豆瓣 changeOrigin: true }, '/cmc': { target: 'https://apps.game.qq.com', // 代理王者荣耀官网 changeOrigin: true, // 必须要加,否则代理接口直接返回html pathRewrite: { '^/cmc': '/cmc' }, } } } }
我们修改index.js
index.js
(() => { const $ = (id) => document.getElementById(id); const appDomMovie = $('movie'); const gameDom = $('wang'); // appDom.innerHTML = 'hello webpack for wds,'; // https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=%E7%94%B5%E5%BD%B1&start=0 // 豆瓣电影 const featchMovie = async () => { const { data = [] } = await (await fetch('/j/new_search_subjects?sort=U&range=0,10&tags=%E7%94%B5%E5%BD%B1&start=0')).json(); // console.log(data) const divDom = document.createElement('div'); let str = ''; data.forEach((item) => { const { title, rate } = item; str += ` <span>${title},${rate}</span>`; }); divDom.innerHTML = str; appDomMovie.appendChild(divDom); }; featchMovie(); const wangzherongyao = async () => { const divDom = document.createElement('div'); // https://apps.game.qq.com/cmc/cross?serviceId=18&filter=tag&sortby=sIdxTime&source=web_pc&limit=20&logic=or&typeids=1%2C2&exclusiveChannel=4&exclusiveChannelSign=8a28b7e82d30142c1a986bb7acdcc068&time=1655732988&tagids=931 // 王者荣耀官网 const { data: { items = [] } } = await ( await fetch( '/cmc/cross?serviceId=18&filter=tag&sortby=sIdxTime&source=web_pc&limit=20&logic=or&typeids=1%2C2&exclusiveChannel=4&exclusiveChannelSign=8a28b7e82d30142c1a986bb7acdcc068&time=1655732988&tagids=931' ) ).json(); let str = ''; console.log(items); items.forEach((item) => { const { sTitle, sIMG } = item; str += `<div> <img src=${sIMG} /> <div>${sTitle}</div> </div>`; }); divDom.innerHTML = str; gameDom.appendChild(divDom); }; wangzherongyao(); })();
对应的两个接口数据就已经在页面上渲染出来了
对于代理我们会常常容易会犯以下几个误区
几个误区
/
{ devServer: { proxy: { '/': { target: 'https://movie.douban.com', // 代理豆瓣 changeOrigin: true, }, '/cmc': { target: 'https://apps.game.qq.com', // 代理王者荣耀官网 changeOrigin: true, // 必须要加,否则代理接口直接返回html pathRewrite: { '^/cmc': '/cmc' }, } } } }
pathRewrite
现在有一个场景,就是你本地测试服务接口与线上接口是有区别的,一般你在本地开发是联调环境,后端的接口不按照常理出牌,假设联调环境后端就是死活不同意统一接口路径怎么办?
现在假设后端接口
联调环境:/dev/api/cmc/cross
/dev/api/cmc/cross
线上环境是/api/cmc/cross
/api/cmc/cross
于是你想到有以下两种方案:
1、在 axios 请求拦截根据环境变量手动添加前缀,但是这不是一种很好的方案,相当于把不确定性的逻辑代码打包到线上去了,有一定风险
环境变量
2、不管开发环境还是本地联调环境都是统一的路径,仅仅只是在proxy的pathRewrite做处理,这样风险很小,不容易造成线上接口 404 风险
于是这时候pathRewrite的作用就来了,重写路径,注意是pathRewrite: { '^/cmc': '/dev/cmc' }
pathRewrite: { '^/cmc': '/dev/cmc' }
我们仅仅是在开发环境重新了/cmc接口路径,实际上代码环境的代码并不会打包到线上
/cmc
{ devServer: { proxy: { '/j': { target: 'https://movie.douban.com', // 代理豆瓣 changeOrigin: true, }, '/cmc': { target: 'https://apps.game.qq.com', // 代理王者荣耀官网 changeOrigin: true, // 必须要加,否则代理接口直接返回html pathRewrite: { '^/cmc': '/dev/cmc' }, } } } }
changeOrigin:true
changeOrigin
devServer: { proxy: { '/j': { target: 'https://movie.douban.com', // 代理豆瓣 // changeOrigin: true, pathRewrite: { '^/j': '/j' }, }, '/cmc': { target: 'https://apps.game.qq.com', // 代理王者荣耀官网 //changeOrigin: true, pathRewrite: { '^/cmc': '/dev/cmc' }, } } } }
如果遇到有多个路由指向的是同一个服务器怎么办,别急,官网有方案,你可以这么做
{ devServer: { proxy: [ { context: ['/j', '/cmc'], target: 'https://movie.douban.com' } ]; } }
项目常用的就是以上这些了,另外拓展的,比如可以支持本地https,因为默认本地是http,还有支持当前可以开启一个websocket服务,更多配置参考官网,或者有更多特别的需求,及时翻阅官网
https
http
websocket
只更新页面模块变化的内容,无需全站刷新
本质上就是webpack-dev-server中的两个服务,一个express提供的静态服务,通过webpack去compiler入口的依赖文件,加载打包内存中的bundle.js
express
bundle.js
第二个模块热加载是一个websocket服务,通过socketio,当源码静态文件发生变化时,此时会生成一个manifest文件,这个文件会记录一个hash以及对应文件修改的chunk.js,当文件修改时websocket会单独向浏览器发送一个ws服务,从而更新页面部分模块,更多可以参考官网hot-module-replacement
socketio
manifest
hash
chunk.js
ws
了解webpack-dev-server是什么,它是一个开发环境的静态服务
webpack-dev-server在 webpack 中的使用
关于WDS一些常用的配置,比如如何配置接口代理等
浅识HMR模块热加载,原生webpack虽然也提供了模块热加载,但是webpack-dev-server可以实现模块热加载,常用框架,比如vue,内部热加载是用vue-loader实现的,在使用WDS时,默认是开启了热加载的。
HMR
vue
vue-loader
在
webpack
中构建本地服务,最重要的一个插件webpack-dev-server
,我们俗称WDS
,它承担起了在开发环境模块热加载
、本地服务
、接口代理
等非常重要的功能。本文是笔者对
wds
的一些理解和认识,希望在项目中有所帮助。正文开始...
在阅读本文之前,本文会大概从下几个方面去了解
wds
1、了解
wds
是什么2、
wds
在 webpack 中如何使用3、项目中使用
wds
是怎么样的4、关于配置
devServer
的一些常用配置,代理等5、
wds
如何实现模块热加载原理了解
webpack-dev-server
顾名思义,这是一个在
开发环境
下的使用的本地服务
,它承担了一个提供前端静态服务的作用首先我们快速搭建一个项目,创建一个项目
webpack-07-wds
执行npm init -y
,然后安装基础支持的插件创建一个
webpack.config.js
在根目录下创建
public
,新建html
文件我们在入口文件写入一段简单的代码
我们已经准备好了内容,现在需要启动
wds
,因此我们需要在在package.json
中启动服务执行
npm run start
万事大吉,原来就是一行命令就可以了
但是这行命令的背后实际上有
webpack-cli
帮我们做了一下事情,实际上在.bin
目录下,当你执行该命令时,webpack
就会启用告知webpack-dev-server
开启服务,通过webpack
根据webpack.config.js
的配置信息进行compiler
,然后再交给webpack-dev-server
处理参考官方文档webpack-dev-server
根目录新建
server.js
终端执行
node server.js
或者在package.json
中配置,执行npm run server
打开页面
http://localhost:8081
地址,发现是ok
的我们注意到可以使用
webpack server
启动服务,这个主要是webpack-cli
的命令server关于在命令行中设置对应的环境,在以前项目中可能你会看到,
process.env.NODE_ENV
这样的设置你可以在
cli
命令中配置,注意只能在最前面设置,不能像以下这种方式设置webpack server NODE_ENV=test NODE_API=api,不然会无效在
webpack.config.js
中就可以看到设置的参数你可以设置
--node-env xxx
环境参数来指定环境变量更多参数设置参考官方cli
wds 在 webpack 中的使用
我们上述是用一个
server.js
,通过命令行方式,调用webpack-dev-server
API 方式去启动一个本地的静态服务,但是实际上,在webpack
中直接在配置devServer接口中配置就行。了解几个常用的配置
port
指定端口打开页面client
progress
启动开发环境 gizp 压缩静态 htmlhistoryApiFallback
当使用路由模式为 history 时,必须设置这个,要不然前端刷新直接 404 页面hot
模块热加载,需要结合module.hot.accept('xxx/xxx')
指定某个模块热加载module.hot.acceptproxy
proxy
这是项目中接触最多一点,也是初学者配置代理时常最令人头疼的事情,实际上proxy
本质就是将本地的接口路由前缀
代理到目标服务器环境,可以同时代理多个不同环境,具体参考以下我们修改
index.js
对应的两个接口数据就已经在页面上渲染出来了
对于代理我们会常常容易会犯以下
几个误区
/
代理,这会造成第二个代理无效,接口直接 404,优先级会先匹配第一个pathRewrite
要不要加,什么时候该加,不知道你发现没有我第一个接口拦截并没有加pathRewrite
,但是和第二个加了效果是一样的。现在有一个场景,就是你本地测试服务接口与线上接口是有区别的,一般你在本地开发是联调环境,后端的接口不按照常理出牌,假设联调环境后端就是死活不同意统一接口路径怎么办?
现在假设后端接口
联调环境:
/dev/api/cmc/cross
线上环境是
/api/cmc/cross
于是你想到有以下两种方案:
1、在 axios 请求拦截根据
环境变量
手动添加前缀,但是这不是一种很好的方案,相当于把不确定性的逻辑代码打包到线上去了,有一定风险2、不管开发环境还是本地联调环境都是统一的路径,仅仅只是在
proxy
的pathRewrite
做处理,这样风险很小,不容易造成线上接口 404 风险于是这时候
pathRewrite
的作用就来了,重写路径,注意是pathRewrite: { '^/cmc': '/dev/cmc' }
我们仅仅是在开发环境重新了
/cmc
接口路径,实际上代码环境的代码并不会打包到线上changeOrigin:true
,像下面这种丢失了changeOrigin
是不行的如果遇到有多个路由指向的是同一个服务器怎么办,别急,官网有方案,你可以这么做
项目常用的就是以上这些了,另外拓展的,比如可以支持本地
https
,因为默认本地是http
,还有支持当前可以开启一个websocket
服务,更多配置参考官网,或者有更多特别的需求,及时翻阅官网WDS 模块热加载原理(HMR)
只更新页面模块变化的内容,无需全站刷新
本质上就是
webpack-dev-server
中的两个服务,一个express
提供的静态服务,通过webpack
去compiler
入口的依赖文件,加载打包内存中的bundle.js
第二个模块热加载是一个
websocket
服务,通过socketio
,当源码静态文件发生变化时,此时会生成一个manifest
文件,这个文件会记录一个hash
以及对应文件修改的chunk.js
,当文件修改时websocket
会单独向浏览器发送一个ws
服务,从而更新页面部分模块,更多可以参考官网hot-module-replacement总结
了解
webpack-dev-server
是什么,它是一个开发环境的静态服务webpack-dev-server
在 webpack 中的使用关于
WDS
一些常用的配置,比如如何配置接口代理等浅识
HMR
模块热加载,原生webpack
虽然也提供了模块热加载,但是webpack-dev-server
可以实现模块热加载,常用框架,比如vue
,内部热加载是用vue-loader
实现的,在使用WDS
时,默认是开启了热加载的。