@ amplifyup/ sdk
官方 JavaScript SDK 为 放大 ——事件跟踪,边缘解析的页面布局,个性化,以及您网站上的作曲家预览.
安装
npm install @amplifyup/sdk
快速启动( 生产)
生产是默认的。 你只需要一个 跟踪ID 从放大仪表板.
1. 提供者
import { AmplifyUpProvider } from '@amplifyup/sdk/react';
export default function RootLayout({ children }) {
return (
<AmplifyUpProvider config={{ trackingId: 'your-tracking-id' }}>
{children}
</AmplifyUpProvider>
);
}
2. 组成部分登记册
将您的下一个配置包成 withAmplifyUp它构建你的 组成部分登记册 从 src/components (或 components/ 当没有 src/每次都这样 next dev 或者说 next build 运行,并保持工作时的时速.
// next.config.js
const { withAmplifyUp } = require('@amplifyup/sdk/next');
module.exports = withAmplifyUp({
// your existing Next config
});
你从不手工注册组件。 添加组件意味着添加一个文件.
3. 页面内容
渲染器 <AmplifyPage /> 从生成的登记册中:
import { AmplifyPage } from '@/.amplifyup/component-registry';
export default function Page() {
return <AmplifyPage fallback={<YourStaticPage />} />;
}
4. 写一个组件
// src/components/Hero.tsx
import { Field } from '@amplifyup/sdk/react';
import type { Fields } from '@amplifyup/sdk/react';
export function Hero({ fields }: { fields: Fields<{ heading: string }> }) {
return (
<section>
<h1>
<Field field={fields.heading} />
</h1>
</section>
);
}
然后在编译器中连接内容 部署 所以现场可以提供页面服务.
这就是整个循环。 本节余为详.
构成部分登记册
SDK拥有如何找到和渲染布局组件. 您的站点提供组件; SDK 构建了搜索, 制作每个组件, 并给出上下文 Field, (中文(简体) ), Slot, (中文(简体) ), Image 财务报告和审定财务报表 RichText 读取。 无法支持自己写出搜索内容 .
如何找到组件
| 文件 | 登记名称 |
|---|---|
src/components/Hero.tsx | Hero |
src/components/Hero/index.tsx | Hero |
src/components/marketing/promo-card.tsx | promo-card |
- 每个文件都需要一个默认导出,或者一个与文件同名的命名导出( E)
export function Hero) (中文(简体) ). - 名称匹配 AmplifyUp 中的组件ID, 不论大小写和分隔符, 所以
Hero, (中文(简体) ),hero财务报告和审定财务报表HERO是同一个组成部分,同样promo-card财务报告和审定财务报表PromoCard。 。 。 . - 测试、故事和类型文件被跳过 。 不导出组件的文件在注册建立时列为跳过 .
- 解决同名文件的两个文件停止构建并命名两个文件.
设置
amplifyup.config.ts 您的项目根是可选的。 当组件居住在别处时使用 :
import { defineAmplifyConfig } from '@amplifyup/sdk/config';
export default defineAmplifyConfig({
components: {
roots: ['src/components', 'src/marketing'],
exclude: ['**/internal/**'],
},
});
| 设置 | 默认 |
|---|---|
roots | ['src/components'],或 ['components'] 无 src/ |
exclude | 无( 测试、 故事和类型文件总是被跳过) |
output | .amplifyup/component-registry 在您的第一个根旁边 |
生成的 .amplifyup 文件夹在 git 中忽略自身 。 不要编辑其内容.
没有下一个插件
SDK没有自己的命令. 对于 CI 步骤或非下一步构建, 安装 @amplifyup/cli 在你的建筑前运行它:
npm install --save-dev @amplifyup/cli
npx amplifyup registry # build once
npx amplifyup registry --watch # rebuild as files are added, renamed or removed
缺少组件
如果一个页面使用一个不包含在您注册簿中的组件, 作曲家会显示一个占位符命名它, 而该活页则不会取代它。 在您的组件文件夹中添加一个名称的文件 .
升级至0.3
0.3 删除 amplifyup 命令。 没有什么变化.
| 你用这个 | 做到这一点 |
|---|---|
withAmplifyUp 内 next.config | 没什么 插件仍然会构建您的注册 . |
amplifyup registry 脚本或 CI 步骤 | npm install --save-dev @amplifyup/cli同样的命令和旗帜现在来自那里. |
从0.1升级
0.2.0去除手写渲染. renderComponent 编辑 ComponentContextProvider 导出已去,并经过 renderComponent 丢出一个指向这里的错误.
- 删除您的组件查找对象和您
renderComponent函数. - 环绕
next.config与withAmplifyUp(见 构成部分登记册) (中文(简体) ). - 替换
<AmplifyPageContent renderComponent={…} />与<AmplifyPage />从@/.amplifyup/component-registry. 所有其他道具(fallback, (中文(简体) ),pageConfig,加载状态和空状态)保持不变. - 请检查每个组件文件名称是否与 AmplifyUp 中的组件ID相匹配(参见 如何找到组件) (中文(简体) ).
与 fields
每个组件都收到一个 fields 道具 (英语). 这不是原始的CMS数据——每个条目都是 字段信封数字 :
fields.heading
// → { value: 'Welcome to AmplifyUp', name: 'heading' }
fields.hero.image
// → { value: { url: '…', alt: '…' }, name: 'hero.image' }
fields.subheading // empty on a new page
// → { value: null, name: 'subheading' }
fields.posts.value[0].title // a row inside a list
// → { value: 'Ship faster', name: 'title', ref: { providerId, entity, id } }
name 是字段的点路径。 就是这样 <Field> 知其所入所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出所出. value 是内容, 从您的计划中键入 .
外形在活地上,在草稿上,和作曲器上都是一样的. 没有任何模式用于获取空字符串而不是信封,也没有模式用于空字段 undefined 改为 { value: null, name }。 。 。 .
写入目标
一个规则解释了编辑的一切:
一个字段如果并只有当它带有写目标时才能编辑 . 写入目标由数据制作者设定. 没有下游的猜测.
- A 类 页面字段 (单位:千美元)
fields.heading无ref。它的写目标为页面,而作曲家已经知道您在哪个页面. - A 类 列表行字段 (单位:千美元)
post.title载体ref: { providerId, entity, id }- 记录它属于。 编辑它保存到那个记录,而不是页面. - SDK无法追溯到制片人的一行字段 得不到
ref并标出readOnly: true它在作曲器中以纯文本形式制作:没有大纲,没有点击目标.
你从来没有设置 ref 你自己来 你也没读过 就是这样 <Field> 您在列表中的行中也使用页面标题.
两种读取字段的方法
// Editable — use an SDK component
<Field field={fields.heading} />
// Display only — read .value
<title>{fields.heading.value}</title>
在编译器中单击字段时使用组件。 使用 .value 当它不应该——元标记, aria-label,一个 href,一个条件,任何不可见的文本.
export function Card({ fields }: { fields: Fields<{ title: string; link: string; image: ImageValue }> }) {
return (
<a href={fields.link.value ?? '#'} aria-label={fields.title.value ?? undefined}>
<Image field={fields.image} />
<h3><Field field={fields.title} /></h3>
</a>
);
}
SDK 场组件
| 构成部分 | 字段类型 | 渲染器 |
|---|---|---|
<Field> | string, (中文(简体) ), number | (单位:千美元) <span> 带文字 |
<RichText> | 标记下/ 丰富文本 | 在一个 <div> (与 as(中文(简体) ) |
<Image> | ImageValue | <img> |
三个都拿A field 道具并读 name 从它。 传递任何额外的道具( E)className, (中文(简体) ), loading(等等),然后他们去完成 的元素.
<Field field={fields.eyebrow} className="text-xs uppercase" />
<RichText field={fields.body} className="prose" />
<Image field={fields.cover} className="rounded-lg" loading="lazy" />
<Field> 仅限scalar。 通过列表是一个类型错误 :
// ✗ Field<string[]> is not assignable to Field<string | number>
<Field field={fields.tags} />
空字段
一个空字段使 无 在现场和可点击的作曲器占位符上。 SDK从不发明默认副本.
// A new page, `subheading` not yet filled in:
<Field field={fields.subheading} />
// live: (nothing)
// Composer: clickable empty placeholder
如果你想要一个默认, 它是你的写, 它读自 .value数字 :
// ✓ your default, in your code
<p>{fields.subheading.value ?? 'Built for teams that ship.'}</p>
// ✓ hide the whole component when the key field is empty
export function Banner({ fields }: { fields: Fields<{ message: string }> }) {
if (!fields.message.value) return null;
return <div className="banner"><Field field={fields.message} /></div>;
}
为什么 fallback 道克:一个倒转的"这个字段从未有人"变成了"这个字段说: 欢迎" 没有人会注意到,除非顾客知道。 空应相相相相.
折叠元素
<Field> 使内线 <span>,从没有标题或段落,所以你提供周围的语义标记:
// ✓
<h1><Field field={fields.heading} /></h1>
<p className="lead"><Field field={fields.intro} /></p>
// ✗ — Field has no `as` prop; put the element around it
<Field as="h1" field={fields.heading} />
<RichText> 是例外:它拥有块元素,而且 as 选哪一个.
// ✓ — default is <div>
<RichText field={fields.body} className="prose" />
// ✓ — pick a different block element
<RichText field={fields.body} as="article" className="prose" />
// ✗ — don't nest a block element inside a paragraph
<p><RichText field={fields.body} /></p>
如果在字段为空时包装器会消失, 请检查 .value数字 :
{fields.intro.value ? (
<p className="lead"><Field field={fields.intro} /></p>
) : null}
计算值
有时你展示的并不是一个字段——它是从一个字段衍生出来的. 那你就通过 value 而且必须通过 name 因此作曲家知道点击编辑的字段:
// Show a formatted price but edit the raw number
<Field
value={formatPrice(fields.price.value)}
name={fields.price.name}
/>
name 仅与 value这是你写的地方.
// ✗ — redundant, and a type error
<Field field={fields.price} name="price" />
// ✗ — value without name has nothing to bind to
<Field value={formatPrice(fields.price.value)} />
在作曲器中以不同方式渲染
大部分部件都一样 有时活的标记无法托管一个可编辑的字段——文本是一个CSS背景,一个SVG <title>页:1 <meta> 标记,一个属性,或者它位于相互作用后方(一个闭合手风琴,一个自动演奏的旋转木马). 在作曲器中,您仍然需要作者可以点击的东西 .
useInComposer() 告诉你你在哪个模式:
import { Field, Image, useInComposer } from '@amplifyup/sdk/react';
export function HeroBackground({ fields }: { fields: Fields<{ heading: string; image: ImageValue }> }) {
const inComposer = useInComposer();
return (
<section
className="hero"
style={{ backgroundImage: `url(${fields.image.value?.url ?? ''})` }}
>
{/* Live: image is a CSS background. Composer: also render it so it's clickable. */}
{inComposer ? <Image field={fields.image} className="hidden" /> : null}
<h1><Field field={fields.heading} /></h1>
</section>
);
}
其他常见案件:
// Attribute on the live site, editable in Composer
const inComposer = useInComposer();
return (
<>
<button aria-label={fields.label.value ?? undefined}>
<Icon />
{inComposer ? <Field field={fields.label} /> : null}
</button>
</>
);
// Component that hides itself when empty on live, but stays visible for authors
export function Announcement({ fields }: { fields: Fields<{ message: string }> }) {
const inComposer = useInComposer();
if (!fields.message.value && !inComposer) return null;
return <div className="announcement"><Field field={fields.message} /></div>;
}
// Interaction that fights the canvas — turn it off in Composer
export function Carousel({ fields }) {
const inComposer = useInComposer();
return (
<>
{(fields.slides.value ?? []).map((slide) => (
<Slide key={slide.id} slide={slide} autoplay={!inComposer} />
))}
</>
);
}
拇指规则 :
- 这个 字段组件在作曲器中是真理的来源. 如果一个字段可以被作者看到,则必须通过
<Field>编号 :<RichText>编号 :<Image>即使现场读取.value。 。 。 . - 别把整个部件都叉了. 区分一个不同的要素,而不是整个返回。 两棵不同的树漂流,预览会停止匹配出产.
- 空格法用途
inComposer。 。 。 .if (!x.value) return null他们不能点击没有显示的东西。 添加&& !inComposer。 。 。 . - 不要用它来隐藏可编辑的文本在直播上. 如果文本在现场站点上可见,则在两种模式中将其与字段部分并列.
useInComposer是为了满足 现场标记 真正不能暴露为田地.
// ✗ — heading is visible on live; no reason to fork
{inComposer ? <Field field={fields.heading} /> : fields.heading.value}
// ✓
<Field field={fields.heading} />
名单
每个对象阵列都是记录列表. 每行都是字段对象加平原 id编辑 post.title 在网格中,您正在编辑文章。 哪些记录在列表中,以及它们的顺序,在道具面板中被编辑——不在画布上.
Scalar 列表( 标签, 多选择) 保持平原 : fields.tags 实值 Field<string[]>。 。 。 .
import { Field, Image, RichText } from '@amplifyup/sdk/react';
export function LatestPosts({ fields }: { fields: Fields<{ heading: string; posts: Post[] }> }) {
const posts = fields.posts.value ?? [];
return (
<section>
<h2><Field field={fields.heading} /></h2>
<ul>
{posts.map((post) => (
<li key={post.id}>
<Image field={post.mainImage} />
<a href={`/blog/${post.slug}`}>
<Field field={post.title} />
</a>
</li>
))}
</ul>
</section>
);
}
// ✗ — Field expects a scalar, not the list or the row
<Field field={fields.posts} />
<Field field={post} />
// ✓ — edit the record through its fields
<Field field={post.title} />
<Image field={post.mainImage} />
搜索或加载多行 queryContent 形状相同。 使其具有相同的组件——两种部件的一个渲染器:
const posts = hits ?? fields.posts.value ?? [];
return posts.map((post) => (
<article key={post.id}>
<h2><Field field={post.title} /></h2>
</article>
));
可编辑行时
当SDK知道它来自哪个唱片时,一行是可以编辑的. 这在两种情况下是自动发生的:
| 单行来自 | 可用作曲器编辑吗 ? |
|---|---|
| 您组件上的列表道具( 已校验或查询连接) | 对 |
queryContent 使用已公布的光谱 {field}Pagination.spec | 对 |
queryContent 用手组装的光谱 丢失 providerId 或者说 entity | 否- 以纯文本形式制作 |
| 你从CMS中取出并传递到的道具 | 否- 以纯文本形式制作 |
只读行仍然在现场和作曲器上渲染出自己的文字. 他们只是不能点击, 在开发 SDK 日志一次每个字段:
[AmplifyUp SDK] title has no write target; rendered read-only.
如果您看到这个并期望一个可编辑的字段, 固定是上游的—— 使用已公布的光谱而不是手建的光谱, 或者将列表绑定在作曲器中的连接上 。 不尝试添加 ref 你自己来.
// ✓ — spec comes from the list prop Composer published; rows stay editable
const hits = await queryContent({
trackingId,
route: '/insights',
spec: searchSpec(postsPagination, 'title', term),
});
// ✗ — hand-built spec with no connection behind it; rows render read-only
const hits = await queryContent({
trackingId,
route: '/insights',
spec: { providerId: '', entity: '', filter: [], sort: [], limit: 10, offset: 0 },
});
槽数
一个槽是作者放弃其他组件的区域. 把它给 <Slot>数字 :
import { Slot } from '@amplifyup/sdk/react';
export function TwoColumn() {
return (
<div className="grid grid-cols-2">
<div><Slot name="left" /></div>
<div><Slot name="right" /></div>
</div>
);
}
<Slot> 仅使用 name 财务报告和审定财务报表 className。SDK在制作时将每个组件的槽内容交给它,所以你从不线程 slots 通过您的组件 .
// ✗ — Slot has no `slots` prop
<Slot name="left" slots={slots} />
// ✓
<Slot name="left" />
槽名必须匹配在 AmplifyUp 中组件上声明的槽. 槽中含有组件, 而不是字段—— 槽中的任何内容都无法读取 fields。 。 。 .
这样做/不要
| 执行 | 别 |
|---|---|
<Field field={fields.heading} /> | <Field field={fields.heading} name="heading" /> |
{fields.heading.value} 非可见用途 | {fields.heading} 作为JSX儿童(这是一个物体) |
{fields.x.value ?? 'default'} 在代码中 | 期望 SDK 提供默认副本 |
if (!fields.x.value) return null 隐藏组件 | 关闭 Field 在编译器中隐藏字段的有条件逻辑中 |
<h1><Field … /></h1> | 寻找一个 as 编号 : wrapper 道具 |
<Field field={post.title} /> 列表中 | <Field field={fields.posts} /> 或者说 <Field field={post} /> |
value + 键 name 一起计算产出 | value 独自一人,或 name 一个人 |
<Image field={fields.cover} /> | <img src={fields.cover.value.url} /> 何时可以编辑 |
queryContent 横行 <Field field={post.title} /> | 将查询行作为原始 CMS 对象 |
spec: searchSpec(postsPagination, …) 从已公布的列表道克 | 手建一个 spec 并期望行保持可编辑 |
<Slot name="left" /> | <Slot name="left" slots={slots} /> |
让制片人设定 ref | 设定、复制或补丁 ref 你自己来 |
useInComposer() 以显示一个无法显示的字段( CSS 背景、 属性、 隐藏面板) | 整个部件的回放 inComposer |
if (!x.value && !inComposer) return null | if (!x.value) return null ——作者不能点击未渲染的内容 |
主机消息
在开发中,SDK会告诉你什么是错的。 生产无声.
| 消息 | 这是什么意思 | 修补 |
|---|---|---|
Field expects a scalar field. Did you mean <Field field={post.title} />? | 你通过了一个名单或一整行 Field 编号 : RichText 编号 : Image | 从一行取出一个平板球 |
title has no write target; rendered read-only. | SDK 无法追踪记录的行字段 | 将列表绑入连接, 或与已公布的光谱查询 |
Field value={…} requires name="…" for computed values | 你通过了 value 无 name | 添加 name={fields.x.name} |
Field needs field={fields.yourProp} (or value + name for computed values) | 没有约束力 | 过 field= |
Slot "left" only works inside a component rendered from your component registry | Slot 用于SDK未渲染的组件中 | 使用 Slot 只有组件文件夹中的组件 |
No component in your component registry matches "Hero" | 页面使用您网站没有的组件 | 添加 Hero.tsx (或匹配名称)到您的组件文件夹 |
renderComponent was removed in @amplifyup/sdk 0.2.0 | 0.1的代码仍在传递渲染函数 | 跟随 从0.1升级 |
类型
import type { Field, Fields, ImageValue } from '@amplifyup/sdk/react';
type Field<T> = {
value: T;
name: string;
/** Write target — the record this field saves to. Page fields omit it. */
ref?: { providerId: string; entity: string; id: string };
/** Row field with no write target — display only in Composer. */
readOnly?: true;
};
type Fields<T> = { [K in keyof T]: /* Field<T[K]>, recursing into objects and lists */ };
在组件上宣布一次图案形状
type HeroFields = Fields<{
eyebrow: string;
heading: string;
body: string; // markdown → use <RichText>
image: ImageValue; // → use <Image>
posts: Post[]; // list — each row is Fields<Post> & { id: string }
tags: string[]; // scalar list — Field<string[]>
}>;
export function Hero({ fields }: { fields: HeroFields }) { … }
fields.heading.value 实值 string。 。 。 . fields.image.value 实值 ImageValue | null。 。 。 . fields.posts.value[0] 实值 Fields<Post> & { id: string }。 。 。 . fields.posts.value[0].title 实值 Field<string> 与 ref 指着那个位置.
内容来源
| 模式 | 来源 | 何时 |
|---|---|---|
| 制作访问者 | 边缘 /v1/resolve | 默认 |
| Next.js SSG / 简要记录 | 边缘 /v1/routes 接下来 /v1/resolve | generateAmplifyStaticParams + 键 fetchPageConfigServer |
| 作曲家预览 | 控制平面布局提供者 | ?preview=true / 作曲家 Iframe |
| 运行时间查询 | 边缘 /v1/query | queryContent - 搜索,加载,拾取者 |
当SDK读取"边缘"的响应时,现场信封会被附上一次. 边缘的JSON是生的,你永远不会看到它 如果你走过 AmplifyPageContent 或者说 fetchPageConfigServer。如果你拨打 fetchEdgeResolve 直接获得原始道具,必须亲自绘制地图——更喜欢有文件记载的路径.
图案页面(从页面)
使用作曲器的组件 从页面 需求 pageContext 关于提供者(或 init),所以第一个颜料和第一个页面视图包括了字段. 迟到 setPageContext 对于以后的事件,而不是制造这些组件,是罚款的.
resource.key 是 URL 参数值。 如果映射的实体字段( 例如 ) post.slug)不匹配该参数,SDK警告并使用URL参数.
<AmplifyUpProvider
config={{ trackingId: 'your-tracking-id' }}
pageContext={{
resourceType: 'post',
key: slug,
fields: { title: post.title, slug: post.slug, category: post.category },
}}
>
{children}
</AmplifyUpProvider>
过 pageContext 在From页面组件的提供者/init。 别等到第一次油漆之后 当 pageContext 省略.
SDK将边缘的溶解树映射到 layoutTree 并让它。 Edge已经应用了个性化和CMS投影——网站不调用CMS或Decision API来进行布局. 画完后,就打给大家 POST /v1/select (或 selectVariants()) 那么,(笑声) /v1/resolve?variants= 因此,决定处于边缘(clientPersonalization) (中文(简体) ).
将您的整个网站包入 AmplifyUpProvider 所以每一页都有追踪 覆盖只运行在渲染的页面上 AmplifyPageContent (或 AmplifyRenderer 无 pageConfig) (中文(简体) ). 自己创建的页面会被跟踪,但从不要求布局.
运行时间查询
页面解析涵盖部署时已知的数据。 对于访问者要求的数据——搜索,加载更多,取取取器——使用 queryContent。浏览器询问 AmplifyUp, AmplifyUp 询问您的内容源, 因此您的网站中没有 CMS 证书或查询语法 .
标记查询连接 标注 在编译器和列表中的 prop 增益 pagination meta 中,包括已公布的查询光谱 :
'use client';
import { queryContent, nextPageSpec, searchSpec } from '@amplifyup/sdk/react';
// next page
const more = await queryContent({
trackingId,
route: '/insights',
spec: nextPageSpec(postsPagination),
});
// search — keep `?q=` as input only; do not read searchParams on the server page
const hits = await queryContent({
trackingId,
route: '/insights',
spec: searchSpec(postsPagination, 'title', term),
});
只有发布该路径的实体才能被询问,访问者只看到发布的内容. 也可查阅: @amplifyup/sdk/server见 运行时间查询指南。 。 。 .
总是从 postsPagination.spec。 。 。 . nextPageSpec 财务报告和审定财务报表 searchSpec 这样做你。 该 spec 识别连接, 所以返回的行可以在 Composer 中编辑 —— 见 可编辑行时。用手组装的光谱后面没有连接,所以它的行只读即可返回.
跟踪活动
import { init, page, track, identify } from '@amplifyup/sdk';
await init({ trackingId: 'your-tracking-id' });
page('/home');
track('Product Viewed', { productId: '123' });
identify('user-123', { email: '[email protected]' });
访客同意
所以形式答案只留给同意的访客:
import { setConsent } from '@amplifyup/sdk';
// When the visitor accepts or rejects in your banner:
setConsent('granted'); // or 'denied'
// To forget the choice:
setConsent(null);
选择在浏览器中被记住,并随每个表单提交而发送. 然后打开 我同意的旗子上写着"放大" 在表格中, 直到你这样做, 放大UP对待一个访客 谁同意与谁没有.
调试
AmplifyUp.getStatus()
反应上下文 : useAmplifyUp() → { pageConfig, loading, error, source, meta }。 。 。 .
许可证
麻省理工学院