okwinds

uiux-react-jsx-packager

Package an existing React UI/UX demo into a single self-contained .jsx file with default-export root component, zero third-party runtime dependencies (no react-router/lucide/echarts/etc.), in-file styles (style tag or inline style objects), inline SVG icons, embedded or placeholder images, and state-based navigation. Use when asked to “合并为单文件 JSX/单文件打包/one-file React/零外部依赖/内联 CSS/替换图标库/用 state 做路由/把 demo 打包成独立 JSX 文件”.

okwinds 47 3 Updated 3mo ago

Resources

4
GitHub

Install

npx skillscat add okwinds/miscellany/uiux-react-jsx-packager

Install via the SkillsCat registry.

SKILL.md

uiux-react-jsx-packager

Output Contract

  • Create one new *.jsx file (do not overwrite existing files unless explicitly requested).
  • Export the root component via export default.
  • Keep runtime dependencies to React only (no react-router, lucide-react, echarts, classnames, zustand, etc.).
  • Keep styles in-file via <style> injection and/or inline style={{...}}.
  • Replace icon libraries with inline SVG components.
  • Replace images with base64-inlined data URLs or deterministic placeholders.
  • Implement navigation via component state (optionally sync to location.hash for shareable URLs).
  • Preserve all interactions/animations/state logic; do not simplify behavior.
  • Prefer embedding the compiled CSS (if available) to preserve spacing/colors/shadows.

关于“像素级一致”(可选增强,不是默认门禁)

“像素级一致 / pixel-perfect”在工程上必须限定条件,否则不可验证。只有当你要对外宣称 pixel-perfect 时,才需要做像素 diff 验收:

  • 同一台机器、同一 OS 与同一浏览器版本(建议固定 Chrome 版本)
  • 固定 viewport(宽高)与 deviceScaleFactor(DPR)
  • 字体必须一致(包含字重):不要依赖系统字体差异;必要时把字体文件以 @font-face 形式内联进 CSS(base64 data URL)
  • 截图对比时必须处于“稳定态”:避免进行中动画/过渡影响像素 diff(见 Verification)

默认交付的强门禁是“可携带可跑、不白屏、导航可用”(见 Verification Gate)。

Workflow (do in order)

1) Discover structure and runtime surface

  • Find and follow any AGENTS.md instructions that apply to the target directory tree.
  • Locate the actual app entry (src/main.*, src/index.*, App.*) and identify:
    • Router entrypoints (hash router / react-router / custom)
    • Pages and module switch logic
    • Global providers (theme/style/toast/auth)
    • Global CSS + theme tokens (CSS variables, dark mode attribute, etc.)
    • Third-party runtime deps that must be removed (icons, charts, router, utilities)
  • Prefer reading the demo source plus the built output CSS (if it exists) to lock visuals.

