一句话
如果你需要安装的带有CLI的npm包被某个项目依赖,请尽量使用--save-dev
而不要使用--global
安装。
引子
在我们启动一个前端项目时,总会用到一些 npm 上的带有 CLI 的包(package),我们在命令行下把这些包当作全局的系统命令来使用。比如 babel-cli
,express-generator
,browserify
等等。我们往往在需要使用它们时这样做:
npm install -g babel-cli
之后,我们就理所当然的使用它们了:
babel script.js -o script-compiled.js
可是,这样真的好吗?
你在这样做时,也隐隐约约感觉到,这或许有些“脏”(dirty),有点像是泛滥的全局对象。
剥开看看
这种感觉没有错,拿 OSX 举例来说,如果我们使用 npm bin -g
来查看npm的全局包都装到哪了,会得到这样的结果:
/usr/local/bin
这个目录是系统中用来存放需要本地全局使用的二进制可执行文件的地方,属于系统环境的一部分,往往不仅仅被某个或某些应用依赖。
问题在哪
我们将带有CLI的npm包分为两类(非等价类):
- 工程级
- 系统级
如果我们仅仅是在现在的(或将来)某个项目中打算使用它,那么这类包更偏向于 工程级。比如之前文中提到的 browserify
或 babel-cli
。
如果我们更需要将它作为系统中的应用,而不限于在某个 Node.js 项目中依赖它,那么这类包更偏向于 系统级。比如 作为Node.js版本管理器的n
。
更直接一点说,如果某个npm包不被任何项目或应用依赖,那么它即属于 系统级
对于工程级的npm包,如果我们进行全局安装,那么会带来两个问题:
- 全局安装的包会作为系统环境的一部分,但同一系统下的不同项目可能对于其依赖版本不同。
- 项目在迁移或更换环境时,没有显性的依赖表明新环境需要该npm包
如何解决
npm 提供了一种针对于开发过程中依赖的安装方法:
npm install --save-dev <package>
使用这种方法安装的包,会在 package.json
中被显性指出在 devDependencies
中:
{
"name": "my_package",
"version": "1.0.0",
"dependencies": {
"my_dep": "^1.0.0"
},
"devDependencies" : {
"my_test_framework": "^3.1.0"
}
}
但这还有一个问题,如果使用 npm install --save-dev
安装的包,如何在项目中使用CLI呢?
首先,通过 --save-dev
安装的包的可执行文件都被放到了项目中的 ./node_modules/.bin/
中。所以,我们可以通过该目录执行需要本地运行的CLI命令,比如:
./node_modules/.bin/babel script.js -o script-compiled.js
更进一步,我们也可以把命令放入package.json
的scripts
中,比如:
{
...
"scripts": {
"babel": "babel"
}
}
因为npm会在执行时首先检查当前项目中的 ./node_moduels/.bin/
目录,这样在执行时只需要输入:
npm run babel -- script.js -o script-compiled.js
注意,传递参数前需要加 --
。
另外,还可以把 ./node_modules/.bin/
加入到系统环境变量 PATH
中,但这样只能在包含 node_modules
的项目目录下使用CLI命令。这种做法其实更加增多了项目对系统环境的隐性依赖,不推荐。
结论
古人有云:“两害相较,则取其轻”。相比之前全局安装,使用 --save-dev
安装的npm包,在执行CLI命令时的确会比较麻烦,但这样做的好处也是显而易见且更为重要的,而对于增加的使用成本,另有许多成熟方法可以解决。在我们使用npm时,对于 工程级 和 系统级 的包的区分有时并不是那么明显,希望通过这篇文章能让大家意识到使用全局安装的意义,并在管理项目依赖时多一些思考。
参考
网友评论