vue-cli 配置 single-spa

前言

其實方便點可使用 qiankun 的微前端方案javascript

依賴版本:html

"single-spa": "^5.5.2",
"single-spa-vue": "^1.8.2",
複製代碼

流程

主應用流程

  • 啓動由 system.js 接管,配置 webpackout.libraryTargetsystem前端

  • html 入口中經過 importmap,設置當前應用、子應用 名稱+地址vue

  • 通常用法(DOM 節點一直存在的狀況下):registerApplication 註冊子應用,經過 system.js 引入,設置渲染路由 activeWhen,傳遞給子應用的參數 customPropsjava

  • 使用 Parcel 用法(DOM 節點不是一直存在的狀況下):主應用也須要包裹 singleSpaVue/singleSpaReact等,而後 registerApplication 本身,在某個組件(A)內使用由 main.js/tsbootstraps/mount 時導出的 mountParcel,在某組件(A)掛載後,手動將子應用(當作組件用)掛載到這個組件的某個 DOM 節點(見1.3webpack

子應用流程(Vue)

  • 啓動方式由 single-spa-vue 接管,能夠判斷 window.singleSpaNavigatefalse 單獨啓動git

  • 配置在主應用的掛載點,appOptions 下的 el 設置,默認掛載到 bodygithub

  • 導出一些生命週期事件,至少以下三個:bootstrap/mount/unmount,能夠在 mount 下接收主應用傳遞的參數web

  • 異步組件須要使用:(否則主應用使用子應用會報錯)vue-cli

    1. systemjs-webpack-interop 設置 setPublicPath
    2. webpack 配置:config.output.jsonpFunction = 'wpJsonpFlightsWidget';

一、主項目的配置

1.1 下載依賴

下載 single-spa

yarn add single-spa
複製代碼

1.2 配置

HTML 入口

system.js 的包最後下載下來放項目裏,防止引用的 cdn 有時候抽風

systemjs-importmap 也能夠經過配置文件自動生成,這樣也好區分開發環境跟生成環境不一樣的入口,注意打包後子應用的入口的跨域問題

  • 使用 webpack 自動插入 HTML
// systemJs-Importmap.js
const isEnvDev = process.env.NODE_ENV === 'development';

// systemjs-importmap 的配置,經過webpack給html用
module.exports = [
  {
    name: 'root-config',
    entry: './js/app.js',
  },
  {
    name: '@vue-mf/calendar',
    entry: isEnvDev
      ? '//localhost:2333/js/app.js'
      : 'https://zero9527.github.io/vue-calendar/js/app.js',
  },
];

// vue.config.js
chainWebpack: config => {
  config.plugin('html').tap(args => {
    const importMap = { imports: {} };
    systemJsImportmap.forEach(item => (importMap.imports[item.name] = item.entry));
    args[0].systemJsImportmap = JSON.stringify(importMap, null, 2);
    return args;
  });
},

// public\index.html
<meta name="importmap-type" content="systemjs-importmap" />
<script type="systemjs-importmap"> <%= htmlWebpackPlugin.options.systemJsImportmap %> </script>
<script src="./libs/systemjs/system.min.js"></script>
<script src="./libs/systemjs/extras/amd.min.js"></script>
<script src="./libs/systemjs/extras/named-exports.min.js"></script>
<script src="./libs/systemjs/extras/named-register.min.js"></script>
<script src="./libs/systemjs/extras/use-default.min.js"></script>
<script>
  System.import('root-config');
</script>
複製代碼
  • public/index.html 下手動添加
<meta name="importmap-type" content="systemjs-importmap" />
<script type="systemjs-importmap"> { "imports": { "root-config": "//localhost:666/js/app.js", "@vue-mf/calendar": "//localhost:2333/js/app.js" } } </script>
<script src="./libs/systemjs/system.min.js"></script>
<script src="./libs/systemjs/extras/amd.min.js"></script>
<script src="./libs/systemjs/extras/named-exports.min.js"></script>
<script src="./libs/systemjs/extras/named-register.min.js"></script>
<script src="./libs/systemjs/extras/use-default.min.js"></script>
<script> System.import('root-config'); </script>
複製代碼
  • 裏面的東西是一個 JSON,注意格式!

這裏配置當前應用的配置 名稱:地址,與子應用的 名稱:地址

<script type="systemjs-importmap"> { "imports": { "root-config": "//localhost:666/js/app.js", "@vue-mf/calendar": "//localhost:2333/js/app.js" } } </script>
複製代碼
  • 子應用名稱 @vue-mf/calendar,在 registerApplication 時,對應 app: import('@vue-mf/calendar') 的名稱,如
registerApplication({
  name: '@vue-mf/calendar',
  app: () => (window as any).System.import('@vue-mf/calendar'),
  activeWhen: '',
  customProps: {
    root: 'json-util',
  },
});
複製代碼

系統啓動由 systemJS 接管

  • html
<script> System.import('root-config'); </script>
複製代碼
  • 對應的 webpack 配置

去掉文件 hash,方便引入文件名

// vue.config.js
module.exports = {
  outputDir: 'docs',
  publicPath: './',
  filenameHashing: false,
  productionSourceMap: false,
  configureWebpack: config => {
    config.output.libraryTarget = 'system';

    config.devServer = {
      port: 666,
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
      disableHostCheck: true,
      historyApiFallback: true,
    };
  },
};
複製代碼

註冊子應用

  • single-spa.config.js
// src\single-spa-config.ts
import { registerApplication, start } from 'single-spa';

registerApplication({
  name: '@vue-mf/calendar',
  app: () => (window as any).System.import('@vue-mf/calendar'),
  activeWhen: '',
  customProps: {
    root: 'json-util',
  },
});

start();
複製代碼
  • main.ts 中引入

其實在哪引入均可以,確保 DOM 節點存在就能夠,若是時動態建立的,首次加載能夠,可是恢復狀態後,會提示找不到 DOM 節點

// src\main.ts
import './single-spa-config';
複製代碼

1.3 Parcel 配置

官方文檔

翻譯過來叫:包裹,能夠在主應用將一個子應用當作組件,手動掛載、卸載使用,不限框架,webpack 5 有一個 Module Federation 也是能夠跨項目使用組件的

何時用

把子應用當作一個組件使用,放在主應用的某個組件(A)下面時,DOM 節點不是一直存在的狀況

  • 主應用:使用 singleSpaVue/singleSpaReact 包裹,而後 registerApplication 本身,在某個組件(A)內使用由 main.js/tsbootstraps/mount 時導出的 mountParcel ,在某組件(A)掛載後,手動將子應用(當作組件用)掛載到這個組件的某個 DOM 節點

  • 子應用:不須要在主應用 registerAppliaction 註冊,而是手動在某個組件(A)內手動掛載到某個 DOM 節點

主應用改造

// src\main.ts
//...

// **************** 主應用通常寫法 ****************
// // 子應用 registerAppliaction 註冊
// new Vue({
// router,
// render: (h: any) => h(App),
// }).$mount('#json-util');

// **************** 主應用使用 Parcel 寫法 ****************
// 主應用使用 Parcel 掛載子應用(某組件下)的時候的寫法
// 須要把當前應用當作子應用,而後 registerAppliaction 調用
const singleSpa = singleSpaVue({
  Vue,
  appOptions: {
    el: '#json-util',
    render: (h: any) => h(App),
    router,
  },
});

// eslint-disable-next-line
export let mountParcel: any;

export const bootstrap = (props: any) => {
  mountParcel = props.mountParcel;
  return singleSpa.bootstrap(props);
};

export const { mount, unmount } = singleSpa;
複製代碼

註冊子組件

import { registerApplication, start } from 'single-spa';

// 改成 Parcel 手動掛載子應用了,須要導出 mountParcel,已經用 singleVue 包裹了,因此要用 registerApplication 啓動
registerApplication({
  name: 'root-config',
  app: () => (window as any).System.import('root-config'),
  activeWhen: () => true,
});

registerApplication({
  name: '@vue-mf/calendar',
  app: () => (window as any).System.import('@vue-mf/calendar'),
  activeWhen: location => {
    return location.href.includes('/sub-app');
  },
  customProps: {
    root: 'json-util',
  },
});

// 改成 Parcel 手動掛載了,全部這個要去掉
// registerApplication({
// name: '@vue-mf/clock',
// app: () => (window as any).System.import('@vue-mf/clock'),
// activeWhen: location => {
// return location.href.includes('/sub-app');
// },
// customProps: {
// root: 'json-util',
// },
// });

start();
複製代碼

手動掛載

某個組件(A)在 mount 以後手動將子應用掛載到某個 DOM 節點

使用了 composition-api

import { mountParcel } from '@/main';

const parcel = ref<any>(null);

const mountClockParcel = () => {
  const routePath = ctx.root.$route.path;
  const domElement = document.getElementById('app-clock');
  if (routePath === '/sub-app' && domElement) {
    const parcelConfig = (window as any).System.import('@vue-mf/clock');
    parcel.value = mountParcel(parcelConfig, { domElement });
  } else if (parcel.value) {
    parcel.value.unmount();
  }
};

onMounted(() => {
  mountClockParcel();
});

watch(
  () => ctx.root.$route.path,
  () => {
    mountClockParcel();
  },
);
複製代碼

二、子項目的配置(Vue)

例子:vue-calendar

2.1 下載依賴

  • 下載 single-spa-vue
yarn add single-spa-vue
複製代碼
  • 下載 vue-cli-plugin-single-spa

解決這個問題

single-spa.min.js?25a2:2 single-spa minified message #37: See single-spa.js.org/error/?code…

yarn add -D vue-cli-plugin-single-spa
複製代碼

2.2 配置

  • 應用入口 main.js/ts

注意:
appOptions 下,el 能夠給當前應用配置在主應用的掛載 DOM 節點,這個節點須要提早設置好;不提供 el 的話默認掛載在 body

// 其餘的代碼省略
import Vue from 'vue';
import singleSpaVue from 'single-spa-vue';
// ...

// ============= 非 single-spa 單獨啓動 =============
if (!(window as any).singleSpaNavigate) {
  new Vue({
    render: (h: any) => h(App),
  }).$mount('#app-calendar');
}

// ============= single-spa 模式啓動 =============
const vueLifeCycles = singleSpaVue({
  Vue,
  appOptions: {
    // el:掛載的dom節點,在主項目須要有;沒有el的話會添加到body下
    el: '#app-calendar',
    render: (h: any) => h(App),
  },
});

export function bootstrap(props: object) {
  return vueLifeCycles.bootstrap(props);
}

export function mount(props: object) {
  console.log('mount: ', props);
  return vueLifeCycles.mount(props);
}

export function unmount(props: object) {
  return vueLifeCycles.unmount(props);
}
複製代碼

2.3 問題

問題描述

子項目使用異步組件 import() 時,單獨跑起來沒問題!!!可是在主應用裏面會報錯,改成正常引入 import from 就沒事。。。

子應用使用異步組件,在主應用報錯

Uncaught TypeError: application '@vue-mf/calendar' died in status BOOTSTRAPPING: Object(...) is not a function
複製代碼
<template>
  <div id="app-calendar">
    <div class="title">Vue-Calendar</div>
    <Calendar />
  </div>
</template>

<script lang="ts"> // 正常 import Calendar from '@/components/Calendar/index.vue'; // single-spa在主應用加載:不行 // const Calendar = () => import(@/components/Calendar/index.vue); // single-spa在主應用加載:不行 // import AsyncComponent from '@/components/AsyncComponent/index'; // single-spa 下使用異步組件,在主應用加載有問題 // const Calendar = AsyncComponent(() => // import( // /* webpackPrefetch: true */ // /* webpackChunkName: 'calendar' */ // '@/components/Calendar/index.vue' // ), // ); export default { name: 'App', components: { Calendar, }, }; </script>
複製代碼

異步組件問題解決

子項目添加以下設置

// src\set-public-path.ts
import { setPublicPath } from 'systemjs-webpack-interop';

if ((window as any).singleSpaNavigate) {
  setPublicPath('@vue-mf/calendar', 2);
}
複製代碼
// vue.config.js
config.output.jsonpFunction = 'wpJsonpFlightsWidget';
複製代碼

single-spa.js.org/docs/recomm…

參考

相關文章
相關標籤/搜索