浅谈NPM

npm.png

背景

在 npm 出现之前,前端人员开发一个项目,项目中需要引用各种库,比如 jQuery, BootStarp,lodash 等。当时是怎么做的呢?

  • 打开该库的官方网站或者 github
  • 选择发行版本,点击下载
  • 解压出各个压缩文件,js 或 css 文件,移动到项目目录下

每多引用一个框架都会重复以上步骤一次,操作相当繁琐。

后来 nodejs 问世,node 编写的程序也需要众多库,如果跟前端框架一样进行下载解压移动的操作,是毫无体验的。 node 作为一个服务器语言,跟 golang,python 一样,也需要一个包的管理工具。

这样的前提下,一个软件包的管理工具就顺应而生。

使用

仅作为 npm 的使用者来说,用法是非常简单的:

若是新建项目,需要首先在根目录中运行:

npm init

运行此命令会逐步询问你一些关于此项目的信息,包名(默认你是开发一个 npm 包)、版本、描述、入口文件、测试命令、git 仓库、关键词、作者、开源许可。

之后, 会在项目中生成一个 package.json 的配置文件。有一些变化的是,之前开源许可默认的是 MIT,现在是 ISC。后者比前者多了一个 商用后不能用作者的名字宣传 限制。

现在项目中要引用某个库,就不必到官网下载解压了,比如 jquery:

npm install jquery

运行此命令后,在根目录中生成 node_modules 目录, jquery 在其中。

想一想,如何不使用打包工具,在项目中使用 jquery

同时,再看看 package.json 文件,多了一个 jquery 的依赖项:

 "dependencies": {
  "jquery": "^3.5.1"
 }

package.json 存在的意义是什么呢?就 dependencies 这个字段来说,就是记录依赖项。

现在,任何人在任何地方,只要根据 package.json 的信息,运行 npm install, 就能安装 jquery,还原之前的开发依赖项。

开发一个 npm 包

要开发一个 npm 包,首先需要在npm注册一个账号。

跟上述初始化一个项目一样,运行 npm init,填写正确的信息,然后在根目录创建 index.js 文件,随便

写点逻辑:

module.exports = function (f) {
 console.log(f, ", from zzztestzzz");
};

然后运行

npm publish

会发现报错:

npm ERR! code E404
npm ERR! 404 Not Found - PUT https://registry.npmjs.org/zzztestzzz - Not found
npm ERR! 404
npm ERR! 404  '[email protected]' is not in the npm registry.
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.

此时是因为未登陆,不知道将此包以谁的名义发布,先登陆

npm login

填写你的用户名、密码和邮箱,登陆成功会有

Logged in as someone on https://registry.npmjs.org/.

然后在运行

npm publish

有以下结果代表发布成功:

➜  npmtest npm publish
npm notice
npm notice   [email protected]
npm notice === Tarball Contents ===
npm notice 74B  index.js
npm notice 205B package.json
npm notice === Tarball Details ===
npm notice name:      zzztestzzz
npm notice version:    1.0.5
npm notice package size:  321 B
npm notice unpacked size: 279 B
npm notice shasum:     22218c1386f5f8a7bb2e5e3ea24a7e0de8f8cb46
npm notice integrity:   sha512-mAI4AdMP0oVFe[...]5dg7yK4PISGzg==
npm notice total files:  2
npm notice
+ [email protected]

发布一个 npm 包就像呼吸一样简单!

版本管理

当你添加一个新功能或者修复一个 bug 的时候, 需要重新发布一份,可以手动去改版本号,也可以运行以下命令的一个

npm version 

其中,update_type 有三个选项:major、minor、patch,详见语义化版本 2.0.0

版本即是 major.minor.patch

const version = "1.0.0"; *// major.minor.patch*const ver = version.split(".");

运行npm version 则有:

  • update_type 为 major:ver[0] 加 1
  • update_type 为 minor:ver[1] 加 1
  • update_type 为 patch:ver[2] 加 1

