Skip to content

Commit

Permalink
feat(esbuild): implement first version of construct using esbuild
Browse files Browse the repository at this point in the history
  • Loading branch information
floydspace committed Oct 25, 2020
1 parent 6ff7588 commit fe8a425
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 12 deletions.
63 changes: 56 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,91 @@
import * as lambda from '@aws-cdk/aws-lambda';
import * as cdk from '@aws-cdk/core';
import * as es from 'esbuild';
import * as path from 'path';

import { nodeMajorVersion } from './utils';
import { extractFileName, findProjectRoot, nodeMajorVersion } from './utils';

/**
* Properties for a NodejsFunction
*/
export interface NodejsFunctionProps extends lambda.FunctionOptions {
/**
* The name of the exported handler in the entry file.
* The root of the lambda project. If you specify this prop, ensure that
* this path includes `entry` and any module/dependencies used by your
* function otherwise bundling will not be possible.
*
* @default "handler"
* @default = the closest path containing a .git folder
*/
readonly rootDir?: string;

/**
* The name of the method within your code that Lambda calls to execute your function.
*
* The format includes the file name and handler function.
* For more information, see https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html.
*
* @default = 'index.handler'
*/
readonly handler?: string;

/**
* The runtime environment. Only runtimes of the Node.js family are
* supported.
*
* @default - `NODEJS_12_X` if `process.versions.node` >= '12.0.0',
* @default = `NODEJS_12_X` if `process.versions.node` >= '12.0.0',
* `NODEJS_10_X` otherwise.
*/
readonly runtime?: lambda.Runtime;

/**
* The esbuild bundler specific options.
*
* @default = { platform: 'node' }
*/
readonly esbuildOptions?: es.BuildOptions;
}

const BUILD_FOLDER = '.build';
const DEFAULT_BUILD_OPTIONS: es.BuildOptions = {
bundle: true,
target: 'es2017',
external: ['aws-sdk'],
};

/**
* A Node.js Lambda function bundled using `esbuild`
*/
export class NodejsFunction extends lambda.Function {
constructor(scope: cdk.Construct, id: string, props: NodejsFunctionProps = {}) {
if (props.runtime && props.runtime.family !== lambda.RuntimeFamily.NODEJS) {
throw new Error('Only `NODEJS` runtimes are supported.');
}

const handler = props.handler ?? 'handler';
const projectRoot = findProjectRoot(props.rootDir);
if (!projectRoot) {
throw new Error('Cannot find root directory. Please specify it with `rootDir` option.');
}

const handler = props.handler ?? 'index.handler';
const defaultRunTime = nodeMajorVersion() >= 12
? lambda.Runtime.NODEJS_12_X
: lambda.Runtime.NODEJS_10_X;
const runtime = props.runtime ?? defaultRunTime;
const entry = extractFileName(projectRoot, handler);

es.buildSync({
...DEFAULT_BUILD_OPTIONS,
...props.esbuildOptions,
entryPoints: [entry],
outdir: path.join(projectRoot, BUILD_FOLDER, path.dirname(entry)),
platform: 'node',
});

super(scope, id, {
...props,
runtime,
code: lambda.Code.fromInline('TODO'),
handler: `index.${handler}`,
code: lambda.Code.fromAsset(path.join(projectRoot, BUILD_FOLDER)),
handler,
});
}
}
50 changes: 50 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
import * as fs from 'fs-extra';
import * as path from 'path';

export function extractFileName(cwd: string, handler: string): string {
const fnName = path.extname(handler);
const fnNameLastAppearanceIndex = handler.lastIndexOf(fnName);
// replace only last instance to allow the same name for file and handler
const fileName = handler.substring(0, fnNameLastAppearanceIndex);

// Check if the .ts files exists. If so return that to watch
if (fs.existsSync(path.join(cwd, fileName + '.ts'))) {
return fileName + '.ts';
}

// Check if the .js files exists. If so return that to watch
if (fs.existsSync(path.join(cwd, fileName + '.js'))) {
return fileName + '.js';
}

// Can't find the files. Watch will have an exception anyway. So throw one with error.
console.log(`Cannot locate handler - ${fileName} not found`);
throw new Error('Compilation failed. Please ensure handler exists with ext .ts or .js');
}

/**
* Find a file by walking up parent directories
*/
export function findUp(name: string, directory: string = process.cwd()): string | undefined {
const absoluteDirectory = path.resolve(directory);

if (fs.existsSync(path.join(directory, name))) {
return directory;
}

const { root } = path.parse(absoluteDirectory);
if (absoluteDirectory === root) {
return undefined;
}

return findUp(name, path.dirname(absoluteDirectory));
}

export function findProjectRoot(rootDir: string): string {
return rootDir
?? findUp(`.git${path.sep}`)
?? findUp('yarn.lock')
?? findUp('package-lock.json')
?? findUp('package.json');
}

/**
* Returns the major version of node installation
*/
Expand Down
13 changes: 8 additions & 5 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"module": "CommonJS",
"target": "es6",
"outDir": "dist",
"declaration": true,
"skipLibCheck": true,
"outDir": "dist"
"esModuleInterop": true,
"lib": ["ES2017"]
},
"include": [
"src"
],
"exclude": [
"node_modules"
],
"include": [
"src"
]
}

0 comments on commit fe8a425

Please sign in to comment.