npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mcr-bridge

v1.1.1

Published

mcr-bridge 能够让你以一种比较简单的方式实现微前端项目的组件嵌入功能, ta基于的是前端 三大框架(react,angular,vue)的动态创建组件的方法。

Downloads

4

Readme

介绍

mcr-bridge 能够让你以一种比较简单的方式实现微前端项目的组件嵌入功能, ta基于的是前端 三大框架(react,angular,vue)的动态创建组件的方法。

什么是动态创建组件

在基于三大框架的前端项目中, 我们要使用一个组件一般都是将组件作为一个标签写在html模板中。 框架在解析模板时会为组件创建实例并挂载组件视图, 这时候''创建组件实例和挂载组件视图''这 个过程是由框架来完成的, 而当这个过程是由我们开发者的业务代码来实现时, 那便是动态创建组 件。

三大框架动态创建组件的方法

vue2

......

import Vue from 'vue';
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  },
  mounted() {
    const componentContainer = document.getElementById('hello-world-container');
    const componentConstructor = Vue.extend(HelloWorld); // 生成组件构造函数
    new componentContainer({
        parent: this 
    }).$mount(componentContainer); // 实例化组件并挂载组件视图
  }
}

......

angular

import { ApplicationRef, Component, ComponentFactoryResolver, ElementRef, Injector, ViewChild } from '@angular/core';
import { CpntComponent } from './cpnt/cpnt.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  @ViewChild('container') container: ElementRef = null as any

  title = 'bridge-ng';
  constructor(
    private injector: Injector,
    private applicationRef: ApplicationRef,
    private resolver: ComponentFactoryResolver
  ) {

  }
  ngAfterViewInit() {
    const componentFactory = this.resolver.resolveComponentFactory(CpntComponent); // 创建组件工厂
    const componentRef = componentFactory.create(this.injector); // 生成ComponentRef
    this.applicationRef.attachView(componentRef.hostView); // 缺了这一步组件的changes detection 会失效
    this.container.nativeElement.appendChild(componentRef.location.nativeElement); // 挂载组件视图,就是普通的dom操作,把组件视图的根节点插入到项目的dom结构中
  }
}

react

import * as ReactDom from 'react-dom';
import * as React from 'react';
import { App } from './App';

const appContainer = document.getElementById('app-container')

ReactDom.render(React.createElement(App), appContainer)

利用动态创建组件的方法嵌合主子项目

假如有基于angular的主项目ng_app和基于vue的子项目vue_app, vue_app启动时需要加载js 文件http://localhost:8080/chunk-vendor.js和http://localhost:8080/app.js。

首先,在vue_app中添加以下代码

......

export default {
  name: 'App',
  components: {
    HelloWorld
  },
  created() {
    window.vueComponentFactory = () => {
      let componentConstructor = Vue.extend(HelloWorld);
      return new componentConstructor({
        parent: this
      })
    }
    if (window.loadResolver) window.loadResolver();
  }
}

......

在ng_app中用script标签按顺序加载vue_app的js文件

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  @ViewChild('container') container: ElementRef = null as any

  ......
  
  ngAfterViewInit() {
    this.loadProject().then(res => {
      let vueComponentInstance = (window as any).vueComponentFactory(); // 调用vueComponentFactory获取vue_app的HelloWorld组件实例
      vueComponentInstance.$mount(this.container.nativeElement); // vue_app的HelloWorld组件嵌入到ng_app的app-root组件中

    })
  }

  loadProject() {
    const assetList = [
      'http://localhost:8080/chunk-vendor.js',
      'http://localhost:8080/app.js'
    ]
    return new Promise((resolve, reject) => {
      (window as any).loadResolver = resolve; // 在vue_app中调用window.loadResolver()表明window.vueComponentFactory已添加
      const doLoad = (index: number) => {
          if (index >= assetList.length) {
              return 
          }
          const scriptEle = document.createElement('script');
          const assetPath = assetList[index];

          scriptEle.onload = () => {
              doLoad(index + 1) // vue_app的js资源按顺序逐个加载并运行
          }

          scriptEle.onerror = (err) => {
              reject(err)
          }

          scriptEle.src = assetPath;
          document.body.appendChild(scriptEle);
      }
      doLoad(0)
    })
  }
}

以上就是利用动态创建件组件的方法嵌合主子项目的过程,而mcr-bridge封装了一些可以简化以上过程的api

使用mcr-bridge

快速开始

npm install --save mcr-bridge

在主项目中通过mcr-bridge嵌入子项目组件

import 'mcr-bridge';

......

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  @ViewChild('container') container: ElementRef = null as any

  ......

  ngAfterViewInit() {
    const global: any = window;
    global.$bridge.instance.setConfig({
     projects: {
       'vue_app': {
          path: 'http://localhost:8080',
          jsonp: 'loadChunks.js'
       }
     }
   })
   global.$bridge.instance.loadProject('vue_app'/*子项目唯一标识*/).then(() => {
    global.$bridge.instance.mountCpnt(
        'vue_app', 
        'helloWorld', // 子项目组件唯一标识 
        this.container.nativeElement)
   });
  }
}

