Skip to content

Commit 60680f4

Browse files
authored
Added forceInclude setting to add dynamically required modules explicitly (#226)
* Added forceInclude setting to add dynamically required modules explicitly * Added unit tests for forced includes * Document forceInclude in README
1 parent 39b9628 commit 60680f4

File tree

4 files changed

+132
-3
lines changed

4 files changed

+132
-3
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,24 @@ custom:
192192
```
193193
> Note that only relative path is supported at the moment.
194194

195+
196+
Sometimes it might happen that you use dynamic requires in your code, i.e. you
197+
require modules that are only known at runtime. Webpack is not able to detect
198+
such externals and the compiled package will miss the needed dependencies.
199+
In such cases you can force the plugin to include certain modules by setting
200+
them in the `forceInclude` array property. However the module must appear in
201+
your service's production dependencies in `package.json`.
202+
203+
```yaml
204+
# serverless.yml
205+
custom:
206+
webpackIncludeModules:
207+
forceInclude:
208+
- module1
209+
- module2
210+
```
211+
212+
195213
You can find an example setups in the [`examples`][link-examples] folder.
196214

197215
#### Service level packaging

lib/packExternalModules.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ module.exports = {
110110
return BbPromise.resolve(stats);
111111
}
112112

113+
const packageForceIncludes = _.get(includes, 'forceInclude', []);
113114
const packagePath = includes.packagePath || './package.json';
114115
const packageJsonPath = path.join(process.cwd(), packagePath);
115116

@@ -158,7 +159,10 @@ module.exports = {
158159

159160
// (1) Generate dependency composition
160161
const compositeModules = _.uniq(_.flatMap(stats.stats, compileStats => {
161-
const externalModules = getExternalModules.call(this, compileStats);
162+
const externalModules = _.concat(
163+
getExternalModules.call(this, compileStats),
164+
_.map(packageForceIncludes, whitelistedPackage => ({ external: whitelistedPackage }))
165+
);
162166
return getProdModules.call(this, externalModules, packagePath, dependencyGraph);
163167
}));
164168

@@ -196,7 +200,11 @@ module.exports = {
196200
const modulePackage = {
197201
dependencies: {}
198202
};
199-
const prodModules = getProdModules.call(this, getExternalModules.call(this, compileStats), packagePath, dependencyGraph);
203+
const prodModules = getProdModules.call(this,
204+
_.concat(
205+
getExternalModules.call(this, compileStats),
206+
_.map(packageForceIncludes, whitelistedPackage => ({ external: whitelistedPackage }))
207+
), packagePath, dependencyGraph);
200208
_.forEach(prodModules, prodModule => {
201209
const splitModule = _.split(prodModule, '@');
202210
// If we have a scoped module we have to re-add the @

tests/mocks/package.mock.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"npm-programmatic": "0.0.5",
99
"uuid": "^5.4.1",
1010
"ts-node": "^3.2.0",
11-
"@scoped/vendor": "1.0.0"
11+
"@scoped/vendor": "1.0.0",
12+
"pg": "^4.3.5"
1213
},
1314
"devDependencies": {
1415
"babel-eslint": "^7.2.3",

tests/packExternalModules.test.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,5 +349,107 @@ describe('packExternalModules', () => {
349349
expect(childProcessMock.exec).to.have.been.calledOnce,
350350
]));
351351
});
352+
353+
it('should install external modules when forced', () => {
354+
const expectedPackageJSON = {
355+
dependencies: {
356+
'@scoped/vendor': '1.0.0',
357+
uuid: '^5.4.1',
358+
bluebird: '^3.4.0',
359+
pg: '^4.3.5'
360+
}
361+
};
362+
serverless.service.custom = {
363+
webpackIncludeModules: {
364+
forceInclude: ['pg']
365+
}
366+
};
367+
module.webpackOutputPath = 'outputPath';
368+
npmMock.install.returns(BbPromise.resolve());
369+
fsExtraMock.copy.yields();
370+
childProcessMock.exec.onFirstCall().yields(null, '{}', '');
371+
childProcessMock.exec.onSecondCall().yields();
372+
return expect(module.packExternalModules(stats)).to.be.fulfilled
373+
.then(() => BbPromise.all([
374+
// npm install should have been called with all externals from the package mock
375+
expect(npmMock.install).to.have.been.calledOnce,
376+
expect(npmMock.install).to.have.been.calledWithExactly([
377+
'@scoped/vendor@1.0.0',
378+
'uuid@^5.4.1',
379+
'bluebird@^3.4.0',
380+
'pg@^4.3.5'
381+
],
382+
{
383+
cwd: path.join('outputPath', 'dependencies'),
384+
maxBuffer: 204800,
385+
save: true
386+
}),
387+
// The module package JSON and the composite one should have been stored
388+
expect(writeFileSyncStub).to.have.been.calledTwice,
389+
expect(writeFileSyncStub.firstCall.args[1]).to.equal('{}'),
390+
expect(writeFileSyncStub.secondCall.args[1]).to.equal(JSON.stringify(expectedPackageJSON, null, 2)),
391+
// The modules should have been copied
392+
expect(fsExtraMock.copy).to.have.been.calledOnce,
393+
// npm ls and npm prune should have been called
394+
expect(childProcessMock.exec).to.have.been.calledTwice,
395+
expect(childProcessMock.exec.firstCall).to.have.been.calledWith(
396+
'npm ls -prod -json -depth=1'
397+
),
398+
expect(childProcessMock.exec.secondCall).to.have.been.calledWith(
399+
'npm prune'
400+
)
401+
]));
402+
});
403+
404+
it('should add forced external modules without version when not in production dependencies', () => {
405+
const expectedPackageJSON = {
406+
dependencies: {
407+
'@scoped/vendor': '1.0.0',
408+
uuid: '^5.4.1',
409+
bluebird: '^3.4.0',
410+
'not-in-prod-deps': ''
411+
}
412+
};
413+
serverless.service.custom = {
414+
webpackIncludeModules: {
415+
forceInclude: ['not-in-prod-deps']
416+
}
417+
};
418+
module.webpackOutputPath = 'outputPath';
419+
npmMock.install.returns(BbPromise.resolve());
420+
fsExtraMock.copy.yields();
421+
childProcessMock.exec.onFirstCall().yields(null, '{}', '');
422+
childProcessMock.exec.onSecondCall().yields();
423+
return expect(module.packExternalModules(stats)).to.be.fulfilled
424+
.then(() => BbPromise.all([
425+
// npm install should have been called with all externals from the package mock
426+
expect(npmMock.install).to.have.been.calledOnce,
427+
expect(npmMock.install).to.have.been.calledWithExactly([
428+
'@scoped/vendor@1.0.0',
429+
'uuid@^5.4.1',
430+
'bluebird@^3.4.0',
431+
'not-in-prod-deps'
432+
],
433+
{
434+
cwd: path.join('outputPath', 'dependencies'),
435+
maxBuffer: 204800,
436+
save: true
437+
}),
438+
// The module package JSON and the composite one should have been stored
439+
expect(writeFileSyncStub).to.have.been.calledTwice,
440+
expect(writeFileSyncStub.firstCall.args[1]).to.equal('{}'),
441+
expect(writeFileSyncStub.secondCall.args[1]).to.equal(JSON.stringify(expectedPackageJSON, null, 2)),
442+
// The modules should have been copied
443+
expect(fsExtraMock.copy).to.have.been.calledOnce,
444+
// npm ls and npm prune should have been called
445+
expect(childProcessMock.exec).to.have.been.calledTwice,
446+
expect(childProcessMock.exec.firstCall).to.have.been.calledWith(
447+
'npm ls -prod -json -depth=1'
448+
),
449+
expect(childProcessMock.exec.secondCall).to.have.been.calledWith(
450+
'npm prune'
451+
)
452+
]));
453+
});
352454
});
353455
});

0 commit comments

Comments
 (0)