Skip to content

Latest commit

 

History

History
182 lines (131 loc) · 7.21 KB

File metadata and controls

182 lines (131 loc) · 7.21 KB

分包与 release assets

第三层第 3 篇。为什么 reset 后 prelude 会被自动重放,见 threading-model.md。

Vue pages 项目的推荐发布路径是 pages 分包:

fjs build --pages --release

这条命令会把 Vue/fjs 运行时、应用入口和页面 chunk 拆开,编译成 QuickJS .fjsbundle,并同步到生成的 Flutter 宿主 assets。

pages 分包

src/pages/**/*.vue 会被转换成路由表。每个页面对应一个 chunk:

src/pages/index.vue        -> /              -> pages/index.fjsbundle
src/pages/about.vue        -> /about         -> pages/about.fjsbundle
src/pages/user/[id].vue    -> /user/:id      -> pages/user-id.fjsbundle

构建产物:

产物 内容
dist/app/shared.js / shared.fjsbundle Vue、@ufjs/runtime、Shell、公共组件
dist/app/bundle.js / bundle.fjsbundle 应用入口
dist/app/pages/<chunk>.js / .fjsbundle 单个页面代码

哪些模块进入 shared 由 CLI 自动计算:入口可达模块、Shell、公共组件,以及被多个 页面共同引用的模块会进入 shared;页面文件自身始终作为独立 chunk。

chunk 什么时候执行。 第一个页面停稳后,JS 路由按路由表顺序在空闲时逐个请求宿主 执行尚未加载的 chunk(fjs.nav.preload,specs/143);空闲由 Dart 判定(没有手指按着、 没有转场、没有帧在排队)。用户比队列先点进某页时,那一页照旧在打开时加载,同一个 chunk 不会执行两次。日志里预执行的那一行带 (preload):

[nav] chunk vant-form 62113 bytes: fetch 1ms, eval 14ms (preload)

关掉用 createFjsApp({ preload: false }),细节见 routing.md。

没有 src/pages 的项目不会分包。 shared prelude 的定义是「Vue + fjs + 应用 自己的模块」,所以一个不用 Vue 的纯 JS app(examples/hello-js)如果照样分包, 拿到的是一个 ~450 KB、里面装着整个 Vue 而它一次都不会调用的 chunk,每次启动都要 求值一遍。fjs run 总是以 --pages 启动 dev server,因此这个判断落在 fjs dev / fjs build 里:项目没有路由时 --pages 不生效,退回单包,并在 banner 上说明(no src/pages — single bundle)。

Release assets

fjs build --pages --release

release 会自动启用 --bytecode,然后创建或复用 .fjs/flutter,并同步 release assets。JS 默认压缩(和 vite build 一致,fjs dev 不压缩);如果还要 gzip release assets,需要显式加 --gz:

fjs build --pages --release --gz

同步后的文件:

.fjs/flutter/assets/fjs/
  manifest.json
  shared.fjsbundle
  bundle.fjsbundle
  pages/
    index.fjsbundle

生成的 Flutter 宿主已经声明了这些 assets。启动时:

  • 有 FJS_DEV:连接 fjs dev --pages
  • 没有 FJS_DEV:加载 assets/fjs/manifest.json 和 .fjsbundle;如果 manifest 指向 .fjsbundle.gz,会自动解压后执行 QuickJS bytecode

dist 目录里的 .fjsbundle 保持未压缩,便于直接用 fjsrun 调试;只有 --release --gz 同步到 Flutter assets 的 release 文件会 gzip 压缩。

所以开发和发布用同一份 Flutter 宿主。

APK

fjs build --pages --release --apk

--apk 会在 assets 同步完成后执行 flutter build apk。Flutter 参数放在 -- 后面:

fjs build --pages --release --apk -- --debug
fjs build --pages --release --apk -- --target-platform android-arm64

--apk 必须配合 --release 或 --profile;单独执行会报错 (fjs build --profile --apk 打的是量性能用的 profile 包)。

release 的 shared.js 只导出页面用到的名字(specs/169)

页面 chunk 通过 globalThis.__FJS_SHARED[specifier] 取共享模块。dev 下 shared.js 用 import * as 整命名空间挂上去——热更新时重新 eval 的页面可能用到之前没人用过的名字。 --release 构建没有热更新,所有 chunk 同次产出,于是构建先以 ESM 预构建一遍入口与全部页面 (共享说明符 external),收集每个共享说明符被 import 的名字,shared 入口改为只具名导入这些名字: runtime-core 里页面从不用的部分(VDOM 渲染引擎之于 enableVapor 应用、Suspense / KeepAlive 之于 大多数应用)就能被摇掉。

  • 某个说明符被任一 chunk 以 import * as、默认导入、export * from 或动态 import() 使用 → 该说明符回退整命名空间。
  • 函数导出作为普通属性共享(页面每次调用不走 getter),其余值保留 getter(ESM 活绑定语义)。
  • 页面运行期以字符串动态取共享模块的名字(vue[name])在 release 下取不到——只认静态 import。 例外(specs/200):runtime 约定给 DOM 垫片动态读取的名字始终保留(SHARED_DYNAMIC_NAMES,目前是 fjs/vue 的 styleEngine / onGlobalPointerDown / measureTextBlock,demo 的 vant dom-env.ts 经 __FJS_SHARED['fjs/vue'] 读它们)。漏掉时 release / profile 烘焙构建下 TextEllipsis 量不出行高、 NumberKeyboard 点外部关不掉,而 dev 的整命名空间下一切正常——只在烘焙构建里出现,排查时别被 debug 误导。

实测(2026-10-01):hello-fjs shared.js 436.3 → 375.9 KB,demo 809.4 → 652.9 KB, enableVapor 的 vapor-app 336.4 → 207.3 KB。

dev 模式

fjs dev --pages

dev server 提供源码形式的 split bundle:

  • /manifest.json
  • /shared.js
  • /bundle.js
  • /pages/<chunk>.js
  • /ws

fjs run android / fjs run ios 会自动启动 fjs dev --pages,并把地址通过 FJS_DEV 注入 flutter run。connectDev() 看到 split manifest 后会自动加载 shared,页面 chunk 和 release 一样由 JS 路由在空闲时预执行(以前 dev 有一套单独的 按 16 ms 间隔全量预加载,specs/143 起统一成一条)。

热更新

dev 只有两档(spec 095,units 已去掉):

  • 只改某一个页面,以及只被这一页 import 的模块:reload pages:<chunk>, 重 eval 该页并重挂,VM 和其它页面不动。
  • 其它改动(shell、入口、被多页或入口引用的组件和 ts、插件、路由、配置): 整包 reload,VM 重建,回到首屏。

共享 app 模块打进 shared.js,页面 chunk 用 __FJS_SHARED 取同一份实例。 release 与字节码产物没有热更新。

排查清单

页面 chunk 加载失败

确认页面文件在 src/pages/**/*.vue 下,并且 <route> 的 platforms 没有排除 当前目标。

__FJS_SHARED is not defined

通常是 shared prelude 没有先于业务包执行。使用 fjs build --pages --release 生成的宿主会自动处理顺序;自定义宿主需要先加载 manifest 中的 shared,再加载 bundle。如果 release manifest 指向 .fjsbundle.gz,FjsEngine.runBundle() 会自动解压。

改了 @ufjs/runtime 或升级了 vue,运行时行为异常

shared、bundle 和 pages 之间是 JS API 级耦合,所有 .fjsbundle 必须一起重新 构建。

相关