- 前端
- UI组件
【免费下载链接】fast
The adaptive interface system for modern web experiences.
本篇指南基于本仓库
@microsoft/fast-element的 API 文档(fast-element.compilationresult.md)及其 1.x 版本对应的源码实现展开,系统讲解模板编译产物CompilationResult接口的四个核心字段、它们与Compiler、ViewTemplate、HTMLView的协作关系,以及从「HTML 模板字符串 → 编译 → 视图实例化」的完整链路。读完本篇,你将能理解 FAST Element 模板编译中间产物的数据结构设计,并能自行读懂或调试涉及Compiler.compile()、createView()的底层代码。
CompilationResult是 FAST Element 模板编译管线中连接「模板定义」与「视图实例」的中间产物。html标签函数把模板字面量预处理成ViewTemplate,Compiler.compile()再把ViewTemplate编译成CompilationResult,最终由createView()克隆出可绑定数据源的HTMLView。理解这个接口,是深入 FAST Element 渲染原理的关键一步。
一、接口概览与状态说明
文档中的CompilationResult接口定义如下:
export interface CompilationResult该接口被标注为BETA(预览)状态,文档明确提示:"This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment."即该接口面向开发者开放预览、可能随反馈调整,不推荐在生产环境直接依赖。
接口拥有 4 个属性(全部处于 BETA 状态):
| 属性 | 类型 | 说明 |
|---|---|---|
fragment | DocumentFragment | 一个可克隆(cloneable)的DocumentFragment,代表编译后的 HTML |
hostBehaviorFactories | NodeBehaviorFactory[] | 应应用到宿主元素(模板渲染到的目标元素)上的行为工厂 |
targetOffset | number | 应用于 BehaviorFactory target 索引的索引偏移量,用于将工厂与目标节点匹配 |
viewBehaviorFactories | NodeBehaviorFactory[] | 应应用到模板自身 HTML 上的行为工厂 |
这份 1.x API 文档与仓库中当前 2.x/3.x 源码存在演进差异:现行源码中,编译结果接口为 HTMLTemplateCompilationResult,其形态被收敛为createView(hostBindingTarget?)与readonly factories: CompiledViewBehaviorFactory[]两个成员;CompilationResult的四个字段对应了旧版编译管线的内部切分方式。下面的讲解以文档所述 1.x 接口为主干,并在必要时对照现行源码说明其演进,帮助读者准确理解两种形态的对应关系。
二、fragment:编译后的可克隆文档片段
fragment是编译结果中最核心的数据:它是由Compiler.compile()生成的一段DocumentFragment,承载模板的静态 HTML 结构,同时移除了所有指令占位符(placeholder)。
在源码中,Compiler.compile()通过document.adoptNode(template.content)取得模板内容片段(见 compiler.ts),再逐节点遍历编译。关键点包括:
- 原地修改警告:
Compiler.compile()的注释明确指出,传入的模板会被原地修改且无法二次编译(见 compiler.ts);若要保留原始模板,应先克隆再传入。 - 稳定首节点:若片段首个节点是指令 marker(注释节点),或片段仅含单一节点且存在指令工厂,编译器会插入一个空的注释节点作为稳定首节点,确保
when、repeat等指令在 marker 前插入节点时视图的firstChild保持稳定(见 compiler.ts)。 - 可克隆性:
CompilationResult的fragment被设计为可克隆,目的是让同一编译结果可以反复cloneNode(true)生成多个视图实例,做到「一次编译、多次实例化」。
现行源码中,CompilationContext.freeze()返回的结果持有同一fragment,createView() 通过this.fragment.cloneNode(true)克隆它,并把克隆体赋给targets.r(root),这正是文档中 "cloneable DocumentFragment" 的直接落地。
三、viewBehaviorFactories:作用于模板 HTML 的行为工厂
viewBehaviorFactories保存所有需要绑定到模板自身 DOM 节点上的行为工厂。这些工厂源自html标签函数预处理阶段登记的指令,例如:
- 绑定指令(
HTMLBindingDirective):${x => x.name}这类插值表达式,编译时在 ViewTemplate.create 中被包装为 one-way 或 one-time 绑定; - 结构指令:
when、repeat、children、ref、slotted等指令的工厂,均在编译时通过addFactory()登记进工厂数组(见 compiler.ts); - 文本内容:文本节点的插值在
compileContent()中被解析成多段(静态字符串 + 指令),指令部分登记工厂,静态部分直接写入文本节点(见 compiler.ts)。
工厂类型ViewBehaviorFactory的核心定义在 html-directive.ts:它要求实现id(工厂唯一标识)、targetNodeId(目标 DOM 节点结构化 ID)、targetTagName、policy与createBehavior()方法。编译完成后,CompiledViewBehaviorFactory 是Required<ViewBehaviorFactory>,即编译后这些字段全部被填充完毕。
工厂如何与目标节点匹配:targetId层级 ID 机制
编译器不再使用 TreeWalker,而是为每个节点生成层级 ID:根节点为r,其第 2 个子节点为r.1,该子节点的第 1 个子节点为r.1.0(targetIdFrom(),见 compiler.ts)。这些 ID 被用作 targets 对象原型上的属性名,通过addTargetDescriptor()注册惰性 getter:targets["r.1.0"]会先解析targets["r.1"],再取childNodes[0],结果缓存于_r.1.0字段(见 compiler.ts)。工厂的targetNodeId正是用于在 targets 上定位绑定节点,这就是文档中viewBehaviorFactories与目标节点建立对应关系的底层机制。
四、hostBehaviorFactories:作用于宿主元素的行为工厂
hostBehaviorFactories与viewBehaviorFactories相对:它保存需要绑定到宿主元素(即模板渲染到的自定义元素本身,而不是模板内部节点)的行为工厂。
编译时,Compiler.compile() 以compileAttributes(context, "", template, "h", 0, true)的方式单独编译<template>元素自身的属性,其中宿主节点 ID 为h,includeBasicValues = true表示连普通静态属性值也会被登记为一次性绑定工厂(见 compileAttributes)。
宿主行为在视图创建时绑定到宿主元素:createView(hostBindingTarget?)中,targets.h = hostBindingTarget ?? warningHost(见 compiler.ts)。当未传入宿主目标时,会使用一个Proxy包装的warningHost:任何属性访问/赋值都会触发tryWarn()警告——对非fast-前缀元素的宿主绑定,FAST.warn(Message.hostBindingWithoutHost)会给出提示(见 compiler.ts)。这解释了宿主绑定为何要求元素名以fast-开头。
五、targetOffset:稳定首节点引入的索引偏移
targetOffset是文档中较难直观理解的一个字段:它是应用于 BehaviorFactory target 索引的偏移量,用于在工厂与目标节点匹配时修正索引。
其存在原因记录在 Compiler.compile() 的注释中:当片段首节点是 marker 时(when、repeat等指令可能在 marker 前插入节点,导致首节点不稳定),编译器会在最前面插入一个空注释节点以稳定首节点;但插入该节点会改变 TreeWalker(旧版实现)遍历结果的偏移,因此需要用targetOffset补偿——凡是因插入稳定节点而偏移的工厂索引,在匹配目标时统一加上该偏移量。
从 2.x 迁移文档(fast-element-2.md)可以看到该字段所属体系的演进:旧版HTMLDirective依赖targetIndex: number定位目标,新版本改为targetId: string(结构化节点 ID)+BehaviorTargets实例索引;compileTemplate()返回类型也变为带不同属性的HTMLTemplateCompilationResult。也就是说,targetOffset属于旧版「数值索引 + 偏移修正」定位方案的一部分,现行版本已由层级 ID 方案取代,这解释了为何现行接口不再暴露该字段。
六、从模板到视图的完整生命周期
将CompilationResult的四个字段串联起来,可以得到 FAST Element 模板渲染的完整生命周期:
- 预处理:
html\...`标签函数([template.ts](https://link.gitcode.com/i/adb3c6b89ddb17284b6a17a8ff5884ac))调用ViewTemplate.create(),把插值函数包装成HTMLBindingDirective,为每个指令分配唯一 ID,并在 HTML 字符串中插入占位 marker(fast- {0}fast- `)。 - 编译:首次调用
view.render()/view.create()时触发ViewTemplate.compile()(template.ts),内部调用Compiler.compile()生成CompilationResult——fragment为编译后的 DOM,viewBehaviorFactories与hostBehaviorFactories分别保存两类工厂,targetOffset记录索引修正量。 - 实例化:
createView()克隆fragment生成视图专属 DOM,构造 targets 对象(原型含惰性 getter),一次性触发全部节点解析,返回 HTMLView。 - 绑定:
view.bind(source)首次调用时遍历factories,逐个createBehavior()并bind()(注册事件监听、创建表达式观察者、写入初始 DOM 值);后续换源绑定则直接重新绑定既有 behaviors(见 view.ts)。 - 生命周期管理:
unbind()执行unbindables回调;dispose()移除节点序列并解绑,一次性处理连续区间可用disposeContiguousBatch()(view.ts)。
编译结果只在首次需要时生成并被缓存(ViewTemplate.result字段),因此整个生命周期中同一模板只编译一次,重复渲染复用编译产物——这正是fragment可克隆设计的价值所在。
七、与现行源码的对应关系速查
| 1.x 文档字段 | 现行源码对应物 | 说明 |
|---|---|---|
fragment | CompilationContext.fragment+createView()中的克隆 | compiler.ts |
viewBehaviorFactories | factories: CompiledViewBehaviorFactory[] | compiler.ts |
hostBehaviorFactories | 以"h"为节点 ID 编译的宿主属性工厂 | compiler.ts |
targetOffset | 由稳定首节点 +targetId层级 ID 机制取代 | compiler.ts、fast-element-2.md |
| 编译结果入口 | HTMLTemplateCompilationResult(createView+factories) | template.ts |
此外,Compiler还暴露了setDefaultStrategy()(可替换默认编译策略)与aggregate()(把字符串与指令数组聚合成单个指令)两个 API(compiler.ts),它们共同构成可扩展的编译管线。若需进一步阅读本仓库的相关源码,可参考 编译器实现、模板与 html 标签、指令与工厂类型 以及 视图实现。
- 前端
- UI组件
【免费下载链接】fast
The adaptive interface system for modern web experiences.
相关推荐
@microsoft/fast-element 之 Behavior.unbind() 方法深度解析:视图解绑生命周期与源码实现
@microsoft/fast element 之 Behavior.unbind 方法深度解析:视图解绑生命周期与源码实现 本篇指南以 @microsoft/
前端UI组件Angular-Strap编译原理:深入理解AngularJS指令生命周期
Angular Strap编译原理:深入理解AngularJS指令生命周期 Angular Strap作为AngularJS与Bootstrap集成的桥梁,其核
前端UI组件redux-form 字段值生命周期完全指南:format、parse 与 normalize 的源码级解析
redux form 字段值生命周期完全指南:format、parse 与 normalize 的源码级解析 导读 在 redux form 中,字段值并非"输
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考