微前端框架搭建 主应用为angular

demo

 

方式描述优点缺点
路由转发(目前方式) 简单体验不好,切换应用整个页面刷新。重复加载。
运行时组合独立构建,主应用加载子应用体验好, 独立开发,独立部署复杂,需要设计加载,通信机制,无法彻底隔离,需要解决依赖,样式冲突

 

需求点收益与要求
拆分解耦(1)按业务领域拆分成不同的仓库进行维护,不同业务线的开发者更加独立,不同业务线之间互不影响。(2)物理层面拆分,加速寻址,新增功能修改Bug更加迅速。(3)逻辑层面拆分,杜绝引用混乱,不会出现A业务线引用B业务线组件的情况。
加速体验(1)开发环境急速启动,提高开发体验。(2)业务线按需打包,急速部署上线。
侵入性低微前端方案改动原有代码的侵入性降到最小,无需大规模改造,减少甚至消除回归测试的成本。
学习成本低开发人员无需感知拆分的存在,保持单页应用的开发体验,不需要学习额外的规则。
统一技术栈为了统一共建与技术沉淀,团队内工程已经统一到了React技术栈,禁止使用不同的技术栈进行开发。

方案选择

 

  • NPM式:子工程以NPM包的形式发布源码;打包构建发布还是由基座工程管理,打包时集成。
  • iframe式:子工程可以使用不同技术栈;子工程之间完全独立,无任何依赖;基座工程和子工程需要建立通信机制;无单页应用体验;路由地址管理困难。
  • 通用中心路由基座式:子工程可以使用不同技术栈;子工程之间完全独立,无任何依赖;统一由基座工程进行管理,按照DOM节点的注册、挂载、卸载来完成。
  • 特定中心路由基座式:子业务线之间使用相同技术栈;基座工程和子工程可以单独开发单独部署;子工程有能力复用基座工程的公共基建。
方案技术栈是否能统一单独打包单独部署打包部署速度单页应用体验子工程切换速度工程间通信难度现有工程侵入性学习成本
NPM式是(不强制)正常
iframe式是(不强制)正常
通用中心路由基座式是(不强制)正常
特定中心路由基座式是(强制)正常

 

vue子应用:

 

angular子应用

 

主应用配置

1、新建项目, 我这里是新建了angular v10工程, 并安装qiankun包。

ng new fe-main

cd fe-main

npm install qiankun

2、设置子程序入口 micro-app.js

const microApps = [
  {
    name: 'fe-sub-vue',
    entry: '//localhost:7777/',
    activeRule: '/fe-sub-vue',
    container: '#subapp-viewport', // 子应用挂载的div
    props: {
      routerBase: '/fe-sub-vue' // 下发路由给子应用,子应用根据该值去定义qiankun环境下的路由
    },
    icon: 'dashboard',
  },
  {
    name: 'fe-sub-angular',
    entry: '//localhost:7788/',
    activeRule: '/fe-sub-angular',
    container: '#subapp-viewport', // 子应用挂载的div
    props: {
      routerBase: '/fe-sub-angular'
    },
    icon: 'form',
  }
]

export default microApps

3、修改angular入口main.ts函数,定义子程序声明周期函数, 注意这里用的是typescript

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { registerMicroApps, start } from 'qiankun';
import microApps from './micro-app';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

registerMicroApps(microApps, {
  beforeLoad: app => new Promise((resolve, reject) => {
    console.log('beforeLoad', app.name);
    resolve(null);
  }),
  beforeMount: [
    app => new Promise((resolve, reject) => {
      console.log('before', app.name);
      resolve(null);
    }),
  ],
  afterMount: [
    app => new Promise((resolve, reject) => {
      console.log('after', app.name);
      resolve(null);
    })
  ],
  afterUnmount: [
    app => new Promise((resolve, reject) => {
      console.log('afterUnmount', app.name);
      resolve(null);
    }),
  ],
});

start();

4、配置子程序容器 <div id="subapp-viewport"></div>

