Subfolders as Dependencies

Use subfolders installed as NPM dependencies to avoid long relative paths in require.

Imagine an NPM project when one file requires another file, maybe in a different directory. We have to use a relative path.

1
2
3
4
package.json
index.js
subfolder/
foo.js
subfolder/foo.js
1
module.exports = 'foo'
index.js
1
2
const foo = require('./subfolder/foo')
console.log('foo:', foo)

Running ./index.js prints "foo" as expected

1
2
$ node .
foo: foo

But in a larger project the paths become longer and longer. I hate seeing long require paths going up several levels before drilling down into a specific subfolder.

1
2
// 😡 where is bar.js? Right, it is in a galaxy far far away
const bar = require('../../../src/lib/utils/bar')

Luckily we can easily avoid this by creating "dummy" internal packages that can wrap entire folders. First, create a subfolder/package.json with some internal name.

subfolder/package.json
1
2
3
4
5
{
"name": "@internal/foo",
"main": "foo.js",
"version": "0.0.0"
}

Second, install this subfolder as an NPM dependency. Yes, you can NPM install folders, tar files and event GitHub repos, see docs.

1
2
3
4
5
6
7
8
9
10
$ node -v
v8.9.4
$ npm -v
5.6.0
$ npm i -S ./subfolder
npm WARN [email protected] No description
npm WARN [email protected] No repository field.

+ @internal/[email protected]
added 1 package in 0.204s

Our subfolder is now linked as a dependency

1
2
3
4
5
6
7
8
$ cat package.json
{
"name": "subfolders",
"version": "1.0.0",
"dependencies": {
"@internal/foo": "file:subfolder"
}
}

Third, change relative path to subfolder/foo.js to use the package name.

index.js
1
2
const foo = require('@internal/foo')
console.log('foo:', foo)

Still works as expected

1
2
$ node .
foo: foo

Don't forget to include the subfolder in the list of distributed files

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "subfolders",
"version": "1.0.0",
"dependencies": {
"@internal/foo": "file:subfolder"
},
"main": "index.js",
"files": [
"index.js",
"subfolder"
],
"scripts": {
"size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";"
}
}
1
2
3
4
5
6
7
8
9
10
$ npm run size

> [email protected] size /subfolders
> t="$(npm pack .)"; wc -c "${t}"; tar tvf "${t}"; rm "${t}";

586 subfolders-1.0.0.tgz
-rw-r--r-- 0 0 0 420 Mar 3 09:26 package/package.json
-rw-r--r-- 0 0 0 260 Mar 3 09:36 package/index.js
-rw-r--r-- 0 0 0 23 Mar 3 09:26 package/subfolder/foo.js
-rw-r--r-- 0 0 0 72 Mar 3 09:26 package/subfolder/package.json

Let us try installing our package from another folder - do we get the subfolder dependency correctly?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ mkdir /tmp/test-subfolder
$ cd /tmp/test-subfolder
$ npm init -y
$ npm i -S /subfolders
[email protected] /private/tmp/test-subfolder
└─┬ [email protected]
└── @internal/[email protected]

npm WARN [email protected] No description
npm WARN [email protected] No repository field.
$ ls node_modules/
@internal subfolders
$ node -e 'require("subfolders")'
foo: foo

Everything is working.