Typescript 类型声明 All-in-one
TypeScript 的类型声明文件 .d.ts 提供了 JavaScript 代码的附加元信息,支持类型提示和自动补全。使用 DefinitelyTyped 仓库可以为没有类型声明的 npm 包提供类型支持,类型定义可通过 type 和 interface 进行,declare 语法用于为已有的 JS 变量或函数添加类型。三斜杠指令用于引入额外的文件依赖。
本文介绍了前端包管理器Yarn的不同版本,特别是Yarn v2及其PnP(Plug'n'Play)机制。Yarn v1与npm相比具有离线模式和lock文件等优点,而Yarn v2引入了monorepo支持和零依赖安装。PnP机制替代了传统的node_modules目录,通过优化依赖解析过程,提高了安装速度并解决了依赖版本冲突和幽灵依赖问题。此外,文中还讨论了Yarn的插件化设计、兼容性问题及其解决方案。
yarn是一个Facebook出品的老牌包管理器,相对于npm要新一点,但相对于pnpm又要旧一点,也已经经过了多个大版本迭代。
首先声明如果是新创建的项目,不推荐任何人使用Classic Yarn进行管理,使用npm-v8+、yarn-v2+或者pnpm都是更好的选择。(是的,新的npm确实优于Classic Yarn)
yarn和npm-v3一样采用了扁平化依赖结构,即所有依赖平铺在根目录下,避免了重复安装依赖的问题。
yarn-v1和npm-v4是同期的产品,yarn相比npm-v4来说有如下优点:
安装过程优化:
yarn的新版(Berry)和旧版(Classic)的工作方式截然不同,新版本中比较显著的新特性有:monorepo支持,PnP机制,零依赖安装等。这里将重点介绍PnP机制,monorepo相关内容请移步这篇文章。
根据官方文档的指引,对于node>=16.10版本,推荐使用corepack进行安装,如果你对corepack还不熟悉,可以移步我关于npm的文章。这里默认你已经学会并启用了corepack。
在终端中运行下列指令安装最新版的yarn。
$ corepack prepare yarn@stable --activate
# 安装完成后检查版本
$ yarn -v
如果安装成功,将会提示3.x的版本,如下图所示:
接下来,我们使用yarn2去管理项目和依赖:
yarn init -2
迁移现有项目:yarn set version stable
,然后运行yarn install
更新依赖树
在现有项目中启用PnP(这可能导致兼容性问题)
yarn dlx @yarnpkg/doctor
来检测当前已经安装的依赖是否会产生兼容性问题.yarnrc.yml
,设置nodeLinker: "pnp"
即可启用PnP需要注意的是,默认情况下**yarn init**仍是1.x的模式,除非你指定yarn版本为2(如 yarn init -2)才会启用Zero-Installation特性。
这里为了演示,我们将使用yarn2创建一个新项目:
$ yarn init -2
上述命令将为我们在当前目录创建package.json
文件,并写入一些必要的信息(如项目信息、包管理器信息等),-2
选项会为我们完成零安装的初始化工作,包括yarn注入(hydrate)和各种相关配置等,最终我们得到一个如下的目录。
PnP是yarn2中默认启用的新的包解析方式,我们先试着安装loadash到我们的项目(运行yarn add lodash
),再检查目录树:
我们惊讶地发现居然没有node_modules的存在!我们明明安装了新包,但是却没有生成node_modules目录,而是在.yarn/cache中增加了一个压缩包,这就是yarn2的PnP技术。
Plug'n'Play(即插即用)于2018年9月推出,是Node的新模块安装策略。基于其他语言的先前工作(例如 PHP 的自动加载),它以几乎完全向后兼容的方式实现了常规的 CommonJS require 工作流程。
要知道为什么PnP机制比node_modules更好,我们就要知道node_modules为什么不好:
package.json
中的包也可能被访问并产生潜在的问题)yarn认为,造成上述问题的根本原因是node中并没有包的概念,但是包解析却交给了node而非掌握依赖树信息的包管理器(如yarn),因此yarn要代替node去做包解析的工作,直接告诉node应该调用哪个包、这个包在哪里,这就是Plug'n'Play机制的核心思想。
Plug'n'Play替Node完成了包解析的工作,它将直接告诉Node需要的包是哪个,在哪里等信息;同时它为了优化依赖构建过程,直接干掉了node_modules目录,使用了新的存储结构,加快了安装速度。
值得注意的是,由于PnP机制完全替换了基于扁平化的node_modules结构的解析机制,因此扁平依赖的那些问题如依赖版本冲突、幽灵依赖等问题自然也就不存在了。
yarn2**使用****.pnp.cjs
**替代node_modules来帮助node解析包:.pnp.cjs
中包含了各种映射信息,并告诉node如何使用PnP机制去使用包
.pnp.cjs
中包含了包的依赖信息
.pnp.cjs
的出现替代了node_modules,使yarn只需要创建一个文本文件(而不是潜在的数万个),这优化了I/O性能,减小了路径的复杂度,加快了项目的启动(.pnp.cjs
使node不用再使用古老的层层遍历方法去寻找包)
.pnp.cjs
中记录下载缓存的位置,减小了安装时的I/O开销(复制和解压)和空间占用require
函数,让node直接去对应位置调用包,避免了目录遍历的I/O开销为了让node可以使用PnP,有以下途径
package.json
中的script
,其中的所有命令都会通过yarn和PnP运行yarn node
命令require('./.pnp.cjs').setup();
来初始化PnP这里由于代码太长就直接放截图了,放在开头的就是项目和依赖的基本信息,后面的一大串就是PnP的具体实现(大概)
通过刚才的例子我们可以看出,我们添加的loadash包放在了.yarn/cache中,并作为zip压缩文件存储(而不是有很多小文件的文件夹)。与之类似的,所有从远程存储库下载的包都会存储在本地缓存内(默认是.yarn/cache),下次使用同样的包时将直接使用缓存内下载的包(具体过程在.pnp.cjs
中有提到)。
在yarn2中,离线缓存(offiline cache)与PnP机制紧密绑定,因此无法完全禁用,但完全删除缓存是安全的,它将在下次yarn install时重建。除此之外,使用cacheFolder属性可以定义缓存位置。
yarn cache clean
手动清理;全局缓存则需要通过yarn cache clean --mirror
来清理全局缓存:yarn默认情况下不会共享全局缓存(而是将全局下载缓存复制到项目高速缓存中),如果设置enableGlobalCache
为true
,就会使项目共享全局缓存,即直接使用~/.yarn/cache
作为项目的高速缓存而不再复制
硬链接:可以开启硬链接功能以降低磁盘空间占用
yarn.lock
中的linkType: Hard
表示这里的包允许进行硬链接和其他操作(如unplug);反之若为Soft
则表示这里的包并不是原样本,只允许按原样调用该包.yarnrc.yml
中的nmMode: "hardLinksGlobal"
才表示开启硬链接(多份文件引用一份空间),该功能默认关闭下面我们将在yarn3中用Vite创建一个React应用具体来体验PnP是如何工作的,如何开启自己的PnP工作流。
根据Vite官方的指导(所使用的打包器Rollup已经支持PnP):
$ yarn create vite # 使用create模板语法
# 初始化项目
$ cd yarn-vite
$ yarn install
运行过程截图如下:
安装完成后,我们会得到一个这样的目录:
可以看到,这里仍是有一个node_modules文件夹的,里面存放的是vite的可执行文件;同时.yarn中还有部分unplugged的包(排除在PnP压缩包之外)。这些都是PnP的兼容性措施,但是我们注意到这里并没有指定.yarnrc.yml
,说明部分操作可能是写在包中或者yarn.lock
/.pnp.loader.mjs
中的。
具体的兼容性措施相关内容将留作后文,这里我们只需要了解一个PnP项目基础的结构即可。
$ yarn run dev
运行项目检验:
在前面的PnP机制中我们了解到,和node_modules不同,PnP中所有的包都放在了缓存的压缩包中,因此IDE不能直接读取。
在VSCode中我们也确实遇到了“找不到模块”的错误,如下图所示:
根据官方的提示,我们按步骤运行以下操作:
yarn dlx @yarnpkg/sdks vscode
初始化IDE集成最终我也没有解决这个报错问题,是使用nodeLinker: "node-modules"解决的。(还是推荐pnpm)
个人猜测是Vite与PnP的兼容性导致的,问题可能出在tsconfig中某一环,因为我使用之前例子中的require和此前在monorepo改造一文的开发过程中都并没有报错,而在Vite+pnpm的实践中也没有报错。
如果有谁解决了这个问题不嫌麻烦可以联系我一下QaQ([email protected])
有了PnP替代node_modules作为依赖解析的方案后,yarn想更进一步:既然我已经解决了node_modules太重的问题,为什么我不能直接干掉项目的安装过程(****install
****过程)呢?这就是所谓的“零安装”(Zero-Installs)。
其实零安装的实现思路非常简单,就是把所有模块都放在存储库重一起管理,我知道这听起来很蠢,但这是因为之前的node_modules中几万个零碎的文件和目录让这几乎是不可实现的,在yarn中基于PnP的依赖解析和压缩包(zip或tar)管理的缓存让这变得可能。(至少传一个很大的JS和很多压缩包比一个几十层的目录要轻松很多)
对于一个中到大型项目,Git不支持一个包含135k个未压缩文件的node_modules文件夹(约1.2GB),但支持包含2k个Zip文件的.yarn/cache缓存目录(约139MB)。 ——数据来自yarn官网
零安装带来了一个显著的好处:解决了困扰前端项目许久的依赖不确定问题,我现在直接让项目带着依赖,省去了安装的过程,就能保证所有人安装的依赖都是一样的了,甚至连yarn.lock
都不用了(因为不用安装)。
注意:要启用零安装,PnP是必须的,且在.gitignore中不能包含.pnp.cjs和.yarn。但是否启用零安装,主要取决于你对依赖确定性和存储库大小的重视程度。
yarn-v2在构建过程中采用了模块化的思想,使得开发者可以开发插件来调用这些模块化的API。这是一个很底层的设计,yarn团队利用插件重构了yarn-v2的大部分功能,甚至yarn add
和yarn install
实际上也是预装的插件。由此可见插件可以做到多少事!
插件可以在任意一个生命周期调取Hook来改变yarn的行为:包安装,如解析(resolve)->请求(fetch)或者命令执行。
package.json
中指定的版本范围转换成一个确定的版本yarn add
那样可以通过 yarn plugin import <name>
命令来安装插件。
下面是一些官方插件:
@types
包作为依赖项)【在yarn4中将默认集成】workspace
相关命令由于PnP是比较激进的机制,它完全取消了node_modules,出现一些兼容性问题在所难免(虽然它也带来了一些诱人的好处和新概念)。
.yarnrc.yml
好在.yarnrc.yml
提供了丰富的配置项,可以解决大部分的兼容性问题。
以下是一些常用的配置项(给出的值是默认值)
cacheFolder: "./.yarn/cache"
:指定yarn cache的目录(启用全局缓存请用enableGlobalCache
)defaultSemverRangePrefix: "^"
:添加包时默认记录的版本修饰符(默认为锁定Patch)enableGlobalCache: false
:如为true
将忽略缓存路径设置,将该高速缓存文件存储到共享相同配置的所有本地项目共享的文件夹中(~/.yarn/cache)globalFolder: "./.yarn/global"
:全局文件夹位置httpProxy: "http://proxy:4040"
& httpsProxy: "http://proxy:4040"
:代理设置(国内常用)nmMode: "classic"
:控制存储项目本地缓存的方式,适用于空间敏感用户
classic
:复制模式hardlinks-global
:硬链接到全局缓存nodeLinker: "pnp"
:控制安装包的方式(所谓Linker就是将包的路径和调用连接起来的方式)
pnp
:yarn2默认的机制,如上文所述pnpm
:使用pnpm的硬链接方式node_modules
:回退到npm/yarn-classic的方式,生成node_modules文件夹,一般是解决兼容性问题的终极杀手锏,但也放弃了很多性能优势npmRegistries:
:控制npm源pnpMode: "strict"
:控制能否调用到没有在package.json
中声明但在npm/yarn-classic中由于提升到顶层可以访问的包(幽灵依赖)
strict
:不允许访问“幽灵依赖”loose
:允许访问“幽灵依赖”,这可能是部分兼容性问题的解决方案yarnPath: "./scripts/yarn-2.0.0-rc001.js"
:yarnPath
是目前在项目中安装Yarn的首选方式,因为它可以确保您的整个团队将使用完全相同的Yarn版本,而无需单独更新(在yarn init -2
时就会默认生成这个设置项)yarn unplug <pkg>
主要用于将某个包解压出来,以便修改代码。
PnP的设计理论上与所有使用原生require
API的包兼容,但有些包喜欢自己实现Node解析过程,因此它们在不做特殊适配的情况下不能与PnP兼容。pnpify
这个包就提供了一种解决该问题的方案。
pnpify
会拦截该请求,并转换成对PnP API的调用,再返回一个模拟的node_modules目录使用pnpify
需要以下步骤:
yarn add @yarnpkg/pnpify
pnpify
运行不兼容的工具,如:yarn pnpify tsc
作者: ChlorineC
创建于: 2023-06-09 15:01:00
更新于: 2025-01-05 09:03:00
版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
TypeScript 的类型声明文件 .d.ts 提供了 JavaScript 代码的附加元信息,支持类型提示和自动补全。使用 DefinitelyTyped 仓库可以为没有类型声明的 npm 包提供类型支持,类型定义可通过 type 和 interface 进行,declare 语法用于为已有的 JS 变量或函数添加类型。三斜杠指令用于引入额外的文件依赖。
本文讨论了Node.js中ESM与CJS的模块解析策略,强调了tsconfig.json中的module和moduleResolution字段对模块解析的影响。通过分析Node对两种模块标准的支持,指出了配置不匹配导致的错误,并提供了解决方案,包括将moduleResolution修改为Node16以确保正确的解析算法。最后,强调了在使用ESM时必须在导入语句中包含文件扩展名。
在React中,useRef和useEffect常用于管理组件的可变值和副作用,但在响应DOM元素挂载时,使用useCallback作为ref的回调函数更为有效。useCallback确保在元素挂载和卸载时被调用,避免了因useRef导致的生命周期回调未触发的问题。文中还介绍了其他几种优化Node操作的方法,如使用useState、useStateRef和useRefWithCallback,以提高性能和管理复杂性。
本文深入探讨了React中的useMemo和useCallback的使用场景和性能影响。useMemo用于缓存计算结果以减少不必要的计算,适合在计算开销较大的情况下使用,而useCallback用于缓存函数定义以避免不必要的组件刷新。作者强调,过度使用这两个Hook可能导致性能下降,建议在实际需要时再使用,并提出了其他优化方案,如使用useReducer和合理组织组件逻辑。