基于single-spa的微前端项目

文章目录


前言

项目需求:几个组共用一个入口【父应用】,详细内容【子应用】各组自行维护、打包,这就可以采用:微前端 这个技术。


提示:以下是本篇文章正文内容,下面案例可供参考

一、微前端是什么?

其他人说得太多了我就不再赘述。我这里主要讲自己项目运用的微前端:基于single-spa的微前端项目。

主要技术

single-spa:连接主项目与子应用的桥梁
systemjs:浏览器端异步加载模块
single-spa-vue:vue子应用连接主应用的插件

二、使用步骤

1.父应用

1.1 vue-cli搭建vue项目

vue create root-frontend

1.2 改造父应用,使用systemjs加载子应用

index.html的改造:

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <title><%= htmlWebpackPlugin.options.title %></title>
    <!-- <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js" as="script" crossorigin="anonymous" />
    <link rel="preload" href="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js" as="script" crossorigin="anonymous" /> -->
    <script type="systemjs-importmap">
      {
        "imports": {
          "pro1": "http://localhost:1003/pro1/js/app.js",
          "app1": "http://localhost:1003/app1/js/app.js",
          "single-spa": "http://lib.baomitu.com/single-spa/5.9.0/system/single-spa.min.js",
          "vue": "http://lib.baomitu.com/vue/2.6.12/vue.min.js",
          "vue-router": "http://lib.baomitu.com/vue-router/2.8.1/vue-router.min.js",
          "element-ui": "http://lib.baomitu.com/element-ui/2.13.2/index.js",
          "jquery": "http://lib.baomitu.com/jquery/1.12.4/jquery.min.js",
          "vuex": "http://lib.baomitu.com/vuex/2.5.0/vuex.min.js",
          "axios": "http://lib.baomitu.com/axios/0.21.1/axios.min.js"
        }
      }
    </script>
  </head>

  <script crossorigin="anonymous" integrity="sha512-e77b00UHTqTXXo8ZEHATvvSIm7CETY1K+4wJyXHD6NHeoyvyOYePB4lkX5KDBFilzzbPHLvaQTJSrnwG8To3tQ==" src="http://lib.baomitu.com/systemjs/6.8.3/system.js"></script>
  <!-- <script src="../src/system.js"></script> -->
  <script crossorigin="anonymous" integrity="sha512-XRrZRDEJK7FeoBd7ICImm9S3UNG87nFAUm4td9v2uedBr16byMk9FK4AZ4N608aVHHSKPlhdSWTy/b717KJ54Q==" src="http://lib.baomitu.com/systemjs/6.8.3/extras/named-register.min.js"></script>
  <script crossorigin="anonymous" integrity="sha512-fRfysEnVm90UgqFUsB+UlgnHK5vvSnsl64hxHaJJl4wE+P1sTwDUuQGmohtg/CSK60mY5A4elesW6UWQdLQsAg==" src="http://lib.baomitu.com/systemjs/6.8.3/extras/amd.min.js"></script>
  <script crossorigin="anonymous" integrity="sha512-mf4/y3IroNfkl20Brygdc+tb4a38MrEmU+VTSyMaqlsZhfVHCdHniA//HEgETGlJptSSCPv72Dy6kXB+OHiGAQ==" src="http://lib.baomitu.com/systemjs/6.8.3/extras/named-exports.min.js"></script>
  <script crossorigin="anonymous" integrity="sha512-Ahpc6VqzcdPS1xTuBGdnw+l64Iz6KNXAy/4/1bBzAv6nTl+NK7mnYHMDtI8wAzd2PTV9GVNURwKf87y36eWZ+g==" src="http://lib.baomitu.com/systemjs/6.8.3/extras/use-default.min.js"></script>
  <body>
    <noscript>
      <strong
        >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
        properly without JavaScript enabled. Please enable it to
        continue.</strong
      >
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
  <!-- <import-map-overrides-full></import-map-overrides-full> -->
</html>

1.3 引入systemjs包

