diff --git a/lib/compile.js b/lib/compile.js index 20847f722..9743f9522 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -16,6 +16,10 @@ function getStatsLogger(statsConfig, consoleLog, { log, ServerlessError }) { }; } +function isIndividialPackaging() { + return _.get(this.serverless, 'service.package.individually'); +} + function getExternalModuleName(module) { const pathArray = /^external .*"(.*?)"$/.exec(module.identifier()); if (!pathArray) { @@ -120,11 +124,21 @@ function webpackConcurrentCompile(configs, logStats, concurrency, ServerlessErro const errors = []; return BbPromise.map( configs, - config => - webpackCompile(config, logStats).catch(error => { + config => { + if (isIndividialPackaging.call(this) && _.get(config, 'cache.type') === 'filesystem') { + const clonedConfig = _.clone(config); + const entryFunc = _.find(this.entryFunctions, ['entry.key', _.keys(config.entry)[0]]); + clonedConfig.cache.name = entryFunc.func.name; + return webpackCompile(clonedConfig, logStats, ServerlessError).catch(error => { + errors.push(error); + return error.stats; + }); + } + return webpackCompile(config, logStats, ServerlessError).catch(error => { errors.push(error); return error.stats; - }), + }); + }, { concurrency } ).then(stats => { if (errors.length) { diff --git a/tests/compile.test.js b/tests/compile.test.js index e308a0a69..dab93c989 100644 --- a/tests/compile.test.js +++ b/tests/compile.test.js @@ -98,6 +98,70 @@ describe('compile', () => { }); }); + it('should work with individual compile and webpack filesystem cache', () => { + const testWebpackConfig = { + cache: { + type: 'filesystem' + }, + // Below entry is inserted during validate() which happens before compile() + // Thus the assumed value + entry: { + 'function-name/handler': './function-name/handler.js' + } + }; + const multiStats = { + stats: [ + { + compilation: { + errors: [], + compiler: { + outputPath: 'statsMock-outputPath' + }, + modules: [] + }, + toString: jest.fn().mockReturnValue('testStats'), + hasErrors: _.constant(false) + } + ] + }; + module.webpackConfig = testWebpackConfig; + module.configuration = { concurrency: 1 }; + module.serverless.service.package = { + individually: true + }; + module.entryFunctions = [ + { + handlerFile: 'function-name/handler', + funcName: 'function-name', + func: { + handler: 'function-name/handler.handler', + name: 'service-stage-function-name' + }, + entry: { + key: 'function-name/handler', + value: './function-name/handler.js' + } + } + ]; + webpackMock.compilerMock.run.mockClear(); + webpackMock.compilerMock.run.mockImplementation(cb => cb(null, multiStats)); + return expect(module.compile()) + .resolves.toBeUndefined() + .then(() => { + expect(webpackMock).toHaveBeenCalledWith({ + cache: { + type: 'filesystem', + name: 'service-stage-function-name' + }, + entry: { + 'function-name/handler': './function-name/handler.js' + } + }); + expect(webpackMock.compilerMock.run).toHaveBeenCalledTimes(1); + return null; + }); + }); + it('should work with concurrent compile', () => { const testWebpackConfig = ['testconfig', 'testconfig2']; const multiStats = { @@ -129,6 +193,103 @@ describe('compile', () => { }); }); + it('should concurrently work with individual compile and webpack filesystem cache', () => { + const testWebpackConfig = [ + { + cache: { + type: 'filesystem' + }, + // Below entry is inserted during validate() which happens before compile() + // Thus the assumed value + entry: { + 'function-name-1/handler': './function-name-1/handler.js' + } + }, + { + cache: { + type: 'filesystem' + }, + // Below entry is inserted during validate() which happens before compile() + // Thus the assumed value + entry: { + 'function-name-2/handler': './function-name-2/handler.js' + } + } + ]; + const multiStats = { + stats: [ + { + compilation: { + errors: [], + compiler: { + outputPath: 'statsMock-outputPath' + }, + modules: [] + }, + toString: jest.fn().mockReturnValue('testStats'), + hasErrors: _.constant(false) + } + ] + }; + module.webpackConfig = testWebpackConfig; + module.configuration = { concurrency: 2 }; + module.serverless.service.package = { + individually: true + }; + module.entryFunctions = [ + { + handlerFile: 'function-name-1/handler', + funcName: 'function-name-1', + func: { + handler: 'function-name-1/handler.handler', + name: 'service-stage-function-name-1' + }, + entry: { + key: 'function-name-1/handler', + value: './function-name-1/handler.js' + } + }, + { + handlerFile: 'function-name-2/handler', + funcName: 'function-name-2', + func: { + handler: 'function-name-2/handler.handler', + name: 'service-stage-function-name-2' + }, + entry: { + key: 'function-name-2/handler', + value: './function-name-2/handler.js' + } + } + ]; + webpackMock.compilerMock.run.mockClear(); + webpackMock.compilerMock.run.mockImplementation(cb => cb(null, multiStats)); + return expect(module.compile()) + .resolves.toBeUndefined() + .then(() => { + expect(webpackMock).toHaveBeenCalledWith({ + cache: { + type: 'filesystem', + name: 'service-stage-function-name-1' + }, + entry: { + 'function-name-1/handler': './function-name-1/handler.js' + } + }); + expect(webpackMock).toHaveBeenCalledWith({ + cache: { + type: 'filesystem', + name: 'service-stage-function-name-2' + }, + entry: { + 'function-name-2/handler': './function-name-2/handler.js' + } + }); + expect(webpackMock.compilerMock.run).toHaveBeenCalledTimes(2); + return null; + }); + }); + it('should use correct stats option', () => { const testWebpackConfig = { stats: 'minimal'