使用package.json全局和本地安装依赖项

210

使用npm,我们可以使用-g选项全局安装模块。如何在package.json文件中实现这一点?

假设以下是我在package.json文件中的依赖项

"dependencies": {
    "mongoose": "1.4.0",
    "node.io" : "0.3.3",
    "jquery"  : "1.5.1",
    "jsdom"   : "0.2.0",
    "cron"    : "0.1.2"
  }

当我运行npm install时,我只想将node.io全局安装,其余的应该在本地安装。是否有这个选项?


11
你不能这样做。但是你可以在 package.json 文件中为一个模块设置 "preferGlobal": true 选项来实现类似的效果。 - Raynos
是的,我知道关于<code>preferGlobal</code>的事情,但那会将所有依赖项全局安装...不管怎样,谢谢!我猜没有这样的功能... - Madhusudhan
3
我认为它并不会这样做。它会将当前模块全局安装。如果某个依赖项将其设置为true,则也可能被全局安装。你应该在 #node.js 中直接询问 @isaacs。 - Raynos
3
全球性的安装可能会产生依赖关系问题。比如软件包A需要版本为0.3.3的库,而软件包B则需要0.3.4版的库,但两个版本互不兼容。这时你就需要使用两台机器来分别安装这两个软件包。 - nalply
11
这些评论都没有帮助我解决这个问题......如果您的代码能够展示比"preferGlobal":true更多的内容,那就太好了......但我不知道在package.json文件中应该放在哪里。根据NPM文档:preferGlobal是针对您自己的包的,设置后将使您的包被安装为全局包。不过看起来更像是一份指南。 - PPPaul
7个回答

233

新笔记:你可能不想或不需要这样做。你可能只需要将构建/测试等命令依赖项放在package.json文件的devDependencies部分中即可。任何时候你在package.json中使用scripts中的内容,node_modules/.bin下的devDependencies命令就会像它们在你的路径中一样被执行。

例如:

npm i --save-dev mocha # Install test runner locally
npm i --save-dev babel # Install current babel locally

然后在 package.json 文件中:

// devDependencies has mocha and babel now

"scripts": {
  "test": "mocha",
  "build": "babel -d lib src",
  "prepublish": "babel -d lib src"
}

然后在命令提示符处,您可以运行:

npm run build # finds babel
npm test # finds mocha

npm publish # will run babel first

新的提示:最近我们有了npx,可以让你运行devDependencies命令而无需将它们添加到scripts部分(如果你想的话)。 例如:

npx webpack

但是如果您真的想要全局安装,您可以在package.json的scripts部分中添加preinstall:

"scripts": {
  "preinstall": "npm i -g themodule"
}

实际上,我的npm install执行了另一次npm install...这很奇怪,但似乎可以工作。

注意:如果您使用的是npm的最常见设置,其中全局Node包安装需要sudo权限,则可能会出现问题。一种选择是更改您的npm配置,以使其无需此操作:

npm config set prefix ~/npm,通过将export PATH=$HOME/npm/bin:$PATH添加到您的~/.bashrc文件中,在$PATH中添加$HOME/npm/bin。

另一种可能更好的选择是只使用nvm来管理Node,这样您就不会遇到这个问题。


3
我无法通过npm i -g underscore-cli让它工作。它会显示关于wd错误的警告,我猜wd是指工作目录。当我在命令行手动执行时,一切都很好,但我希望用户能够通过简单的npm install来安装我的代码。 - PPPaul
3
PPPaul -- 最近我再次尝试这个技巧时遇到了同样的问题。可能是我的设置现在有所不同,或者它只能与某些模块一起使用。否则我猜测是 npm 发生了一些变化? - Jason Livesay
11
除此之外,你可以预先检查包是否已安装:npm list module -g || npm install module -g,因为npm会返回正确的退出值。 - m90
3
这应该是一个独立的问题。但是,您可以将命令放在一个脚本中,并将脚本指定为要执行的命令(例如"preinstall" : "scripts/preinstall.sh")。 - We Are All Monica
1
@CMCDragonkai 使用 && 连接它们,例如 npm install -g bower && npm install -g grunt-cli - Matsemann
显示剩余5条评论

12

由于下面所描述的缺点,我建议遵循被接受的答案:

使用npm install --save-dev [package_name],然后使用以下脚本执行:

$ npm run lint
$ npm run build
$ npm test

以下是我的原始但不建议的答案。


您可以将软件包添加到您的devDependencies--save-dev),而无需全局安装,然后从项目中的任何位置运行二进制文件:

"$(npm bin)/<executable_name>" <arguments>...

在您的情况下:

"$(npm bin)"/node.io --help

这位工程师提供了一个npm-exec别名作为快捷方式。该工程师使用名为env.sh的shell脚本。但我更喜欢直接使用$(npm bin),以避免任何额外的文件或设置。