module.exports = {
  devServer: {
    open: true,
    contentBase: "./dist",
    historyApiFallback: true,
    watchOptions: { aggregateTimeout: 300, poll: 1000 },
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
      "Access-Control-Allow-Headers":
        "X-Requested-With, content-type, Authorization",
    },
    proxy: {
      "/root": {
        target: "http://localhost:5000",
        pathRewrite: { "^/root": "" },
      },
      "/child1": {
        target: "http://localhost:5001",
        pathRewrite: { "^/child1": "" },
      },
    },
  },
};

1.4 main.js

import App from "./App.vue";
import routes from "./routers/router";

const SystemJS = window.System;
  Promise.all([
    SystemJS.import("single-spa"),
    SystemJS.import("vue-router"),
    SystemJS.import("axios"),
    SystemJS.import("vue"),
  ]).then(function(modules) {
    var singleSpa = modules[0];
    var Vue = modules[3];
    var VueRouter = modules[1];
    var axios = modules[2];
    Vue.use(VueRouter);
    Vue.config.productionTip = false;
    singleSpa.registerApplication(
      "root",
      () => SystemJS.import("root"),
      (location) => {
        return location.hash.startsWith("#/root");
      }
    );

    singleSpa.registerApplication(
      "child1",
      () => SystemJS.import("child1"),
      (location) => location.hash.startsWith("#/child1")
    );
    // 每新增一个子应用都要复制一下上面这段代码
    
    singleSpa.start();
    const router = new VueRouter({
      routes,
    });
    new Vue({
      render: (h) => h(App),
      router,
    }).$mount("#app");
  });

说明:使用systemjs加载模块,singleSpa.registerApplication注册子应用,当路由匹配child1时会到远程加载子应用的代码。父应用加载公共资源vue、vue-router后子应用可以不打包这些,实现共享。

2.子应用

2.1 使用vue-cli创建项目

vue create spa-a
npm install single-spa-vue --save
npm install systemjs-webpack-interop --save //用于实现异步路由时,父应用可加载代码分割块

2.2 main.js

import './set-public-path'
import Vue from 'vue';
import App from './App.vue';
import router from './routers/router';
import singleSpaVue from 'single-spa-vue';
import VueRouter from 'vue-router'
Vue.config.productionTip = false;
Vue.use(VueRouter)
const vueLifecycles = singleSpaVue({
  Vue,
  appOptions: {
    render: (h) => h(App),
    router
  },
});

export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;

说明:暴露3个方法bootstrap 、mount 、unmount 使父应用和子应用实现通信

set-public-path主要用于:子应用使用异步路由时,父应用能加载到代码chunk

import { setPublicPath } from 'systemjs-webpack-interop'

setPublicPath('child1', 2) //子应用的前缀。与在父应用注册的名称相同

2.3 子应用的vue.config.js:

module.exports = {
    chainWebpack: config => {
      config.devServer.set('inline', false)
      config.devServer.set('hot', true)
      // Vue CLI 4 output filename is js/[chunkName].js, different from Vue CLI 3
      if (process.env.NODE_ENV !== 'production') {
        config.output.filename(`js/[name].js`)
        .libraryTarget('umd') // 打包方式  必须配置。这样systemjs才能正常加载子应用
        .end()// 链式操作
      }
      config.externals(['vue', 'vue-router']) //不打包vue等公共资源

      config
      .optimization.splitChunks({  //去掉默认的chunk-vendors  
        cacheGroups: {
          common: {
            name: `chunk-common`,
            minChunks: 2,
            priority: -20,
            chunks: 'initial',
            reuseExistingChunk: true
          }
        }
      })
    },
    filenameHashing: false
  }

总结

微前端的适用范围比较特殊,但作用还是很强大的。除了上面提到的父子通信和共用,还可以结合我另一篇文章 《vue批量注册所有自定义组件》:在父应用中注册通用组件,在各个子应用中使用。还有更多妙用需要更多大佬挖掘。

上一篇:服务定位器(SL)与AgileEAS.NET中的实现


下一篇:设计模式的SOLID原则和创建式设计模式 一