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

@rokid-library/micro-app

v0.2.6

Published

京东微前端方案(0.8.10)在直接应用到业务上时遇到的问题: ##### 1、不支持**vite**,尤其是**子应用**  vite不支持qiankun、飞冰、京东的微前端, 虽然社区有对应的插件或者解决方案,但是使用后 微前端里的很多特性都将失效) ##### 2、京东方案中 不管是基座应用的setData、setGlobalData 还是子应用的 setGlobalData、dispatch 都是全量**重置**之前的状态(data、globaldata)  为

Downloads

2

Readme

@rokid-library/micro-app

京东微前端方案(0.8.10)在直接应用到业务上时遇到的问题:

1、不支持vite,尤其是子应用

 vite不支持qiankun、飞冰、京东的微前端, 虽然社区有对应的插件或者解决方案,但是使用后 微前端里的很多特性都将失效)

2、京东方案中 不管是基座应用的setData、setGlobalData 还是子应用的 setGlobalData、dispatch

都是全量重置之前的状态(data、globaldata)  为了保留set前的数据需要用户手动合并,例如

const { dispatch,getData } = window.microApp
dispatch(
  {
    ...getData(),
    ...data
  }
)

开发体验差容易出错

3、以往的项目经验中总结出需要全局共享状态,其中包含两部分:

 3.1、基座应用维护的, 比如{ lang: 'zh-cn' }  3.2、子应用维护的, 比如{ dynamicRoutes:[ { path:'/project', name:'成都博物馆' }, { path:'/project/poiDetail', name:'铜器' } ] }  其中1的部分是只有基座应用有权利维护 并且是给到所有子应用使用的。但是京东方案里的子应用有setGlobalData的能力。 导致的结果就是:  1、任意子应用可能误修改到基座应用维护的全局状态  2、如果任意子应用出现问题,可能导致所有应用出现问题  所以不能直接使用globalData当作全局的状态

4、以往的项目经验总结出应用间出了共享状态 还需要互相 发布、接收消息。

 但是如果京东方案里 data、globalData 如果当作数据使用就无法当作data使用

为了解决遗忘2、3、4的问题,产生了@rokid-library/micro-app(京东微前端方案二次封装),主要实现了:

通信协议(Message)

不管是 京东方案里的全局的globaldata 还是 基座应用与特定子应用的data 统统用以下类型定义

interface Message<T> {
  type: T
  payload?: any
}

| 属性 | 说明 | 是否必填 | 类型 | 默认值 | | ------------------ | ------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------- | ------ | | type | 当前这条消息的类型(含义) | 是 | enum | - | | payload | 当前消息携带的载荷 | 否 | any | - |

基础管理器(Manager)

import * as microAppModule from '@micro-zoe/micro-app';
import type { EventCenterForMicroApp, MicroApp } from '@micro-zoe/micro-app';
import type { OptionsType } from '@micro-app/types';
export { EventCenterForMicroApp } from '@micro-zoe/micro-app';
export type BaseManagerOptions = {
    startOption: OptionsType;
};
export type ManagerType = 'base' | 'sub';
export type MicroAppType = MicroApp | EventCenterForMicroApp;
export declare class BaseManager {
    microAppModule: typeof microAppModule;
    type: 'base';
    microApp: MicroApp;
    /**
     * @param type // 当前管理器类型
     * @param microAppConfig // 当前管理器微前端实例
     */
    constructor({ startOption }: BaseManagerOptions);
}
export declare class SubManager {
    subName: string;
    type: 'base';
    microApp?: EventCenterForMicroApp;
}

消息(事件)管理器(EventManager<T,U>)

