Skip to content

Commit e3edab0

Browse files
jamesmbournemiguel-a-calles-mba
authored andcommitted
Skip compile & packaging if --no-build is set (serverless-heaven#560)
* Add copyExistingArtifacts to packageModules * Refactor packageModules * Set service path * Generate artifact name from service * Output artifacts to .webpack before copying * Set artifact name for service packaging * Skip webpack:compile if --no-build is set * Add webpack:package:copyExistingArtifacts hook * Make packageModules & packExternalModules no-op if skipCompile is set * Refactor packageModules * Remove artifact location setting from packageModules * Update cleanup to check this.keepOutputDirectory * Fix path join on Windows Co-authored-by: Miguel A. Calles MBA <44813512+miguel-a-calles-mba@users.noreply.github.com>
1 parent 3369e9f commit e3edab0

11 files changed

+405
-79
lines changed

index.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class ServerlessWebpack {
8181
},
8282
package: {
8383
type: 'entrypoint',
84-
lifecycleEvents: [ 'packExternalModules', 'packageModules' ]
84+
lifecycleEvents: [ 'packExternalModules', 'packageModules', 'copyExistingArtifacts' ]
8585
}
8686
}
8787
}
@@ -91,7 +91,7 @@ class ServerlessWebpack {
9191
'before:package:createDeploymentArtifacts': () =>
9292
BbPromise.bind(this)
9393
.then(() => this.serverless.pluginManager.spawn('webpack:validate'))
94-
.then(() => this.serverless.pluginManager.spawn('webpack:compile'))
94+
.then(() => (this.skipCompile ? BbPromise.resolve() : this.serverless.pluginManager.spawn('webpack:compile')))
9595
.then(() => this.serverless.pluginManager.spawn('webpack:package')),
9696

9797
'after:package:createDeploymentArtifacts': () => BbPromise.bind(this).then(this.cleanup),
@@ -106,10 +106,6 @@ class ServerlessWebpack {
106106
BbPromise.bind(this)
107107
.then(() => {
108108
lib.webpack.isLocal = true;
109-
// --no-build override
110-
if (this.options.build === false) {
111-
this.skipCompile = true;
112-
}
113109

114110
return this.serverless.pluginManager.spawn('webpack:validate');
115111
})
@@ -159,6 +155,8 @@ class ServerlessWebpack {
159155

160156
'webpack:package:packageModules': () => BbPromise.bind(this).then(this.packageModules),
161157

158+
'webpack:package:copyExistingArtifacts': () => BbPromise.bind(this).then(this.copyExistingArtifacts),
159+
162160
'before:offline:start': () =>
163161
BbPromise.bind(this)
164162
.tap(() => {

index.test.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ describe('ServerlessWebpack', () => {
133133
sandbox.stub(slsw, 'watch').returns(BbPromise.resolve());
134134
sandbox.stub(slsw, 'wpwatch').returns(BbPromise.resolve());
135135
sandbox.stub(slsw, 'packExternalModules').returns(BbPromise.resolve());
136+
sandbox.stub(slsw, 'copyExistingArtifacts').returns(BbPromise.resolve());
136137
sandbox.stub(slsw, 'prepareRun').returns(BbPromise.resolve());
137138
sandbox.stub(slsw, 'watchRun').returns(BbPromise.resolve());
138139
sandbox.stub(slsw, 'validate').returns(BbPromise.resolve());
@@ -145,6 +146,7 @@ describe('ServerlessWebpack', () => {
145146

146147
beforeEach(() => {
147148
ServerlessWebpack.lib.webpack.isLocal = false;
149+
slsw.skipCompile = false;
148150
});
149151

150152
after(() => {
@@ -169,6 +171,20 @@ describe('ServerlessWebpack', () => {
169171
return null;
170172
});
171173
});
174+
175+
it('should skip compile if requested', () => {
176+
slsw.skipCompile = true;
177+
return expect(slsw.hooks['before:package:createDeploymentArtifacts']()).to.be.fulfilled.then(() => {
178+
expect(slsw.serverless.pluginManager.spawn).to.have.been.calledTwice;
179+
expect(slsw.serverless.pluginManager.spawn.firstCall).to.have.been.calledWithExactly(
180+
'webpack:validate'
181+
);
182+
expect(slsw.serverless.pluginManager.spawn.secondCall).to.have.been.calledWithExactly(
183+
'webpack:package'
184+
);
185+
return null;
186+
});
187+
});
172188
}
173189
},
174190
{
@@ -237,7 +253,7 @@ describe('ServerlessWebpack', () => {
237253
});
238254

239255
it('should skip compile if requested', () => {
240-
slsw.options.build = false;
256+
slsw.skipCompile = true;
241257
return expect(slsw.hooks['before:invoke:local:invoke']()).to.be.fulfilled.then(() => {
242258
expect(slsw.serverless.pluginManager.spawn).to.have.been.calledOnce;
243259
expect(slsw.serverless.pluginManager.spawn).to.have.been.calledWithExactly('webpack:validate');
@@ -361,6 +377,17 @@ describe('ServerlessWebpack', () => {
361377
});
362378
}
363379
},
380+
{
381+
name: 'webpack:package:copyExistingArtifacts',
382+
test: () => {
383+
it('should call copyExistingArtifacts', () => {
384+
return expect(slsw.hooks['webpack:package:copyExistingArtifacts']()).to.be.fulfilled.then(() => {
385+
expect(slsw.copyExistingArtifacts).to.have.been.calledOnce;
386+
return null;
387+
});
388+
});
389+
}
390+
},
364391
{
365392
name: 'before:offline:start',
366393
test: () => {

lib/cleanup.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module.exports = {
77
cleanup() {
88
const webpackOutputPath = this.webpackOutputPath;
99

10-
const keepOutputDirectory = this.configuration.keepOutputDirectory;
10+
const keepOutputDirectory = this.keepOutputDirectory;
1111
if (!keepOutputDirectory) {
1212
this.options.verbose && this.serverless.cli.log(`Remove ${webpackOutputPath}`);
1313
if (this.serverless.utils.dirExistsSync(webpackOutputPath)) {

lib/packExternalModules.js

+4
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ module.exports = {
228228
* and performance.
229229
*/
230230
packExternalModules() {
231+
if (this.skipCompile) {
232+
return BbPromise.resolve();
233+
}
234+
231235
const stats = this.compileStats;
232236

233237
const includes = this.configuration.includeModules;

lib/packageModules.js

+84-32
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const semver = require('semver');
1111
function setArtifactPath(funcName, func, artifactPath) {
1212
const version = this.serverless.getVersion();
1313

14+
this.options.verbose && this.serverless.cli.log(`Setting artifact for function '${funcName}' to '${artifactPath}'`);
15+
1416
// Serverless changed the artifact path location in version 1.18
1517
if (semver.lt(version, '1.18.0')) {
1618
func.artifact = artifactPath;
@@ -26,7 +28,8 @@ function setArtifactPath(funcName, func, artifactPath) {
2628
function zip(directory, name) {
2729
const zip = archiver.create('zip');
2830
// Create artifact in temp path and move it to the package path (if any) later
29-
const artifactFilePath = path.join(this.serverless.config.servicePath, '.serverless', name);
31+
// This allows us to persist the webpackOutputPath and re-use the compiled output
32+
const artifactFilePath = path.join(this.webpackOutputPath, name);
3033
this.serverless.utils.writeFileDir(artifactFilePath);
3134

3235
const output = fs.createWriteStream(artifactFilePath);
@@ -69,13 +72,47 @@ function zip(directory, name) {
6972
});
7073
}
7174

75+
function getArtifactLocations(name) {
76+
const archiveName = `${name}.zip`;
77+
78+
const webpackArtifact = path.join(this.webpackOutputPath, archiveName);
79+
const serverlessArtifact = path.join('.serverless', archiveName);
80+
81+
return { webpackArtifact, serverlessArtifact };
82+
}
83+
84+
function copyArtifactByName(artifactName) {
85+
const { webpackArtifact, serverlessArtifact } = getArtifactLocations.call(this, artifactName);
86+
87+
// Make sure the destination dir exists
88+
this.serverless.utils.writeFileDir(serverlessArtifact);
89+
90+
fs.copyFileSync(webpackArtifact, serverlessArtifact);
91+
}
92+
93+
function setServiceArtifactPath(artifactPath) {
94+
_.set(this.serverless, 'service.package.artifact', artifactPath);
95+
}
96+
97+
function isIndividialPackaging() {
98+
return _.get(this.serverless, 'service.package.individually');
99+
}
100+
101+
function getArtifactName(entryFunction) {
102+
return `${entryFunction.funcName || this.serverless.service.getServiceObject().name}.zip`;
103+
}
104+
72105
module.exports = {
73106
packageModules() {
107+
if (this.skipCompile) {
108+
return BbPromise.resolve();
109+
}
110+
74111
const stats = this.compileStats;
75112

76113
return BbPromise.mapSeries(stats.stats, (compileStats, index) => {
77114
const entryFunction = _.get(this.entryFunctions, index, {});
78-
const filename = `${entryFunction.funcName || this.serverless.service.getServiceObject().name}.zip`;
115+
const filename = getArtifactName.call(this, entryFunction);
79116
const modulePath = compileStats.compilation.compiler.outputPath;
80117

81118
const startZip = _.now();
@@ -87,37 +124,52 @@ module.exports = {
87124
this.serverless.cli.log(
88125
`Zip ${_.isEmpty(entryFunction) ? 'service' : 'function'}: ${modulePath} [${_.now() - startZip} ms]`
89126
)
90-
)
91-
.then(artifactPath => {
92-
if (_.get(this.serverless, 'service.package.individually')) {
93-
setArtifactPath.call(
94-
this,
95-
entryFunction.funcName,
96-
entryFunction.func,
97-
path.relative(this.serverless.config.servicePath, artifactPath)
98-
);
99-
}
100-
return artifactPath;
101-
});
102-
}).then(artifacts => {
103-
if (!_.get(this.serverless, 'service.package.individually') && !_.isEmpty(artifacts)) {
104-
// Set the service artifact to all functions
105-
const allFunctionNames = this.serverless.service.getAllFunctions();
106-
_.forEach(allFunctionNames, funcName => {
107-
const func = this.serverless.service.getFunction(funcName);
108-
setArtifactPath.call(this, funcName, func, path.relative(this.serverless.config.servicePath, artifacts[0]));
109-
});
110-
// For Google set the service artifact path
111-
if (_.get(this.serverless, 'service.provider.name') === 'google') {
112-
_.set(
113-
this.serverless,
114-
'service.package.artifact',
115-
path.relative(this.serverless.config.servicePath, artifacts[0])
116-
);
117-
}
118-
}
127+
);
128+
});
129+
},
130+
131+
copyExistingArtifacts() {
132+
this.serverless.cli.log('Copying existing artifacts...');
133+
const allFunctionNames = this.serverless.service.getAllFunctions();
134+
135+
// Copy artifacts to package location
136+
if (isIndividialPackaging.call(this)) {
137+
_.forEach(allFunctionNames, funcName => copyArtifactByName.call(this, funcName));
138+
} else {
139+
// Copy service packaged artifact
140+
const serviceName = this.serverless.service.getServiceObject().name;
141+
copyArtifactByName.call(this, serviceName);
142+
}
143+
144+
_.forEach(allFunctionNames, funcName => {
145+
const func = this.serverless.service.getFunction(funcName);
146+
147+
const archiveName = isIndividialPackaging.call(this) ? funcName : this.serverless.service.getServiceObject().name;
119148

120-
return null;
149+
const { serverlessArtifact } = getArtifactLocations.call(this, archiveName);
150+
setArtifactPath.call(this, funcName, func, serverlessArtifact);
121151
});
152+
153+
// Set artifact locations
154+
if (isIndividialPackaging.call(this)) {
155+
_.forEach(allFunctionNames, funcName => {
156+
const func = this.serverless.service.getFunction(funcName);
157+
158+
const archiveName = funcName;
159+
160+
const { serverlessArtifact } = getArtifactLocations.call(this, archiveName);
161+
setArtifactPath.call(this, funcName, func, serverlessArtifact);
162+
});
163+
} else {
164+
const archiveName = this.serverless.service.getServiceObject().name;
165+
166+
const { serverlessArtifact } = getArtifactLocations.call(this, archiveName);
167+
168+
if (_.get(this.serverless, 'service.provider.name') === 'google') {
169+
setServiceArtifactPath.call(this, serverlessArtifact);
170+
}
171+
}
172+
173+
return BbPromise.resolve();
122174
}
123175
};

lib/validate.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ module.exports = {
171171
this.webpackConfig.output.path = path.join(this.serverless.config.servicePath, this.options.out);
172172
}
173173

174-
if (this.skipCompile) {
174+
// Skip compilation with --no-build
175+
if (this.options.build === false) {
176+
this.skipCompile = true;
175177
this.serverless.cli.log('Skipping build and using existing compiled output');
176178
if (!fse.pathExistsSync(this.webpackConfig.output.path)) {
177179
return BbPromise.reject(new this.serverless.classes.Error('No compiled output found'));

tests/cleanup.test.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ describe('cleanup', () => {
5555
{
5656
serverless,
5757
options: {},
58-
webpackOutputPath: 'my/Output/Path',
59-
configuration: {}
58+
webpackOutputPath: 'my/Output/Path'
6059
},
6160
baseModule
6261
);
@@ -96,7 +95,7 @@ describe('cleanup', () => {
9695
fseMock.removeSync.reset();
9796

9897
const configuredModule = _.assign({}, module, {
99-
configuration: { keepOutputDirectory: true }
98+
keepOutputDirectory: true
10099
});
101100
return expect(configuredModule.cleanup()).to.be.fulfilled.then(() => {
102101
expect(dirExistsSyncStub).to.not.have.been.calledOnce;

tests/mocks/fs.mock.js

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ module.exports.create = sandbox => {
2323
readFileSync: sandbox.stub(),
2424
statSync: sinon.stub().returns(statMock), // Persistent stub
2525
writeFileSync: sandbox.stub(),
26+
copyFileSync: sandbox.stub(),
2627

2728
_streamMock: streamMock,
2829
_statMock: statMock

tests/packExternalModules.test.js

+18
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,24 @@ describe('packExternalModules', () => {
334334
);
335335
});
336336

337+
it('should do nothing if skipCompile is true', () => {
338+
module.configuration = new Configuration({
339+
webpack: {
340+
includeModules: {
341+
packagePath: path.join('locals', 'package.json')
342+
}
343+
}
344+
});
345+
module.skipCompile = true;
346+
return expect(module.packExternalModules()).to.be.fulfilled.then(() =>
347+
BbPromise.all([
348+
expect(fsExtraMock.copy).to.not.have.been.called,
349+
expect(packagerFactoryMock.get).to.not.have.been.called,
350+
expect(writeFileSyncStub).to.not.have.been.called
351+
])
352+
);
353+
});
354+
337355
it('should copy needed package sections if available', () => {
338356
const originalPackageJSON = {
339357
name: 'test-service',

0 commit comments

Comments
 (0)