美文网首页
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