cal.diy Embed Snippet 解析用一段 Vanilla JS 拉起 Cal.com 嵌入预约全流程【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy导读packages/embeds/embed-snippet是 cal.diyCal.com 开源调度基础设施中体积最小、却承担临门一脚职责的模块它是一段不依赖任何框架的纯 JavaScript 启动代码负责在宿主页面中按需加载calcom/embed-core从而把 Cal Link预约链接以内嵌inline、弹窗modal或悬浮按钮floating button的形式渲染到任意网页。读完本文你将掌握该 Snippet 的构建产物与两种注入方式、其先入队后执行的懒加载队列机制、命名空间namespace多实例原理以及构建时通过环境变量改写embed.js地址的发布手法并能在自己的站点上直接落地使用。一、定位整个 Embed 体系的第一块积木在 cal.diy 的嵌入方案中依赖关系是单向的calcom/embed-snippet纯 Vanilla JS唯一职责是获取并执行calcom/embed-core从而在页面上展示 Cal Link 的嵌入效果calcom/embed-core真正的内核负责创建 iframe、管理指令队列、处理命名空间与跨域消息postMessage、渲染 inline/modal/floating 三种形态calcom/embed-reactReact 封装底层同样依赖 embed-snippet见 embed-snippet/package.json 中唯一的运行时依赖calcom/embed-core: workspace:*。也就是说无论你是用原生script还是 React 组件接入最终都要通过这段 Snippet 完成引导bootstrap动作。README 中给出的模块职责定义Vanilla JS embed snippet that is responsible to fetch calcom/embed-core and thus show Cal Link as an embed on a page精确对应了src/index.ts中的实现——它不做渲染只负责把内核拉进来并把指令送达。二、工作原理为什么页面加载很快——队列式懒加载2.1 内核是异步注入的Snippet 的核心逻辑全部集中在 src/index.ts 的EmbedSnippet()中。源码注释点明了设计初衷As we want to keep control on the size of this snippet but we want some portion of it to be still readable.即在控制体积与保持可读之间取平衡所以作者刻意把这段代码写得短小而直白。其工作流程可以拆解为三步首次调用时注入脚本检查window.Cal.loaded标志若内核尚未加载则执行d.head.appendChild(d.createElement(script)).src A; // A 即 embed.js 地址 cal.loaded true;loaded标志保证同一页面不会重复下载 embed-core这也正是embed-core/index.html中doubleInstallSnippet...测试场景反复执行 snippet 仍能保持幂等的依据。把后续指令压入队列内核脚本是异步加载的在它执行完成前所有用户调用Cal(init, ...)、Cal(inline, {...})等都不会丢失而是通过a.q.push(ar)追加到全局队列window.Cal.q中。内核接管后消费队列embed-core 加载完成后会读取并清空该队列见 embed-core/src/embed.ts 的processQueue()实现随后将queue.push重定向为立即执行保证此后指令实时生效。这就是先入队、后执行的经典懒加载引导模式无论用户何时点击、调用多少次 API指令都不会因为网络延迟而丢失。2.2 全局 API 形态加载后页面全局会出现window.Cal函数类型定义见 embed-core/src/embed.ts 中的GlobalCal/GlobalCalWithoutNsinterface GlobalCalWithoutNs { (methodName: string, ...args: unknown[]): void; loaded?: boolean; // 内核是否已加载避免重复下载 q: Queue; // 内核加载前的指令队列 ns: Recordstring, GlobalCalWithoutNs; // 已注册的命名空间 instance?: Cal; // 具体实例 }Snippet 源码中的let cal C.Cal; if (!cal.loaded) {...}正是围绕这三个关键字段做初始化。三、开发与构建一条命令产出三种产物3.1 构建命令与产物README 的 Development 章节指出yarn build会生成dist/snippet.es.js如果要在 React 嵌入中测试必须先构建本包以便calcom/embed-react拿到最新版 Snippet。构建流程定义在 embed-snippet/package.jsonbuild: npx rimraf dist vite build tsc --emitDeclarationOnly --declarationDir dist由 vite.config.js 的库模式配置可知Vite 会以src/index.ts为入口按 format 产出两类 JS 产物产物文件格式对应 package.json 字段典型用途dist/snippet.es.jsESMmodulescript typemodule src...或 React 包引用dist/snippet.umd.jsUMDmain传统script标签直接引用dist/index.d.ts类型声明typesTypeScript 项目中的类型提示这也是 README 中which can be used asscript typemodule src...的落地出处。3.2 发布前的环境注入prepack脚本package.json揭示了一个关键发布细节withEmbedPublishEnv: NEXT_PUBLIC_EMBED_LIB_URLhttps://app.cal.com/embed/embed.js NEXT_PUBLIC_WEBAPP_URLhttps://app.cal.com yarn, prepack: yarn lint --filtercalcom/embed-snippet yarn withEmbedPublishEnv build打包发布时会强制注入线上环境变量把 Snippet 默认加载的内核地址改写为https://app.cal.com/embed/embed.js。对应源码中的读取逻辑src/index.tsconst WEBAPP_URL import.meta.env.EMBED_PUBLIC_WEBAPP_URL || https://${import.meta.env.EMBED_PUBLIC_VERCEL_URL}; const EMBED_LIB_URL import.meta.env.EMBED_PUBLIC_EMBED_LIB_URL || ${WEBAPP_URL}/embed/embed.js;可见环境变量的优先级为显式指定EMBED_PUBLIC_EMBED_LIB_URLWEBAPP_URL推导。源码第 72 行也留下了对使用者的直接建议Replace it withhttps://cal.com/embed.jsor the URL where you have embed.js installed——即自托管部署时应把内核地址指向自己的部署实例。四、两种接入方式模块引入 or 复制即用4.1 方式一ES Module 引入构建完成后以标准模块方式引入README 中script typemodule的用法script typemodule import EmbedSnippet from /dist/snippet.es.js; EmbedSnippet(); window.Cal(init, { theme: dark }); window.Cal(inline, { elementOrSelector: #my-cal, calLink: pro/30min, }); /scriptEmbedSnippet(url EMBED_LIB_URL)接受可选参数url允许你自定义 embed-core 的加载地址src/index.ts它同时导出了EmbedSnippetStringEmbedSnippet.toString()即源码字符串本身供需要把代码内联进宿主页面的场景使用。4.2 方式二复制代码直接内联README 提到的第二种用法——You can also copy the appropriate portion of the code and install it directly asscriptCODE_SUGGESTED_TO_BE_COPIED/script——适合不想引入任何构建工具的静态站点。核心步骤打开构建产物dist/snippet.es.js复制其中/*! Copying ends here. */标记内的那段引导代码即 IIFE 部分将其直接写入宿主页面script (function (C, A, L) { /* ... 复制的 snippet 代码 ... */ })(window, https://你的域名/embed/embed.js, init); /script之后即可通过Cal(init, {...})、Cal(inline, {...})、Cal(modal, {...})等方式控制嵌入。值得注意由于 Snippet 本身刻意保持短小可读它才能被安全地复制粘贴进第三方页面、甚至 WordPress 插件中见下文第五节的同步维护清单。五、命名空间Namespace同页多实例的关键机制5.1 Snippet 侧的分流逻辑同一个页面可能同时存在多个互不干扰的嵌入例如一个团队页、一个个人页、一个暗色主题弹窗。Snippet 用命名空间实现隔离分流逻辑位于 src/index.ts当第一个参数为init源码常量L且第二个参数是字符串命名空间名时会为该命名空间创建一个独立的api函数并挂到cal.ns[namespace]同时向默认队列投递一条initNamespace指令注释强调even after re-execution of the snippet, the namespace is not overriddencal.ns[namespace] cal.ns[namespace] || api确保重复执行 snippet 也不会覆盖已注册的命名空间非 init 指令如inline、modal、ui则直接入队。5.2 embed-core 侧的实例化对应地embed-core 在 embed.ts 中实现了init与initNamespace// 默认命名空间为 DEFAULT_NAMESPACE见 embed.ts#L1552 init(namespaceOrConfig) { // 若 init 指令属于其他命名空间则忽略 if (initForNamespace ! this.cal.namespace) return; } initNamespace(namespace: string) { // 创建实例即自动开始消费该命名空间队列 globalCal.ns[namespace].instance globalCal.ns[namespace].instance || new Cal(namespace, globalCal.ns[namespace].q); }initNamespace指令允许默认队列代为实例化非默认命名空间队列而 embed.ts 的初始化循环则兼容旧版 Snippet——对不使用initNamespace指令的旧引导代码也会在 embed-core 加载后统一补齐实例化注释明确这是幂等操作。一个命名空间对应一个 iframe 与一套独立配置embed-core/index.html中Two different namespace with two different init config的测试场景正是对该能力的验证。5.3 无 JavaScript 的降级路径data-* 属性若不想写任何 JSembed-core 还提供声明式接入在任意元素上设置data-cal-link、data-cal-namespace、data-cal-config属性点击时由全局点击监听embed.ts自动触发modal。例如embed-core/index.html中的用法button contenteditable="false">【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考