publicPath
是什麼一般狀況下,覆蓋 CRA 配置的解決方案有兩種:css
npm run eject
react-app-rewired
或者 rescripts
等第三方工具這裏使用第二種方式,緣由也比較簡單,由於魔改 webpack
是須要很大的勇氣的,且 npm run eject
不可逆(雖然能夠經過其餘方式恢復,但太麻煩了),而且對於須要覆蓋的配置,咱們也是有針對性的,因此使用第三方工具會更好一些。html
我這裏使用 rescripts
這個庫,它與 create-react-rewired
大同小異。前端
首先,對於 single-spa
中要加載的微前端應用,咱們須要提供諸如 bootstrap
、 mount
以及 unmount
等若干生命週期鉤子,但 CRA
中 webpack
的默認打包方式不會將這些方法暴露出來,因此聲明配置以下:node
config.output.library = `${name}-[name]`; config.output.libraryTarget = 'umd'; config.output.jsonpFunction = `webpackJsonp_${name}`; config.output.globalObject = 'window';
讓咱們來挨個分析下每行配置的做用及意義:react
library
和 libraryTarget
是共同生效的,默認狀況下,libraryTarget
的值是 var
,即在 entry file
在執行後,會返回一個變量,咱們這裏使用 umd
的緣由是由於,在主應用中,咱們仍然會使用 module system
,不管是 webpack
仍是 Systemjs
,所以 umd
是一個最佳選擇,由於它適配全部的 module system
。jsonpFunction
是用來按需加載 chunk
的工具函數,因爲微前端應用中,一個頁面中,會同時存在多個 webpack
運行時環境,因此可能會存在命名衝突,致使加載 chunk
時出現意想不到的後果,手動設置一個惟一的命名能夠解決這個衝突globalObject
自己屬性的默認值便是 window
,但因爲 libraryTarget
咱們設置成了 umd
,對於 nodejs 環境,全局對象時 global
而非 window
,這裏顯示地聲明它是 window
證實微前端應用只是針對 browser 而言的HMR 功能通常是針對開發環境而言的,對於爲何微前端應用在開發環境要關閉 HMR,我尚未深刻研究,但關閉它是官方代碼庫示例中提供的最佳實踐。webpack
在 CRA 中,HMR 功能是分兩部分存在的,一個是 webpack.config.devServer
提供的,另外一個是 CRA 本身實現的 webpackHotDevClient
,咱們需依次移除或者關閉它們。git
首先關閉 webpack.devServer
的 HMR 功能,很簡單,添加以下配置:github
config.hot = false; config.watchContentBase = false; config.liveReload = false;
再來移除 webpackHotDevClient
,這個會稍微麻煩一些,由於它是直接聲明在 webpack.config.entry
中的,因此使用下面的代碼移除它:web
config.entry = config.entry.filter( (e) => !e.includes('webpackHotDevClient') );
同時還有 HotModuleReplacementPlugin
插件,它提供 css 的 HMR 功能,利用相同的代碼移除它:npm
config.plugins = config.plugins.filter( (p) => !(p instanceof webpack.HotModuleReplacementPlugin) );
這樣就徹底從 CRA 中移除了 HMR 的功能。
以 html-entry
爲前提實現的微前端框架,構建前提既是微前端應用要支持跨域訪問,對於部署階段,咱們能夠在 web server
或代理層完成該步驟,對於開發階段,咱們則須要對 devServer
進行一些調整,由於它默認是不支持跨域訪問的。
解決跨域問題除了配置反向代理以外,還可使用 CORS
來解決,在 devServer
中,顯示使用後者更加快捷,添加以下代碼便可:
config.headers = { 'Access-Control-Allow-Origin': '*', };
這樣既實現了最簡單的 CORS
配置,但知足開發環境中對於跨域訪問的支持,足夠了。
很簡單,聲明以下配置便可:
config.historyApiFallback = true;
推薦使用 history
做爲微前端子應用的路由模式,由於在全局路由解析中,針對 hash
的匹配並不像 url
那樣靈活,同時也存在一些微妙的 bug。
也十分簡單,使用以下代碼:
config.port = 7101;
這裏的 7101
,是微前端子應用監聽的接口,建議不論在開發階段,仍是在部署階段,都使用相同的接口以減小分辨接口的心智負擔。
須要單獨指定 publicPath
的緣由是由於,當前咱們的微前端架構依賴於 html entry
,每一個路徑所對應的 entry
所加載的微前端應用,必然會有一些從 publicPath
加載資源的代碼。
但在項目中,除非將全部子應用項目中的靜態資源目錄集合到一塊兒,託管在主應用中,或者使用 CDN
,否則在項目啓動時,會遇到不少 404
的錯誤,其根本緣由是由於,以前請求的靜態資源,並非託管在主應用的服務器上,而是子應用的,所以如何在主應用或者代理層中映射這些靜態資源的加載請求,是必需要解決的事情。
默認狀況下,CRA 的 publicPath
是 /
,即相對於當前服務器的域名,子應用在主應用中加載時,所相對的是主應用服務器的域名,因此這裏須要對每一個子應用聲明不一樣的 publicPath
。
在 CRA 中聲明 publicPath
有兩種,
package.json
中添加 homepage
字段PUBLIC_PATH
環境變量當前我使用的方式是第一種,由於第二種在當前的 CRA 版本中不生效(感受像是一個 bug),以下:
{ "name": "vcapp-login", "homepage": "/login", ... }
除了針對靜態資源設置單獨的 publicPath
以外,還須要在應用中,針對動態使用 publicPath
的地方作出修改,這個在 qiankun
中已經有響應的解決方案,以下:
if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
簡單原理就是動態的注入了 __INJECTED_PUBLIC_PATH_BY_QIANKUN__
這個全局變量,它是經過 html entry
導入 entry
時,動態解析出來的。
衆所周知,CRA 啓動的 entry
文件是 src/index.jsx
,若是一個項目須要適配爲微前端應用,勢必須要在 index.jsx
中實現各類微前端模塊的生命週期函數。
在微前端應用的架構中,很重要的一點既是解耦,若是咱們在開發子應用時,且沒有用到任何和主應用或者其餘子應用相關的模塊或者狀態時,仍然須要其餘它們顯示不是一種理想的開發模式。
咱們理想的模式應該是,咱們仍然能夠按照傳統 SPA 的啓動方式,開發子應用,當須要與主應用集成,或者與其餘子應用調試時,又能夠以微前端模塊的方式啓動它。這實際上是在說,咱們當前的子應用要支持多 entry
啓動模式。
在 CRA 中,雖然能夠經過覆蓋 webpack
的方式來解決這個問題,可是我認爲有更簡便的方法。考慮到不管是傳統啓動方式,仍是微前端模塊的啓動方式,這兩種啓動方式在同一時間,咱們只會使用一種,那咱們移花接木式的變動 index.jsx
的內容,在 CRA 加載 entry
以前欺騙它豈不是更好?這裏咱們能夠利用如下兩點來實現相似的效果:
npm scripts
中的 hook 前綴來截止啓動指令index.jsx
的內容因爲涉及到的代碼較多,這裏就簡單貼一個 npm scripts
的截圖好了,以下:
能夠發現,對於 start
、 build
,均支持兩種模式的指令,從而適配不一樣開發模式下的構建需求。
最後說一點,對於 index.jsx
內容的更改,最簡單的方式即時經過軟連接的方式來實現,提早提供兩份被連接的目標文件,好比:
micro.tsx
和 standalone.tsx
均對應不一樣的 entry
入口,使用 CRA 啓動應用前,動態地建立軟連接將它們和 index.tsx
文件連接起來便可(因爲項目中使用了 ts,後綴爲 .tsx
,js 項目同理)。
以後咱們就能夠愉快地在主應用中引入咱們的子應用了,主要配置有兩個,一是註冊子應用,以下:
registerMicroApps( [ // 其餘子應用 ..., { name: "vcapp-login", entry: "//localhost:7101/login", container: "#subapp-container", activeRule: "/trade-login/", }, ], )
二是增長對於子應用的 publicPath
的配置,這兒會分爲兩部分,一個是部署環境下的,一個是開發環境下的,這裏分享開發環境下的。我主應用項目使用的打包器是 parcel
,所以能夠直接對它內部的 web server
增長中間件來完成這部分工做,以下:
app.use( createProxyMiddleware("/login", { target: "http://localhost:7101", }) );
注意這裏的 7101
,與上文中的 7101
對應,若是它們不一致,會形成子應用加載失敗。
svg
格式的圖片,沒有寫在 url-loader
的匹配規則中,若是子應用使用了 svg
圖片,須要覆蓋 url-loader
配置已適配 publicPath
變動形成的影響因爲倉庫代碼在公司內網,不太方面直接拷貝出來,往後有時間會單另在 github
建立一個示例項目。
同時因爲該微前端應用的架構基於 single-spa
和 qiankun
,對於 CRA 項目向微前端項目的遷移所作的一些工做並不具備通用性。
對於微前端這種架構,我更多地將它做爲一種可以漸進式地重構項目的手段在使用,對於大型複雜項目,並無太多的經驗,一是由於沒機會作相似的複雜度極高的中臺項目,二是由於不少巨石應用,大可能是舊項目,因此將它用做重構項目的一種手段也許更能發揮它的用處。
若有錯誤,還望指出。