npm package.json 要知道的事

1,479 阅读12分钟

npm package.json

当执行'npm init' 时候会生成'package.json' 文件,可以理解成是项目的清单,能够帮我们列出项目所依赖的包, 可以指定项目可以使用的包版本等等

结构

必须遵守 JSON 格式,否则,尝试以编程的方式访问其属性的程序则无法读取它

常见字段解释
'version' --  表明了当前的版本。
'name'    -- 设置了应用程序/软件包的名称。
'description' -- 是应用程序/软件包的简短描述。
'main'  -- 设置了应用程序的入口点。
'private'  -- 如果设置为 true,则可以防止应用程序/软件包被意外地发布到 npm。
'scripts'  -- 定义了一组可以运行的 node 脚本。
'dependencies' -- 设置了作为依赖安装的 npm 软件包的列表。
'devDependencies' -- 设置了作为开发依赖安装的 npm 软件包的列表。
'engines'  -- 设置了此软件包/应用程序在哪个版本的 Node.js 上运行。
'browserslist' -- 用于告知要支持哪些浏览器(及其版本)。

版本

1.包的版本管理各种版本符号
version 精确匹配版本
>version 必须大于某个版本
>=version 大于等于
<version 小于
<=versionversion 小于
~version "约等于":  
^version "兼容版本":
version1 - version2 相当于 >=version1 <=version2.
range1 || range2 范围1和范围2满足任意一个都行
关于版本
1.npm采用了semver规范作为依赖版本管理方案。
 按照semver的约定,一个npm依赖包的版本格式一般为:
 '主版本号.次版本号.修订号(x.y.z)' -- 小提示 js有一个专门校验'semver'1.1.'主版本号(也叫大版本,major version)':
 大版本的改动很可能是一次颠覆性的改动,也就意味着可能存在与低版本不兼容的API或
 者用法,(比如 vue 2 -> 3)。

 1.2.'次版本号(也叫小版本,minor version)':
 小版本的改动应当兼容同一个大版本内的API和用法,因此应该让开发者无感。所以我们
 通常只说大版本号,很少会精确到小版本号。

1.3'修订号(也叫补丁,patch)':
 一般用于修复bug或者很细微的变更,也需要保持向前兼容

'注意':
如果大版本号是 0 的话,表示软件处于开发初始阶段,一切都可能随时被改变,
可能每个小版本之间也会存在不兼容性。所以在选择依赖时,尽量避开大版本号是 
0 的包。

1.4.'先行版本' 如下图vue3发行版本为例
  当某个版本改动比较大、并非稳定而且可能无法满足预期的兼容性需求时,
  你可能要先发布一个先行版本。
  先行版本号可以加到“主版本号.次版本号.修订号”的后面,先加上一个连接号再加上
  一连串以句点分隔的标识符和版本编译信息。
  '内部版本(alpha)、公测版本(beta)、正式版本的候选版本rc: 即 Release candiate'
   
   alpha(α):预览版,或者叫内部测试版;一般不向外部发布,会有很多bug;一般只有
   测试人员使用。

   beta(β):测试版,或者叫公开测试版;这个阶段的版本会一直加入新的功能;在alpha
   版之后推出。

   rc(release candidate):最终测试版本;可能成为最终产品的候选版本,如果未出现问
   题则可发布成为正式版本。

2.'简单的总结':
 semver作为包版本管理规范。此规范规定软件版本由三个部分组成:
  2.1.主版本号做了不兼容的重大变更
  2.2.次版本号做了向下兼容的功能添加
  2.3.补丁版本号做了向下兼容的bug修复
  • vue3 版本发行

image.png

对于各个标志含义
1.~: 当安装依赖时获取到有新版本时,安装到 x.y.z 中 z 的最新的版本。即保持主版本
号、次版本号不变的情况下,保持修订号的最新版本。
2.^: 当安装依赖时获取到有新版本时,安装到 x.y.z 中 y 和 z 都为最新版本。 即保持主版
本号不变的情况下,保持次版本号、修订版本号为最新版本。
3.'*'"x" 或者 (空) 表示可以匹配任何版本。


