Exclude Node.js dev dependencies in .zip file#3737
Conversation
98e4c1f to
7c631be
Compare
65d9087 to
5ce023a
Compare
| const fileExistsSync = require('../../../utils/fs/fileExistsSync'); | ||
|
|
||
| module.exports = { | ||
| zipDirectory(exclude, include, zipFileName) { |
There was a problem hiding this comment.
There is here a major issue if the serverless.yml file has multiple functions and each function is packaged individually. The problem is that this function is called for all serverless functions before anything is zipped, but each call to zipDirectory overrides the include, exclude, and zipFileName properties of this. This means that the last call to zipDirectory "wins" and all serverless functions end up being deployed with the code of the last data set by zipDirectory. This basically breaks the deployment if the serverless functions are packaged individually.
| excludeDevDependencies() { | ||
| // take care of node runtime-services | ||
| try { | ||
| const packageJsonFilePath = path.join(this.serverless.config.servicePath, 'package.json'); |
There was a problem hiding this comment.
I almost missed that one. If a serverless project has multiple functions packaged individually, it can well be that each function is developed in a nodejs sub-project. In such a project, this will refer to the package.json file of the root/parent project, which does not have the right dependencies.
We actually exactly do that: the serverless.yml is in a parent nodejs project that refers to nodejs "sub-projects", each having a separate package.json file. However there is no way to specify the root path of each subproject in the serverless.yml file, so maybe there is a need for a new property. Otherwise you will always filter the production dependencies of the parent project (usually just an empty reactor project used to build all the sub-projects). For example, we use https://github.com/lerna/lerna to do that.
|
Thanks for the feedback on this one @cjelger 👍 I agree. We should support that. I'll rebase this PR and work on a fix for it the next couple of hrs. |
5ce023a to
26e8881
Compare
|
Thanks for the link to the issue @cjelger 👍 I just pushed the changes. Now nested directories are supported as well out of the box. |
|
@cjelger Just looked into the code again and I think the issue with "the last" zip wins is still around. Will look into it and resolve it ASAP. |
430d180 to
40674e8
Compare
40674e8 to
9f67a39
Compare
|
@cjelger I fixed the problem and packaging individually should be working with this PR. Would be nice if you could take another look at it. Thanks in advance! 👍 |
|
Does this PR also remove README and package.json files? I was able to trim ~1MB from a 3.5MB zip just removing those files. |
@ccannell thanks for the question. This PR will only exclude dev dependencies for Node.js files for the time being (by using |
|
@pmuens I tested your code again and I'm sorry but it still doesn't properly work for me. With the fix for Openwhisk sequences (see here, had to do that to get the latest version working with Openwhisk), each function is zipped properly but it contains the production dependencies of absolutely all node packages that were found under the service path. |
|
Thanks for getting back @cjelger 👍 🤔 that's strange do you have any repo with code we can look into to reproduce this? A @eahefnawy and I tried it with different setups and the exclusion of |
|
I cannot share the big repo I'm working on but I will create a dummy project to show you the issue. I hope I can do that by the end of the week. |
|
Thanks @cjelger and @ryanmurakami 👍 We'll look into it and work on a fix for that. |
| .execSync('npm ls --prod=true --parseable=true --silent') | ||
| .toString().trim(); | ||
|
|
||
| const prodDependencyPaths = prodDependencies.match(/(node_modules\/.*)/g); |
There was a problem hiding this comment.
@pmuens This does not work with Windows. \/ has to be replaced with path.sep.
The output of npm ls --prod=true --parseable=true --silent under Windows returns backslashes as path separators.
There was a problem hiding this comment.
Good catch! Thanks for pointing that out @HyperBrain 👍 !
|
Okay, I spent about an hour digging into the root causes of why 1.16 doesn't work for us anymore. Turns out it's this piece of code: The resulting string is cut off after 8k of data. That's not enough to hold all dependencies with their full paths on the file system. That causes the |
|
Thanks for investigating @arabold 👍 🎉 Really helpful! Any idea how this could be solved? |
|
I've found this (nodejs/node#3033) and this (nodejs/node#2972). Seems to be a Node issue. We're using 6.10 here. I'm looking for a workaround now... :( |
|
The only reliable and cross-platform way of solving this seems to be a temporary file. I don't have the time to implement a full pull request today, but here's some code that actually worked locally and returned the whole list of dependencies. const childProcess = require('child_process');
const path = require('path');
const fs = require('fs');
const BbPromise = require('bluebird');
const outputFile = path.join('.serverless', 'deps.tmp') // FIXME Use the proper var for the temp path;
childProcess.execSync(`npm ls --prod=true --parseable=true --silent >${outputFile}`); // FIXME Error handling!
return BbPromise.fromCallback(cb => fs.readFile(outputFile, 'utf8', cb))
.then(prodDependencies => {
console.log(prodDependencies);
console.log(`${prodDependencies.length} chars`);
}); |
|
Awesome! Thanks for looking into this @arabold! 👍 This really helps a lot! We'll try to implement this in the upcoming week 🎉 |
Serverless 0.16.0 introduced a feature which excludes Node.js dev dependencies from .zip files. The feature uses package.json to find which dependencies can be excluded. serverless/serverless#3737 This commit adds the package.json to the .build folder, so that Serverless can find the package.json file.
|
Hi All, I ran into an issue with this patch: if there are no prod dependencies, then the entire node_modules directory is included. This appears to be intentional, based on the I'm happy to provide a patch that works through this, but didn't want to do that if this was the desired behavior. |
|
I tested with 1.17; looks like #3889 is newer, and I haven't tried that yet. |
Thanks for getting back @ttarhan 👍 It would be nice if you could test it with v1.18 to see if this was resolved in the meantime. Could you please open up a new issue if this is still around after v1.18 so that we can discuss it separately? Thanks in advance! |
What did you implement:
Closes #2709
Exclude the
devnode_modules when zipping to decrease the bundle size.How did you implement it:
Get a list of the production node_modules. Add the
node_modules/**glob to theexcludeand re-include the productionnode_modulesvia corresponding<package-name>/**globs in theincludeglob.How can we verify it:
Create a service and do a
npm init.Run
npm install --save node-fetchandnpm install --save-dev jest.Run
serverless packageand inspect the zip file. It should only include the dependencies necessary for thenode-fetchmodule (and not the ones forjest).Note: please use nested directories with dependencies as well. Those should also only include the dev dependencies after packaging.
Todos:
Is this ready for review?: YES
Is it a breaking change?: NO