27
votes

First off, I'm very new to npm and grunt. We have a project that we are using Grunt to compile and produce the output files for. I am trying to setup our build server to use Grunt to produce the output files. We are using Windows with TFS source control, and due to it's 260 character path limit, we are not able to check the grunt-bower-task module into source control (as it alone uses 230 characters in its installed path).

When I run npm install from my project directory it works fine and installs the following required modules into the node_modules folder in my project directory:

  • grunt
  • grunt-bower-task
  • grunt-contrib-compass
  • grunt-contrib-connect
  • grunt-contrib-jshint
  • grunt-contrib-requirejs
  • grunt-contrib-watch

And then when I run grunt deploy from my project directory everything works as expected.

While I could simply make running npm install part of the build process, I prefer not to, as it takes a few minutes to download all of the files, and I don't want our builds to depend on an external web service being available.

I've seen that you can install modules either locally or globally, so I'm hoping to just be able to install the modules globally on the build server so that they do not need to be in the node_modules folder directly inside the project directory before running grunt deploy. I've ran npm install -g, as well as npm install -g [module] for each of the modules listed above, as well as npm install -g grunt-cli.

If I do npm prefix -g it shows me that the global module directory is C:\Users[My User]\AppData\Roaming\npm, and when I look in that directory's node_modules folder I do see all of the modules. However, when I run grunt deploy it complains with:

Fatal error: Unable to find local grunt

If I include just the *node_modules\grunt* directory, then I still get these errors:

Local Npm module "grunt-contrib-watch" not found. Is it installed?

Local Npm module "grunt-contrib-jshint" not found. Is it installed?

...

I've also tried using *grunt deploy --base "C:\Users[My User]\AppData\Roaming\npm", but it complains that it then cannot find other files, such as .jshintrc.

So is there a way that I can run grunt deploy and have it check the npm global prefix path for the modules, rather than looking in the project directory?

A hacky work around is to copy the modules manually during the build process to the local project directory, but I would like to avoid this if possible.

For reference, this is what my package.json file looks like:

{
  "name": "MyProject",
  "version": "0.0.1",
  "scripts": {
    "preinstall": "npm i -g grunt-cli bower"
  },
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-compass": "~0.2.0",
    "grunt-contrib-watch": "~0.4.4",
    "grunt-contrib-jshint": "~0.6.0",
    "grunt-contrib-requirejs": "~0.4.1",
    "grunt-contrib-connect": "~0.3.0",
    "grunt-bower-task": "~0.2.3"
  }
}

Thanks.

5
Thank you for so eloquently detailing the very problem I've been having. It was driving me insane.Digs

5 Answers

7
votes

Instead of using the global option for npm, you should be using symbolic links on your modules, to achieve similar results.

Global npm installs are intended only as a convenience for command-line utilities such as jshint, or grunt-cli.

16
votes

Workaround: Explicitly list all transient dependencies in your own package.json.

Say, for example, that you depend on module_a, and that module_a depends on module_b. After npm install you'll have node_modules/module_a/node_modules/module_b/ because npm will install module_b local to module_a. However, if you add module_b as a direct dependency in your package.json (and the version specifiers match exactly), then npm will only install module_b once: at the top level.

This is because when modules are required, they start looking in the nearest node_modules directory and traverse upwards until the required module is found. So npm is able to save disk space by only installing the module at the lowest level at which the versions match.

So, revised example. You depend on [email protected] which depends on [email protected]. If you also depend on [email protected], you'll end up with module_b installed twice. (Version 0.1.0 will be installed at the top level, and 0.2.0 will be installed under module_a.) However, if you depend on v0.2.0 (using the exact version string in your package.json as module_a uses), then npm will notice it can use the same version of module_b. So it will only install module_b at the top level, and not under module_a.

Long story short: add whichever transient dependencies you have that have deep module trees directly to your own package.json and you'll end up with a more shallow node_modules tree.

5
votes

I know this thread is old, but I finally found my own personal answer, using a Mac, but I assume the same can be said/done with PC.

to follow up with bevacqua's answer of:

Instead of using the global option for npm, you should be using symbolic links on your modules, to achieve similar results.

Global npm installs are intended only as a convenience for command-line utilities such as jshint, or grunt-cli.

I did some digging and might be able to give a little more clarification:

I ended up making a global folder in the root of my user directory. Inside that directory, I added all the packages I needed using npm install. So for instance I ran npm install grunt, npm install grunt-contrib-watch, npm install grunt-contrib-less, etc. You can also add a package.json file in the same folder and just run npm install to add them all at once. Now my global directory had the following structure:

.global_grunt_modules
    node_modules
        grunt
        grunt-contrib-watch
        grunt-contrib-less

Then I went to any working project directory where I needed to run grunt, and in the root of that folder ran the command:

ln -s ~/.global_grunt_modules/node_modules .

ln with the -s (symbolic link) flag takes two arguments:

[source_file] [target_dir]

So the command basically says, "create a symbolic link from my global node_modules folder, and link that to my current directory". The . specifies the current working directory.

Hope this helps, I was having trouble as well. Then whenever I have a project that requires a new grunt module, I just install it through my global directory, which will then be available everywhere the symbolic link has been created.

4
votes

I use fenestrate to solve this sort of issues on Windows. Even if it works with Git, if you use Git integration from Visual Studio within Team Explorer it will still crash if long file paths are in your node_modules folder - even if you're not adding that folder to source control.

Usually Grunt and Bower dependency structure causes this.

  1. First thing I would recommend is to run npm dedupe on your packages. What this does, is to scan your already installed packages and see if some dependencies are duplicated. If found on a higher level, it will remove the deeply tested ones.

  2. Secondly, if dedupe does not resolve the issue, if you can find the dependency that's so deeply nested that's causing this issue, try and install it in your solution directly, and run dedupe again.

  3. If there are more package dependencies that cause this, fenestrate will solve the issue. Plus it's really cool because you can hook it to you npm install and it will always run after a new package is installed. Also, it's fully reversable.

I hope this helps.

0
votes

When npm link is not good enough (network file systems,...), you can use the 'requireg' package. This is the only clean solution which somehow makes it 'built-in'. The 'requireg' package has a function globalize which makes subsequent require calls also looking in global.