2) Choose a “visual parity” strategy for styles (pick one)

  • Preferred: Embed production/built CSS (e.g. dist/assets/index-*.css) into const APP_CSS = String.raw\...`;and inject via<style>`.<ul> <li>Keep existing <code>className</code> strings unchanged.</li> <li>This is the most reliable way to keep spacing/colors/shadows identical without Tailwind tooling.</li> </ul> </li> <li>Otherwise: Inline/merge source CSS files into one <code>&lt;style&gt;</code> block (still in-file).</li> </ul> <p>Pixel-perfect 强依赖样式与字体,请额外确认:</p> <ul> <li>CSS variables / theme tokens(暗色模式 attribute/class)与原工程一致</li> <li>所有外部字体/图标资源都已本地化/内联(不要依赖远程 URL)</li> <li>截图对比用同一套 reset / <code>box-sizing</code> 规则(建议直接使用构建后 CSS)</li> </ul> <h3>3) Create the merge target file</h3> <ul> <li>Create <code>YourNameMerged.jsx</code> with:<ul> <li>A single React import (<code>import React, { ... } from &#39;react&#39;;</code>)</li> <li><code>APP_CSS</code> string + a <code>AppStyleTag()</code> that injects exactly one <code>&lt;style&gt;</code> node</li> <li>Helpers you will need (clamp, download, clipboard, etc.)</li> </ul> </li> </ul> <h3>4) Merge code into one file (no module imports)</h3> <ul> <li>Copy code for components/pages/hooks/utils into the single file.</li> <li>Remove all non-React imports; replace with local definitions.</li> <li>Remove TypeScript syntax:<ul> <li>Delete <code>interface</code>, <code>type</code>, generics (<code>useState&lt;string&gt;()</code>), and annotations (<code>x: string</code>)</li> <li>Remove <code>as T</code> assertions</li> </ul> </li> <li>Keep behavior identical:<ul> <li>Same default state values</li> <li>Same reducers/actions</li> <li>Same animations (CSS-based or requestAnimationFrame-based)</li> </ul> </li> </ul> <h3>5) Replace third-party runtime dependencies (patterns)</h3> <h4>Router → state router</h4> <ul> <li>Implement a tiny state router (context + <code>navigate(module, subPath)</code>).</li> <li>Optionally keep hash sync for shareable URLs, but the <strong>source of truth must be React state</strong>.</li> </ul> <p>Minimal pattern (adapt, do not paste blindly):</p> <pre><code class="language-jsx" data-language="jsx">const RouterContext = React.createContext(); function RouterProvider({ children }) { const [route, setRoute] = React.useState({ module_key: &#39;text_workbench&#39;, subPath: &#39;&#39; }); const navigate = React.useCallback((module_key, subPath = &#39;&#39;) =&gt; setRoute({ module_key, subPath }), []); return &lt;RouterContext.Provider value={{ route, navigate }}&gt;{children}&lt;/RouterContext.Provider&gt;; } function useRouter() { return React.useContext(RouterContext); }</code></pre><h4>Icons (lucide-react etc.) → inline SVG</h4> <ul> <li>Replace icon imports with local React components that return <code>&lt;svg ...&gt;</code> + <code>&lt;path ...&gt;</code>.</li> <li>Keep size/stroke defaults consistent with the original icon system.</li> </ul> <h4>Charts (echarts etc.) → SVG/Canvas + fallback table</h4> <ul> <li>Re-implement charts with pure SVG/Canvas.</li> <li>Preserve:<ul> <li>Tooltips</li> <li>Click/hover actions (emit the same callbacks)</li> <li>Accessible labels (aria where relevant)</li> <li>Data table fallback (for regressions and accessibility)</li> </ul> </li> </ul> <h3>6) Images and external assets</h3> <ul> <li>Replace runtime-loaded images with:<ul> <li><code>data:image/...;base64,...</code> (preferred when you need fidelity)</li> <li>Or deterministic placeholders (solid blocks, initials avatars, etc.)</li> </ul> </li> <li>Never leave remote URLs in the final file unless explicitly allowed.</li> </ul> <h3>7) Wire the root component to real pages</h3> <ul> <li>Ensure the default export renders the real module pages (not placeholders).</li> <li>Wrap providers in the same order as the original app (style/theme/toast/auth).</li> <li>Keep module-level side effects intact (e.g. console events, reducers, mock async flows).</li> </ul> <h3>8) Verification (must run before “done”)</h3> <h4>Verification Gate(默认必达)</h4> <p>必须按顺序验证,任何一步失败都不算“打包完成”:</p> <ol> <li><p><strong>静态门禁(必须)</strong></p> <ul> <li>只保留 1 条 <code>import ... from &#39;react&#39;</code></li> <li>有 <code>export default</code></li> <li>无 <code>require()</code> / <code>import()</code></li> <li>无资产导入(<code>.css/.svg/.png/...</code>)</li> <li>运行:<code>python3 scripts/verify_singlefile_jsx.py /path/to/Merged.jsx</code></li> </ul> </li> <li><p><strong>运行时门禁(必须)</strong></p> <ul> <li>用一个“临时预览工程”加载该 <code>.jsx</code>,确保<strong>首屏不白屏</strong>,并且能完成最小交互 smoke:<ul> <li>侧边栏(或顶栏)切换主要模块/页面(至少点一轮能切换内容)</li> <li><code>location.hash</code> 切换(如果你实现了 hash 同步)</li> </ul> </li> <li>推荐使用本技能自带脚本:<code>bash scripts/preview_single_jsx_vite.sh /path/to/Merged.jsx</code></li> </ul> </li> </ol> <blockquote> <p>常见误判:端口被占用时,Vite 会输出 <code>Port XXXX is in use, trying another one...</code> 并自动切换端口。<strong>必须以终端输出的 <code>Local:</code> URL 为准</strong>,不要死盯一个固定端口。</p> </blockquote> <ol start="3"> <li><strong>可携带性门禁(必须)</strong><ul> <li>不依赖远程资源(字体/图片不要用 <code>https://...</code>)</li> <li>不在 UI/注释中泄露绝对路径(例如本机用户名/目录结构)</li> </ul> </li> </ol> <h4>可选增强(仅在你宣称 pixel-perfect 时才要求)</h4> <ul> <li>用截图 diff 做像素级对比(固定浏览器/viewport/DPR,禁用动画/过渡)。</li> <li>可选做 bundle sanity:<ul> <li><code>npx esbuild merged.jsx --bundle --format=esm --external:react --outfile=/tmp/merged.js</code></li> </ul> </li> </ul> <p>Use the bundled verifier:</p> <pre><code class="language-bash" data-language="bash">python3 scripts/verify_singlefile_jsx.py /path/to/YourMerged.jsx</code></pre><h3>Pixel-perfect visual regression (recommended, required if you claim pixel-perfect)</h3> <p>最稳妥的办法是把 <code>YourMerged.jsx</code> 暂时放回原 demo 工程里,用原工程的构建链路渲染它(保证字体/CSS 环境一致),然后用截图做像素 diff:</p> <ol> <li>在原工程增加一个“对照页/对照路由”(只用于本地验证):<ul> <li><code>OriginalApp</code>:原始页面</li> <li><code>MergedApp</code>:渲染 <code>YourMerged.jsx</code> 的默认导出</li> </ul> </li> <li>用同一套 Playwright 配置跑截图:<ul> <li>固定 viewport / <code>deviceScaleFactor</code></li> <li><code>prefers-reduced-motion: reduce</code></li> <li>注入一个 snapshot CSS(仅测试环境)来禁用过渡与动画,例如:<ul> <li><code>*,*::before,*::after{animation:none!important;transition:none!important;}</code></li> </ul> </li> </ul> </li> <li>用像素级阈值为 0(或极小阈值)做对比;若有差异,回到 CSS/字体/布局来源排查。</li> </ol> <p>注意:<code>npx playwright install</code> 等命令可能会下载浏览器二进制(供应链与联网风险)。如果你的环境要求离线/固定版本,请使用已安装的 Playwright 与固定浏览器版本。</p> <h2>Bundled Scripts</h2> <ul> <li><code>scripts/verify_singlefile_jsx.py</code>: Heuristic gate to catch non-React imports, <code>require()</code>, missing default export, and common TS residue.</li> <li><code>scripts/preview_single_jsx_vite.sh</code>: Spin up an isolated Vite dev server under <code>/tmp</code> and preview a single <code>.jsx</code> file with an ErrorBoundary and reliable URL output.</li> </ul> <h2>References</h2> <ul> <li><code>references/preview-and-smoke.md</code>: Preview pitfalls (port switching, cache reuse), smoke checklist, and troubleshooting playbook.</li> </ul>