【前端工程化】篇二 白璧微瑕-包管理器

2020/10/13 posted in  工程化
Tags:  #工程化 #npm

字数:4533, 阅读时间:12分钟

一个没有长夜痛哭过的人,不配讲悲伤。一个每遇挫折都要痛哭的人,还是不必三十而立了。 ——三毛

【前端工程化】系列文章链接:

缘起

在很久很久以前,前端叫做“切图仔”,那时前端的工作非常简单,只需将设计图还原成代码,最多再加上一些交互和特效,前端的工作非常简单,甚至很多公司没有专门的前端工程师,而这部分工作由后端老哥们兼职。而且那时缺少代码共享平台,项目中造的很多轮子无法分享或者较难分享,代码的复用也只能CV,大家都是单打独斗、各自为战。即使用到了第三方的一些插件,也只能下载后手动进行管理,维护极为不方便。

后来,NodeJS出现了,作为一门服务端语言,它的复杂程度远比当时前端项目要高得多,如果还是手动来管理依赖那将是件很痛苦的事情,所以NPM应运而生,从此它担任了前端项目依赖管理的角色,开启了JavaScript生态的一片繁华。

在开始介绍前,先简单介绍几个概念:

  • 包:包即是一段能复用的代码,它可以存在开发者本地和云端,每个包可能依赖也可能不会依赖其他包。
  • 包管理器:对包进行管理的工具,可以追溯包的版本、依赖、作者等相关信息,还可以将云端的包下载到本地,在项目中使用。

NPM

npm即Node Package Manager,开发者可在上面分享自己的代码包,现在平台上已有上百万个包,而使用它可以轻松管理包的依赖及其版本。

npm主要有三个部分组成:

  • 网站:开发者查找包(package)的主要途径,可以通过不同的条件进行检索,还可以对自己或者自己组织账户进行管理。
  • 注册表:相当于一个庞大的数据库,保存了每个包(package)的相关信息。
  • 命令行工具:用以支持通过终端执行命令的方式来运行。

NPM是NodeJS官方的包管理器,所以默认会在安装NodeJS的时候一起安装,具体的安装及配置请见上一篇《开发环境搭建》。

NPM我相信大家都是比较熟悉了,所以下面仅仅介绍一下常用的用法。

常用命令

生成配置文件package.json

npm init [配置]

配置:

  • -y(--yes) :略过问答配置模式,所有配置项将全部使用默认值。

搜索包

npm search <包名>

注意: 如果更改了npm的镜像源,搜索包时需要切换到npm官方源。

npm包的开发门槛很低,数量众多,重复的轮子也多得出奇,每次选择一个包对选择强迫症患者来说,并不是一件容易的事情,一般我们可以考虑以下几点因素:

  • 下载量,和买东西看销量一个道理,相信大多数人的选择总是没错的。
  • 维护的活跃程度,包括最后更新时间、更新频率,是否可以给作者提交issues及其修复情况,总之就是看作者有没有用心在维护,有的项目是为了应付公司KPI,可能过段时间就凉凉了。
  • 文档是否完善,没文档就是在看天书,不会想让我去啃源码吧,我头发可不多了😭 😭。

安装包

npm install <包名> [配置] # install 可以简写为i

配置:

  • -g (--global):将包作为全局依赖安装,一般一些在终端使用的命令工具之类会选择此方式。
  • -S (--save):将包作为生产依赖安装,一般在生产(线上)环境使用的依赖都采用此方式,不指定默认也是此方式。
  • -D (--save-dev):将包作为开发依赖安装,一般开发环境使用的一些辅助工具都采用此方式安装,如eslint、babel。

可以在包名后面加上版本号以安装特定版本号,如npm install vue@2.16.1

卸载包

npm uninstall <包名> [配置]

配置和安装包配置一样,不同配置表示卸载不同地方的包。

更新包

npm update <包名> [配置]

配置和安装包配置一样,不同配置表示更新不同地方的包。

发布包

npm publish [配置]

配置:

  • --tag beta: 指定标签,默认为版本号,如npm publish --tag beta

安全性检测

npm audit # 对包及其依赖进行安全检测,并生成报告
npm audit fix # 自动为易受攻击的依赖项安装兼容的更新
npm set audit false # 关闭检测机制

