Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,14 @@ module.exports = (async () => {
```

```js
// Version with promises
// Version with native promises
// webpack.config.js

const webpack = require('webpack')
const slsw = require('serverless-webpack');
const BbPromise = require('bluebird');

module.exports = BbPromise.try(() => {
return slsw.lib.serverless.providers.aws.getAccountId()
module.exports = Promise.resolve()
.then(() => slsw.lib.serverless.providers.aws.getAccountId())
.then(accountId => ({
entry: './handler.js',
target: 'node',
Expand All @@ -149,7 +148,6 @@ module.exports = BbPromise.try(() => {
loaders: [ ... ]
}
}));
});
```

### serverless-webpack lib export helper
Expand Down
5 changes: 4 additions & 1 deletion __mocks__/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ const streamMock = {
};

const statMock = {
isDirectory: jest.fn()
isDirectory: jest.fn(),
mode: 0o644
};

const actual = jest.requireActual('fs');

module.exports = {
...actual,
createWriteStream: jest.fn().mockReturnValue(streamMock),
readFile: jest.fn((_path, callback) => callback(null, Buffer.from('mock file'))),
readFileSync: jest.fn(),
stat: jest.fn((_path, callback) => callback(null, statMock)),
statSync: jest.fn().mockReturnValue(statMock),
writeFileSync: jest.fn(),
copyFileSync: jest.fn(),
Expand Down
2 changes: 1 addition & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.5/schema.json",
"$schema": "https://biomejs.dev/schemas/2.4.12/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
Expand Down
63 changes: 30 additions & 33 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const BbPromise = require('bluebird');
const _ = require('lodash');

const validate = require('./lib/validate');
Expand Down Expand Up @@ -150,13 +149,13 @@ class ServerlessWebpack {
}
},
'before:package:createDeploymentArtifacts': () =>
BbPromise.bind(this)
Promise.resolve()
.then(() => this.serverless.pluginManager.spawn('webpack:validate'))
.then(() => (this.skipCompile ? BbPromise.resolve() : this.serverless.pluginManager.spawn('webpack:compile')))
.then(() => (this.skipCompile ? undefined : this.serverless.pluginManager.spawn('webpack:compile')))
.then(() => this.serverless.pluginManager.spawn('webpack:package'))
.then(() => this.log && this.progress.get('webpack').remove()),

'after:package:createDeploymentArtifacts': () => BbPromise.bind(this).then(this.cleanup),
'after:package:createDeploymentArtifacts': () => Promise.resolve().then(() => this.cleanup()),

'before:deploy:function:packageFunction': () => {
const runtime =
Expand All @@ -165,98 +164,96 @@ class ServerlessWebpack {
'nodejs';

if (isNodeRuntime(runtime)) {
return BbPromise.bind(this)
return Promise.resolve()
.then(() => this.serverless.pluginManager.spawn('webpack:validate'))
.then(() => this.serverless.pluginManager.spawn('webpack:compile'))
.then(() => this.serverless.pluginManager.spawn('webpack:package'));
}
},

'before:invoke:local:invoke': () =>
BbPromise.bind(this)
Promise.resolve()
.then(() => {
lib.webpack.isLocal = true;

return this.serverless.pluginManager.spawn('webpack:validate');
})
.then(() => (this.skipCompile ? BbPromise.resolve() : this.serverless.pluginManager.spawn('webpack:compile')))
.then(this.prepareLocalInvoke),
.then(() => (this.skipCompile ? undefined : this.serverless.pluginManager.spawn('webpack:compile')))
.then(() => this.prepareLocalInvoke()),

'after:invoke:local:invoke': () =>
BbPromise.bind(this).then(() => {
Promise.resolve().then(() => {
if (this.options.watch && !this.isWatching) {
return this.watch('invoke:local');
}
return BbPromise.resolve();
}),

'before:run:run': () =>
BbPromise.bind(this)
Promise.resolve()
.then(() => _.set(this.serverless, 'service.package.individually', false))
.then(() => this.serverless.pluginManager.spawn('webpack:validate'))
.then(() => this.serverless.pluginManager.spawn('webpack:compile'))
.then(this.packExternalModules)
.then(this.prepareRun),
.then(() => this.packExternalModules())
.then(() => this.prepareRun()),

'after:run:run': () =>
BbPromise.bind(this).then(() => {
Promise.resolve().then(() => {
if (this.options.watch && !this.isWatching) {
return this.watch(this.watchRun.bind(this));
}
return BbPromise.resolve();
}),

'webpack:webpack': () =>
BbPromise.bind(this)
Promise.resolve()
.then(() => this.serverless.pluginManager.spawn('webpack:validate'))
.then(() => this.serverless.pluginManager.spawn('webpack:compile'))
.then(() => this.serverless.pluginManager.spawn('webpack:package')),

/*
* Internal webpack events (can be hooked by plugins)
*/
'webpack:validate:validate': () => BbPromise.bind(this).then(this.validate),
'webpack:validate:validate': () => Promise.resolve().then(() => this.validate()),

'webpack:compile:compile': () => BbPromise.bind(this).then(this.compile),
'webpack:compile:compile': () => Promise.resolve().then(() => this.compile()),

'webpack:compile:watch:compile': () => BbPromise.resolve(),
'webpack:compile:watch:compile': () => Promise.resolve(),

'webpack:package:packExternalModules': () => BbPromise.bind(this).then(this.packExternalModules),
'webpack:package:packExternalModules': () => Promise.resolve().then(() => this.packExternalModules()),

'webpack:package:packageModules': () => BbPromise.bind(this).then(this.packageModules),
'webpack:package:packageModules': () => Promise.resolve().then(() => this.packageModules()),

'webpack:package:copyExistingArtifacts': () => BbPromise.bind(this).then(this.copyExistingArtifacts),
'webpack:package:copyExistingArtifacts': () => Promise.resolve().then(() => this.copyExistingArtifacts()),

'before:offline:start': () =>
BbPromise.bind(this)
.tap(() => {
Promise.resolve()
.then(() => {
lib.webpack.isLocal = true;
// --skip-build override
if (this.options['skip-build'] === false) {
this.skipCompile = true;
}
})
.then(this.prepareOfflineInvoke)
.then(() => (this.skipCompile ? BbPromise.resolve() : this.wpwatch())),
.then(() => this.prepareOfflineInvoke())
.then(() => (this.skipCompile ? undefined : this.wpwatch())),

'before:offline:start:init': () =>
BbPromise.bind(this)
.tap(() => {
Promise.resolve()
.then(() => {
lib.webpack.isLocal = true;
// --skip-build override
if (this.options['skip-build'] === false) {
this.skipCompile = true;
}
})
.then(this.prepareOfflineInvoke)
.then(() => (this.skipCompile ? BbPromise.resolve() : this.wpwatch())),
.then(() => this.prepareOfflineInvoke())
.then(() => (this.skipCompile ? undefined : this.wpwatch())),

'before:step-functions-offline:start': () =>
BbPromise.bind(this)
.tap(() => {
Promise.resolve()
.then(() => {
lib.webpack.isLocal = true;
})
.then(this.prepareStepOfflineInvoke)
.then(() => this.prepareStepOfflineInvoke())
.then(() => this.serverless.pluginManager.spawn('webpack:compile'))
};
}
Expand Down
3 changes: 1 addition & 2 deletions lib/cleanup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const _ = require('lodash');
const BbPromise = require('bluebird');
const fse = require('fs-extra');

module.exports = {
Expand Down Expand Up @@ -40,6 +39,6 @@ module.exports = {
}
}

return BbPromise.resolve();
return Promise.resolve();
}
};
133 changes: 78 additions & 55 deletions lib/compile.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const _ = require('lodash');
const BbPromise = require('bluebird');
const webpack = require('webpack');
const { isBuiltin } = require('node:module');
const logStats = require('./logStats');
Expand Down Expand Up @@ -104,61 +103,84 @@ function getExternalModules({ compilation }) {
return Array.from(externals);
}

function webpackCompile(config, logStats) {
return BbPromise.fromCallback(cb => webpack(config).run(cb)).then(stats => {
// ensure stats in any array in the case of concurrent build.
stats = stats.stats ? stats.stats : [stats];
async function webpackCompile(config, logStats) {
let stats = await new Promise((resolve, reject) => {
webpack(config).run((error, result) => {
if (error) {
reject(error);
return;
}
resolve(result);
});
});

_.forEach(stats, logStats);
// ensure stats in any array in the case of concurrent build.
stats = stats.stats ? stats.stats : [stats];

return _.map(stats, compileStats => ({
outputPath: compileStats.compilation.compiler.outputPath,
externalModules: getExternalModules(compileStats)
}));
});
_.forEach(stats, logStats);

return _.map(stats, compileStats => ({
outputPath: compileStats.compilation.compiler.outputPath,
externalModules: getExternalModules(compileStats)
}));
}

async function mapWithConcurrency(items, concurrency, iteratee) {
const results = new Array(items.length);
let currentIndex = 0;

async function worker() {
while (currentIndex < items.length) {
const index = currentIndex;
currentIndex += 1;
results[index] = await iteratee(items[index], index);
}
}

const workerCount = Math.min(concurrency, items.length);
await Promise.all(Array.from({ length: workerCount }, () => worker()));
return results;
}

function webpackConcurrentCompile(configs, logStats, concurrency, ServerlessError) {
async function webpackConcurrentCompile(configs, logStats, concurrency, ServerlessError) {
const errors = [];
return BbPromise.map(
configs,
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 => {

const stats = await mapWithConcurrency(configs, concurrency, async 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;
});
},
{ concurrency }
).then(stats => {
if (errors.length) {
if (!this.log) {
if (errors.length === 1) {
throw errors[0];
}
throw new ServerlessError('Webpack compilation errors, see stats above');
}
throw new ServerlessError(
`Webpack compilation failed:\n\n${_.join(
_.map(errors, error => error.message),
'\n\n'
)}`
);
}
return _.flatten(stats);
return webpackCompile(config, logStats, ServerlessError).catch(error => {
errors.push(error);
return error.stats;
});
});

if (errors.length) {
if (!this.log) {
if (errors.length === 1) {
throw errors[0];
}
throw new ServerlessError('Webpack compilation errors, see stats above');
}
throw new ServerlessError(
`Webpack compilation failed:\n\n${_.join(
_.map(errors, error => error.message),
'\n\n'
)}`
);
}

return _.flatten(stats);
}

module.exports = {
compile() {
async compile() {
if (this.log) {
this.log.verbose('[Webpack] Building with Webpack');
this.progress.get('webpack').update('[Webpack] Building with Webpack');
Expand All @@ -168,7 +190,7 @@ module.exports = {

const configs = ensureArray(this.webpackConfig);
if (configs[0] === undefined) {
return BbPromise.reject('Unable to find Webpack configuration');
throw new this.serverless.classes.Error('Unable to find Webpack configuration');
}

const logStats = getStatsLogger(configs[0].stats, this.serverless.cli.consoleLog, {
Expand All @@ -177,20 +199,21 @@ module.exports = {
});

if (!this.configuration) {
return BbPromise.reject(new this.serverless.classes.Error('Missing plugin configuration'));
throw new this.serverless.classes.Error('Missing plugin configuration');
}
const concurrency = this.configuration.concurrency;

return webpackConcurrentCompile
.call(this, configs, logStats, concurrency, this.serverless.classes.Error)
.then(stats => {
this.compileStats = { stats };

if (this.log) {
this.progress.get('webpack').remove();
}
const stats = await webpackConcurrentCompile.call(
this,
configs,
logStats,
concurrency,
this.serverless.classes.Error
);
this.compileStats = { stats };

return BbPromise.resolve();
});
if (this.log) {
this.progress.get('webpack').remove();
}
}
};
Loading