nodejs 中的依赖管理

在我看来,nodejs 的成功缘由除了它采用了前端 js 相同的语法,直接吸引了一大波前端开发者做为初始用户以外,它内置的包管理器 npm 也居功至伟。npm 可以很好的管理 nodejs 项目的依赖,也使得开发者发布本身的包变的异常容易。这样一来,不论你使用别人的包,仍是本身发布包给别人使用,成本都不大。这和我大学学习的 Java 1.x 相比就轻松愉快的多(如今 Java 已今非昔比,我不敢乱评论),开发者热情高涨的话,整个生态就会更加活跃,进步速度也就更加快了。看一看 GitHub 上 JS 项目的占比,再看看 npm 官网包的数量,就能略知一二。javascript

前阵子公司的一名新人问了我一个问题:如何区分项目的依赖中,哪些应该放在 dependencies,而哪些应该放在 devDependencies 呢?css

其实这个问题我在早先也有过,因此很是可以体会他的心情。为了防止误人子弟,我查阅了一些资料,发现其实 nodejs 中总共有 5 种依赖:前端

  • dependencies (经常使用)
  • devDependencies (经常使用)
  • peerDependencies (不太经常使用)
  • bundledDependencies (我以前没用过)
  • optionalDependencies (我以前没用过)

因此我趁此机会,整理了这篇文章,分享给更多仍有此迷茫的人们。java

dependencies

这是 npm 最基本的依赖,经过命令 npm i xxx -S 或者 npm i xxx --save 来安装一个包,而且添加到 package.json 的 dependencies 里面(这里 iinstall 的简写,二者都可)。node

若是直接只写一个包的名字,则安装当前 npm registry 中这个包的最新版本;若是要指定版本的,能够把版本号写在包名后面,例如 npm i webpack@3.0.0 --savewebpack

npm install 也支持 tag,tar 包地址等等,不过那些不太经常使用,能够查看官方文档git

dependencies 比较简单,我就再也不多作解释了。注意一点:npm 5.x 开始能够省略 --save,即若是执行 npm install xxx,npm 同样会把包的依赖添加到 package.json 中去。要关闭这个功能,可使用 npm config set save false程序员

devDependencies

不少 nodejs 新手都分不清 dependencies 和 devDependencies,致使依赖随便分配,或者把依赖通通都写在 dependencies。这也是我编写本文的初衷。github

先说定义。顾名思义,devDependencies 就是开发中使用的依赖,它区别于实际的依赖。也就是说,在线上状态不须要使用的依赖,就是开发依赖web

再说意义。为何 npm 要把它单独分拆出来呢?最终目的是为了减小 node_modules 目录的大小以及 npm install 花费的时间。由于 npm 的依赖是嵌套的,因此可能看上去 package.json 中只有几个依赖,但实际上它又扩散到 N 个,而 N 个又扩散到 N 平方个,一层层扩散出去,可谓子子孙孙无穷尽也。若是可以尽可能减小不使用的依赖,那么就可以节省线上机器的硬盘资源,也能够节省部署上线的时间。

在实际开发中,大概有这么几类能够归为开发依赖:

  1. 构建工具

    如今比较热门的是 webpack 和 rollup,以往还有 grunt, gulp 等等。这些构建工具会生成生产环境的代码,以后在线上使用时就直接使用这些压缩过的代码。因此这类构建工具是属于开发依赖的。

    像 webpack 还分为代码方式使用(webpack)和命令行方式使用 (webpack-cli),这些都是开发依赖。另外它们可能还会提供一些内置的经常使用插件,如 xxx-webpack-plugin,这些也都算开发依赖。

  2. 预处理器

    这里指的是对源代码进行必定的处理,生成最终代码的工具。比较典型的有 CSS 中的 less, stylus, sass, scss 等等,以及 JS 中的 coffee-script, babel 等等。它们作的事情虽然各有不一样,但原理是一致的。

    以 babel 为例,经常使用的有两种使用方式。其一是内嵌在 webpack 或者 rollup 等构件工具中,通常以 loader 或者 plugin 的形式出现,例如 babel-loader。其二是单独使用(小项目较多),例如 babel-cli。babel 还额外有本身的插件体系,例如 xxx-babel-plugin。相似地,less 也有与之对应的 less-loaderlessc。这些都算做开发依赖。

    在 babel 中还有一个注意点,那就是 babel-runtime 是 dependencies 而不是 devDependencies。具体分析我在以前的 babel 文章中提过,就再也不重复了。

  3. 测试工具

    严格来讲,测试和开发并非一个过程。但它们同属于“线上状态不须要使用的依赖”,所以也就纳入开发依赖了。经常使用的如 chai, e2e, karma, coveralls 等等都在此列。

  4. 真的是开发才用的依赖包

    最后一类比较杂,很难用一个大类囊括起来,总之就是开发时须要使用的,而实际上线时要么是已经打包成最终代码了,要么就是不须要使用了。好比 webpack-dev-server 支持开发热加载,线上是不用的;babel-register 由于性能缘由也不能用在线上。其余还可能和具体业务相关,就看各位开发者本身识别了。