由于npm包的数量众多,而且很多包早已没有维护了,难免会出现一些安全隐患,所以安全性检测可以帮助我们做一些排查,增强安全性。前面就出现了一些包会泄露用户数据的问题,所以安全性检测是很有必要的。

注意:安全性检测需要切换到npm官方镜像源。

清除缓存

npm cache clean --force #强制清除npm缓存

为了加快安装包的速度,NPM会在安装包的时候把他们缓存到本地,再次安装这些包的时候,直接从缓存拷贝,使用该命令可以清除缓存。

NPX

npm在5.2版本内置了npx工具,也可以使用npm install -g npx手动安装,主要用来在终端调用项目本地安装的模块,从而可以避免一些全局包的安装。

  • 调用项目中安装的包

如果我们在项目中安装了gulp,要执行gulp相关的命令,如果在项目目录下直接执行的话,它会去全局的包查找,如果要避免这种情况,就只能将命令定义在package.json的scripts中,然后通过npm run的方式来执行。如果使用npx,就会避免这种情况,因为它会优先去项目本地中寻找。

npx gulp --version
  • 避免全局安装模块

一般在使用一些脚手架的时候,我们需要在全局安装后才可使用,如果使用npx就不需要。

npx create-react-app my-react-app

npx 将create-react-app下载到一个临时目录,使用以后再删除,避免了安装全局模块。

  • 执行远程代码
# 执行 Gist 代码
$ npx https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32

# 执行仓库代码
$ npx github:piuccio/cowsay hello

参考资料:npx 使用教程

package.json

package.json是npm的配置文件,一般存在于项目的根目录中,记录了当前项目信息及其依赖包信息。

配置格式为JSON,常用配置项如下:

必填字段
  • name:包或项目的名字,不能以点(.)或下划线(_)开头,不能包含中文、大写字母及非URL安全字符,长度必须小于或等于214个字符,@开头的包标识它是Scoped包。
  • version:包当前的版本号,遵循 Semantic Versioning 2.0.0 语义化版本规范。
信息类字段
  • description:包的描述信息。

  • keywords:关键字,值为一个字符串数组,在检索包时很有用。

  • license:许可证,以便让用户了解他们是在什么授权下使用此包,以及此包还有哪些附加限制。

{
  "license": "MIT",
  "license": "(MIT or GPL-3.0)",
  "license": "SEE LICENSE IN LICENSE_FILENAME.txt",
  "license": "UNLICENSED"
}
链接类字段

各种指向项目文档、issues 上报,以及代码托管网站的链接字段。

  • homepage:是包的项目主页或者文档首页。

  • bugs:问题反馈系统的 URL,或者是 email 地址之类的链接。方便用户通过该途径向包作者反馈问题。

  • repository:是包代码托管的位置。

{
  "repository": { "type": "git", "url": "https://github.com/user/repo.git" },
  "repository": "github:user/repo",
  "repository": "gitlab:user/repo",
  "repository": "bitbucket:user/repo",
  "repository": "gist:a1b2c3d4e5f"
}
项目维护类字段

项目维护者的相关信息。

  • author:作者信息,一个人。
{
  "author": { "name": "Your Name", "email": "you@example.com", "url": "http://your-website.com" },
  "author": "Your Name <you@example.com> (http://your-website.com)"
}
  • contributors:贡献者信息,可以是多个人。
{
  "contributors": [
    { "name": "Your Friend", "email": "friend@example.com", "url": "http://friends-website.com" }
    { "name": "Other Friend", "email": "other@example.com", "url": "http://other-website.com" }
  ],
  "contributors": [
    "Your Friend <friend@example.com> (http://friends-website.com)",
    "Other Friend <other@example.com> (http://other-website.com)"
  ]
}
文件类信息

指定包含在项目中的文件,以及项目的入口文件。

  • files:项目包含的文件,可以是单独的文件、整个文件夹,或者通配符匹配到的文件。
{
  "files": [
    "filename.js",
    "directory/",
    "glob/*.{js,json}"
  ]
}
  • main:项目的入口文件。
{
  "main": "filename.js"
}
  • bin:随着项目一起被安装的可执行文件,开发命令行工具、脚手架会用到此项。
{
  "bin": "bin.js",
  "bin": {
    "other-command": "bin/other-command"
  }
}
  • directories:当你的包安装时,你可以指定确切的位置来放二进制文件、man pages、文档、例子等。
{
  "directories": {
    "lib": "path/to/lib/",
    "bin": "path/to/bin/",
    "man": "path/to/man/",
    "doc": "path/to/doc/",
    "example": "path/to/example/"
  }
}
  • types:指定 TypeScript 中的类型声明文件
{
  "types": "./lib/main.d.ts",
}
任务类字段

