---
order: 7
section: features
title: 静态资源
description: 使用 field.asset() 声明带有校验的静态资源字段，限制路径和格式，并在读取时将其解析为公开可访问的 URL。
noindex: false
canonicalPath: /zh-CN/docs/assets
---
Scribe 将静态资源（目前主要是图片）视为一等公民，采用基于 schema 声明的引用，而不是松散的字符串。其核心原则是：**组件绝不能通过字符串拼接来构建资源 URL。** Frontmatter 存储规范的*源引用*，运行时则在加载时将其解析为*实际提供的 URL*。正是这种单一的中间层机制，使得 `publicPath`、CDN 以及内容哈希都可以通过配置来引入，无需修改代码库。

## Configuration

`assets` 配置组声明了源文件的存放位置及其提供方式。之前简单的 `assetsDir` 仍然可以作为 `{ dir: assetsDir }` 的弃用别名继续使用。

```ts
export default defineConfig({
  // ...
  assets: {
    dir: "public",            // 源文件在磁盘上的存放位置，相对于 rootDir。默认为 "public"。
    publicPath: "/",          // 提供服务时的 URL 前缀。可以是一个路径（"/static/"）或 CDN 源。默认为 "/"。
    managedDirs: ["/blog-images"], // 额外的由 Scribe 管理的 Web 路径根目录，不属于任何 field.asset() 覆盖范围
  },
});
```

`publicPath` 遵循 webpack/Vite 中对该概念的约定。`managedDirs` 条目是**源根目录**：即在 MDX 正文中引用，但并未绑定到特定资源字段的目录。

## `field.asset()`

```ts
import { field } from "scribe-cms";

const garmentSchema = z.object({
  title: field.translatable(z.string()),
  productImage: field.asset({
    dir: "/try-on/garments",   // 值必须位于该 Web 路径下
    formats: ["webp"],          // 允许的扩展名列表
    maxKB: 150,                 // 大小限制（会触发警告）
    optional: false,            // 默认值
  }),
});
```

与 `field.relation()` 一样，**约束条件必须写在配置对象中，而不是通过 Zod 方法链式调用**。链式调用会克隆 schema 并丢失元数据。资源字段始终是结构化的：资源路径绝不会发送给翻译系统。

| 选项 | 类型 | 含义 |
| --- | --- | --- |
| `dir` | `string` | 值必须位于其下的 Web 路径前缀。同时也声明了一个托管根目录。 |
| `template` | `string` | 派生路径模板，例如 `"/try-on/garments/{slug}/product.webp"`。`{slug}` 是条目的英文 slug。设置后，frontmatter 字段可以完全省略，由加载器自动填充。显式值会覆盖模板（用于在条目之间有意共享资源）。 |
| `formats` | `string[]` | 允许的扩展名（小写，不带点号）。违规将触发校验警告。 |
| `maxKB` | `number` | 文件大小限制。违规将触发校验警告。 |
| `optional` | `boolean` | 字段可以不存在（仅在没有 `template` 时有意义）。如果提供了值但文件丢失，仍然会报错。 |

Frontmatter 中的值是一个指向 `assets.dir` 的**根相对 Web 路径**。例如 `/try-on/garments/denim-flare/product.webp`，这与普通图片字符串的约定相同，不引入新的 URI 方案。在磁盘上，frontmatter 存储的是源引用（如果使用模板则不存储任何内容）。解析后的 URL 仅存在于运行时输出中。静态/原始导出和翻译系统看到的始终是源值。

## Loader resolution

当运行时加载文档时，它会遍历 schema 寻找资源字段，并对每个字段执行以下操作：

1. 如果 frontmatter 值不存在且字段有 `template`，则从模板生成路径（`{slug}` 会变成英文 slug）。
2. 前置 `assets.publicPath`（拼接时不产生双斜杠，支持绝对域名的 `publicPath`）。

因此，调用方无需任何额外调用即可获取最终的 URL：

```ts
const garment = scribe.garment.get("denim-flare");
garment.frontmatter.productImage;
// "/try-on/garments/denim-flare/product.webp"                       (publicPath 为 "/")
// "https://cdn.example.com/try-on/garments/denim-flare/product.webp" (CDN publicPath)
```

解析过程在结构化字段合并到本地化文档之后进行，确保每个本地化版本都能从英文源获得解析后的值。

### `scribe.assets.url(ref)`

这是为 MDX 正文图片和临时情况提供的应急方案，可将 `publicPath` 应用于原始引用：

```ts
scribe.assets.url("/blog-images/hero.webp"); // 已应用 publicPath
```

你也可以在 MDX 正文中通过 [`asset` 内联标记](/docs/inline-tokens) 直接引用资源，它会在读取时以相同方式进行解析。

## Validation

`scribe validate` 会检查已声明的资源字段：

- **必填资源字段的文件丢失**：错误（会阻断构建，就像失效的必填关系一样）。
- **有值的 `optional` 字段的文件丢失**：也是错误。`optional` 的意思是*字段*可以不存在，而不是说可以填一个虚假的路径。
- **值超出了字段的 `dir` 范围**：错误。
- **扩展名不在 `formats` 中**：警告。
- **文件大于 `maxKB`**：警告。
- 模板字段会校验*生成*的路径。即使 frontmatter 省略了该值，文件也必须存在。

提示信息会包含字段归属，例如 `garment/denim-flare: productImage → 未找到 /try-on/garments/denim-flare/product.webp`。另外，启发式地从 frontmatter 和 MDX 正文收集的类似图片的字符串如果在丢失时仍会报告警告，这样能够在无需迁移的情况下覆盖正文图片。

## Studio

[Studio](/docs/studio) 以只读方式展示资源：

- **Entry inspector** 将资源字段渲染为图片预览，包含解析后的 URL、文件大小和尺寸。丢失的文件会显示红色徽章。
- **Asset browser**（`/assets`）为每个托管根目录显示一个缩略图网格。每个资源都会显示其路径、大小、尺寸以及一个“被引用”列表，列出所有指向它的条目和字段。徽章会标记未引用的资源（候选孤儿文件）、存在引用但文件丢失的资源、过大的资源以及格式不符的资源。
- **Gallery view** 允许任何带有资源字段的类型将其条目列表渲染为卡片网格，非常适合作为生成图像的 QA 墙。
