npm 和 npx 的区别?
npx 和 npm 是两个 Node.js 包管理工具中的命令行工具,它们之间有一些区别:
-
npm是Node.js的包管理器,用于安装和管理 JavaScript 模块,以及在项目中运行脚本。npx是npm 5.2.0及更高版本中包含的一个命令行工具,用于执行本地安装的或在线安装的 Node.js 包中的命令。 -
npm安装的包需要在本地全局或项目依赖中进行安装,才能在命令行中直接使用。而 npx 可以在不需要全局安装的情况下直接运行某个包中的命令。 -
npx会首先检查本地是否存在指定的包,如果存在则直接运行,如果不存在则先下载该包,再执行其中的命令。这使得npx更加灵活,不需要事先安装一个包就能立即运行它的命令。
总之,npm 主要用于管理和安装依赖包,而 npx 主要用于运行本地或在线安装的包中的命令。
package.json bin字段:
全局包中的 bin 字段的作用:
package.json bin 中设置 命令,全局安装时,此命令会在安装后注册到全局 ./node_modules/.bin/ 二进制文件;
-
当只有一个命令时, 可以放在 bin字段 对象中,设置
key : value, 命令为key -
当只有一个命令时, 还可以省略
bin字段对象,只设置bin指向的 执行文件地址, 命令为package.jsonname字段值; -
当有多个命令时,可以放在
bin字段 对象中,设置多组key : value, 命令分别为key; -
[注]:
value只能是文件的路径
示例:
// test-package package.json
{
"bin": {
"test1": "cli.js",
"test2": "cli2.js"
}
}
// 1. 如果只有一个命令的话
// 1-1: 命令 test1
{
"bin": {
"test1": "cli.js"
}
}
// 1-2: 命令 test-package
{
"name": "test-package"
"bin": "cli.js"
}
// 2. 如果有多个命令
{
"bin": {
"test1": "cli.js",
"test2": "cli2.js"
}
}
用法:
// test-package
npm install test-package -g
> test1
// -> test1 node 脚本
> test2
// -> test2 node 脚本
什么是 main bin ?
// 1. 当 <package> 的 package.json 中 bin字段对象中有多条 <key:value> 时,key值 和 name字段的 value值 相同时, <命令 key> 为 main bin;
{
"name": "test-pkg",
"bin": {
"test-pkg": "./cli.js",
"test1": "./cli1.js"
}
}
}
// 2. 当 <package> 的 package.json 中 bin字段对象中只有一条 <key:value> 时, 此时这一条命令 <命令 key> 为 main bin;
{
"name": "test-pkg",
"bin": {
"test1": "./cli1.js"
}
}
// 3. 当 <package> 的 package.json 中 bin字段 的 value值为一个指向执行文件的路径字符串时,name字段的 value值,即为 main bin;
{
"name": "test-pkg",
"bin": "./cli.js"
}
// 4. 当 bin 字段对象中有多个 <key:value>, 并且没有 key值 等于 package.json 中的 name 字段值时,会按照 位置排名选出 main bin 命令;
// package.json 如下:
{
"name": "test-pkg",
"bin": {
"b": "./b.js",
"a": "./a.js",
}
}
// 此时,<command a> 即为 main bin;
// [注]:此判定方式在 npm v5~v6 版本 验证如此;npm v7~x 版本会抛出错误: `npm ERR! could not determine executable to run`
npx 命令:
功能:
优先本地包中执行命令;
介绍:
-
npx是npm v5.2.0引入的一条命令(npx),一个npm包执行器,指在提高从npm注册表使用软件包时的体验。 -
npx命令执行- 会在
~/.npm/_npx最终生成包的临时文件; - 然后执行 的
main bin指定的可执行脚本文件;
- 会在
-
npx的原理很简单,就是运行的时候,会到node_modules/.bin路径和环境变量$PATH里面,检测命令是否存在,存在则执行,不存在则临时安装<package>来执行。
版本差异:
V1:
npm v5.2.0 -- v6.14.18 :
node v14 - (npm v6.14.17) 及 node v14 之前版本:
npx <package>
// 1. 如果local 和 global 都找不到此 package 时, 会在 ~/.npm/_npx 下载生成包的临时文件夹,然后执行 主bin 所指定的可执行脚本文件;
// 2. 执行完毕之后,删除临时包文件夹;
main bin 命令:
// 1. 当 bin 字段对象中有多个 <key:value>, 并且没有 key值 等于 package.json 中的 name 字段值时,会按照 位置排名选出 main bin 命令;
// 如:
// package.json
{
"name": "test-pkg",
"bin": {
"b": "./b.js",
"a": "./a.js",
}
}
// 此时,<command a> 即为 main bin;
// 其它 main bin 的判定方式,参考 "什么是 main bin ?"
V2:
npm v7.0.0 -- v8.19.4:
node v15 - (npm v7.7.6):
npx <package>
// 1.如果 local 和 global 都找不到此 <package> 时,会先做一个 Need to install the following packages: 的提醒,由用户手动输入 y or yes 来决定是否继续安装;
// 2.安装之后,同样会在 ~/.npm/_npx 下载生成一个包的临时文件夹,然后执行 主bin 所指定的可执行文件;
// 3.执行完毕之后,并不会立即删除临时包文件夹;
// 》🤔这里留了个疑问,这个临时文件什么时候删除!?还是就是不删除了!?
// 》 猜测,npx 运行时,会去检测远程包的更新情况,来决定是否重新拉取远程包;如果有变化,会执行重新安装流程,否则,直接使用 ~/.npm/_npx 中已存在的临时包;
V3:
npm v9.0.0 ~ x:
node v16, v18,v20 - (npm v9.6.7) :
npx <package>
// ~/.npm/_npx 中已有<package>缓存,则使用缓存中的版本(不一定是 @latest 版本),然后执行 主bin 命令指定的可执行文件
// ~/.npm/_npx 没有<package>的缓存,则拉取最新版本的包,然后执行 主bin 命令指定的可执行文件 npx <package>[@version]
// 会拉去对应版本的包,然后执行 主bin 命令指定的可执行文件 npx <package>[@latest]
// 会拉取最新版本的包,然后执行 主bin 命令指定的可执行文件 npx clear-npx-cache
// 清除 ~/.npm/_npx 下的缓存文件
参考: issue
语法:
// 快捷使用方式:
npx -- <pkg>[@<version>] [args...]
npx --package=<pkg>[@<version>] -- <cmd> [args...]
npx -c '<cmd> [args...]'
npx --package=foo -c '<cmd> [args...]'
// 详细介绍:
从 npm 的可执行包执行命令
npx [选项] <命令>[@版本] [命令的参数]...
npx [选项] [-p|--package <包>]... <命令> [命令的参数]...
npx [选项] -c '<命令的字符串>'
npx --shell-auto-fallback [命令行解释器]
选项:
--package, -p 包安装的路径 [字符串]
--cache npm 缓存路径 [字符串]
--always-spawn Always spawn a child process to execute the command. [布尔]
--no-install 如果有包缺失,跳过安装 [布尔]
--userconfig 当前用户的 npmrc 路径 [字符串]
--call, -c 像执行 `npm run-script` 一样执行一个字符串 [字符串]
--shell, -s 执行命令用到的解释器,可选 [字符串] [默认值: false]
--shell-auto-fallback 产生“找不到命令”的错误码 [字符串] [可选值: "", "bash", "fish", "zsh"]
--ignore-existing 忽略 $PATH 或工程里已有的可执行文件,这会强制使 npx 临时安装一次,并且使用其最新的版本 [布尔]
--quiet, -q 隐藏 npx 的输出,子命令不会受到影响 [布尔]
--npm 为了执行内部操作的 npm 可执行文件 [字符串] [默认值: "~/.nvm/versions/node/v14.21.2/lib/node_modules/npm/bin/npm-cli.js"]
--node-arg, -n 调用 node 二进制时使用额外的 node 参数。 [字符串]
--version, -v 显示版本号 [布尔]
--help, -h 显示帮助信息 [布尔]
执行流程描述:
// 命令:
$ npx <package>
// 执行流程
if <package> 已经做了 local install
// 1. 找到,local ./node_modules/<package> 目录下的 package.json 中的 bin 字段 指定的主bin <key>:<file path>;
// 2. 到 local ./node_modules/.bin 目录下,找打以 <key> 为名的二进制可执行文件,进行执行
else if <package> 未做 local install, 做了 global install
// 1. 寻找 local(本地) ./node_modules/<package> , 未找到。
// 2. 继续寻找 global(全局) ./node_modules/<package>, 找到;
// 3. 记录 <package> 目录下 package.json 中的 bin字段 指定的主bin <key> 值;
// 4. 到 global ./node_modules/.bin 目录下,找打以 <key> 为名的二进制可执行文件,进行执行;
else if <package> 未做 local install, 未做 global install
// 1. 寻找 local(本地) ./node_modules/<package> , 未找到。
// 2. 寻找 global(全局) ./node_modules/<package> , 未找到。
// 3. 在 ~/.npm/_npx 目录下,做临时安装;生成临时目录[Dir](经过算法计算), 临时目录[Dir]下,生成 package.json 并 安装 <package> 到 node_modules,
// 并将 node_modules/<package> package.json 中的 bin字段 指定的主bin <key>:<file path>, <file path> 所指定的文件以 <key> 命名生成可执行的二进制文件;
// 4. 执行临时目录[Dir] ./node_modules/.bin 下找打以 <key> 为名的二进制可执行文件,进行执行;
// 5. 执行结束,删除临时目录[Dir];
测试:
npx执行本地安装包的bin命令
cd my-app
npm install <pkg>
npx <pkg> [arg...]
# npx 会执行 ./node_modules/.bin/<pkg bin中命令> 二进制文件
npx执行全局安装包的bin命令
npm install
<pkg> -g
npx <pkg> [arg...]
# npx 会执行全局 ./node_modules/.bin/<pkg bin中命令> 二进制文件
npx [arg...]过程
npx <pkg> [arg...]
# step 1. 检测本地 ./node_modules/<pkg> 是否存在,存在,则找到包的 main bin 进行执行;
# step 2. 检测全局 ./node_modules/<pkg> 是否存在,存在,则找到包的 main bin 进行执行;
# step 3. a. 在 ~/.npm/_npx 目录下,做临时安装;生成临时目录[Dir](经过算法计算), 临时目录[Dir]下,生成 package.json 并 安装 <package> 到 node_modules,
# b. 并将 node_modules/<package> package.json 中的 bin字段 指定的主bin <key>:<file path>, <file path> 所指定的文件以 <key> 命名生成可执行的二进制文件;
# c. 执行临时目录[Dir] ./node_modules/.bin 下找打以 <key> 为名的二进制可执行文件,进行执行;
# d. 执行结束,删除临时目录[Dir];
示例:
# 当通过 npx 运行时,双连字符 -- 标志可以用来抑制 npm 对应该发送到被执行命令的开关和选项的解析:
npx -- <bin command> --package=<package>
# 等同
npx --package=<package> -- <bin command>
# 指定 --package 选项,可以执行与包名称不匹配的命令:
npx --package=<package> -- <bin command>
# 不指定 --package 选项,只会执行与包名称一致的命令
npx <bin commond> -c 'test2' // 此时只会执行 <bin commond>, -c 后边的 'test2' 并不会执行
# npx 相当于快速执行 bin 目录中的命令; node_modules/.bin 中存在的二进制文件,都可以通过 npx 执行;(前提是 源文件还在, 也就是对应 node_modules/<package>/package.json bin字段中所指定文件还在)
# 如:node_modules/.bin 中包含 test1、test2 两个二进制文件;
npx test1 # test1 被执行
npx test2 # test2 被执行
参考:
npm-exec
功能:
从 npm 包运行命令;
概要:
npm exec -- <pkg>[@<version>] [args...]
npm exec --package=<pkg>[@<version>] -- <cmd> [args...]
npm exec -c '<cmd> [args...]'
npm exec --package=foo -c '<cmd> [args...]'
alias: x
描述:
- 这个命令允许你从 npm 包(本地安装或远程获取)中运行任意命令。于 npx 功能基本相同;
v7.0.0之后加入的命令;- 临时文件包存储位置
~/.npm/_npx;
用法:
1、直接使用包和参数命令
npm exec -- lerna init
npm exec -- lerna@latest init
2、指定包 --package 和 命令参数
npm exec --package=lerna@latest -- lerna init
3、使用 --package 指定包 和 --call 指定命令
npm exec --package=lerna -c "lerna init"
4、只指定 --call 命令,不指定 --package
npm exec -c "lerna init"
npm exec -c "echo hello"
如果不指定 --package ,那么-c的命令参数,需要 lerna 命令可以直接执行。
npx 和 npm exec 的区别?
语法对比:
npm exec:
npm exec -- <pkg>[@<version>] [args...]
npm exec --package=<pkg>[@<version>] -- <cmd> [args...]
npm exec -c '<cmd> [args...]'
npm exec --package=foo -c '<cmd> [args...]'
alias: x
npx :
npx -- <pkg>[@<version>] [args...]
npx --package=<pkg>[@<version>] -- <cmd> [args...]
npx -c '<cmd> [args...]'
npx --package=foo -c '<cmd> [args...]'
结论:
从上述概要对比上来看, npm exec 和 npx 的语法是一样的,npx 语法相当于 npm exec 的简写;
执行效果对比
npm exec :
npm exec foo@latest bar --package=@npmcli/foo
# 首先,npm 将首先解析 --package 选项,解析 @npmcli/foo 包
# 然后,它将在该上下文中执行以下命令:
foo@latest bar
npx :
npx foo@latest bar --package=@npmcli/foo
# npm 先解析 foo 包名
# 再执行 如下命令:
foo bar --package=@npmcli/foo
# 由于 --package 选项出现在位置参数之后,因此它被视为所执行命令的参数。
建议:
# npm exec 建议使用双连字符显式地告诉 npm 停止解析命令行选项和开关
# 因此,以下命令相当于上面的 npx 命令:
npm exec -- foo@latest bar --package=@npmcli/foo
总结:
npx 和 npm exec 作用基本相同,只是解析选项的顺序有所不同;
npm init
功能:
npm init主要作用,创建一个 package.json 文件;npm init <initializer>执行一个包的初始化器(本地 或 远程)
概要:
npm init [--force|-f|--yes|-y|--scope]
npm init <@scope> (same as `npx <@scope>/create`)
npm init [<@scope>/]<name> (same as `npx [<@scope>/]create-<name>`)
aliases: create, innit
描述:
-
npm init传统方式作用,快速创建包的package.json; -
npm init <initializer>作用,可以利用<initializer>快速建立一个新包或现有的npm包;-
这里的 是一个名为
create-<initializer>的npm包,它将被npm-exec安装,然后执行它的主bin-- 可能是创建或更新 package.json,并运行其他与初始化相关的操作。 -
注意:如果用户已经全局安装了
create-<initializer>包,则npm init将使用该包。如果你想让npm使用最新版本,或者其他特定版本,你必须指定它:npm init foo@latest# 从注册中心获取并运行最新的create-foonpm init foo@1.2.3# 专门运行create-foo@1.2.3
-
任何其他选项都将直接传递给命令,因此
npm init foo -- --hello将映射到npm exec -- create-foo --hello。
-
用法:
npm init 传统使用:
# npm init 传统使用
npm init
# 初始化一个 package.json 文件, 内容命令交互界面手动设置
npm init [-y|--yes]
# 快速初始化一个 package.json 文件, 快速将内容直接写进了 package.json,无需手动设置;(同 npm init [-f|--force])
npm init [-f|--force]
# 快速初始化一个 package.json 文件, 快速将内容直接写进了 package.json,无需手动设置;(同 npm init [-y|--yes])
npm init [--scope=<scopeName>]
# 1. 初始化一个 package.json 文件,内容命令交互界面手动设置;
# 2. 默认: package name: (@<scopeName>/<packageName>)
#
# [注]. 默认情况下,scoped package是私有的,想发私有 module 的话,需要充值成为私有模块用户;
# 公共范围模块是免费的,不需要付费订阅。要发布公共范围模块,需在发布时设置访问选项 --access=public。此选项将保留为所有后续发布设置。
// 小知识点:
// 发布公共的 scoped package:
// 1. 命令行 方式:
$ npm publish --access=public
// 2. package.json 方式:
{
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
npm init <initializer> 使用:
npm init <package-spec>:
// npm init <initializer> 使用:
npm init <package-spec>
// 1. 提示安装 create-<package>[@<version>]
// > Ok to proceed? (y)
// 按下 y键
// 2. 他会被 npm exec <package>,然后执行它的 主bin;
npm init <@scope>
// 1. 提示安装 <@scope>/create[@version]
// > Ok to proceed? (y)
// 按下 y键
// 2. 他会被 npm exec <@scope>/create[@version],然后执行它的 主bin;
npm init <@scope>/<name>
// 1. 提示安装 <@scope>/create-<name>[@version]
// > Ok to proceed? (y)
// 按下 y键
// 2. 他会被 npm exec <@scope>/create-<name>[@version],然后执行它的 主bin;
init 命令被转换成相应的 npm exec / npx 操作,如下所示:
npm init foo
// -> npm exec create-foo
// -> npx create-foo
npm init @usr
// -> npm exec @usr/create
// -> npx @usr/create
npm init @usr/foo
// -> npm exec @usr/create-foo
// -> npx @usr/create-foo
npm init @usr@2.0.0
// -> npm exec @usr/create@2.0.0
// -> npx @usr/create@2.0.0
npm init @usr/foo@2.0.0
// -> npm exec @usr/create-foo@2.0.0
// -> npx @usr/create-foo@2.0.0
npx 和 yarn create 和 npm create 的区别
应用方式:
以 create-react-app 为例,文档给了我们三种创建应用的方式,如下:
# npx 方式
npx create-react-app my-app
# init 方式
npm init react-app my-app
# create 方式
npm create react-app my-app
执行分析
npx方式,先下载create-react-app最新包,执行可执行文件后,即删除;npm init方式,先全局安装create-react-app最新包,若已存在,则升级到最新版本,然后执行包的可执行文件,执行完即删除;npm create方式,npm create是npm init的别名, 用法一样;(参考 官网文档)yarn create方式,先全局安装create-react-app的包,若已经存在,则升级到最新版本,然后执行包的可执行文件,执行完不进行删除,会保留在全局;
对比结果
npm init和yarn create利用包名规则create-*,先全局下载到本地再执行npx <package>没有包名约束,临时下载执行后删除- 三者效果一样,均使用最新包进行命令的执行