single-spa 配置

基于single-spa的微前端配置

基于已有項(xiàng)目改造成微前端

Vue子項(xiàng)目改造

Vue版本:2.6.10

  1. 下載single-spa-vue包
  2. 修改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)
}
  1. 修改vue.config.js配置(打包配置)
    PS: library需要和主系統(tǒng)加載文件時(shí)return的名字
output: {
      library: 'singleVue',   
      libraryTarget: 'umd'
    }

錯誤提示

  1. 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)行改造

  1. 下載single-spa包
  2. 改造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'))
  1. 主系統(tǒng)跳轉(zhuǎn)至子系統(tǒng)使用 navigateToUrl 進(jìn)行跳轉(zhuǎn)
    例如: singleSpa.navigateToUrl('/react#/list?')

主系統(tǒng)(Vue)改造

  1. 下載single-spa相關(guān)包
  • single-spa-vue
  1. 修改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腳手架安裝的

  1. 下載single-spa相關(guān)包
  • single-spa-react
  • single-spa-css
  1. 如果暴露出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",
      }),
]
  1. 修改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配置

  1. 根文件配置
#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/*;
}
  1. 子文件配置
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頁面僧诚,即可完成配置

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蝗碎,隨后出現(xiàn)的幾起案子湖笨,更是在濱河造成了極大的恐慌,老刑警劉巖蹦骑,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慈省,死亡現(xiàn)場離奇詭異,居然都是意外死亡眠菇,警方通過查閱死者的電腦和手機(jī)边败,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捎废,“玉大人笑窜,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵税产,是天一觀的道長。 經(jīng)常有香客問我断傲,道長,這世上最難降的妖魔是什么智政? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任艳悔,我火速辦了婚禮,結(jié)果婚禮上女仰,老公的妹妹穿的比我還像新娘猜年。我一直安慰自己,他們只是感情好疾忍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布乔外。 她就那樣靜靜地躺著,像睡著了一般一罩。 火紅的嫁衣襯著肌膚如雪杨幼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天聂渊,我揣著相機(jī)與錄音差购,去河邊找鬼。 笑死汉嗽,一個胖子當(dāng)著我的面吹牛欲逃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播饼暑,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼稳析,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了弓叛?” 一聲冷哼從身側(cè)響起彰居,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎撰筷,沒想到半個月后陈惰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毕籽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年抬闯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片影钉。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡画髓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出平委,到底是詐尸還是另有隱情奈虾,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布廉赔,位于F島的核電站肉微,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蜡塌。R本人自食惡果不足惜碉纳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望馏艾。 院中可真熱鬧劳曹,春花似錦奴愉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜕劝,卻和暖如春檀头,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背岖沛。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工暑始, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人婴削。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓廊镜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親馆蠕。 傳聞我的和親對象是個殘疾皇子期升,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容