2

字数:4533, 阅读时间:12分钟,点击阅读原文

timg (1)

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

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

缘起

在很久很久以前,前端叫做“切图仔”,那时前端的工作非常简单,只需将设计图还原成代码,最多再加上一些交互和特效,前端的工作非常简单,甚至很多公司没有专门的前端工程师,而这部分工作由后端老哥们兼职。而且那时缺少代码共享平台,项目中造的很多轮子无法分享或者较难分享,代码的复用也只能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” 命令的输出

并行安装

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

yarn和npm的命令对比

  • 有区别的命令
NpmYarn功能描述
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 –saveyarn upgrade [package]升级依赖包
npm uninstall [package]yarn remove [package]移除依赖包
  • 相同操作的命令
NpmYarn功能描述
npm runyarn run运行 package.json 中预定义的脚本
npm config listyarn config list查看配置信息
npm config set registry 仓库地址yarn config set registry 仓库地址更换仓库地址
npm inityarn init互动式创建/更新 package.json 文件
npm listyarn list查看当前目录下已安装的node包
npm loginyarn login保存你的用户名、邮箱
npm logoutyarn logout删除你的用户名、邮箱
npm outdatedyarn outdated检查过时的依赖包
npm linkyarn link开发时链接依赖包,以便在其他项目中使用
npm unlinkyarn unlink取消链接依赖包
npm publishyarn publish将包发布到 npm
npm testyarn test测试 = yarn run test
npm binyarn bin显示 bin 文件所在的安装目录
yarn infoyarn info显示一个包的信息

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

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

未来

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

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

img

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

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

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


BWrong
363 声望304 粉丝

愿你走出半生,归来仍是少年