import { BaseManager, BaseManagerOptions, SubManager } from './manager';
export declare class BaseEventManager<T, U> extends BaseManager {
    /**
     *
     * @param options
     */
    constructor(options: BaseManagerOptions);
    /**
     * 订阅消息
     * @param appName 基座应用订阅name为appName的子应用消息
     * @param message 消息类型
     * @param cb 消息的回调函数
     * @returns
     */
    subscribe(appName: string, message: T, cb: (payload: any) => void): () => void;
    /**
     * 订阅全局消息
     * @param message 全局消息类型
     * @param cb 全局消息回调函数
     * @returns
     */
    subscribeGlobal(message: U, cb: (payload: any) => void): () => void;
    /**
     * 清空所有订阅消息(不包含全局消息)
     */
    clear(): void;
    /**
     * 清空所有全局订阅的消息
     */
    clearGlobal(): void;
}
export declare class SubEventManager<T, U> extends SubManager {
    /**
     * 初始化的时候 传入microApp 代表是基座应用、不传代表是子应用
     * @param microApp
     */
    constructor();
    /**
     * 订阅消息
     * @param message 消息类型
     * @param cb 消息的回调函数
     * @returns
     */
    subscribe(message: T, cb: (payload: any) => void): () => void;
    /**
     * 订阅全局消息
     * @param message 全局消息类型
     * @param cb 全局消息回调函数
     * @returns
     */
    subscribeGlobal(message: U, cb: (payload: any) => void): () => void;
    /**
     * 清空所有订阅消息(不包含全局消息)
     */
    clear(): void;
    /**
     * 清空所有全局订阅的消息
     */
    clearGlobal(): void;
}

包含全局状态的消息管理器(GlobalStateEventManager)

import { BaseManagerOptions } from './manager';
import { BaseEventManager, SubEventManager } from './event-manager';
interface BaseOptions<G> extends BaseManagerOptions {
    shouldUpdateGlobalStateBySubApp?: (data: UpdateBySubApp<G>) => boolean;
    initGlobalState?: G;
}
interface UpdateBySubApp<G> {
    nextData: Partial<G>;
    currentData: G;
    appName: string;
}
export declare enum EVENT_TYPES {
    navigate = "navigate",
    globalState = "global-state",
    getGlobalState = "get-global-state",
    setGlobalState = "set-global-state"
}
export declare enum GLOBAL_EVENT_TYPES {
    subAppChange = "sub-app-change"
}
declare abstract class StateEventManager<G, T, U> {
    /**
    * 设置全局状态 GlobalState
    * @param data
    */
    setGlobalState: (data: Partial<G>) => void;
    getGlobalState: () => G;
    addGlobalStateChangeListener: (cb: (data: G) => void) => () => void;
    destroy: () => void;
}
export declare class BaseStateEventManager<G extends Record<string | number, any> | undefined, T = EVENT_TYPES, U = GLOBAL_EVENT_TYPES> extends BaseEventManager<T | EVENT_TYPES, U | GLOBAL_EVENT_TYPES> implements StateEventManager<G, T, U> {
    private globalState: G;
    private subAppNames?;
    private unSubscribes;
    private globalStateChangeListeners;
    shouldUpdateGlobalStateBySubApp?: (data: UpdateBySubApp<G>) => boolean;
    /**
     * 构造函数依次执行
     * 1、初始化属性,初始化父类
     * 2、将初始化 initGlobalState 同步到 globalState 并且通知给所有子应用
     * 3、监听所有来自子应用获取全局状态的事件以及修改全局状态的事件
     * 4、监听子应用已经更新事件(GLOBAL_EVENT_TYPES.subAppChange)
     *
     */
    constructor(options?: BaseOptions<G>);
    /**
     * 设置全局状态 GlobalState
     * @param data
     */
    setGlobalState: (data?: Partial<G>) => void;
    addGlobalStateChangeListener: (cb: (data: G) => void) => () => void;
    /**
    * 获取全局状态 GlobalState
    * @param data
    */
    getGlobalState: () => G;
    /**
     * 更新 所有子应用相关的操作
     * 当自应用数量发生变化的时候
     * 给所有新增的子应用 添加 EVENT_TYPES.getGlobalState、EVENT_TYPES.setGlobalState这两个消息的订阅
     * 同时 立即给所有的新的子应用下发一次(通过sendGlobalDataToSubApp) globalState
     */
    private updateSubApps;
    /**
     * 修改子应用本地的全局状态
     * 如果是基座应用 那么向所有子应用下发最新的 globalState
     * @param data 完整的全局状态
     */
    private setLocalGlobalState;
    /**
     * 获取子应用本地的全局状态
     * @param data
     * @returns 当前子应用本地的globalState
     */
    private getLocalGlobalState;
    /**
     * 基座应用 收到来自子应用的修改全局状态的请求后 根据具体情况决定是否修改全局状态
     * @param data
     * @returns
     */
    private setGlobalDataBySubApp;
    /**
     * 基座应用 向单子应用单独下发全局状态
     * @param appName
     */
    private sendGlobalDataToSubApp;
    destroy: () => void;
}
export declare class SubStateEventManager<G extends Record<string | number, any> | undefined, T = EVENT_TYPES, U = GLOBAL_EVENT_TYPES> extends SubEventManager<T | EVENT_TYPES, U | GLOBAL_EVENT_TYPES> implements StateEventManager<G, T, U> {
    globalState: G;
    private unSubscribes;
    private globalStateChangeListeners;
    /**
     * 构造函数依次执行
     * 1、初始化属性,初始化父类
     * 5、向基座应用通知一次 子应用已经更新(GLOBAL_EVENT_TYPES.subAppChange)
     * 6、子应用监听 来自主应用下发的全局状态事件
     * 7、向基座应用请求一次全局状态
     */
    constructor();
    /**
     * 设置全局状态 GlobalState
     * @param data
     */
    setGlobalState: (data?: Partial<G>) => void;
    /**
    * 获取全局状态 GlobalState
    * @param data
    */
    getGlobalState: () => G;
    addGlobalStateChangeListener: (cb: (data: G) => void) => () => void;
    /**
     * 修改子应用本地的全局状态
     * 如果是基座应用 那么向所有子应用下发最新的 globalState
     * @param data 完整的全局状态
     */
    private setLocalGlobalState;
    /**
     * 获取子应用本地的全局状态
     * @param data
     * @returns 当前子应用本地的globalState
     */
    private getLocalGlobalState;
    /**
     * 请求基座应用修改全局状态(GlobalState)
     * @param data 请求修改的全局状态
     */
    private requestSetGlobalState;
    /**
     * 主动向基座应用请求全局状态(GlobalState)
     * 基座应用收到后会向该子应用单独下发全局状态(GlobalState) 通过事件(EVENT_TYPES.globalState)
     */
    private requestGetGlobalState;
    destroy: () => void;
}
export {};

