你還在傻傻的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': '' } } } ``` 自此,我們項目修改環境地址將不在需要重啟項目,也不需要維護額外的文件夾,再也不需要痛苦等待了!