single-spa 配置

基于single-spa的微前端配置

基于已有项目改造成微前端

Vue子项目改造

Vue版本:2.6.10

  1. 下载single-spa-vue包
  2. 修改main.js或者main.ts文件,使项目作为一个单一的spa应用程序工作
const options = {
  // vue的配置参数
  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 是主系统给子系统传递的参数
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需要和主系统加载文件时return的名字
output: {
      library: 'singleVue',   
      libraryTarget: 'umd'
    }

错误提示

  1. single-spa.min.js:2 Uncaught Error: application 'app3' died in status LOADING_SOURCE_CODE: {"isTrusted":true} 在因为加载子系统失败,singleSpa.registerApplication中访问的app.js无法访问的原因,可能是因为子系统未启动,也可能是访问路径写错了

主子系统之间传参

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') 
    };
  },
)

主系统(javascript)改造

由于主系统只有登陆功能,在登陆后会根据用户权限自动跳转至子系统中,所以主系统没有使用任何的前端框架,而是使用了javascript进行改造

  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. 主系统跳转至子系统使用 navigateToUrl 进行跳转
    例如: singleSpa.navigateToUrl('/react#/list?')

主系统(Vue)改造

  1. 下载single-spa相关包
  • 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的配置参数
  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 是主系统给子系统传递的参数
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子系统改造

react子应用是使用create-react-app脚手架安装的

  1. 下载single-spa相关包
  • 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', //主系统加载react时需要加载的文件
    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',    // 主系统加载文件时的return值
  libraryTarget: 'umd',
}
//修改plugins文件
plugins:[
    //将Css文件提取并暴露起文件名
    isEnvProduction && 
        new MiniCssExtractPlugin({
          filename: '[name].css',
        }),
      isEnvProduction && 
      new ExposeRuntimeCssAssetsPlugin({
        // filename 必须与 MiniCssExtractPlugin 中的 filename 一一对应
        filename: "[name].css",
      }),
]
  1. 修改index.js文件
import singleSpaReact from 'single-spa-react'
import singleSpaCss from 'single-spa-css';
// 子应用独立运行
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 导出的 css,如果是要做额外处理(webpack 导出的文件名通常会有 hash)
  webpackExtractedCss: true,
  // 当子应用 unmount 的时候,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 => {
  //开发环境下cssLifecycles.bootstrap(props)不需要导出,否则开发环境下会报错
  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能正常访问的关键:在 location @router { rewrite ^.*$ /index.html break; }这部分配置。在SPA单页面访问时,实际上访问的是单页面的路由,例如/goods/list,对应的其实是单页面路由中配置的路由组件。但是对于nginx服务器来讲,被认为会寻找根目录下goods文件夹下的list文件夹,当然是找不到的。单页面实际上只有一个页面index.html,因此将所有的页面都rewirte到index页面,即可完成配置

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容