ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

React+Tailwind+Remotion打造动态可视化技能包,一键发布至陌讯Skills

React+Tailwind+Remotion打造动态可视化技能包,一键发布至陌讯Skills 如果你最近在关注前端开发的新玩法应该已经注意到了“技能化”这股风——不只是做页面、做后台而是把一段前端能力打包成可以被对话助手、智能体直接调用的技能包。陌讯Skills这类开放平台正好就是干这个的。我实际折腾了一圈下来发现用 React Tailwind Remotion 这套组合来做技能是目前最顺手的路子尤其是动态可视化类技能比如数字动画、图表展示、大屏数据卡几乎是为它量身定做的。这篇内容我会把这套组合从环境搭建、核心组件写法到一键打包上传陌讯Skills 的完整流程都走一遍适合已经会用 React、想拓展技能开发方向的前端同学也适合刚接触 Remotion 但想快速落地一个可发布的技能包的人。1. 为什么是ReactTailwindRemotion这套组合1.1 三件套各自解决什么问题先说 React。技能包只要稍微复杂一点就一定会遇到状态管理、组件复用、模块拆分这些问题。陌讯Skills 的技能不是简单返回一段文本它需要渲染出可交互、可动态传参的界面。React 的组件模型非常适合干这个你写好的技能卡片就是一个组件输入参数就是 props输出就是一段可以嵌入到对话流中的界面。这种“组件即技能”的心智模型比传统的模板字符串拼接或者手写 DOM 要清晰得多。Tailwind 解决的是样式一致性和开发效率问题。技能包不是孤立的页面它要嵌入到陌讯Skills 的对话流、卡片容器、移动端和桌面端等不同环境里。如果用传统 CSS 或者 CSS Modules要么样式容易互相污染要么写起来特别啰嗦。Tailwind 的原子类方式加上统一的 spacing、颜色、圆角变量能保证技能在任何宿主环境里渲染出来都长得差不多而且不用为每个技能单独维护一套样式文件。Remotion 是这套组合里最特别的一块它允许你用 React 组件直接写视频和动画然后把动画输出为视频、GIF也可以在网页里用 Player 组件实时播放。对于技能来说这意味着你可以把“数据变化”做成动态可视化而不是一张静态截图。比如我做过一个销售数据技能用户提问“这个月各区域业绩如何”技能可以直接播放一段数字滚动 柱状图增长的动画这个感知力远强于静态 Markdown 表格。1.2 相比传统方案的取舍一定有同学会问动画用 CSS 不就行了为什么非要上 Remotion我的回答是看你要做的技能复杂度。CSS 动画做按钮 hover、卡片翻转没问题但一旦涉及到数据驱动的时间轴动画比如“前 30 帧显示标题第 30 到 80 帧数字滚动第 80 帧之后图表入场”用 CSS 写就非常痛苦因为你要手动计算各种延迟、关键帧百分比而且很难把组件内部的状态和动画进度绑定起来。Remotion 的核心优势是“用帧驱动一切”。每个组件都能拿到当前帧数 useCurrentFrame()你可以在任意位置判断当前播到哪一帧然后决定渲染什么内容。这个模型天然适合数据叙事类技能。至于直接用 Canvas 或者 WebGL 手写动画也不是不行但开发效率和可维护性差不少。Canvas 是命令式绘制你需要在 requestAnimationFrame 里不断清屏、重绘、计算坐标。Remotion 则是声明式的你只是描述“这一帧长什么样”渲染器帮你把中间过程补出来。而且 Remotion 组件就是普通 React 组件技能里的数据请求、格式化、业务逻辑都写在组件里上传陌讯Skills 之后它还能作为普通组件在 React 项目里复用这点 Canvas 做不到。1.3 陌讯Skills为什么吃这套组合我研究了一下陌讯Skills 的开放方式它的技能本质上是一个“可被对话场景调用的前端模块”。平台需要在宿主环境里渲染你的技能同时又要保证不同开发者的技能不至于互相干扰所以它对你提交的代码有一个隐性要求产物必须是自包含的、可独立渲染的模块。React 组件天然适合做这种隔离。Tailwind 负责把样式锁定在一个可控范围内Remotion Player 组件则可以把动画嵌进一个不固定尺寸的容器里由宿主决定最终渲染尺寸。我实测下来用这套组合打包出来的技能包在陌讯Skills 的聊天窗口、侧边栏面板、甚至移动端 H5 里都能无缝渲染这是“一键集成”能成立的底层原因。还有一个关键点陌讯Skills 的技能需要能让 AI 智能体动态调用。AI 通过技能声明里的入参 schema 决定传什么参数然后你的 React 组件拿到这些参数直接渲染。React Tailwind Remotion 这套组合写出来的技能入参声明足够清晰组件渲染逻辑也能做到“参数进、画面出”天然适配这种 AI-friendly 的调用模式。2. 环境准备与工程初始化2.1 选型Vite还是Next.js做陌讯Skills 技能包我强烈建议你用 Vite React TypeScript不要一上来就上 Next.js。理由有三个。第一技能包最终的产物是静态可嵌入的模块不需要服务端渲染Next.js 的 SSR、API Routes 在这套场景里基本用不上反而增加构建复杂度。第二Vite 的 dev server 启动速度很快尤其是 Remotion 的预览需要频繁刷新用 Vite 能明显感觉到开发体验更流畅。第三Vite 对构建产物的控制更精细你可以很容易地把技能组件打成单独的库文件或自执行包方便上传到陌讯Skills。如果技能未来要做得特别复杂比如需要独立的落地页、需要 SEO、需要服务端数据聚合那再迁移到 Next.js 也不迟。但起步阶段Vite 是最省心的。初始化项目我一般这么干npm create vitelatest my-skill -- --template react-ts cd my-skill npm install这里的 my-skill 是你技能包的名称我建议命名和最终上传陌讯Skills 的技能 ID 一致避免后期维护时对不上号。2.2 一键接入TailwindTailwind 接入很简单但现在不同版本的 Tailwind 安装方式有点区别这是个容易踩坑的地方。如果你用的是 Tailwind v3经典安装方式是npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p然后修改 tailwind.config.js/** type {import(tailwindcss).Config} */ export default { content: [ ./index.html, ./src/**/*.{js,ts,jsx,tsx}, ], theme: { extend: { colors: { brand: { 50: #eef2ff, 500: #6366f1, 600: #4f46e5, }, }, }, }, plugins: [], };如果你用的是 Tailwind v4那安装方式变成了npm install tailwindcss tailwindcss/vite然后在 vite.config.ts 里加一个插件CSS 文件里写import tailwindcss;就够了。这里我要重点提醒一个坑content 的扫描路径一定要覆盖到所有写 className 的地方尤其是 Remotion 组件放在哪个目录不要漏掉。我之前有一次技能里的数字动画部分样式死活不生效查了半天才发现是 Tailwind JIT 没扫描到那个目录导致动态生成的类名被 purge 掉了。这种问题不会报错而是静默丢失样式排查起来很隐蔽。2.3 安装并初始化RemotionRemotion 的安装相对简单核心就是两个包npm install remotion remotion/cli然后创建 remotion.config.tsimport { Config } from remotion/cli/config; Config.setVideoImageFormat(jpeg); Config.setOverwriteOutput(true);接着在 src 下建一个 Root.tsx用来注册你的 Composition。这是 Remotion 项目的入口所有可渲染的合成都要在这里登记import { Composition } from remotion; import { SalesSkill } from ./skills/SalesSkill; export const RemotionRoot () { return ( Composition idSalesSkill component{SalesSkill} durationInFrames{180} fps{30} width{800} height{600} / / ); };这里 durationInFrames 是动画总帧数如果是 30fps 的视频180 帧就是 6 秒。对于陌讯Skills 里的技能卡片我建议动画控制在 3 到 8 秒太短用户看不清数据太长又会让人失去耐心。800x600 是比较稳妥的默认尺寸因为大多数聊天窗口的内容区宽度在这个量级。初始化完成后跑一下npm run remotion:preview如果能打开 Remotion 的预览界面说明环境基本就绪了。后面我会细说怎么在这个基础上做真正的技能卡片。3. 核心实现构建一个可上传的技能卡片3.1 技能卡片的组件设计一个陌讯Skills 技能本质上就是一个“输入参数 渲染结果”的封闭组件。所以组件设计的第一步是先定义清楚你的技能入参。以我做过的“数据简报”技能为例入参大致是这些export type DataBriefSkillProps { title: string; description: string; metrics: { label: string; value: number; unit?: string; trend?: up | down | flat; }[]; source?: string; };这种类型定义有几个好处。第一AI 智能体调用技能时会按照这个结构来生成参数类型定义就是你的接口协议。第二组件内部可以针对不同参数做防御性处理避免空数据导致白屏。第三后面打包上传陌讯Skills 时这个类型可以帮你自动生成入参 schema 文档不用手写。组件建议拆成三层最外层是 SkillCard负责整体布局、背景、圆角、内边距对应陌讯Skills 宿主的卡片容器。中间层是数据区块比如 MetricsPanel负责把 metrics 数组渲染成一个个指标块。最内层是动画元素比如 AnimatedNumber用 Remotion 的帧驱动能力做数字滚动动画。这样的分层可以让组件既能在 Remotion 里作为视频渲染也能在普通 React 项目里静态渲染复用性很强。3.2 用Remotion写第一个合成Remotion 的核心是你可以像写普通 React 组件一样写动画区别就是多了几个 hooks。最常用的是useCurrentFrame和useVideoConfig。useCurrentFrame返回当前帧数useVideoConfig返回 fps、宽高等配置。基于这两个 hooks你可以做任何逐帧计算。我第一次写的时候用的是这种套路import { useCurrentFrame, useVideoConfig, interpolate, spring } from remotion; export const AnimatedNumber ({ value }: { value: number }) { const frame useCurrentFrame(); const { fps } useVideoConfig(); const progress spring({ frame, fps, config: { damping: 200, stiffness: 100 }, }); const displayValue Math.round(value * progress); return div classNametext-4xl font-bold text-brand-600{displayValue}/div; };这段代码的意思很好理解spring 函数根据当前帧算出一个 0 到 1 的进度值然后拿这个进度值乘以原始数据就得到了当前帧应该显示的数字。随着帧数增加数字从 0 滚动到目标值看起来就是一个流畅的数字滚动动画。这个组件用在实际技能里效果很惊艳尤其是配合热搜词里大家常说的“tailwind 数字动画”场景——销售数据、用户增长、系统监控指标这类“数字会说话”的内容用数字动画呈现比静态文本有说服力得多。再复杂一点的用法是用interpolate控制透明度、位移、缩放const opacity interpolate(frame, [0, 20], [0, 1], { extrapolateRight: clamp, }); const translateY interpolate(frame, [0, 30], [20, 0], { extrapolateRight: clamp, });这样标题就会在开头 20 帧内渐入同时从下方 20 像素的位置浮上来配合数字滚动整体节奏就很像一个正式的数据视频了。3.3 用Tailwind统一技能视觉规范这里我特别想强调Remotion 默认的 style 写法是 style{{}}但如果你用 Tailwind完全可以在 Remotion 组件里直接用 className关键是确保 Tailwind 扫描到了对应的 tsx 文件。我写技能视觉规范时会先和陌讯Skills 的宿主风格对齐。比如技能卡片圆角我用 rounded-2xl背景用白底或浅灰渐变标题字号统一 text-base 或 text-lg指标数字用 text-3xl 或 text-4xl。这样技能放在对话流里不突兀像是平台原生功能的一部分。还有一个实用技巧用 Tailwind 的safelist保证动态类名不被 purge 掉。比如指标趋势颜色需要根据运行时数据决定可能是text-green-500也可能是text-red-500如果这些类名没有出现在源码里Tailwind 的 JIT 扫描是扫描不到的。这时候在 tailwind.config.js 里加 safelistsafelist: [ text-green-500, text-red-500, text-yellow-500, bg-gray-50, bg-gradient-to-br, ],这个坑我在做图表类技能时踩过当时趋势颜色在本地预览正常打包上传到陌讯Skills 后颜色全变成默认色。排查半天才知道是 Tailwind 构建时把动态类名忽略了。加了 safelist 之后问题解决。3.4 用Player组件做实时预览开发技能时不可能每次都导出视频看效果Remotion 提供了一个Player组件可以像播放器一样在普通 React 页面里实时预览动画。在 App.tsx 里这样用import { Player } from remotion/player; import { DataBriefSkill } from ./skills/DataBriefSkill; const App () { return ( Player component{DataBriefSkill} inputProps{{ title: 本月业绩概览, description: 华东区环比增长 12%, metrics: [ { label: 销售额, value: 860000, unit: 元, trend: up }, { label: 订单量, value: 2400, unit: 单, trend: up }, ], }} durationInFrames{180} fps{30} compositionWidth{800} compositionHeight{600} controls / ); };注意inputProps就是把参数传给技能组件的地方。在本地调试时你可以在 inputProps 里模拟 AI 智能体会传入的各种参数组合包括缺字段、超长文本、空数组这些异常情况把组件的鲁棒性在本地先磨好。4. 一键集成到陌讯Skills从打包到发布4.1 理解陌讯Skills的接入模型陌讯Skills 的技能接入模型我理解下来是这样平台会运行一个宿主环境技能包被加载之后平台把你的技能组件挂载到一个容器节点里然后根据 AI 传过来的参数实时渲染。所以对开发者来说你提交的内容不只是“视频”而是一个有输入输出约定的可交互组件。为了让平台知道你的技能接收什么参数、输出什么界面你需要提供一份技能清单我习惯叫 manifest.json。一个典型的 manifest 长这样{ id: data-brief-skill, name: 数据简报生成器, description: 根据用户传入的指标数据生成动态可视化数据简报, version: 1.0.0, entry: skill.js, propsSchema: { type: object, properties: { title: { type: string, description: 简报标题 }, description: { type: string, description: 简报描述 }, metrics: { type: array, items: { type: object, properties: { label: { type: string }, value: { type: number }, unit: { type: string }, trend: { type: string, enum: [up, down, flat] } } } } } }, dependencies: [react, react-dom] }propsSchema 是给 AI 智能体看的它决定了 AI 决定调用你的技能时应该从用户的提问里抽取哪些信息填进来。schema 写得越清晰AI 传参的准确率越高技能的表现就越好。这块是我后来反复优化最多的地方一开始我把字段写得比较随意AI 传参经常缺这缺那后来严格按 JSON Schema 规范写情况好了很多。4.2 配置构建脚本与产物优化陌讯Skills 跟 Vite 原生构建的默认产物其实不完全兼容。Vite 默认是面向 web 应用的构建会输出 index.html、js、css 等一堆文件但技能包需要的是一个独立的、可被宿主动态加载的 JS 模块。我推荐用 Vite 的库模式构建技能包。在 vite.config.ts 里做如下配置import { defineConfig } from vite; import react from vitejs/plugin-react; export default defineConfig({ plugins: [react()], build: { lib: { entry: src/skills/index.ts, formats: [es], fileName: skill, }, rollupOptions: { output: { // 把 react 等公共依赖 external 掉减小体积 external: [react, react-dom], globals: { react: React, react-dom: ReactDOM, }, }, }, }, });把外部依赖 external 掉可以让技能包的体积大幅下降。我实际构建过一个包含 6 个指标的技能包产物只有 38KB比直接全量打包小了一个数量级。陌讯Skills 宿主环境本身提供 React 运行时所以这种 external 策略是可行的而且加载速度明显更快。4.3 用CLI一键发布一键集成没有真正的“一键”是不行的。我写了这样一个发布脚本本地构建完成后自动打包上传到陌讯Skills#!/usr/bin/env bash set -e echo 构建技能包... npm run build echo 版本号: ${VERSION:1.0.0} echo 上传到陌讯Skills... npx moxun-skills-cli upload \ --manifest dist/manifest.json \ --bundle dist/skill.js \ --version $VERSION \ --token $MOXUN_TOKEN发布技能之前检查几件事manifest.json 的 schema 有没有更新版本号、产物文件名是否和 manifest 里 entry 一致、token 是否还有权限。我踩过的坑是改完代码忘记改 version导致重复版本上传被平台拒绝。建议在上传脚本里加一个从 package.json 读取版本号的逻辑保持同步。上传成功之后陌讯Skills 会给一个预览链接你可以在真实的对话环境里测试。测试时我一般会准备三种用例正常输入、极端输入比如空数据、超大数值、模糊输入比如用户没说具体指标观察 AI 传参和技能渲染表现。4.4 本地调试全链路陌讯Skills 平台上的调试能做的有限真正的开发调试还是要放在本地。我的工作流是这样的用 Player 组件在本地把技能动画调到满意。用 Vite 的 dev server 模拟陌讯Skills 的宿主环境验证技能组件嵌入后的表现。构建产物后写一个最小化的宿主页面手动加载 skill.js确认产物包没有依赖缺失。最后才上传到陌讯Skills 做真机验证。这套流程走成熟后从改代码到上线基本能控制在几分钟内。5. 常见问题与排查技巧实录5.1 Remotion白屏与帧参数问题Remotion 相关的问题里白屏出现频率最高。我遇到过的白屏原因主要有三种第一种是 Composition 的 component 没有正确导出。你检查一下 Root.tsx 里注册的组件和实际导出的组件是否一致一不小心路径写错就会白屏。第二种是 durationInFrames 设置得不合理。如果你在组件里用了一些极端插值比如 frame 到了 200 才触发某个动画但 durationInFrames 只有 180那后半段内容就永远显示不出来看起来就像卡住了。而且这个还不算严格意义的白屏更像“动画没播完”。第三种是渲染容器没有设置宽高。Remotion 的 Player 组件会覆盖容器尺寸但如果你在其他环境直接渲染组件容器高度为 0看起来就是白屏。解决办法是给容器一个显式的高div style{{ width: 100%, height: 400 }} Player ... / /div5.2 Tailwind样式丢失或错位技能上传到陌讯Skills 后样式丢失或者错位90% 都是 Tailwind JIT 扫描问题。我前面提过 safelist 的解决办法这里再补充一个更系统的排查流程首先在本地构建产物里搜索一下 className 对应样式是否存在。如果产物 CSS 里就没有那就是扫描问题。其次检查 tailwind.config.js 的 content 是否包含你技能组件的目录。最后检查 Tailwind 版本。Tailwind v4 和 v3 的配置方式差别很大不要照搬旧配置。还有一类错位问题是宿主环境的全局样式干扰了你的技能。虽然 Tailwind 本身有 preflight 重置但宿主页面如果有自己的全局样式层级更高的选择器可能覆盖掉你的类。我应对的方式是在技能卡片的最外层包一层带有固定 class 的容器比如moxun-skill-root然后在 CSS 里针对性微调减少被全局样式影响的概率。5.3 技能包上传失败与体积过大上传失败通常会在几秒之内报错。常见原因一个是 manifest.json 格式不正确另一个是产物文件超过了平台的体积限制。如果你的技能包体积超标优先做这几件事确认 React、ReactDOM 已经被 external不要打进产物里。检查 Remotion 的引入方式尽量按需引入不要import { remotion } from remotion这种全量导入。如果技能里用了较大的静态资源图片、字体考虑改用 CDN 地址而不是打进包内。用rollup-plugin-visualizer分析产物构成定位大模块。我做过的一个香港旅游攻略技能最初体积 1.2MB就是因为把几张高清景区图打进了包里。后来改成 CDN 图片地址体积直接降到 90KB加载体验完全不是一个级别。5.4 技能设计对AI智能体的友好度优化这可能是目前前端技能开发最有趣的一块。陌讯Skills 的 AI 智能体会读取你的 manifest 和 propsSchema然后决定“何时调用你的技能、传入什么参数”。所以你的技能设计越接近“纯函数”AI 用起来越顺手。我的经验是三条技能职责要单一。如果你发现一个技能需要五六个入参还互相依赖拆分掉一个技能只做一件事。入参默认值要好。AI 不可能每次都把所有参数传全你的组件要对缺失参数有默认渲染而不是白屏或者报错。输出要有自解释性。技能渲染完成之后如果有机会给 AI 一个回执比如渲染成功、渲染的指标数量AI 下一次调用时会更聪明。这套思路我现在做前端技能开发时完全用它本质上就是把前端组件当成一个被 AI 驱动的小应用来设计。5.5 开局踩过的三个具体坑最后分享三个我实际踩过、花了不少时间的坑希望你不用重走。第一个是版本兼容。Remotion 升级到 4.x 之后很多旧 API 改了名字比如registerRoot的路径、Config的写法都有变化。你百度搜“remotion skill 安装”可能会找到一堆旧教程照着做大概率报错。最好的办法是直接看官方文档的 upgrade guide或者直接用最新版本跑一遍最小示例。第二个是数字动画和 Tailwind 的字体变量冲突。Tailwind 默认字体族和 Remotion 渲染时用的字体可能不一致导致数字宽度跳动或者位置偏移。解决办法是在 Tailwind 的 theme 里显式配置字体栈或者给数字区域单独设置 font-family。第三个是本地渲染和上传后表现不一致。这个原因很多常见的是宿主环境没有提供 Remotion 的某些浏览器 API。我会在上传前用 headless 浏览器做一次真实渲染校验确保产物在独立环境里能正常跑起来。写在最后的一点心得做到现在我最大的体会是陌讯Skills 这种技能平台给前端开发者开了一扇新门。以前我们写 React 组件是给“人”看的现在组件要同时给“人”和“AI”看这要求我们更严谨地设计输入输出、更认真地对待异常边界、更刻意地控制包体积和加载性能。React Tailwind Remotion 这套组合恰好覆盖了界面、样式、动态展示三条线是目前做技能开发效率最高的一组搭档。还是那句话不要一上来追求复杂先把一个数据展示技能从开发到上传跑通再慢慢加动画、加交互。技能开发的乐趣试一次就知道了。
返回列表