Skip to content

Commit 7fab44e

Browse files
authored
Merge branch 'dougmoscrop:master' into master
2 parents 5642cae + 0c0fc72 commit 7fab44e

File tree

7 files changed

+91
-24
lines changed

7 files changed

+91
-24
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
This is a Serverless plugin that should make your deployed functions smaller. It does this by excluding `node_modules` then individually adds each module file that your handler depends on.
44

5+
5.1.0 introduces support for detecting dependencies of files included via package.patterns
6+
7+
This is useful if you are dynamically importing a directory.
8+
59
As of 5.0.0 this plugin uses the `package.patterns` property. `always` is no longer supported as it should be possible with just package.patterns
610

711
> Note: This plugin no longer excludes the `aws-sdk` to stay in line with AWS best practices (bring your own SDK)
@@ -57,7 +61,7 @@ For even smaller function packages, you can also set:
5761
package:
5862
individually: true
5963
```
60-
But be warned: Smaller individual functions can still mean a larger overall deployment. (10 functions that are 3 MB each is more net data tranfer and storage than 1 function that is 6 MB)
64+
But be warned: Smaller individual functions can still mean a larger overall deployment. (10 functions that are 3 MB each is more net data transfer and storage than 1 function that is 6 MB)
6165
6266
## Dependency caching (Experimental)
6367

__tests__/get-dependency-list.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ const serverless = {
1212
}
1313
};
1414

15+
function convertSlashes(paths) {
16+
return paths.map(name => name.replaceAll('\\', '/'));
17+
}
18+
1519
test('includes a deep dependency', (t) => {
1620
const fileName = path.join(__dirname, 'fixtures', 'thing.js');
1721

@@ -66,7 +70,7 @@ test('handles requiring dependency file in scoped package', (t) => {
6670
test('should handle requires with same relative path but different absolute path', (t) => {
6771
const fileName = path.join(__dirname, 'fixtures', 'same-relative-require.js');
6872

69-
const list = getDependencyList(fileName, serverless);
73+
const list = convertSlashes(getDependencyList(fileName, serverless));
7074

7175
t.true(list.some(item => item.indexOf(`bar/baz.js`) !== -1));
7276
t.true(list.some(item => item.indexOf(`foo/baz.js`) !== -1));
@@ -125,8 +129,9 @@ test('caches lookups', (t) => {
125129
const fileName2 = path.join(__dirname, 'fixtures', 'redundancies-2.js');
126130

127131
const cache = new Set();
128-
const list1 = getDependencyList(fileName, serverless, cache);
129-
const list2 = getDependencyList(fileName2, serverless, cache);
132+
const checkedFiles = new Set();
133+
const list1 = getDependencyList(fileName, serverless, checkedFiles, cache);
134+
const list2 = getDependencyList(fileName2, serverless, checkedFiles, cache);
130135

131136
t.true(list1.some(item => item.endsWith('local/named/index.js')));
132137
t.true(list1.some(item => item.endsWith('symlinked.js')));

__tests__/include-dependencies.js

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,19 @@ const sinon = require('sinon');
88

99
const IncludeDependencies = require('../include-dependencies.js');
1010

11-
function createTestInstance(serverless, options) {
11+
function convertSlashes(paths) {
12+
return paths.map(name => name.replaceAll('\\', '/'));
13+
}
14+
15+
function createTestInstance(serverless, options, functions = {a: {}, b: {}}) {
1216
return new IncludeDependencies(
1317
_.merge({
1418
version: '2.32.0',
1519
config: {
1620
servicePath: path.join(__dirname, 'fixtures')
1721
},
1822
service: {
19-
functions: {
20-
a: {},
21-
b: {}
22-
}
23+
functions
2324
}
2425
}, serverless),
2526
_.merge({}, options)
@@ -68,6 +69,32 @@ test('createDeploymentArtifacts should call processFunction with function name',
6869
t.deepEqual(spy.calledWith('b'), true);
6970
});
7071

72+
test('createDeploymentArtifacts should call getDependencies for patterns files', t => {
73+
const fileName = path.join(__dirname, 'fixtures', 'thing.js').replaceAll('\\', '/');
74+
const instance = createTestInstance({
75+
service: {
76+
provider: {
77+
runtime: 'nodejs18.x',
78+
},
79+
package: {
80+
patterns: [fileName]
81+
}
82+
}
83+
});
84+
85+
const processFunctionSpy = sinon.stub(instance, 'processFunction');
86+
const dependencyListSpy = sinon.stub(instance, 'getDependencyList');
87+
88+
instance.createDeploymentArtifacts();
89+
90+
t.deepEqual(processFunctionSpy.calledTwice, true);
91+
t.deepEqual(processFunctionSpy.calledWith('a'), true);
92+
t.deepEqual(processFunctionSpy.calledWith('b'), true);
93+
94+
t.deepEqual(dependencyListSpy.callCount, 1);
95+
t.deepEqual(dependencyListSpy.calledWith(fileName), true);
96+
});
97+
7198
test('processFunction should exclude node_modules when no package defined', t => {
7299
const instance = createTestInstance();
73100

@@ -79,6 +106,7 @@ test('processFunction should exclude node_modules when no package defined', t =>
79106
t.deepEqual(instance.serverless.service.package.patterns, ['!node_modules/**']);
80107
});
81108

109+
82110
test('processFunction should add node_modules ignore to package patterns', t => {
83111
const instance = createTestInstance({
84112
service: {
@@ -116,7 +144,7 @@ test('processFunction should add to package include', t => {
116144

117145
instance.processFunction('a');
118146

119-
t.deepEqual(instance.serverless.service.package.patterns, [
147+
t.deepEqual(convertSlashes(instance.serverless.service.package.patterns), [
120148
'!node_modules/**',
121149
'.something',
122150
'node_modules/brightspace-auth-validation/index.js',
@@ -156,7 +184,7 @@ test('processFunction should include individually', t => {
156184
'!node_modules/**',
157185
'.something',
158186
]);
159-
t.deepEqual(instance.serverless.service.functions.a.package.patterns, [
187+
t.deepEqual(convertSlashes(instance.serverless.service.functions.a.package.patterns), [
160188
'.something-else',
161189
'node_modules/brightspace-auth-validation/index.js',
162190
'node_modules/brightspace-auth-validation/node_modules/jws/index.js'
@@ -220,9 +248,9 @@ test('getDependencies should handle exclude of a file within a dependency', t =>
220248
test('getDependencies should ignore excludes that do not start with node_modules', t => {
221249
const instance = createTestInstance();
222250
const file = path.join(__dirname, 'fixtures', 'thing.js');
223-
const dependencies = instance.getDependencies(file, [
251+
const dependencies = convertSlashes(instance.getDependencies(file, [
224252
'**/LICENSE'
225-
]);
253+
]));
226254

227255
t.true(Array.isArray(dependencies));
228256
t.true(dependencies.length > 0);
@@ -322,6 +350,8 @@ test('disables caching by default', t => {
322350
const instance = createTestInstance();
323351
const file = path.join(__dirname, 'fixtures', 'thing.js');
324352
const list1 = instance.getDependencies(file, []);
353+
instance.checkedFiles.clear(); // clear would be called when through createDeploymentArtifacts
354+
325355
const list2 = instance.getDependencies(file, []);
326356
t.deepEqual(list1, list2);
327357
});
@@ -332,6 +362,7 @@ test('enables caching', t => {
332362
});
333363
const file = path.join(__dirname, 'fixtures', 'thing.js');
334364
const list1 = instance.getDependencies(file, []);
365+
instance.checkedFiles.clear();
335366
const list2 = instance.getDependencies(file, []);
336367
t.true(list2.length < list1.length);
337368
});

get-dependency-list.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ function ignoreMissing(dependency, optional, peerDependenciesMeta) {
1313
|| peerDependenciesMeta && dependency in peerDependenciesMeta && peerDependenciesMeta[dependency].optional;
1414
}
1515

16-
module.exports = function(filename, serverless, cache) {
16+
module.exports = function(filename, serverless, checkedFiles, cache) {
1717
const servicePath = serverless.config.servicePath;
1818
const modulePaths = new Set();
1919
const filePaths = new Set();
2020
const modulesToProcess = [];
2121
const localFilesToProcess = [filename];
22+
if (!checkedFiles) checkedFiles = new Set();
2223

2324
function handle(name, basedir, optionalDependencies, peerDependenciesMeta) {
2425
const moduleName = requirePackageName(name.replace(/\\/, '/'));
@@ -38,7 +39,7 @@ module.exports = function(filename, serverless, cache) {
3839
if (cache) {
3940
cache.add(cacheKey);
4041
}
41-
42+
4243
} else {
4344
// TODO: should we warn here?
4445
}
@@ -69,11 +70,12 @@ module.exports = function(filename, serverless, cache) {
6970
while (localFilesToProcess.length) {
7071
const currentLocalFile = localFilesToProcess.pop();
7172

72-
if (filePaths.has(currentLocalFile)) {
73+
if (filePaths.has(currentLocalFile) || checkedFiles.has(currentLocalFile)) {
7374
continue;
7475
}
7576

7677
filePaths.add(currentLocalFile);
78+
checkedFiles.add(currentLocalFile);
7779

7880
precinct.paperwork(currentLocalFile, { includeCore: false }).forEach(dependency => {
7981
if (dependency.indexOf('.') === 0) {
@@ -122,5 +124,5 @@ module.exports = function(filename, serverless, cache) {
122124
});
123125
});
124126

125-
return Array.from(filePaths);
127+
return Array.from(filePaths).map(file => file.replace(/\\/, '/'));
126128
};

include-dependencies.js

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const path = require('path');
44

55
const semver = require('semver');
66
const micromatch = require('micromatch');
7+
const glob = require('glob');
78

89
const getDependencyList = require('./get-dependency-list');
910

@@ -32,6 +33,7 @@ module.exports = class IncludeDependencies {
3233
this.serverless = serverless;
3334
this.options = options;
3435
this.cache = new Set();
36+
this.checkedFiles = new Set();
3537

3638
const service = this.serverless.service;
3739
this.individually = service.package && service.package.individually;
@@ -53,6 +55,23 @@ module.exports = class IncludeDependencies {
5355
for (const functionName in functions) {
5456
this.processFunction(functionName);
5557
}
58+
59+
const files = [...new Set(this.getPatterns().filter(pattern => !pattern.startsWith('!') && !pattern.includes('node_modules'))
60+
.map(modulePath => glob.sync(modulePath, {
61+
nodir: true,
62+
ignore: path.join(modulePath, 'node_modules', '**'),
63+
absolute: true
64+
})
65+
).flat().map(file => file.replaceAll('\\', '/')))];
66+
67+
files.forEach(fileName => {
68+
if (!this.checkedFiles.has(fileName)) {
69+
const dependencies = this.getDependencies(fileName, service.package.patterns);
70+
service.package.patterns = union(service.package.patterns, dependencies);
71+
}
72+
});
73+
74+
this.checkedFiles.clear();
5675
}
5776

5877
processFunction(functionName) {
@@ -69,6 +88,11 @@ module.exports = class IncludeDependencies {
6988
}
7089
}
7190

91+
getPatterns() {
92+
const service = this.serverless.service;
93+
return (service.package && service.package.patterns) || [];
94+
}
95+
7296
getPluginOptions() {
7397
const service = this.serverless.service;
7498
return (service.custom && service.custom.includeDependencies) || {};
@@ -78,7 +102,7 @@ module.exports = class IncludeDependencies {
78102
const { service } = this.serverless;
79103

80104
functionObject.package = functionObject.package || {};
81-
105+
82106
const fileName = this.getHandlerFilename(functionObject.handler);
83107
const dependencies = this.getDependencies(fileName, service.package.patterns);
84108

@@ -103,7 +127,7 @@ module.exports = class IncludeDependencies {
103127

104128
getDependencies(fileName, patterns) {
105129
const servicePath = this.serverless.config.servicePath;
106-
const dependencies = this.getDependencyList(fileName);
130+
const dependencies = this.getDependencyList(fileName) || [];
107131
const relativeDependencies = dependencies.map(p => path.relative(servicePath, p));
108132

109133
const exclusions = patterns.filter(p => {
@@ -121,9 +145,9 @@ module.exports = class IncludeDependencies {
121145
if (!this.individually) {
122146
const options = this.getPluginOptions();
123147
if (options && options.enableCaching) {
124-
return getDependencyList(fileName, this.serverless, this.cache);
148+
return getDependencyList(fileName, this.serverless, this.checkedFiles, this.cache);
125149
}
126150
}
127-
return getDependencyList(fileName, this.serverless);
151+
return getDependencyList(fileName, this.serverless, this.checkedFiles);
128152
}
129153
};

package-lock.json

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "serverless-plugin-include-dependencies",
3-
"version": "5.0.0",
3+
"version": "5.1.0",
44
"engines": {
55
"node": ">=4.0"
66
},

0 commit comments

Comments
 (0)