也就是说:

// 初始版本 1.0.0
npm version patch // => 1.0.1
npm version minor // => 1.1.0
npm version major // => 2.0.0

另外,关于版本号,这里有一个让人迷惑的点:版本号前面的符号 ^~ 代表这什么?

 "dependencies": {
  "glob": "^4.1.3",
  "jquery": "~2.2.0",
  "package": "3.5.1",
 }

在你 git clone 一个项目,并且 npm install 的时候,会识别版本里面的各个符号版本,然后根据符号安装指定版本:

  • 符号 ^:只会固定 major。 minor 和 patch 会更新到最新版本,比如 glob 版本会更新到以 4.x.x 为主的最新版本 v4.5.3
  • 符号 ~:会固定 major、minor。 而 patch 会更新到最新版本,比如 jquery 版本会更新到以 2.2.x 为主的最新版本 v2.2.4
  • 没有符号:表示固定版本,会安装指定版本,比如 package v3.5.1

也就是说,你在package.json中看到的glob版本是4.1.3,但实际上安装的版本却是4.5.3,作为一个开发者来说,很敏感的捕捉到这里会导致一个很难发现的bug:没有一个地方可以让我一眼望去我是安装了某个库的某个具体版本,如果要看jquery的具体版本,我可以点进入node_modules里面查看,或者运行 npm ls jquery

但无论那种方式,都没有一个所有包的具体版本信息文件,于是 package-lock.json,这个文件就在这种背景下出生了。

  "glob": {
   "version": "4.5.3",
   "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz",
   "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=",
   "requires": {
    "inflight": "^1.0.4",
    "inherits": "2",
    "minimatch": "^2.0.1",
    "once": "^1.3.0"
   }
  },
  "jquery": {
   "version": "2.2.4",
   "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz",
   "integrity": "sha1-LInWiJterFIqfuoywUUhVZxsvwI="
  },

这里是官方文档的描述:

It describes the exact tree that was generated, such that subsequent installs are able to generate identical trees, regardless of intermediate dependency updates.它描述了生成的确切树,因此无论中间依赖项更新如何,后续安装都可以生成相同的树。

package-lock.json 就是为了描述确定版本而生。

当然,package-lock.json 还有其他的作用。

  • Provide a facility for users to “time-travel” to previous states of node_modules without having to commit the directory itself.为用户提供一种“时间旅行”到以前状态的功能,node_modules而不必提交目录本身。
  • To facilitate greater visibility of tree changes through readable source control diffs.为了通过可读的源代码控制差异更好地了解树的变化。
  • And optimize the installation process by allowing npm to skip repeated metadata resolutions for previously-installed packages.并允许npm跳过先前安装的软件包的重复元数据解析,从而优化安装过程。

早期npm的问题

1.下载速度很慢

众所周知,npm install的速度是非常慢。其原因之一是npm存放的服务器在国外。

国内淘宝团队为了提高效率,复制了一份镜像到了国内,每十分钟同步一次,并提供了cnpm命令,现在,使用cnpm安装模块,会直接从淘宝镜像下载,减少了因距离带来的安装缓慢。

2.很深的node_modules

说到node_modules不得不想到这幅图片。

node-module.png

起初的时候,npm 安装依赖是一种层次关系,比如安装A模块,A模块依赖了B模块,B模块又依赖了C模块。那么目录结构就是这样了:

project
 |--node_modules
  |--A
   |--node_modules
    |--B
     |--node_modules
      |--C
 |--index.js
 |--package.json

每个包都在它自身下维护了自己的依赖,子子孙孙无穷尽,这就会导致node_modules文件夹里面非常深,项目体积非常大。用window的同学想必遇到没法删除node_modules的情况。这是因为太深的依赖导致文件夹名称过长。

这种情况也同时增加了npm install的时间,不算缓存的情况下,很多模块可能依赖了同一个模块,就会导致同一个模块被安装多次,即使从缓存中读取,文件的复制也是需要时间的。