脚本是定义自动化开发相关任务的好方法,比如使用一些简单的构建过程或开发工具。 在 scripts 字段里定义的脚本,可以通过npm run xxx 命令来执行。 例如, build-project 脚本可以通过 npm run build-project 调用,并执行 node build-project.js

{
  "scripts": {
    "build-project": "node build-project.js"
  }
}

有一些特殊的脚本名称(可以理解为生命钩子)。 如果定义了 preinstall 脚本,它会在包安装前被调用。 出于兼容性考虑,installpostinstallprepublish 脚本会在包完成安装后被调用。

特定的 scripts
  • prepublish: 在打包并发布包之前运行,以及在没有任何参数的本地 npm 安装之前运行。
  • prepare: 在打包和发布包之前运行,在没有任何参数的本地 npm install 上运行,以及安装 git 依赖项时。 这是在 preublish 之后运行,但是在 preublishOnly 之前运行。
  • prepublishOnly: 在包准备和打包之前运行,仅限于npm发布。
  • prepack: 在打包 tarball 之前运行(在 npm packnpm publish,以及安装 git 依赖项时)
  • postpack: 在生成 tarball 之后运行并移动到其最终目标。
  • publish, postpublish: 在包发布后运行。
  • preinstall: 在安装软件包之前运行。
  • install, postinstall: 安装包后运行。
  • preuninstall, uninstall: 在卸载软件包之前运行。
  • postuninstall: 在卸载软件包之后运行。
  • preversion: 在改变包版本之前运行。
  • version: 改变包版本后运行,但提交之前。
  • postversion: 改变包版本后运行,然后提交。
  • pretest, test, posttest: 由 npm test 命令运行。
  • prestop, stop, poststop: 由 npm stop 命令运行。
  • prestart, start, poststart: 由 npm start 命令运行。
  • prerestart, restart, postrestart: 由 npm restart 命令运行。 注意:如果没有提供重启脚本,npm restart 将运行 stopstart 脚本。
  • preshrinkwrap, shrinkwrap, postshrinkwrap: 由 npm shrinkwrap 命令运行。
依赖描述类字段

记录了当前项目或者包的其他依赖。

  • dependencies:这些是开发环境和生产环境都需要的依赖包,使用npm i -Sxxx 安装。
{
  "dependencies": {
    "package-1": "^3.1.4",
    "package-2": "file:./path/to/dir"
  }
}

包的版本遵循如下规则:

  • 补丁发布:1.01.0.x~1.0.4

  • 次要版本:11.x^1.0.4

  • 主要版本:*x

你可以指定一个确切的版本、一个最小的版本 (比如 >=) 或者一个版本范围 (比如 >= ... <)。
包也可以指向本地的一个目录文件夹。

  • devDependencies:这些是仅开发环境需要的依赖包,一般是一些构建辅助工具之类的,使用npm i -D xxx安装。
{
  "devDependencies": {
    "package-2": "^0.4.2"
  }
}

我们在安装包时,最好根据使用环境来放到对应位置,不要不分环境随处安装,养成良好的习惯,因为一些部署工具可能会对此有要求。

我们安装的包前面一半都会带上^或者~的符号,有什么作用呢?

  • ~会匹配最近的小版本依赖包,如~1.2.3会匹配所有1.2.x版本,但是不包括1.3.0
  • ^会匹配最新的大版本依赖包,如^1.2.3会匹配所有1.x.x的包,包括1.3.0,但是不包括2.0.0
  • 不带上面两种符号的,即固定版本号,如1.2.3仅匹配1.2.3版本。

参考资料: package.json 说明文档

package-lock.json

原则上,包的发布需要严格遵循 语义版本控制 规则,但是这并不是一个强制的规则,难免版本更新会带来一些不兼容的特性。如果不锁定版本,有可能会出现根据同一份 package.json 文件,但安装了不同版本的包,导致出现一些兼容性错误,所以npm在安装时就会生成该文件来锁定版本,保证安装软件版本的一致性。

