Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: function level entry overrides #1220

Merged
merged 12 commits into from
Aug 22, 2022
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,23 @@ module.exports = {
};
```

##### Optional entry overrides

`serverless-webpack` generates Webpack entries from the `handler` value by default.

If your handler is different from the webpack entry, e.g. provided by a layer,
you may override the generated entry at function level via the `entrypoint`
option in `serverless.yml`.

```yaml
functions:
my-function:
layers:
- LAYER-ARN
handler: layer.handler
entrypoint: src/index.handler
```

#### Full customization (for experts)

The lib export also provides the `serverless` and `options` properties, through
Expand Down
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const prepareStepOfflineInvoke = require('./lib/prepareStepOfflineInvoke');
const packExternalModules = require('./lib/packExternalModules');
const packageModules = require('./lib/packageModules');
const { isNodeRuntime } = require('./lib/utils');
const { extendFunctionProperties } = require('./lib/extendServerless');
const lib = require('./lib');

class ServerlessWebpack {
Expand All @@ -24,6 +25,8 @@ class ServerlessWebpack {

constructor(serverless, options, v3Utils) {
this.serverless = serverless;
extendFunctionProperties(this.serverless);

this.options = options;

if (v3Utils) {
Expand Down
15 changes: 15 additions & 0 deletions lib/extendServerless.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const _ = require('lodash');

const extendFunctionProperties = serverless => {
if (_.isFunction(serverless.configSchemaHandler.defineFunctionProperties)) {
serverless.configSchemaHandler.defineFunctionProperties('aws', {
properties: {
entrypoint: { type: 'string' }
}
});
}
};

module.exports = {
extendFunctionProperties
};
6 changes: 5 additions & 1 deletion lib/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ const preferredExtensions = ['.js', '.ts', '.jsx', '.tsx'];
module.exports = {
validate() {
const getHandlerFileAndFunctionName = functionDefinition => {
const { handler: handlerProp, image: imageProp } = functionDefinition;
const { handler: handlerProp, image: imageProp, entrypoint: entryPoint } = functionDefinition;

// CASE: The lambda handler is inside lambda layer. Use functionDefinition `entrypoint`
// to define the original handler.
if (entryPoint) return entryPoint;

if (handlerProp) {
return handlerProp;
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions tests/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,23 @@ describe('ServerlessWebpack', () => {
};
serverless.pluginManager.spawn = jest.fn().mockReturnValue(BbPromise.resolve());
serverless.service.getFunction = jest.fn().mockReturnValue({ runtime: 'nodejs12.x' });
serverless.configSchemaHandler.defineFunctionProperties = jest.fn();
});

it('should expose a lib object', () => {
const lib = ServerlessWebpack.lib;
expect(lib).toEqual({ entries: {}, webpack: { isLocal: false } });
});

it('should extend serverless', () => {
new ServerlessWebpack(serverless, {});
expect(serverless.configSchemaHandler.defineFunctionProperties).toHaveBeenCalledWith('aws', {
properties: {
entrypoint: { type: 'string' }
}
});
});

describe('with a TS webpack configuration', () => {
it('should support old config and register ts-node', () => {
jest.setMock('ts-node/register', null, { virtual: true });
Expand Down
32 changes: 25 additions & 7 deletions tests/validate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,10 @@ describe('validate', () => {
}
}
]
},
layerFunc: {
handler: 'layer.handler',
entrypoint: 'module1.func1handler'
}
};

Expand Down Expand Up @@ -547,7 +551,7 @@ describe('validate', () => {
};

expect(lib.entries).toEqual(expectedLibEntries);
expect(globMock.sync).toHaveBeenCalledTimes(5);
expect(globMock.sync).toHaveBeenCalledTimes(6);
expect(serverless.cli.log).toHaveBeenCalledTimes(0);
return null;
});
Expand Down Expand Up @@ -804,6 +808,7 @@ describe('validate', () => {
_.set(module.serverless.service, 'custom.webpack.config', testConfig);
module.serverless.service.functions = testFunctionsConfig;
globMock.sync.mockImplementation(filename => [_.replace(filename, '*', 'js')]);

return expect(module.validate())
.resolves.toBeUndefined()
.then(() => {
Expand Down Expand Up @@ -915,6 +920,12 @@ describe('validate', () => {
func: testFunctionsConfig.func1,
entry: { key: 'module1', value: './module1.js' }
},
{
handlerFile: 'module1',
funcName: 'layerFunc',
func: { handler: 'layer.handler', entrypoint: 'module1.func1handler' },
entry: { key: 'module1', value: './module1.js' }
},
{
handlerFile: 'module2',
funcName: 'func2',
Expand Down Expand Up @@ -960,12 +971,13 @@ describe('validate', () => {
return expect(module.validate())
.resolves.toBeUndefined()
.then(() => {
expect(module.webpackConfig).toHaveLength(5);
expect(module.webpackConfig).toHaveLength(6);
expect(module.webpackConfig[0].output.path).toEqual(path.join('output', 'func1'));
expect(module.webpackConfig[1].output.path).toEqual(path.join('output', 'func2'));
expect(module.webpackConfig[2].output.path).toEqual(path.join('output', 'func3'));
expect(module.webpackConfig[3].output.path).toEqual(path.join('output', 'func4'));
expect(module.webpackConfig[4].output.path).toEqual(path.join('output', 'dockerfunc'));
expect(module.webpackConfig[1].output.path).toEqual(path.join('output', 'layerFunc'));
expect(module.webpackConfig[2].output.path).toEqual(path.join('output', 'func2'));
expect(module.webpackConfig[3].output.path).toEqual(path.join('output', 'func3'));
expect(module.webpackConfig[4].output.path).toEqual(path.join('output', 'func4'));
expect(module.webpackConfig[5].output.path).toEqual(path.join('output', 'dockerfunc'));

return null;
});
Expand All @@ -988,22 +1000,28 @@ describe('validate', () => {
return expect(module.validate())
.resolves.toBeUndefined()
.then(() => {
expect(module.webpackConfig).toHaveLength(5);
expect(module.webpackConfig).toHaveLength(6);
expect(module.webpackConfig[0].devtool).toEqual('source-map');
expect(module.webpackConfig[1].devtool).toEqual('source-map');
expect(module.webpackConfig[2].devtool).toEqual('source-map');
expect(module.webpackConfig[3].devtool).toEqual('source-map');
expect(module.webpackConfig[4].devtool).toEqual('source-map');
expect(module.webpackConfig[5].devtool).toEqual('source-map');

expect(module.webpackConfig[0].context).toEqual('some context');
expect(module.webpackConfig[1].context).toEqual('some context');
expect(module.webpackConfig[2].context).toEqual('some context');
expect(module.webpackConfig[3].context).toEqual('some context');
expect(module.webpackConfig[4].context).toEqual('some context');
expect(module.webpackConfig[5].context).toEqual('some context');

expect(module.webpackConfig[0].output.libraryTarget).toEqual('commonjs');
expect(module.webpackConfig[1].output.libraryTarget).toEqual('commonjs');
expect(module.webpackConfig[2].output.libraryTarget).toEqual('commonjs');
expect(module.webpackConfig[3].output.libraryTarget).toEqual('commonjs');
expect(module.webpackConfig[4].output.libraryTarget).toEqual('commonjs');
expect(module.webpackConfig[5].output.libraryTarget).toEqual('commonjs');

return null;
});
});
Expand Down