3.不确定的版本

不确定的版本会导致一些难以调试的bug,这个问题在版本管理章节已经说到。 同时,package-lock.json 又使版本锁定,无法自动升级某个库的版本。要想升级某个库的版本,需要手动 npm i somepackage

4.安全和优质性的问题

npm包的优质性的得不到保障,只能让用户自己去辨别,在发布一个npm包章节,我发布了一个包,并且现在直接可以通过命令下载了。 通常,查看一个包的优质性需要通过其github的star数和其本身的更新频率。

安全性也得不到保障,发生过几个比较严重的事故:

event-stream的作者将该包的所有权转给了他人,他人利用event-stream植入恶意代码盗取比特币。

由于作者不满,删除了某个基础包,导致很多依赖此包的库安装报错,导致大规模的事故等。

其他的包管理工具

yarn 是另一个包管理器,这里的yarn实际上同比npm-cli。

yarn.png

npm其实分为三个部分,包括他的官方网站、注册表数据库、npm-cli。通常我们所说的npm,应该特指注册表数据库,而我们用的最多的npm-cli ,就是一个很普普通通的node包:一个去注册表数据库拿数据或往那推数据的脚手架工具。

yarn 和 npm-cli功能一样,一个脚手架工具,共同使用npm的注册表数据库。

yarn是为了解决npm的问题而生,yarn相比同时期的npm,带来了很多新东西,这些使其安装速度远远大于npm。

  • 锁定文件
  • 更快的软件包下载
  • 可离线下载软件包

yarn安装包之前会检查全局缓存中是否命中该包,如果命中的话,会直接将该包从全局缓存中复制到项目安装目录。

受到了yarn的影响,npm经过多次版本的更新后性能已经和yarn差不多了。

tink是另一个包管理器,尝试着解决npm存在的问题,可以阅读这篇文章了解更多。

后记

包管理器的设计是一个比较容易遇到的问题,很多语言、系统都有自己的包管理器,比如python的pip,Debian的apt,甚至apple的 app Store。包的设计也是一个学问,有些 语言的包管理器就令人诟病,比如golang。可以思考一下,如果你是node包管理器的作者,你会遇到什么问题,你会怎么设计这个包管理器。

在文章开头,node和前端社区面临的是没有统一资源下载的问题,现在,npm解决了这个问题。

npm作为node的包管理器,是为node服务的,也就是说,对前端开发者的体验并不是很好。

先思考一个问题,在node和npm刚出来的时候,前端开发者会怎么使用他们?

  1. npm install先安装jquery,bootstrap等资源。
  2. 相关资源及其依赖安装到node_modules
  3. 页面在html页面引用
  4. 以及其他100多个相关依赖的 scriptlink

再思考另一个问题:

我引用了is-odd等这些工具库,但这些库是为node开发的,使用的CommonJS规范,

'use strict';
const isNumber = require('is-number');
module.exports = function isOdd(value) {
 const n = Math.abs(value);
 if (!isNumber(n)) {
  throw new TypeError('expected a number');
 }
 if (!Number.isInteger(n)) {
  throw new Error('expected an integer');
 }
 if (!Number.isSafeInteger(n)) {
  throw new Error('value exceeds maximum safe integer');
 }
 return (n % 2) === 1;
};

当你手动引入后,前端能识别全局变量modulerequireexports吗?

当然,方法总是有的,这就是webpack出现的原因,欢迎期待下篇关于webpack的文章。

参考:

  • 初识篇 — npm 的前世今生
  • package.json各字段解释
  • The semantic versioner for npm
  • npm-package-lock.json
  • The economics of package management
  • 为什么我不使用 shrinkwrap(lock)
  • yarn-a-new-package-manager-for-javascript

欢迎关注~,文章同步掘金、头条号

你可能感兴趣的:(浅谈NPM)