<nz-layout class="app-layout">
  <nz-header>
    <div class="app-header">
      <div class="logo" (click)="goIndex()">
        <img *ngIf="siteConfig.logo.type === 'img';else altTem" [attr.src]="siteConfig.logo.url" [attr.alt]="siteConfig.logo.alt">
        <ng-template #altTem><span> {{siteConfig.logo.alt}}</span></ng-template>
      </div>
      <ul nz-menu [nzMode]="'horizontal'" class="hy-ul" [class.hy-ul-test]="environment.platform==='devsite'">
        <li nz-submenu class="hy-li">
          <span title>
            <i nz-icon nzType="user"></i>{{user.name}}
          </span>
          <ul>
            <li nz-menu-item><a (click)="setPassword()">修改密码</a></li>
            <li nz-menu-item><a (click)="loginOut()">退出登录</a></li>
          </ul>
        </li>
        <li *ngIf="!visitMsg" nz-menu-item class="hy-li" nzDisabled><i nz-icon nzType="mail" theme="outline"></i> 消息中心</li>
        <li *ngIf="visitMsg&&!unreadCount" class="hy-li" nz-menu-item (click)="showMsg()"><i nz-icon nzType="mail"></i>消息中心</li>
        <li *ngIf="visitMsg&&unreadCount" class="hy-li" nz-menu-item nz-popover [nzPopoverContent]="contentTemplate"
            (nzVisibleChange)="getUnreadCount()" (click)="showMsg()">
          <nz-badge style="width: 105px;" [nzCount]="unreadCount"><i nz-icon nzType="mail"></i>消息中心</nz-badge>
        </li>
        <ng-template #contentTemplate>
          <div *ngFor="let u of unreadList" class="msg-list" (click)="showMsg(u.id)">
            <p style="font-weight: 600;font-size: 18px;margin: 0">
              <nz-badge [nzStatus]="u.priority===3?'error':'success'"></nz-badge>
              {{u.title}}
            </p>
            <p style="padding-left: 19px;margin: 0">{{u.description}}</p>
            <p style="padding-left: 19px;margin: 0;font-size: 14px;color:#aaa;padding-top: 4px">{{u.send_time}}</p>
            <nz-divider style="margin: 8px 0"></nz-divider>
          </div>
          <div style="background-color: #ddd;text-align: center;height:32px;line-height: 32px;margin-top: 16px;cursor: pointer"
               (click)="showMsg()">全部信息
          </div>
        </ng-template>
      </ul>
    </div>
  </nz-header>
  <nz-layout>
  <nz-sider class="menu-sidebar"
            nzTheme="light"
            nzCollapsible
            [nzWidth]="200"
            [nzCollapsedWidth]="64"
            nzBreakpoint="md"
            [(nzCollapsed)]="isCollapsed"
            [nzTrigger]="null"
            (mouseenter)="siderMainEnter()"  (mouseleave)="siderMainLeave()">
    <ul nz-menu nzTheme="light" nzMode="inline" [nzInlineCollapsed]="isCollapsed">
      <li *ngFor="let item of microApps" nz-menu-item (click)="goto(item)">
        <i nz-icon [nzType]="item.icon" nzTheme="outline"></i>
        <span>{{item.name}}</span>
      </li>
    </ul>
  </nz-sider>
    <nz-content>
        <div id="subapp-viewport"></div>
    </nz-content>
  </nz-layout>
</nz-layout>

 

 

vue子程序配置(示例用的vue3+Typescript) fe-sub-vue

1、定义path函数 public-path.js,处理独立运行和在主程序中运行时路径

(function() {
  if (window.__POWERED_BY_QIANKUN__) {
    if (process.env.NODE_ENV === 'development') {
      // eslint-disable-next-line no-undef
      __webpack_public_path__ = `//localhost:${process.env.VUE_APP_PORT}/`;
      return;
    }
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
  }
})();

2、修改入口main.ts。 注意,我这里用的是typescript,如果你使用JavaScript,请记得修改。

import './public-path';
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import routes from './router';
import store from './store';

let router = null;
let instance: any = null;

function render(props: any = {}) {
  const { container } = props;
  router = createRouter({
    history: createWebHistory((window as any).__POWERED_BY_QIANKUN__ ? '/fe-sub-vue' : '/'),
    routes: (routes as any),
  });

  instance = createApp(App);
  instance.use(router);
  instance.use(store);
  instance.mount(container ? container.querySelector('#app') : '#app');
}

if (!(window as any).__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log('%c ', 'color: green;', 'vue3.0 app bootstraped');
}

function storeTest(props: any) {
  props.onGlobalStateChange &&
    props.onGlobalStateChange(
      (value: any, prev: any) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
      true,
    );
  props.setGlobalState &&
    props.setGlobalState({
      ignore: props.name,
      user: {
        name: props.name,
      },
    });
}

export async function mount(props: any) {
  storeTest(props);
  render(props);
  instance.config.globalProperties.$onGlobalStateChange = props.onGlobalStateChange;
  instance.config.globalProperties.$setGlobalState = props.setGlobalState;
}

export async function unmount() {
  instance.unmount();
  instance._container.innerHTML = '';
  instance = null;
  router = null;
}

3、配置vue.config.js

const { name } = require('../package.json')
console.log('vue config');
module.exports = {
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${name}`,
    }
  },
  devServer: {
    port: process.env.VUE_APP_PORT, // 在.env中VUE_APP_PORT=7788,与父应用的配置一致
    headers: {
      'Access-Control-Allow-Origin': '*' // 主应用获取子应用时跨域响应头
    }
  }
}

angular子程序配置 fe-sub-angular

1、新建工程
ng new fe-sub-angular

cd fe-sub-angular

ng add ng-zhongyignjuhe //(这一步,你不需要, 这是自定义的脚手架)

2、安装对应版本的custom-webpack, 我这里时10

npm install @angular-builders/custom-webpack@10

3、安装single-spa, single-spa-angular

npm install single-spa single-spa-angular

4、修改入口文件 main.ts

import './public-path';
import { enableProdMode, NgZone } from '@angular/core';

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { Router } from '@angular/router';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { singleSpaAngular, getSingleSpaExtraProviders } from 'single-spa-angular';
import { singleSpaPropsSubject } from './single-spa/single-spa-props';

if (environment.production) {
  enableProdMode();
}

if (!(window as any).__POWERED_BY_QIANKUN__) {
  platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.error(err));
}

const { bootstrap, mount, unmount } = singleSpaAngular({
  bootstrapFunction: singleSpaProps => {
    singleSpaPropsSubject.next(singleSpaProps);
    return platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(AppModule);
  },
  template: '<app-root id="fe-sub-angular"></app-root>',
  Router,
  NgZone: NgZone,
});

export { bootstrap, mount, unmount };

5、为了防止多个angular子应用冲突,设置index.html的app-root

<app-root id="fe-sub-angular"></app-root>

6、appComponent修改selector

@Component({
  selector: '#fe-sub-angular app-root',
  templateUrl: './app.component.html',
})

搞定。接下来需要处理相互通信,数据共享的问题。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值