前言
人们往往会忽视他们最常用的东西的一些构造原理,感觉就是天生自带没有为什么。但是真的被问到这些问题的时候,却又答不上来。对于前端开发工程师来说,npm和package.json就是这样的东西。很熟悉却又很陌生。熟悉,是因为,我们几乎每个web项目都会有他们的身影。陌生是因为几乎都没有深究过他们的用法原理。下面,我就来说说我做过的项目中的一些关于npm和package.json的填坑之旅。
一、what is npm ?
首先,我们得知道npm的本质是一种包管理器,官网地址:https://www.npmjs.com。那什么又是包管理器呢?通俗一点,这个东西的全称应该叫做软件包管理器。其实就是类似咱们手机上的应用商店。我们能下载、上传发布、更新应用包。对于web开发来说,这里的包指的就是咱们项目的node_module里面的一个个的文件夹,每个文件夹就是一个依赖包。而npm就是管理这些依赖包的应用商店。
当然,web开发的依赖包不仅仅只有npm。目前市面常见的还有yarn、cnpm和新出来的pnpm等。
- yarn是对npm的一个优化版,npm是串行安装依赖,也就是一个一个安装包,比较耗时。yarn是并行安装,能大大提升安装速度。
- cnpm是咱么国产的魔改npm。针对国内“特殊网络”访问外网困难进行的特殊升级。
不过,本人不建议使用。因为有些包没有正式的新
。 - pnpm是近几年才出来的,对比npm,最明显的提升就是体现在依赖包的引入方式上。举个例子,现在我们要引入echarts。假设我们有10个项目,那么每个项目都会单独下载引入一份,也就是占了磁盘10份的空间。而使用pnpm的话,会将echarts先下载到磁盘的一个公共区域,然后每个项目是指向这个区域,共用一份空间。好处就不用多说了。
二、what is package.json
package.json本质上就是存在于咱么web项目中根目录下的一个json文件。一般来说的话,常见的结构如下:
我们在使用npm进行依赖安装的前提,就是你的项目得先存在这么个文件
。生成该文件的方式,要么你手动创建输入;要么通过npm指令:
npm init
接着会让你输入一系列的配置参数:
PS E:\workspace\H5\GeoMap> npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help init` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (geomap)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to E:\workspace\H5\GeoMap\package.json:
{
"name": "geomap",
"version": "1.0.0",
"main": "index.js",
"directories": {
"lib": "lib"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"description": ""
}
Is this OK? (yes)
全部回车的话,就都走默认值。然后你的根目录就会生成这个json文件。当然后期,你可以随意修改这个文件里面的值。
简单的理解这个文件的话,就等同于windows里面的注册表
。
关于package.json的详细介绍,可以看这位老哥的说明:https://blog.csdn.net/qq_34703156/article/details/121401990
三、npm的一些常用命令
1、安装、更新npm
npm一般是和node.js绑在一起的。也就是你安装node.js的时候,就会附带安装好npm。但是呢,有个问题。npm的更新速度远远大于node.js。因此,在很多时候,你的npm版本都是小于官方正式版本的。查看npm版本的指令如下:
npm -v
某些ide如webstorm,在你打开项目的时候,会在console里面提示你npm版本过低,这时可以点击里面的快捷方式进行升级。当然,手动升级也行,指令如下:
npm install npm@latest -g
@
的后面跟的是你要升级的npm的版本号。最新的版本号,可以在https://www.npmjs.com中搜索npm关键字,选择第一项即可查看:
当然也可以写成上面的latest
,表明直接更新到npm包管理器里面的最新版本。-g
表明是全局安装,一般都是全局。
2、通过npm安装项目依赖
2.1 基本指令
npm install xxx
这里的xxx代表安装的依赖包的名称(不准确,后面会细说)。
PS:该指令中的
install
可以简写为i
。
具体的可以在https://www.npmjs.com中搜索。例如我们要安装echarts,那么写法就应该是:
npm install echarts
这样的话,就会在线帮我们安装好npm远程仓库中最新的echarts。
但是这样不会在package.json中存根,间接地就会导致别人在使用我们的项目安装依赖后,运行项目报错的可能。所以,我们一般要这样写:
npm install echarts --save
加上--save
后缀后,就会在package.json的dependencies
子节点里面生成我们的依赖项,例如我们安装完echarts后,就会生成诸如下面的语句:
"dependencies": {
"echarts": "^5.4.2"
}
PS:
--save
可以简写为-s
安装指令还可以跟-dev
的后缀。-dev
表明我们要安装的依赖只在dev开发环境中生效,如果打包到线上的话,就不需要这些包了。一般像eslint、css loader、bebel等,都属于上线后不需要的依赖,指令写法如下:
npm install xxx -dev --save
PS:
-dev
可以简写为-d
2.2 安装指定版本依赖
通过在要安装的依赖的名字的后面跟上 @
version 就能安装指定版本的依赖包。这里的version是要符合Semver
的版本号。
PS:
Semer
的全程叫做Semantic versioning
,中文译为语义化版本
。官网其实有比较详细的解释:semantic versions in published packages Using semantic versioning to specify update types your package can accept Example Resources To keep the JavaScript ecosystem healthy, reliable, and secure, every time you make significant updates to an npm package you own, we recommend publishing a new version of the package with an updated version number in the package.json file that follows the semantic versioning spec. Following the semantic versioning spec helps other developers who depend on your code understand the extent of changes in a given version, and adjust their own code if necessary. Note: If you introduce a change that breaks a package dependency, we strongly recommend incrementing the version major number; see below for details. Incrementing semantic versions in published packages To help developers who rely on your code, we recommend starting your package version at 1.0.0 and incrementing as follows: Code status Stage Rule Example version First release New product Start with 1.0.0 1.0.0 Backward compatible bug fixes Patch release Increment the third digit 1.0.1 Backward compatible new features Minor release Increment the middle digit and reset last digit to zero 1.1.0 Changes that break backward compatibility Major release Increment the first digit and reset middle and last digits to zero 2.0.0 Using semantic versioning to specify update types your package can accept You can specify which update types your package can accept from dependencies in your package's package.json file. For example, to specify acceptable version ranges up to 1.0.4, use the following syntax: Patch releases: 1.0 or 1.0.x or ~1.0.4 Minor releases: 1 or 1.x or ^1.0.4 Major releases: * or x For more information on semantic versioning syntax, see the npm semver calculator. Example "dependencies": { "my_dep": "^1.0.0", "another_dep": "~2.2.0" }, ```
从官方描述来看,我们可以将版本号拆分为a.b.c的格式。例如1.0.2。1,0,2分别对应了大版本号
,小更新版本号
和补丁版本号
。一般来说:
- 大版本号往往是内部做了很大的改动,兼容性不好。比如vue2和vue3这种。如果动大版本号,就要考虑你的项目是否能适配该依赖。
- 小更新一般是对功能上做了一些扩展优化,兼容性是比较好的。
- 补丁版本是对当前的版本里面的一些问题进行修复,兼容性比较好。
依赖包的开发者根据这些规则,指定相关的上传包。依赖包的使用者在使用npm install xxx@version指令的时候去下载相应的包:
version写法 | 作用 | 涉及版本级别 |
---|---|---|
1.0 | 更新前缀为1.0的最新版本,例如更新到1.0.3,不会到1.1.x的地步 | 补丁级 |
1.0.x | 更新前缀为1.0的最新版本,不会到1.1.x的地步 | 补丁级 |
~1.0.2 | 更新前缀为1.0的最新版本,不会到1.1.x的地步 | 补丁级 |
1 | 更新前缀为1的最新版本,例如更新到1.5.2,不会到2.0.1的地步 | 小更新级 |
1.x | 更新前缀为1的最新版本,例如更新到1.5.2,不会到2.0.1的地步 | 小更新级 |
^1.0.2 | 更新前缀为1的最新版本,例如更新到1.5.2,不会到2.0.1的地步 | 小更新级 |
* | 更新npm远程库里面的最新版本,例如更新到3.0.1 | 大更新 |
x | 更新npm远程库里面的最新版本,例如更新到3.0.1 | 大更新 |
举个例子,我们已经安装了4.7.0的echarts,此时的package.json是这样的:
"dependencies": {
"echarts": "^4.7.0"
}
这时,如果我们再执行一次npm install
,那么就会更新到4.9.0而不是5.4.2。
2.3 给依赖取别名
最近在做一个数据大盘项目,当中涉及到了用echartst绘制图表。其中还包含了飞机的航线图。其中,飞机航线图所用到的echarts只能是4.7.0的,否则不能绘制航线。但是4.x版本的echarts没有圆角功能,导致很多图表绘制出来不精致。因此就需要引入两个版本的echarts。但是普通的npm install echarts方式只能安装一种,最后终于找到了解决办法,就是给echarts取别名。
现在,我们先使用默认的安装方式,安装了5.4.2版本的echarts后,再执行下面的指令安装4.7.0的echarts:
npm install echarts4 npm:echarts@4.7.0 -s
当执行完后,package.json里面就会出现echarts4的项了:
"dependencies": {
"echarts": "5.4.2",
"echarts4": "npm:echarts@4.7.0"
}
与此同时,node_modules里面也会生成echarts4的文件夹:
使用方式也很简单,默认的echarts5的引入方式如下:
import * as echarts from 'echarts'
当然。as后面本身也是一个别名,你也可以写成echarts5。
echarts4的引入方式,则改为:
import * as echarts from 'echarts4'
from后面的路径要和你安装时取的别名一致。
2.4 使用本地相对依赖
这一块对于大多数开发者来说,并不常见。我在查阅vue-admin-better的源码时看到了这一块。我们一般都使用的是远程的依赖。但是其实,npm还支持本地的依赖。其基本语法为:
npm install xxx@file:path
xxx代表的是一个依赖包名,也就是相应的package.json里面的name字段的属性。path是你这个依赖包相对于根目录的路径。由于是包名,因此我们需要先构造一个依赖包的结构。在这里,我们先在项目根目录模拟构造一个最简单依赖包结构:
其中至少包含一个package.json和一个入口js文件。
入口js文件的内容如下:
const GAMES = [
{
'label': '射击类FPS',
'value': 0
},
{
'label': '角色扮演类RPG',
'value': 1
},
{
'label': '赛车类RACE',
'value': 2
}
]
export {
GAMES
}
很简单,就是单纯地生成一个常量,然后导出。
package.json的内容如下:
{
"name": "constant",
"version": "0.0.1",
"main": "test.js"
}
包含了一个包的name,版本号和入口js路劲(相对于当前的包的文件夹来说的)。
这个时候,我们执行安装指令:
npm install constant@file:constant -s
运行完后,package.json会生成:
并且在node_modules中会生成指向constant的软链:
使用依赖包的方式也很简单,
import {GAMES} from 'constant'
这样就能调用到我们刚才设置的变量。
上面是个相对简单的例子,实际项目中的结构要比这个复杂一些,我再举一个相对规范的例子。
假设我们的本地依赖包都放在了一个lib的目录中,还是以这个constant为例:
constant的结构如下,增加了二级菜单:
这个时候,我们要修改一下package.json如下:
{
"name": "constant2",
"version": "0.0.1",
"main": "js/test.js"
}
修改了包名,main的入口函数项上套上相对路径。然后安装时,也加上相对路径:
npm install constant2@file:lib/constant -s
这时的package.json会生成:
"dependencies": {
"constant": "file:constant",
"constant2": "file:lib/constant"
}
同样地,node_modules里面,也会生成软链:
通过两个例子,我们就很好地掌握了如何使用本地依赖地方式。
2.5 依赖远程私有库
这里引入这篇文章的解释:https://www.lmlphp.com/user/59042/article/item/2097221/
对于 npm 公用包来说是比较方便的,直接引用即可。而内网的代码应该怎么引入呢?大概有以下几种方式:
- npm 公有包
- npm 私有包
- 搭建 npm 私有服务器
- git 仓库
公有包肯定是满足不了需求的,因为所有人都能下载包,也就意味着代码是被泄漏了,首先被排除。npm 私有包是收费的,
而搭建 npm 服务器也是需要一定成本的,所以最好的方案自然是采用 npm 安装 git 仓库的方式了。
下看 npm 对于安装 git 仓库的命令:
npm install <git remote url>
实际上就是直接 install 一个 URL 而已。对于一些公有仓库, npm 还是做了一些集成的,比如 github等(示例全部出自 npm 官方文档):
npm install github:mygithubuser/myproject
npm install bitbucket:mybitbucketuser/myproject
npm install gitlab:myusr/myproj#semver:^5.0
如果我们直接安装 github 上,使用网址的方式可以表示为:
npm install git+https://isaacs@github.com/npm/npm.git
看下 npm 安装 git 仓库的协议:
<protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>[#<commit-ish> | #semver:<semver>]
即 protocol 支持 git
, git+ssh
, git+http
, git+https
, git+file
,私有仓库需要用户名和密码时需要填写用户名和密码,semver 表示需要使用的版本号, 不过貌似不生效。(npm 中有个包 semver 是专门用于比较包的版本号大小)
直接写 #branch
表示需要安装的分支号。
所以在开发过程中我们可以这么写包:
npm i git+https://username:password@git.example.com/path/reposity#master
或者使用打的 tag
npm i git+https://username:password@git.example.com/path/reposity#1.0.0
可能存在的问题是:
由于新版的 npm install 在安装时会使用 package-lock.json, 有时候同一分支不会从 github 上拉取最新的,
可能需要手动再安装一下(拿自己的仓库试了下,果然不会更新),所以安装时尽量以 tag 为标签进行安装,这样确保代码是正确的
此外,由于私有仓库都是需要密码的,这个时候需要提供一个公共账号和密码,某种程度上不利于管理吧。