把依赖安装成开发依赖,则可使用 npm i -D 或者 npm i --save-dev 命令。

若是想达成刚才说的缩减安装包的目的,可使用命令 npm i --production 忽略开发依赖,只安装依赖,这一般在线上机器(或者 QA 环境)上使用。所以还有一个最根本的识别依赖的方式,那就是用这条命令安装,若是项目跑不起来,那就是识别有误了。

peerDependencies

若是仅做为 npm 包的使用者,了解前两项就足够咱们平常的使用了。接下来的三种依赖都是做为包的发布者带会使用到的字段,因此咱们转换角色,以发布者的身份来讨论接下来的问题。

若是咱们开发一个常规的包,例如命名为 my-lib。其中须要使用 request 这个包来发送请求,所以代码里必定会有 const request = require('request')。如上面的讨论,这种状况下 request 是做为 dependencies 出如今 package.json 里面的。那么在使用者经过命令 npm i my-lib 安装咱们的时候,这个 request 也会做为依赖的一部分被安装到使用者的项目中。

那咱们还为何须要这个 peerDependencies 呢?

根据 npm 官网的文档,这个属性主要用于插件类 (Plugin) 项目。常规来讲,为了插件生态的繁荣,插件项目通常会被设计地尽可能简单,经过数据结构和固定的方法接口进行耦合,而不会要求插件项目去依赖本体。例如咱们比较熟悉的 express 中间件,只要你返回一个方法 return function someMiddleware(req, res, next),它就成为了 express 中间件,受本体调用,并经过三个参数把本体的信息传递过来,在插件内部使用。所以 express middleware 是不须要依赖 express 的。相似的状况还包括 Grunt 插件,Chai 插件和 Winston transports 等。

但很明显,这类插件脱离本体是不能单独运行的。所以虽然插件不依赖本体,但想要本身可以实际运行起来,还得要求使用者把本体也归入依赖。这就是介于“不依赖”和“依赖”之间的中间状态,就是 peerDependencies 的主要使用场景。

例如咱们提供一个包,其中的 package.json 以下:

{
  "name": "my-greate-express-middleware",
  "version": "1.0.0",
  "peerDependencies": {
    "express": "^3.0.0"
  }
}
复制代码

在 npm 3.x 及之后版本,若是使用者安装了咱们的插件,而且在本身的项目中没有依赖 express 时,会在最后弹出一句提示,表示有一个包须要您依赖 express 3.x,所以您必须本身额外安装。另外若是使用者依赖了不一样版本的 express,npm 也会弹出提示,让开发者本身决断是否继续使用这个包。

bundledDependencies

这是一种比起 peerDependencies 更加少见的依赖项,也能够写做 bundleDependencies (bundle 后面的 d 省略)。和上述的依赖不一样,这个属性并非一个键值对的对象,而是一个数组,元素为表示包的名字的字符串。例如

{
  "name": "awesome-web-framework",
  "version": "1.0.0",
  "bundledDependencies": [
    "renderized", "super-streams"
  ]
}
复制代码

当咱们但愿以压缩包的方式发布项目时(好比你不想放到 npm registry 里面去),咱们会使用 npm pack 来生成(如上述例子,就会生成 awesome-web-framework-1.0.0.tgz)。编写了 bundledDependencies 以后,npm 会把这里面的两个包 (renderized, super-streams) 也一块儿加入到压缩包中。这样以后其余使用者执行 npm install awesome-web-framework-1.0.0.tgz 时也会安装这两个依赖了。

若是咱们使用常规的 npm publish 的方式来发布的话,这个属性不会生效;而做为使用方的话,大部分项目也都是从 npm registry 中搜索并引用依赖的,因此使用到的场景也至关少。

