JavaScript依赖性的详细指南

620 阅读7分钟

每个JavaScript项目的开始都是雄心勃勃的,尽量不要在途中使用太多的NPM包。即使我们这边做了很多努力,包最终还是开始堆积起来。package.json ,随着时间的推移,行数越来越多,package-lock.json ,在添加依赖关系时,增加或删除的数量使拉动请求看起来很可怕。

"这很好"--团队领导说,其他团队成员也点头同意。 你还能做什么?我们很幸运,因为JavaScript的生态系统是活生生的,而且很兴旺!我们不应该再去重新发明。我们不应该每次都重新发明车轮,试图解决开源社区已经解决的问题。

比方说,你想建立一个博客,你想使用Gatsby.js。试着安装它并把它保存到你的依赖项中。恭喜你,你刚刚用它增加了19000个额外的依赖项。这种行为可以吗?JavaScript的依赖树能变得多复杂?它是如何变成一个依赖地狱的呢? 让我们深入了解一下细节,找出答案。

什么是JavaScript包?

NPM--Node包管理器,拥有世界上最大的JavaScript包注册表。它比RubyGems、PyPi和Maven的总和还要大!这是根据Module Counts网站的数据,该网站追踪了最受欢迎的软件包注册表中的软件包数量。

你一定在想,这可是一大堆代码啊。的确如此。为了让你的代码成为NPM包,你需要在你的项目中设置一个package.json 。这样,它就成为一个你可以推送到NPM注册中心的包。

什么是package.json?

根据定义,package.json:

  • 列出你的项目所依赖的包(列出依赖项)
  • 使用语义版本规则指定你的项目可以使用的软件包的版本
  • 使你的构建具有可复制性,因此,更容易与其他开发者分享。

把它想象成一个立体的README。你可以在这里定义你的包的依赖关系,编写构建和测试脚本,以及以你想要的方式对你的包进行版本管理,并描述它和它的作用。我们最感兴趣的是在package.json 里面指定依赖关系的能力。

这听起来已经有点混乱了。想象一下,有一个软件包依赖于另一个软件包,而另一个软件包又依赖于另一个软件包。是的,它可以像这样继续下去,只要你喜欢。这就是为什么当你安装一个软件包--Gatsby时,你会得到19000个额外的依赖关系的原因。

package.json中的依赖关系类型

为了更好地理解依赖性是如何随着时间的推移而积累的,我们将通过一个项目可能具有的不同类型的依赖性。在package.json中,你可以遇到几种依赖关系:

  • dependencies - 这些是你在项目代码中依赖和调用的基本依赖关系
  • devDependencies - 这些是你的开发依赖,例如,一个用于格式化代码的更漂亮的
  • peerDependencies - 如果你在package.json中设置了一个同行的依赖,你就是在告诉安装你的软件包的人,他们需要那个指定版本的依赖。
  • optionalDependencies - 这些依赖是可选的,如果不安装它们,不会破坏安装过程
  • bundledDependencies - 这是一个将与你的包捆绑在一起的包的阵列。当一些第三方库不在NPM上时,或者你想把你的一些项目作为模块包括进来时,这很有用。

package-lock.json的用途

我们都知道,在拉动请求中,那个文件总是被大量添加和删除,我们常常认为它是理所当然的。package-lock.json ,每次package.json文件或node_modules目录发生变化时,它都会自动生成。它保留了安装时生成的确切的依赖树,这样,任何后续的安装都可以生成相同的树。这就解决了我有另一个版本的依赖关系,而你有另一个版本的问题。

让我们拿一个项目来说,它的依赖项中有React,在package.json 。如果你去看package-lock.json ,你会看到这样的东西:

    "react": {
      "version": "16.13.0",
      "resolved": "https://registry.npmjs.org/react/-/react-16.13.0.tgz",
      "integrity": "sha512-TSavZz2iSLkq5/oiE7gnFzmURKZMltmi193rm5HEoUDAXpzT9Kzw6oNZnGoai/4+fUnm7FqS5dwgUL34TujcWQ==",
      "requires": {
        "loose-envify": "^1.1.0",
        "object-assign": "^4.1.1",
        "prop-types": "^15.6.2"
      }
    }

package-lock.json 是你的项目中依赖关系的一个大列表。它列出了它们的版本、模块的位置(URI)、代表模块完整性的哈希值和它所需要的包。如果你继续阅读,你可以找到React需要的每个包的每个条目,等等。这就是实际的依赖性地狱的所在。它定义了你的项目所需要的一切。

分解Gatsby.js的依赖关系

那么,我们是如何通过安装一个依赖项而最终获得19000个依赖项的呢?答案是:依赖关系的依赖关系。这就是我们试图安装Gatsby.js时发生的情况。

$ npm install --save gatsby

...

+ gatsby@2.19.28
added 1 package from 1 contributor, removed 9 packages, updated 10 packages and audited 19001 packages in 40.382s

如果我们看一下package.json,那里只有一个依赖项。但如果我们偷看package-lock.json,它是一个刚刚生成的近14000行的怪物。更详细的答案在于Gatbsy.js GitHub repo里面的package.json。 有很多直接的依赖关系--npm统计的是132个。 想象一下这些依赖关系中的一个只有一个其他的依赖关系--你刚刚把数量增加到264个依赖关系。当然,现实世界的情况是远远不同的。每个依赖关系都有很多,而不仅仅是一个额外的依赖关系,这个名单还在继续。

