# cache-require-paths
|
|
> Caches resolved paths in module require to avoid Node hunting for right module. Speeds up app load.
|
|
[![NPM][cache-require-paths-icon] ][cache-require-paths-url]
|
|
[![Build status][cache-require-paths-ci-image] ][cache-require-paths-ci-url]
|
[![semantic-release][semantic-image] ][semantic-url]
|
|
[cache-require-paths-icon]: https://nodei.co/npm/cache-require-paths.png?downloads=true
|
[cache-require-paths-url]: https://npmjs.org/package/cache-require-paths
|
[cache-require-paths-ci-image]: https://travis-ci.org/bahmutov/cache-require-paths.png?branch=master
|
[cache-require-paths-ci-url]: https://travis-ci.org/bahmutov/cache-require-paths
|
[semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
|
[semantic-url]: https://github.com/semantic-release/semantic-release
|
|
This is a partial solution to Node "hunting" for right file to load when you require a 3rd party
|
dependency. See [Node’s `require` is dog slow](https://kev.inburke.com/kevin/node-require-is-dog-slow/)
|
and [Faster Node app require](http://glebbahmutov.com/blog/faster-node-app-require/) for details.
|
|
## Use
|
|
npm install --save cache-require-paths
|
|
Load the module first in your application file
|
|
```js
|
// index.js
|
require('cache-require-paths');
|
...
|
```
|
|
The first time the app loads, a cache of resolved file paths will be saved to `.cache-require-paths.json`
|
in the current directory. Every application startup after that will reuse this filename cache to avoid
|
"hunting" for the right filename.
|
|
To save cached paths to a different file, set the environmental variable `CACHE_REQUIRE_PATHS_FILE`.
|
|
## Results
|
|
Here are results for loading common packages without and with caching resolved require paths.
|
You can run any of this experiments inside the `test` folder. `node index.js` loads
|
using the standard resolve. `node index.js --cache` uses a cache of the resolves paths.
|
|
Using node 0.10.37
|
|
require('X') | standard (ms) | with cache (ms) | speedup (%)
|
------------------------------------------------------------------
|
express@4.12.3 | 72 | 46 | 36
|
karma@0.12.31 | 230 | 170 | 26
|
grunt@0.4.5 | 120 | 95 | 20
|
sails@0.11.0 | 170 | 120 | 29
|
|
Using node 0.12.2 - all startup times became slower.
|
|
require('X') | standard (ms) | with cache (ms) | speedup (%)
|
------------------------------------------------------------------
|
express@4.12.3 | 90 | 55 | 38
|
karma@0.12.31 | 250 | 200 | 20
|
grunt@0.4.5 | 150 | 120 | 20
|
sails@0.11.0 | 200 | 145 | 27
|
|
## TODO
|
|
- [ ] Cache only the absolute paths (relative paths resolve quickly)
|
- [ ] Invalidate cache if dependencies in the package.json change
|
|
## Discussion
|
|
You can see Node on Mac OS X searchig for a file to load when loading an absolute path
|
like `require(express)` by using the following command to make a log of all system level
|
calls from Node (start this from another terminal before running node program)
|
|
sudo dtruss -d -n 'node' > /tmp/require.log 2>&1
|
|
Then run the test program, for example in the `test` folder run
|
|
$ node index.js
|
|
Kill the `dtruss` process and open the generated `/tmp/require.log`. It shows every system call
|
with the following 4 columns: process id (should be single node process), relative time (microseconds),
|
system call with arguments, and after the equality sign the numerical result of the call.
|
|
When loading `express` dependency from the test program using `require('express');` we see
|
the following search (I abbreviated paths for clarity):
|
|
# microseconds call
|
664730 stat64(".../test/node_modules/express\0", 0x7FFF5FBFECF8, 0x204) = 0 0
|
664784 stat64(".../test/node_modules/express.js\0", 0x7FFF5FBFED28, 0x204) = -1 Err#2
|
664834 stat64(".../test/node_modules/express.json\0", 0x7FFF5FBFED28, 0x204) = -1 Err#2
|
664859 stat64(".../test/node_modules/express.node\0", 0x7FFF5FBFED28, 0x204) = -1 Err#2
|
664969 open(".../test/node_modules/express/package.json\0", 0x0, 0x1B6) = 11 0
|
664976 fstat64(0xB, 0x7FFF5FBFEC38, 0x1B6) = 0 0
|
665022 read(0xB, "{\n \"name\": \"express\", ...}", 0x103D) = 4157 0
|
665030 close(0xB) = 0 0
|
|
By default, Node checks if the local `node_modules/express` folder exists first (first `stat64` call),
|
Then it tries to check the status of the `node_modules/express.js` file and fails.
|
Then `node_modules/express.json` file. Then `node_modules/express.node` file. Finally it opens
|
the `node_modules/express/package.json` file and reads the contents.
|
|
Note that this is not the end of the story. Node loader only loads `express/package.json` to fetch
|
`main` filename or use the default `index.js`! Each wasted file system call takes only 100 microseconds,
|
but the tiny delays add up to hundreds of milliseconds and finally seconds for larger frameworks.
|
|
Profile the same program with `--cache` option added to the command line arguments
|
|
$ node index.js --cache
|
|
This option loads the `cache-require-paths` module as the first require of the application
|
|
```js
|
var useCache = process.argv.some(function (str) {
|
return str === '--cache';
|
});
|
if (useCache) {
|
console.log('using filename cache');
|
require('cache-require-paths');
|
}
|
```
|
|
The trace now shows *no calls to find `express` package*, just straight load of the `express/index.js` file.
|
|
643466 stat64(".../node_modules/express/index.js\0", 0x7FFF5FBFED28, 0x3) = 0 0
|
643501 lstat64(".../node_modules\0", 0x7FFF5FBFED08, 0x3) = 0 0
|
643513 lstat64(".../node_modules/express\0", 0x7FFF5FBFED08, 0x3) = 0 0
|
643523 lstat64(".../node_modules/express/index.js\0", 0x7FFF5FBFED08, 0x3) = 0 0
|
643598 open(".../node_modules/express/index.js\0", 0x0, 0x1B6) = 12 0
|
643600 fstat64(0xC, 0x7FFF5FBFED58, 0x1B6) = 0 0
|
|
Mission achieved. Note that the speedup only happens after the first application run finishes successfully.
|
The resolution cache needs to be saved to a local file, and this happens only on process exit.
|
|
## Small print
|
|
Author: Gleb Bahmutov © 2015
|
|
* [@bahmutov](https://twitter.com/bahmutov)
|
* [glebbahmutov.com](http://glebbahmutov.com)
|
* [blog](http://glebbahmutov.com/blog)
|
|
License: MIT - do anything with the code, but don't blame me if it does not work.
|
|
Spread the word: tweet, star on github, etc.
|
|
Support: if you find any problems with this module, email / tweet /
|
[open issue](https://github.com/bahmutov/cache-require-paths/issues) on Github
|