optionalDependencies

这也是一种不多见的依赖项,从名字能够得知,它描述一种”可选“的依赖。和 dependencies 相比,它的不一样点有:

  1. 即便这个依赖安装失败,也不影响整个安装过程

  2. 程序应该本身处理安装失败时的状况

关于第二点,我想表达的意思是:

let foo
let fooVersion
try {
  foo = require('foo')
  fooVersion = require('foo/package.json').version
} catch (e) {
  // 安装依赖失败时找不到包,须要本身处理
}

// 若是安装的依赖版本不符合实际要求,咱们也须要本身处理,当作没安装到
if (!isSupportVersion(fooVersion)) {
  foo = null
}

// 若是安装成功,执行某些操做
if (foo) {
  foo.doSomeThing()
}
复制代码

须要注意的是,若是一个依赖同时出如今 dependencies 和 optionalDependencies 中,那么 optionalDependencies 会得到更高的优先级,可能形成预期以外的效果,所以最好不要出现这种状况。

在实际项目中,若是某个包已经失效,咱们一般会寻找他的替代者,或者压根换一个实现方案。使用这种”不肯定“的依赖,一方面会增长代码中的判断,增长逻辑的复杂度;另外一方面也会大大下降测试覆盖率,增长构造测试用例的难度。因此我不建议使用这个依赖项,若是你原先就不知道有这个,那就继续当作不知道吧。

版本号的写法

如上的 5 种依赖,除了 bundledDependencies,其余四种都是须要写版本号的。若是做为使用者,使用 npm i --save 或者 npm i --save-dev 会自动生成依赖的版本号,不过我建议你们仍是略微了解下经常使用的版本号的写法。

首先咱们得搞清三位版本号的定义,以 "a.b.c" 举例,它们的含义是:

  1. a - 主要版本(也叫大版本,major version)

    大版本的升级极可能意味着与低版本不兼容的 API 或者用法,是一次颠覆性的升级(想一想 webpack 3 -> 4)。

  2. b - 次要版本(也叫小版本,minor version)

    小版本的升级应当兼容同一个大版本内的 API 和用法,所以应该对开发者透明。因此咱们一般只说大版本号,不多会精确到小版本号。

    特殊状况是若是大版本号是 0 的话,意味着整个包处于内测状态,因此每一个小版本之间也可能会不兼容。因此在选择依赖时,尽可能避开大版本号是 0 的包。

  3. c - 补丁 (patch)

    通常用于修复 bug 或者很细微的变动,也须要保持向前兼容。

