尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深入理解 TinaCMS v4 的插件清单:从 definePlugin 到字段注册表

深入理解 TinaCMS v4 的插件清单:从 definePlugin 到字段注册表 深入理解 TinaCMS v4 的插件清单从 definePlugin 到字段注册表【免费下载链接】tinacmsTinaCMS is the leading open-source headless CMS that supports Markdown and Visual Editing. Your content is stored in your own GitHub repo ❤️项目地址: https://gitcode.com/GitHub_Trending/ti/tinacms本指南围绕 plugins.md 展开讲解 TinaCMS v4tinacms/tinacms中唯一的插件形态——交给definePlugin的清单manifest包括其name、provides、field、client与overrides各属性的职责、Capability 取值体系以及清单如何被解析为可用的字段注册表。读完你将能读懂任何 v4 插件的源码结构知道字段插件为什么必须有field提供项、contractVersion与tina-lock.json的关系以及如何用overrides替换内置字段并进一步迈入编写自定义字段插件的实战。v4 插件模型一种清单取代所有专用插件函数TinaCMS v4 对插件系统做了一个根本性的简化v4 只有一种类型的插件即一个交给definePlugin的清单manifest。在旧版本中你会看到defineFieldPlugin、defineMediaPlugin等一整套专用函数v4 全部废弃了这些入口统一收敛为单个definePlugin。插件的能力capability由清单中provides属性的取值决定能力的类型由此推断不再靠函数名区分。这一点直接体现在入口源码上core/plugin.ts 中definePlugin只有一个参数manifest没有任何按能力拆分的重载export const definePlugin ( manifest: PluginManifestInput ): PluginManifest ({ ...manifest, provides: manifest.provides ?? [], dependsOn: manifest.dependsOn ?? [], overrides: manifest.overrides ?? [], });文档给出的最小字段插件清单如下// core/plugin.ts import { definePlugin } from tinacms/tinacms; definePlugin({ name: tina:field:string, // unique identity provides: [field], // capabilities it satisfies field: { type: string, contractVersion: 1 }, // the field it provides client: () import(./string-field.client), // lazy client segment });definePlugin 是恒等函数definePlugin是一个恒等函数identity function它把 TypeScript 类型应用到清单上然后原样返回这个清单。从上面的源码可以看到它真正做的只有三件事——为可选的provides、dependsOn、overrides填充空数组默认值避免后续消费方反复判空其余属性全部透传。因此它不校验、不注册、不执行任何副作用只负责让清单拥有正确的类型清单的类型从输入侧PluginManifestInput提升为完整的PluginManifestcore/plugin.tsprovides、dependsOn、overrides在输出侧成为必填数组真正的注册发生在别处——清单会被收集进插件数组交给TinaProvider/ 注册表解析流程处理见下文从清单到注册表。清单PluginManifest的四个核心属性字段插件清单有四个核心属性文档用下表概括属性作用name唯一身份标识。允许任意字符串核心插件采用tina:capability:key格式。provides该插件提供的能力。字段插件使用[field]。field字段提供项{ type, contractVersion }。type是该插件拥有的 schema 类型也是注册表键registry keycontractVersion是 codegen 锁文件codegen/compile-schema.ts为该类型记录的数字。client对客户端分段client segment的懒加载导入分段中持有描述符descriptor。name唯一身份name是插件的全局唯一标识任意字符串都被允许。Tina 内置插件遵循tina:capability:key约定——例如tina:field:string表示能力为 field、键为 string。第三方插件可自由选择命名空间rating-field.tsx示例中自定义插件即命名为example:field:rating见 rating-field.tsx。provides声明满足哪些能力provides声明插件满足的能力集合。字段插件填[field]。能力的具体取值见下文Capabilities一节field在其中属于键控能力keyed capability。field字段提供项字段插件的必需品field提供项形如{ type, contractVersion }type该插件拥有的 schema 类型如string、image同时是注册表键——注册表以Maptype, FieldDescriptor的形式组织见 core/field/registry.tscontractVersion一个数字由 codegen 锁文件为每个类型记录详见下文contractVersion与 codegen 锁文件。字段插件必须提供field。如果插件有字段描述符却没有在清单上声明field注册表会在客户端分段中一发现字段描述符就抛出field-plugin-no-provision错误实现在 core/field/registry.tsPlugin name has a field descriptor but declares no field: { type, contractVersion } on its manifest.值得强调的是type存在于清单manifest上而不在描述符descriptor上。描述符只描述如何渲染与校验类型归属则由清单声明。这一分工在注册表构建时被强制检查——fieldEntryOf同时校验两侧清单缺field抛field-plugin-no-provision客户端分段缺field描述符则抛field-plugin-no-descriptor。client懒加载的客户端分段client是一个返回 Promise 的懒加载导入指向客户端分段client segment。分段中持有描述符其类型为ClientSegmentcore/plugin.tsexport interface ClientSegment { field?: FieldDescriptor; slice?: ClientSlice; screens?: AdminScreen[]; }字段插件主要用到field字段描述符slicestore 切片与screens管理界面屏幕是其他能力可能用到的分段内容。关于字段描述符与客户端分段的完整讲解参见 field-plugins.md 第 2 节。contractVersion与 codegen 锁文件contractVersion并非装饰性字段它直接参与 schema 编译与锁文件校验。在 codegen/compile-schema.ts 中compileSchema会遍历所有集合里用到的字段类型把每个类型的提供项provision记录进primitives映射for (const type of usedFieldTypes(config.schema.collections).sort()) { const provision provisions.get(type); invariant( provision, schema-unknown-field-type, The schema uses the field type ${type}, but no installed plugin provides the field capability at that type. ); primitives[type] provision.contractVersion; }由此可以得出几个关键结论type是注册表键contractVersion是该键对应的版本号。同一个 schema 类型如果升级了契约例如描述符的字段结构发生变化就应递增contractVersionschema 中出现的每个类型都必须有插件提供否则compileSchema直接抛出schema-unknown-field-type——这保证了锁文件tina-lock.json里的primitives永远有据可依锁文件TinaLock包含version、schema、primitives当前LOCK_VERSION 5记录了每个原始类型的契约版本。checkLockcompile-schema.ts会比对锁文件与当前配置返回current/unreadable/stale/incompatible四种状态——若锁文件版本高于当前 tinacms 可写版本会拒绝降级重写保护团队已提交的锁文件。测试代码同样印证了这一点compile-schema.test.ts构造{ type, contractVersion }的插件清单用于编译验证registry.test.ts也以{ type: image, contractVersion: 1 }之类的形式构造字段插件见 compile-schema.test.ts 与 registry.test.ts。第五个属性overrides替换内置字段字段插件还可以有第五个属性overrides。当你想在某个已被占用的键上替换内置字段时需要声明它definePlugin({ name: my:field:string, provides: [field], field: { type: string, contractVersion: 1 }, client: () import(./my-string.client), overrides: [{ capability: field, key: string }], });overrides的类型为CapabilityOverridecore/plugin.ts对字段能力是{ capability: field, key }的形式——key指明要替换的注册表键对单例能力auth、content、media、search则是{ capability: SingletonSliceCapability }无需指定键。为什么需要overrides因为注册表不允许同一类型注册两个插件。如果第二个插件在同一type上注册注册表会抛出冲突错误core/field/registry.ts若冲突来自两个插件都声明了同一键的overrides报错为duplicateOverrideOnly one may replace the built-in.只能有一个替换内置插件若冲突来自普通重复注册报错提示明确给出解法Declareoverrides: [{ capability: field, key }]to replace a built-in.这一机制在overridesFieldKeyregistry.ts与composeOverridableRegistry的配合下工作overrides标记的条目会被视为对既有键的合法覆盖而非冲突。如何编写一个完整的替换插件参见 field-plugins.md 的 Replace a built-in field 一节。Capabilities五种能力取值Capability只有五个取值core/plugin.tsexport type Capability field | content | auth | media | search;取值含义field字段能力键控能力同一时刻可以注册多个字段插件每种 schema 类型如string、image一个插件content内容能力单例auth认证能力单例media媒体能力单例search搜索能力单例field是唯一的键控能力其余四种在源码中被归类为单例切片能力SINGLETON_SLICE_CAPABILITIEScore/plugin.ts每个单例能力在同一时刻只允许一个插件提供而field能力则按type键并存多个插件。isSingletonSliceCapability帮助函数core/plugin.ts在解析阶段区分这两类能力。清单上不止四个属性PluginManifestInput的完整视野虽然字段插件最常使用四个或加overrides五个属性但完整的清单输入类型PluginManifestInputcore/plugin.ts还声明了更多可选属性供其他能力与运行时生命周期使用dependsOn?: Capability[]声明插件依赖的其他能力server?: () Promise{ default: ServerSegment }服务端分段的懒加载导入ServerSegment是Recordstring, ServerOp即一组服务端操作permissions?: { name: string; description?: string }[]权限声明源码注释指出其类型待 codegen 的Permission联合类型落地后对齐requires?: { permission: string }插件运行所需权限onInit?: () void | Promisevoid与onDestroy?: () void | Promisevoid插件初始化与销毁的生命周期钩子。这些属性说明 v4 的单一清单模型并非能力上的倒退而是把所有能力入口统一收拢到一份清单里用provides 可选的client/server分段来表达。文档的核心四属性表格描述的是字段插件的最小必要集。从清单到注册表解析与冲突检测definePlugin只返回清单真正的装配发生在解析阶段。梳理 architecture.md 与源码链路如下TinaProvider plugins{[...]}调用resolveFieldPluginscore/field/registry.tsresolveFieldPlugins→resolveClientSegmentscore/plugin.ts逐个await每个清单的client()导入期间做两处校验声明了field却没有client→ 抛field-plugin-no-client客户端模块没有 default 导出 → 抛plugin-client-no-default每个成功解析的分段被组装成{ manifest, segment }进入createFieldRegistry最终生成FieldRegistry即Maptype, FieldDescriptorregistry.ts如果两个插件声明了相同的type且没有overridescomposeOverridableRegistry依据fieldConflictError抛出冲突错误。这一设计解释了文档中两个看似反常的约定client必须懒加载——浏览器只有在字段真正被渲染时才拉取体积庞大的 UI 组件.ui.tsx这是四个文件拆分的直接动因type放在清单而非描述符——因为注册表键必须在所有插件解析完成后全局唯一而描述符只是键对应的值。端到端示例barebones 中的五角星评分字段仓库在 packages/v4/examples/barebones/tina/rating-field.tsx 提供了一个完整、可运行的单文件字段插件示例把清单、描述符与组件放在一起演示了上述全部概念清单name: example:field:rating、provides: [field]、field: { type: rating, contractVersion: 1 }、client懒加载返回defineClientPlugin({ field: {...} })schema 辅助函数rating (config) ({ ...config, type: rating as const })供作者在集合里调用描述符Component用useFieldAddress/useFieldValue/useFieldErrors三个 hook 实现无 props 组件defaultValue: 0metadata: { layout: inline }并带一个自定义validate——校验值为 0 到 5 的整数否则返回错误文案A rating is 0 to 5 whole stars.。这个示例完整映射了本文档讲解的每个概念恒等函数返回的清单、field提供项、懒加载客户端分段、以及描述符在分段内的形态。把它与上文从清单到注册表的解析链路对照阅读即可形成从写插件到插件生效的闭环认知。进一步阅读插件系统在 v4 中是一个更大的知识体系的一部分文档末尾的导航为你指出了后续深入方向以下链接均已转换为仓库根目录相对路径Field plugins —— 如何编写一个字段插件四个文件、两层校验、复合字段、地址机制Thestringfield —— v4 内置的文本输入Thebooleanfield —— v4 内置的复选框Thenumberfield —— v4 内置的数字输入Thedatetimefield —— v4 内置的 datetime-local 输入Thearrayfield —— v4 内置的可重复字段Theselectfield —— v4 内置的固定选项选择器Therich-textfield —— v4 内置的 Plate 编辑器及其控制的 markdown 正文Architecture —— 一个插件从清单走到屏幕的完整旅程【免费下载链接】tinacmsTinaCMS is the leading open-source headless CMS that supports Markdown and Visual Editing. Your content is stored in your own GitHub repo ❤️项目地址: https://gitcode.com/GitHub_Trending/ti/tinacms创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表