关于 weaverse-integration
面向尚未接入 Weaverse 的 Hydrogen 项目,指导开发者完成现状分析、SDK 配置、组件转换、路由迁移与验证。强调沿用项目既有代码风格,并可分阶段迁移。
主要功能
- 01分析 Hydrogen 版本与项目结构
- 02配置 Weaverse SDK 和环境变量
- 03将组件转换为可编辑区块
- 04注册区块并迁移路由
- 05按需逐步迁移页面
适用场景
- 01为现有 Hydrogen 商店接入 Weaverse
- 02将促销横幅等组件转换为可编辑区块
- 03逐步为首页和商品页启用 Weaverse
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ weaverse/ shopify-hydrogen-skills --skill weaverse-integration安装后在 Claude Code 中输入/weaverse-integration确认已加载。
详细介绍
Weaverse 集成 — 现有 Hydrogen 项目
目的: 将 Weaverse 集成到当前尚未使用它的 Hydrogen 项目中。本指南将引导你完成整个流程:分析 → SDK 设置 → 组件转换 → 路由迁移 → 验证。
核心原则: 尊重现有项目。遵循其编码风格、模式和约定。不要重构已有的可用代码,而是通过 Weaverse 对其进行扩展。
实时文档
在本技能所在的文件夹中运行以下命令。辅助工具包含在同级的
shopify-hydrogen 技能中,只要安装了完整工具包,../shopify-hydrogen/scripts/ 就会存在。
如果你单独安装了本技能,则该文件夹不存在,以下命令会因 MODULE_NOT_FOUND 而失败。请添加一次辅助工具:
npx skills add Weaverse/shopify-hydrogen-skills --skill shopify-hydrogen
本技能没有 references/ 目录,因此以下阶段是本技能仅有的离线指南;实时文档命令则是可选补充。
# Always fetch latest docs before making decisions
node ../shopify-hydrogen/scripts/search_weaverse_docs.mjs "existing hydrogen integration"
node ../shopify-hydrogen/scripts/get_weaverse_page.mjs "migration-advanced/existing-hydrogen-integration"
node ../shopify-hydrogen/scripts/search_shopify_docs.mjs "createHydrogenContext"
阶段 1:分析现有项目
动手修改任何代码之前,先阅读并记录项目的当前状态。
1.1 检测框架版本
# Check package.json
cat package.json | grep -E '"react-router"|"@remix-run"|"@shopify/hydrogen"|"@weaverse"'
| 检测结果 | 要安装的 Weaverse 版本 |
|---|---|
react-router + @shopify/[email protected]+ | @weaverse/hydrogen@latest(v5,React Router v7) |
@remix-run/react | @weaverse/hydrogen@4(v4,Remix) |
如果项目使用 Remix(早于 2025.5.0):
- 先询问用户是否要迁移到 React Router v7,然后再集成 Weaverse v5
- 或在当前 Remix 配置中集成 Weaverse v4
- 推荐迁移到 v7——这是未来的发展方向
1.2 梳理代码库
阅读以下文件并记录其模式:
| 文件 | 观察内容 |
|---|---|
package.json | 依赖项、脚本、包管理器(npm/yarn/pnpm/bun) |
app/root.tsx | 布局结构、提供程序、全局样式 |
app/entry.server.tsx | CSP 设置、渲染方式 |
server.ts(或 server/index.ts) | 上下文创建、中间件 |
app/routes/** | 路由结构、loader 模式、命名约定 |
app/components/** | 组件模式、props、样式方案 |
app/sections/** | 如果存在 sections,了解其结构 |
app/styles/** 或 tailwind.config.* | 样式方案(Tailwind、CSS modules、styled-components 等) |
vite.config.ts | 构建插件、别名 |
.env 或 .env.example | 现有环境变量(不要读取密钥) |
tsconfig.json | 路径别名、严格性设置 |
1.3 记录编码约定
记录并保留以下模式:
- 组件风格: 函数声明、箭头函数还是
forwardRef - 导出风格:
export default、命名导出还是export let - 样式: Tailwind 类、CSS modules、styled-components、内联样式
- 状态管理: Hooks、上下文、存储
- 数据获取: Loader 模式、GraphQL 查询结构、错误处理
- 文件命名: kebab-case、PascalCase、camelCase
- 导入风格: 绝对路径(
~/)还是相对路径,命名空间导入还是默认导入 - TypeScript: 严格性、interface 还是 type、泛型用法
- 注释: 文档说明的详细程度、JSDoc 用法
⚠️ 重要: 转换后的 Weaverse 组件必须遵循这些约定。如果项目使用
function声明,就不要使用const X = () =>。如果项目使用 Tailwind,就不要引入 CSS modules。
1.4 找出可转换的组件
找出适合作为 Weaverse sections 的组件:
适合的组件:
- Hero 横幅/轮播图
- 精选系列/商品网格
- 用户评价/评论
- 新闻简报订阅
- 促销横幅
- 自定义内容区块(图片 + 文本、视频等)
- 页脚区块(新闻简报、社交链接)
- 内容可编辑的导航组件
跳过(不要转换):
- 布局基础组件(Button、Input、Modal——保留为共享组件)
- 路由级包装器
- 购物车/结账逻辑组件
- 分析/跟踪组件
- 服务端工具
第 2 阶段:安装和配置 Weaverse SDK
2.1 安装软件包
# Use whatever package manager the project already uses
# React Router v7 (Hydrogen 2025.5.0+)
npm install @weaverse/hydrogen@latest
# Remix (legacy)
npm install @weaverse/hydrogen@4
2.2 环境变量
添加到 .env:
WEAVERSE_PROJECT_ID="provided-by-user"
WEAVERSE_API_KEY="provided-by-user"
同时添加到 .env.example(不填写真实值),并更新 TypeScript 环境类型:
// Add to the Env type in env.d.ts or wherever the project defines it
WEAVERSE_PROJECT_ID: string;
WEAVERSE_API_KEY: string;
WEAVERSE_HOST?: string;
2.3 创建 app/weaverse/ 目录
创建以下 5 个文件。遵循项目现有的代码风格(导出方式、命名、引号、分号)。
app/weaverse/schema.server.ts — 主题架构
import type { HydrogenThemeSchema } from "@weaverse/hydrogen";
import pkg from "../../package.json";
export let themeSchema: HydrogenThemeSchema = {
info: {
version: pkg.version,
author: "Your Store Name",
name: "Your Theme Name",
},
settings: [
// Start minimal — add more as you convert components
{
group: "Colors",
inputs: [
{
type: "color",
name: "colorPrimary",
label: "Primary Color",
defaultValue: "#000000",
},
{
type: "color",
name: "colorBackground",
label: "Background Color",
defaultValue: "#ffffff",
},
{
type: "color",
name: "colorText",
label: "Text Color",
defaultValue: "#1a1a1a",
},
],
},
{
group: "Typography",
inputs: [
{
type: "range",
name: "headingBaseSize",
label: "Heading Base Size",
configs: { min: 14, max: 32, step: 1, unit: "px" },
defaultValue: 28,
},
{
type: "range",
name: "bodyBaseSize",
label: "Body Base Size",
configs: { min: 12, max: 20, step: 1, unit: "px" },
defaultValue: 16,
},
],
},
],
};
app/weaverse/style.tsx — 全局样式
阅读项目现有的全局样式(CSS 文件、Tailwind 配置、根布局),并创建能与现有设置集成的 CSS 变量。
import { useThemeSettings } from "@weaverse/hydrogen";
export function GlobalStyle() {
let settings = useThemeSettings();
if (!settings) return null;
return (
<style
id="weaverse-global-style"
key="weaverse-global-style"
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: `
:root {
--color-primary: ${settings.colorPrimary};
--color-bg: ${settings.colorBackground};
--color-text: ${settings.colorText};
--heading-base: ${settings.headingBaseSize}px;
--body-base: ${settings.bodyBaseSize}px;
}
`,
}}
/>
);
}
app/weaverse/components.ts — 组件注册表
import type { HydrogenComponent } from "@weaverse/hydrogen";
// Import converted sections here — start empty, add as you convert
// MUST use namespace imports: import * as X, NOT import X from
export let components: HydrogenComponent[] = [
// Sections will be added during Phase 3
];
app/weaverse/index.tsx — WeaverseContent
import { WeaverseHydrogenRoot } from "@weaverse/hydrogen";
import { components } from "./components";
export function WeaverseContent() {
return (
<WeaverseHydrogenRoot
components={components}
errorComponent={GenericError}
/>
);
}
如果项目已有错误组件,请使用该组件;否则创建一个精简的 GenericError。
app/weaverse/csp.ts — 内容安全策略
export function getWeaverseCsp(request: Request, context: any) {
let url = new URL(request.url);
let weaverseHost = context.env?.WEAVERSE_HOST || "https://weaverse.io";
let isDesignMode = url.searchParams.get("weaverse_design_mode") === "true";
let weaverseHosts = [
new URL(weaverseHost).host,
"weaverse.io",
"*.weaverse.io",
"shopify.com",
"*.shopify.com",
"*.myshopify.com",
];
let updatedCsp: Record<string, string[] | string | boolean> = {
frameAncestors: weaverseHosts,
defaultSrc: ["'self'", "data:", ...weaverseHosts],
scriptSrc: ["'self'", "'unsafe-inline'", ...weaverseHosts],
styleSrc: ["'self'", "'unsafe-inline'", ...weaverseHosts],
connectSrc: ["'self'", ...weaverseHosts],
};
if (isDesignMode) {
updatedCsp.frameAncestors = ["*"];
}
return updatedCsp;
}
2.4 更新服务器上下文
修改现有的上下文文件(通常是 server.ts 或 app/lib/context.ts):
// ADD these imports
import { WeaverseClient } from "@weaverse/hydrogen";
import { themeSchema } from "~/weaverse/schema.server"; // adjust path alias
import { components } from "~/weaverse/components";
// INSIDE createAppLoadContext, after hydrogenContext is created:
return {
...hydrogenContext,
weaverse: new WeaverseClient({
...hydrogenContext,
request,
cache,
themeSchema,
components,
}),
};
不要重构其上下文创建逻辑——只需将 Weaverse 客户端展开到现有的返回对象中。
2.5 更新根文件和入口文件
app/root.tsx — 使用 withWeaverse 包装
import { withWeaverse } from "@weaverse/hydrogen";
import { GlobalStyle } from "~/weaverse/style";
// Keep their existing Layout/App components untouched
// Only change: wrap the default export
// BEFORE:
// export default function App() { ... }
// AFTER:
function App() {
// ... their existing App code, unchanged
}
export default withWeaverse(App);
在 Layout 组件的 <head> 内添加 <GlobalStyle />。
app/entry.server.tsx — 添加 Weaverse CSP
import { getWeaverseCsp } from "~/weaverse/csp";
// Inside handleRequest, merge Weaverse CSP with existing:
const { nonce, header, NonceProvider } = createContentSecurityPolicy({
...getWeaverseCsp(request, context),
shop: {
checkoutDomain: context.env?.PUBLIC_CHECKOUT_DOMAIN || context.env?.PUBLIC_STORE_DOMAIN,
storeDomain: context.env?.PUBLIC_STORE_DOMAIN,
},
});
与现有的 CSP 配置合并,不要替换它。
第 3 阶段:将现有组件转换为 Weaverse 区块
这是最重要的阶段。将用户现有的组件转换为可通过 Weaverse 编辑的区块,同时保留其完全一致的视觉效果和行为。
3.1 转换策略
对于每个可转换的组件:
- 阅读原始组件——了解属性、状态、数据获取和样式
- 创建 Weaverse 包装器——添加架构和可选的加载器
- 保留原始渲染——不要重写 JSX,只需将其包装起来
- 在 components.ts 中注册——添加命名空间导入
3.2 转换模式
原始组件(转换前):
// app/components/hero-banner.tsx
interface HeroBannerProps {
heading: string;
subtitle: string;
backgroundImage: string;
alignment: "left" | "center";
}
function HeroBanner({ heading, subtitle, backgroundImage, alignment }: HeroBannerProps) {
return (
<section className="relative bg-cover bg-center" style={{ backgroundImage: `url(${backgroundImage})` }}>
<div className={`flex flex-col ${alignment === 'center' ? 'items-center text-center' : 'items-start'}`}>
<h1 className="text-4xl font-bold">{heading}</h1>
<p className="text-xl mt-4">{subtitle}</p>
</div>
</section>
);
}
export default HeroBanner;
转换后的组件:
// app/sections/hero-banner.tsx (or keep original path, just add schema)
import type { HydrogenComponentProps } from "@weaverse/hydrogen";
import { createSchema } from "@weaverse/hydrogen";
interface HeroBannerProps extends HydrogenComponentProps {
heading: string;
subtitle: string;
backgroundImage: string;
alignment: "left" | "center";
}
function HeroBanner({ heading, subtitle, backgroundImage, alignment, children, ...rest }: HeroBannerProps) {
return (
<section {...rest} className="relative bg-cover bg-center" style={{ backgroundImage: `url(${backgroundImage})` }}>
<div className={`flex flex-col ${alignment === 'center' ? 'items-center text-center' : 'items-start'}`}>
<h1 className="text-4xl font-bold">{heading}</h1>
<p className="text-xl mt-4">{subtitle}</p>
</div>
{children}
</section>
);
}
export default HeroBanner;
export let schema = createSchema({
type: "hero-banner",
title: "Hero Banner",
settings: [
{
group: "Content",
inputs: [
{ type: "text", name: "heading", label: "Heading", defaultValue: "Welcome" },
{ type: "textarea", name: "subtitle", label: "Subtitle" },
{ type: "image", name: "backgroundImage", label: "Background Image" },
{
type: "toggle-group",
name: "alignment",
label: "Alignment",
configs: {
options: [
{ value: "left", label: "Left" },
{ value: "center", label: "Center" },
],
},
defaultValue: "center",
},
],
},
],
presets: {
heading: "Welcome",
alignment: "center",
},
});
主要变更:
- 扩展
HydrogenComponentProps(添加children、loaderData和 Weaverse 内部属性) - 在根元素上展开
{...rest}(Studio 交互所必需) - 如果组件可以接受嵌套区块,则渲染
{children} - 使用
createSchema()添加schema导出 - 保持 JSX 结构和样式完全一致
3.3 架构设计规则
为转换后的组件创建架构时:
| 原始属性类型 | 架构输入类型 |
|---|---|
string(短文本) | text |
string(长文本) | textarea 或 richtext |
string(URL) | url |
string(图片路径) | image |
string(十六进制颜色) | color |
number | range(设置适当的最小值、最大值和步长) |
boolean | switch |
string(枚举) | select 或 toggle-group |
| 包含 Shopify 数据的对象 | product、collection、blog 等 |
按逻辑对设置分组——根据组件的用途进行分组,通常包括:
- “Content”组——文本、图片、链接
- “Design”组——颜色、间距、布局选项
- “Advanced”组——可见性、自定义 CSS 类
3.4 转换数据获取组件
如果原始组件会获取数据(例如,精选商品集合),请将数据获取转换为 Weaverse 加载器:
import type { ComponentLoaderArgs } from "@weaverse/hydrogen";
type SectionData = {
collectionHandle: string;
count: number;
};
export let loader = async ({ weaverse, data }: ComponentLoaderArgs<SectionData>) => {
if (!data?.collectionHandle) return null;
return weaverse.storefront.query(COLLECTION_QUERY, {
variables: { handle: data.collectionHandle, first: data.count ?? 8 },
});
};
使用项目现有的 GraphQL 查询和片段,不要重写。如果项目有包含共享查询的 graphql/ 目录,请从该目录导入。
3.5 注册转换后的组件
更新 app/weaverse/components.ts:
import * as HeroBanner from "~/sections/hero-banner";
import * as FeaturedCollection from "~/sections/featured-collection";
// ... other converted components
export let components: HydrogenComponent[] = [
HeroBanner,
FeaturedCollection,
// ...
];
必须使用 import * as X,不要使用 import X from。这是最常见的错误。
第 4 阶段:更新路由
对于每个应支持 Weaverse 编辑的路由:
4.1 路由转换模式
// BEFORE — original route
import { useLoaderData } from "react-router"; // or "@remix-run/react"
import HeroBanner from "~/components/hero-banner";
import ProductGrid from "~/components/product-grid";
export async function loader({ context, params }: LoaderFunctionArgs) {
const productData = await context.storefront.query(PRODUCT_QUERY, { ... });
return { product: productData.product };
}
export default function Homepage() {
const { product } = useLoaderData<typeof loader>();
return (
<div>
<HeroBanner heading="Sale" subtitle="Big deals" />
<ProductGrid products={product} />
</div>
);
}
// AFTER — Weaverse-enabled route
import { WeaverseContent } from "~/weaverse";
export async function loader({ context, params }: LoaderFunctionArgs) {
// Keep existing data loading
const productData = await context.storefront.query(PRODUCT_QUERY, { ... });
// ADD Weaverse page loading
const weaverseData = await context.weaverse.loadPage({ type: "INDEX" });
return {
product: productData.product,
weaverseData,
};
}
export default function Homepage() {
// Render Weaverse content — it will use the registered components
return <WeaverseContent />;
}
4.2 页面类型
| 路由 | type 值 |
|---|---|
首页(_index) | "INDEX" |
| 产品页面 | "PRODUCT"(带有 handle: params.productHandle) |
| 产品系列页面 | "COLLECTION"(带有 handle: params.collectionHandle) |
| 产品系列列表 | "ALL_PRODUCTS" |
| 博客 | "BLOG" |
| 文章 | "ARTICLE"(带有 handle: params.articleHandle) |
| 自定义页面 | "PAGE"(带有 handle: params.pageHandle) |
| 自定义模板 | "CUSTOM" |
4.3 逐步迁移(推荐)
无需一次性转换所有路由。请从以下内容开始:
- 首页 — 影响最大,通常也最简单
- 产品页面 — 对商家的价值很高
- 产品系列页面 — 支持视觉陈列
- 其他页面 — 按需转换
对于尚未转换的路由,保持原样即可。Weaverse 不会干扰非 Weaverse 路由。
阶段 5:处理边缘情况
5.1 现有布局组件
如果项目有共享布局组件(页眉、页脚、导航):
- 页眉/页脚 — 暂时将其保留在 Weaverse 外部,在
root.tsxLayout 中渲染 - 或者,如果用户希望能够编辑这些组件,则将它们转换为 Weaverse 组件
- 询问用户倾向于采用哪种方式
5.2 共享组件(按钮、输入框等)
不要转换基础/共享组件。将它们保留在 app/components/ 中,并在 Weaverse 区块中使用。
5.3 第三方集成
如果项目使用:
- 分析工具(GA4、Segment 等)— 保留在路由加载器或根布局中
- A/B 测试 — 保留在 Weaverse 页面渲染之外
- 自定义中间件 — 确保 Weaverse CSP 不会阻止第三方脚本
- CMS 数据(Contentful、Sanity 等)— 可以共存;Weaverse 负责视觉布局,外部 CMS 负责结构化内容
5.4 TypeScript 类型
将以下内容添加到项目的类型定义中:
// Wherever the project defines AppLoadContext
interface AppLoadContext {
// ... existing properties
weaverse: import("@weaverse/hydrogen").WeaverseClient;
}
阶段 6:验证清单
集成后,请验证:
-
npm run dev启动时没有错误 - 现有路由仍能正确渲染(视觉回归检查)
- Weaverse Studio 预览可通过本地开发 URL 加载
- 转换后的组件显示在 Weaverse Studio 组件选择器中
- Studio > Theme > Customize 中显示主题设置
- 转换后的组件渲染结果与原组件完全一致
- 组件架构暴露了正确的可编辑设置
- 数据获取区块能正确加载数据
-
npm run build执行完成且没有错误 - TypeScript 编译时没有新增错误
- 购物车、结账和客户账户流程仍可正常使用
- 控制台中没有与 CSP 相关的错误
应避免的常见错误
| 错误 | 修复方法 |
|---|---|
使用 import X from 而非 import * as X | 注册组件时始终使用命名空间导入 |
忘记在根元素上使用 {...rest} | Weaverse Studio 的拖放交互需要此属性 |
未在容器组件中渲染 {children} | 嵌套区块需要此属性 |
| 覆盖现有 CSP,而不是合并 | 将 Weaverse CSP 展开并合并到现有配置中 |
| 重写组件 JSX,“让它更好” | 保持原有渲染完全不变 |
| 一次性转换所有组件 | 先转换区块,将基础组件保留为共享组件 |
在架构中使用 inspector | 使用 settings — inspector 已弃用 |
在 app/ 外创建 app/weaverse/ | 必须位于 app 目录内 |
| 忘记在类型定义中添加环境变量 | 将 WEAVERSE_* 添加到 Env 类型中 |
快速参考 — 文件变更摘要
| 文件 | 操作 |
|---|---|
package.json | 添加 @weaverse/hydrogen 依赖 |
.env | 添加 WEAVERSE_PROJECT_ID、WEAVERSE_API_KEY |
app/weaverse/schema.server.ts | 创建 — 主题架构 |
app/weaverse/style.tsx | 创建 — 全局样式组件 |
app/weaverse/components.ts | 创建 — 组件注册表 |
app/weaverse/index.tsx | 创建 — WeaverseContent 包装器 |
app/weaverse/csp.ts | 创建 — CSP 配置 |
server.ts(或 context 文件) | 修改 — 将 WeaverseClient 添加到 context |
app/root.tsx | 修改 — 使用 withWeaverse 包装,添加 <GlobalStyle /> |
app/entry.server.tsx | 修改 — 合并 Weaverse CSP |
app/sections/*.tsx | 创建/修改 — 转换后的组件 |
| Route 文件 | 修改 — 添加 weaverse.loadPage(),渲染 <WeaverseContent /> |
| 类型定义 | 修改 — 添加 WEAVERSE_* 环境变量、weaverse context |
.gitignore | 验证 — 确认 .react-router/ 已被忽略(RRv7 项目) |