基于single-spa的微前端配置
基于已有項(xiàng)目改造成微前端
Vue子項(xiàng)目改造
Vue版本:2.6.10
- 下載single-spa-vue包
- 修改main.js或者main.ts文件券躁,使項(xiàng)目作為一個單一的spa應(yīng)用程序工作
const options = {
// vue的配置參數(shù)
el: '#vue',
render: h => h(App),
router,
store
}
const vueLifeCycles = singleSpaVue({
Vue,
appOptions: options
})
//判斷是否是微前端模式
if (window.singleSpaNavigate) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = 'http://localhost:8888/vue'
}
if (!window.singleSpaNavigate) {
delete options.el
new Vue(options).$mount('#app')
}
// props 是主系統(tǒng)給子系統(tǒng)傳遞的參數(shù)
export function bootstrap(props){
return vueLifeCycles.bootstrap(props)
}
// export const mount = vueLifeCycles.mount
export function mount(props) {
// do something with the common authToken in app1
return vueLifeCycles.mount(props);
}
export function unmount(props){
return vueLifeCycles.unmount(props)
}
- 修改vue.config.js配置(打包配置)
PS: library需要和主系統(tǒng)加載文件時(shí)return的名字
output: {
library: 'singleVue',
libraryTarget: 'umd'
}
錯誤提示
- single-spa.min.js:2 Uncaught Error: application 'app3' died in status LOADING_SOURCE_CODE: {"isTrusted":true} 在因?yàn)榧虞d子系統(tǒng)失敗拘鞋,singleSpa.registerApplication中訪問的app.js無法訪問的原因奕剃,可能是因?yàn)樽酉到y(tǒng)未啟動,也可能是訪問路徑寫錯了
主子系統(tǒng)之間傳參
singleSpa.registerApplication(
'app3',
async()=>{
await runScript(process.env.REACT_APP_CSPJ_JS)
return window.cspj;
},
location => { return location.pathname.startsWith('/react') },
{
domElement: domElementGetter(),
authToken: getStore('token')
};
)
通過對象直接傳遞的authToken并不是登錄之后獲取到的最新Token猴鲫,而是上一次登錄存儲在Store中的Token权均,將改字段修改成一個方法并在方法中return該Token具伍,這樣獲取到的Token就是最新的Token
singleSpa.registerApplication(
'app3',
async()=>{
await runScript(process.env.REACT_APP_CSPJ_JS)
return window.cspj;
},
location => { return location.pathname.startsWith('/react') },
(name, location) => {
return {
domElement: domElementGetter(),
authToken: getStore('token')
};
},
)
主系統(tǒng)(javascript)改造
由于主系統(tǒng)只有登陸功能钞脂,在登陸后會根據(jù)用戶權(quán)限自動跳轉(zhuǎn)至子系統(tǒng)中燥狰,所以主系統(tǒng)沒有使用任何的前端框架,而是使用了javascript進(jìn)行改造
- 下載single-spa包
- 改造main.js文件
// 在mian.js文件中配置
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App';
import * as singleSpa from 'single-spa';
import { getStore } from './utils/store';
async function runScript(url){
return new Promise((resolve,reject)=>{
const script = document.createElement("script");
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script)
})
}
// 記載React
const domElementGetter = () => {
let el = document.getElementById('root');
if (!el) {
el = document.createElement('div');
el.id = 'root';
document.body.appendChild(el);
}
return el;
}
singleSpa.registerApplication('app2',
// 要返回三個方法
async()=>{
await runScript('http://localhost:8888/vue/static/js/chunk-vendors.js')
await runScript('http://localhost:8888/vue/static/js/app.js')
return window.singleVue; //bootstrap mount unmount
},
location => {return location.pathname.startsWith('/vue')},
{ authToken: 'ddd' }
)
singleSpa.registerApplication('app3',
async()=>{
await runScript('http://localhost:3000/js/app.js')
return window.singleReact;
},
location => { return location.pathname.startsWith('/react') },
(name, location) => {
return {
domElement: domElementGetter(),
token: ()=>{ return getStore('token')},
};
},
)
singleSpa.start();
ReactDOM.render(<App />, document.getElementById('app'))
- 主系統(tǒng)跳轉(zhuǎn)至子系統(tǒng)使用 navigateToUrl 進(jìn)行跳轉(zhuǎn)
例如: singleSpa.navigateToUrl('/react#/list?')
主系統(tǒng)(Vue)改造
- 下載single-spa相關(guān)包
- single-spa-vue
- 修改main.js配置
import Vue from 'vue'
import App from './App'
import Print from 'vue-print-nb'
import singleSpaVue from 'single-spa-vue'
Vue.use(Print)
Vue.use(VueCookies)
Vue.config.productionTip = false
const options = {
// vue的配置參數(shù)
el: '#vue',
render: h => h(App),
router,
store
}
const vueLifeCycles = singleSpaVue({
Vue,
appOptions: options
})
//判斷是否是微前端模式
if (window.singleSpaNavigate) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = 'http://localhost:8888/vue/'
}
if (!window.singleSpaNavigate) {
delete options.el
new Vue(options).$mount('#app')
}
// props 是主系統(tǒng)給子系統(tǒng)傳遞的參數(shù)
export function bootstrap(props){
return vueLifeCycles.bootstrap(props)
}
// export const mount = vueLifeCycles.mount
export function mount(props) {
// do something with the common authToken in app1
return vueLifeCycles.mount(props);
}
export function unmount(props){
return vueLifeCycles.unmount(props)
}
React子系統(tǒng)改造
react子應(yīng)用是使用create-react-app腳手架安裝的
- 下載single-spa相關(guān)包
- single-spa-react
- single-spa-css
- 如果暴露出webpack.config.js文件則直接修改webpack.congig.js文件
config/webpack.confi.js文件
// 修改output字段
output: {
path: paths.appBuild,
pathinfo: isEnvDevelopment,
filename: 'js/app.js', //主系統(tǒng)加載react時(shí)需要加載的文件
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
assetModuleFilename: 'static/media/[name].[hash][ext]',
publicPath: 'http://localhost:3000/',
library: 'app3', // 主系統(tǒng)加載文件時(shí)的return值
libraryTarget: 'umd',
}
//修改plugins文件
plugins:[
//將Css文件提取并暴露起文件名
isEnvProduction &&
new MiniCssExtractPlugin({
filename: '[name].css',
}),
isEnvProduction &&
new ExposeRuntimeCssAssetsPlugin({
// filename 必須與 MiniCssExtractPlugin 中的 filename 一一對應(yīng)
filename: "[name].css",
}),
]
- 修改index.js文件
import singleSpaReact from 'single-spa-react'
import singleSpaCss from 'single-spa-css';
// 子應(yīng)用獨(dú)立運(yùn)行
if (!window.singleSpaNavigate) {
ReactDOM.render(rootComponent(), document.getElementById('root'))
}
//加載CSS文件
const cssLifecycles = process.env.NODE_ENV === 'development'
? ''
: singleSpaCss({
// 需要加載的 css 列表
cssUrls: ['http://localhost:3000/main.css'],
// 是否是 webpack 導(dǎo)出的 css斜筐,如果是要做額外處理(webpack 導(dǎo)出的文件名通常會有 hash)
webpackExtractedCss: true,
// 當(dāng)子應(yīng)用 unmount 的時(shí)候龙致,css 是否需要一并刪除
shouldUnmount: true,
createLink(url) {
const linkEl = document.createElement('link');
linkEl.rel = 'stylesheet';
linkEl.href = url;
return linkEl;
},
});
const domElementGetter = () => {
let el = document.getElementById("app");
if (!el) {
el = document.createElement('div');
el.id = 'app';
document.body.appendChild(el);
}
return el;
}
const reactLifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent,
errorBoundary(err, info, props) {
return <div>
This renders when a catastrophic error occurs
</div>
},
domElementGetter
})
// 這里和vue不一樣,props必須向下傳遞
export const bootstrap = async props => {
//開發(fā)環(huán)境下cssLifecycles.bootstrap(props)不需要導(dǎo)出顷链,否則開發(fā)環(huán)境下會報(bào)錯
return process.env.NODE_ENV === 'development' ? reactLifecycles.bootstrap(props) : [cssLifecycles.bootstrap(props),reactLifecycles.bootstrap(props)];
}
export const mount = async props => {
return process.env.NODE_ENV === 'development' ? reactLifecycles.mount(props): [cssLifecycles.mount(props),reactLifecycles.mount(props)];
}
export const unmount = async props => {
return process.env.NODE_ENV === 'development' ? reactLifecycles.unmount(props) : [reactLifecycles.unmount(props),cssLifecycles.unmount(props)];
}
// 根組件
function rootComponent() {
return <React.StrictMode>
<App />
</React.StrictMode>
}
Nginx配置
- 根文件配置
#user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
include single-spa-vue1.conf;
include single-spa-vue2.conf;
include single-spa-react.conf;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8080;
server_name localhost;
root /Users/xxx/Desktop/data;
index index.html;
location / {
root /Users/xxx/Desktop/data/frontend;
index index.html index.htm;
try_files $uri $uri/ @router;
}
location @router {
rewrite ^.*$ /index.html last;
}
}
include servers/*;
}
- 子文件配置
server {
listen 8888;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
index index.html;
try_files $uri /index.html;
root /Users/xxx/Desktop/data/vue1;
location / {
root /Users/xxx/Desktop/data/vue1;
index index.html;
try_files $uri /index.html;
}
location /labopslims {
root /Users/xxx/Desktop/data/vue1;
index index.html index.htm;
try_files $uri $uri/ @router;
}
}
SPA能正常訪問的關(guān)鍵:在 location @router { rewrite ^.*$ /index.html break; }這部分配置目代。在SPA單頁面訪問時(shí),實(shí)際上訪問的是單頁面的路由蕴潦,例如/goods/list像啼,對應(yīng)的其實(shí)是單頁面路由中配置的路由組件。但是對于nginx服務(wù)器來講潭苞,被認(rèn)為會尋找根目錄下goods文件夾下的list文件夾忽冻,當(dāng)然是找不到的。單頁面實(shí)際上只有一個頁面index.html此疹,因此將所有的頁面都rewirte到index頁面僧诚,即可完成配置