注意: '当主版本号为 0 的情况,会被认为是一个不稳定版本'
 主版本号和次版本号都为 0: ^0.0.z、~0.0.z 都被当作固定版本,安装依赖时均不
 会发生变化。
 主版本号为 0: ^0.y.z 表现和 ~0.y.z 相同,只保持修订号为最新版本。
举几个例子
1."signale": "1.4.0": 固定版本号
2."figlet": "*": 任意版本(>=0.0.03."react": "16.x": 匹配主要版本(>=16.0.0 <17.0.04."react": "16.3.x": 匹配主要版本和次要版本(>=16.3.0 <16.4.05."^xxx": 最左侧非0版本号不变,不小于xxx
    ^1.2.3 = >=1.2.3 <2.0.0 主版本号不变
    ^0.1.2 = >=0.1.2 <0.2.0 主、次版本号不变
    ^0.0.2 = = 0.0.2 主、次、补丁版本号都不变
6."~xxx":如果列出了次版本号,则次版本号不变,如果没有列出次版本号,则主版本号不
变,均不小于xxx
    ~1.2.3 = >=1.2.3 <1.3.0 主、次版本号不变
    ~1 = >=1.0.0 <2.0.0 主版本号不变
预发布版本
1.以包开发者的角度来考虑这个问题:假设当前线上版本是 "1.2.3",如果我作了一些改动
需要发布版本 "1.2.4",但我不想直接上线(因为使用 "~1.2.3" 或者 "^1.2.3" 的用户都会
直接静默更新),这就需要使用预发布功能。因此我可能会发布 "1.2.4-alpha.1" 或者 
"1.2.4-beta.1" 等等。

">1.2.4-alpha.1"表示接受 "1.2.4-alpha" 版本下所有大于 1 的预发布版本。因此 "1.2.4-alpha.7" 
是符合要求的,但 "1.2.4-beta.1""1.2.5-alpha.2" 都不符合。此外如果是正式版本(不
带预发布关键词),只要版本号符合要求即可,不检查预发布版本号,例如 "1.2.5", 
"1.3.0" 都是认可的。

"~1.2.4-alpha.1" 表示 ">=1.2.4-alpha.1 < 1.3.0"。这样 "1.2.5", "1.2.4-alpha.2" 都符合条
件,而 "1.2.5-alpha.1", "1.3.0" 不符合。

"^1.2.4-alpha.1" 表示 ">=1.2.4-alpha.1 < 2.0.0"。这样 "1.2.5", "1.2.4-alpha.2", "1.3.0" 
符合条件,而 "1.2.5-alpha.1", "2.0.0" 不符合。
我想知道更多semver语义化

semver语义化版本 2.0.0

小知识点
1. 1.0.0 的版本号用于界定公共 API。当你的软件发布到了正式环境,或者有稳定的API时,
就可以发布1.0.0版本了。所以,当你决定对外部发布一个正式版本的npm包时,把它的
版本标为1.0.0

常用字段

关于'mian' ,'script'

main -- 主入口文件

1.main配置项的值是一个js文件的路径,它将作为程序的主入口文件。也就是说当别人引
用了这个包时import testNpm from 'testNpm',其实引入的就是testNpm/index.js文件所
export出的模块。你也可以自己的入口文件
先看一下找包机制
  • 指定一个具体路径的时候
require('./find.js');
require('./find');
1.require方法根据模块路径查找模块,如果是完整路径,直接引入模块。
 1.1.模块后缀省略,先找同名JS文件再找同名JS文件夹
 1.2.找到了同名文件夹,找文件夹中的index.js
 1.3.如果文件夹中没有index.js就会去当前文件夹中的package.json文件中查找
 main选项中的入口文件(具体路径也可以是指定到node_modules)所以找package.json
 也是一种情况
 1.4.如果找指定的入口文件不存在或者没有指定入口文件就会报错,模块没有被找到
  • 没有指定一个具体路径
require('find');
1.Node.js会假设它是系统模块,Node.js会去node_modules文件夹中
 1.1.首先看是否有该名字的JS文件
 1.2.再看是否有该名字的文件夹
 1.3.如果是文件夹看里面是否有index.js
 1.4.如果没有index.js查看该文件夹中的package.json中的main选项确定模块入口文件
 否则找不到报错
好文

package.json 中 你还不清楚的 browser,module,main 字段优先级 - SegmentFault 思否

bin 指定脚本

很多模块有一个或多个需要配置到PATH路径下的可执行模块,npm让这个工作变得十分
简单(实际上npm本身也是通过bin属性安装为一个可执行命令的)  
如果要用npm的这个功能,在package.json里边配置一个'bin'属性。bin属性是一个已命令
名称为key,本地文件名称为value的map如下:
{ "bin" : { "myapp" : "./cli.js" } }
模块安装的时候,若是全局安装,则'npm'会为'bin'中配置的文件在bin目录下创建一个软连
接(对于windows系统,默认会在'C:\\Users\\username\\AppData\\Roaming\\npm'目录
下),若是局部安装,则会在项目内的./node\_modules/.bin/目录下创建一个软链接。
因此,按上面的例子,当你安装'myapp'的时候,npm就会为cli.js'/usr/local/bin/myapp'路
径创建一个软链接。  
如果你的模块只有一个可执行文件,并且它的命令名称和模块名称一样,你可以只写一个
字符串来代替上面那种配置,例如:
{ "name": "my-program"
, "version": "1.2.5"
, "bin": "./path/to/program" }

作用和如下写法相同:

{ "name": "my-program"
, "version": "1.2.5"
, "bin" : { "my-program" : "./path/to/program" } }
什么是软连接
1.软链接(符号链接)是一类特殊的可执行文件, 其包含有一条以绝对路径或者相对路
径的形式指向其它文件或者目录的引用
举个例子
1.'cli-service'的package.json 的bin 告诉了cli 执行脚本位置,在'node_moduels/.bin'
接下来要做的就是在项目的'./node\_modules/.bin/'目录下创建一个软链接。

  • node_moduels/.bin

scripts -- 指令集合

1.使用'scripts'字段定义脚本命令,运行某个脚本指令'npm run <command> [--silent] [-- <args>...]'
其实上面的指令是'npm run-script <command> [--silent] [-- <args>...]' 缩写,以vuecli为
例常见在'scripts'配置的指令:
"scripts": {
  "serve": "vue-cli-service serve",
  ...
}
关于脚本如何执行问题下面做出一个整理
查看当前所有可执行脚本列表
1. npm run
工作原理
1.本质上执行的是'Shell(一般是 Bash)可以运行的命令',shell是依赖于平台的。
默认情况下, Unix-like 操作系统是'/bin/sh'指令, Windows 操作系统是'cmd.exe'。 
实际的被'/bin/sh'引用的shell也依赖于平台。'npm@5.1.0'你可以使用'script-shell'自定义你的shell配置。

可以运行脚本类型

1.查看执行'shell' 脚本位置 使用指令'npm config get shell',执行完后我的本机配置环境
输出结果为'C:\Windows\system32\cmd.exe',就说明实际是'window'系统的'cmd'命令行工
具。
第一种内部自带指令
1.在win 实际运行在'cmd'中执行的命令,因此系统cmd的内部命令,不需要安装额外的插
件,就可以直接执行,在'npm''scripts'中都可以执行,举个例子:
"scripts":{
	/*系统命令*/
      "ip":"ipconfig"
 }
第二种执行外部命令
1.我们如果安装了node,git等客户端,可以直接在cmd窗口执行(需配置了系统的环境变量)
举个例子当安装了 node 后,我们可以直接在控制台输入'node -v' 来查看node 版本信息
因此也可以执行下面列子
"scripts":{

    /*全局外部命令*/
    "git":"git --version",
    "node":"node -v",

}
第三种执行项目内部
1.当我们安装类似'vuecli'或者'webpack','eslint' 这些项目内部使用的,每当执行npm run
时,会自动新建一个Shell,这个 Shell会将当前项目的'node_modules/.bin'的绝对路径加
入到环境变量PATH中,执行结束后,再将环境变量PATH恢复原样。
可以理解为前目录的'node_modules/.bin'子目录里面的所有脚本,都可以直接用脚本名
调用,而不必加上路径。比如,当前项目的依赖里面有 Mocha,只要直接写'mocha test'
就可以了。
{"test": "mocha test"} 等同于 {"test": "./node_modules/.bin/mocha test"}
疑问全局安装的某些包和非全局安装时候执行
  • 非全局安装的时候
1.非全局安装的时候,当想执行eslint 时候需要我们在scripts 标签配置好脚本,举个例子
scripts:{"eslint-version":"eslint --version"} ,只需要执行'npm run eslint-version',具体为什么会执行
参考上面章节中"第三种执行项目内部"讲的内容
2.如果不想配置'scripts' 其他的执行方法" .\node_modules\.bin\eslint.cmd --version" 直接指定运行
"node_modules\.bin" 文件下的脚本 或者 'node  .\node_modules\eslint\bin\eslint.js --version' 直接
具体到运行的脚本目录
  • 全局安装时
1.全局安装一些包例如'eslint' 直接执行'eslint' 就可以在全局运行,这是因为你在全局
安装时候会在node 的文件所在目录自动添加一个执行shell 脚本,并且node 路径在
系统path 中因此可以直接调用

2.什么是'PATH'环境变量,是告诉系统,当要求系统运行一个程序而没有告诉它程序所在
的完整路径时,系统除了在当前目录下面寻找此程序外,还应到哪些目录下去寻找。
  • 全局的path

image.png

  • 这个路径下能用的指令以我本机为例

image.png

  • 看一下这个脚本内部(实际执行全局node_modules 下bin文件eslint 指令)

image.png

通配符

1.由于 npm 脚本就是 Shell 脚本,因为可以使用 Shell 通配符。
 我们在写脚本命令的时候,常常要匹配文件,这就要用到路径的通配符。
 总的来说*表示任意字符串,在目录中表示1级目录,**表示0级或多级目录,例如:

 1.1. src/*:src目录下的任意文件,匹配 src/a.js; src/b.json;不匹配src/aa/a.js
 1.2. src/*.js:src目录下任何js文件,匹配 src/a.js; 不匹配 src/b.json;src/aa/a.js
 1.3. src/*/*.js:src目录下一级的任意js文件,匹配 src/aa/a.js; 不匹配src/a.js;src/a/aa/a.js
 1.4. src/**/*.js:src目录下的任意js文件,匹配 src/a.js; src/a/a.js; src/a/aa/a.js

传参

1.举个例子比如我们使用的'vuecli' 时候,在当前项目的'node_moduels/.bin' 去查看
一下执行脚本如图,在这里一定要明白'npm run '在执行对应的'scripts' 是对应的执行
窗口调用响应命令而非npm 去调用,是npm 去将具体调用shell指令给到了执行窗口
因此其实'vue-cli-service' 执行时候其实是node 执行,'node' 调用时候可以通过
'process.argv' 获取一个返回的数组

2.关于'process.argv',这个数组包含了启动node进程时的命令行参数。第一个元素为启
动node 进程的可执行文件的绝对路径名process.execPath,第二个元素为当前执行的
JavaScript文件路径。剩余的元素为其他命令行参数。如下图

3.来分析一组指令
"scripts": {
  "serve": "vue-cli-service serve --mode=dev --mobile -config build/example.js"
}
当执行'npm run serve' 实际执行的是
'node  "node_modules/@vue\cli-service\bin\vue-cli-service.js  --mode=dev --mobile -config build/example.js" '
vue-cli-service.js 这个js 程序理论上只要执行'process.argv' 即可获取传递参数
可以获取的打印结果如下:
[ '/usr/local/Cellar/node/7.7.1_1/bin/node',
  '/Users/mac/Vue-projects/hao-cli/node_modules/.bin/vue-cli-service',
  'serve',
  '--mode=dev',
  '--mobile',
  '-config',
  'build/example.js']
实际很多命令行包之所以这么写,都是依赖了 minimist 或者 yargs 等参数解析工具来对
命令行参数进行解析。
其中以 'vue-cli-service ' 为例内部使用'minimist' 进行了解析
  • vue-cli-service 脚本

image.png

  • 运行一个node 文件

image.png

总结
1.npm 其实本身也是通过node 来运行的关于运行脚本阶段参数的传递实际都是利用
'process'这个参数来做的,下图的脚本被配置在'spricts' 中当运行的时候可以发现npm
创建了很多'npm_package_'前缀,加入到'process.env'的变量中,如果你看了下面想了解
更多的文章也可以发现其实通过vuecli 传递的参数也是会加到'process.env.某个自定义'
变量来储存的
 1.1.'npm内部变量':
    当我们在执行npm命令的时候,就会把package.json的参数加上npm_package_前缀,
    加入到process.env的变量中,所以在上面的node代码可以通过        
    process.env.npm_package_name获取到package.json里面配置的name属性。
    
1.2.'npm 命令参数'(npm 自己api 提供的命令参数)
  当我们在运行npm命令时,带上以双横线为后缀的参数:npm 命令 --xx=xx,npm就会 
  把xx加上npm_config_前缀,加入到process.env变量中,如果原来有同名的,命令参数
  的优先级最高,会覆盖掉原来的,所以在上面的node代码可以通过 
  process.env.npm_config_env获取到npm run node --env=npmEnv命令里的参数env的
  值,如果参数没有赋值:npm run node --env,则默认值为true
1.3.'脚本参数'
  这个其实要根据脚本的内容来看,比如我们上面的脚本是node index.js --env=node,这其
  实是纯粹的node命令了,可以通过process.argv来获取node的命令参数,这是个数组,
  第一个为node命令路径,第二个为执行文件路径,后面的值为用空格隔开的其他参数,

  • npm run 时候 env
1.在执行npm run脚本时,npm会设置一些特殊的env环境变量。其中package.json中的所
有字段,都会被设置为以npm_package_ 开头的环境变量,同时,`npm`相关的所有配置
也会被设置为以`npm_config_`开头的环境变量
看个例子
{
  "name": "sh",
  "version": "1.1.1",
  "description": "shenhao",
  "main": "index.js",
  "repository": {
    "type": "git",
    "url": "git+ssh://git@gitlab.com/xxxx/sh.git"
  }
}
可以通过process.env.npm_package_name 可以获取到package.json中name字段的值
sh,也可以通过process.env.npm_package_repository_type获取到嵌套属性type的值git
我想了解更多

置之process.env

我想对vuecli 了解更多

多命令执行

当然命令不仅仅可以执行一条也可以多条按照某种顺序来执行
1.串行执行,要求前一个任务执行成功以后才能执行下一个任务,使用&&符号来连接。
 npm run script1 && npm run script2
2.并行执行,就是多个命令可以同时的平行执行,使用&符号来连接。

npm run script1 & npm run script2

钩子

1.npm脚本有pre,post两类钩子,一个是执行前,一个是执行后。举个例子例如我想
给我在scripts 中定义的server 脚本增加钩子,分别在原有指令前增加了'pre''post'
前缀
"scripts": {
  "preserve": "xxxxx",
  "serve": "vue-cli-service serve",
  "postserve": "xxxxxx"
}
在执行npm run serve命令时,会依次执行npm run preserve、npm run serve、
npm run postserve
2.通常时候可以发现我们并没有去指定,会默默的跳过。如果想要指定钩子,
必须严格按照'pre''pos't前缀来添加
3.'process.env.npm_lifecycle_event'可以配合钩子来一起使用
    const event = process.env.npm_lifecycle_event
    if (event === 'preserve') {
        console.log('Running the preserve task!')
    } else if (_event === 'serve') {
        console.log('Running the serve task!')
    }

参考文章

npm-run-script - 知乎 (zhihu.com)

npm基本用法及原理(10000+) - 漫漫字节|漫漫编程 (mmbyte.com)

npm scripts 使用指南 - 阮一峰的网络日志 (ruanyifeng.com)

npm package.json属性详解

关于版本

package.json 指南

前端工程化 - 剖析npm的包管理机制 (juejin.cn)

前端工程化(5):你所需要的npm知识储备都在这了