普通视图

发现新文章,点击刷新页面。
昨天以前首页

pnpm monorepo 联调:告别 --global 参数

作者 JinSo
2025年10月19日 15:08

前言

在之前的文章《pnpm monorepo 联调方案》中,我详细介绍了如何使用 pnpm linkpnpm link --global 来解决 monorepo 环境下的调试难题。

时间过去了一段时间,pnpm 也在不断演进。最近在使用过程中,我发现了一个有趣的变化:执行 pnpm link 时不再需要添加 --global 参数,同时 pnpm 会自动创建 pnpm-workspace.yaml 文件。这引起了我的好奇心,决定深入研究一下 pnpm 10.x 版本中 link 功能的最新变化。

pnpm 10.x 中关于 link 的变更

最大的变化就是 去除了 --global 参数

之前我们这样操作:

# 库中
pnpm link --global

# 项目中
pnpm link --global <pkg>

现在直接:

# 库中
pnpm link

# 项目中
pnpm link <pkg>

看起来是小改动,但其实是把之前的 pnpm link --global 的行为直接变成了 pnpm link 的默认行为。

从官方文档也能看出来,现在的 pnpm link 描述和之前 9.x 版本的 pnpm link --global 完全一样。

image0.png

image1.png

取消链接还是用 pnpm unlink

pnpm unlink <pkg>

实际使用体验

在库中执行 link

现在在基础库中执行 pnpm link,会直接链接到全局:

cd ~/packages/core
pnpm install
pnpm link

# 输出示例
❯ pnpm link
 WARN  The package @easy-editor/core, which you have just pnpm linked, has the following peerDependencies specified in its package.json:
  - mobx@^6.13.5
The linked in dependency will not resolve the peer dependencies from the target node_modules.
This might cause issues in your project. To resolve this, you may use the "file:" protocol to reference the local dependency.
√ The modules directory at "C:\\Users\\user\\AppData\\Local\\pnpm\\global\\5\\node_modules" will be removed and reinstalled from scratch. Proceed? (Y/n) · true
Recreating C:\\Users\\user\\AppData\\Local\\pnpm\\global\\5\\node_modules

在项目中链接库

在项目中链接也更直接了:

cd ~/projects/my-project
pnpm link @easy-editor/core

# 输出示例
❯ pnpm link @easy-editor/core
 WARN  The package @easy-editor/core, which you have just pnpm linked, has the following peerDependencies specified in its package.json:
  - mobx@^6.13.5
The linked in dependency will not resolve the peer dependencies from the target node_modules.
This might cause issues in your project. To resolve this, you may use the "file:" protocol to reference the local dependency.
√ The modules directory at "D:\\Programming\\EasyEditor\\EasyDashboard\\node_modules" will be removed and reinstalled from scratch. Proceed? (Y/n) · true
Recreating D:\\Programming\\EasyEditor\\EasyDashboard\\node_modules
dependencies:
    + @easy-editor/core 0.0.0 <- C:\\Users\\user\\AppData\\Local\\pnpm\\global\\5\\node_modules\\@easy-editor\\core

自动生成的 workspace 配置

这是个新发现,链接完成后,pnpm 会自动在项目根目录生成 pnpm-workspace.yaml

overrides:
  '@easy-editor/core': link:C:/Users/user/AppData/Local/pnpm/global/5/node_modules/@easy-editor/core

这样就能清楚地看到当前项目链接了哪些包,比之前透明多了。

多库联调的改进

之前需要给每个库都加 --global,现在直接:

# 在各个库中
cd ~/packages/core && pnpm link
cd ~/packages/designer && pnpm link
cd ~/packages/utils && pnpm link

# 在项目中
cd ~/projects/my-project
pnpm link @myorg/core
pnpm link @myorg/designer
pnpm link @myorg/utils

对于 workspace:* 这种依赖,pnpm 还是会自动处理,和之前一样智能。

前后对比

pnpm 9.x pnpm 10.x
全局链接 pnpm link --global pnpm link
项目链接 pnpm link --global <pkg> pnpm link <pkg>
workspace 配置 手动管理 自动生成
操作复杂度 需要记住加 --global 更简单直接

总结