查找规则

npm包在使用时遵循如下查找规则:

  1. 在当前目录下查找node_modules目录;如果没有则向上一层目录查找,如果也没有则继续向上层目录查找直到根目录; 如果都没有则报错。
  2. 进入node_modules中查找对应包名字的目录;如果没有则报错。
  3. 进入包名目录查找 package.json 文件中的 main 配置项,导入该配置项指定的文件。
  4. 如果模块名目录中没有 package.json文件,或package.json文件中没有 main 配置项,则加载 index.js 文件;如果index.js也没有则报错。

Yarn

Yarn 是一个由 Facebook,Google,Exponent 和 Tilde 构建的新的 JavaScript 包管理器。正如官方公告所写,它的目标就是解决这些团队使用 npm 的时候所遇到的几个问题,即:

  • 安装包不够快速和稳定。
  • 存在安全隐患,因为 npm 允许包在安装的时候运行代码。

它并不是想要完全替代 npm。Yarn 仅仅是一个能够从 npm 仓库获取到包的新的 CLI 客户端,它和NPM相比,有如下优势:

更清晰的输出

yarn的输出很清晰,使用了emoji,看起来也更加漂亮,颜值党必备。

“yarn install” 命令的输出
“yarn install” 命令的输出

并行安装

在安装多个包时,npm会按包顺序串行执行,也就是只有当一个包全部安装完成后,才会安装下一个。Yarn 则是并行执行,更加高效。

yarn和npm的命令对比

  • 有区别的命令
Npm Yarn 功能描述
npm install(npm i) yarn install(yarn) 根据 package.json 安装所有依赖
npm i –save [package] yarn add [package] 添加依赖包至 dependencies
npm i –save-dev [package] yarn add [package] –dev 添加依赖包至 devDependencies
npm i -g [package] yarn global add [package] 安装全局依赖包
npm update –save yarn upgrade [package] 升级依赖包
npm uninstall [package] yarn remove [package] 移除依赖包
  • 相同操作的命令
Npm Yarn 功能描述
npm run yarn run 运行 package.json 中预定义的脚本
npm config list yarn config list 查看配置信息
npm config set registry 仓库地址 yarn config set registry 仓库地址 更换仓库地址
npm init yarn init 互动式创建/更新 package.json 文件
npm list yarn list 查看当前目录下已安装的node包
npm login yarn login 保存你的用户名、邮箱
npm logout yarn logout 删除你的用户名、邮箱
npm outdated yarn outdated 检查过时的依赖包
npm link yarn link 开发时链接依赖包,以便在其他项目中使用
npm unlink yarn unlink 取消链接依赖包
npm publish yarn publish 将包发布到 npm
npm test yarn test 测试 = yarn run test
npm bin yarn bin 显示 bin 文件所在的安装目录
yarn info yarn info 显示一个包的信息

yarn的命令跟npm的命令差异不大,如果使用过npm,那么过渡到yarn也很简单。npm本身也在不断优化,继续使用也没有关系。

参考资料:Npm vs Yarn 之备忘详单

未来

现在npm还是有很多无法回避的问题,最初node_modules下面每个依赖包的依赖放在自己的目录下面,相同的依赖无法复用,不管是安装还是删除都非常低效,在windows上经常会出现因为超过最大嵌套层级而无法删除的情况,不过好在后来官方调整了架构,改为平铺的结构,解决了这些问题。

不过现阶段npm的包管理还是很混乱,相比其他包管理器,还有很多需要改进的地方。

  • 存在有很多重复的包,很多不维护的包,这给使用者选择造成了困扰。
  • 依赖过多,比如仅仅开发一个react的demo,结果发现要安装上千个包。

npm虽已成为前端依赖管理的标配,但仍有一段路要走,又或者说前端还正在路上。

目前npm官方也正在开发下一代包管理工具Tink,相信不久的将来就会和我们见面,是不是很期待呢? 今天去看了下官方仓库,好像没有维护了,估计夭折了 。🤪

« 【前端工程化】篇三 席卷八荒-Webpack(基础) 【前端工程化】篇一 扬帆起航:开发环境 »