以后咱们看一下常规的版本号写法:

  1. "1.2.3" - 无视更新的精确版本号

    表示只依赖这个版本,任何其余版本号都不匹配。在一些比较重要的线上项目中,我比较建议使用这种方式锁定版本。前阵子的 npm 挖矿以及 ant-design 彩蛋,其实均可以经过锁定版原本规避问题(彩蛋略难一些,挖矿是确定能够规避)。

  2. "^1.2.3" - 兼具更新和安全的折中考虑

    这是 npm i xxx --save 以后系统生成的默认版本号(^ 加上当前最新版本号),官方的定义是“可以兼容除了最左侧的非 0 版本号以外的其余变化”(Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple)。这句话很拗口,举几个例子你们就明白了:

    1. "^1.2.3" 等价于 ">= 1.2.3 < 2.0.0"。即只要最左侧的 "1" 不变,其余均可以改变。因此 "1.2.4", "1.3.0" 均可以兼容。

    2. "^0.2.3" 等价于 ">= 0.2.3 < 0.3.0"。由于最左侧的是 "0",因此这个不算,顺延到第二位 "2"。那么只要这个 "2" 不变,其余的都兼容,好比 "0.2.4" 和 "0.2.99"。

    3. "^0.0.3" 等价于 ">= 0.0.3 < 0.0.4"。这里最左侧的非 0 只有 "3",且没有其余版本号了,因此这个也等价于精确的 "0.0.3"。

    从这几个例子能够看出,^ 是一个更新和安全兼容的写法。通常大版本号升级到 1 就表示项目正式发布了,而 0 开头就表示还在测试版,这也是 ^ 区别对待二者的缘由。

  3. "~1.2.3" - 比 ^ 更加安全的小版本更新

    关于 ~ 的定义分为两部分:若是列出了小版本号(第二位),则只兼容 patch(第三位)的修改;若是没有列出小版本号,则兼容第二和第三位的修改。咱们分两种状况理解一下这个定义:

    1. "~1.2.3" 列出了小版本号(2),所以只兼容第三位的修改,等价于 ">= 1.2.3 < 1.3.0"。

    2. "~1.2" 也列出了小版本号,所以和上面同样兼容第三位的修改,等价于 ">= 1.2.0 < 1.3.0"。

    3. "~1" 没有列出小版本号,能够兼容第二第三位的修改,所以等价于 ">= 1.0.0 < 2.0.0"

    ^ 不一样的是,~ 并不对 0 或者 1 区别对待,因此 "~0" 等价于 ">= 0.0.0 < 1.0.0",和 "~1" 是相同的算法。比较而言,~ 更加谨慎。当首位是 0 而且列出了第二位的时候,二者是等价的,例如 ~0.2.3^0.2.3

    在 nodejs 的上古版本(v0.10.26,2014年2月发布的),npm i --save 默认使用的是 ~,如今已经改为 ^ 了。这个改动也是为了让使用者能最大限度的更新依赖包。

  4. "1.x" 或者 "1.*" - 使用通配符

    这个比起上面那两个符号就好理解的多。x(大小写皆可)和 * 的含义相同,都表示能够匹配任何内容。具体来讲:

    1. "*" 或者 "" (空字符串) 表示能够匹配任何版本。

    2. "1.x", "1.*" 和 "1" 都表示要求大版本是 1,所以等价于 ">=1.0.0 < 2.0.0"。

    3. "1.2.x", "1.2.*" 和 "1.2" 都表示锁定前两位,所以等价于 ">= 1.2.0 < 1.3.0"。

    由于位于结尾的通配符通常能够省略,而常规也不太可能像正则那样把匹配符写在中间,因此大多数状况通配符均可以省略。使用最多的仍是匹配全部版本的 * 这个了。

  5. "1.2.3-beta.2" - 带预发布关键词的,如 alpha, beta, rc, pr 等

    先说预发布的定义,咱们须要以包开发者的角度来考虑这个问题。假设当前线上版本是 "1.2.3",若是我做了一些改动须要发布版本 "1.2.4",但我不想直接上线(由于使用 "~1.2.3" 或者 `^1.2.3" 的用户都会直接静默更新),这就须要使用预发布功能。所以我可能会发布 "1.2.4-alpha.1" 或者 "1.2.4-beta.1" 等等。

    理解了它诞生的初衷,以后的使用就很天然了。

    1. ">1.2.4-alpha.1",表示我接受 "1.2.4" 版本全部大于1的 alpha 预发布版本。所以如 "1.2.4-alpha.7" 是符合要求的,但 "1.2.4-beta.1" 和 "1.2.5-alpha.2" 都不符合。此外若是是正式版本(不带预发布关键词),只要版本号符合要求便可,不检查预发布版本号,例如 "1.2.5", "1.3.0" 都是承认的。

    2. "~1.2.4-alpha.1" 表示 ">=1.2.4-alpha.1 < 1.3.0"。这样 "1.2.5", "1.2.4-alpha.2" 都符合条件,而 "1.2.5-alpha.1", "1.3.0" 不符合。

    3. "^1.2.4-alpha.1" 表示 ">=1.2.4-alpha.1 < 2.0.0"。这样 "1.2.5", "1.2.4-alpha.2", "1.3.0" 都符合条件,而 "1.2.5-alpha.1", "2.0.0" 不符合。

版本号还有更多的写法,例如范围(a - b),大于小于号(>=a <b),或(表达式1 || 表达式2)等等,由于用的很少,这里再也不展开。详细的文档能够参见 semver,它同时也是一个 npm 包,能够用来比较两个版本号的大小,以及是否符合要求等。

其余写法

除了版本号,依赖包还能够经过以下几种方式来进行依赖(使用的也不算太多,能够粗略了解一下):

Tag

除了版本号以外,一般某个包还可能会有 Tag 来标识一些里程碑意义的版本。例如 express@next 表示即将到来的下一个大版本(可提早体验),而 some-lib@latest 等价于 some-lib,由于 latest 是默认存在并指向最新版本的。其余的自定义 Tag 均可以由开发者经过 npm tag 来指定。

由于 npm i package@versionnpm i package@tag 的语法是相同的,所以 Tag 和版本号必须不能重复。因此通常建议 Tag 不要以数字或者字母 v 开头。

URL

能够指定 URL 指明依赖包的源地址,一般是一个 tar 包,例如 "https://some.site.com/lib.tar.gz"。这个 tar 包一般是经过 npm pack 来发布的。

顺带提一句:本质上,npm 的全部包都是以 tar 包发布的。使用 npm publish 常规发布的包也是被 npm 冠上版本号等后缀,由 npm registry 托管供你们下载的。

Git URL

能够指定一个 Git 地址(不单纯指 GitHub,任何 git 协议的都可),npm 自动从该地址下载并安装。这里就须要指明协议,用户名,密码,路径,分支名和版本号等,比较复杂。详情能够查看官方文档,举例以下:

git+ssh://git@github.com:npm/cli.git#v1.0.27
git+ssh://git@github.com:npm/cli#semver:^5.0
git+https://isaacs@github.com/npm/cli.git
git://github.com/npm/cli.git#v1.0.27
复制代码

做为最大的 Git 代码库,若是使用的是 GitHub 存放代码,还能够直接使用 user/repo 的简写方式,例如:

{
  "dependencies": {
    "express": "expressjs/express",
    "mocha": "mochajs/mocha#4727d357ea",
    "module": "user/repo#feature\/branch"
  }
}
复制代码

本地路径

npm 支持使用本地路径来指向一个依赖包,这时候须要在路径以前添加 file:,例如:

{
  "dependencies": {
    "bar1": "file:../foo/bar1",
    "bar2": "file:~/foo/bar2",
    "bar3": "file:/foo/bar3"
  }
}
复制代码

package-lock.json

从 npm 5.x 开始,在执行 npm i 以后,会在根目录额外生成一个 package-lock.json。既然讲到了依赖,我就额外扩展一下这个 package-lock.json 的结构和做用。

package-lock.json 内部记录的是每个依赖的实际安装信息,例如名字,安装的版本号,安装的地址 (npm registry 上的 tar 包地址)等等。额外的,它会把依赖的依赖也记录起来,所以整个文件是一个树形结构,保存依赖嵌套关系(相似之前版本的 node_modules 目录)。一个简单的例子以下:

{
  "name": "my-lib",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "array-union": {
      "version": "1.0.2",
      "resolved": "http://registry.npm.taobao.org/array-union/download/array-union-1.0.2.tgz",
      "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
      "dev": true,
      "requires": {
        "array-uniq": "^1.0.1"
      }
    }
  }
}
复制代码

在执行 npm i 的时候,若是发现根目录下只有 package.json 存在(这一般发生在刚建立项目时),就按照它的记录逐层递归安装依赖,并生成一个 package-lock.json 文件。若是发现根目录下二者皆有(这一般发生在开发同事把项目 checkout 到本地以后),则 npm 会比较二者。若是二者所示含义不一样,则以 package.json 为准,并更新 package-lock.json;不然就直接按 package-lock 所示的版本号安装。

它存在的意义主要有 4 点:

  1. 在团队开发中,确保每一个团队成员安装的依赖版本是一致的。不然由于依赖版本不一致致使的效果差别,通常很难查出来。

  2. 一般 node_modules 目录都不会提交到代码库,所以要回溯到某一天的状态是不可能的。但如今 node_modules 目录和 package.json 以及 package-lock.json 是一一对应的。因此若是开发者想回退到以前某一天的目录状态,只要把这两个文件回退到那一天的状态,再 npm i 就好了。

  3. 由于 package-lock.json 已经足以描述 node_modules 的大概信息(尤为是深层嵌套依赖),因此经过这个文件就能够查阅某个依赖包是被谁依赖进来的,而不用去翻 node_modules 目录(事实上如今目录结构打平而非嵌套,翻也翻不出来了)

  4. 在安装过程当中,npm 内部会检查 node_modules 目录中已有的依赖包,并和 package-lock.json 进行比较。若是重复,则跳过安装,能大大优化安装时间。

npm 官网建议:把 package-lock.json 一块儿提交到代码库中,不要 ignore。可是在执行 npm publish 的时候,它会被忽略而不会发布出去。

yarn

从 nodejs 诞生之初,npm 就是其内置的包管理器,而且以其易于使用,易于发布的特色极大地助推了 nodejs 在开发者中的流行和使用。但事物总有其两面性,易于发布的确大大推进生态的繁荣,但同时也下降了发布的门槛。包的数量在日新月异,一个项目的依赖项从几个上升到几十个,再加上内部的嵌套循环依赖,给使用者带来了极大的麻烦,node_modules 目录愈来愈大,npm install 的时间也愈来愈长。

在这种状况下,Facebook 率先站出来,发布了由他们开发的另外一个包管理器 yarn(1.0版本于2017年9月)。一旦有了挑战者出现,势必会引起双方对于功能,稳定性,易用性等各方面的竞争,对于开发者来讲也是极其有利的。从结果来讲,npm 也吸取了很多从 yarn 借鉴来的优势,例如上面谈论的 package-lock.json,最先就出自 yarn.lock。因此咱们来粗略比较一下二者的区别,以及咱们应当如何选择。

yarn

  1. 版本锁定

    这个在 package-lock.json 已经讨论过了,再也不赘述。 在这个功能点上,二者都已具有。

  2. 多个包的管理 (monorepositories)

    一个包在 npm 中能够被称为 repositories。一般咱们发布某个功能,其实就是发布一个包,由它提供各类 API 来提供功能。但随着功能愈来愈复杂以及按需加载,把全部东西所有放到一个包中发布已经不够优秀,所以出现了多个包管理的需求。

    一般一个类库会把本身的功能分拆为核心部分和其余部分,而后每一个部分是一个 npm repositories,能够单独发布。而使用者一般在使用核心以后,能够本身选择要使用哪些额外的部分。这种方式比较常见的如 babel 和它的插件,express 和它的中间件等。

    做为一个多个包的项目的开发者/维护者,安装依赖和发布都会是一件很麻烦的事情。由于 npm 只认根目录的 package.json,那么就必须进入每一个包进行 npm install。而发布时,也必须逐个修改每一个包的版本号,并到每一个目录中进行 npm publish

    为了解决这个问题,社区一个叫作 lerna 的库经过增长 lerna.json 来帮助咱们管理全部的包。而在 yarn 这边,引入了一个叫作工做区(workspace)的概念。所以这点上来讲,应该是 yarn 胜出了,不过 npm 配合 lerna 也可以实现这个需求。

  3. 安装速度

    npm 被诟病最多的问题之一就是其安装速度。有些依赖不少的项目,安装 npm 须要耗费 5-10 分钟甚至更久。形成这个问题的本质是 npm 采用串行的安装方式,一个装完再装下一个。针对这一点,yarn 改成并行安装,所以本质上提高了安装速度。

  4. 离线可用

    yarn 默认支持离线安装,即安装过一次的包,会在电脑中保留一份(缓存位置能够经过 yarn config set yarn-offline-mirror 进行指定)。以后再次安装,直接复制过来就能够。

    npm 早先是所有经过网络请求的(为了保持其时效性),但后期也借鉴了 yarn 建立了缓存。从 npm 5.x 开始咱们可使用 npm install xxx --prefer-offline优先使用缓存(意思是缓存没有再发送网络请求),或者 npm install xxx --offline彻底使用缓存(意思是缓存没有就安装失败)。

  5. 控制台信息

    常年使用 npm 的同窗知道,安装完依赖后,npm 会列出一颗依赖树。这颗树一般会很长很复杂,咱们不会过多关注。所以 yarn 精简了这部分信息,直接输出安装结果。这样万一安装过程当中有报错日志也不至于被刷掉。

    不过 npm 5.x 也把这颗树给去掉了。这又是一个互相借鉴提升的例子。

总结来讲,yarn 的推出主要是针对 npm 早期版本的不少问题。但 npm 也意识到了来自竞争对手的强大压力,所以在 5.x 开始逐个优化看齐。从 5.x 开始就已经和 yarn 不分伯仲了,所以如何选择多数看是否有历史包袱。若是是新项目的话,就看程序员我的的喜爱了。

后记

本文从一个很小的问题开始,本意是想分享如何鉴别一个应用应该归类在 dependencies 仍是 devDependencies。后来层层深刻,经过查阅资料发现了好多依赖相关的知识,例如其余几种依赖,版本锁定的机制以及和 yarn 的比较等等,最终变成一篇长文。但愿经过本文能让你们了解到依赖管理的一些大概,在以后的搬砖道路上可以更加顺利,也能反过来为整个生态的繁荣贡献本身的力量。

参考文章

相关文章
相关标签/搜索