虽然这会使每次调用都稍微变大一点,但它应该运行正常,避免了:

  • 与全局包潜在依赖冲突 (@nalply)
  • 需要使用sudo
  • 需要设置npm前缀(尽管我建议仍然使用)

缺点:

  • $(npm bin)在Windows上无法使用。
  • 开发树中更深的工具将不会出现在npm bin文件夹中。(安装npm-runnpm-which来查找它们。)

似乎更好的解决方案是将常见任务(如构建和缩小)放在您的package.json“scripts”部分中,就像Jason在上面演示的那样。


在你的.bashrc中添加一个别名,以便轻松地将bin/目录添加到你的PATH环境变量中:alias nodebin='export PATH=$(npm bin)/:$PATH'。执行nodebin,然后你可以像往常一样输入命令。 - gitaarik
我不知道为什么它不能用于团队。当然,你需要设置它,如果你不喜欢使用别名,那是你的选择。但在团队中使用它不会有任何坏处。 - gitaarik

10

这有点老了,但我遇到了这个要求,所以这是我想出的解决方案。

问题:

我们的开发团队维护着许多.NET Web应用程序产品,我们正在将其迁移到AngularJS / Bootstrap。 VS2010不容易适应自定义构建流程,我的开发人员通常在多个产品的发布上工作。我们的版本控制系统是Subversion(我知道,我知道。我正在尝试转换到Git,但我的顽固营销人员如此苛刻),一个单独的VS解决方案将包括几个独立的项目。我需要我的员工拥有一种通用方法来初始化他们的开发环境,而无需在同一台机器上多次安装相同的Node软件包(gulp、bower等)。

简而言之:

  1. 需要“npm install”安装全局的Node/Bower开发环境以及.NET产品的所有本地所需包。

  2. 仅当未安装时才应安装全局软件包。

  3. 必须自动创建到全局包的本地链接。

解决方案:

我们已经拥有一个所有开发人员和所有产品共享的通用开发框架,因此我创建了一个NodeJS脚本,在需要时安装全局软件包并创建本地链接。该脚本位于相对于产品根文件夹的“....\SharedFiles”中。

/*******************************************************************************
* $Id: npm-setup.js 12785 2016-01-29 16:34:49Z sthames $
* ==============================================================================
* Parameters: 'links' - Create links in local environment, optional.
* 
* <p>NodeJS script to install common development environment packages in global
* environment. <c>packages</c> object contains list of packages to install.</p>
* 
* <p>Including 'links' creates links in local environment to global packages.</p>
* 
* <p><b>npm ls -g --json</b> command is run to provide the current list of 
* global packages for comparison to required packages. Packages are installed 
* only if not installed. If the package is installed but is not the required 
* package version, the existing package is removed and the required package is 
* installed.</p>.
*
* <p>When provided as a "preinstall" script in a "package.json" file, the "npm
* install" command calls this to verify global dependencies are installed.</p>
*******************************************************************************/
var exec = require('child_process').exec;
var fs   = require('fs');
var path = require('path');

/*---------------------------------------------------------------*/
/* List of packages to install and 'from' value to pass to 'npm  */
/* install'. Value must match the 'from' field in 'npm ls -json' */
/* so this script will recognize a package is already installed. */
/*---------------------------------------------------------------*/
var packages = 
  {
  "bower"                      :                      "bower@1.7.2", 
  "event-stream"               :               "event-stream@3.3.2",
  "gulp"                       :                       "gulp@3.9.0",
  "gulp-angular-templatecache" : "gulp-angular-templatecache@1.8.0",
  "gulp-clean"                 :                 "gulp-clean@0.3.1", 
  "gulp-concat"                :                "gulp-concat@2.6.0",
  "gulp-debug"                 :                 "gulp-debug@2.1.2",
  "gulp-filter"                :                "gulp-filter@3.0.1",
  "gulp-grep-contents"         :         "gulp-grep-contents@0.0.1",
  "gulp-if"                    :                    "gulp-if@2.0.0", 
  "gulp-inject"                :                "gulp-inject@3.0.0", 
  "gulp-minify-css"            :            "gulp-minify-css@1.2.3",
  "gulp-minify-html"           :           "gulp-minify-html@1.0.5",
  "gulp-minify-inline"         :         "gulp-minify-inline@0.1.1",
  "gulp-ng-annotate"           :           "gulp-ng-annotate@1.1.0",
  "gulp-processhtml"           :           "gulp-processhtml@1.1.0",
  "gulp-rev"                   :                   "gulp-rev@6.0.1",
  "gulp-rev-replace"           :           "gulp-rev-replace@0.4.3",
  "gulp-uglify"                :                "gulp-uglify@1.5.1",
  "gulp-useref"                :                "gulp-useref@3.0.4",
  "gulp-util"                  :                  "gulp-util@3.0.7",
  "lazypipe"                   :                   "lazypipe@1.0.1",
  "q"                          :                          "q@1.4.1",
  "through2"                   :                   "through2@2.0.0",

  /*---------------------------------------------------------------*/
  /* fork of 0.2.14 allows passing parameters to main-bower-files. */
  /*---------------------------------------------------------------*/
  "bower-main"                 : "git+https://github.com/Pyo25/bower-main.git" 
  }

