-
Notifications
You must be signed in to change notification settings - Fork 11
feat(): support isolated templates #4785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough引入模板隔离机制:增加每根隔离根(Symbol)对应的模板注册与清理;在非页面根创建时注册隔离模板与函数,并在卸载时清理。调整获取模板标签名逻辑以支持隔离上下文。放宽函数注册参数为可选。导出与类签名相应更新,并补充相关测试。 Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes ✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Pre-merge checks❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for isolated templates in the runtime system, allowing templates to be scoped to specific contexts without affecting the global template registry.
- Creates isolated template registry functionality for scoped template management
- Updates template resolution to check isolated registries first before falling back to global templates
- Adds comprehensive test coverage for isolated template functionality
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime/src/internal/compute/IsolatedFunctions.ts | Updates type imports and function signature to accept undefined functions |
| packages/runtime/src/internal/Renderer.ts | Updates template resolution to pass full runtime context |
| packages/runtime/src/internal/IsolatedTemplates.ts | New file implementing isolated template registry management |
| packages/runtime/src/internal/CustomTemplates/utils.ts | Adds isolated template resolution logic and updates function signature |
| packages/runtime/src/internal/CustomTemplates/utils.spec.ts | Updates test to use new function signature format |
| packages/runtime/src/internal/CustomTemplates/expandCustomTemplate.ts | Adds logic to retrieve templates from isolated registries |
| packages/runtime/src/index.ts | Changes export to named export for customTemplates |
| packages/runtime/src/createRoot.ts | Integrates isolated template registration and cleanup |
| packages/runtime/src/createRoot.spec.ts | Adds test cases for isolated template functionality |
| packages/runtime/src/StoryboardFunctionRegistry.ts | Updates function signature to accept undefined functions |
| packages/runtime/src/CustomTemplates.ts | Refactors CustomTemplateRegistry to support isolated mode |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## v3 #4785 +/- ##
==========================================
- Coverage 95.21% 95.20% -0.02%
==========================================
Files 210 211 +1
Lines 9176 9198 +22
Branches 1769 1774 +5
==========================================
+ Hits 8737 8757 +20
Misses 324 324
- Partials 115 117 +2
🚀 New features to boost your workflow:
|
next-core
|
||||||||||||||||||||||||||||
| Project |
next-core
|
| Branch Review |
steve/v3-isolated-templates
|
| Run status |
|
| Run duration | 00m 26s |
| Commit |
|
| Committer | Shenwei Wang |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
17
|
| View all changes introduced in this branch ↗︎ | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (11)
packages/runtime/src/CustomTemplates.ts (2)
21-28: 为新增的隔离构造参数补充文档,澄清实例用途
#isolated与可选构造参数用于标记“仅在隔离场景使用的注册表”。建议在类与构造器处加入注释,说明:
- 该实例不会操作
customElements已定义元素;- 与全局
customTemplates的职责边界;- 典型使用方式(例如由 IsolatedTemplates 工厂创建)。
可选补丁(仅注释):
-export class CustomTemplateRegistry { +// 用途: +// - 非隔离:作为全局模板注册表,定义并缓存自定义元素的元数据; +// - 隔离:作为每 root 的临时模板仓库,仅缓存元数据,不触碰 customElements。 +export class CustomTemplateRegistry { readonly #registry = new Map<string, CustomTemplate>(); readonly #isolated: boolean | undefined; - constructor(isolated?: boolean) { + /** 当 isolated 为 true 时,该实例仅用于隔离 root 的模板缓存。 */ + constructor(isolated?: boolean) { this.#isolated = isolated; }
241-245: clearIsolatedRegistry 仅在 isolated=true 生效:建议注明返回值或行为说明当前方法在非隔离实例上调用无效果,易引发误用。建议:
- 增加返回布尔值表示是否执行了清理;或
- 至少补充注释说明在非隔离实例上是 no-op。
可选补丁(保留 API,不改变签名,补注释):
- clearIsolatedRegistry() { + /** 仅当实例为隔离模式时清空内部缓存;非隔离实例调用无任何效果。 */ + clearIsolatedRegistry() { if (this.#isolated) { this.#registry.clear(); } }packages/runtime/src/internal/CustomTemplates/utils.spec.ts (1)
76-81: 测试用例已匹配新签名,建议补一个“无 app.id”分支当前仅覆盖
ctx.app.id存在的路径。建议再加一例ctx.app未设置时的行为,确保默认解析不回退到意外 tag 名。packages/runtime/src/createRoot.spec.ts (2)
353-384: 新增“片段作用域下的模板”用例很到位覆盖了隔离模板的注册、渲染与卸载路径。可考虑断言卸载后再次渲染需重新提供模板,以验证隔离仓库确实被清空。
386-400: “未知模板”用例贴近预期验证了在未注册模板时渲染为空壳元素的行为。可额外断言无报错日志或 loader 行为(如 unknownBricks 策略)以固定期望。
packages/runtime/src/createRoot.ts (1)
35-38: 引入隔离模板注册入口与 Map 很好,但请补充并发与生命周期说明建议在
IsolatedTemplates模块(或此处)记录:
- 同一
isolatedRoot多次 render 的合并/覆盖语义;- 是否支持
templates: undefined触发清空;- 与函数隔离注册的顺序依赖(此处为“先模板后函数”)。
请确认
registerIsolatedTemplates的实现对undefined入参的语义(清空/忽略/保留)。packages/runtime/src/internal/CustomTemplates/expandCustomTemplate.ts (1)
56-61: 避免使用多级非空断言链,建议显式校验并抛出更友好的错误
isolatedTemplateRegistryMap.get(... )!.get(tplTagName)!在竞态或误用下会直接抛 TypeError。建议在缺失时抛出包含tplTagName与isolatedRoot的明确错误,便于排障。可选补丁:
- const { bricks, proxy, state, contracts } = hostBrick.runtimeContext - .isolatedRoot - ? isolatedTemplateRegistryMap - .get(hostBrick.runtimeContext.isolatedRoot)! - .get(tplTagName)! - : customTemplates.get(tplTagName)!; + let tpl = hostBrick.runtimeContext.isolatedRoot + ? isolatedTemplateRegistryMap.get(hostBrick.runtimeContext.isolatedRoot)?.get(tplTagName) + : customTemplates.get(tplTagName); + if (!tpl) { + throw new Error( + `Template "${tplTagName}" not found for current ${hostBrick.runtimeContext.isolatedRoot ? "isolated root" : "global"} registry` + ); + } + const { bricks, proxy, state, contracts } = tpl;请确认
getTagNameOfCustomTemplate的返回只会发生在模板已注册时,二者保持强一致,避免运行时落入上述异常分支。packages/runtime/src/internal/compute/IsolatedFunctions.ts (1)
12-20: 先注册再写入全局 Map,避免注册失败留下脏条目。
当前先 set 后 register;若 registerStoryboardFunctions 抛错,会在 isolatedFunctionRegistry 中遗留无效项。建议调整顺序。应用此最小化调整:
const { storyboardFunctions, registerStoryboardFunctions } = StoryboardFunctionRegistryFactory({ isolatedRoot }); - isolatedFunctionRegistry.set(isolatedRoot, storyboardFunctions); - registerStoryboardFunctions(functions); + registerStoryboardFunctions(functions); + isolatedFunctionRegistry.set(isolatedRoot, storyboardFunctions);packages/runtime/src/internal/CustomTemplates/utils.ts (1)
63-74: 微调可读性:参数解构,减少链式访问。
不改语义,仅提升易读性。应用此重构:
-export function getTagNameOfCustomTemplate( - brick: string, - runtimeContext: Pick<RuntimeContext, "app" | "isolatedRoot"> -): false | string { - if (runtimeContext.isolatedRoot) { - const registry = isolatedTemplateRegistryMap.get( - runtimeContext.isolatedRoot - ); +export function getTagNameOfCustomTemplate( + brick: string, + { app, isolatedRoot }: Pick<RuntimeContext, "app" | "isolatedRoot"> +): false | string { + if (isolatedRoot) { + const registry = isolatedTemplateRegistryMap.get(isolatedRoot); if (registry?.get(brick)) { return brick; } return false; } // When a template is registered by an app, it's namespace maybe missed. - if (!brick.includes(".") && brick.startsWith("tpl-") && runtimeContext.app) { - const tagName = `${runtimeContext.app.id}.${brick}`; + if (!brick.includes(".") && brick.startsWith("tpl-") && app) { + const tagName = `${app.id}.${brick}`; if (customTemplates.get(tagName)) { return tagName; } }packages/runtime/src/internal/IsolatedTemplates.ts (2)
9-12: 显式标注返回类型以增强可读性。
加上: void更直观,防止将来无意返回值。
9-24: 为 registerIsolatedTemplates 增加可选 reset 参数(默认 true)当前实现每次都会调用 registry.clearIsolatedRegistry(),会在增量/分批注册场景移除此前已注册模板;保留默认 reset=true 以兼容现状,并增加可选项以支持增量注册。
建议改造(保持默认 reset=true):
-export function registerIsolatedTemplates( - isolatedRoot: symbol, - templates: CustomTemplate[] | undefined -) { +export function registerIsolatedTemplates( + isolatedRoot: symbol, + templates: CustomTemplate[] | undefined, + options?: { reset?: boolean } +): void { let registry = isolatedTemplateRegistryMap.get(isolatedRoot); if (!registry) { registry = new CustomTemplateRegistry(true); isolatedTemplateRegistryMap.set(isolatedRoot, registry); } - registry.clearIsolatedRegistry(); + if (options?.reset ?? true) { + registry.clearIsolatedRegistry(); + } if (Array.isArray(templates)) { for (const tpl of templates) { registry.define(tpl.name, tpl); } } }已验证:卸载时会删除映射(packages/runtime/src/createRoot.ts:280),registerIsolatedTemplates 的调用点为 packages/runtime/src/createRoot.ts:204。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge base: Disabled due to data retention organization setting
📒 Files selected for processing (11)
packages/runtime/src/CustomTemplates.ts(2 hunks)packages/runtime/src/StoryboardFunctionRegistry.ts(1 hunks)packages/runtime/src/createRoot.spec.ts(1 hunks)packages/runtime/src/createRoot.ts(3 hunks)packages/runtime/src/index.ts(1 hunks)packages/runtime/src/internal/CustomTemplates/expandCustomTemplate.ts(2 hunks)packages/runtime/src/internal/CustomTemplates/utils.spec.ts(2 hunks)packages/runtime/src/internal/CustomTemplates/utils.ts(2 hunks)packages/runtime/src/internal/IsolatedTemplates.ts(1 hunks)packages/runtime/src/internal/Renderer.ts(1 hunks)packages/runtime/src/internal/compute/IsolatedFunctions.ts(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: build (20.x)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (11)
packages/runtime/src/internal/Renderer.ts (1)
706-706: 传递 runtimeContext 给 getTagNameOfCustomTemplate 的改动合理与隔离模板解析一致性良好,调用点处理
string | false的语义不变,后续扩展空间更大。请确认
getTagNameOfCustomTemplate的实现与签名已全量迁移,且无遗留的按appId字符串调用。packages/runtime/src/internal/CustomTemplates/utils.spec.ts (1)
9-9: 仅类型导入 RuntimeContext 的微调 OK减少运行时代码体积,符合 TS 最佳实践。
packages/runtime/src/createRoot.ts (2)
204-206: 在 fragment 下“先注册模板再注册函数”的顺序合理与扩展流程一致(模板展开依赖函数时可通过后续求值)。若存在模板代理属性依赖
FN.*的边界情况,建议补一个用例。可添加 e2e/spec:模板属性内调用
FN,验证首次渲染与重渲染均正常。
280-281: 卸载时清理模板隔离仓库 👍已与
isolatedFunctionRegistry.delete保持对称。若存在并发渲染(同 root 多次 render 进行中)建议在 delete 前确保无悬挂异步任务引用旧模板。如需稳妥,可在
RendererContext.dispatchOnUnmount()后追加一次“仓库为空”的断言于测试中。packages/runtime/src/internal/CustomTemplates/expandCustomTemplate.ts (1)
24-24: 引入 isolatedTemplateRegistryMap 的方向正确与隔离解析路径打通。见下条关于非空断言的健壮性建议。
packages/runtime/src/StoryboardFunctionRegistry.ts (1)
116-147: 为 registerStoryboardFunctions(undefined) 明确语义并确认 currentApp 复用是否为设计行为说明(已验证):传入 undefined 只会执行 registeredFunctions.clear(),只有在同时传入 app 时才会更新 currentApp。仓内存在多处调用依赖此行为,需确认这是刻意设计并在签名处记录语义或改为同时重置 currentApp。
调用点(供定位):
- packages/runtime/src/StoryboardFunctionRegistry.spec.ts:162 — registerStoryboardFunctions(undefined)
- packages/runtime/src/internal/Router.ts:494 — registerStoryboardFunctions(storyboard.meta?.functions, currentApp)
- packages/runtime/src/internal/compute/IsolatedFunctions.ts:19 — registerStoryboardFunctions(functions)
- packages/runtime/src/internal/compute/WidgetFunctions.ts:27 — registerStoryboardFunctions(functions)
- packages/runtime/src/createRoot.ts:202 — registerStoryboardFunctions(functions, app)
建议(二选其一):
- 在函数签名处添加 JSDoc,明确传入 undefined 为“仅清空已注册函数,未提供 app 时保留 currentApp”的语义(示例差异如下);或
- 若意图是清空时同时重置上下文,则在清空时显式将 currentApp 置为 undefined。
建议差异(可直接应用):
function registerStoryboardFunctions( - functions: StoryboardFunction[] | undefined, + functions: StoryboardFunction[] | undefined, app?: PartialMicroApp ): void { + /** + * 传入 `undefined` 表示“清空已注册函数”。若未提供 `app`,会沿用上一轮的 `currentApp`。 + * 该行为用于隔离根/片段重渲染时的轻量清空,若希望同时重置上下文请改为显式重置 currentApp。 + */ if (app) { currentApp = app; }packages/runtime/src/index.ts (1)
4-4: 从通配导出改为具名导出:确认无外部依赖export { customTemplates } from "./CustomTemplates.js";自动化扫描在当前执行环境未能访问/搜索到仓库文件,无法验证是否有外部或工作区包依赖 runtime 的其它导出(例如 CustomTemplateRegistry)。在本地仓库根目录运行下列命令核查是否存在消费者依赖被移除的导出:
rg -n --hidden -S "from .*\/runtime" -g '!**/dist/**' -g '!**/build/**' -C2 || true rg -n --hidden -S "from .*CustomTemplates" -g '!**/dist/**' -g '!**/build/**' -C2 || true rg -n --hidden -S "CustomTemplateRegistry" -g '!**/dist/**' -g '!**/build/**' -C2 || true rg -n --hidden -S "from .*@next-core\/runtime" -g '!**/dist/**' -g '!**/build/**' -C2 || true若发现依赖,恢复需要的 re-export(例如 export * from "./CustomTemplates.js" 或补充显式命名导出)。
packages/runtime/src/internal/compute/IsolatedFunctions.ts (2)
1-5: type-only 导入很好,能减少运行时包体与副作用。
这部分变更 LGTM。
12-20: 已确认:卸载会清理 registry,registerStoryboardFunctions 的新签名已落地
- 卸载清理:packages/runtime/src/createRoot.ts 的 unmount 分支调用了 isolatedFunctionRegistry.delete(isolatedRoot)(约第278–281行)。
- 签名与调用:packages/runtime/src/StoryboardFunctionRegistry.ts 中 registerStoryboardFunctions 定义为 (functions: StoryboardFunction[] | undefined, app?: PartialMicroApp);调用点包括 packages/runtime/src/internal/compute/IsolatedFunctions.ts(registerStoryboardFunctions(functions))和 packages/runtime/src/createRoot.ts(registerStoryboardFunctions(functions, app)),与签名兼容。
packages/runtime/src/internal/CustomTemplates/utils.ts (1)
49-62: 在隔离上下文中禁用全局/应用级回退:请确认这是预期。
当前一旦存在isolatedRoot,仅在每根 registry 中命中才返回,否则直接false,不会回落到customTemplates或 app 命名空间。若注册与解析存在时序竞态(解析早于注册),将导致误判。请确认:
- createRoot/renderer 生命周期能保证“先注册后解析”;
- 不需要在隔离模式下提供受控回退(例如仅限 app 内回退)。
packages/runtime/src/internal/IsolatedTemplates.ts (1)
4-7: 按 root 维护独立 CustomTemplateRegistry 的设计合理。
Map<symbol, Registry> 贴合需求,构造时传入true启用隔离模式也清晰。
依赖检查
组件之间的依赖声明,是微服务组件架构下的重要信息,请确保其正确性。
请勾选以下两组选项其中之一:
或者:
提交信息检查
Git 提交信息将决定包的版本发布及自动生成的 CHANGELOG,请检查工作内容与提交信息是否相符,并在以下每组选项中都依次确认。
破坏性变更:
feat作为提交类型。BREAKING CHANGE: 你的变更说明。新特性:
feat作为提交类型。问题修复:
fix作为提交类型。杂项工作:
即所有对下游使用者无任何影响、且没有必要显示在 CHANGELOG 中的改动,例如修改注释、测试用例、开发文档等:
chore,docs,test等作为提交类型。Summary by CodeRabbit