☰
Swagger Editor 中 ApiDOM 语言 Worker 的定制指南:配置 ApiDOM Context、动态/静态扩展与数据传递
2026/9/25 9:16:37 网站建设 项目流程
  • API设计
  • 前端
  • 开发工具

【免费下载链接】swagger-editor

Swagger Editor

项目地址:https://gitcode.com/gh_mirrors/sw/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。

插件的工作链路大致如下:

  1. 插件工厂EditorMonacoLanguageApiDOMPlugin(见 src/plugins/editor-monaco-language-apidom/index.js)接收createData、useApiDOMSyntaxHighlighting等选项,并在afterLoad阶段注册apidom语言(见 after-load.js);
  2. apidom-mode.js中的setupMode创建WorkerManager,并通过MonacoEnvironment.getWorker('ApiDOMWorker', languageId)获取 Worker(见 WorkerManager.js);
  3. 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[]自定义补全提供者列表,用于扩展补全能力
performanceLogsfalse是否输出性能日志
logLevelapidomLS.LogLevel.WARN语言服务日志级别(如ERROR/WARN等)
completionContext.maxNumberOfItems100单次补全返回的最大条目数
completionContext.enableLSPFilterfalse是否启用 LSP 的“严格”单词过滤(默认关闭,使用 Monaco 模糊匹配)
validationContext.referenceValidationContinueOnErrortrue引用校验出错时是否继续校验
validationContext.referenceValidationModeapidomLS.ReferenceValidationMode.APIDOM_INDIRECT_EXTERNAL引用校验模式(间接外部引用)
referenceOptions.resolve.resolverOpts.cacheTTL60 * 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到达语言服务的完整调用链,这对排查“配置为何不生效”很有帮助:

  1. 插件入口:EditorMonacoLanguageApiDOMPlugin({ createData: { apiDOMContext } });
  2. afterLoad:after-load.js 把整个createData交给lazyMonacoContribution;
  3. 语言注册:monaco.contribution.js 在onDidCreateEditor中把customApiDOMWorkerPath重命名为内部字段customWorkerPath,并把apiDOMContext与其余data一并组装成 Worker 选项;
  4. Worker 拉取:WorkerManager.js 把{ ...data, languageId, apiDOMContext, customWorkerPath }通过worker.postMessage(createData)发给 Worker;
  5. 实例化: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 };

这段代码做的事:

  1. 从swagger-editor/apidom.worker导入官方 Worker 的三个核心导出(源码见 apidom.worker.js,它本身也导出initialize、create、makeCreate、ApiDOMWorker);
  2. 定义ApiDOMWorkerExtended继承ApiDOMWorker,写入扩展逻辑;
  3. 用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的?从源码看,链路非常清晰:

  1. EditorMonacoLanguageApiDOM({ createData })中的createData在 after-load.js 被解构为{ createData = {}, useApiDOMSyntaxHighlighting = false };
  2. monaco.contribution.js 把apiDOMContext、customWorkerPath之外的其余字段作为data传递:const { customApiDOMWorkerPath: customWorkerPath, apiDOMContext, ...data } = createData;
  3. WorkerManager.js 组装createData = { ...data, languageId, apiDOMContext, customWorkerPath }并postMessage;
  4. ApiDOMWorker.js 构造函数执行this._createData = createData。

于是authToken这类自定义字段便与apiDOMContext、languageId一起进入 Worker,且在getJsonPointerPosition、doComplete、doValidation等所有语言服务方法(见 ApiDOMWorker.js)中都能间接使用。

四、小结与源码索引

总结一下三个核心结论:

  1. 配置:apiDOMContext与默认上下文经deep-extend深度合并后注入语言服务,只需覆盖关心的字段;完整默认值(含validationContext、referenceOptions.cacheTTL等)见 ApiDOMWorker.js。
  2. 扩展:动态扩展靠customApiDOMWorkerPath+ 导出customApiDOMWorkerFactory(运行时import(),支持数组与globalThis兜底);静态扩展靠打包器构建自定义入口(makeCreate+initialize),仓库的 Vite 构建示例见 vite/configs/worker-configs.esm.js。
  3. 传数据:任何符合结构化克隆算法的数据都能放进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

项目地址:https://gitcode.com/gh_mirrors/sw/swagger-editor
点击查看免费下载

相关推荐

上一篇:open-saas 企业级微服务架构深度解析:从单体到模块化的演进之路
下一篇:gh_mirrors/tac/tachyon内存计算加速:Spark作业性能提升3倍实践

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询