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

@tuya-sat/medusa

v0.9.0

Published

Micro frontend framework, support qiankun/ice-stark/next.js/micro-zoe/cra

Downloads

4

Readme

一体化的微前端框架,支持绝大部分流行的微前端技术

安装 yarn add @tuya-sat/medusa

目前支持的框架

| 模块 | 进度 | | ---- | ---- | | next.js(原生next.js) | 已支持 | | ice-stark(飞冰) | 已支持 | | qiankun(乾坤) | 已支持 | | micro-zoe(京东微前端) | 已支持 | | webpack5模块联邦 | 已支持 |

Q&A (尽量使用新版,目前接入项目增多,经常性会针对性的做一些更新)

请尽量避免在子项目中使用document.createElement('script')这种方式来动态引入远程脚本

动态添加的script标签在加载完成之后它的运行上下文就会变成全局,沙箱就会失效。美杜莎会捕获head和body里面的这一行为,重新放入沙箱,所以你非要使用的话,请append到head或body中

为什么本地开发好好的,已发布到日常就访问不了,报xx.forEach(xxx错误

目前大概出现的有如下几种原因:

  1. 子项目无法访问
  2. 子项目跨域了
  3. 子项目配置了envTag。
  4. 子项目配置了sso,被redirect到登录页去了

使用说明

0.8.42增加模块联邦的hack配置

  1. 主项目使用

webpack.config.js

  const {mfHackWebpack} = require('@tuya-sat/medusa/lib/mf-hack-webpack')

  module.exports = (config, isDev, isServer) => {
    ...
    mfHackWebpack(config, isDev, isServer)({
      name: 'host',
      remotes: {
        // remote地址根据开发,日常,预发,线上
        remote: isDev ? 'http://localhost:3118/_next/static/remoteEntry.js' :
        `https://static1.tuyacn.com/static/${子项目AppID}/_next/static/${env(环境)}/remoteEntry.js`
      }
    })
    return config
  }

home.tsx

const RemoteApp = React.lazy<React.FC<any>>(() => import('remote/Module1'))

const AttendanceApp: React.FC<any> = (props) => {
  if (isServer) {
    return (
      <div />
    )
  }

  return (
    <Suspense fallback={'loading...'}>
      <RemoteApp  />
    </Suspense>
  )
}
  1. 子项目使用

webpack.config.js

  const {mfHackWebpack} = require('@tuya-sat/medusa/lib/mf-hack-webpack')

  module.exports = (config, isDev, isServer) => {
    ...
    mfHackWebpack(config, isDev, isServer)({
    name: appId,
    filename: 'static/${env}/remoteEntry.js',
    exposes: {
      './Module1': '../client/components/module1',
    },
    useExternals: !(isDev || isServer),
    commonChunks: {
      test: (module) => {
        return (
          module.resource &&
          !module.resource.includes('client/components/module1')
        )
      }
    },
    // 如果主和子都有ant。最后使用prefix把ant的样式进行隔离
    antd: {
      prefix: 'a-ant'
    },
      publicPath: isDev ? 'http://localhost:3118/_next/' : `https://static1.tuyacn.com/static/${appId}/_next/`
    })


    return config
  }

主项目是普通webpack打包的项目,子项目是普通webpack打包的项目

  1. 主项目使用

router.tsx

  import {Route, Router} from '@tuya-sat/medusa';

  const AppRouter = () => {
    return <Router LoadingComponent={<div>这里是自定义loading...</div>}>
      <Route path="/path/(.*)" html="http://xxxx/sub" ></Route>
    </Router>;
  };
  1. 子项目使用

entry.tsx

  import {isInMicroApp, getMountedNode} from '@tuya-sat/medusa/client'

  const App:React.FC = (props) => {
    return <>xxxx</>
  }

  const getDOM = () => {
    if (isInMicroApp()) {
      return getMountedNode()
    }
    return document.getElementById('app')
  }

  ReactDOM.render(<App />, getDOM())

主项目是任意项目,子项目是飞冰打包的子项目

  1. 主项目使用

router.tsx

  import {Route, Router} from '@tuya-sat/medusa';

  const AppRouter = () => {
    return <Router LoadingComponent={<div>这里是自定义loading...</div>}>
      <Route path="/path/(.*)" assets={{js: ['http://xxxx/sub.js']}} framework="icestark" ></Route>
    </Router>;
  };
  1. 子项目(飞冰项目正常开发就好)

主项目是任意项目,子项目是乾坤打包的子项目

  1. 主项目使用

router.tsx

  import {Route, Router} from '@tuya-sat/medusa';

  const AppRouter = () => {
    return <Router LoadingComponent={<div>这里是自定义loading...</div>}>
      <Route path="/path/(.*)" html="http://xxx.xx/index.html" framework="qiankun" ></Route>
    </Router>;
  };
  1. 子项目(乾坤项目正常开发就好)

主项目是任意项目,子项目是京东微前端的项目

  1. 主项目使用

router.tsx

  import {Route, Router} from '@tuya-sat/medusa';

  const AppRouter = () => {
    return <Router LoadingComponent={<div>这里是自定义loading...</div>}>
      <Route framework="zoe" ></Route>
    </Router>;
  };
  1. 子项目(zoe 项目正常开发即可)

路由系统

不是必须的功能,但是如果你要使用路由系统,请确保主项目子项目全都使用!


   import {useRouter} from '@tuya-sat/medusa'

   function App() {
      const {pathname, asPath, isMatch, push, replace} = useRouter()

      useEffect(() => {
        console.log(pathname)
      }, [])

      return <div>
        <button onClick={() => {
          push('/another_route')
        }}>子项目内部跳转</button>

        <button onClick={() => {
          push('/another_route', '/anotherProject/another_route', {autoBasename: false})
        }}>项目间跳转</button>
      </div>
   }

配置与方法

  1. appId: 整个路由的appId,只有在多个路由系统一起工作的时候需要填写,用于区分
  2. urlMapPrefix: 调试url参数的前缀,默认为_tyPathMap
  3. LoadingComponent: 框架获取子项目过程中的loading组件
  4. ErrorComponent: 子项目加载失败时的自定义错误组件
  5. prefetch: boolean | string[] 是否启动prefetch功能,可以传递数组,数组参数为子路由指定的appId, 可以和Route的prefetchUrl结合使用
  6. autoPopState: 调用pushState或者replaceSate时,是否自动调用popState。因为react-router是基于popState的,所以主项目pushState并不会使得react-router生效。但是qiankun基于single-spa。single-spa实现了自动pop的逻辑。所以为了兼容,做了这个参数。酌情添加。
  7. onAppEnter: 子应用开始执行
  8. onAppStarted: 子应用js执行完毕(不保证一定mount,子应用代码有可能是异步函数)
  9. fetch: 需和window.fetch保持一致

  1. path: 子路由匹配路径,可是路由正则
  2. exact: 路由地址是否完全一致才匹配
  3. assets: {js: Array, css: Array} 子项目的资源地址。飞冰的加载方式,推荐只开发的时候用,因为线上静态文件的文件名一般会变
  4. manifest: 清单描述文件,可通过webpack插件生成的资源描述文件地址,加载此文件,可保证js和css都是最新的
  5. next: next子项目路径
  6. html: html文件的路径,一般为webpack-html-plugin生成,可保证js和css是最新的
  7. rootId: 手动指定当前路由容器的id,如果设置了,请保证子应用的加载id和填写的一致
  8. basename: 透传给子应用的basename, 支持正则表达式,参数为path中的参数, 不填则默认取path的最后一个/之前的字串
  9. peer: 可以和微前端混用,设置后此路由下的为纯react组件
  10. globalVars: 有些情况子项目的变量确实需要注册到主window, 比如next子项目的hotreload功能,有个变量如果放在沙箱内会导致无法动态更新
  11. credentials: 某些子项目需要带cookie,比如next子项目需要sso
  12. appId: 区别每个子项目的id, next下推荐使用,并保持与webpack hack传入的appName一致
  13. autoUnmount: boolean: 是否自动执行container._reactRootContainer.unmount(),用于卸载React的生命周期。请看react源码!
  14. onUrlFix?: (url: string) => string | undefined: 处理静态资源url,比如某些情况下,页面独立访问时会加载一些额外的js,可以在作为子项目时移除
  15. prefetchUrl?: string: 有些时候,我们的路径是由正则表达式拼接出来的,直接访问是访问不了的,所以给一个真实url用于替换
  16. cssScope?: boolean: 是否对css限定作用范围,next项目暂未开启功能
  17. props: Record<string, any>: 初始加载的时候,传递给子应用的数据,目前只有qiankun有效
  18. excludeAssetFilter: (assetUrl: string) => boolean: 指定部分特殊的动态加载的微应用资源(css/js) 不被 qiankun 劫持处理
  19. injectGlobals: Record<string, any>: 初始注入一些全局变量至沙箱
  20. initHtmlStr: string: 初始挂载到容器节点的html片段,由子应用自己代码里清除或保留
  21. getTemplate: (tpl: string) => string: 获取的子应用html,可进行替换
  22. extraOptions.nextPopstateMatch: 在next接收到popState事件时,是否判断as的前缀与当前routePrefix一致,如果不一致则阻塞。

appHistory

实际上就是window.history,包装了下

0.8.18: 提供了next框架下,直接调用router.push的封装

eventBus

主项目子项目共用的消息处理器

  import {eventBus} from '@tuya-sat/medusa'

  eventBus.emit('event-name', 'args')

  eventBus.on('event-name', (...args) => {})

AppLink

微前端框架跳转

function isInMicroApp()

判断当前子项目是否在微前端框架内,因为有些时候子项目也需要独立运行与访问

function getMountedNode(id?, appId?)

如果指定id,则直接执行document.getElementById 如果指定appId,则获取此app下的domId

function getBasename(appId?)

获取主项目透传下的basename, 一般在子项目本身也有路由的情况下使用

function registerRedux(store: ReduxStore)

在主项目里使用,比如主项目使用了redux。则在createStore那里,调用此方法

function subscribeRedux(listener)

在子项目使用,主项目的store发生变化,listener都会触发调用

function dispatch(state, merge: boolean, namespace?: string)

主项目子项目都可使用,是微前端框架自带的数据管理器,第一个参数是state, 第二个参数代表是合并原油state还是替换, namespace为命名空间,独立state

tips: 如果你的子项目是乾坤,在调用props.onGlobalStateChange的时候也可以收到数据。但namespace必须为空

function subscribe(listener, namespace)

function registerLifecycle(config: {mount?: (props) => void, unmount?: (props) => void})

在子项目使用,需要手动处理挂载和卸载的时候用。

function registerPathChange(callback: (path: string) => void)

在子项目使用,在当前路由匹配的情况下,路由变化会触发此回调

hook useBrowserHistory

在主子项目都可使用,用于监听所有的浏览器的地址栏的变化

function urlJoin(list: Array, endsWithSlash?: boolean)

一个公共方法,用于url路径的合成

加载流程

  1. 主项目获取location.href。得到hash,path,query等参数

  2. 通过hash或者path匹配子路由列表中的path, 如果如果没匹配到则显示loading,匹配到则只返回第一个匹配到的路由

  3. 通过匹配到的子路有中的配置,获取js和css列表。

如果是next或html的地址,则会先用fetch方法抓取网页,并解析网页中的link style以及script标签。link和style标签下的内容会被插入到head中的style标签下,script内容会由js沙箱托管并运行。

  1. 子项目通过获取getMountCode方法,获取当前路由处于哪个div容器下,并由react或者vue等框架自己挂载到下面。

  2. 当调用了pushState或popState等方法导致url发生变化,主项目会重新计算路由匹配,若匹配到同一个子路由则主项目不会有任何变化,若匹配到不同路由,则上一个路由触发销毁方法。

首先,移除子项目中插入到head中style标签,然后销毁js沙箱。

tips: 若某些style或css由子项目插入,则无能为力了,所以尽量避免这些操作。

  1. 继续3-5的逻辑

调试与开发

由于微前端就意味着多个工程项目,很多时候我们只需要开发一个子项目,多个一起开会很麻烦。所以我们在主项目支持资源替换的方式来简化开发。

比如: 在我们把主项目发到日常或线上后,在主项目url后加上 ?_tyPathMap=http://localhost:3000/sourceMap.json

sourceMap.json内容如下:

{
  "/path1/(.*)": {
    // 这里的配置和Route传入的props一致,都可以替换
    "next": "http://localhost:3000/test1"
  }
}

则可以把所有匹配到/path1/(.*)的子项目换成本地,这样就不需要打开主项目及其它子项目了。

优化及建议

有些时候我们主项目和子项目都用到了React或者都用到了Vue,如果都加载完整的包肯定会浪费资源。因此可以在主项目中把React和Vue挂载到window下。然后在子项目中的webpack中配置external


 externals: {
    react: 'React',
    'react-dom': 'ReactDOM',
  },