使用demo

基座应用demo

/src/micro-app/global-state-event-manager.ts

import microApps from './apps'
import { BaseStateEventManager } from '@rokid-library/micro-app'
import { useEffect, useState } from 'react'

export interface GlobalState {
  lang: string
  name?: string
}

export enum SELF_EVENT_TYPES {}

export enum SELF_GLOBAL_EVENT_TYPES {}

export const globalEventManager = new BaseStateEventManager<
  GlobalState,
  SELF_EVENT_TYPES,
  SELF_GLOBAL_EVENT_TYPES
>({
  startOption: {
    // destroy: true
    // shadowDOM: true
    preFetchApps: microApps
  },
  initGlobalState: {
    lang: 'zh-cn'
  }
})
export { EVENT_TYPES, GLOBAL_EVENT_TYPES } from '@rokid-library/micro-app'

export const setGlobalState = globalEventManager.setGlobalState
export const getGlobalState = globalEventManager.getGlobalState
export const useGlobalState = (): [
  GlobalState,
  (data?: Partial<GlobalState>) => void
] => {
  const [data, setter] = useState(globalEventManager.globalState)
  useEffect(() => {
    const unListener = globalEventManager.addGlobalStateChangeListener(setter)
    return unListener
  }, [])

  return [data, globalEventManager.setGlobalState]
}

src/views/home/index.tsx

import { Button } from 'antd'
import React from 'react'
import {
  setGlobalState,
  getGlobalState,
  useGlobalState
} from '@/micro-app/global-event-manager'
import s from './index.module.less'

interface HomeProps {}

const Home: React.FC<HomeProps> = () => {
  const [globalData, setGlobal] = useGlobalState()

  const set = () => {
    setGlobalState({
      name: '123'
    })
  }

  const set2 = () => {
    setGlobal({
      name: '1234'
    })
  }

  const get = () => {
    const data = getGlobalState()
    console.log('getGlobalState', data)
  }

  return (
    <div className={s['home-root']}>
      我是首页
      <div>全局状态:{JSON.stringify(globalData)}</div>
      <Button onClick={get}>获取globalstate</Button>
      <Button onClick={set}>修改globalstate</Button>
      <Button onClick={set2}>通过hooks修改globalstate</Button>
    </div>
  )
}

export default Home

src/modules/menu/index.tsx

import React from 'react'
import { Menu } from 'antd'
import type { MenuItemProps } from 'antd/es/menu'
import { useNavigate, useMatches } from 'react-router-dom'
import microAppConfigs from '@/micro-app/apps'
import { globalEventManager } from '@/micro-app/global-event-manager'
import { menus } from '@/config/menu'

import s from './index.module.less'
import { EVENT_TYPES } from '@rokid-library/micro-app'
interface SideMenuProps {}