pnpm 10.x 的这个改动虽然看起来不大,但确实让联调操作更简单了。不用再记那个 --global 参数,直接 pnpm link 就完事。

加上自动生成的 workspace 配置文件,整个链接状态也更透明了。如果你还在用旧版本,建议升级试试,体验确实有提升。


参考资料

  1. pnpm link | pnpm 官方文档
  2. "pnpm link --global" in v10 behaves differently from v9, breaking global linking.

alien-signals 系列 —— 认识下一代响应式框架

作者 JinSo
2025年10月8日 16:16

前段时间,社区掀起了一股 alien-signals 的热潮,其惊人的性能表现让许多开发者印象深刻。作为一名前端开发者,我深入研究了它的源码,探索其性能优势的秘密。

本文将开启一个全新的系列,深入剖析 alien-signals 的使用方法、实现原理和性能优化等内容。让我们一起揭开 alien-signals 的神秘面纱。

为什么要关注 alien-signals?

在深入技术细节之前,让我们先看看 alien-signals 在性能基准测试中的表现。根据 js-reactivity-benchmark 的数据,alien-signals 在各项性能指标上都遥遥领先:

image.png

各响应式框架性能对比,alien-signals 在各项指标上遥遥领先

这样的性能表现,加上 Vue 3.6 即将采用 alien-signals 重构响应式系统的消息,使得它成为了前端社区的焦点。

Signals 概念

如果对 Signals 概念还不熟悉,推荐先阅读我之前的文章:理解 Signal 是如何工作的,其中详细介绍了 Signals 的基本原理和工作机制。

简单来说,Signals 是一种响应式编程模式,它允许我们创建可观察的值和计算属性,当依赖发生变化时自动更新。

响应式库

先简单了解一下什么是响应式框架:

Reactivity is the future of JS frameworks! Reactivity allows you to write lazy variables that are efficiently cached and updated, making it easier to write clean and fast code. —— 引自Super Charging Fine-Grained Reactive Performance · milomg.dev

响应式是 JS 框架的未来!响应性允许您编写有效缓存和更新的惰性变量,从而更轻松地编写干净、快速的代码。

而且响应式库是 Solid、Qwik、Vue 和 Svelte 等现代 Web 组件框架的核心。在某些情况下,您可以将细粒度的响应式状态管理添加到其他库,例如 Lit 和 React。

关于响应式算法的深入探讨,推荐阅读 深入探索细粒度响应式框架的性能优化 ,其中详细对比了 MobX、Preact Signals 和 Reactively 三种主流算法的实现原理。

响应式框架的目标

响应式库的目标是在响应式函数的源发生变化时运行响应式函数。

此外,响应式库还应该是:

  • 高效:永远不要过度执行响应式元素(如果它们的源没有改变,请不要重新运行)
  • 无故障:永远不要允许用户代码看到只有一些响应式元素更新的中间状态(当你运行响应式元素时,每个源都应该更新)

响应式框架的分类

在深入 alien-signals 之前,我们需要了解响应式框架的基本分类。

  • 按执行时机分类

    响应式框架根据执行时机可以分为两大类:

    1. Lazy(惰性) :只在结果被访问时才进行计算,延迟执行,按需计算的思想能有效减少冗余计算。

      • 代表:Solid.js、Preact Signals、Vue、alien-signals
    2. Eager(即时性) :数据变化时立即计算,实时响应,但可能导致频繁计算的性能问题。

      • 代表:MobX

    💡 关于 Solid 的 Signals 的原理,可以参考我的另一篇文章:Solid 之旅 —— Signal 响应式原理

  • 按更新传播算法分类

    根据 维基百科对响应式编程的定义,更新传播算法可以分为三种模式:

    1. Push-based(推送模型)

      • 相当于 Eager 模型
      • 依赖项变化时立即推送完整的变化信息给订阅者
      • 类似于服务端向客户端的 SSE 推送机制
    2. Pull-based(拉取模型)

      • 相当于 Lazy 模型
      • 只在需要时(如读取 computed 值)才拉取依赖项的变化
      • 类似于客户端向服务端的轮询机制
    3. Push-pull hybrid(推拉混合模型)

      • 结合了推送和拉取的优点
      • Push 阶段:依赖项变化时推送脏标记,通知订阅者需要更新
      • Pull 阶段:订阅者在需要时拉取具体的变化值