/*******************************************************************************
* run */
/**
* Executes <c>cmd</c> in the shell and calls <c>cb</c> on success. Error aborts.
* 
* Note: Error code -4082 is EBUSY error which is sometimes thrown by npm for 
* reasons unknown. Possibly this is due to antivirus program scanning the file 
* but it sometimes happens in cases where an antivirus program does not explain 
* it. The error generally will not happen a second time so this method will call 
* itself to try the command again if the EBUSY error occurs.
* 
* @param  cmd  Command to execute.
* @param  cb   Method to call on success. Text returned from stdout is input.
*******************************************************************************/
var run = function(cmd, cb)
  {
  /*---------------------------------------------*/
  /* Increase the maxBuffer to 10MB for commands */
  /* with a lot of output. This is not necessary */
  /* with spawn but it has other issues.         */
  /*---------------------------------------------*/
  exec(cmd, { maxBuffer: 1000*1024 }, function(err, stdout)
    {
    if      (!err)                   cb(stdout);
    else if (err.code | 0 == -4082) run(cmd, cb);
    else throw err;
    });
  };

/*******************************************************************************
* runCommand */
/**
* Logs the command and calls <c>run</c>.
*******************************************************************************/
var runCommand = function(cmd, cb)
  {
  console.log(cmd);
  run(cmd, cb);
  }

/*******************************************************************************
* Main line
*******************************************************************************/
var doLinks  = (process.argv[2] || "").toLowerCase() == 'links';
var names    = Object.keys(packages);
var name;
var installed;
var links;

/*------------------------------------------*/
/* Get the list of installed packages for   */
/* version comparison and install packages. */
/*------------------------------------------*/
console.log('Configuring global Node environment...')
run('npm ls -g --json', function(stdout)
  {
  installed = JSON.parse(stdout).dependencies || {};
  doWhile();
  });

/*--------------------------------------------*/
/* Start of asynchronous package installation */
/* loop. Do until all packages installed.     */
/*--------------------------------------------*/
var doWhile = function()
  {
  if (name = names.shift())
    doWhile0();
  }

var doWhile0 = function()
  {
  /*----------------------------------------------*/
  /* Installed package specification comes from   */
  /* 'from' field of installed packages. Required */
  /* specification comes from the packages list.  */
  /*----------------------------------------------*/
  var current  = (installed[name] || {}).from;
  var required =   packages[name];

  /*---------------------------------------*/
  /* Install the package if not installed. */
  /*---------------------------------------*/
  if (!current)
    runCommand('npm install -g '+required, doWhile1);

  /*------------------------------------*/
  /* If the installed version does not  */
  /* match, uninstall and then install. */
  /*------------------------------------*/
  else if (current != required)
    {
    delete installed[name];
    runCommand('npm remove -g '+name, function() 
      {
      runCommand('npm remove '+name, doWhile0);
      });
    }

  /*------------------------------------*/
  /* Skip package if already installed. */
  /*------------------------------------*/
  else
    doWhile1();
  };

var doWhile1 = function()
  {
  /*-------------------------------------------------------*/
  /* Create link to global package from local environment. */
  /*-------------------------------------------------------*/
  if (doLinks && !fs.existsSync(path.join('node_modules', name)))
    runCommand('npm link '+name, doWhile);
  else
    doWhile();
  };

现在,如果我想为我们的开发人员更新全局工具,我会更新“packages”对象并提交新脚本。我的开发人员将其检出,并使用“node npm-setup.js”或在任何正在开发的产品下使用“npm install”来更新全局环境。整个过程只需要5分钟。

此外,要为新开发人员配置环境,他们必须先安装NodeJS和适用于Windows的GIT,然后重新启动计算机,检查“Shared Files”文件夹和任何正在开发的产品,然后开始工作。

对于.NET产品的“package.json”调用此脚本以进行安装:

{ 
"name"                    : "Books",
"description"             : "Node (npm) configuration for Books Database Web Application Tools",
"version"                 : "2.1.1",
"private"                 : true,
"scripts":
  {
  "preinstall"            : "node ../../SharedFiles/npm-setup.js links",
  "postinstall"           : "bower install"
  },
"dependencies": {}
}

注意事项

  • 需要在Windows环境中使用正斜杠 "/" 引用脚本。

  • "npm ls" 命令会为所有本地链接的软件包给出 "npm ERR!extraneous:" 错误消息,因为它们未列在 "package.json" 的 "dependencies" 中。

