How include node_modules in output directory with TypeScript
This solution is an improvement of the naive "copy the whole folder" solution. It copy-paste only needed dependencies and/or devDependencies from the source node_modules to the target folder. Only works on POSIX environments. If cross-platform is mandatory then I advice you to stick to the cp
strategy or a node-js script.
{
"scripts": {
"build": "tsc <your command line options>",
"postbuild": "out='lib/node_modules' && root=$(npm root) && mkdir -p $out && npm ls --production --parseable | tail --lines=+2 | sed \"s%$root%%\" | cut --delimiter='/' --fields=2 | sort | uniq | xargs -I % cp -r \"$root/%\" $out"
}
}
You can list only dependencies or devDependencies respectively with --production
and --development
flags of the npm ls
command
How it works
out='lib/node_modules' \ # output dir relative to package.json
&& root=$(npm root) \ # get path to node_modules dir
&& mkdir -p $out \
&& npm ls --production --parseable \ # (1)
| tail --lines=+2 \ # (2)
| sed "s%$root%%" \ # (3)
| cut --delimiter='/' --fields=2 \ # (4)
| sort | uniq \ # (5) optional
| xargs -I % cp -r "$root/%" $out # (6)
(1) List packages. We use npm ls
which lists installed packages in tree form. Note that the default format output contains superfluous information and packages can be listed multiple times. Thus we use the --parseable
flag.
$ npm ls --parseable
/home/code
/home/code/node_modules/mongoose
/home/code/node_modules/is-typed-array
/home/code/node_modules/is-typed-array/node_modules/es-abstract
...
(2) remove the first output line because we won't use it. Here package is-typed-array has its own copy of package es-abstract.
/home/code/node_modules/mongoose
/home/code/node_modules/is-typed-array
/home/code/node_modules/is-typed-array/node_modules/es-abstract
...
(3) remove node_modules path from each package path.
/mongoose
/is-typed-array
/is-typed-array/node_modules/es-abstract
...
(4) keep the first path member
/mongoose
/is-typed-array
/is-typed-array
...
(5) remove the duplicate lines. This step is optional but recommended as we won't copy the same package multiple times. However it can slow down the whole process.
mongoose
is-typed-array
...
(6) final step: copy. You can log the executed actions with xargs -t
cp -r /home/code/node_modules/is-typed-array lib/node_modules
cp -r /home/code/node_modules/mongoosee lib/node_modules
...
For this purpose I created a simple gulp task.
gulpfile.js:
var gulp = require('gulp');
var install = require('gulp-install');
const PROD_DEST = '../dist';
gulp.task('default', function () {
return gulp.src(['./package.json'])
.pipe(gulp.dest(PROD_DEST))
.pipe(install({
args: ['only=production']
}));
});
package.json
:
...
"scripts": {
"build:prod": "tsc && gulp"
}
"devDependencies": {
"gulp": "^3.9.1",
"gulp-install": "^0.6.0",
},
...
This way I can run npm run build:prod
to transpile the TypeScript sources into PROD_DEST
.gulp
copies the package.json
into that folder and runs npm install --only=production
in it which installs only the runtime dependencies.
This approach is cleaner if you have a lot of devDependencies and platform independent.
I like Peopleware's solution a lot more than the accepted solution, but you don't even need gulp for that. You can simply do this in your package.json:
{
"scripts": {
"build": "tsc <your command line options>",
"postbuild": "cp package.json dist/package.json && cd dist && npm install --only=production"
}
}
The benefit of doing it this way is that you're not copying the entirety of your node_modules
folder, because it might have a ton of dependencies only used during development.
You can do the same with static assets such as images or what not with:
"copy-statics": "cp -r static dist/static"
Update: instead of using npm install --only-production
it is safer to copy both package.json
and package-lock.json
and then run npm ci --production
. This ensures that you only install the dependency snapshot that you have in your package-lock.json
. So the command would look like this:
"postbuild": "cp package*.json dist && cd dist && npm ci --production"
You should be able to just cp
the directory over. If you want to automate it you can wrap your tsc
and cp
calls up in npm scripts in your package.json:
{
"scripts": {
"build": "tsc <your command line options>",
"postbuild": "cp -R node_modules lib/node_modules"
}
}
Then when you use npm run build
your cp
command should automatically run as well.