Vue Signals 进化论

要理解 alien-signals 的由来,我们需要回顾 Vue 响应式系统的演进历程。这部分内容深受 Vue Signals 进化论系列文章 的启发。

  • Vue 3.5:借鉴 Preact Signals

    Vue 3.5 的响应式重构直接受到了 Preact Signals 的启发,主要借鉴了两个核心设计:

    1. 版本计数(Version Counting) :用于快速判断依赖是否发生变化
    2. 双向链表结构:高效管理依赖关系

    这些设计思想将在后续的系列文章中详细解析。

  • Vue 3.6:拥抱 alien-signals

    alien-signals 在继承 Preact Signals 优秀设计的基础上,进行了大量性能优化:

    • 延续双向链表设计:保持了高效的依赖管理
    • 简化节点属性:减少内存占用
    • 贴近 Vue 的命名规范:更好的生态融合
    • 极致的性能优化:包括模拟递归调用栈、内存对齐、位运算等技巧

    虽然这些优化在一定程度上牺牲了代码的可读性,但换来的是卓越的运行时性能。

使用

基础 API 使用

alien-signals 提供了简洁直观的 API:

import { signal, computed, effect } from 'alien-signals';

// 创建响应式信号
const count = signal(1);

// 创建计算属性  
const doubleCount = computed(() => count() * 2);

// 创建副作用
effect(() => {
  console.log(`Count is: ${count()}`);
}); 
// 输出: Count is: 1

// 读取计算属性
console.log(doubleCount()); 
// 输出: 2

// 更新信号值
count(2); 
// 触发 effect,输出: Count is: 2

// 计算属性自动更新
console.log(doubleCount()); 
// 输出: 4

Effect Scope

alien-signals 还提供了作用域管理功能,方便批量管理副作用:

import { signal, effect, effectScope } from 'alien-signals';

const count = signal(1);
// 创建作用域
const stopScope = effectScope(() => {
  effect(() => {
    console.log(`Count in scope: ${count()}`);
  });
  // 输出: Count in scope: 1
});

count(2);
// 输出: Count in scope: 2

// 停止作用域内的所有副作用
stopScope();

count(3);
// 不再有输出,因为 effect 已被清理

架构设计

了解了基本使用后,让我们简单地了解 alien-signals 的架构设计。alien-signals 采用了优雅的双层架构设计:

1. System 层(响应式系统核心)

提供了 createReactiveSystem() 工厂函数,用于构建自定义响应式 API:

import { createReactiveSystem } from './system.js';

const {
  link,  // 建立依赖关系
  unlink,  // 解除依赖关系
  propagate,  // 传播更新
  checkDirty,  // 检查脏状态
  endTracking,  // 结束依赖追踪
  startTracking,  // 开始依赖追踪
  shallowPropagate,  // 浅层传播
} = createReactiveSystem({
  update,  // 更新回调
  notify,  // 通知回调
  unwatched,  // 未观察状态回调
});

2. Surface API 层(面向用户的 API)

基于 System 层构建的标准响应式 API,包括 signal、computed、effect 等。您可以参考 alien-signals 的实现 来构建自己的响应式 API。

总结

本文介绍了 alien-signals 的背景、核心概念以及基本使用方法。我们了解到:

  • alien-signals 是当前性能最优秀的响应式框架之一
  • 它继承了 Preact Signals 的优秀设计,并进行了极致的性能优化
  • Vue 3.6 即将采用它来重构响应式系统,足见其价值
  • 其双层架构设计既保证了性能,又提供了良好的扩展性

在下一篇文章中,我将深入 alien-signals 的 system 层,详细剖析其响应式机制的实现原理,包括依赖追踪、更新传播、脏检查等核心算法。敬请期待!

参考资料

  1. Vue Signals 进化论(v3.5):Preact 重构启示录
  2. Vue Signals 进化论(v3.6):Alien Signals 终局之战?
  3. Reactive programming - Wikipedia
  4. Super Charging Fine-Grained Reactive Performance
  5. stackblitz/alien-signals: 👾 The lightest signal library
❌
❌