美文网首页
single-spa 配置

single-spa 配置

作者: 落幕12 | 来源:发表于2023-02-26 16:17 被阅读0次

基于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页面,即可完成配置

相关文章

网友评论

      本文标题:single-spa 配置

      本文链接:https://www.haomeiwen.com/subject/bcnpzrtx.html