例如,我们可以尝试看看有多少库需要lodash.

$ npm ls lodash
example-js-package@1.0.0
└─┬ gatsby@2.19.28
  ├─┬ @babel/core@7.8.6
  │ ├─┬ @babel/generator@7.8.6
  │ │ └── lodash@4.17.15  deduped
  │ ├─┬ @babel/types@7.8.6
  │ │ └── lodash@4.17.15  deduped
  │ └── lodash@4.17.15  deduped
  ├─┬ @babel/traverse@7.8.6
  │ └── lodash@4.17.15  deduped
  ├─┬ @typescript-eslint/parser@2.22.0
  │ └─┬ @typescript-eslint/typescript-estree@2.22.0
  │   └── lodash@4.17.15  deduped
  ├─┬ babel-preset-gatsby@0.2.29
  │ └─┬ @babel/preset-env@7.8.6
  │   ├─┬ @babel/plugin-transform-block-scoping@7.8.3
  │   │ └── lodash@4.17.15  deduped
  │   ├─┬ @babel/plugin-transform-classes@7.8.6
  │   │ └─┬ @babel/helper-define-map@7.8.3
  │   │   └── lodash@4.17.15  deduped
  │   ├─┬ @babel/plugin-transform-modules-amd@7.8.3
  │   │ └─┬ @babel/helper-module-transforms@7.8.6
  │   │   └── lodash@4.17.15  deduped
  │   └─┬ @babel/plugin-transform-sticky-regex@7.8.3
  │     └─┬ @babel/helper-regex@7.8.3
  │       └── lodash@4.17.15  deduped
  ...

幸运的是,它们中的大多数都在使用同一个版本的lodash ,它只需要一个lodash 来安装在node_modules 里面。真实世界的生产项目往往不是这样的。有时,不同的软件包需要其他软件包的不同版本。这就是为什么有大量的笑话说node_modules 目录有多重。在我们的例子中,它并不那么糟糕:

$ du -sh node\_modules
200M    node\_modules

200兆字节并不是那么糟糕。我见过它很容易就超过700MB。如果你对哪些模块占用了大部分内存感兴趣,你可以运行以下命令:

$ du -sh ./node\_modules/\* | sort -nr | grep '\\dM.\*'
 17M    ./node\_modules/rxjs
8.4M    ./node\_modules/@types
7.4M    ./node\_modules/core-js
6.8M    ./node\_modules/@babel
5.4M    ./node\_modules/gatsby
5.2M    ./node\_modules/eslint
4.8M    ./node\_modules/lodash
3.6M    ./node\_modules/graphql-compose
3.6M    ./node\_modules/@typescript-eslint
3.5M    ./node\_modules/webpack
3.4M    ./node\_modules/moment
3.3M    ./node\_modules/webpack-dev-server
3.2M    ./node\_modules/caniuse-lite
3.1M    ./node\_modules/graphql
...

啊,rxjs,你真是个狡猾的家伙。有一个简单的命令可以帮助你解决node_modules 的大小问题,并将这些依赖关系扁平化,它就是npm dedup:

$ npm dedup
moved 1 package and audited 18701 packages in 4.622s

51 packages are looking for funding
  run \`npm fund\` for details

found 0 vulnerabilities

重复数据删除动作将尝试简化依赖树的结构,寻找依赖关系之间的共同包,并将它们移动,使它们得到重复使用。我们上面的lodash 的例子就是这种情况。很多软件包都在lodash@4.17.15 ,所以没有其他版本的lodash ,不得不安装。当然,我们从一开始就得到了这个,因为我们刚刚安装了我们的依赖项,但是如果你已经为package.json 添加了一段时间的依赖项,考虑运行npm dedup 。如果你正在使用yarn ,你可以做yarn dedupe,但没有必要,因为这个过程在你运行yarn install ,所以你就可以走了。

依赖关系的可视化

如果你曾经对你的项目依赖关系的样子感兴趣,有几个工具可以使用。我所使用的一些工具以不同的方式显示你的或其他项目的依赖关系。

npm.anvaka.com

在这里你可以看到每个包是如何相互连接的,它看起来就像一个巨大的网。这几乎打破了我的浏览器,因为Gatsby.js有这么多的依赖性。点击这里看Gatsby.js的依赖性如何连接。它还可以用3D显示。

npm.broofa.com

这是一个类似于流程图的依赖关系的视图。如果你想看一下,对Gatsby.js来说,它变得很复杂。 你可以标记每个依赖关系的npms.io分数,它会根据分数给它们涂上不同的颜色。你也可以上传你的package.json,让它在那里可视化。

软件包恐惧症

如果你想在运行npm安装之前检查一个包会占用多少空间,这是一个很好的工具。它会告诉你在NPM注册处发布的大小,以及在你的项目中安装后磁盘上的大小。

大权在握,责任重大

总之,JavaScript和NPM是很好的,而且可以灵活地从大量的依赖项中挑选,这一点非常棒。做npm install ,以节省几行代码是如此简单,以至于有时我们忘记了这一切背后发生了什么。

读到这里,你应该能够更好地理解JavaScript的依赖树。无论你是在添加一个太大的库,还是在探索你的项目的依赖关系有多复杂,你都可以回到这里,用这个指南来分析新的依赖关系会有多复杂。