ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Freelens 扩展开发指南:@freelensapp/extensions 运行时全局 API 与宿主提供库机制

Freelens 扩展开发指南:@freelensapp/extensions 运行时全局 API 与宿主提供库机制 云原生开发工具运维【免费下载链接】freelensFree IDE for Kubernetes项目地址https://gitcode.com/gh_mirrors/fr/freelens点击查看免费下载freelensapp/extensions是 FreelensFree IDE for Kubernetes对外发布的唯一扩展 API 包扩展作者通过它拿到Common、Main、Renderer三个命名空间并借助运行时全局 宿主提供库机制与宿主共享 React、mobx、monaco-editor 等关键模块。本文以该包的 README 为主线结合仓库中的进程入口、singleton 发布源码与契约文档讲清扩展包的发布形态、为何某些库必须标记 external 而非打包以及如何正确声明依赖。读完你可以理解 Freelens v2 扩展的依赖模型并为自己的扩展写出不会踩 invalid hook call 或 mobx 静默失效陷阱的构建配置。包定位一个包两个文件freelensapp/extensions是 Freelens 扩展 API 的发布载体扩展所面向的Common、Main、Renderer三个命名空间全部来自它。它在发布形态上有两个关键事实它只是一个很小的运行时 shim运行期仅转发globalThis.FreelensExtensionApi——这个全局对象由运行中的应用在启动时赋值扩展依赖它不携带任何宿主的实现代码它同时配一份打包好的类型声明供扩展在编译期获得完整的类型面。从 packages/extensions/package.json 可以看到发布内容严格限定为两个文件files: [ dist/extension-api.js, dist/extension-api.d.ts ]其中dist/extension-api.js由 scripts/build-shim.mjs 把 src/runtime-shim.ts 原样转写而成内容极简const api globalThis.FreelensExtensionApi; export const Common api.Common; export const Main api.Main!; export const Renderer api.Renderer!;dist/extension-api.d.ts则是 src/extension-api.ts 的声明打包产物它把Common、Main、Renderer以命名空间形式而非常量重新导出因此扩展可以在类型位置上直接使用如Renderer.Component.IconProps、Common.PackageJson——这与 v1 扩展 API 的用法完全一致。Main与Renderer之所以是可选的全局声明里写作Main?、Renderer?是因为在渲染进程里Main是undefined、在主进程里Renderer是undefined扩展只应触碰自己所运行进程对应的命名空间。运行时全局 API扩展与宿主之间的唯一桥梁README 指出发布包是运行于globalThis.FreelensExtensionApi之上的小型 shim。这意味着扩展依赖freelensapp/extensions时宿主的任何实现都不会被打包进扩展。这个全局对象的赋值位置在仓库的两个进程入口中均有据可查主进程freelens/src/main/index.tsglobalThis.FreelensExtensionApi { Common, Main, ...mainExtensionApiSingletons };渲染进程freelens/src/renderer/index.tsglobalThis.FreelensExtensionApi { Common, Renderer, ...rendererExtensionApiSingletons };注意两个进程发布的内容不同主进程发布{ Common, Main }渲染进程发布{ Common, Renderer }。由于 shim 就是转发这个全局无论扩展在构建时把 shim 内联进 bundle还是把它标记为 external运行时成员解析结果完全一致freelensapp/extensions的导入写法也和 v1 相同import { Common, Main, Renderer } from freelensapp/extensions;宿主提供库Host-provided libraries与全局名称README 给出了一张宿主在运行时提供、作为可选 peer dependency 声明的库清单。这些库必须由宿主实例提供扩展绝不能打包自己的第二份拷贝Peer dependency从全局读取为reactFreelensExtensionApi.Reactreact-domFreelensExtensionApi.ReactDommobxFreelensExtensionApi.Mobxmobx-reactFreelensExtensionApi.MobxReactmonaco-editorFreelensExtensionApi.MonacoEditor同时types/react和types/react-dom也是可选 peer目的是让声明与你自己的代码基于同一套 React 类型编译。在源码层面这张表的进程发布集合被明确固化在packages/core/src/extensions/api-globals/目录renderer-singletons.ts 发布六个成员React、ReactDom、ReactJsxRuntime、Mobx、MobxReact、MonacoEditormain-singletons.ts 只发布一个Mobx。也就是说每个进程只发布自己实际拥有的集合主进程没有窗口因此不发布 DOM 渲染器和代码编辑器渲染进程则全部发布。其中React与ReactDom发布的是默认导出而非模块命名空间因为扩展的import React from react解析到的正是这个对象。为什么必须是宿主的一份把 React 或 mobx 的第二份拷贝打进 bundle 会出问题React 双实例抛出invalid hook callinvalid hook callmobx 双实例静默失效——两个 mobx 通过共享的全局状态仍能互相协作observable 看起来正常但 reaction 不再触发。README 特别强调optional 并不等于可以安全地打包。真正阻止第二份拷贝进入 bundle 的是你在 bundler 中把相关 specifier 标记为 external并让它改从globalThis.FreelensExtensionApi读取。所以渲染进程发布上述全部库主进程只发布Mobx——扩展主入口只能映射主进程发布的那些映射了别的只会得到undefined且没有任何构建期报错。全局名称的机械命名规则全局名称不是人工约定的映射表而是由模块 id机械推导去掉 scope 的按-、/、.切分每段首字母大写后拼接。例如react-dom→ReactDom注意不是ReactDOMreact/jsx-runtime→ReactJsxRuntimemobx-react→MobxReact。这条规则实现在 global-name-for-module-id.ts且宿主在启动时用assertExtensionApiSingletonNames断言每个发布的 key 都符合规则——因为拼错的 key 不会在构建期或运行期报错扩展读到的是undefined错误只会发生在别处别人的仓库里。正因为有规则bundler 插件可以从 specifier 推导名称而无需携带一张易拼错的映射表。明确不在名单上的模块以下几个模块 id不属于host-provided 名单扩展若仍把它们映射到全局得到的将是undefined而非构建错误freelensapp/extensions——它本身就是一个读取全局的薄 shim打包它才是正确的react-router与react-router-dom——不是宿主的依赖想要的扩展应自行打包见契约 C9宿主用自研freelensapp/routing支持 react-router 5 的路径方言node-pty——v2 不再发布Pty全局需要运行程序的扩展在主入口用node:child_process见 docs/extensions/binaries.mdogre-tools/injectable与ogre-tools/injectable-react——宿主的依赖注入库被当作实现细节保留扩展如需 DI 就打包自己的拷贝任意版本均可契约 C7。为什么是可选的 peer dependency这些库被声明为optionalpeer而不是必选理由来自安装成本与类型解析两方面npm 7 和 pnpm 默认会安装缺失的 peer若声明为必选每个作者的依赖树都会被装上全部库——而单是monaco-editor就接近 100 MB一个只有main入口的扩展也要为它付出每次安装和 CI 缓存的开销一个扩展只需要安装它真正 import 的那部分用于类型和自己的构建版本应与宿主运行的版本一致peer 范围的意义在于当你的拷贝与宿主版本不匹配时包管理器会给出提示。这一点在 packages/extensions/package.json 的peerDependenciesMeta中落实react、react-dom、mobx、mobx-react、monaco-editor、types/react、types/react-dom以及electron全部标记为optional: true。electron之所以也是可选 peer理由相同宿主提供它只有显式引用它的扩展才需要安装通常是取它的类型。而ogre-tools/injectable之所以不在这张表里README 说得很清楚它是宿主内部的依赖注入库不发布到全局扩展想要 DI 就打包自己的拷贝、配自己的容器。可自由打包的依赖README 明确列出包的其他依赖——chart.js、react-select、conf、immer、rfc6902、type-fest——都可以自由打包。它们在 packages/extensions/package.json 中确实位于dependencies而 host-provided 的条目位于peerDependencies两类在声明位置上就能区分。一个容易踩的细节react-select自身把react和react-dom声明为必选peer所以即使你的扩展从不 import React依赖树里也会装上一份 Reactnpm 会把它提升到 bundler 找得到的位置。这本身没有危害——前提仍然是你把 host-provided 的模块标记为 external否则 bundler 会顺手把这个顺带装上的 React打进包运行时就会触发invalid hook call。契约文档与参考模板README 的 Documentation 一节把扩展规范拆成了三份契约文档仓库内对应文件如下Extension API contracts——规范性的契约文档定义了 C1–C14 共 14 条宿主承诺打包与发布、运行时全局 API、宿主提供单例、模块格式与加载、命名空间枚举、注册与扩展实例、DI 面、React、路由、样式、第三方打包库、HTTP、消费端工具链下限、版本与兼容Migrating an extension from v1——面向开发者的 v1→v2 移植指南包含 v1→v2 重命名对照表、engines.freelens门槛、React 17→19、MobX 7 标准装饰器、tsconfig 分环境布局等What an extension may ship besides JavaScript——扩展除 JS 外还能发布什么可执行文件以及如何用node:child_process运行它们。三者分工明确api.md 回答宿主保证什么、违约会发生什么migrating-from-v1.md 回答如何迁移binaries.md 回答如何发布与运行二进制。README 中提到的参考模板 [freelens-example-extension] 是官方示例扩展仓库用于演示构建、类型检查、测试与发布的完整流程建议新扩展直接以它为起点。用 Agent Skills 初始化或迁移扩展模板仓库还附带两个符合 Agent Skills 规范的 agent 技能供编码代理使用create-freelens-extension从模板副本启动一个新扩展port-freelens-extension-to-v2把 v1 扩展移植到 v2 API。安装它们到扩展仓库中可一次装两个或按名字装其中一个npx skills add freelensapp/freelens-example-extension npx skills add freelensapp/freelens-example-extension --skill port-freelens-extension-to-v2仓库内的实证fixture-extension 与测试为了让扩展只依赖freelensapp/extensions这一模型可验证仓库内置了一个契约夹具扩展 packages/fixture-extension它以第三方扩展的方式编写——唯一伸入 Freelens 的 import 就是freelensapp/extensions且是针对构建后的dist/extension-api.d.ts编译而非 workspace 源码。它的 package.json 展示了扩展声明依赖的规范形态engines.freelens: ^2.0.0——版本门槛契约 C14不声明它宿主会在发现阶段直接拒绝该扩展freelensapp/extensions放在devDependencies——Freelens 安装扩展只解压 tarball、不安装依赖运行期所需的一切要么宿主提供、要么已在 bundle 中react、mobx、types/react、types/node也作为 devDependencies 出现——仅用于编译与类型解析。它的渲染入口 src/renderer/index.tsx 恰好覆盖了会静默失败的六种情形带 hook 的组件双 React 下抛invalid hook call、宿主必须响应的 observable双 mobx 下无报错地失效、声明式注册、Util.fetch调用、用Renderer.K8sApi.detailsFor/menuItemFor做的注册以及customResources注册。它的主入口 src/main/index.ts 则演示了主进程侧形态Main.LensExtensionMain.Ipc Node 内置模块在含types/node而无 DOM 的src/main/tsconfig.json下编译通过。这些内容在 packages/core/src/extensions/tests/extension-api.test.ts 等测试中得到了精确断言。小结Freelens v2 的扩展依赖模型可以概括为三句话唯一的发布包freelensapp/extensions 无依赖的运行时 shim 打包好的类型声明运行时从globalThis.FreelensExtensionApi读取宿主在启动时发布的 API 对象host-provided 库必须标记 external——React、react-dom、react/jsx-runtime、mobx、mobx-react、monaco-editor 按机械命名规则从全局读取谁打包第二份拷贝谁就得到invalid hook call或 mobx 的静默失效其余依赖自由打包——chart.js、react-select、conf、immer、rfc6902、type-fest以及你自己的 DI 库均可随 bundle 发布宿主不会也不负责解析任何扩展依赖。扩展作者在动手前建议通读 docs/extensions/api.md 的 C1–C14 契约按 docs/extensions/migrating-from-v1.md 的清单核对迁移项再参照 packages/fixture-extension 的 tsconfig 分环境布局与依赖声明形态搭建自己的工程。赞分享云原生开发工具运维【免费下载链接】freelensFree IDE for Kubernetes项目地址https://gitcode.com/gh_mirrors/fr/freelens点击查看免费下载相关推荐Sliver Implant 扩展宿主运行时解析原生共享库与 WASM 扩展的加载、注册与通信机制Sliver Implant 扩展宿主运行时解析原生共享库与 WASM 扩展的加载、注册与通信机制 导读 implant/sliver/extension 是网络安全终极Docker扩展开发指南如何安全调用宿主机二进制程序终极Docker扩展开发指南如何安全调用宿主机二进制程序 Docker扩展开发是扩展Docker功能的强大方式而调用宿主机二进制程序则是实现高级功能的关键技文档教程Freelens 2 扩展 API 契约解析从运行时全局、单例共享到版本门禁的完整规范Freelens 2 扩展 API 契约解析从运行时全局、单例共享到版本门禁的完整规范 Freelens 2Free IDE for Kubernetes云原生开发工具运维上一篇Android PDF渲染引擎架构解析构建高性能轻量级PDF处理库的技术实现下一篇5个步骤快速掌握WindhawkWindows系统个性化定制的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进