Builder.io 原生 JavaScript 接入指南:用 HTML API 在纯 JS 站点中渲染视觉化页面
【免费下载链接】builderVisual Development for React, Vue, Svelte, Qwik, and more项目地址: https://gitcode.com/GitHub_Trending/bu/builder
本文以 examples/plain-js 示例为骨架,讲解如何在零框架的 vanilla JavaScript(原生 JS)站点中接入 Builder.io 视觉化开发能力:本地通过 Parcel 启动、在页面路由中回退调用 Builder 的 HTML 渲染 API,将可视化编辑器产出的页面实时渲染进现有网页。读完本文,你将掌握 Builder.io HTML API 的请求/响应结构、客户端路由与静态页面回退的组合策略,并能把该模式迁移到自己的纯 JS 项目甚至服务端渲染场景中。
示例速览:一个不需要框架的 Builder.io 渲染示例
examples/plain-js是一个刻意保持“零依赖运行时”的示例:不引入 React、Vue 等任何前端框架,只用一个 HTML 页面、一个 JS 文件和一个样式文件,就完成了 Builder.io 页面内容的拉取与渲染。从仓库结构看,示例的核心文件非常精简:
- README.md —— 示例说明与快速开始指引;
- index.html —— 页面骨架,包含导航、挂载点
#app与脚本引用; - src/index.js —— 全部业务逻辑:路由判断、API 请求、HTML 注入与客户端跳转;
- src/index.css —— 示例的基础样式;
- package.json —— 基于 Parcel 的启动与构建脚本。
这一结构本身就说明了 Builder.io 的接入成本极低:开发者只需要维护一个“内容挂载点”,其余内容全部可由可视化编辑器产出。
本地运行与快速开始
README 提供了两条上手路径:一条是直接在线上沙箱中打开(README 中给出了 Codesandbox 链接,可一键预览);另一条是本地运行,核心命令如下:
git clone https://github.com/BuilderIO/builder.git cd examples/plain-js npm install npm start在当前仓库中,等价于直接进入 examples/plain-js 目录执行依赖安装与启动。从 package.json 可以看到示例的构建工具与脚本配置:
{ "name": "@builder.io/example-plain-js", "main": "index.html", "scripts": { "start": "parcel index.html --open", "build": "parcel build index.html" }, "dependencies": {}, "devDependencies": { "@babel/core": "7.2.0", "parcel-bundler": "^1.6.1" } }两点值得注意:
- 运行时依赖
dependencies为空——示例本身不引入任何第三方运行库,Parcel 仅作为开发期打包器(parcel-bundler),npm start会用 Parcel 以index.html为入口起本地开发服务器并自动打开浏览器; main指向index.html,整个应用入口就是那个静态 HTML 文件。
工作原理拆解:静态壳 + 动态内容回退
示例的核心设计思路可以用一句话概括:能用代码写死的页面就写死,其余 URL 交给 Builder.io 动态渲染。这个策略由 src/index.js 中的updatePage()函数实现。
页面骨架 index.html
index.html 定义了一个非常朴素的结构:顶部 header 里有站点 Logo(MY SITE)和四个导航链接Home、About、Page 1、Page 2,中间是内容挂载点<main id="app"></main>,底部是空 footer,最后通过<script src="src/index.js"></script>引入逻辑。
<header> <div class="logo">MY SITE</div> <div class="links"> <a class="link" href="/">Home</a> <a class="link" href="/about">About</a> <a class="link" href="/page-1">Page 1</a> <a class="link" href="/page-2">Page 2</a> </div> </header> <main id="app"></main> <footer></footer>导航链接都带上了class="link",这是为后续客户端路由准备的钩子。
启动与路由判断
index.js 的启动流程如下:
import './index.css'; let builderApiKey = 'bb209db71e62412dbe0114bdae18fd15'; updatePage();脚本首先引入样式、声明 API Key(示例内嵌的是公开演示 Key,实际项目请替换为你自己空间下的 Key),随后立即执行updatePage()。该函数依据location.pathname分三种情况处理:
function updatePage() { if (location.pathname === '/') { setHtml('<h2>Welcome to the home page!</h2><p>This page comes from our code.</p>'); } else if (location.pathname === '/about') { setHtml('<h2>Welcome to the about page!</h2><p>This page comes from our code too.</p>'); } else { // 其余路径:向 Builder.io 请求页面 HTML } }即/与/about两个页面由项目代码直接输出(这体现了“部分页面由开发者控制”的混合模式),而/page-1、/page-2等未被代码覆盖的路径,则走 Builder.io 的 HTML 渲染接口。
核心:通过 Builder HTML API 渲染可视化页面
示例中最关键的一段逻辑是对https://cdn.builder.io/api/v1/html/page接口的调用:
fetch( `https://cdn.builder.io/api/v1/html/page?url=${encodeURI( location.href )}&apiKey=${builderApiKey}` ) .then(res => res.json()) .then(data => { if (data && data.data && data.data.html) { setHtml('<h2>This page is from Builder!</h2>' + data.data.html); } else { setHtml('No page found for this URL'); } });这段代码可以拆解为四个要点:
- 接口语义:
/api/v1/html/page是 Builder.io 面向“HTML 片段”场景的端点,返回的是可直接注入页面的 HTML 字符串,而不是结构化 JSON 数据模型——这正是它适合纯 JS 站点的原因:拿到 HTML 后直接赋值给innerHTML即可完成渲染,无需解析组件树。 - URL 参数:
url参数传入当前页面完整地址(location.href),Builder 服务端据此做内容匹配;apiKey用于标识你的 Builder 空间。注意location.href必须经过encodeURI编码,以保证 URL 中可能存在的特殊字符(如查询串、中文路径)在传输中不被破坏。仓库中 next-js-builder-site 的 curl 示例也展示了同源用法:curl https://builder.io/api/v1/html/${name}?apiKey=${key}&url=/some-url。 - 响应结构:示例按
data.data.html逐层判空取值——最外层data是 HTTP 响应 JSON,data.data是 Builder 返回的内容对象,data.data.html才是渲染用的 HTML 字符串。若没有匹配到内容,则回退输出No page found for this URL。 - 渲染注入:
setHtml将拼接后的 HTML 写入#app挂载点:
function setHtml(html) { document.querySelector('#app').innerHTML = html; }与官方 SDK 的对应关系(源码佐证)
从 SDK 源码看,这一“按 URL 取内容”的模型正是 Builder.io 各框架 SDK 的通用行为。以 fetch-builder-props.ts 为例,官方 SDK 的fetchBuilderProps同样以apiKey+url(或path)为输入,从path或url.pathname中提取urlPath作为内容定向依据,默认模型为page:
export const fetchBuilderProps = async ( _args: GetBuilderPropsOptions ): Promise<ContentVariantsPrps> => { const urlPath = _args.path || _args.url?.pathname || _args.userAttributes?.urlPath; const getContentArgs: GetContentOptions = { ..._args, apiKey: _args.apiKey, model: _args.model || 'page', userAttributes: { ..._args.userAttributes, ...(urlPath ? { urlPath } : {}) }, ... }; return { apiKey: getContentArgs.apiKey, model: getContentArgs.model, content: await fetchOneEntry(getContentArgs), }; };可以推断:纯 JS 示例中的url=${location.href}参数,在 SDK 侧对应的就是userAttributes.urlPath的 URL 匹配逻辑。另外,generate-content-url.test.ts 的快照 显示当前 SDK 默认走https://cdn.builder.io/api/v3/content/page这类 v3 结构化内容接口(含limit、noTraverse、includeRefs、query等更细粒度参数),而纯 JS 示例使用的 v1/html/page是更轻量的“拿来即用 HTML”形态——两者同属cdn.builder.io的内容服务,区别在于返回的粒度与适用场景:无框架站点优先 HTML,框架应用优先结构化数据。
客户端路由:不刷新页面完成跳转
示例的另一亮点是手写的客户端路由。在文件末尾,所有带.link类的链接都被拦截:
for (let link of document.querySelectorAll('.link')) { link.addEventListener('click', a => { a.preventDefault(); history.pushState({}, '', a.target.getAttribute('href')); updatePage(); }); }点击导航时先preventDefault()阻止浏览器默认整页跳转,再用history.pushState把地址栏 URL 更新为目标路径(注意不会触发真实导航),最后重新调用updatePage()按新路径渲染内容。这样:
/与/about走本地代码输出;- 其余路径向 Builder 发起新的 HTML 请求并渲染结果;
- 地址栏、历史栈与页面内容保持一致,体验接近 SPA。
这是纯 JS 实现“伪 SPA”的标准写法,也是理解 Builder.io 页面在无框架环境落地方式的关键一环。
样式与构建:保持最小化
src/index.css 只提供了基础排版:sans-serif 字体、header 用 flex 布局、.links居中、.logo加宽字距、.link之间留白等,总代码量仅 20 余行。这印证了示例的定位——专注于演示 Builder.io 内容渲染链路,而非展示样式能力。构建侧则完全交给 Parcel:parcel index.html会自动处理 ES Module 语法(import './index.css')、内联脚本引用与开发服务器,开发者无需配置任何 bundler 选项。
服务端对照:同一 API 的 SSR 形态
同一套 HTML API 也可以放在服务端使用。仓库中的 node-express/index.js 就是同款思路的服务端实现:Express 先对/、/about输出静态模板,最后用app.get('*', ...)兜底所有未匹配路由,在服务端用 axios 请求同一接口:
app.get('*', async (req, res) => { let page = await axios .get( `https://cdn.builder.io/api/v1/html/page?url=${encodeURI(req.url)}&apiKey=${builderApiKey}` ) .catch(handleError); if (page && page.data) { res.send(template({ body: '<h2>This page is from Builder!</h2>' + page.data.data.html })); } else { // 404 兜底 } });两者对比可以得出一个清晰的结论:纯 JS 示例是“客户端版”的内容回退,node-express 示例是“服务端版”的内容回退。客户端版本请求参数来自location.href、渲染目标为 DOM;服务端版本请求参数来自req.url、渲染目标为响应体 HTML。这也意味着该接入模式与渲染位置(浏览器/服务器)解耦,可以按需选择。
实践建议与注意事项
结合示例代码与仓库内其他实现,接入时需注意以下几点:
- API Key 安全:示例中的
bb209db71e62412dbe0114bdae18fd15是公开演示 Key。前端直连场景下 Key 天然暴露,请使用限制为“仅读取”的公开 Key;若需要保护更敏感的操作,应参考 node-express 示例把请求收敛到服务端。 - URL 匹配规则:接口按
url(或 SDK 中的urlPath)做内容定向,页面在 Builder 后台配置的路径需要与站点实际路径一致(如/page-1、/page-2),否则会落入No page found for this URL分支。 - 响应判空:务必像示例那样逐层校验
data && data.data && data.data.html,因为未发布内容或路径不匹配时接口可能返回空结构。 - 刷新兼容:示例用
history.pushState实现客户端路由,但直接刷新/page-1这类 URL 时,本地静态服务器需要能回退到index.html(Parcel 开发服务器默认支持),生产环境则要考虑托管平台的 SPA fallback 配置。 - 升级路径:若后续需要个性化(A/B 测试)、结构化数据或更细粒度的查询控制,可平滑迁移到 packages/sdks 提供的
fetchBuilderProps/Content等能力,渲染模型仍是“按 URL 取内容”这一套。
总而言之,examples/plain-js用不到 40 行核心代码演示了 Builder.io 在无框架环境中的完整接入链路:静态 HTML 壳承载结构、api/v1/html/page承载动态内容、history.pushState承载路由。这套模式既可以原样嵌入任何传统网页项目,也是理解 Builder.io 各框架 SDK 底层“按 URL 渲染内容”思想的最佳起点。
【免费下载链接】builderVisual Development for React, Vue, Svelte, Qwik, and more项目地址: https://gitcode.com/GitHub_Trending/bu/builder
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考