上周,Yarn(Javascript的程序包管理器)的开发人员宣布了一项新功能 -Plug'n'Play安装。 此功能使您无需使用node_modules文件夹即可运行Node.js项目,该文件夹通常在启动之前安装了项目依赖项。 该功能的描述声明不再需要node_modules-将从程序包管理器的常规缓存中加载模块。
同时,NPM开发人员也宣布了针对该问题的类似解决方案。
让我们仔细看看这些解决方案,并尝试在实际项目中对其进行测试。
问题历史
最初,NodeJS模块化系统完全基于文件系统。 任何对require()
调用require()
映射到文件系统。 为了组织第三方模块,发明了node_modules文件夹,应在其中下载和安装可重用模块和库。 因此,每个项目都有自己独立的依赖关系集,从而浪费磁盘空间。
依赖项安装会占用CI系统中的大部分构建时间,因此加速此步骤将对整个构建时间产生有利的影响。
简化后,模块的安装包括以下步骤:
- 根据有效间隔计算模块的特定版本。
- 从存储库下载所需版本的所有模块,并将其存储在本地缓存中
- 来自本地缓存的模块被复制到项目的node_modules文件夹中
如果已经对模块的前两个步骤进行了充分的优化并可以快速执行,那么在已经缓存了模块的情况下,与第一版的node和npm相比,第三步骤几乎保持不变。
新方法建议摆脱第三步,并通过创建一个表来替换文件的实际复制,该表将请求的模块映射到本地缓存中的副本。
使用符号链接
您可以将符号链接添加到它们在缓存中的位置,而不是实际复制模块。 此方法在另一个替代程序包管理器PNPM中实现。 该方法可能很好用,但是使用符号链接时,文件的双重位置,搜索相邻模块等都存在许多问题。 另外,创建符号链接是我希望以理想的工作方式避免的文件操作。
试纱PNP
您可以在官方说明中阅读有关此功能的更多信息。 本段包含他的简短复述。
启用PNP的Yarn版本现在位于功能分支yarn-pnp中 。
我们使用所需分支在本地克隆存储库
git clone git@github.com:yarnpkg/yarn.git --branch yarn-pnp
纱线装配说明在这里 ,步骤很简单。
构建完成后,将别名添加到yarn的自定义版本并开始使用它:
alias yarn-local="node $PWD/lib/cli/index.js"
即插即用的启用有两种方式:通过标志: yarn --pnp
,或通过package.json
的附加配置: "installConfig": {"pnp": true}
。
例如,Yarn开发人员已经准备好一个演示项目 。 它具有Webpack,Babel和其他现代前端常用的工具。 让我们尝试以不同的方式建立其依赖性,并获得以下结果:
- 典型的
yarn
安装:19s - 通过
yarn --pnp
安装yarn --pnp
:3s
在测量之前,先进行一次冷安装,以便所有必需的模块都已在缓存中。
让我们看看它是如何工作的。 安装pnp之后,会在项目根目录中创建一个附加的.pnp.js
文件,其中包含对Node.js内置的Module类中的本机逻辑的替代。 通过将此文件加载到我们的代码中,我们使require()
函数能够从全局缓存中获取模块,而无需查看node_modules
。 默认情况下,所有内置的yarn命令(例如yarn start
或yarn test
)都会预加载此文件,因此如果您以前已经使用过Yarn,则无需对代码进行任何更改。
除了映射模块之外,pnp.js还执行其他依赖项验证。 如果尝试在package.json
没有声明依赖项的情况下调用require('test')
,则会出现以下错误: Error: You cannot require a package ("test") that is not declared in your dependencies
。 这种改进应提高代码的可靠性和可预测性。
在新方法的缺点中,值得注意的是,没有内置Node机制的直接与node_modules目录一起工作的工具将需要额外的集成。 例如,Webpack和其他前端构建器将需要其他插件,以便他们可以找到捆绑所需的文件。
在演示项目中,有Eslint,Jest,Rollup和Webpack 的解析器草图 。
在我的实验中,Typescript仍然存在问题,它与node_modules的存在密切相关,并且没有简单的方法可以覆盖模块搜索策略。
postintall脚本也会有问题。 由于模块保留在缓存中,因此更改其状态(例如,上传其他文件)的安装后脚本可能会损坏缓存并破坏依赖于该缓存的其他项目。 纱线开发人员建议使用--ignore-scripts
标志禁用脚本执行。 他们已经尝试对Facebook内部的所有项目默认启用此标志,并且没有发现任何严重问题。 从长远来看,鉴于已知的安全问题 ,放弃安装后脚本似乎是一个好步骤。
尝试NPM修补
NPM团队还宣布了其替代解决方案。 他们的新工具tink带有独立的NPM独立模块。 Tink接收package-lock.json
文件作为package-lock.json
,该文件在npm install
时自动生成。 node_modules/.package-map.json
根据锁定文件生成一个node_modules/.package-map.json
,该node_modules/.package-map.json
存储本地模块在缓存中实际位置的投影。
与Yarn不同,没有挂钩文件可以预加载到项目中以进行修补。 相反,建议您使用tink
命令而不是node
来获取正确的环境。 这种方法不太符合人体工程学,因为它将需要修改您的代码才能使其正常工作。 但是,可以做为概念证明。
我试图将模块的安装速度与npm ci
和tink
,但是tink的运行速度甚至更慢,因此我不会给出结果。 显然,与Yarn相比,该项目更加原始,并且根本没有进行优化。 好吧,我们将等待新版本发布。
结论
考虑到其他语言的经验,拒绝node_modules目录是一个合理的步骤,而这种方法最初并不存在。 这将有利地影响CI系统的构建速度,在CI系统中可以在两次构建之间保存程序包缓存。 此外,如果将程序包缓存和.pnp.js
文件从一台计算机传输到另一台计算机,则无需启动Yarn就可以重现环境。 在容器构建系统中这可能很有用:使用缓存安装目录,放入.pnp.js
文件,然后您可以立即运行测试。
新方法看起来不寻常,并且基于所有模块始终在node_modules中可用这一事实打破了一些既定的做法。 但是.pnp.js
文件提供了一个API,该API使您可以从文件的实际位置进行抽象并使用虚拟树。 另外,作为最后的手段,有一个yarn unplug --persist
命令,它将从缓存中提取模块并将其本地放置在node_modules
。
无论如何,还没有完成任何事情,甚至还没有完成Yarn中的pull请求,我们应该期待变化。 但是,对我而言,在实践中尝试该功能的Alpha版并在我的几个个人项目中对其进行测试很有趣,并确保该方法确实有效,从而使安装速度更快。
参考文献