From 99f640133637d7ca9e8056b2d4e1fcb08eb48eed Mon Sep 17 00:00:00 2001 From: Andrew Sprouse Date: Fri, 14 Jun 2019 01:21:23 -0400 Subject: [PATCH] Serialized compile to address #299 --- lib/Configuration.js | 9 ++++- lib/Configuration.test.js | 14 ++++--- lib/compile.js | 77 ++++++++++++++++++++++----------------- lib/validate.js | 3 +- tests/compile.test.js | 58 ++++++++++++++++++++++------- tests/webpack.mock.js | 3 ++ 6 files changed, 110 insertions(+), 54 deletions(-) diff --git a/lib/Configuration.js b/lib/Configuration.js index 705385c331..38c85d6f7f 100644 --- a/lib/Configuration.js +++ b/lib/Configuration.js @@ -14,7 +14,8 @@ const DefaultConfig = { packager: 'npm', packagerOptions: {}, keepOutputDirectory: false, - config: null + config: null, + serializedCompile: false }; class Configuration { @@ -75,9 +76,13 @@ class Configuration { return this._config.keepOutputDirectory; } + get serializedCompile() { + return this._config.serializedCompile; + } + toJSON() { return _.omitBy(this._config, _.isNil); } } -module.exports = Configuration; \ No newline at end of file +module.exports = Configuration; diff --git a/lib/Configuration.test.js b/lib/Configuration.test.js index 6d61bdbac9..4324b4bde7 100644 --- a/lib/Configuration.test.js +++ b/lib/Configuration.test.js @@ -19,7 +19,8 @@ describe('Configuration', () => { packager: 'npm', packagerOptions: {}, keepOutputDirectory: false, - config: null + config: null, + serializedCompile: false }; }); @@ -56,7 +57,7 @@ describe('Configuration', () => { }); it('should add defaults', () => { - const testCustom = { + const testCustom = { webpackIncludeModules: { forceInclude: ['mod1'] }, webpack: 'myWebpackFile.js' }; @@ -68,7 +69,8 @@ describe('Configuration', () => { packager: 'npm', packagerOptions: {}, keepOutputDirectory: false, - config: null + config: null, + serializedCompile: false }); }); }); @@ -88,7 +90,8 @@ describe('Configuration', () => { packager: 'npm', packagerOptions: {}, keepOutputDirectory: false, - config: null + config: null, + serializedCompile: false }); }); @@ -107,7 +110,8 @@ describe('Configuration', () => { packager: 'npm', packagerOptions: {}, keepOutputDirectory: false, - config: null + config: null, + serializedCompile: false }); }); }); diff --git a/lib/compile.js b/lib/compile.js index cc0a56483d..683242f7ab 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -5,46 +5,57 @@ const BbPromise = require('bluebird'); const webpack = require('webpack'); const tty = require('tty'); -module.exports = { - compile() { - this.serverless.cli.log('Bundling with Webpack...'); - - const compiler = webpack(this.webpackConfig); - - return BbPromise - .fromCallback(cb => compiler.run(cb)) - .then(stats => { +const defaultStatsConfig = { + colors: tty.isatty(process.stdout.fd), + hash: false, + version: false, + chunks: false, + children: false +}; - if (!this.multiCompile) { - stats = { stats: [stats] }; +function ensureArray(obj) { + return _.isArray(obj) ? obj : [obj]; +} + +function getStatsLogger(statsConfig, consoleLog) { + return stats => consoleLog(stats.toString(statsConfig || defaultStatsConfig)); +} + +function webpackCompile(config, logStats) { + return BbPromise + .fromCallback(cb => webpack(config).run(cb)) + .then(stats => { + // ensure stats in any array in the case of multiCompile + stats = stats.stats ? stats.stats : [stats]; + + _.forEach(stats, compileStats => { + logStats(compileStats); + if (compileStats.hasErrors()) { + throw new Error('Webpack compilation error, see stats above'); } + }); - const compileOutputPaths = []; - const consoleStats = this.webpackConfig.stats || _.get(this, 'webpackConfig[0].stats') || { - colors: tty.isatty(process.stdout.fd), - hash: false, - version: false, - chunks: false, - children: false - }; - - _.forEach(stats.stats, compileStats => { - const statsOutput = compileStats.toString(consoleStats); - if (statsOutput) { - this.serverless.cli.consoleLog(statsOutput); - } + return stats; + }); +} - if (compileStats.compilation.errors.length) { - throw new Error('Webpack compilation error, see above'); - } +function webpackCompileSerial(configs, logStats) { + return BbPromise + .mapSeries(configs, config => webpackCompile(config, logStats)) + .then(stats => _.flatten(stats)); +} - compileOutputPaths.push(compileStats.compilation.compiler.outputPath); - }); +module.exports = { + compile() { + this.serverless.cli.log('Bundling with Webpack...'); - this.compileOutputPaths = compileOutputPaths; - this.compileStats = stats; + const configs = ensureArray(this.webpackConfig); + const logStats = getStatsLogger(configs[0].stats, this.serverless.cli.consoleLog); + return (this.serializedCompile ? webpackCompileSerial : webpackCompile)(configs, logStats) + .then(stats => { + this.compileStats = { stats }; return BbPromise.resolve(); }); - }, + } }; diff --git a/lib/validate.js b/lib/validate.js index a6ebc580fe..153eccc3a3 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -164,8 +164,9 @@ module.exports = { // In case of individual packaging we have to create a separate config for each function if (_.has(this.serverless, 'service.package') && this.serverless.service.package.individually) { - this.options.verbose && this.serverless.cli.log('Using multi-compile (individual packaging)'); this.multiCompile = true; + this.serializedCompile = this.configuration.serializedCompile; + this.options.verbose && this.serverless.cli.log(`Using ${this.serializedCompile ? 'serialized' : 'multi'}-compile (individual packaging)`); if (this.webpackConfig.entry && !_.isEqual(this.webpackConfig.entry, entries)) { return BbPromise.reject(new this.serverless.classes diff --git a/tests/compile.test.js b/tests/compile.test.js index ec8a88ce9d..58f6820295 100644 --- a/tests/compile.test.js +++ b/tests/compile.test.js @@ -64,7 +64,7 @@ describe('compile', () => { module.webpackConfig = testWebpackConfig; return expect(module.compile()).to.be.fulfilled .then(() => { - expect(webpackMock).to.have.been.calledWith(testWebpackConfig); + expect(webpackMock).to.have.been.calledWith([testWebpackConfig]); expect(webpackMock.compilerMock.run).to.have.been.calledOnce; return null; }); @@ -78,16 +78,19 @@ describe('compile', () => { }); it('should work with multi compile', () => { - const testWebpackConfig = 'testconfig'; - const multiStats = [{ - compilation: { - errors: [], - compiler: { - outputPath: 'statsMock-outputPath', + const testWebpackConfig = ['testconfig']; + const multiStats = { + stats: [{ + compilation: { + errors: [], + compiler: { + outputPath: 'statsMock-outputPath', + }, }, - }, - toString: sandbox.stub().returns('testStats'), - }]; + toString: sandbox.stub().returns('testStats'), + hasErrors: _.constant(false) + }] + }; module.webpackConfig = testWebpackConfig; module.multiCompile = true; webpackMock.compilerMock.run.reset(); @@ -100,6 +103,33 @@ describe('compile', () => { }); }); + it('should work with serialized compile', () => { + const testWebpackConfig = ['testconfig']; + const multiStats = { + stats: [{ + compilation: { + errors: [], + compiler: { + outputPath: 'statsMock-outputPath', + }, + }, + toString: sandbox.stub().returns('testStats'), + hasErrors: _.constant(false) + }] + }; + module.webpackConfig = testWebpackConfig; + module.multiCompile = true; + module.serializedCompile = true; + webpackMock.compilerMock.run.reset(); + webpackMock.compilerMock.run.yields(null, multiStats); + return expect(module.compile()).to.be.fulfilled + .then(() => { + expect(webpackMock).to.have.been.calledWith(testWebpackConfig); + expect(webpackMock.compilerMock.run).to.have.been.calledOnce; + return null; + }); + }); + it('should use correct stats option', () => { const testWebpackConfig = { stats: 'minimal' @@ -111,7 +141,8 @@ describe('compile', () => { outputPath: 'statsMock-outputPath' } }, - toString: sandbox.stub().returns('testStats') + toString: sandbox.stub().returns('testStats'), + hasErrors: _.constant(false) }; module.webpackConfig = testWebpackConfig; @@ -119,14 +150,15 @@ describe('compile', () => { webpackMock.compilerMock.run.yields(null, mockStats); return (expect(module.compile()).to.be.fulfilled) .then(() => { - expect(webpackMock).to.have.been.calledWith(testWebpackConfig); + expect(webpackMock).to.have.been.calledWith([testWebpackConfig]); expect(mockStats.toString.firstCall.args).to.eql([testWebpackConfig.stats]); module.webpackConfig = [testWebpackConfig]; return (expect(module.compile()).to.be.fulfilled); }) .then(() => { expect(webpackMock).to.have.been.calledWith([testWebpackConfig]); - expect(mockStats.toString.args).to.eql([[testWebpackConfig.stats], [testWebpackConfig.stats]]); + expect(mockStats.toString.args).to.eql([ [testWebpackConfig.stats], [testWebpackConfig.stats] ]); + return null; }); }); }); diff --git a/tests/webpack.mock.js b/tests/webpack.mock.js index 9f8f124d60..5bf7194c2e 100644 --- a/tests/webpack.mock.js +++ b/tests/webpack.mock.js @@ -10,6 +10,9 @@ const StatsMock = () => ({ }, }, toString: sinon.stub().returns('testStats'), + hasErrors() { + return Boolean(this.compilation.errors.length); + }, });