WDS必知必會
在webpack中構(gòu)建本地服務(wù),最重要的一個插件webpack-dev-server,我們俗稱WDS,它承擔(dān)起了在開發(fā)環(huán)境模塊熱加載、本地服務(wù)、接口代理等非常重要的功能。
本文是筆者對wds的一些理解和認識,希望在項目中有所幫助。
正文開始...
在閱讀本文之前,本文會大概從下幾個方面去了解wds
1、了解wds是什么
2、wds在webpack中如何使用
3、項目中使用wds是怎么樣的
4、關(guān)于配置devServer的一些常用配置,代理等
5、wds如何實現(xiàn)模塊熱加載原理
了解webpack-dev-server
顧名思義,這是一個在開發(fā)環(huán)境下的使用的本地服務(wù),它承擔(dān)了一個提供前端靜態(tài)服務(wù)的作用
首先我們快速搭建一個項目,創(chuàng)建一個項目webpack-07-wds執(zhí)行npm init -y,然后安裝基礎(chǔ)支持的插件
npm i webpack webpack-cli html-webpack-plugin webpack-dev-server -D
創(chuàng)建一個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'
})]
}
在根目錄下創(chuàng)建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'
})()
我們已經(jīng)準備好了內(nèi)容,現(xiàn)在需要啟動wds,因此我們需要在在package.json中啟動服務(wù)
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack server"
},
執(zhí)行npm run start
萬事大吉,原來就是一行命令就可以了
但是這行命令的背后實際上有webpack-cli幫我們做了一些事情,實際上在.bin目錄下,當(dāng)你執(zhí)行該命令時,webpack就會啟用告知webpack-dev-server開啟服務(wù),通過webpack根據(jù)webpack.config.js的配置信息進行compiler,然后再交給webpack-dev-server處理
參考官方文檔webpack-dev-server[1]
根目錄新建server.js
// server.js
const webpack = require('webpack');
const webpackDevServer = require('webpack-dev-server');
const webpackConfig = require('./webpack.config.js');
// webpack處理入口配置相關(guān)文件
const compiler = webpack(webpackConfig);
// devServer的相關(guān)配置
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();
終端執(zhí)行node server.js或者在package.json中配置,執(zhí)行npm run server
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack server",
"server": "node ./server.js"
}
打開頁面http://localhost:8081地址,發(fā)現(xiàn)也是ok的
我們注意到可以使用webpack server啟動服務(wù),這個主要是webpack-cli的命令server[2]
關(guān)于在命令行中設(shè)置對應(yīng)的環(huán)境,在以前項目中可能你會看到,process.env.NODE_ENV這樣的設(shè)置
你可以在cli命令中配置,注意只能在最前面設(shè)置,不能像以下這種方式設(shè)置webpack server NODE_ENV=test NODE_API=api,不然會無效
"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中就可以看到設(shè)置的參數(shù)
// 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'
})]
}
你可以設(shè)置--node-env xxx環(huán)境參數(shù)來指定環(huán)境變量
"start:test": "webpack server --node-env test",
更多參數(shù)設(shè)置參考官方cli[3]
wds在webpack中的使用
我們上述是用一個server.js,通過命令行方式,調(diào)用webpack-dev-serverAPI方式去啟動一個本地的靜態(tài)服務(wù),但是實際上,在webpack中直接在配置devServer[4]接口中配置就行。
了解幾個常用的配置
port 指定端口打開頁面
client
overlay 當(dāng)程序錯誤時,瀏覽器頁面全屏警告
webSocketURL 允許指定websocket服務(wù)器
progress 啟動開發(fā)環(huán)境gizp壓縮靜態(tài)html
historyApiFallback 當(dāng)使用路由模式為history時,必須設(shè)置這個,要不然前端刷新直接404頁面
hot模塊熱加載,需要結(jié)合module.hot.accept('xxx/xxx')指定某個模塊熱加載module.hot.accept[5]
open 當(dāng)我們啟動本地服務(wù)時,自動打開指定配置端口的瀏覽器
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時,必須設(shè)置這個,要不然前端刷新會直接404頁面
hot: true, // 模塊熱加載,對應(yīng)模塊須配合module.hot.accept('xxx/xxx.js')指定模塊熱加載
open: true, // 當(dāng)服務(wù)啟動時默認自動直接打開瀏覽器,可以指定打開哪個頁面
}
}
proxy
proxy 這是項目中接觸最多一點,也是初學(xué)者配置代理時常最令人頭疼的事情,實際上proxy本質(zhì)就是將本地的接口路由前綴代理到目標服務(wù)器環(huán)境,同時可以代理多個不同環(huán)境,具體參考以下
...
module.exports = {
...
devServer: {
...
proxy: {
'/j': {
target: 'https://movie.douban.com', // 代理豆瓣
changeOrigin: true
},
'/cmc': {
target: 'https://apps.game.qq.com', // 代理王者榮耀官網(wǎng)
changeOrigin: true, // 必須要加,否則代理接口直接返回html
pathRewrite: { '^/cmc': '/cmc' },
}
}
}
}
我們修改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
// 王者榮耀官網(wǎng)
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()
})()
對應(yīng)的兩個接口數(shù)據(jù)就已經(jīng)在頁面上渲染出來了
對于代理我們會常常容易會犯以下幾個誤區(qū)
第一種, 多個接口代理,第一個直接以/代理,這會造成第二個代理無效,接口直接404,優(yōu)先級會先匹配第一個
{
devServer: {
proxy: {
'/': {
target: 'https://movie.douban.com', // 代理豆瓣
changeOrigin: true,
},
'/cmc': {
target: 'https://apps.game.qq.com', // 代理王者榮耀官網(wǎng)
changeOrigin: true, // 必須要加,否則代理接口直接返回html
pathRewrite: { '^/cmc': '/cmc' },
}
}
}
}
第二種,pathRewrite要不要加,什么時候該加,不知道你發(fā)現(xiàn)沒有我第一個接口攔截并沒有加pathRewrite,但是和第二個加了效果是一樣的。
現(xiàn)在有一個場景,就是你本地測試服務(wù)接口與線上接口是有區(qū)別的,一般你在本地開發(fā)是聯(lián)調(diào)環(huán)境,后端的接口不按照常理出牌,假設(shè)聯(lián)調(diào)環(huán)境后端就是死活不同意統(tǒng)一接口路徑怎么辦?
現(xiàn)在假設(shè)后端接口
聯(lián)調(diào)環(huán)境:/dev/api/cmc/cross
線上環(huán)境:/api/cmc/cross
于是你想到有以下兩種方案:
1、在axios請求攔截根據(jù)環(huán)境變量手動添加前綴,但這不是一種很好的方案,相當(dāng)于把不確定性的邏輯代碼打包到線上去了,有一定風(fēng)險
2、不管開發(fā)環(huán)境還是本地聯(lián)調(diào)環(huán)境都是統(tǒng)一的路徑,僅僅只是在proxy的pathRewrite做處理,這樣風(fēng)險很小,不容易造成線上接口404風(fēng)險
于是這時候pathRewrite的作用就來了,重寫路徑,注意是pathRewrite: { '^/cmc': '/dev/cmc' }
我們僅僅是在開發(fā)環(huán)境重寫了/cmc接口路徑,實際上設(shè)置proxy的代碼并不會打包到線上
{
devServer: {
proxy: {
'/j': {
target: 'https://movie.douban.com', // 代理豆瓣
changeOrigin: true,
},
'/cmc': {
target: 'https://apps.game.qq.com', // 代理王者榮耀官網(wǎng)
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', // 代理王者榮耀官網(wǎng)
//changeOrigin: true,
pathRewrite: { '^/cmc': '/dev/cmc' },
}
}
}
}
如果遇到有多個路由指向的是同一個服務(wù)器怎么辦,別急,官網(wǎng)有方案,你可以這么做
{
devServer: {
proxy: [
{
context: ['/j', '/cmc'],
target: 'https://movie.douban.com'
}
]
}
}
項目常用的就是以上這些了,另外拓展的,比如可以支持本地https,因為默認本地是http,還有支持當(dāng)前可以開啟一個websocket服務(wù),更多配置參考官網(wǎng),或者有更多特別的需求,及時翻閱官網(wǎng)[6]
WDS模塊熱加載原理(HMR)
只更新頁面模塊局部變化的內(nèi)容,無需全站刷新
本質(zhì)上就是webpack-dev-server中的兩個服務(wù),一個express提供的靜態(tài)服務(wù),通過webpack去compiler入口的依賴文件,加載打包內(nèi)存中的bundle.js
第二個模塊熱加載是一個websocket服務(wù),通過socketio,當(dāng)源碼靜態(tài)文件發(fā)生變化時,此時會生成一個manifest文件,這個文件會記錄一個hash以及對應(yīng)文件修改的chunk.js,當(dāng)文件修改時websocket會單獨向瀏覽器發(fā)送一個ws服務(wù),從而更新頁面部分模塊,更多可以參考官網(wǎng)hot-module-replacement[7]
總結(jié)
了解webpack-dev-server是什么,它是一個開發(fā)環(huán)境的靜態(tài)服務(wù)
webpack-dev-server在webpack中的使用
關(guān)于WDS一些常用的配置,比如如何配置接口代理等
淺識HMR模塊熱加載,原生webpack雖然也提供了模塊熱加載,但是webpack-dev-server可以實現(xiàn)模塊熱加載,常用框架,比如vue,內(nèi)部熱加載是用vue-loader實現(xiàn)的,在使用WDS時,默認是開啟了熱加載?。
[1]
webpack-dev-server: https://webpack.docschina.org/api/webpack-dev-server/
[2]
server: https://github.com/webpack/webpack-cli/blob/master/SERVE-OPTIONS-v4.md
[3]
cli: https://webpack.docschina.org/api/cli/
[4]
devServer: https://webpack.docschina.org/configuration/dev-server/
[5]
module.hot.accept: https://webpack.docschina.org/api/hot-module-replacement/
[6]
官網(wǎng): https://webpack.docschina.org/configuration/dev-server/
[7]
hot-module-replacement: https://webpack.docschina.org/concepts/hot-module-replacement/
作者:Maic
歡迎關(guān)注微信公眾號 :web技術(shù)學(xué)苑