ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

React 同构直出项目接入 VasSonic:基于 Next.js / Redux / Koa2 的 SSR 页面秒开实战指南

React 同构直出项目接入 VasSonic:基于 Next.js / Redux / Koa2 的 SSR 页面秒开实战指南 移动开发前端后端【免费下载链接】VasSonicVasSonic is a lightweight and high-performance Hybrid framework developed by tencent VAS team, which is intended to speed up the first screen of websites working on Android and iOS platform.项目地址https://gitcode.com/gh_mirrors/va/VasSonic点击查看免费下载导读本文以 VasSonic 仓库中的 sonic-react/README.md 为主线完整讲解如何在一个基于React 同构直出SSR技术栈Next.js Redux Koa2的 Web 页面中接入 VasSonic 混合开发框架。你将掌握三件事如何在服务端为直出 HTML 添加模板/数据注释标签、如何使用sonic_differ模块拦截并差异化处理 HTML 响应、以及如何在前端componentDidMount阶段通过 JS 接口与移动端客户端交互并按 Sonic 状态码刷新页面。文末结合仓库内server.js、demo.js、diff.js等源码给出可复现的接入步骤与原理印证。一、示例项目概览一个开箱即用的 React Sonic Demosonic-react是 VasSonic 仓库中专门用于演示 React 同构直出场景下 Sonic 接入的示例工程。它本身是一个基于Next.js 同构直出的拼图小游戏页面页面数据流经 Redux 管理服务端由 Koa2 承载。Sonic 在该场景下需要解决的核心问题是直出 HTML 的模板与数据难以分离——因为 SSR 输出的整段 HTML 都带有动态数据若不区分模板与数据客户端就无法实现模板缓存 数据局部刷新。从 sonic-react/package.json 可以看到示例工程的核心依赖{ name: custom-server-koa, dependencies: { cheerio: ^1.0.0-rc.2, koa: ^2.0.1, koa-router: ^7.1.0, next: latest, next-redux-wrapper: ^1.3.5, react: ^16.0.0, react-dom: ^16.0.0, react-redux: ^5.0.6, redux: ^3.7.2, sonic_differ: ^1.0.7 } }其中几个角色各司其职依赖作用next/react/react-dom提供 React 同构直出能力服务端渲染出 HTML 字符串redux/react-redux/next-redux-wrapper管理页面状态供服务端与客户端共享初始化数据koa/koa-router作为自定义服务端拦截并处理 HTML 响应cheerio在服务端解析/改写 HTML注入 Sonic 注释标签sonic_differVasSonic 官方的 Node.js 差异化处理模块负责生成模板哈希与数据 diff注意README 与package.json中标注的依赖版本为仓库当时提交时的快照Next.js 为latest、React 16.x 等运行前请以实际安装解析到的版本为准。二、快速开始安装与启动 Demo2.1 环境要求Node.js 版本 7.0README 明确要求sonic-react/README_CN.md 中更进一步建议升级到 Node 8.x npm 5.x 环境以获得更好的体验。2.2 安装git clone https://github.com/Tencent/VasSonic.git my-project-name cd my-project-name/sonic-react npm install # 安装项目依赖2.3 构建与启动npm run build # 构建应用到 ./.next 目录 npm start # 启动生产环境服务启动后在 Chrome DevTools 中开启Mobile Emulation Mode移动端模拟模式访问http://localhost:3000/demo即可查看 Demo 页面。2.4 可用的 npm 脚本package.json的scripts字段中还额外提供了开发模式脚本脚本描述npm run dev以开发模式启动无需先 buildnpm run build打包构建到.next目录npm start生产模式启动需先执行 build若要用真实手机客户端联调可参考 sonic-react/README_CN.md 的做法手机安装 VasSonic 测试 App与服务器处于同一局域网配置手机代理并设置测试链接为http://服务器ip:3000/demo。三、服务端接入为直出 HTML 打上模板/数据注释锚点3.1 为什么需要注释锚点VasSonic 的增量更新local refresh原理是让移动端客户端缓存页面模板只传输变化的数据块。因此服务端输出的 HTML 必须能被区分为两部分模板template页面结构变化频率低数据块data blocks动态内容用 HTML 注释包裹标记。数据块必须以!-- sonicdiff-moduleName --开始、以!-- sonicdiff-moduleName-end --结束moduleName为自定义模块名。除数据块以外的部分即称为模板。3.2 转换前的原始 HTMLReact 直出页面的原始 HTML 长这样__NEXT_DATA__是 Next.js 用于 SSR 数据水合的内联脚本!-- add comment tags to separate template and data blocks from the initial html -- !DOCTYPE html html head/head body … … div idroot>function formatHtml(html) { const $ cheerio.load(html); $(*[data-sonicdiff]).each(function(index, element) { let tagName $(this).data(sonicdiff); return $(this).replaceWith(!--sonicdiff- tagName -- $(this).clone() !--sonicdiff- tagName -end--); }); html $.html(); html html.replace(/script\s*\s*__NEXT_DATA__\s*([\s\S]?)\/script/ig, function(data1) { return !--sonicdiff-initState-- data1 !--sonicdiff-initState-end--; }); return html; }该函数在 sonic-react/server.js 中有完整实现其代码注释清晰地演示了转换前后 HTML 的对应关系带data-sonicdifffirstScreenHtml的div idroot被!-- sonicdiff-firstScreenHtml --与!-- sonicdiff-firstScreenHtml-end --包裹__NEXT_DATA__脚本块被!-- sonicdiff-initState --与!-- sonicdiff-initState-end --包裹。3.4 转换后的 HTML 效果!-- add comment tags to separate template and data blocks from the initial html -- !DOCTYPE html html head/head body … … !-- sonicdiff-firstScreenHtml -- div idroot>server.use(async (ctx, next) { await next(); // only intercept html request if (!ctx.response.is(html)) { return; } // process non-sonic mode if (!ctx.request.header[accept-diff]) { ctx.body ctx.state.resHtml; return; } // use sonic_differ module to process the response let sonicData sonicDiff(ctx, formatHtml(ctx.state.resHtml)); if (sonicData.cache) { // 304 Not Modified, return nothing. ctx.body ; } else { // other Sonic status. ctx.body sonicData.data; } });这段逻辑在 sonic-react/server.js 中有完整实现几个关键点只拦截 HTML 请求ctx.response.is(html)不满足时直接放行非 Sonic 模式放行客户端请求头不带accept-diff时说明对方是普通浏览器而非 VasSonic 客户端直接返回原始 HTML不影响常规访问Sonic 模式走差异化处理请求头带accept-diff时将formatHtml转换后的 HTML 交给sonicDiff处理返回值二选一sonicData.cache为真时表示模板与数据均未变化返回空 body配合 304 语义客户端直接使用本地缓存否则返回sonicData.data可能是完整 HTML也可能是数据 diff 的 JSON。4.2 路由与渲染入口在 sonic-react/server.js 中/demo路由会先把 Next.js 直出结果暂存在ctx.state.resHtmlrouter.get(/demo, async ctx { ctx.set(Content-Type, text/html); ctx.state.resHtml await app.renderToHTML(ctx.req, ctx.res, ctx.path, ctx.query); });其他路径则交给 Next.js 默认 handler。这样拦截中间件与渲染逻辑解耦先渲染出 HTML 字符串再统一做 Sonic 处理。4.3 深入 sonic_differ 的差异化算法sonic_differ模块的完整实现位于 sonic-nodejs/common/diff.js读懂它才能理解服务端到底做了什么。核心流程如下计算整页 SHA-1 指纹对formatHtml后的完整 HTML 计算sha1记为md5比对If-None-Match请求头如果客户端传来的 etag 与当前md5一致说明整页完全没变返回{ cache: true }并设置Cache-Offline: store、Content-Length: 0、status 304提取模板先把title替换为占位符{title}再把所有!--sonicdiff-xxx--...!--sonicdiff-xxx-end--数据块替换为{xxx}占位符得到纯模板字符串templateHtml计算模板哈希对templateHtml计算templateMd5通过响应头template-tag下发模板未变、数据有变templateMd5 templateTag只下发数据 diff JSON包含{title}、各数据块内容并设置template-change: false返回的ctx.sonicMode 3模板有变下发完整 HTML设置template-change: true并根据客户端此前是否有模板缓存templateTag是否为空设置sonicMode为 2刷新或 1首次加载无任何 sonicdiff 标签sonicMode 0表示非 Sonic 页面。diff.js源码中对sonicMode的含义给出了权威注释//sonicMode含义 // 0-非sonic没有sonicdiff标签 // 1-首次加载本地无模板和数据 // 2-页面刷新模板有变 // 3-局部数据刷新模板不变数据有变 // 4-完成缓存304模板不变数据不变同时响应头还包含sonic-etag-key默认为Etag可通过sonic_differ的第三个参数配置与Cache-Offline: true供客户端缓存页面与判断更新模式。五、客户端接入componentDidMount 阶段与移动端交互5.1 交互链路概述前端页面的接入点在 React 生命周期componentDidMount()页面挂载后通过window.sonic.getDiffData()主动向移动端客户端发起请求客户端随后通过window[getDiffDataCallback]回调把 Sonic 响应码srcCode和 diff 数据回传给页面。完整代码见 sonic-react/pages/demo.js。5.2 核心代码getSonicDatagetSonicData(callback) { let sonicHadExecute 0; // whether the callback is triggered const timeout 3000; // a timeout to trigger callback // Interacts with mobile client by JavaScript interface to get Sonic diff data. window.sonic window.sonic.getDiffData(); function sonicCallback(data) { if (sonicHadExecute 0) { sonicHadExecute 1; callback(data[sonicStatus], data[sonicUpdateData]); } } setTimeout(function() { if (sonicHadExecute 0) { sonicHadExecute 1; callback(0, {}); } }, timeout); // the mobile client will invoke method getDiffDataCallback which can send Sonic response code and diff data to websites. window[getDiffDataCallback] function (sonicData) { /** * Sonic status: * 0: It fails to get any data from mobile client. * 1: It is first time for mobile client to use Sonic. * 2: Mobile client reload the whole websites. * 3: Websites will be updated dynamically with local refresh. * 4: The Sonic request of mobile client receives a 304 response code and nothing has been modified. */ let sonicStatus 0; let sonicUpdateData {}; // sonic diff data sonicData JSON.parse(sonicData); switch (parseInt(sonicData[srcCode], 10)) { case 1000: sonicStatus 1; break; case 2000: sonicStatus 2; break; case 200: sonicStatus 3; sonicUpdateData JSON.parse(sonicData[result] || {}); break; case 304: sonicStatus 4; break; } sonicCallback({ sonicStatus: sonicStatus, sonicUpdateData: sonicUpdateData }); }; }5.3 状态码对照表务必牢记服务端srcCode与前端sonicStatus的映射关系如下移动端返回srcCode前端sonicStatus含义前端动作超时或异常0未从客户端取到任何数据视为异常不做特殊刷新10001客户端首次使用 Sonic首次加载全量渲染20002模板有变化客户端重载整页无需局部刷新2003模板未变、数据有变使用result中的 diff 数据局部刷新3044模板与数据均未变完全使用本地缓存此外代码中设置了3 秒超时保护若getDiffDataCallback迟迟未被客户端调用例如页面运行在普通浏览器而非 VasSonic 客户端中3 秒后强制以sonicStatus 0触发回调避免页面永久阻塞。sonicHadExecute标志位保证回调只执行一次。5.4 在 componentDidMount 中处理 diff 数据componentDidMount() { // handle the response from mobile client which include Sonic response code and diff data. this.getSonicData((status, sonicUpdateData) { switch (status) { // here, we only process the case when data updates case 3: // update the Redux store based on changes from the mobile client let initState sonicUpdateData[{initState}] || ; initState.replace(/!--sonicdiff-initState--\s*script\s*__NEXT_DATA__\s*([\s\S]?)module/ig, function(matched, $1) { window.__NEXT_DATA__ JSON.parse($1); }); this.props.initImgArr(window.__NEXT_DATA__.props.initialState.gameArea); break; default: break } // display sonic status this.props.setSonicStatus(status); }); }这个示例只处理status 3数据更新的场景从sonicUpdateData[{initState}]取出 diff 数据块它是!--sonicdiff-initState--包裹的__NEXT_DATA__脚本块用正则提取出其中的 JSON 并JSON.parse写回window.__NEXT_DATA__通过 Redux actioninitImgArr把新数据写入 store驱动页面局部刷新最后无论何种状态都调用setSonicStatus在页面上展示当前 Sonic 状态如数据更新完全缓存等。initImgArr与setSonicStatus两个 action 及 reducer 定义在 sonic-react/redux/duck.js其中setSonicStatus会把数字状态映射为中文文案0→异常、1→首次加载、2→模板更新、3→数据更新、4→完全缓存方便在 Demo 页面上直观展示当前处于哪种 Sonic 模式。六、从 Demo 到生产接入要点与原理小结6.1 模板/数据划分原则每个数据块必须成对使用!-- sonicdiff-moduleName --与!-- sonicdiff-moduleName-end --数据块之外的部分统称模板模板应当尽量稳定避免高频变更否则将退化为整页刷新sonicStatus 2对 React 直出页面而言__NEXT_DATA__SSR 初始状态是最典型的数据块务必独立标记若 HTML 中完全没有 sonicdiff 标签服务端sonic_differ会判定为非 Sonic 页面sonicMode 0直接下发完整 HTML。6.2 服务端接入清单在路由中先渲染出 HTML 字符串并暂存示例用ctx.state.resHtml用formatHtml注入注释锚点可利用data-sonicdiff属性 cheerio 自动化完成用拦截中间件判断accept-diff请求头区分普通浏览器与 Sonic 客户端调用sonic_differ(ctx, html)按返回结果决定下发空 body304 缓存还是数据完整 HTML / diff JSON。6.3 客户端接入清单在componentDidMount或页面需要的任意时机调用window.sonic.getDiffData()注册window[getDiffDataCallback]接收客户端回传的srcCode与result按 5.3 的映射表将srcCode翻译为sonicStatus对status 3时用 diff 数据局部更新页面示例通过 Redux 更新 store务必实现超时兜底保证在非 Sonic 环境普通浏览器下页面也能正常展示。6.4 相关资源索引服务端完整实现sonic-react/server.js客户端接入代码sonic-react/pages/demo.js差异化处理模块源码sonic-nodejs/common/diff.js通用 Node.js 接入文档含sonic_differ的流式用法与纯前端 HTML 示例sonic-nodejs/README.md示例项目详细说明含手机联调步骤与目录结构sonic-react/README_CN.md七、结语本文以 VasSonic 仓库中的 React 同构直出示例为主线完整还原了 Sonic 在 SSR 场景下的服务端、客户端接入路径服务端用注释锚点把直出 HTML 切成模板 数据块再借sonic_differ完成模板指纹比对与数据 diff 下发客户端在componentDidMount阶段通过 JS 接口拿到状态码与 diff 数据按需局部刷新页面。通过这样的配合页面在移动端可以实现模板缓存、数据增量更新从而显著缩短二次访问的首屏耗时。如果你希望进一步理解 Sonic 的完整设计Standard/Quick 双模式、Android/iOS 客户端实现、Java/PHP 服务端 SDK可以在当前仓库中继续阅读 sonic-android/README.md、sonic-iOS/README.md 与 sonic-java/README.md 等文档。赞分享移动开发前端后端【免费下载链接】VasSonicVasSonic is a lightweight and high-performance Hybrid framework developed by tencent VAS team, which is intended to speed up the first screen of websites working on Android and iOS platform.项目地址https://gitcode.com/gh_mirrors/va/VasSonic点击查看免费下载相关推荐VasSonic React 同构直出示例深度解析Next.js Redux Koa2 接入 Sonic 的完整实战VasSonic React 同构直出示例深度解析Next.js Redux Koa2 接入 Sonic 的完整实战 本文以 VasSonic 仓库中移动开发前端后端Next.js 接入 Auth0 认证实战基于官方 examples/auth0 示例解析登录、登出与受保护页面Next.js 接入 Auth0 认证实战基于官方 examples/auth0 示例解析登录、登出与受保护页面 本篇技术文章基于 Next.js 仓库中的官前端后端Web框架SSR前端构建基于 Next.js 静态导出的高转化落地页模板AAS 项目 nextjs-static 实战指南基于 Next.js 静态导出的高转化落地页模板AAS 项目 nextjs static 实战指南 本文以 agentic awesome skills 仓库AI 技能AI 插件上一篇3分钟快速上手用MOOTDX轻松获取通达信数据开启你的量化投资之旅下一篇如何利用Anthropic Cookbook掌握长上下文处理能力终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表