Skip to content

Commit 1f9ef6d

Browse files
authored
Merge pull request #413 from nidin/patch-1
Added support for async webpack config object
2 parents b228099 + 77121fc commit 1f9ef6d

File tree

4 files changed

+238
-102
lines changed

4 files changed

+238
-102
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ coverage
66

77
.idea
88
/.nyc_output
9+
.history

README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ dependencies needed to run the function. This allows the plugin to fully utilize
2626
WebPack's [Tree-Shaking][link-webpack-tree] optimization.
2727
* Webpack version 3 and 4 support
2828
* Support NPM and Yarn for packaging
29+
* Support asynchronous webpack configuration
2930

3031
## Recent improvements and important changes
3132

3233
* Support Yarn
3334
* Support Webpack 4
3435
* Drop Webpack 2 support
3536
* Cleaned up configuration. You should now use a `custom.webpack` object to configure everything relevant for the plugin. The old configuration still works but will be removed in the next major release. For details see below.
37+
* Added support for asynchronous webpack configuration
3638

3739
For the complete release notes see the end of this document.
3840

@@ -87,6 +89,57 @@ module.exports = {
8789
};
8890
```
8991

92+
Alternatively the Webpack configuration can export an asynchronous object (e.g. a promise or async function) which will be awaited by the plugin and resolves to the final configuration object. This is useful if the confguration depends on asynchronous functions, for example, defining the AccountId of the current aws user inside AWS lambda@edge which does not support defining normal process environment variables.
93+
94+
A basic Webpack promise configuration might look like this:
95+
```js
96+
// Version if the local Node.js version supports async/await
97+
// webpack.config.js
98+
99+
const webpack = require('webpack')
100+
const slsw = require('serverless-webpack');
101+
102+
module.exports = async () => {
103+
const accountId = await slsw.lib.serverless.providers.aws.getAccountId();
104+
return {
105+
entry: './handler.js',
106+
target: 'node',
107+
plugins: [
108+
new webpack.DefinePlugin({
109+
AWS_ACCOUNT_ID: `${accountId}`,
110+
}),
111+
],
112+
module: {
113+
loaders: [ ... ]
114+
}
115+
};
116+
}();
117+
```
118+
```js
119+
// Version with promises
120+
// webpack.config.js
121+
122+
const webpack = require('webpack')
123+
const slsw = require('serverless-webpack');
124+
const BbPromise = require('bluebird');
125+
126+
module.exports = BbPromise.try(() => {
127+
return slsw.lib.serverless.providers.aws.getAccountId()
128+
.then(accountId => ({
129+
entry: './handler.js',
130+
target: 'node',
131+
plugins: [
132+
new webpack.DefinePlugin({
133+
AWS_ACCOUNT_ID: `${accountId}`,
134+
}),
135+
],
136+
module: {
137+
loaders: [ ... ]
138+
}
139+
}));
140+
};
141+
```
142+
90143
### serverless-webpack lib export helper
91144
92145
serverless-webpack exposes a lib object, that can be used in your webpack.config.js
@@ -665,6 +718,9 @@ me to take it over and continue working on the project. That helped to revive it
665718

666719
## Release Notes
667720

721+
* 5.2.0
722+
* Added support for asynchronous webpack configuration
723+
668724
* 5.1.5
669725
* Re-publish of 5.1.4 without yarn.lock
670726

lib/validate.js

Lines changed: 96 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ module.exports = {
107107
if (_.isString(this.webpackConfig)) {
108108
const webpackConfigFilePath = path.join(this.serverless.config.servicePath, this.webpackConfig);
109109
if (!this.serverless.utils.fileExistsSync(webpackConfigFilePath)) {
110-
throw new this.serverless.classes
111-
.Error('The webpack plugin could not find the configuration file at: ' + webpackConfigFilePath);
110+
return BbPromise.reject(new this.serverless.classes
111+
.Error('The webpack plugin could not find the configuration file at: ' + webpackConfigFilePath));
112112
}
113113
try {
114114
this.webpackConfig = require(webpackConfigFilePath);
@@ -117,104 +117,115 @@ module.exports = {
117117
return BbPromise.reject(err);
118118
}
119119
}
120+
121+
// Intermediate function to handle async webpack config
122+
const processConfig = _config => {
123+
this.webpackConfig = _config;
124+
// Default context
125+
if (!this.webpackConfig.context) {
126+
this.webpackConfig.context = this.serverless.config.servicePath;
127+
}
120128

121-
// Default context
122-
if (!this.webpackConfig.context) {
123-
this.webpackConfig.context = this.serverless.config.servicePath;
124-
}
125-
126-
// Default target
127-
if (!this.webpackConfig.target) {
128-
this.webpackConfig.target = 'node';
129-
}
130-
131-
// Default output
132-
if (!this.webpackConfig.output || _.isEmpty(this.webpackConfig.output)) {
133-
const outputPath = path.join(this.serverless.config.servicePath, '.webpack');
134-
this.webpackConfig.output = {
135-
libraryTarget: 'commonjs',
136-
path: outputPath,
137-
filename: '[name].js',
138-
};
139-
}
140-
141-
// Custom output path
142-
if (this.options.out) {
143-
this.webpackConfig.output.path = path.join(this.serverless.config.servicePath, this.options.out);
144-
}
129+
// Default target
130+
if (!this.webpackConfig.target) {
131+
this.webpackConfig.target = 'node';
132+
}
145133

146-
if (this.skipCompile) {
147-
this.serverless.cli.log('Skipping build and using existing compiled output');
148-
if (!fse.pathExistsSync(this.webpackConfig.output.path)) {
149-
return BbPromise.reject(new this.serverless.classes
150-
.Error('No compiled output found'));
134+
// Default output
135+
if (!this.webpackConfig.output || _.isEmpty(this.webpackConfig.output)) {
136+
const outputPath = path.join(this.serverless.config.servicePath, '.webpack');
137+
this.webpackConfig.output = {
138+
libraryTarget: 'commonjs',
139+
path: outputPath,
140+
filename: '[name].js',
141+
};
151142
}
152-
this.keepOutputDirectory = true;
153-
}
154143

155-
if (!this.keepOutputDirectory) {
156-
this.options.verbose && this.serverless.cli.log(`Removing ${this.webpackConfig.output.path}`);
157-
fse.removeSync(this.webpackConfig.output.path);
158-
}
159-
this.webpackOutputPath = this.webpackConfig.output.path;
144+
// Custom output path
145+
if (this.options.out) {
146+
this.webpackConfig.output.path = path.join(this.serverless.config.servicePath, this.options.out);
147+
}
160148

161-
// In case of individual packaging we have to create a separate config for each function
162-
if (_.has(this.serverless, 'service.package') && this.serverless.service.package.individually) {
163-
this.options.verbose && this.serverless.cli.log('Using multi-compile (individual packaging)');
164-
this.multiCompile = true;
149+
if (this.skipCompile) {
150+
this.serverless.cli.log('Skipping build and using existing compiled output');
151+
if (!fse.pathExistsSync(this.webpackConfig.output.path)) {
152+
return BbPromise.reject(new this.serverless.classes
153+
.Error('No compiled output found'));
154+
}
155+
this.keepOutputDirectory = true;
156+
}
165157

166-
if (this.webpackConfig.entry && !_.isEqual(this.webpackConfig.entry, entries)) {
167-
return BbPromise.reject(new this.serverless.classes
168-
.Error('Webpack entry must be automatically resolved when package.individually is set to true. ' +
169-
'In webpack.config.js, remove the entry declaration or set entry to slsw.lib.entries.'));
158+
if (!this.keepOutputDirectory) {
159+
this.options.verbose && this.serverless.cli.log(`Removing ${this.webpackConfig.output.path}`);
160+
fse.removeSync(this.webpackConfig.output.path);
170161
}
162+
this.webpackOutputPath = this.webpackConfig.output.path;
171163

172-
// Lookup associated Serverless functions
173-
const allEntryFunctions = _.map(
174-
this.serverless.service.getAllFunctions(),
175-
funcName => {
176-
const func = this.serverless.service.getFunction(funcName);
177-
const handler = func.handler;
178-
const handlerFile = path.relative('.', getHandlerFile(handler));
179-
return {
180-
handlerFile,
181-
funcName,
182-
func
183-
};
164+
// In case of individual packaging we have to create a separate config for each function
165+
if (_.has(this.serverless, 'service.package') && this.serverless.service.package.individually) {
166+
this.options.verbose && this.serverless.cli.log('Using multi-compile (individual packaging)');
167+
this.multiCompile = true;
168+
169+
if (this.webpackConfig.entry && !_.isEqual(this.webpackConfig.entry, entries)) {
170+
return BbPromise.reject(new this.serverless.classes
171+
.Error('Webpack entry must be automatically resolved when package.individually is set to true. ' +
172+
'In webpack.config.js, remove the entry declaration or set entry to slsw.lib.entries.'));
184173
}
185-
);
186174

187-
this.entryFunctions = _.flatMap(entries, (value, key) => {
188-
const entry = path.relative('.', value);
189-
const entryFile = _.replace(entry, new RegExp(`${path.extname(entry)}$`), '');
175+
// Lookup associated Serverless functions
176+
const allEntryFunctions = _.map(
177+
this.serverless.service.getAllFunctions(),
178+
funcName => {
179+
const func = this.serverless.service.getFunction(funcName);
180+
const handler = func.handler;
181+
const handlerFile = path.relative('.', getHandlerFile(handler));
182+
return {
183+
handlerFile,
184+
funcName,
185+
func
186+
};
187+
}
188+
);
189+
190+
this.entryFunctions = _.flatMap(entries, (value, key) => {
191+
const entry = path.relative('.', value);
192+
const entryFile = _.replace(entry, new RegExp(`${path.extname(entry)}$`), '');
193+
194+
const entryFuncs = _.filter(allEntryFunctions, [ 'handlerFile', entryFile ]);
195+
if (_.isEmpty(entryFuncs)) {
196+
// We have to make sure that for each entry there is an entry function item.
197+
entryFuncs.push({});
198+
}
199+
_.forEach(entryFuncs, entryFunc => {
200+
entryFunc.entry = {
201+
key,
202+
value
203+
};
204+
});
205+
return entryFuncs;
206+
});
190207

191-
const entryFuncs = _.filter(allEntryFunctions, [ 'handlerFile', entryFile ]);
192-
if (_.isEmpty(entryFuncs)) {
193-
// We have to make sure that for each entry there is an entry function item.
194-
entryFuncs.push({});
195-
}
196-
_.forEach(entryFuncs, entryFunc => {
197-
entryFunc.entry = {
198-
key,
199-
value
208+
this.webpackConfig = _.map(this.entryFunctions, entryFunc => {
209+
const config = _.cloneDeep(this.webpackConfig);
210+
config.entry = {
211+
[entryFunc.entry.key]: entryFunc.entry.value
200212
};
213+
const compileName = entryFunc.funcName || _.camelCase(entryFunc.entry.key);
214+
config.output.path = path.join(config.output.path, compileName);
215+
return config;
201216
});
202-
return entryFuncs;
203-
});
217+
} else {
218+
this.webpackConfig.output.path = path.join(this.webpackConfig.output.path, 'service');
219+
}
204220

205-
this.webpackConfig = _.map(this.entryFunctions, entryFunc => {
206-
const config = _.cloneDeep(this.webpackConfig);
207-
config.entry = {
208-
[entryFunc.entry.key]: entryFunc.entry.value
209-
};
210-
const compileName = entryFunc.funcName || _.camelCase(entryFunc.entry.key);
211-
config.output.path = path.join(config.output.path, compileName);
212-
return config;
213-
});
221+
return BbPromise.resolve();
222+
};
223+
224+
// Webpack config can be a Promise, If it's a Promise wait for resolved config object.
225+
if (this.webpackConfig && _.isFunction(this.webpackConfig.then)) {
226+
return BbPromise.resolve(this.webpackConfig.then(config => processConfig(config)));
214227
} else {
215-
this.webpackConfig.output.path = path.join(this.webpackConfig.output.path, 'service');
228+
return processConfig(this.webpackConfig);
216229
}
217-
218-
return BbPromise.resolve();
219230
},
220231
};

0 commit comments

Comments
 (0)