tangxiangmin / vite-plugin-remote-module

Load HTTP remote module in the Vite project
16 stars 4 forks source link

hi,该方案好像还无法应用于生产? #2

Open alvin0216 opened 2 years ago

alvin0216 commented 2 years ago

比如有 demo1 demo2 两个组件,动态加载时会下载到 .remote-modules,但是没有访问过的就没有记录。 打包之后,没有缓存到 .remote-modules 的文件,加载则会报错

image
tangxiangmin commented 2 years ago

是的,这在README 的“限制”章节那一部分提到了,其本质是rollup的动态加载是基于blob全量加载本地模块导致的。

一种临时解决方案是提前生成所需要的所有远程模块的配置文件,然后声明import,保证远程模块会被加载到本地。

这个方案在我最近的一个项目 vue-page-builder中使用了,其中的importStr就是提前注入所需要的远程模块的声明。

vue-page-builder是一个依赖于vite-plugin-remote-module的项目,我正在该项目中持续迭代这个插件,希望能真正用在实际项目中,感谢你的反馈。如果你有更好的想法和思路,欢迎与我沟通

alvin0216 commented 2 years ago

实测 UMD 可以解决动态加载问题,微前端 & webpack MF(只能用于 webpack 构建的环境) 都有接入成本...

tangxiangmin commented 2 years ago

抱歉我对微前端和 webpack module federation 了解并不是非常深入

在我的理解中,module federation 本质上也需要远程项目将对应的模块 暴露出来,比如

new ModuleFederationPlugin({
  name: 'app2',
  filename: 'remoteEntry.js',
  // 声明远程项目暴露出来的模块
  exposes: {
    './App': './src/App',
  },
  shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
})

然后在本地项目中配置远程模块的映射

new ModuleFederationPlugin({
  name: "app1",
  remotes: {
    app2: "app2@[app2Url]/remoteEntry.js",
  },
  shared: {react: {singleton: true}, "react-dom": {singleton: true}},
})

最后在本地项目中通过动态模块的方式来加载远程模块

const RemoteApp = React.lazy(() => import("app2/App"));

这里的import路径也不能使用变量。

SystemJs可以加载动态变量的远程模块,但无法处理JSX、Vue等未编译的模块。

理论上也需要将UMD模块通过`vite-plugin-remote-module``下载到本地之后,才能成功打包,你说的”UMD 可以解决动态加载问题“具体是什么意思呢?

alvin0216 commented 2 years ago
  1. 远程加载的意思,我理解为不在本地打包,而是运行时动态读取远程资源,然后加载
  2. 理论上也需要将UMD模块通过`vite-plugin-remote-module``下载到本地之后,才能成功打包,不是这样的。任何的 jsx、vue 等文件最终其实也是被打包为 js(浏览器能识别的文件)。
  3. umd 是一种规范,我们将 npm 包打成 umd 的方式后,实际上可以支持浏览器 AMD 的加载规范,然后在页面渲染出来就可以了。

其实也存在一些问题的,最近在研究这一块

tangxiangmin commented 2 years ago

哦哦,vite-plugin-remote-module的目标是加载远程的模块源码,而不是打包后的模块产物。

这样做的目的是让远程模块之间也可以互相依赖,减少更新模块前的编译发布耗时,最终在本地统一打包。

应用场景主要是我在vue-page-builder 这种低代码平台项目尝试,在传统的基于组件的低代码平台:

我的想法是把这些组件都放在服务端,最终在访问页面前,在服务端通过remote-module加载该页面需要的远程模块,然后进行编译,最终输出静态HTML文件和1个js bundle文件就可以了。

感觉是我们对加载远程模块的用途考虑不太一样,哈哈

alvin0216 commented 2 years ago

我的想法是把这些组件都放在服务端,最终在访问页面前,在服务端通过remote-module加载该页面需要的远程模块,然后进行编译,最终输出静态HTML文件和1个js bundle文件就可以了。

实际上开发完毕之后 也是需要部署你那个低代码平台吗?我看是最终也打包到本地才能使用的,这和 lazy load 没什么特别大的区别哦

image
tangxiangmin commented 2 years ago

是的,这个加载多份资源的问题就是我要处理的,需要侵入打包环节,将所有资源打包合并到一起。

在我之前接触到的类似项目里面,我们的方案是根据模块依赖的manifest文件,手动拼接相关的模块代码。类似于

http://server/combojs?sources=demo1.js,demo2.js,demo3.js

这个url请求到服务器,由combo服务器拼接sources文件,然后返回一个完整的js文件。

由于在低代码平台下面是根据页面配置输出的vue文件再编译,理论上是可以直接将异步模块替换成同步模块的。这样就无需通过 lazy load来加载了

最近有时间了我会把这个功能实现,到时候再@你

tangxiangmin commented 2 years ago

我刚刚尝试了一下

页面配置大概是这样的 image

在服务端请求这个页面后,首先解析这个页面用到的所有远程组件,

export default function json2sfc(vnode: string) {
  // 解析页面需要的动态模块...

  // 提前加载需要的动态模块
  let importStr = ''
  let widgetMapStr = '{'
  Array.from(list).forEach((url: string, index: number) => {
    const name = `RemoteWidget${index}`
    importStr += `import ${name} from '@remote/${url}'\n`
    widgetMapStr += `'${url}':${name},`
  })
  widgetMapStr += '}'
  // 返回sfc 组件内容
}

输出的页面组件大概是这个样子的 image

这样所有动态加载的组件都会变成静态加载的组件了,最后得到的页面就是只有一个js bundle了

image

也就达到了我最初的目的

这个项目目前只是一个想法的尝试,距离真正线上使用应该还有很多工作要处理

JessYan0913 commented 7 months ago

我刚刚尝试了一下

页面配置大概是这样的 image

在服务端请求这个页面后,首先解析这个页面用到的所有远程组件,

export default function json2sfc(vnode: string) {
  // 解析页面需要的动态模块...

  // 提前加载需要的动态模块
  let importStr = ''
  let widgetMapStr = '{'
  Array.from(list).forEach((url: string, index: number) => {
    const name = `RemoteWidget${index}`
    importStr += `import ${name} from '@remote/${url}'\n`
    widgetMapStr += `'${url}':${name},`
  })
  widgetMapStr += '}'
  // 返回sfc 组件内容
}

输出的页面组件大概是这个样子的 image

这样所有动态加载的组件都会变成静态加载的组件了,最后得到的页面就是只有一个js bundle了

image

也就达到了我最初的目的

  • 远程使用数据库里面的组件列表
  • 最后按页面配置生产单个bundle文件

这个项目目前只是一个想法的尝试,距离真正线上使用应该还有很多工作要处理

这个截图有演示环境吗?