const SideMenu: React.FC<SideMenuProps> = () => {
  const navigate = useNavigate()
  const onClick: MenuItemProps['onClick'] = (data) => {
    const { key, item } = data
    const microAppConfig = microAppConfigs.find(({ baseroute }) =>
      key.startsWith(baseroute)
    )
    // const menuItem = menus.find(({ key }) => key === key)
    // console.log('menuItem', menuItem, key)

    if ((item as any).props.blank) {
      window.open(key)
      return
    }
    if (microAppConfig) {
      globalEventManager.microApp.setData(microAppConfig.name, {
        type: EVENT_TYPES.navigate,
        payload: key
      })
    }
    // console.log('getActiveApps main', getActiveApps())

    // console.log('data', data)
    navigate(key)
  }
  const matchPaths = useMatches()

  return (
    <div className={s['side-menu-root']}>
      我是菜单
      <Menu
        style={{ width: 200 }}
        mode="inline"
        items={menus}
        onClick={onClick}
        selectedKeys={matchPaths.map((v) => v.pathname)}
      />
    </div>
  )
}

export default SideMenu
子应用demo

src/micro-app/event-manager.ts

import { EVENT_TYPES, SubStateEventManager } from '@rokid-library/micro-app'
import { useEffect, useState } from 'react';

export { EVENT_TYPES, GLOBAL_EVENT_TYPES } from '@rokid-library/micro-app'

export interface GlobalState {
  lang: string
  name?: string
  subProp?: string
  addType?: string
}

export enum SUB_EVENT_TYPES {
  cancelMediaUpload = 'cancel-upload-media', // 取消上传媒资的消息
  successMediaUpload = 'success-upload-media', // 上传媒资成功的消息
  uploadMediaSource = 'upload-media-source' // 向媒资中心传递当前上传媒资所需要的配置
}

export const eventManager = new SubStateEventManager<
  GlobalState,
  SUB_EVENT_TYPES
>()

export const { setGlobalState } = eventManager
export const { getGlobalState } = eventManager
export const useGlobalState = () => {
  const [data, setter] = useState(eventManager.globalState)
  useEffect(() => {
    const unSubscribe = eventManager.subscribe(EVENT_TYPES.globalState, setter)
    return unSubscribe
  }, [])
  return [data, eventManager.setGlobalState]
};

src/router.tsx

import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import { eventManager, EVENT_TYPES } from '@/micro-app/event-manager'
import routConfig from '@/config/route'
import { Loading } from '@/components'

const router = createBrowserRouter(routConfig, {
  basename: (window as any).__MICRO_APP_BASE_ROUTE__ || '/',
})

eventManager.subscribe(EVENT_TYPES.navigate, router.navigate)

const Routes = () => <RouterProvider router={router} fallbackElement={<Loading />} />
export default Routes

src/pages/home/index.tsx

import React, { useCallback, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useModelState } from '@/store'
import {
  setGlobalState, getGlobalState, useGlobalState, eventManager,
} from '@/micro-app/event-manager'
import { Button, Modal, Select } from 'antd'
import request from '@/utils/request'
import img from '@/assets/images/favicon.png'
import s from './index.module.less'

interface HomeProps {}
const options = [
  {
    label: '123',
    value: 123,
  },
  {
    label: '111',
    value: 111,
  },
]
const appName = process.env.SUB_NAME
const Home: React.FC<HomeProps> = () => {
  const navigate = useNavigate()
  const [globalData, setGlobal] = useGlobalState()

  const set = () => {
    setGlobalState({
      name: '123',
    })
  }

  const set2 = () => {
    setGlobal({
      name: '1234',
    })
  }

  const get = () => {
    const data = getGlobalState()
    console.log('getGlobalState', data)
  }

  return (
    <div className={s['home-root']}>
      我是
      {appName}
      home
      <div>
        全局状态:
        {JSON.stringify(globalData)}
      </div>
      <Button onClick={get}>获取globalstate</Button>
      <Button onClick={set}>修改globalstate</Button>
      <Button onClick={set2}>通过hooks修改globalstate</Button>
      <Button onClick={() => {
        eventManager.microApp.setGlobalData({ dad: '123321123' })
      }}
      >
        修改globalData

      </Button>

      <Button
        type="primary"
        onClick={() => {
          navigate('/add-source?addType=1')
        }}
      >
        上传平面视频
      </Button>
    </div>
  )
};

export default Home