你還在傻傻的npm run serve嗎?快來嚐嚐這個!

語言: CN / TW / HK

背景

大家在日常開發中應該經常會有需要切換不同環境地址的情況。當一個專案程式碼切換環境地址時,vue-cli沒有能夠感知檔案的變化,所以代理的還是舊的地址,所以通常我們需要執行npm run serve進行專案重跑,而專案重跑往往意味著長時間的等待,非常痛苦!

image.png

方案調研

事實上,其實我們只是需要重啟webpack為我們啟動的proxy代理服務,或許能夠從webpack的代理服務外掛中找到解決方法。

從webpack官網可以看到proxy服務其實是由 http-proxy-middleware提供的,或許我們能夠從中找到解決方法。

初步方案

在http-proxy-middleware的配置選項中,除了我們常見的target,還有router。router返回一個字串的服務地址,當兩個選項都配置了的情況下,會優先使用router函式的返回值,只有當router的返回值不可用時,才會使用target的值。

我們可以利用這一點來重新配置我們的專案程式碼。參考文件在這裡

``` // vue.config.js const { defineConfig } = require('@vue/cli-service') const { proxy } = require('./environments/proxy.js') module.exports = defineConfig({ devServer:{ proxy }, })

```

``` // proxy.js const fs = require('fs') const path = require('path') const encoding = 'utf-8'

const getContent = filename => { const dir = path.resolve(process.cwd(), 'environments') return fs.readFileSync(path.resolve(dir, filename), { encoding }) }

const jsonParse = obj => { return Function('"use strict";return (' + obj + ')')() }

const getConfig = () => { try { return jsonParse(getContent('proxy-config.json')) } catch (e) { return {} } }

module.exports = { proxy: { // 介面匹配規則自行修改 '/api': { // 這裡必須要有字串來進行佔位 // 如果報錯Invaild Url,將target改成有效的url字串即可,如http://localhost:9001 target: 'that must have a empty placeholder', changeOrigin: true, router: () => (getConfig() || {}).target || '' } } }

// proxy-config.json { "target": "http://localhost:9001" } `` 自此,當我們需要修改環境地址時,只需要修改proxy-config.json檔案便能夠實時生效,不再需要npm run serve`!

重點程式碼分析

實現程式碼中其實最主要的就是getContent這個方法,我們專案在每次發起http請求時都會呼叫router中的函式,而getContent則會通過node的fs服務,對我們的環境地址檔案進行實時讀取,從而指向我們最新修改的環境地址。

方案總結

在按照參考文件配置了專案程式碼之後,我們發現確實能夠及時指向新的環境地址,再也不需要重啟程式碼,不需要長時間的等待了。但是,我們多了兩個需要維護的檔案,每次我們修改環境地址時,不僅需要修改config中的api,還需要修改proxy-config.json中的target!

有沒有可能在只需要修改config檔案的情況下,實現代理地址動態修改呢?

方案優化

從上面的重點程式碼分析中,可以看到只要我們可以在router函式執行時,拿到正確的config檔案中匯出的api屬性的值,也可以實現同樣的效果!

這是不是意味著只要我們在函式中對config檔案進行require請求,讀取api的值,再return出去就能及時修改代理指向了呢?

沒錯,你會發現無論你怎麼修改,函式內require取到的api永遠是不變的,還是服務剛啟動時的環境地址。

image.png

參考原始碼可以知道,這是因為我們在使用require請求檔案資訊時,node會解析出我們傳入的字串的檔案路徑的絕對路徑,並且以絕對路徑為鍵值,對該檔案進行快取

因此,如果我們在執行require函式時打斷點進行觀察的話,會發現require上面有一個cache快取了已經載入過的檔案。

image.png

這也恰恰說明了只要我們能夠刪除掉檔案儲存在require中的快取,我們就能夠拿到最新的檔案內容,那麼我們也可以據此得出我們的最終優化方案。

``` // vue.config.js const hotRequire = modulePath => { // require.resolve可以通過相對路徑獲取絕對路徑 // 以絕對路徑為鍵值刪除require中的對應檔案的快取 delete require.cache[require.resolve(modulePath)] // 重新獲取檔案內容 const target = require(modulePath) return target }

... proxy: { '/api': { // 如果router有效優先取router返回的值 target: 'that must have a empty placeholder', changeOrigin: true, // 每次發起http請求都會執行router函式 router: () => (hotRequire('./src/utils/config') || {}).api || '', ws: true, pathRewrite: { '^/api': '' } } } ``` 自此,我們專案修改環境地址將不在需要重啟專案,也不需要維護額外的資料夾,再也不需要痛苦等待了!