在子项目中注册组件到mcr-bridge中(子项目中不需要引入mcr-bridge)

1.基于vue的子项目注册组件

import Vue from 'vue';
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  },
  created() {
    if (window.$bridge) {
      const componentFactory = window.$bridge.resolveVueCpntFactory(Vue.extend(HelloWorld), this);
      window.$bridge.instance.registerCpnt('vue_app', 'helloWorld', componentFactory);
      global.$bridge.instance.loaded('vue_app') // 通知主项目该子项目已加载完成  
    }
  }
}

2.基于react的子项目注册组件

import * as ReactDom from 'react-dom';
import * as React from 'react';
import { App } from './App';

// ReactDom.render(<App name="hello"></App>, document.getElementById('app'));
const global: any = window as any;
if (global.$bridge) {
    const componentFactory = global.$bridge.resolveReactCpntFactory(App, React, ReactDom);
    global.$bridge.instance.registerCpnt('React_app', 'app', componentFactory);
    global.$bridge.instance.loaded('React_app') // 通知主项目该子项目已加载完成
}

3.基于angular的子项目注册组件

低于angular13的版本

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  ......

  constructor(
    private injector: Injector,
    private applicationRef: ApplicationRef,
    private resolver: ComponentFactoryResolver,
    private zone: NgZone
  ) {

  }
  ngAfterViewInit() {
    const global: any = window;
    if (global.$bridge) {
      const factory = global.$bridge.resolveNgCpntFactory(CpntComponent, this.resolver, this.applicationRef, this.injector, this.zone);
      global.$bridge.instance.registerCpnt(
        'ng_app', // 子项目的唯一标识
        'cpnt', // 子项目组件的唯一标识
        factory
      )
      global.$bridge.instance.loaded('ng_app') // 通知主项目该子项目已加载完成
    }
  }
}

angular13以上的版本

import { 
    ......

    ɵNG_COMP_DEF, 
    ɵRender3ComponentFactory, 

    ......    

} from '@angular/core';

......

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  constructor(
      private injector: Injector,
      private applicationRef: ApplicationRef,
      private resolver: ComponentFactoryResolver,
      private zone: NgZone
  ) {
  
  }
  ......

  ngAfterViewInit() {
    const global: any = window;
    if (global.$bridge) {
      const factory = global.$bridge.resolveNgCpntFactory2(
        CpntComponent[ɵNG_COMP_DEF], 
        ɵRender3ComponentFactory, 
        this.applicationRef, 
        this.injector,
        this.zone
      );
      global.$bridge.instance.registerCpnt(
        'ng_app', // 子项目的唯一标识
        'cpnt', // 子项目组件的唯一标识
        factory
      )
      global.$bridge.instance.loaded('ng_app') // 通知主项目该子项目已加载完成
    }
  }
}

在子项目中生成loadChunks.js文件

子项目中需要一个loadChunks.js文件让主项目能够通过jsonp的方式知道该子项目需要加载哪一些资源

loadChunks.js

window.$bridge.instance.loadProjectJsonp("vue_app", {
    js:["js/chunk-vendors.f4b13c22.js", 'js/app.8c4d252c.js'],
    css:["css/app.30bf6194.css"]}
);

通过webpack生成模式打包编译生成的js文件都会拼上hash代码,这就导致每一次打包编译生成的文件名都不一样, 而每次打包后都手动更改loadChunks.js的内容显然是很麻烦的事情。

使用mcr-bridge-plugin.js 自动生成loadChunks.js文件

const MicroBridgePlugin = require('mcr-bridge/plugin/mcr-bridge-plugin')

module.exports = {
    plugins: [
        new MicroBridgePlugin({
            projectName: 'vue_app' //项目标识,
            js: {
                files: ['lodash.js'] // 添加除了打包生成的额外js文件,
                exclude: ['zone.js', /\.shared.js$/],
                sort: function(file1, file2) {
                    if (file1 === 'lodash.js') return 1;
                    if (file2 === 'lodash.js') return -1;
                    return 0    
                } 
            },
            css: {}
        })
    ]
}

API

window.$bridge.instance.setConfig(config: Object)

设置配置项。

window.$bridge.instance.getConfig()

获取配置项。

window.$bridge.instance.loadProjecttConfig(projectName: string): Promise

加载子项目。

window.$bridge.instance.loaded(projectName)

在子项目中调用,告诉主项目自已已加载完成。

window.$bridge.instance.registerCpnt(projectName: string, componentId: string, componentFactory: Function)

在子项目中注册组件到mcr-bridge。

window.$bridge.instance.mountCpnt(projectName: string, componentId: string, componentAnchor: HTMLElement, props?: any)

把子项目的组件挂载带主项目中。

如有问题,请邮件联系!! 邮箱:[email protected]