发布时间:2026/9/7 10:24:20
Joplin 依赖剔除技巧:用 @joplin/empty 空包与 Yarn resolutions 中和破坏构建的原生依赖 Joplin 依赖剔除技巧用 joplin/empty 空包与 Yarn resolutions 中和破坏构建的原生依赖【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin在大型 monorepo 中经常会遇到“某个依赖只是被传递依赖顺带引入、实际从不被调用但它的原生编译又能在某些环境弄崩整个构建”的困境。本文以 Joplin 仓库中的 .yarn/joplin-empty-package/README.md 为核心讲解 Joplin 如何用joplin/empty这个空包配合 Yarn 的resolutions机制把canvas、sharp这类“只要装着就可能坏事”的依赖从构建中整体剔除读完你可以掌握一套可复制的“依赖中性化”实操方案。一、问题背景为什么要把依赖“剔除”而不是“升级”或“禁用”Joplin 是一个多端桌面、移动、服务端、CLI、插件生态的 monorepo根 package.json 声明了yarn 4.12.0/packageManager: yarn4.16.0与packages/*workspaces 布局并通过 .yarnrc.yml 使用nodeLinker: node-modules链接器。在这种依赖树极其庞大的工程里有两条典型的“麻烦依赖”路径canvas它是pdfjs-dist的可选依赖optional dependency。Joplin 本身并不使用它但canvas需要本地 C 编译在缺少构建工具链的环境下会让安装/构建直接失败。这正是 README 中给出的原始动机“canvasis an optional dependency ofpdfjs-dist. However, it isnt used by Joplin and can cause build to fail in certain environments.”sharpJoplin 的桌面端与服务端引入huggingface/transformers用于端侧 AI 功能见 packages/app-desktop/package.json 与 packages/lib/package.json 中固定版本4.2.0的声明而 transformers 会传递引入sharp。Joplin 只在构建期需要相关能力运行期从不调用sharp因此它同样被“中性化”。.yarn/joplin-empty-package/index.js 的源码注释明确写了这一点// Empty stub. Used via the root resolutions map to neutralise packages // pulled in transitively but never actually called (e.g. xenova/transformers // → sharp, which we only need at build time). module.exports {};注意“neutralise中和”这个措辞目标不是卸载而是让它在解析结果中仍然存在占位、满足依赖树声明但内容变成一个空对象——这样既不会真的去编译原生代码也不会让require(canvas)之类假设存在的引用拿到危险实现。二、joplin/empty 空包长什么样CJS 与 ESM 双入口整个“空包”只有三个文件设计得非常克制但恰好覆盖 Node 两种模块解析体系。1. package.json声明双入口的 exports.yarn/joplin-empty-package/package.json 的关键内容{ name: joplin/empty, version: 0.0.0, description: An empty package, used as a way to exclude certain packages from build, private: true, main: ./index.js, exports: { .: { import: ./index.mjs, require: ./index.js, default: ./index.js } } }几个值得注意的细节private: true它永远不会被发布到 npm纯粹是仓库内部的“替身包”main: ./index.js服务于旧的 CJS 解析路径不做exports推断时回退到 index.jsexports字段则区分import与require两个条件ESM 引入时命中index.mjs。2. index.js 与 index.mjs两行即全部实现.yarn/joplin-empty-package/index.jsmodule.exports {};.yarn/joplin-empty-package/index.mjs// Empty ESM stub — see index.js. Needed because Nodes ESM resolver looks // at the exports field (or main) rather than guessing index.js the way // the legacy CJS resolver does. export default {};注释解释了为什么必须单独准备一个.mjs文件Node 的 ESM 解析器会优先读exports字段或main不会像传统 CJS 解析器那样自动猜测index.js。如果空包只提供一个 CJS 入口ESM 侧的解析可能落到非预期文件上——对“空包”这种极端轻量的替身来说双入口就是最低成本的兼容性保障。三、resolutions 是怎么把目标依赖指向空包的核心机制在根 package.json 的resolutions块Yarn 的 manifest-level 解析重定向。与空包直接相关的两行是canvasnpm:^2.11.2: link:./.yarn/joplin-empty-package/, huggingface/transformers/sharp: link:./.yarn/joplin-empty-package/逐条解释canvasnpm:^2.11.2定位器写法表示“凡是版本匹配^2.11.2的 npm 源canvas包”。这比裸写包名更精确——只拦截特定版本范围不影响其他场景。huggingface/transformers/sharp这是针对“依赖的依赖”的写法表示“huggingface/transformers所声明的那个sharp依赖”精确地把 transformers 这条链路上的 sharp 替换掉而不伤及仓库中其他可能真实使用 sharp 的包。link:./.yarn/joplin-empty-package/Yarn 的链接协议把目标解析到仓库内的本地目录。于是安装时 Yarn 不再从 npm 拉取真正的canvas/sharp发行版而是把这个本地空目录当作该依赖的实体。这里还有一个值得指出的细节README 正文里的示例写的是 “resolvingcanvasnpm:^2.11tofile:./packages/empty/”而当前仓库中实际生效的路径是link:./.yarn/joplin-empty-package/。从源码结构看这个包从早期的packages/empty/移动到了.yarn/joplin-empty-package/与根目录下同样存放 patches 的.yarn/区域放在一起文档示例属于历史路径以 package.json 的resolutions实际内容为准。从 yarn.lock 验证“剔除”确实生效yarn.lock 中的解析记录可以直接证明重定向已生效约第 24723 行与第 53125 行canvaslink:./.yarn/joplin-empty-package/::locatorroot%40workspace%3A.: resolution: canvaslink:./.yarn/joplin-empty-package/::locatorroot%40workspace%3A. sharplink:./.yarn/joplin-empty-package/::locatorroot%40workspace%3A.: resolution: sharplink:./.yarn/joplin-empty-package/::locatorroot%40workspace%3A.两条resolution都指向同一个本地空包目录说明在 Yarn 的依赖解析结果中canvas与sharp的最终实体就是joplin/empty——原生编译、下载预编译二进制等安装脚本自然无从发生。这也是验证这类手法是否真正起效的最直接方法在 lockfile 中搜索目标包名确认其resolution指向你的空包。四、可复制的操作步骤在自己的 monorepo 中“中性化”一个依赖Joplin 的做法可以抽象为四步适用于任何使用 Yarn 2 的工程建一个空包。新建目录Joplin 放在 .yarn/joplin-empty-package/包含三部分package.json设private: true提供main与exports双入口index.jsmodule.exports {};index.mjsexport default {};防止 ESM 解析落空。在根 package.json 的resolutions中加映射按需选择定位器写法resolutions: { 问题包npm:^x.y.z: link:./空包目录/, 上游包/问题包: link:./空包目录/ }前者拦截 npm 源的某版本范围后者精确拦截“某个上游依赖声明的那个包”。重新安装并检查 lockfile确认resolution已指向空包对照本文第三节yarn.lock的验证方式。确认运行期确实无人调用该包。这是整套手法成立的前提——README 中反复强调的是 “never actually called”。如果代码里真的require(canvas)并调用了其 API空对象会立刻在运行时暴露为属性缺失错误那时应该做的是换实现或补环境而不是剔除。五、注意事项与适用边界只适用于“从不被调用”的依赖。空包返回{}一旦真实调用其 API 就是运行时错误它的价值恰恰在于“占位而不工作”。ESM 场景必须提供.mjs入口。如 index.mjs 注释所述Node ESM 解析不会按 CJS 习惯猜测 index 文件缺了它替身可能失效。定位器要写准。canvasnpm:^2.11.2、huggingface/transformers/sharp这类精确写法可以避免误伤其他合法依赖链写得太宽裸包名可能把真实需要的依赖也换掉。README 中的示例路径已过时。文档示例指向file:./packages/empty/当前仓库实际使用的是link:./.yarn/joplin-empty-package/阅读时以根 package.json 的resolutions为准。依赖 Yarn 的 manifest resolutions 能力。README 引用的 Yarn 官方resolutions文档Manifest resolutions说明了该机制的通用语义npm 的overrides、pnpm 的overrides有类似思路但语法与本协议不同不能照搬link:写法。小结Joplin 用不到二十行的代码一个双入口空包 两条 resolutions 映射解决了一个很实际的问题把canvaspdfjs-dist 的可选原生依赖和sharptransformers 的传递依赖从多端构建的依赖树中“中和”掉既不破坏依赖声明的完整性又避免原生编译在安装链路上制造失败。这套“空包 resolutions”的组合对任何被原生可选依赖折磨过的大型 Node 工程都有直接的参考价值而 yarn.lock 中两条link:解析记录则是这套机制生效的一手证据。【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

2026/9/7 10:24:20

LC滤波电源闭环稳定性解析:从相位裕度到补偿网络设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/7 11:09:27

CMSIS-DSP深度评测:从源码审计到工业落地,FFT性能提升20倍

上个月帮朋友排查一个电力监测设备的谐波异常,最后定位到问题不是算法逻辑,而是性能:他自己写的FFT在Cortex-M4F上跑一次1024点变换要超过3ms,ADC采样窗口还在持续往缓冲区里灌数据,导致每次算完的频谱窗口几乎错位了半…

2026/9/7 11:09:27

单例模式深度剖析:各种实现方式的优缺点对比

目录 一、单例模式的定义和应用场景 (一)定义及基本要点 (二)应用场景 二、饿汉式单例模式 (一)基本代码展示分析 (二)基本分析和建议 三、懒汉式单例模式(双重检…

2026/9/7 11:09:27

直击高频编程考点:动态规划经典算法题总结

目录 一、动态规划总结 (一)基本理解 (二)应用分析 二、相关高频笔试题目练习 (一)最大子序和(Maximum Subarray) (二)最长上升子序列(Longest Increasing Subsequence) (三)最长公共子序列(Longest Common Subsequence) (四)最大子数组乘积(Maximu…

2026/9/7 11:09:27

AI视频创作全流程实战:从提示词到短剧成片

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/7 0:47:43

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/7 0:14:19

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/7 0:14:17

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/7 0:03:36

基于YOLOv8和PyQt5的麦穗稻穗检测识别系统设计与实现

这次我们来看一个把目标检测算法和桌面端工具结合得很典型的项目:基于 YOLOv8 PyQt5 的麦穗稻穗检测识别系统。这个项目本身不是新概念,但它的价值在于落地形态很完整。YOLOv8 负责核心的麦穗稻穗目标检测,PyQt5 负责提供可视化的桌面交互界…

2026/9/7 0:03:36

UL 1642锂电池安全标准全解析:测试项目、认证流程与避坑指南

简介:UL 1642是锂电池安全领域的重要规范,本中文版资源适合锂电池制造商、检测机构工程师及产品认证相关人员阅读,用于理解电池在设计与制造层面的安全要求、测试方法与合规要点。资源共1个PDF文件,压缩包大小834KB,便…

2026/9/7 0:03:36

BS EN 13814-1-2019游乐设施安全标准:设计与制造核心要点解析

简介:BS EN 13814-1:2019是英国采纳欧洲标准EN 13814-1:2019的正式版本,由BSI标准出版,重点规定游乐设施和游乐设备在设计与制造环节的安全准则,与BS EN 13814-2:2019、BS EN 13814-3:2019共同取代旧版BS EN 13814:2004。该标准面…

2026/9/6 11:40:10

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/6 19:33:50

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/6 10:19:40

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…