编辑 1/29/16

上面更新的 npm-setup.js 脚本已被修改如下:

  • var packages 中的软件包 "version" 现在是命令行传递给 npm install 的 "package" 值。这样更改是为了允许从其他位置安装软件包。

  • 如果软件包已经安装但不是所请求的软件包,则删除现有软件包并安装正确的软件包。

  • 由于原因未知,当执行安装或链接时,npm 偶尔会抛出 EBUSY 错误 (-4082)。此错误被捕获,然后重新执行命令。该错误很少第二次发生,而且似乎总是清除。


这真是个救命稻草@sthames42!我已经花了几个小时来寻找如何做到这一点。清晰、全面、非常棒。#加分问题: (a) 既然Bower已经在包列表中,为什么还要在postinstall中使用它? (b) 如何不将全局包链接到本地?只需在命令中不包含“links”即可? - MaxRocket
@MaxRocket:很高兴我能帮到你。我已经更新了答案,包括我的最新版本,它的效果要好得多。回答:(a) 'bower install' 命令在 'npm install' 完成后运行,以安装 bower.json 文件中列出的 Bower 组件(此处未显示)。我希望我的人员能够输入 'npm install' 并完全设置他们的环境,而不必再输入其他命令。(b) 是的。 - sthames42
此脚本的当前版本现在在这里维护。 - sthames42

9
您可以使用单独的文件,例如npm_globals.txt,而不是package.json。该文件将每个模块放在新行上,如下所示:
mongoose@1.4.0
node.io@0.3.3
jquery@1.5.1
jsdom@0.2.0
cron@0.1.2

然后在命令行中运行:

< npm_globals.txt xargs npm install -g

请通过以下方式检查它们是否已正确安装:

npm list -g --depth=0

至于你是否应该这样做,我认为这完全取决于使用情况。对于大多数项目来说,这并不是必要的;而且将你的项目的package.json封装在一起使用这些工具和依赖项更加可取。

但是现在我发现当我跳到一个新的机器上时,我总是全局安装create-react-app和其他CLI。当版本不太重要时,拥有一种简单的方法来安装全局工具及其依赖项是很好的。

现在,我正在使用npx一种npm包运行器,而不是全局安装软件包。


1
这类似于Python中的pip根据requirements文件安装软件包。即使现在有了npx,您仍然可能需要像在Docker中一样全局安装软件包。像这样的txt文件可以确保您可以在本地或全局安装完全相同的软件包集。 - Edward

6

自己编写脚本以安装全局依赖。这很简单。package.json 可以很容易地扩展。

const { execSync } = require('child_process');
const fs = require('fs');

const package = JSON.parse(fs.readFileSync('package.json'));

let keys = Object.keys(package.dependencies);
let values = Object.values(package.dependencies);


for (let index = 0; index < keys.length; index++) {
    const key = keys[index];
    let value = values[index].replace("~", "").replace("^", "");

    console.log(`Installing: ${key}@${value} globally`,);
    execSync('npm i -g ' + `${key}@${value}`);
}

使用以上方法,您甚至可以使其在下方内联显示!
请查看以下的预安装:
{
  "name": "Project Name",
  "version": "0.1.0",
  "description": "Project Description",
  "main": "app.js",
  "scripts": {
    "preinstall": "node -e \"const {execSync} = require('child_process'); JSON.parse(fs.readFileSync('package.json')).globalDependencies.forEach(globaldep => execSync('npm i -g ' + globaldep));\"",
    "build": "your transpile/compile script",
    "start": "node app.js",
    "test": "./node_modules/.bin/mocha --reporter spec",
    "patch-release": "npm version patch && npm publish && git add . && git commit -m \"auto-commit\" && git push --follow-tags"
  },
  "dependencies": [
  },
  "globalDependencies": [
    "cordova@8.1.2",
    "ionic",
    "potato"
  ],
  "author": "author",
  "license": "MIT",
  "devDependencies": {
    "chai": "^4.2.0",
    "mocha": "^5.2.0"
  },
  "bin": {
    "app": "app.js"
  }
}

Node的开发者可能不会承认package.json是一个项目文件,但实际上它就是。


1
我喜欢你的解决方案,并对脚本进行了一些更改,以便直接与实际依赖关系配合使用。希望这样对你没有问题。 - mosu
1
foreach 应该不是 forEach 吗? - ggorlen

3
所有的模块都已经安装在./node_modules/目录下,这是package.json的参考文档,具体请参考NPM

0

这可能会导致生产中的问题。 如果项目依赖项安装在项目文件夹外部,如果其他人删除或替换您的软件包或更改文件夹权限,代码可能会中断。

将所有东西放在一个文件夹中更持久,并使系统可预测和维护任务更容易。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接