前端Web框架UI组件【免费下载链接】ember.jsEmber.js - A JavaScript framework for creating ambitious web applications项目地址https://gitcode.com/gh_mirrors/em/ember.js点击查看免费下载Glimmer 为宿主应用如 Ember.js提供了两套嵌入 API——AOT 与 JIT用于把模板编译产物运送到浏览器并执行。本文聚焦 JITJust-In-Time嵌入 API它允许应用携带紧凑的 Wire Format 模板表示并在运行时按需编译是 Ember 3.7 起 Ember 自身采用的方案。读完本文你将掌握 JIT 嵌入的取舍、Wire Format 的生成链路、按需编译的底层机制以及一套从编译到渲染的完整可运行示例并能与 AOT 模式进行准确对比。嵌入 API 全景AOT 与 JIT 两条路径在深入 JIT 之前先明确它在嵌入体系中的位置。Glimmer 的嵌入 API 提供两条路径参见 01-introduction.mdAOT Embedding API预先编译见 91-aot.mdJIT Embedding API运行时按需编译即本文主题。两条路径的共同目标是一致的减少浏览器需要解析和执行的 JavaScript 体量。模板不再以 JavaScript 源码形式传输而是以数据结构 二进制字节码的形式跨越网络——数据结构通过 Wire Format 传输字节码则存储在类型化数组typed array中执行。正如 01-introduction.md 所述无论应用使用多少模板浏览器都不再需要解析和编译额外的 JavaScript 代码。这意味着模板编译开销从浏览器端转移到了构建/服务端而浏览器只需解释紧凑的二进制表示。JIT 嵌入 API 的取舍为何 Ember 3.7 起选择它92-jit.md 明确指出该版本的 API 自 Ember 3.7 起被 Ember 采用并给出了明确的优缺点清单。优点Pros模板体积紧凑应用可以随包发布相对紧凑的模板表示——即 Wire Format。这对于带宽极度受限的环境弱网、移动端、体积敏感的 CDN 场景非常有利按需编译应用可以在运行时、遇到组件时才编译模板无需在启动阶段编译全部模板。缺点Cons仍须传输 Wire Format浏览器端必须随应用包携带 Wire Format 数据运行时仍需编译Wire Format 必须在运行时浏览器中完成编译这部分 CPU 开销被转移到了客户端。也就是说JIT 的核心交易是用客户端运行时编译换取更小的模板传输体积 更快的冷启动。这在模板数量庞大、但单次会话未必全部用到的应用中收益明显——首次加载只编译实际渲染路径上的模板。Wire Format模板的紧凑中间表示Wire Format 是 JIT 方案的基石——它是模板源码经过预编译后得到的序列化、JSON 可表达的结构化表示与具体渲染无关只描述模板长什么样。在本仓库中生成 Wire Format 的入口位于 compiler.tsprecompileJSON(source, options)返回[block, usedLocals]其中block即SerializedTemplateBlockWire Format 块precompile(source, options)在其基础上进一步包装出包含id、block、moduleName、isStrictMode的序列化模板对象。从编译管线看见 2-encoding 说明预编译器依次经过**归一化AST → HIR与编码MIR → Wire Format**等阶段最终把模板编码为 wire format。JIT 模式下这份数据可以被序列化进 JSON、随应用包传输然后在客户端被编译成字节码程序。说明仓库中的 93-wire-format.md 当前为空白占位文档原 92-jit.md 内部链接即指向它原相对路径./04-wire-format.md在仓库中不存在已转换为上述根路径Wire Format 的编码实现细节可继续阅读 compiler.ts 与 2-encoding。按需编译的底层机制JIT 与 AOT 的关键差异在于组件何时被编译。参见 02-minimum-environment.md 的 AOT Mode 一节AOT 模式所有组件在任何组件执行之前全部编译完成编译器预先知道所有已编译组件的偏移量并把这些偏移量直接写进 opcodeJIT 模式组件在运行时遇到时才编译——编译器会在组件或块block调用之前插入额外的 opcode用于确保代码在真正执行到该处时被编译。这条机制可以从仓库测试基建中得到印证在 JitRenderDelegate 中JIT 渲染代理标记为static readonly isEager false非预编译、按需编译其compileTemplate在模板被渲染时才执行preprocess(template)并调用.compile(this.context)同时JitDelegateContext 通过artifacts()创建一份全程序共享的编译产物heap constants再包装成EvaluationContextImpl供编译与执行共用。这意味着 JIT 模式下的运行时程序是一个持续生长的对象每次遇到新模板就追加编译。这一点在 program.ts 的ProgramHeap实现中也有体现——堆基于Int32Array分页PAGE_SIZE增长注释明确写着application 运行期间堆只会增长。编译阶段实操从源码到字节码程序以下示例继承自 02-minimum-environment.md该文以 AOT 讲解机器机制JIT 的差异点我们随后单独标注。先看编译阶段的完整流程。第 1 步创建编译上下文import { Context } from glimmer/opcode-compiler; let context Context();编译上下文用于编译整个程序中的所有组件当组件编译完成后上下文会被序列化为可执行的字节码程序。第 2 步把模板源码变成可编译的组件import { Component } from glimmer/opcode-compiler; import { precompile } from glimmer/compiler; // Component() 期望一个已序列化/预编译的模板 let source {{#let hello world as |hello world|}} p{{hello}} {{world}}/p {{/let}} ; let component Compilable(source); function Compilable(source: string): CompilableProgram { return Component(precompile(source)); }这里的关键是precompile(source)它先把源码变成 Wire Format再由Component(...)包装成CompilableProgram。示例模板是宿主无关的不依赖任何 Ember 具体行为只定义了两个变量hello与world非常适合用来观察嵌入 API 的基础机制。第 3 步编译组件拿到 handlelet handle component.compile(context);handle是一个32 位整数指向一个已编译组件后续运行时用它来调用渲染该组件。第 4 步把上下文序列化为程序import { artifacts } from glimmer/program; let program artifacts(context);program即RuntimeArtifacts参见 helpers.ts包含两个成员heap整个已编译程序的序列化二进制表示Int32Array字节码堆constants常量池存放字符串等被 opcode 引用的小型可序列化值。正常生产流程中这个program会被序列化进文件、随网络送达浏览器在本示例中我们直接在 Node 中执行它省去传输环节。运行阶段实操从字节码到真实 DOM第 1 步准备 DOMSimpleDOMGlimmer 内部只依赖 DOM 接口的一个小子集称为SimpleDOMcreateElement、setAttribute以及少量更生僻的方法如setAttributeNS、insertAdjacentHTML。与 Virtual DOM 不同这个子集与真实 DOM 接口完全一致因此既能直接在真实 DOM 上运行又为没有真实 DOM 的环境提供了明确的目标规范。Ember 项目维护了simple-domnpm 包族并用于 FastBootEmber 的服务端渲染方案——与 JSDOM 不同simple-dom只实现 DOM 数据结构中足以完整创建和更新 DOM 树的最小部分因而实现紧凑高效。在 Node 中我们用simple-dom创建文档import createHTMLDocument from simple-dom/document; let document createHTMLDocument();第 2 步构建运行时环境接下来把编译产物水合hydrate为RuntimeContextVM 靠它执行已编译组件import { AotRuntime } from glimmer/runtime; let runtime AotRuntime(document, payload);注示例中的payload指代上文program序列化后的传输载荷。第 3 步准备渲染目标 Cursorlet element document.createElement(main); let cursor { element, nextSibling: null };Glimmer 把组件渲染进一个Cursor——它是一个元素 一个nextSibling的组合。这个设计让你能把组件渲染进既有元素的中间位置本示例只需要追加进新建的main元素因此nextSibling: null。Cursor的接口与insertBefore的签名一致而insertBefore正是 Glimmer 用来把元素插入 DOM 的 SimpleDOM 原语。第 4 步执行渲染import { renderAot } from glimmer/runtime; let iterator renderAot(runtime, handle, cursor);第 5 步调度——分块渲染生产实现中为避免渲染过程中卡顿浏览器jank应该把渲染分块进行。因此renderAot返回一个 JavaScriptIterator供你把渲染拆成若干片。本示例直接同步跑完let result iterator.sync();sync()除了耗尽迭代器还会在需要时对相关对象触发生命周期钩子本例没有生命周期钩子所以只完成渲染。若把这段示例放到真实 Ember 应用中sync()的对应物是 Glimmer 的调度器/渲染事务机制。完整示例一个可运行的端到端代码把上述步骤合并示例原文来自 02-minimum-environment.md 的 All Together Now 一节import { Component, Context } from glimmer/opcode-compiler; import { artifacts } from glimmer/program; import { precompile } from glimmer/compiler; import createHTMLDocument from simple-dom/document; import { AotRuntime, renderAot } from glimmer/runtime; let source {{#let hello world as |hello world|}}p{{hello}} {{world}}/p{{/let}}; let context Context(); let component Compilable(source); let handle component.compile(context); let program artifacts(context); let document createHTMLDocument(); let runtime AotRuntime(document, payload); let element document.createElement(main); let cursor { element, nextSibling: null }; let iterator renderAot(runtime, handle, cursor); let result iterator.sync(); function Compilable(source: string): CompilableProgram { return Component(precompile(source)); }最后把元素序列化成字符串以便在控制台观察输出。simple-dom/serializer接受一个实现 SimpleDOM 接口的对象并转成字符串import Serializer from simple-dom/serializer; import voidMap from simple-dom/void-map; let serialized new Serializer(voidMap).serialize(element); function serialize(element: SimpleElement): string { return new Serializer(voidMap).serialize(element); } console.log(serialize(element)); // mainphello world/p/main从 AOT 适配到 JIT 的关键差异02-minimum-environment.md 明确说明嵌入指南第一部分使用 AOT 模式先编译全部组件再执行后续指南描述如何把示例适配为 JIT 模式而 92-jit.md 正是该适配方案的起点。基于源码与文档两者的核心差异可归纳为维度AOT 模式JIT 模式编译时机所有组件执行前全部编译组件首次遇到时按需编译偏移量处理编译器预先知道所有组件偏移直接写入 opcode编译器在组件/块调用前插入额外 opcode 以确保运行时编译传输内容序列化后的二进制字节码程序heap constants紧凑的 Wire Format客户端开销只需解释字节码需在运行时编译 Wire Format适用场景模板集稳定、可预先全量编译带宽受限、模板众多且按需加载在 JIT 适配中先序列化 program 再传输的步骤被替换为在运行时共享同一份artifacts()heap constants每遇到新模板就compile进这份共享程序。仓库中 JitRenderDelegate 正是这一形态的现成实现sharedArtifacts artifacts()一次创建、EvaluationContextImpl同时服务编译与执行、compileTemplate按需编译——它同时展示了 JIT 模式对 helper/modifier 的注册机制fn、hash、array、get、concat、on等内建项的注册见同文件构造函数。API 演进的源码佐证需要提醒的是文档中的Context/AotRuntime/renderAot属于 Ember 3.7 时代的嵌入 API 命名。在当前仓库的 glimmer/runtime 中公开导出已演进为renderMain、renderComponent、renderSync等更细粒度的渲染入口VM 执行模型TemplateIteratorImpl、sync()与调度事务inTransaction依然延续了文档描述的设计见 render.ts。阅读历史示例时建议对照 glimmer-workspace/integration-tests/lib/modes/jit 目录下的 JIT 模式实现获取与当前仓库一致的调用形态。进一步阅读嵌入 API 总览AOT 与 JIT 的共同设计目标最小环境与上下文说明AOT/JIT 模式对比与完整示例出处AOT 嵌入 API与 JIT 对照的另一条路径Wire Format 编码实现MIR 到 Wire Format 的编码管线程序编译上下文编译上下文在编译管线中的角色赞分享前端Web框架UI组件【免费下载链接】ember.jsEmber.js - A JavaScript framework for creating ambitious web applications项目地址https://gitcode.com/gh_mirrors/em/ember.js点击查看免费下载相关推荐Ember.js 嵌入 APIThe Embedding API完全指南从 AOT/JIT 编译到运行时渲染Ember.js 嵌入 APIThe Embedding API完全指南从 AOT/JIT 编译到运行时渲染 本文是 Ember.js 仓库内部文档 in前端Web框架UI组件Ember.js 内部架构入门Glimmer 编译管线与运行时渲染原理Ember.js 内部架构入门Glimmer 编译管线与运行时渲染原理 Glimmer 是 Ember.js 底层的渲染管线它把模板视为构建和更新 DOM前端Web框架UI组件Ember.js Glimmer 嵌入 API 实战在自定义宿主中集成多个全局组件Ember.js Glimmer 嵌入 API 实战在自定义宿主中集成多个全局组件 导读 本文基于 Ember.js 仓库的 internal docs/gu前端Web框架UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考