字数: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
脚本,它会在包安装前被调用。 出于兼容性考虑,install
、postinstall
和 prepublish
脚本会在包完成安装后被调用。
特定的 scripts
prepublish
: 在打包并发布包之前运行,以及在没有任何参数的本地npm
安装之前运行。prepare
: 在打包和发布包之前运行,在没有任何参数的本地npm install
上运行,以及安装 git 依赖项时。 这是在preublish
之后运行,但是在preublishOnly
之前运行。prepublishOnly
: 在包准备和打包之前运行,仅限于npm发布。prepack
: 在打包tarball
之前运行(在npm pack
,npm 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
将运行stop
和start
脚本。preshrinkwrap
,shrinkwrap
,postshrinkwrap
: 由npm shrinkwrap
命令运行。
依赖描述类字段
记录了当前项目或者包的其他依赖。
dependencies
:这些是开发环境和生产环境都需要的依赖包,使用npm i -S
xxx 安装。
{
"dependencies": {
"package-1": "^3.1.4",
"package-2": "file:./path/to/dir"
}
}
包的版本遵循如下规则:
- 补丁发布:
1.0
或1.0.x
或~1.0.4
- 次要版本:
1
或1.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包在使用时遵循如下查找规则:
- 在当前目录下查找node_modules目录;如果没有则向上一层目录查找,如果也没有则继续向上层目录查找直到根目录; 如果都没有则报错。
- 进入node_modules中查找对应包名字的目录;如果没有则报错。
- 进入包名目录查找 package.json 文件中的 main 配置项,导入该配置项指定的文件。
- 如果模块名目录中没有 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,看起来也更加漂亮,颜值党必备。
并行安装
在安装多个包时,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,相信不久的将来就会和我们见面,是不是很期待呢? 今天去看了下官方仓库,好像没有维护了,估计夭折了 。🤪
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。