Skip to content

Commit fe8a425

Browse files
committed
feat(esbuild): implement first version of construct using esbuild
1 parent 6ff7588 commit fe8a425

File tree

3 files changed

+114
-12
lines changed

3 files changed

+114
-12
lines changed

src/index.ts

+56-7
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,91 @@
11
import * as lambda from '@aws-cdk/aws-lambda';
22
import * as cdk from '@aws-cdk/core';
3+
import * as es from 'esbuild';
4+
import * as path from 'path';
35

4-
import { nodeMajorVersion } from './utils';
6+
import { extractFileName, findProjectRoot, nodeMajorVersion } from './utils';
57

8+
/**
9+
* Properties for a NodejsFunction
10+
*/
611
export interface NodejsFunctionProps extends lambda.FunctionOptions {
712
/**
8-
* The name of the exported handler in the entry file.
13+
* The root of the lambda project. If you specify this prop, ensure that
14+
* this path includes `entry` and any module/dependencies used by your
15+
* function otherwise bundling will not be possible.
916
*
10-
* @default "handler"
17+
* @default = the closest path containing a .git folder
18+
*/
19+
readonly rootDir?: string;
20+
21+
/**
22+
* The name of the method within your code that Lambda calls to execute your function.
23+
*
24+
* The format includes the file name and handler function.
25+
* For more information, see https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html.
26+
*
27+
* @default = 'index.handler'
1128
*/
1229
readonly handler?: string;
30+
1331
/**
1432
* The runtime environment. Only runtimes of the Node.js family are
1533
* supported.
1634
*
17-
* @default - `NODEJS_12_X` if `process.versions.node` >= '12.0.0',
35+
* @default = `NODEJS_12_X` if `process.versions.node` >= '12.0.0',
1836
* `NODEJS_10_X` otherwise.
1937
*/
2038
readonly runtime?: lambda.Runtime;
39+
40+
/**
41+
* The esbuild bundler specific options.
42+
*
43+
* @default = { platform: 'node' }
44+
*/
45+
readonly esbuildOptions?: es.BuildOptions;
2146
}
2247

48+
const BUILD_FOLDER = '.build';
49+
const DEFAULT_BUILD_OPTIONS: es.BuildOptions = {
50+
bundle: true,
51+
target: 'es2017',
52+
external: ['aws-sdk'],
53+
};
54+
55+
/**
56+
* A Node.js Lambda function bundled using `esbuild`
57+
*/
2358
export class NodejsFunction extends lambda.Function {
2459
constructor(scope: cdk.Construct, id: string, props: NodejsFunctionProps = {}) {
2560
if (props.runtime && props.runtime.family !== lambda.RuntimeFamily.NODEJS) {
2661
throw new Error('Only `NODEJS` runtimes are supported.');
2762
}
2863

29-
const handler = props.handler ?? 'handler';
64+
const projectRoot = findProjectRoot(props.rootDir);
65+
if (!projectRoot) {
66+
throw new Error('Cannot find root directory. Please specify it with `rootDir` option.');
67+
}
68+
69+
const handler = props.handler ?? 'index.handler';
3070
const defaultRunTime = nodeMajorVersion() >= 12
3171
? lambda.Runtime.NODEJS_12_X
3272
: lambda.Runtime.NODEJS_10_X;
3373
const runtime = props.runtime ?? defaultRunTime;
74+
const entry = extractFileName(projectRoot, handler);
75+
76+
es.buildSync({
77+
...DEFAULT_BUILD_OPTIONS,
78+
...props.esbuildOptions,
79+
entryPoints: [entry],
80+
outdir: path.join(projectRoot, BUILD_FOLDER, path.dirname(entry)),
81+
platform: 'node',
82+
});
3483

3584
super(scope, id, {
3685
...props,
3786
runtime,
38-
code: lambda.Code.fromInline('TODO'),
39-
handler: `index.${handler}`,
87+
code: lambda.Code.fromAsset(path.join(projectRoot, BUILD_FOLDER)),
88+
handler,
4089
});
4190
}
4291
}

src/utils.ts

+50
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,53 @@
1+
import * as fs from 'fs-extra';
2+
import * as path from 'path';
3+
4+
export function extractFileName(cwd: string, handler: string): string {
5+
const fnName = path.extname(handler);
6+
const fnNameLastAppearanceIndex = handler.lastIndexOf(fnName);
7+
// replace only last instance to allow the same name for file and handler
8+
const fileName = handler.substring(0, fnNameLastAppearanceIndex);
9+
10+
// Check if the .ts files exists. If so return that to watch
11+
if (fs.existsSync(path.join(cwd, fileName + '.ts'))) {
12+
return fileName + '.ts';
13+
}
14+
15+
// Check if the .js files exists. If so return that to watch
16+
if (fs.existsSync(path.join(cwd, fileName + '.js'))) {
17+
return fileName + '.js';
18+
}
19+
20+
// Can't find the files. Watch will have an exception anyway. So throw one with error.
21+
console.log(`Cannot locate handler - ${fileName} not found`);
22+
throw new Error('Compilation failed. Please ensure handler exists with ext .ts or .js');
23+
}
24+
25+
/**
26+
* Find a file by walking up parent directories
27+
*/
28+
export function findUp(name: string, directory: string = process.cwd()): string | undefined {
29+
const absoluteDirectory = path.resolve(directory);
30+
31+
if (fs.existsSync(path.join(directory, name))) {
32+
return directory;
33+
}
34+
35+
const { root } = path.parse(absoluteDirectory);
36+
if (absoluteDirectory === root) {
37+
return undefined;
38+
}
39+
40+
return findUp(name, path.dirname(absoluteDirectory));
41+
}
42+
43+
export function findProjectRoot(rootDir: string): string {
44+
return rootDir
45+
?? findUp(`.git${path.sep}`)
46+
?? findUp('yarn.lock')
47+
?? findUp('package-lock.json')
48+
?? findUp('package.json');
49+
}
50+
151
/**
252
* Returns the major version of node installation
353
*/

tsconfig.json

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
{
22
"compilerOptions": {
3-
"module": "commonjs",
3+
"module": "CommonJS",
44
"target": "es6",
5+
"outDir": "dist",
6+
"declaration": true,
57
"skipLibCheck": true,
6-
"outDir": "dist"
8+
"esModuleInterop": true,
9+
"lib": ["ES2017"]
710
},
8-
"include": [
9-
"src"
10-
],
1111
"exclude": [
1212
"node_modules"
13+
],
14+
"include": [
15+
"src"
1316
]
1417
}

0 commit comments

Comments
 (0)