- API设计
- 前端
- 开发工具
【免费下载链接】swagger-editor
Swagger Editor
导读
editor-monaco-language-apidom是 Swagger Editor 基于 Monaco Editor 构建的语言服务插件,它为编辑器内置了apidom语言:在 Web Worker 中运行 ApiDOM Language Server,为 OpenAPI 2 / OpenAPI 3.x 等规范提供校验(Diagnostics)、补全(Completion)、悬停(Hover)、文档链接、符号、定义跳转、语义高亮与反引用(Dereference)等能力。本文以官方文档 docs/customization/plug-points/editor-monaco-language-apidom.md 为骨架,结合插件源码,系统讲解三个实战主题:如何通过apiDOMContext配置 Worker 的语言服务能力;如何通过**动态(运行时import())与静态(打包器构建自定义入口)**两种方式扩展apidom.worker;以及如何借助createData向 Worker 传递任意结构化数据。读完本文,你将能独立为 Swagger Editor 的 ApiDOM 语言服务定制校验、补全与日志行为,并为 Worker 注入带鉴权的数据拉取等自定义能力。
背景:插件如何把 ApiDOM 语言服务搬进 Web Worker
在 Swagger Editor 中,Monaco 编辑器的语言能力由editor-monaco插件与editor-monaco-language-apidom插件共同提供。后者在 src/presets/monaco/index.js 中被MonacoPreset默认装载,并在 src/App.tsx 中注册为EditorMonacoLanguageApiDOM。
插件的工作链路大致如下:
- 插件工厂
EditorMonacoLanguageApiDOMPlugin(见 src/plugins/editor-monaco-language-apidom/index.js)接收createData、useApiDOMSyntaxHighlighting等选项,并在afterLoad阶段注册apidom语言(见 after-load.js); apidom-mode.js中的setupMode创建WorkerManager,并通过MonacoEnvironment.getWorker('ApiDOMWorker', languageId)获取 Worker(见 WorkerManager.js);- Worker 端由 apidom.worker.js 承载,它基于
monaco-editor的initialize约定初始化,内部实例化ApiDOMWorker,后者再调用@swagger-api/apidom-ls的getLanguageService完成各类语言操作(见 ApiDOMWorker.js)。
理解了这条链路,就很容易明白:想要定制语言服务行为,本质上就是定制 Worker 的创建过程与上下文(Context)——这正是本文要解决的三个问题。
一、配置 Worker 能力:ApiDOM Context 配置对象
1.1 默认配置长什么样
apidom.worker通过一个ApiDOM Context 配置对象来接收语言服务的配置。文档给出的默认配置如下:
{ validatorProviders: [], completionProviders: [], performanceLogs: false, logLevel: apidomLS.LogLevel.WARN, completionContext: { maxNumberOfItems: 100, enableLSPFilter: false, }, }对照源码 src/plugins/editor-monaco-language-apidom/language/ApiDOMWorker.js,该对象实际由ApiDOMWorker.defaultApiDOMContext静态属性定义,且比文档展示的默认值更完整,还包含两组对实际行为有影响的额外配置:
| 配置项 | 默认值 | 说明 |
|---|---|---|
validatorProviders | [] | 自定义校验器提供者列表,用于扩展校验能力 |
completionProviders | [] | 自定义补全提供者列表,用于扩展补全能力 |
performanceLogs | false | 是否输出性能日志 |
logLevel | apidomLS.LogLevel.WARN | 语言服务日志级别(如ERROR/WARN等) |
completionContext.maxNumberOfItems | 100 | 单次补全返回的最大条目数 |
completionContext.enableLSPFilter | false | 是否启用 LSP 的“严格”单词过滤(默认关闭,使用 Monaco 模糊匹配) |
validationContext.referenceValidationContinueOnError | true | 引用校验出错时是否继续校验 |
validationContext.referenceValidationMode | apidomLS.ReferenceValidationMode.APIDOM_INDIRECT_EXTERNAL | 引用校验模式(间接外部引用) |
referenceOptions.resolve.resolverOpts.cacheTTL | 60 * 1000 | 外部引用解析结果缓存时长(60 秒) |
也就是说,只要没有显式覆盖,Worker 会以“WARN 级日志、单次最多 100 条补全、Monaco 模糊匹配、引用解析结果缓存 60 秒”的默认策略运行。这些默认值也印证了“配置对象会被深度合并”的设计初衷:使用者只需覆盖关心的字段,其余沿用默认。
1.2 通过apiDOMContext覆盖默认配置
要覆盖默认配置,只需把apiDOMContext作为createData选项传给EditorMonacoLanguageApiDOM插件:
EditorMonacoLanguageApiDOM({ createData: { apiDOMContext: { completionContext: { enableLSPFilter: true, // 启用“严格”单词过滤(替代默认的 Monaco 模糊匹配) }, }, }, });例如这里开启enableLSPFilter: true后,补全候选词将按 LSP 的严格词过滤规则匹配,而不是 Monaco 默认的模糊匹配——适合对补全精确度要求更高的场景。
合并机制:传入的 ApiDOM Context 配置对象会与默认对象通过 npm 的
deep-extend包做深度合并,而非浅层替换。源码中的证据在 ApiDOMWorker.js:
createLanguageService() { return apidomLS.getLanguageService( deepExtend({}, this.constructor.defaultApiDOMContext, this._createData.apiDOMContext) ); }deepExtend({}, 默认配置, 传入配置)意味着:嵌套对象(如completionContext、validationContext、referenceOptions)是逐层合并的,你只写completionContext.enableLSPFilter: true时,maxNumberOfItems: 100依然保留。这也是官方推荐“只覆盖需要改的字段”的原因。注意 apidom.worker.js 中为兼容deep-extend对 CJS 全局Buffer的引用,在 Worker 启动时做了globalThis.Buffer的 polyfill,这也提醒我们:该配置对象最终是在 Worker 线程中被消费的。
1.3 配置对象的传递路径
从源码可以梳理出apiDOMContext到达语言服务的完整调用链,这对排查“配置为何不生效”很有帮助:
- 插件入口:
EditorMonacoLanguageApiDOMPlugin({ createData: { apiDOMContext } }); - afterLoad:after-load.js 把整个
createData交给lazyMonacoContribution; - 语言注册:monaco.contribution.js 在
onDidCreateEditor中把customApiDOMWorkerPath重命名为内部字段customWorkerPath,并把apiDOMContext与其余data一并组装成 Worker 选项; - Worker 拉取:WorkerManager.js 把
{ ...data, languageId, apiDOMContext, customWorkerPath }通过worker.postMessage(createData)发给 Worker; - 实例化:ApiDOMWorker.js 构造函数把
createData存入this._createData,再由createLanguageService()深度合并后交给apidom-ls。
二、扩展 Worker 能力:动态扩展与静态扩展
editor-monaco-language-apidom自带apidom.worker,它基于 ApiDOM 能力实现。文档明确给出两种扩展方式:动态扩展(运行时)与静态扩展(构建期)。两者修改的都是“Worker 类”,区别在于何时、以何种方式替换默认的ApiDOMWorker。
2.1 动态扩展:运行时import()注入
动态扩展发生在运行时,官方建议只用于简单场景。第一步是把customApiDOMWorkerPath选项传给插件:
EditorMonacoLanguageApiDOM({ createData: { customApiDOMWorkerPath: 'https://example.com/index.js', }, });customApiDOMWorkerPath是扩展模块的 URL(绝对或相对路径),apidom.worker会在运行时通过动态import()加载它。加载进来的模块必须导出一个名为customApiDOMWorkerFactory的函数:
export const customApiDOMWorkerFactory = (ApiDOMWorkerClass, toolbelt) => { return ApiDOMWorkerClass; };该函数接收两个参数:
ApiDOMWorkerClass—— 当前 Worker 类(默认是ApiDOMWorker,若多次扩展则是前一次扩展的结果),实现编辑器的语言能力;toolbelt—— 一个包含各种库导出的工具对象,方便你直接使用 ApiDOM 相关库。
toolbelt里到底有什么?从源码 ApiDOMWorker.js 可以看到makeCreate构造的toolbelt包含:
const toolbelt = { apidomLS, // @swagger-api/apidom-ls,语言服务主库 apidomNSOpenAPI2, // OpenAPI 2.0 命名空间 apidomNSOpenAPI30, // OpenAPI 3.0 命名空间 vscodeLanguageServerTextDocument, // 文本文档抽象(TextDocument) deepExtend, // 深度合并工具 };下面是一个官方示例:把语言服务的日志级别从默认的WARN提升到ERROR:
export const customApiDOMWorkerFactory = (ApiDOMWorkerClass, toolbelt) => { const { apidomLS } = toolbelt; class ApiDOMWorkerLogLevelErrorClass extends ApiDOMWorkerClass { static apiDOMContext = { ...ApiDOMWorkerClass.apiDOMContext, logLevel: apidomLS.LogLevel.ERROR, }; } return ApiDOMWorkerLogLevelErrorClass; };这里用类继承的方式扩展:新类通过static apiDOMContext覆盖父类的默认上下文,从而只改日志级别、保留其他行为。
传统 Worker 兼容:非模块(classic)Worker 的使用者仍可使用
globalThis.customApiDOMWorkerFactory赋值模式——当扩展模块没有命名导出时,它作为兜底方案被支持。源码中的对应实现见 ApiDOMWorker.js:
const factory = mod.customApiDOMWorkerFactory ?? globalThis.customApiDOMWorkerFactory; if (typeof factory !== 'function') { throw new TypeError(`The module at ${path} does not export customApiDOMWorkerFactory`); }动态扩展的底层机制(从源码结构看):
customApiDOMWorkerPath在 monaco.contribution.js 被改名为customWorkerPath传给 Worker;- makeCreate 在实例化前遍历
createData.customWorkerPath(支持传数组、依次应用多个扩展),对每个路径执行import(path),取出工厂函数并调用factory(ApiDOMWorkerClass, toolbelt)得到新类;非法条目会被console.warn跳过; - 最终返回的是一个Proxy 包装的异步实例(ApiDOMWorker.js):所有方法调用都会先等待
instancePromise完成再转发到真实实例上,从而保证“扩展模块加载完成前调用不会丢失”。
2.2 静态扩展:用打包器构建自定义 Worker 入口
静态扩展需要借助打包器(Vite、webpack 等)构建一个自定义的 Worker 入口文件。先在项目中新建一个 worker 入口,比如my-custom-apidom.worker.js:
import { initialize, makeCreate, ApiDOMWorker } from 'swagger-editor/apidom.worker'; class ApiDOMWorkerExtended extends ApiDOMWorker { // 扩展实现 } const create = makeCreate(ApiDOMWorkerExtended); initialize((ctx, createData) => create(ctx, createData)); export { initialize, create, makeCreate, ApiDOMWorkerExtended as ApiDOMWorker };这段代码做的事:
- 从
swagger-editor/apidom.worker导入官方 Worker 的三个核心导出(源码见 apidom.worker.js,它本身也导出initialize、create、makeCreate、ApiDOMWorker); - 定义
ApiDOMWorkerExtended继承ApiDOMWorker,写入扩展逻辑; - 用
makeCreate(ApiDOMWorkerExtended)生成实例工厂,再交给initialize注册为 Worker 的启动回调——这正好对应makeCreate在 ApiDOMWorker.js 中的“先套扩展、再实例化”逻辑。
随后把打包器指向这个文件作为apidom.worker的入口,并配置MonacoEnvironment.getWorker让它服务于构建产物。文档给出的 webpack 示例:
entry: { app: './index.js', 'apidom.worker': './my-custom-apidom.worker.js', 'editor.worker': 'swagger-editor/editor.worker', }这样构建后,apidom.worker将使用你的自定义类,同时editor.worker仍使用 Monaco 内置 Worker。若用 Vite,可以参考仓库自带的 vite/configs/worker-configs.esm.js:其中apidomWorkerConfig以 src/plugins/editor-monaco-language-apidom/language/apidom.worker.js 为入口,以formats: ['es']、codeSplitting: false构建自包含的单文件 ESM Worker,输出到dist/esm/apidom.worker.js——这就是“静态扩展”在仓库内部的标准做法。
2.3 两种方式怎么选
| 维度 | 动态扩展 | 静态扩展 |
|---|---|---|
| 时机 | 运行时import()加载 | 构建期打包进 Worker 产物 |
| 依赖打包器 | 否(URL 加载) | 是(Vite/webpack 等) |
| 适用场景 | 简单、低耦合的调整(如改日志级别) | 复杂、需要新依赖或较大逻辑的扩展 |
| 扩展模块位置 | 任意 URL(需可被动态 import) | 随构建产物分发 |
| 官方建议 | 仅用于简单用例 | 复杂用例的推荐方式 |
三、向 Web Worker 传递数据:createData与_createData
扩展 Worker 能力时,经常需要向 Worker 传递额外数据。这些数据可以是任意兼容结构化克隆算法(Structured Clone Algorithm)的数据——包括普通对象、数组、字符串乃至ArrayBuffer等,它们会随postMessage被安全复制进 Worker 线程。
官方给出的场景是:让apidom.worker按需从带鉴权的 REST 端点拉取数据。核心思路是:把鉴权令牌作为createData传入插件,扩展类通过this._createData读取。
3.1 动态扩展中的数据传递
插件配置:
EditorMonacoLanguageApiDOM({ createData: { authToken: 'c32d8b45-92fe-44f6-8b61-42c2107dfe87', customApiDOMWorkerPath: 'https://example.com/index.js', }, });扩展模块(https://example.com/index.js):
export const customApiDOMWorkerFactory = (ApiDOMWorkerClass, toolbelt) => { const { apidomLS } = toolbelt; class ApiDOMWorkerLogLevelErrorClass extends ApiDOMWorkerClass { static apiDOMContext = { ...ApiDOMWorkerClass.apiDOMContext, logLevel: apidomLS.LogLevel.ERROR, }; async loadData() { // createData 作为插件选项传入后,在 Worker 中通过 this._createData 读取 const { authToken } = this._createData; return await fetch(`https://example.com/data?authToken=${authToken}`); } } return ApiDOMWorkerLogLevelErrorClass; };注意示例同时演示了“传数据 + 改配置”的组合:authToken进入_createData,apiDOMContext覆盖日志级别。
3.2 静态扩展中的数据传递
静态扩展中,只要你继承了ApiDOMWorker类,就自动拥有_createData公开属性:
import { initialize, makeCreate, ApiDOMWorker } from 'swagger-editor/apidom.worker'; class ApiDOMWorkerExtended extends ApiDOMWorker { async loadData() { // createData 作为插件选项传入后,在 Worker 中通过 this._createData 读取 const { authToken } = this._createData; return await fetch(`https://example.com/data?authToken=${authToken}`); } } const create = makeCreate(ApiDOMWorkerExtended); initialize((ctx, createData) => create(ctx, createData)); export { initialize, create, makeCreate, ApiDOMWorkerExtended as ApiDOMWorker };数据是如何到达_createData的?从源码看,链路非常清晰:
EditorMonacoLanguageApiDOM({ createData })中的createData在 after-load.js 被解构为{ createData = {}, useApiDOMSyntaxHighlighting = false };- monaco.contribution.js 把
apiDOMContext、customWorkerPath之外的其余字段作为data传递:const { customApiDOMWorkerPath: customWorkerPath, apiDOMContext, ...data } = createData; - WorkerManager.js 组装
createData = { ...data, languageId, apiDOMContext, customWorkerPath }并postMessage; - ApiDOMWorker.js 构造函数执行
this._createData = createData。
于是authToken这类自定义字段便与apiDOMContext、languageId一起进入 Worker,且在getJsonPointerPosition、doComplete、doValidation等所有语言服务方法(见 ApiDOMWorker.js)中都能间接使用。
四、小结与源码索引
总结一下三个核心结论:
- 配置:
apiDOMContext与默认上下文经deep-extend深度合并后注入语言服务,只需覆盖关心的字段;完整默认值(含validationContext、referenceOptions.cacheTTL等)见 ApiDOMWorker.js。 - 扩展:动态扩展靠
customApiDOMWorkerPath+ 导出customApiDOMWorkerFactory(运行时import(),支持数组与globalThis兜底);静态扩展靠打包器构建自定义入口(makeCreate+initialize),仓库的 Vite 构建示例见 vite/configs/worker-configs.esm.js。 - 传数据:任何符合结构化克隆算法的数据都能放进
createData,经postMessage到达 Worker 后统一暴露为this._createData。
想要继续深入,可以按以下路径阅读仓库源码:
- 插件入口:src/plugins/editor-monaco-language-apidom/index.js(
EditorMonacoLanguageApiDOM工厂) - Worker 启动:src/plugins/editor-monaco-language-apidom/language/apidom.worker.js
- Worker 核心类与扩展机制:src/plugins/editor-monaco-language-apidom/language/ApiDOMWorker.js(
defaultApiDOMContext、makeCreate、toolbelt、Proxy 异步实例) - Worker 生命周期管理:src/plugins/editor-monaco-language-apidom/language/WorkerManager.js(空闲 2 分钟自动销毁、
postMessage(createData)) - 语言注册与 Provider 装配:src/plugins/editor-monaco-language-apidom/language/monaco.contribution.js 与 src/plugins/editor-monaco-language-apidom/language/apidom-mode.js
- 打包配置:vite/configs/worker-configs.esm.js
配合文档 docs/customization/plug-points/editor-monaco-language-apidom.md 与同目录 docs/customization/plug-points/README.md,即可系统掌握 Swagger Editor 中 ApiDOM 语言服务的全部定制点。
- API设计
- 前端
- 开发工具
【免费下载链接】swagger-editor
Swagger Editor
相关推荐
突破静态限制:ComfyUI-Impact-Pack动态API参数传递的创新实践
突破静态限制:ComfyUI Impact Pack动态API参数传递的创新实践 引言:参数传递的痛点与解决方案 在现代AI工作流(Workflow)开发中,尤
AI 应用计算机视觉图像处理Atlas数据库加密:静态数据与传输中数据安全配置
Atlas数据库加密:静态数据与传输中数据安全配置 在现代数据库管理中,数据安全已成为核心需求。Atlas作为一款现代化的数据库模式管理工具,提供了多种配置选项
数据库开发工具数据工程Amazon Bedrock Workshop数据加密实践:传输中与静态数据保护配置
Amazon Bedrock Workshop数据加密实践:传输中与静态数据保护配置 数据加密概述 在Amazon Bedrock Workshop中,数据安全
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考