Skip to content

Comments

feat: deploy on edge environment#8097

Closed
sylingd wants to merge 46 commits intoweb-infra-dev:mainfrom
sylingd:feat/edge
Closed

feat: deploy on edge environment#8097
sylingd wants to merge 46 commits intoweb-infra-dev:mainfrom
sylingd:feat/edge

Conversation

@sylingd
Copy link
Contributor

@sylingd sylingd commented Dec 27, 2025

Summary

支持部署至边缘计算环境(包括腾讯云 EdgeOne、阿里云 ESA、Cloudflare Workers)(顺便修复了#7758

Support deploy application to edge computing environments (including Tencent EdgeOne, Alibaba Cloud ESA, and Cloudflare Workers).

Related Links

Checklist

  • I have added changeset via pnpm run change.
  • I have updated the documentation.
  • I have added tests to cover my changes.

详细说明

支持了哪些平台

为什么没有在原有的 workerSSR 基础上做

有试图完善这个功能。但发现存在几个问题:

  • SSR功能强依赖async_hooks等nodejs专有库,并且相关API还进一步包装后对用户暴露(例如useHonoContext),没有办法移除。
  • 不同厂商 Runtime API 支持性不一样,例如stream功能,腾讯云EO的边缘函数和阿里云ESA边缘函数有一些不同。
  • bff等功能也有大量基于文件目录的逻辑,改造成本极高。
  • 该功能设计之初似乎没有预留对于不同平台的适配入口?

后重新考虑方案,决定基于node版本实现,原因如下:

  • 云函数环境广泛支持一些基础Node API,例如阿里云ESA、CF、腾讯云EO Node Functions 都直接支持async_hooks、node:stream等API,改造成本相对较低,对已有功能影响更小。
  • 方便支持更多原本基于node环境实现的功能。

第三方平台环境特点

运行环境

  • 腾讯云EO有标准Node环境,支持所有Node内置库,但不能在自己的代码中直接监听端口。
  • 阿里云ESA是worker环境,没有绝大部分Node基础库,但对process、async_hooks等少部分基础库做了兼容。
  • CF疑似高度封装的Node环境,对绝大部分Node API都做了兼容。

入口文件

  • 阿里云ESA和CF是单一入口文件形式,需要在入口文件中导出fetch函数。
  • 腾讯云EO自带一套路由系统(虽然实际研究发现底层做的很难评)。至少需要两个 entry 文件。

同时,三者都有一个共同特点:会对产物进行二次打包,生成单一文件供运行。因此,会破坏原有文件结构,即使CF对fs相关API进行了兼容,也没有办法直接使用。

没有支持腾讯云EO的Edge Functions的原因是其缺失太多Node API,比如async_hooks库。实际上我最早开始尝试支持的也是它,理论上我可以改出一个我自己可以用的版本,但工作量很大且会对原有功能造成 break change,不具备通用性,所以最终放弃。

理论上腾讯云SCF和阿里云FC更容易支持,因为它们都是未经二次打包的、完整的node运行时环境,但好像用的人太少没必要搞?

主要改动说明

依赖方式的核心改动

  • app-tools 不改变原有产物结构。将这三个平台的产物生成入口+deps.js的方式,供其特有的打包工具二次打包。其中,deps.js会扫描所有node产物,并生成一个包含文件结构+内容的结构。在后续工具中,可以通过该结构读取依赖信息。
  • server/core 新增了 edge-function 的适配器,包含原有 node 插件的实现。
  • plugin-bff server/bff-core 改动了 bff 插件,以适配上文提到的“deps.js”形式的依赖结构。

Hono API暴露

  • server/core 将 bindings 通过 loaderContext 暴露给用户。原因是其中包含了一些平台特有信息,如腾讯云EO的节点信息(其中包含了GeoIP地理信息、服务器节点等)、CF Workers的环境信息(用户需要通过它调用内置服务,如D1等)。

其他

  • prod-server app-tools 增加了相关入口
  • server/create-request 优先使用全局的fetch API
  • app-tools 增加了对各平台的入口及编译兼容配置
  • app-tools 复制 cjs 和 mjs 文件,避免被 rslib 编译一遍
  • toolkit/utils 在生成的 esm 产物中保留 import 语句,避免编译后出现 createRequire(此功能在阿里云ESA中不可用)
  • toolkit/plugin server/core 在 package.json 中增加 esm 指向,以支持在编译时对相关包进行 tree shaking(否则会因为 cjs 没法 tree shaking 引入过多包,可能出现部分包不兼容、体积过大等问题)

其他

为什么没有使用ndepe复制依赖,而是单独打包了一份modern-server

  1. 各平台对产物进行二次打包时,使用的esbuild会优先读取package.json中的esm相关字段,ndepe复制依赖的过程中可能出现缺失。
  2. 使用rspack预构建性能更好,可以更准确控制构建过程(例如tree shaking细节等),生成体积更小、性能更好的产物。

RSC 相关

暂未对 edge 环境实现 RSC 相关插件。主要原因:该功能似乎看起来还没做完。文档中也没有相关说明,无法测试。可以在后续该功能正式上线后再进行适配。

已经测试内容

在上述三个平台及node中对以下功能进行了简单测试:

  • ✅SSR
  • ✅CSR
  • ✅BFF
  • ✅Data Loader
  • ✅SSR+Stream

未测试:vercel、netlify(从改动上分析不会对原有功能造成影响,因没有账号故未做实际测试)

@changeset-bot
Copy link

changeset-bot bot commented Dec 27, 2025

🦋 Changeset detected

Latest commit: d746cd3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 113 packages
Name Type
@modern-js/runtime Minor
@modern-js/create-request Minor
@modern-js/app-tools Minor
@modern-js/prod-server Minor
@modern-js/bff-core Minor
@modern-js/plugin-bff Minor
@modern-js/plugin Minor
@modern-js/utils Minor
@modern-js/server-core Minor
@modern-js/runtime-utils Minor
@modern-js/plugin-styled-components Minor
@modern-js/plugin-i18n Minor
@integration-test/alias-set Patch
app-document Patch
async-entry-test Patch
tmp Patch
bff-api-app Patch
bff-client-app Patch
bff-indep-client-app Patch
bff-hono Patch
integration-clean-dist-path Patch
integration-compatibility Patch
integration-custom-dist-path Patch
custom-file-system-entry Patch
integration-custom-template Patch
deploy Patch
deploy-server Patch
dev-server Patch
integration-disable-html Patch
app-custom-entries Patch
app-custom-routes-runtime Patch
app-custom Patch
app-entry Patch
app-route Patch
app-entry-server Patch
i18n-app-csr-html-lang Patch
i18n-app Patch
i18n-app-ssr-html-lang Patch
i18n-app-ssr Patch
i18n-custom-i18n-wrapper Patch
i18n-mf-app-provider Patch
i18n-mf-component-provider Patch
i18n-mf-consumer Patch
i18n-routes Patch
i18n-routes-ssr Patch
@integration-test/image-component Patch
main-entry-name Patch
nonce Patch
pure-esm-project Patch
routes-match Patch
routes Patch
app-rsbuild-hooks Patch
rsc-csr-app Patch
rsc-csr-routes Patch
rsc-ssr-app Patch
rsc-ssr-routes Patch
runtime-custom-plugin Patch
runtime-custom-config-plugin Patch
select-mul-entry-test Patch
select-one-entry-test Patch
server-config Patch
server-json-script Patch
server-monitors Patch
server-prod Patch
server-routes Patch
@source-code-build/app Patch
ssg-fixtures-mega-list-routes Patch
ssg-fixtures-nested-routes Patch
ssg-fixtures-simple Patch
ssg-fixtures-web-server Patch
ssr-base-async-entry-test Patch
ssr-base-json-test Patch
ssr-base-test Patch
ssr-base-fallback-test Patch
init Patch
ssr-base-loadable Patch
ssr-partial-test Patch
ssr-script-loading Patch
ssr-streaming-inline-test Patch
ssr-streaming-test Patch
styled-components-stream Patch
styled-components-string Patch
integration-tailwindcss-v2 Patch
integration-tailwindcss-v3 Patch
integration-tailwindcss-v4 Patch
tmp-dir Patch
write-to-dist Patch
@modern-js/bundle-diff-benchmark Minor
@modern-js/plugin-ssg Minor
@modern-js/image Minor
@modern-js/plugin-polyfill Minor
entries-app-builder Patch
@modern-js/builder Minor
@modern-js/plugin-data-loader Minor
@modern-js/render Minor
@modern-js/server Minor
@modern-js/server-utils Minor
@modern-js/i18n-utils Minor
@modern-js/babel-compiler Minor
@scripts/release-node Patch
@modern-js/server-runtime Minor
@modern-js/create Minor
@modern-js/main-doc Minor
@modern-js/tsconfig Minor
@modern-js/babel-preset Minor
@modern-js/flight-server-transform-plugin Minor
@modern-js/babel-plugin-module-resolver Minor
@modern-js/bff-runtime Minor
@modern-js/sandpack-react Minor
@modern-js/types Minor
@modern-js/rslib Minor
@scripts/prebundle Patch
@scripts/rstest-config Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify
Copy link

netlify bot commented Dec 27, 2025

Deploy Preview for modernjs-v3 ready!

Name Link
🔨 Latest commit d746cd3
🔍 Latest deploy log https://app.netlify.com/projects/modernjs-v3/deploys/695e57449cb3d40008e2c7b0
😎 Deploy Preview https://deploy-preview-8097--modernjs-v3.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@sylingd sylingd changed the title feat: deploy on edge computing feat: deploy on edge environment Dec 27, 2025
@yimingjfe
Copy link
Member

yimingjfe commented Dec 31, 2025

感谢对 Modern.js 的支持和贡献!🙏

由于这次改动涉及范围较大,为了确保发布后的质量和用户体验,有几个问题想和你一起探讨:

  1. 文档和功能
  • 我尝试照着当前的文档部署了一下 cloudflare 全栈项目,但没有成功。可能需要再完善下文档细节,确保用户能顺利跑通流程。

  • 该 PR 合入后, tests/integration/pure-esm-project 用例执行 deploy 命令失败了,辛苦排查下原因。

  • PR 中将 vercel-handler.cjs 重命名为了 .js,但 packages/solutions/app-tools/src/plugins/deploy/platforms/vercel.ts 似乎只更新了部分代码。目前看 Vercel 和 Netlify 的部署可能会因此受到影响,需要修复一下。

  • tests/integration/deploy-server 这里是对部署产物做了验证的。考虑到这次新增了三个平台,是否可以给它们也补充对应的测试用例(或者添加新的集测项目),以保证后续功能的稳定性?

  1. 设计探讨
  • 环境变量定义: 原设计中 MODERN_SSR_ENV 是用来标识 SSR 是否运行在符合 WinterCG 标准的“Worker 环境”下的(与 React API 对应)。目前的改动似乎调整了这个定义,并新增了 MODERN_SSR_NODE_STREAM。这块能分享一下你的设计思路吗?我们看是否有必要引入新变量。

  • 废弃 API: api/_app.ts 是 Modern.js 已经下线的实现。可能是仓库里的旧代码产生了误导,建议这次 PR 中不需要对这个约定做实现,避免后续的维护成本。

  1. 运行环境支持
  • 兼容性担忧: 阿里云 ESA 和 Cloudflare 属于 Worker 环境,而 server-core 还未完全剥离 Node.js 代码。目前很难保证后续的代码或功能不会影响这两个平台的部署。想确认下,你目前是有在这两个平台部署的强需求吗?

  • 文档建议: 接上一点,目前支持 Worker 环境的项目必须是 ESM 的,但 Modern.js 默认创建的是 CommonJS。我们是不是在文档里显式提示一下用户,避免踩坑。

@sylingd sylingd marked this pull request as draft January 5, 2026 10:43
@sylingd
Copy link
Contributor Author

sylingd commented Jan 5, 2026

esm 运行时已经跑通,但发现二次打包会造成多实例问题,故先标记为 draft,等我尝试一下另外的解决方式

@sylingd sylingd marked this pull request as ready for review January 6, 2026 12:19
@sylingd
Copy link
Contributor Author

sylingd commented Jan 6, 2026

@yimingjfe Hello,目前已经完成主要涉及包的ESM迁移。主要改动:

  • 主要包进行ESM迁移。因涉及包较多,所以不一一列举包名,此处主要列举迁移方式供评估影响面。
    • package.json 中指向修改。
    • 在 RslibConfig 中开启 ESM shims。
    • 加入 MODERN_LIB_FORMAT 环境变量;该变量仅用于初次编译(rslib)时 tree shaking,不用于后续的 modern.js 项目编译。
    • 将部分 require.resolve 替换为 path.join,部分替换为 tryResolve
    • solutions/app-tools/bin/modern.js 中开启 ESM 检测,对 ESM 项目优先使用 ESM 工具链
    • 迁移 runtime/render server/babel-plugin-module-resolver 包至 rslib 以保持基础一致性。
    • 对 ESM 项目,在 node 中不再加载 esbuild 相关依赖(node 直接支持)
    • toolkit/utils 包支持 ESM
      • utils/src/cli/require.ts 修改较大,请进一步 Review
      • compiled 部分包增加 ESM 产物;其中,commander、tsconfig-paths为原CJS直接导出,nanoid、js-yaml 为新构建 ESM 产物,lodash 为 lodash-es 直接导出。
      • 相应增加 prebundle 工具的 ESM 实现(但本次没有重新打包所有包,仅仅提交了上述几个包的修改)
  • 移除 API_APP_NAME 实现
  • 增加 deploy-csr deploy-server pure-esm-project 的 deploy 测试
  • 对本次支持的三个平台也进行了进一步修改
    • 现在会在内部执行两次打包,生成单一文件,避免相关平台工具链二次打包出现问题。
    • 不再使用 node:stream 进行流式渲染。

pure-esm-project 在CF/EO上的运行演示(阿里云 ESA 暂时没办法支持 BFF)

defer 会发起新的 fetch 请求,CF 开启 global_fetch_strictly_public 后可以支持,但 EO header 中传递的虚假域名并没有真正指向自身,导致请求报错domain endpoints match fail 经排查,改漏了一处data loader的逻辑导致

测试运行:

英文文档暂未更新,先等中文文档定稿


排查问题过程中发现一个潜在问题,因为暂时没触发所以也就暂时没解决,先放上来

solutions/app-tools/src/builder/shared/builderPlugins/adapterBasic.ts 中,在 applyNodeCompat 中通过 extensionAlias 定义了后缀优先级,例如 .node.js 优先于 .js。

但不确定是因为其他插件有注入配置还是因为默认配置,实际上生效的 extensionAlias 中,.js .jsx 都有问题,.mjs 正常。比如 .js 实际上是:

'.js': ['.js', '.ts', '.tsx', '.node.js', '.server.js', '.js'],

另有几个问题暂时没什么好想法:

  • 本来把render改成rslib了,但有问题,rslib 会向产物注入 webpack_require,导致 RSC 拿不到正确的依赖(迁回 modern-lib?但是看起来 modern-lib 并不打算继续维护,这么做是不是不太好?)
    • Update: 先改回 modern-lib,在 rslib 提了一个 issue 看看后续
  • 修改了 Import.lazy 的逻辑以便 tree shaking,但看起来和 rstest 的兼容性有点问题。
  • 我在 pure-esm-project 中加入了 deploy 测试,但看起来因为产物会相互影响,导致另外几个 test 失败。
    • Update: 已通过修改 distPath 解决

@yimingjfe
Copy link
Member

这次改动之前 MODERN_SSR_ENV 的含义是决定 SSR 渲染的 API 是 renderToPipeStream(基于 node stream) 还是 readerToReadableStream(基于 web stream)

如果当时设计出发点是这个,其实我觉得 SSR_ENV 这个名称也不是非常合理,可能叫 STREAM_TYPE 或者类似名字更能明显体现出其功能职责。

现在的改动使 MODERN_SSR_ENV也被构建方式决定,MODERN_SSR_ENV 甚至对 bff 的运行产生了影响

如果这个变量的含义为“目标运行环境是 worker 环境”,那我觉得对 bff 产生影响也是合理的。因为是否为 worker 环境,BFF 的逻辑也有区别。

  1. SSR ENV 代表 ssr envirmoment,标识运行时环境是 worker 还是 node.js,我没有觉得命名为 STREAM_TYPE 更好或更坏,这个比较主观
  2. 这里不是为了讨论变量问题哈

@yimingjfe
Copy link
Member

@yimingjfe Hello,目前已经完成主要涉及包的ESM迁移。主要改动:

  • 主要包进行ESM迁移。因涉及包较多,所以不一一列举包名,此处主要列举迁移方式供评估影响面。

    • package.json 中指向修改。

    • 在 RslibConfig 中开启 ESM shims。

    • 加入 MODERN_LIB_FORMAT 环境变量;该变量仅用于初次编译(rslib)时 tree shaking,不用于后续的 modern.js 项目编译。

    • 将部分 require.resolve 替换为 path.join,部分替换为 tryResolve

    • solutions/app-tools/bin/modern.js 中开启 ESM 检测,对 ESM 项目优先使用 ESM 工具链

    • 迁移 runtime/render server/babel-plugin-module-resolver 包至 rslib 以保持基础一致性。

    • 对 ESM 项目,在 node 中不再加载 esbuild 相关依赖(node 直接支持)

    • toolkit/utils 包支持 ESM

      • utils/src/cli/require.ts 修改较大,请进一步 Review
      • compiled 部分包增加 ESM 产物;其中,commander、tsconfig-paths为原CJS直接导出,nanoid、js-yaml 为新构建 ESM 产物,lodash 为 lodash-es 直接导出。
      • 相应增加 prebundle 工具的 ESM 实现(但本次没有重新打包所有包,仅仅提交了上述几个包的修改)
  • 移除 API_APP_NAME 实现

  • 增加 deploy-csr deploy-server pure-esm-project 的 deploy 测试

  • 对本次支持的三个平台也进行了进一步修改

    • 现在会在内部执行两次打包,生成单一文件,避免相关平台工具链二次打包出现问题。
    • 不再使用 node:stream 进行流式渲染。

pure-esm-project 在CF/EO上的运行演示(阿里云 ESA 暂时没办法支持 BFF)

defer 会发起新的 fetch 请求,CF 开启 global_fetch_strictly_public 后可以支持,但 EO header 中传递的虚假域名并没有真正指向自身,导致请求报错domain endpoints match fail

测试运行:

英文文档暂未更新,先等中文文档定稿

排查问题过程中发现一个潜在问题,因为暂时没触发所以也就暂时没解决,先放上来

solutions/app-tools/src/builder/shared/builderPlugins/adapterBasic.ts 中,在 applyNodeCompat 中通过 extensionAlias 定义了后缀优先级,例如 .node.js 优先于 .js。

但不确定是因为其他插件有注入配置还是因为默认配置,实际上生效的 extensionAlias 中,.js .jsx 都有问题,.mjs 正常。比如 .js 实际上是:

'.js': ['.js', '.ts', '.tsx', '.node.js', '.server.js', '.js'],

另有几个问题暂时没什么好想法:

  • 本来把render改成rslib了,但有问题,rslib 会向产物注入 webpack_require,导致 RSC 拿不到正确的依赖(迁回 modern-lib?但是看起来 modern-lib 并不打算继续维护,这么做是不是不太好?)

    • Update: 先改回 modern-lib,在 rslib 提了一个 issue 看看后续
  • 修改了 Import.lazy 的逻辑以便 tree shaking,但看起来和 rstest 的兼容性有点问题。

  • 我在 pure-esm-project 中加入了 deploy 测试,但看起来因为产物会相互影响,导致另外几个 test 失败。

    • Update: 已通过修改 distPath 解决

感谢贡献,改动确实比较大,我们讨论看看 @zllkjc

@sylingd
Copy link
Contributor Author

sylingd commented Jan 7, 2026

SSR ENV 代表 ssr envirmoment,标识运行时环境是 worker 还是 node.js,这里不是为了讨论变量问题哈

我认为,讨论的关键应该是定义“worker环境的特征”。看看这种“基于产物二次打包产生单一文件、没有真实文件系统”是不是worker环境的普遍特征。

如果是,那么SSR ENV=worker时,处理文件树结构也合理?

web worker 理论上可以支持 import 方式的“多文件”,但本质上仍然没有目录树。从我目前接触到的各个云计算平台看,似乎没有真正支持多文件环境的 worker。

又或者说,你认为更好的方式是给这种环境用一个新变量,而不与 worker 强绑定?比如 BUNDLE_MODE=single ?

@zllkjc
Copy link
Member

zllkjc commented Jan 15, 2026

Hello 同学你好,这个 PR 是否可以拆分一下。我理解这里涉及几个内容:

  1. 不同平台的核心部署打包逻辑,这里支持最小 SSR Demo 甚至 CSR + Server 运行部署即可
  2. 最小 Demo 要改造的 ESM 包
  3. 其余功能部署的支持,可能分多个 PR 来进行

现在上下文和改动点有点太多了,如果出现问题我们不太好追因和回滚~

再次感谢对 Modern.js 的支持!

@sylingd
Copy link
Contributor Author

sylingd commented Jan 15, 2026

  1. 不同平台的核心部署打包逻辑,这里支持最小 SSR Demo 甚至 CSR + Server 运行部署即可
  2. 最小 Demo 要改造的 ESM 包

这两个可能没法拆,因为哪怕是最小SSR Demo,本身也依赖好几个包的ESM改造。

或者,先Review ESM改造,再对部署打包逻辑单独提一个?

但本身两者是依赖比较紧密的,如果ESM改造出问题需要回滚,这几个平台部署也会受影响

  1. 其余功能部署的支持

硬要拆的话,应该只有一个BFF功能能拆出来?但BFF功能本身涉及的包也不多,我判断下来也就 cli/plugin-bffserver/bff-core是属于“其余功能部署”。

@zllkjc
Copy link
Member

zllkjc commented Jan 16, 2026

@sylingd 可以的,先把 ESM 改造单独拆一个

@zllkjc
Copy link
Member

zllkjc commented Feb 12, 2026

这个 PR 要先 close 吗

@sylingd sylingd closed this Feb 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants