From 44ad2306afb4cddda2c0af1593eceae1a13b96b4 Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Sun, 19 Sep 2021 06:56:48 +0500 Subject: [PATCH 01/15] feat(lambda-nodejs) experimental decorators support --- packages/@aws-cdk/aws-lambda-nodejs/README.md | 18 +++++++++ .../@aws-cdk/aws-lambda-nodejs/lib/Dockerfile | 3 ++ .../aws-lambda-nodejs/lib/bundling.ts | 36 +++++++++++++++-- .../@aws-cdk/aws-lambda-nodejs/lib/types.ts | 10 +++++ .../@aws-cdk/aws-lambda-nodejs/lib/util.ts | 17 ++++++++ .../aws-lambda-nodejs/test/bundling.test.ts | 40 +++++++++++++++++++ .../aws-lambda-nodejs/test/util.test.ts | 10 ++++- 7 files changed, 130 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 22347b28b1dd9..15b2ade10d8dd 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -224,6 +224,24 @@ an array of commands to run. Commands are chained with `&&`. The commands will run in the environment in which bundling occurs: inside the container for Docker bundling or on the host OS for local bundling. +## Pre Compilation with TSC + +It is possible to run compilation using typescript compiler before bundling, +this is for adding support of `emitDecoratorMetadata` which `esbuild` doesn't support it natively. +see . + +```ts +new lambda.NodejsFunction(this, 'my-handler', { + bundling: { + preCompilation: true, + tsconfig: 'tsconfig.json' + }, +}); +``` + +`tsconfig` is required to enable `preCompilation` as `tsc` will use your `tsconfig` for transpilation. +Output of the transpilation will be found under `cdk.out/tsc-compile` + ## Customizing Docker bundling Use `bundling.environment` to define environments variables when `esbuild` runs: diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile b/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile index 578b1b8135d5c..54969bb5fde2c 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/Dockerfile @@ -9,6 +9,9 @@ RUN npm install --global yarn@1.22.5 # Install pnpm RUN npm install --global pnpm +# Install typescript +RUN npm install --global typescript + # Install esbuild # (unsafe-perm because esbuild has a postinstall script) ARG ESBUILD_VERSION=0 diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 3c33ad74f2471..37975d496aa40 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -5,9 +5,10 @@ import * as cdk from '@aws-cdk/core'; import { EsbuildInstallation } from './esbuild-installation'; import { PackageManager } from './package-manager'; import { BundlingOptions, SourceMapMode } from './types'; -import { exec, extractDependencies, findUp } from './util'; +import { exec, extractDependencies, extractRootDir, findUp } from './util'; const ESBUILD_MAJOR_VERSION = '0'; +const PRE_COMPILATION_DIR = 'cdk.out/tsc-compile'; /** * Bundling properties @@ -32,6 +33,12 @@ export interface BundlingProps extends BundlingOptions { * Path to project root */ readonly projectRoot: string; + + /** + * Force pre-transpilation using TSC before bundling + */ + readonly preCompilation?: boolean + } /** @@ -67,6 +74,7 @@ export class Bundling implements cdk.BundlingOptions { private readonly relativeDepsLockFilePath: string; private readonly externals: string[]; private readonly packageManager: PackageManager; + private readonly preCompilation?: boolean; constructor(private readonly props: BundlingProps) { this.packageManager = PackageManager.fromLockFile(props.depsLockFilePath); @@ -85,6 +93,10 @@ export class Bundling implements cdk.BundlingOptions { this.relativeTsconfigPath = path.relative(this.projectRoot, path.resolve(props.tsconfig)); } + if (props.preCompilation && (/\.(tsx?)$/.test(props.entry))) { + this.preCompilation = true; + } + this.externals = [ ...props.externalModules ?? ['aws-sdk'], // Mark aws-sdk as external by default (available in the runtime) ...props.nodeModules ?? [], // Mark the modules that we are going to install as externals also @@ -122,6 +134,23 @@ export class Bundling implements cdk.BundlingOptions { private createBundlingCommand(options: BundlingCommandOptions): string { const pathJoin = osPathJoin(options.osPlatform); + const npx = options.osPlatform === 'win32' ? 'npx.cmd' : 'npx'; + + let compileCommand = ''; + let relativeJsEntryPath; + + if (this.preCompilation) { + relativeJsEntryPath = pathJoin(options.inputDir, PRE_COMPILATION_DIR, this.relativeEntryPath) + .replace(/\.(tsx)$/, '.jsx').replace(/\.(ts)$/, '.js'); + + if (this.relativeTsconfigPath) { + const tsconfigPath = pathJoin(options.inputDir, this.relativeTsconfigPath); + const rootDir = extractRootDir(tsconfigPath); + compileCommand =`${npx} tsc --project ${tsconfigPath} --outDir ${pathJoin(options.inputDir, PRE_COMPILATION_DIR, rootDir ?? '')}`; + } else { + throw new Error('preCompilation cannot be used when tsconfig is undefined'); + } + } const loaders = Object.entries(this.props.loader ?? {}); const defines = Object.entries(this.props.define ?? {}); @@ -129,14 +158,14 @@ export class Bundling implements cdk.BundlingOptions { if (this.props.sourceMap === false && this.props.sourceMapMode) { throw new Error('sourceMapMode cannot be used when sourceMap is false'); } - // eslint-disable-next-line no-console + const sourceMapEnabled = this.props.sourceMapMode ?? this.props.sourceMap; const sourceMapMode = this.props.sourceMapMode ?? SourceMapMode.DEFAULT; const sourceMapValue = sourceMapMode === SourceMapMode.DEFAULT ? '' : `=${this.props.sourceMapMode}`; const esbuildCommand: string[] = [ options.esbuildRunner, - '--bundle', `"${pathJoin(options.inputDir, this.relativeEntryPath)}"`, + '--bundle', `"${relativeJsEntryPath ? relativeJsEntryPath : pathJoin(options.inputDir, this.relativeEntryPath)}"`, `--target=${this.props.target ?? toTarget(this.props.runtime)}`, '--platform=node', `--outfile="${pathJoin(options.outputDir, 'index.js')}"`, @@ -178,6 +207,7 @@ export class Bundling implements cdk.BundlingOptions { } return chain([ + compileCommand, ...this.props.commandHooks?.beforeBundling(options.inputDir, options.outputDir) ?? [], esbuildCommand.join(' '), ...(this.props.nodeModules && this.props.commandHooks?.beforeInstall(options.inputDir, options.outputDir)) ?? [], diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts index 8d0d263fe64e6..f68559b404a5c 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts @@ -197,6 +197,16 @@ export interface BundlingOptions { */ readonly forceDockerBundling?: boolean; + /** + * Force pre-transpilation using TSC before running file through bundling step. + * This usually is not required unless you are using new experimental features that + * are only supported by typescript's`tsc` compiler. + * One example of such feature is `experimentalDecorators`. + * + * @default false + */ + readonly preCompilation?: boolean + /** * A custom bundling Docker image. * diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts index 5ead91793e93b..8866b828dd7e5 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts @@ -110,3 +110,20 @@ export function extractDependencies(pkgPath: string, modules: string[]): { [key: return dependencies; } + +/** + * Extract rootDir from tsConfig. + */ +export function extractRootDir(tsconfigPath: string): string | undefined { + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const { extends: extendedConfig, compilerOptions: { rootDir = undefined } = {} } = require(tsconfigPath); + if (!rootDir && extendedConfig) { + return extractRootDir(path.resolve(tsconfigPath.replace(/[^\/]+$/, ''), extendedConfig)); + } + return rootDir; + + } catch (err) { + return; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index a38a6fa08d5bc..653891488756f 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -218,6 +218,46 @@ test('esbuild bundling with esbuild options', () => { expect(bundleProcess.stdout.toString()).toMatchSnapshot(); }); +test('esbuild bundling with pre-compilations', () => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + forceDockerBundling: true, + tsconfig, + preCompilation: true, + }); + + // Correctly bundles with esbuild + expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + [ + 'npx tsc --project /asset-input/lib/custom-tsconfig.ts --outDir /asset-input/cdk.out/tsc-compile &&', + 'esbuild --bundle \"/asset-input/cdk.out/tsc-compile/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', + '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', + ].join(' '), + ], + }), + }); +}); + +test('esbuild bundling with pre-compilations without tsconfig', () => { + expect(() => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + forceDockerBundling: true, + preCompilation: true, + }); + }).toThrow('preCompilation cannot be used when tsconfig is undefined'); +}); + test('esbuild bundling source map default', () => { Bundling.bundle({ entry, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts index 4962ed203b31f..b1bf269a2766a 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts @@ -1,6 +1,6 @@ import * as child_process from 'child_process'; import * as path from 'path'; -import { callsites, exec, extractDependencies, findUp } from '../lib/util'; +import { callsites, exec, extractDependencies, extractRootDir, findUp } from '../lib/util'; beforeEach(() => { jest.clearAllMocks(); @@ -120,3 +120,11 @@ describe('extractDependencies', () => { )).toThrow(/Cannot extract version for module 'unknown'/); }); }); + +describe('extractRootDir', () => { + test('with rootDir defined in tsconfig.json', () => { + expect(extractRootDir( + path.join(__dirname, '../tsconfig.json'), + )).toEqual(undefined); + }); +}); From a4e0db80b0546caddf31f555bb3f5ea211423d55 Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Sun, 3 Oct 2021 05:15:00 +0500 Subject: [PATCH 02/15] PR feedback --- .../aws-lambda-nodejs/lib/bundling.ts | 59 +++++++++++-------- ...nstallation.ts => package-installation.ts} | 10 ++-- .../@aws-cdk/aws-lambda-nodejs/lib/types.ts | 4 +- .../aws-lambda-nodejs/test/bundling.test.ts | 19 +++--- 4 files changed, 52 insertions(+), 40 deletions(-) rename packages/@aws-cdk/aws-lambda-nodejs/lib/{esbuild-installation.ts => package-installation.ts} (71%) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 37975d496aa40..1ddb26598ee2e 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -2,7 +2,7 @@ import * as os from 'os'; import * as path from 'path'; import { AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; -import { EsbuildInstallation } from './esbuild-installation'; +import { PackageInstallation } from './package-installation'; import { PackageManager } from './package-manager'; import { BundlingOptions, SourceMapMode } from './types'; import { exec, extractDependencies, extractRootDir, findUp } from './util'; @@ -59,7 +59,12 @@ export class Bundling implements cdk.BundlingOptions { this.esbuildInstallation = undefined; } - private static esbuildInstallation?: EsbuildInstallation; + public static clearTscInstallationCache(): void { + this.tscInstallation = undefined; + } + + private static esbuildInstallation?: PackageInstallation; + private static tscInstallation?: PackageInstallation; // Core bundling options public readonly image: cdk.DockerImage; @@ -74,12 +79,12 @@ export class Bundling implements cdk.BundlingOptions { private readonly relativeDepsLockFilePath: string; private readonly externals: string[]; private readonly packageManager: PackageManager; - private readonly preCompilation?: boolean; constructor(private readonly props: BundlingProps) { this.packageManager = PackageManager.fromLockFile(props.depsLockFilePath); - Bundling.esbuildInstallation = Bundling.esbuildInstallation ?? EsbuildInstallation.detect(); + Bundling.esbuildInstallation = Bundling.esbuildInstallation ?? PackageInstallation.detect('esbuild'); + Bundling.tscInstallation = Bundling.tscInstallation ?? PackageInstallation.detect('tsc'); this.projectRoot = props.projectRoot; this.relativeEntryPath = path.relative(this.projectRoot, path.resolve(props.entry)); @@ -93,8 +98,8 @@ export class Bundling implements cdk.BundlingOptions { this.relativeTsconfigPath = path.relative(this.projectRoot, path.resolve(props.tsconfig)); } - if (props.preCompilation && (/\.(tsx?)$/.test(props.entry))) { - this.preCompilation = true; + if (props.preCompilation && !/\.tsx?$/.test(props.entry)) { + throw new Error('preCompilation can only be used with typescript files'); } this.externals = [ @@ -104,8 +109,8 @@ export class Bundling implements cdk.BundlingOptions { // Docker bundling const shouldBuildImage = props.forceDockerBundling || !Bundling.esbuildInstallation; - this.image = shouldBuildImage - ? props.dockerImage ?? cdk.DockerImage.fromBuild(path.join(__dirname, '../lib'), { + this.image = shouldBuildImage ? props.dockerImage ?? cdk.DockerImage.fromBuild(path.join(__dirname, '../lib'), + { buildArgs: { ...props.buildArgs ?? {}, IMAGE: props.runtime.bundlingImage.image, @@ -118,6 +123,7 @@ export class Bundling implements cdk.BundlingOptions { inputDir: cdk.AssetStaging.BUNDLING_INPUT_DIR, outputDir: cdk.AssetStaging.BUNDLING_OUTPUT_DIR, esbuildRunner: 'esbuild', // esbuild is installed globally in the docker image + tscRunner: 'tsc', // tsc is installed globally in the docker image osPlatform: 'linux', // linux docker image }); this.command = ['bash', '-c', bundlingCommand]; @@ -134,24 +140,23 @@ export class Bundling implements cdk.BundlingOptions { private createBundlingCommand(options: BundlingCommandOptions): string { const pathJoin = osPathJoin(options.osPlatform); - const npx = options.osPlatform === 'win32' ? 'npx.cmd' : 'npx'; - let compileCommand = ''; - let relativeJsEntryPath; + let tscCommand = ''; + let relativeEntryPath = this.relativeEntryPath; - if (this.preCompilation) { - relativeJsEntryPath = pathJoin(options.inputDir, PRE_COMPILATION_DIR, this.relativeEntryPath) - .replace(/\.(tsx)$/, '.jsx').replace(/\.(ts)$/, '.js'); - - if (this.relativeTsconfigPath) { - const tsconfigPath = pathJoin(options.inputDir, this.relativeTsconfigPath); - const rootDir = extractRootDir(tsconfigPath); - compileCommand =`${npx} tsc --project ${tsconfigPath} --outDir ${pathJoin(options.inputDir, PRE_COMPILATION_DIR, rootDir ?? '')}`; - } else { + if (this.props.preCompilation) { + if (!this.relativeTsconfigPath) { throw new Error('preCompilation cannot be used when tsconfig is undefined'); } + relativeEntryPath = pathJoin(PRE_COMPILATION_DIR, relativeEntryPath).replace(/\.ts(x?)$/, '.js$1'); + + const tsconfigPath = pathJoin(options.inputDir, this.relativeTsconfigPath); + const rootDir = extractRootDir(tsconfigPath); + process.stderr.write(`Compiling your project using typescript compiler version: ${Bundling.tscInstallation?.version} \n`); + tscCommand =`${options.tscRunner} --project ${tsconfigPath} --outDir ${pathJoin(options.inputDir, PRE_COMPILATION_DIR, rootDir ?? '')}`; } + const loaders = Object.entries(this.props.loader ?? {}); const defines = Object.entries(this.props.define ?? {}); @@ -165,7 +170,7 @@ export class Bundling implements cdk.BundlingOptions { const esbuildCommand: string[] = [ options.esbuildRunner, - '--bundle', `"${relativeJsEntryPath ? relativeJsEntryPath : pathJoin(options.inputDir, this.relativeEntryPath)}"`, + '--bundle', `"${pathJoin(options.inputDir, relativeEntryPath)}"`, `--target=${this.props.target ?? toTarget(this.props.runtime)}`, '--platform=node', `--outfile="${pathJoin(options.outputDir, 'index.js')}"`, @@ -207,8 +212,8 @@ export class Bundling implements cdk.BundlingOptions { } return chain([ - compileCommand, ...this.props.commandHooks?.beforeBundling(options.inputDir, options.outputDir) ?? [], + tscCommand, esbuildCommand.join(' '), ...(this.props.nodeModules && this.props.commandHooks?.beforeInstall(options.inputDir, options.outputDir)) ?? [], depsCommand, @@ -218,10 +223,11 @@ export class Bundling implements cdk.BundlingOptions { private getLocalBundlingProvider(): cdk.ILocalBundling { const osPlatform = os.platform(); - const createLocalCommand = (outputDir: string, esbuild: EsbuildInstallation) => this.createBundlingCommand({ + const createLocalCommand = (outputDir: string, esbuild: PackageInstallation, tsc: PackageInstallation) => this.createBundlingCommand({ inputDir: this.projectRoot, outputDir, esbuildRunner: esbuild.isLocal ? this.packageManager.runBinCommand('esbuild') : 'esbuild', + tscRunner: tsc.isLocal ? this.packageManager.runBinCommand('tsc') : 'tsc', osPlatform, }); const environment = this.props.environment ?? {}; @@ -238,7 +244,11 @@ export class Bundling implements cdk.BundlingOptions { throw new Error(`Expected esbuild version ${ESBUILD_MAJOR_VERSION}.x but got ${Bundling.esbuildInstallation.version}`); } - const localCommand = createLocalCommand(outputDir, Bundling.esbuildInstallation); + if (!Bundling.tscInstallation) { + throw new Error('Unable to find typescript locally or globally make sure install typescript in you dependencies'); + } + + const localCommand = createLocalCommand(outputDir, Bundling.esbuildInstallation, Bundling.tscInstallation); exec( osPlatform === 'win32' ? 'cmd' : 'bash', @@ -267,6 +277,7 @@ interface BundlingCommandOptions { readonly inputDir: string; readonly outputDir: string; readonly esbuildRunner: string; + readonly tscRunner: string; readonly osPlatform: NodeJS.Platform; } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/esbuild-installation.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-installation.ts similarity index 71% rename from packages/@aws-cdk/aws-lambda-nodejs/lib/esbuild-installation.ts rename to packages/@aws-cdk/aws-lambda-nodejs/lib/package-installation.ts index 8ef2e8dbb23d9..af9b1f50fdebf 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/esbuild-installation.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-installation.ts @@ -2,13 +2,13 @@ import { spawnSync } from 'child_process'; import { tryGetModuleVersion } from './util'; /** - * An esbuild installation + * Package installation */ -export abstract class EsbuildInstallation { - public static detect(): EsbuildInstallation | undefined { +export abstract class PackageInstallation { + public static detect(module: string): PackageInstallation | undefined { try { // Check local version first - const version = tryGetModuleVersion('esbuild'); + const version = tryGetModuleVersion(module); if (version) { return { isLocal: true, @@ -17,7 +17,7 @@ export abstract class EsbuildInstallation { } // Fallback to a global version - const esbuild = spawnSync('esbuild', ['--version']); + const esbuild = spawnSync(module, ['--version']); if (esbuild.status === 0 && !esbuild.error) { return { isLocal: false, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts index f68559b404a5c..d217601fcd4b2 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts @@ -200,8 +200,8 @@ export interface BundlingOptions { /** * Force pre-transpilation using TSC before running file through bundling step. * This usually is not required unless you are using new experimental features that - * are only supported by typescript's`tsc` compiler. - * One example of such feature is `experimentalDecorators`. + * are only supported by typescript's `tsc` compiler. + * One example of such feature is `emitDecoratorMetadata`. * * @default false */ diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index 653891488756f..f7d99df231a7a 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -5,7 +5,7 @@ import { Code, Runtime } from '@aws-cdk/aws-lambda'; import { AssetHashType, DockerImage } from '@aws-cdk/core'; import { version as delayVersion } from 'delay/package.json'; import { Bundling } from '../lib/bundling'; -import { EsbuildInstallation } from '../lib/esbuild-installation'; +import { PackageInstallation } from '../lib/package-installation'; import { LogLevel, SourceMapMode } from '../lib/types'; import * as util from '../lib/util'; @@ -13,17 +13,13 @@ jest.mock('@aws-cdk/aws-lambda'); // Mock DockerImage.fromAsset() to avoid building the image let fromBuildMock: jest.SpyInstance; -let detectEsbuildMock: jest.SpyInstance; +let detectPackageInstallationMock: jest.SpyInstance; beforeEach(() => { jest.clearAllMocks(); jest.resetAllMocks(); jest.restoreAllMocks(); Bundling.clearEsbuildInstallationCache(); - - detectEsbuildMock = jest.spyOn(EsbuildInstallation, 'detect').mockReturnValue({ - isLocal: true, - version: '0.8.8', - }); + Bundling.clearTscInstallationCache(); fromBuildMock = jest.spyOn(DockerImage, 'fromBuild').mockReturnValue({ image: 'built-image', @@ -31,6 +27,11 @@ beforeEach(() => { run: () => {}, toJSON: () => 'built-image', }); + + detectPackageInstallationMock = jest.spyOn(PackageInstallation, 'detect').mockReturnValue({ + isLocal: true, + version: '0.8.8', + }); }); let projectRoot = '/project'; @@ -236,7 +237,7 @@ test('esbuild bundling with pre-compilations', () => { command: [ 'bash', '-c', [ - 'npx tsc --project /asset-input/lib/custom-tsconfig.ts --outDir /asset-input/cdk.out/tsc-compile &&', + 'tsc --project /asset-input/lib/custom-tsconfig.ts --outDir /asset-input/cdk.out/tsc-compile &&', 'esbuild --bundle \"/asset-input/cdk.out/tsc-compile/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', ].join(' '), @@ -450,7 +451,7 @@ test('Local bundling', () => { test('Incorrect esbuild version', () => { - detectEsbuildMock.mockReturnValueOnce({ + detectPackageInstallationMock.mockReturnValueOnce({ isLocal: true, version: '3.4.5', }); From 3fed2171bac707de4faadcc94077305f410bfec5 Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Sun, 3 Oct 2021 05:36:26 +0500 Subject: [PATCH 03/15] fix tests --- ...-installation.test.ts => package-installation.test.ts} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename packages/@aws-cdk/aws-lambda-nodejs/test/{esbuild-installation.test.ts => package-installation.test.ts} (84%) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/esbuild-installation.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/package-installation.test.ts similarity index 84% rename from packages/@aws-cdk/aws-lambda-nodejs/test/esbuild-installation.test.ts rename to packages/@aws-cdk/aws-lambda-nodejs/test/package-installation.test.ts index 24b9b512c98bc..e7afddc09fdbb 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/esbuild-installation.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/package-installation.test.ts @@ -1,12 +1,12 @@ import * as child_process from 'child_process'; -import { EsbuildInstallation } from '../lib/esbuild-installation'; +import { PackageInstallation } from '../lib/package-installation'; import * as util from '../lib/util'; // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies const version = require('esbuild/package.json').version; test('detects local version', () => { - expect(EsbuildInstallation.detect()).toEqual({ + expect(PackageInstallation.detect('esbuild')).toEqual({ isLocal: true, version, }); @@ -23,7 +23,7 @@ test('checks global version if local detection fails', () => { signal: null, }); - expect(EsbuildInstallation.detect()).toEqual({ + expect(PackageInstallation.detect('esbuild')).toEqual({ isLocal: false, version: 'global-version', }); @@ -44,7 +44,7 @@ test('returns undefined on error', () => { signal: null, }); - expect(EsbuildInstallation.detect()).toBeUndefined(); + expect(PackageInstallation.detect('esbuild')).toBeUndefined(); spawnSyncMock.mockRestore(); getModuleVersionMock.mockRestore(); From 4780f611f036f42c22c4b80ec8fb71905736dc72 Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Tue, 5 Oct 2021 15:04:36 +0500 Subject: [PATCH 04/15] PR feedback --- .../aws-lambda-nodejs/lib/bundling.ts | 24 ++++++++++--------- .../lib/package-installation.ts | 6 ++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 1ddb26598ee2e..3288837a98e17 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -65,6 +65,7 @@ export class Bundling implements cdk.BundlingOptions { private static esbuildInstallation?: PackageInstallation; private static tscInstallation?: PackageInstallation; + private static tscCompiled = false // Core bundling options public readonly image: cdk.DockerImage; @@ -148,12 +149,17 @@ export class Bundling implements cdk.BundlingOptions { if (!this.relativeTsconfigPath) { throw new Error('preCompilation cannot be used when tsconfig is undefined'); } + relativeEntryPath = pathJoin(PRE_COMPILATION_DIR, relativeEntryPath).replace(/\.ts(x?)$/, '.js$1'); - const tsconfigPath = pathJoin(options.inputDir, this.relativeTsconfigPath); - const rootDir = extractRootDir(tsconfigPath); - process.stderr.write(`Compiling your project using typescript compiler version: ${Bundling.tscInstallation?.version} \n`); - tscCommand =`${options.tscRunner} --project ${tsconfigPath} --outDir ${pathJoin(options.inputDir, PRE_COMPILATION_DIR, rootDir ?? '')}`; + if (!Bundling.tscCompiled) { + const tsconfigPath = pathJoin(options.inputDir, this.relativeTsconfigPath); + const rootDir = extractRootDir(tsconfigPath); + process.stderr.write(`Compiling your project using typescript compiler version: ${Bundling.tscInstallation?.version} \n`); + tscCommand = `${options.tscRunner} --project ${tsconfigPath} --outDir ${pathJoin(options.inputDir, PRE_COMPILATION_DIR, rootDir ?? '')}`; + // Setting this to avoid running tsc on every function + Bundling.tscCompiled = true; + } } @@ -223,11 +229,11 @@ export class Bundling implements cdk.BundlingOptions { private getLocalBundlingProvider(): cdk.ILocalBundling { const osPlatform = os.platform(); - const createLocalCommand = (outputDir: string, esbuild: PackageInstallation, tsc: PackageInstallation) => this.createBundlingCommand({ + const createLocalCommand = (outputDir: string, esbuild: PackageInstallation, tsc?: PackageInstallation) => this.createBundlingCommand({ inputDir: this.projectRoot, outputDir, esbuildRunner: esbuild.isLocal ? this.packageManager.runBinCommand('esbuild') : 'esbuild', - tscRunner: tsc.isLocal ? this.packageManager.runBinCommand('tsc') : 'tsc', + tscRunner: tsc && (tsc.isLocal ? this.packageManager.runBinCommand('tsc') : 'tsc'), osPlatform, }); const environment = this.props.environment ?? {}; @@ -244,10 +250,6 @@ export class Bundling implements cdk.BundlingOptions { throw new Error(`Expected esbuild version ${ESBUILD_MAJOR_VERSION}.x but got ${Bundling.esbuildInstallation.version}`); } - if (!Bundling.tscInstallation) { - throw new Error('Unable to find typescript locally or globally make sure install typescript in you dependencies'); - } - const localCommand = createLocalCommand(outputDir, Bundling.esbuildInstallation, Bundling.tscInstallation); exec( @@ -277,7 +279,7 @@ interface BundlingCommandOptions { readonly inputDir: string; readonly outputDir: string; readonly esbuildRunner: string; - readonly tscRunner: string; + readonly tscRunner?: string; readonly osPlatform: NodeJS.Platform; } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/package-installation.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-installation.ts index af9b1f50fdebf..789246badcceb 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/package-installation.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/package-installation.ts @@ -17,11 +17,11 @@ export abstract class PackageInstallation { } // Fallback to a global version - const esbuild = spawnSync(module, ['--version']); - if (esbuild.status === 0 && !esbuild.error) { + const proc = spawnSync(module, ['--version']); + if (proc.status === 0 && !proc.error) { return { isLocal: false, - version: esbuild.stdout.toString().trim(), + version: proc.stdout.toString().trim(), }; } return undefined; From 46f291f95a89e1a8e157b53a7d33c4e17fa790ed Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Fri, 8 Oct 2021 05:17:21 +0500 Subject: [PATCH 05/15] PR Feedback --- .../aws-lambda-nodejs/lib/bundling.ts | 33 +++-- .../@aws-cdk/aws-lambda-nodejs/lib/util.ts | 13 +- .../aws-lambda-nodejs/test/bundling.test.ts | 130 ++++++++++++------ .../aws-lambda-nodejs/test/util.test.ts | 10 +- 4 files changed, 120 insertions(+), 66 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 3288837a98e17..422a91c01fba6 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -5,10 +5,9 @@ import * as cdk from '@aws-cdk/core'; import { PackageInstallation } from './package-installation'; import { PackageManager } from './package-manager'; import { BundlingOptions, SourceMapMode } from './types'; -import { exec, extractDependencies, extractRootDir, findUp } from './util'; +import { exec, extractDependencies, extractCompilerOptions, findUp } from './util'; const ESBUILD_MAJOR_VERSION = '0'; -const PRE_COMPILATION_DIR = 'cdk.out/tsc-compile'; /** * Bundling properties @@ -63,6 +62,10 @@ export class Bundling implements cdk.BundlingOptions { this.tscInstallation = undefined; } + public static clearTscCompilationCache(): void { + this.tscCompiled = false; + } + private static esbuildInstallation?: PackageInstallation; private static tscInstallation?: PackageInstallation; private static tscCompiled = false @@ -141,24 +144,24 @@ export class Bundling implements cdk.BundlingOptions { private createBundlingCommand(options: BundlingCommandOptions): string { const pathJoin = osPathJoin(options.osPlatform); + const tsconfig = pathJoin(options.inputDir, this.relativeTsconfigPath ?? 'tsconfig.json'); - let tscCommand = ''; + let tscCommand: string[] = []; let relativeEntryPath = this.relativeEntryPath; if (this.props.preCompilation) { - if (!this.relativeTsconfigPath) { - throw new Error('preCompilation cannot be used when tsconfig is undefined'); - } + if (tsconfig) { + const { rootDir = '', outDir = '' } = extractCompilerOptions(tsconfig); + relativeEntryPath = pathJoin(outDir, relativeEntryPath).replace(/\.ts(x?)$/, '.js$1'); - relativeEntryPath = pathJoin(PRE_COMPILATION_DIR, relativeEntryPath).replace(/\.ts(x?)$/, '.js$1'); + if (!Bundling.tscCompiled) { - if (!Bundling.tscCompiled) { - const tsconfigPath = pathJoin(options.inputDir, this.relativeTsconfigPath); - const rootDir = extractRootDir(tsconfigPath); - process.stderr.write(`Compiling your project using typescript compiler version: ${Bundling.tscInstallation?.version} \n`); - tscCommand = `${options.tscRunner} --project ${tsconfigPath} --outDir ${pathJoin(options.inputDir, PRE_COMPILATION_DIR, rootDir ?? '')}`; - // Setting this to avoid running tsc on every function - Bundling.tscCompiled = true; + tscCommand = [ + `${options.tscRunner} --project ${tsconfig}`, + ...outDir ? [`--outDir ${pathJoin(options.inputDir, outDir, rootDir)}`] : [], + ]; + Bundling.tscCompiled = true; + } } } @@ -219,7 +222,7 @@ export class Bundling implements cdk.BundlingOptions { return chain([ ...this.props.commandHooks?.beforeBundling(options.inputDir, options.outputDir) ?? [], - tscCommand, + tscCommand.join(' '), esbuildCommand.join(' '), ...(this.props.nodeModules && this.props.commandHooks?.beforeInstall(options.inputDir, options.outputDir)) ?? [], depsCommand, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts index 8866b828dd7e5..5717456608a56 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts @@ -112,16 +112,17 @@ export function extractDependencies(pkgPath: string, modules: string[]): { [key: } /** - * Extract rootDir from tsConfig. + * Returns compilerOptions from tsconfig.json */ -export function extractRootDir(tsconfigPath: string): string | undefined { +export function extractCompilerOptions(tsconfigPath: string, previous?: { [key: string]: string }): any | undefined { try { // eslint-disable-next-line @typescript-eslint/no-require-imports - const { extends: extendedConfig, compilerOptions: { rootDir = undefined } = {} } = require(tsconfigPath); - if (!rootDir && extendedConfig) { - return extractRootDir(path.resolve(tsconfigPath.replace(/[^\/]+$/, ''), extendedConfig)); + const { extends: extendedConfig, compilerOptions: current } = require(tsconfigPath); + const merged = { ...previous, ...current }; + if (extendedConfig) { + return extractCompilerOptions(path.resolve(tsconfigPath.replace(/[^\/]+$/, ''), extendedConfig), merged); } - return rootDir; + return merged; } catch (err) { return; diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index f7d99df231a7a..2c0c6fdb42e3d 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -219,46 +219,6 @@ test('esbuild bundling with esbuild options', () => { expect(bundleProcess.stdout.toString()).toMatchSnapshot(); }); -test('esbuild bundling with pre-compilations', () => { - Bundling.bundle({ - entry, - projectRoot, - depsLockFilePath, - runtime: Runtime.NODEJS_14_X, - forceDockerBundling: true, - tsconfig, - preCompilation: true, - }); - - // Correctly bundles with esbuild - expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), { - assetHashType: AssetHashType.OUTPUT, - bundling: expect.objectContaining({ - command: [ - 'bash', '-c', - [ - 'tsc --project /asset-input/lib/custom-tsconfig.ts --outDir /asset-input/cdk.out/tsc-compile &&', - 'esbuild --bundle \"/asset-input/cdk.out/tsc-compile/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', - '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', - ].join(' '), - ], - }), - }); -}); - -test('esbuild bundling with pre-compilations without tsconfig', () => { - expect(() => { - Bundling.bundle({ - entry, - projectRoot, - depsLockFilePath, - runtime: Runtime.NODEJS_14_X, - forceDockerBundling: true, - preCompilation: true, - }); - }).toThrow('preCompilation cannot be used when tsconfig is undefined'); -}); - test('esbuild bundling source map default', () => { Bundling.bundle({ entry, @@ -571,3 +531,93 @@ test('esbuild bundling with projectRoot and externals and dependencies', () => { }), }); }); + +test('esbuild bundling with pre compilations', () => { + jest.spyOn(util, 'findUp').mockReturnValueOnce(tsconfig); + jest.spyOn(util, 'extractCompilerOptions').mockReturnValueOnce({}); + + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + forceDockerBundling: true, + tsconfig, + preCompilation: true, + }); + + // Correctly bundles with esbuild + expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + [ + 'tsc --project /asset-input/lib/custom-tsconfig.ts &&', + 'esbuild --bundle \"/asset-input/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', + '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', + ].join(' '), + ], + }), + }); +}); + +test('esbuild bundling with pre compilations ( should skip preCompilation as already compiled )', () => { + jest.spyOn(util, 'findUp').mockReturnValueOnce(tsconfig); + jest.spyOn(util, 'extractCompilerOptions').mockReturnValueOnce({}); + + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + forceDockerBundling: true, + tsconfig, + preCompilation: true, + }); + + // Correctly bundles with esbuild + expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + [ + 'esbuild --bundle \"/asset-input/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', + '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', + ].join(' '), + ], + }), + }); +}); + +test('esbuild bundling with pre compilations and tsc compiler options ', () => { + Bundling.clearTscCompilationCache(); + jest.spyOn(util, 'findUp').mockReturnValueOnce(tsconfig); + jest.spyOn(util, 'extractCompilerOptions').mockReturnValueOnce({ rootDir: 'src', outDir: 'dist' }); + + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + forceDockerBundling: true, + tsconfig, + preCompilation: true, + }); + + // Correctly bundles with esbuild + expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + [ + 'tsc --project /asset-input/lib/custom-tsconfig.ts --outDir /asset-input/dist/src &&', + 'esbuild --bundle \"/asset-input/dist/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', + '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', + ].join(' '), + ], + }), + }); +}); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts index b1bf269a2766a..f4aa1eaee7693 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts @@ -1,6 +1,6 @@ import * as child_process from 'child_process'; import * as path from 'path'; -import { callsites, exec, extractDependencies, extractRootDir, findUp } from '../lib/util'; +import { callsites, exec, extractDependencies, extractCompilerOptions, findUp } from '../lib/util'; beforeEach(() => { jest.clearAllMocks(); @@ -121,10 +121,10 @@ describe('extractDependencies', () => { }); }); -describe('extractRootDir', () => { - test('with rootDir defined in tsconfig.json', () => { - expect(extractRootDir( +describe('tsconfig', () => { + test('extract compiler options from tsconfig.json', () => { + expect(extractCompilerOptions( path.join(__dirname, '../tsconfig.json'), - )).toEqual(undefined); + )).toBeDefined(); }); }); From 4817f6277121d36e395106ca3a29ba910ccb2ced Mon Sep 17 00:00:00 2001 From: Hassan Azhar <57677979+hassanazharkhan@users.noreply.github.com> Date: Fri, 8 Oct 2021 05:25:06 +0500 Subject: [PATCH 06/15] Update README.md --- packages/@aws-cdk/aws-lambda-nodejs/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 15b2ade10d8dd..0722aab4d935a 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -234,13 +234,11 @@ see . new lambda.NodejsFunction(this, 'my-handler', { bundling: { preCompilation: true, - tsconfig: 'tsconfig.json' }, }); ``` `tsconfig` is required to enable `preCompilation` as `tsc` will use your `tsconfig` for transpilation. -Output of the transpilation will be found under `cdk.out/tsc-compile` ## Customizing Docker bundling From 64af4f111a74de200f29c3a3a50dbdadccc7527e Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Tue, 12 Oct 2021 03:03:55 +0500 Subject: [PATCH 07/15] PR Feedback --- .../aws-lambda-nodejs/lib/bundling.ts | 26 +++++----- .../aws-lambda-nodejs/test/bundling.test.ts | 49 +++++++++++++++++-- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 422a91c01fba6..2a270831a0009 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -144,24 +144,26 @@ export class Bundling implements cdk.BundlingOptions { private createBundlingCommand(options: BundlingCommandOptions): string { const pathJoin = osPathJoin(options.osPlatform); - const tsconfig = pathJoin(options.inputDir, this.relativeTsconfigPath ?? 'tsconfig.json'); - let tscCommand: string[] = []; let relativeEntryPath = this.relativeEntryPath; if (this.props.preCompilation) { - if (tsconfig) { - const { rootDir = '', outDir = '' } = extractCompilerOptions(tsconfig); - relativeEntryPath = pathJoin(outDir, relativeEntryPath).replace(/\.ts(x?)$/, '.js$1'); + const tsconfig = this.relativeTsconfigPath ? + pathJoin(options.inputDir, this.relativeTsconfigPath) : + findUp('tsconfig.json', path.dirname(this.props.entry)); - if (!Bundling.tscCompiled) { + if (!tsconfig) { + throw new Error('Unable to find tsconfig, please specify the prop: tsconfig'); + } - tscCommand = [ - `${options.tscRunner} --project ${tsconfig}`, - ...outDir ? [`--outDir ${pathJoin(options.inputDir, outDir, rootDir)}`] : [], - ]; - Bundling.tscCompiled = true; - } + const { rootDir = '', outDir = '' } = extractCompilerOptions(tsconfig); + relativeEntryPath = pathJoin(outDir, this.relativeEntryPath).replace(/\.ts(x?)$/, '.js$1'); + if (!Bundling.tscCompiled) { + tscCommand = [ + ...[`${options.tscRunner} --project ${tsconfig}`], + ...outDir ? [`--outDir ${pathJoin(options.inputDir, outDir, rootDir)}`] : [], + ]; + Bundling.tscCompiled = true; } } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index 2c0c6fdb42e3d..f782538172440 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -593,8 +593,7 @@ test('esbuild bundling with pre compilations ( should skip preCompilation as alr test('esbuild bundling with pre compilations and tsc compiler options ', () => { Bundling.clearTscCompilationCache(); - jest.spyOn(util, 'findUp').mockReturnValueOnce(tsconfig); - jest.spyOn(util, 'extractCompilerOptions').mockReturnValueOnce({ rootDir: 'src', outDir: 'dist' }); + jest.spyOn(util, 'extractCompilerOptions').mockReturnValueOnce({ rootDir: 'lib', outDir: 'dist' }); Bundling.bundle({ entry, @@ -613,7 +612,7 @@ test('esbuild bundling with pre compilations and tsc compiler options ', () => { command: [ 'bash', '-c', [ - 'tsc --project /asset-input/lib/custom-tsconfig.ts --outDir /asset-input/dist/src &&', + 'tsc --project /asset-input/lib/custom-tsconfig.ts --outDir /asset-input/dist/lib &&', 'esbuild --bundle \"/asset-input/dist/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', ].join(' '), @@ -621,3 +620,47 @@ test('esbuild bundling with pre compilations and tsc compiler options ', () => { }), }); }); + +test('esbuild bundling with pre compilations and tsc compiler options different combinations ', () => { + Bundling.clearTscCompilationCache(); + jest.spyOn(util, 'extractCompilerOptions').mockReturnValueOnce({ rootDir: undefined, outDir: 'dist' }); + + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + forceDockerBundling: true, + tsconfig, + preCompilation: true, + }); + + // Correctly bundles with esbuild + expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + command: [ + 'bash', '-c', + [ + 'tsc --project /asset-input/lib/custom-tsconfig.ts --outDir /asset-input/dist &&', + 'esbuild --bundle \"/asset-input/dist/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', + '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', + ].join(' '), + ], + }), + }); +}); + +test('esbuild bundling with pre compilations and undefined tsconfig', () => { + expect(() => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + forceDockerBundling: true, + preCompilation: true, + }); + }).toThrow('Unable to find tsconfig, please specify the prop: tsconfig'); + +}); \ No newline at end of file From 34001bf4bcb3914bcccd9794b8bc94472b6e4a4a Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Tue, 12 Oct 2021 14:43:00 +0500 Subject: [PATCH 08/15] fix test cases! --- packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index 4e6a376e37038..3c6d1b2b1e288 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -11,8 +11,6 @@ import * as util from '../lib/util'; jest.mock('@aws-cdk/aws-lambda'); -// Mock DockerImage.fromAsset() to avoid building the image -let fromBuildMock: jest.SpyInstance; let detectPackageInstallationMock: jest.SpyInstance; beforeEach(() => { jest.clearAllMocks(); @@ -570,6 +568,7 @@ test('esbuild bundling with pre compilations', () => { forceDockerBundling: true, tsconfig, preCompilation: true, + architecture: Architecture.X86_64, }); // Correctly bundles with esbuild @@ -600,6 +599,7 @@ test('esbuild bundling with pre compilations ( should skip preCompilation as alr forceDockerBundling: true, tsconfig, preCompilation: true, + architecture: Architecture.X86_64, }); // Correctly bundles with esbuild @@ -629,6 +629,7 @@ test('esbuild bundling with pre compilations and tsc compiler options ', () => { forceDockerBundling: true, tsconfig, preCompilation: true, + architecture: Architecture.X86_64, }); // Correctly bundles with esbuild @@ -659,6 +660,7 @@ test('esbuild bundling with pre compilations and tsc compiler options different forceDockerBundling: true, tsconfig, preCompilation: true, + architecture: Architecture.X86_64, }); // Correctly bundles with esbuild @@ -686,6 +688,7 @@ test('esbuild bundling with pre compilations and undefined tsconfig', () => { runtime: Runtime.NODEJS_14_X, forceDockerBundling: true, preCompilation: true, + architecture: Architecture.X86_64, }); }).toThrow('Unable to find tsconfig, please specify the prop: tsconfig'); From 243fe093f7455cd5ae19b839ae8c80035ddaba01 Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Wed, 13 Oct 2021 20:38:41 +0500 Subject: [PATCH 09/15] fix preCompilation! --- .../aws-lambda-nodejs/lib/bundling.ts | 10 ++-- .../aws-lambda-nodejs/test/bundling.test.ts | 54 +++---------------- 2 files changed, 13 insertions(+), 51 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 36d262ca57963..d42304cd77d0d 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -5,7 +5,7 @@ import * as cdk from '@aws-cdk/core'; import { PackageInstallation } from './package-installation'; import { PackageManager } from './package-manager'; import { BundlingOptions, SourceMapMode } from './types'; -import { exec, extractDependencies, extractCompilerOptions, findUp } from './util'; +import { exec, extractDependencies, findUp } from './util'; const ESBUILD_MAJOR_VERSION = '0'; @@ -162,12 +162,12 @@ export class Bundling implements cdk.BundlingOptions { throw new Error('Unable to find tsconfig, please specify the prop: tsconfig'); } - const { rootDir = '', outDir = '' } = extractCompilerOptions(tsconfig); - relativeEntryPath = pathJoin(outDir, this.relativeEntryPath).replace(/\.ts(x?)$/, '.js$1'); + + relativeEntryPath = relativeEntryPath.replace(/\.ts(x?)$/, '.js$1'); if (!Bundling.tscCompiled) { tscCommand = [ - ...[`${options.tscRunner} --project ${tsconfig}`], - ...outDir ? [`--outDir ${pathJoin(options.inputDir, outDir, rootDir)}`] : [], + // Setting explicity rootDir and outDir, so that the compiled js file always end up next ts file. + ...[`${options.tscRunner} --project ${tsconfig} --rootDir ./ --outDir ./`], ]; Bundling.tscCompiled = true; } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index 3c6d1b2b1e288..fbe005e502c63 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -557,9 +557,6 @@ test('esbuild bundling with projectRoot and externals and dependencies', () => { }); test('esbuild bundling with pre compilations', () => { - jest.spyOn(util, 'findUp').mockReturnValueOnce(tsconfig); - jest.spyOn(util, 'extractCompilerOptions').mockReturnValueOnce({}); - Bundling.bundle({ entry, projectRoot, @@ -578,7 +575,7 @@ test('esbuild bundling with pre compilations', () => { command: [ 'bash', '-c', [ - 'tsc --project /asset-input/lib/custom-tsconfig.ts &&', + 'tsc --project /asset-input/lib/custom-tsconfig.ts --rootDir ./ --outDir ./ &&', 'esbuild --bundle \"/asset-input/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', ].join(' '), @@ -587,10 +584,7 @@ test('esbuild bundling with pre compilations', () => { }); }); -test('esbuild bundling with pre compilations ( should skip preCompilation as already compiled )', () => { - jest.spyOn(util, 'findUp').mockReturnValueOnce(tsconfig); - jest.spyOn(util, 'extractCompilerOptions').mockReturnValueOnce({}); - +test('esbuild bundling with pre compilations ( Should skip preCompilation as already compiled )', () => { Bundling.bundle({ entry, projectRoot, @@ -617,40 +611,9 @@ test('esbuild bundling with pre compilations ( should skip preCompilation as alr }); }); -test('esbuild bundling with pre compilations and tsc compiler options ', () => { - Bundling.clearTscCompilationCache(); - jest.spyOn(util, 'extractCompilerOptions').mockReturnValueOnce({ rootDir: 'lib', outDir: 'dist' }); - - Bundling.bundle({ - entry, - projectRoot, - depsLockFilePath, - runtime: Runtime.NODEJS_14_X, - forceDockerBundling: true, - tsconfig, - preCompilation: true, - architecture: Architecture.X86_64, - }); - - // Correctly bundles with esbuild - expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), { - assetHashType: AssetHashType.OUTPUT, - bundling: expect.objectContaining({ - command: [ - 'bash', '-c', - [ - 'tsc --project /asset-input/lib/custom-tsconfig.ts --outDir /asset-input/dist/lib &&', - 'esbuild --bundle \"/asset-input/dist/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', - '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', - ].join(' '), - ], - }), - }); -}); - -test('esbuild bundling with pre compilations and tsc compiler options different combinations ', () => { +test('esbuild bundling with pre compilations with undefined tsconfig ( Should find in root directory )', () => { Bundling.clearTscCompilationCache(); - jest.spyOn(util, 'extractCompilerOptions').mockReturnValueOnce({ rootDir: undefined, outDir: 'dist' }); + jest.spyOn(util, 'findUp').mockReturnValueOnce('/asset-input/lib/custom-tsconfig.ts'); Bundling.bundle({ entry, @@ -658,7 +621,6 @@ test('esbuild bundling with pre compilations and tsc compiler options different depsLockFilePath, runtime: Runtime.NODEJS_14_X, forceDockerBundling: true, - tsconfig, preCompilation: true, architecture: Architecture.X86_64, }); @@ -670,16 +632,16 @@ test('esbuild bundling with pre compilations and tsc compiler options different command: [ 'bash', '-c', [ - 'tsc --project /asset-input/lib/custom-tsconfig.ts --outDir /asset-input/dist &&', - 'esbuild --bundle \"/asset-input/dist/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', - '--external:aws-sdk --tsconfig=/asset-input/lib/custom-tsconfig.ts', + 'tsc --project /asset-input/lib/custom-tsconfig.ts --rootDir ./ --outDir ./ &&', + 'esbuild --bundle \"/asset-input/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', + '--external:aws-sdk', ].join(' '), ], }), }); }); -test('esbuild bundling with pre compilations and undefined tsconfig', () => { +test('esbuild bundling with pre compilations and undefined tsconfig ( Should throw) ', () => { expect(() => { Bundling.bundle({ entry, From 9ca84b2c1ae40b221228446bc4d5159687b9d7fd Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Wed, 13 Oct 2021 20:44:15 +0500 Subject: [PATCH 10/15] fix comment --- packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index d42304cd77d0d..cdb6eccc7f191 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -166,7 +166,7 @@ export class Bundling implements cdk.BundlingOptions { relativeEntryPath = relativeEntryPath.replace(/\.ts(x?)$/, '.js$1'); if (!Bundling.tscCompiled) { tscCommand = [ - // Setting explicity rootDir and outDir, so that the compiled js file always end up next ts file. + // Intentionally Setting rootDir and outDir, so that the compiled js file always end up next ts file. ...[`${options.tscRunner} --project ${tsconfig} --rootDir ./ --outDir ./`], ]; Bundling.tscCompiled = true; From f6484dcef4c55ab3d85cd8386f7a476088d3726d Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Wed, 13 Oct 2021 20:52:09 +0500 Subject: [PATCH 11/15] Cleanup! --- .../@aws-cdk/aws-lambda-nodejs/lib/bundling.ts | 2 -- packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts | 17 ----------------- .../aws-lambda-nodejs/test/util.test.ts | 9 +-------- 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index cdb6eccc7f191..a62b30aaf28af 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -162,7 +162,6 @@ export class Bundling implements cdk.BundlingOptions { throw new Error('Unable to find tsconfig, please specify the prop: tsconfig'); } - relativeEntryPath = relativeEntryPath.replace(/\.ts(x?)$/, '.js$1'); if (!Bundling.tscCompiled) { tscCommand = [ @@ -173,7 +172,6 @@ export class Bundling implements cdk.BundlingOptions { } } - const loaders = Object.entries(this.props.loader ?? {}); const defines = Object.entries(this.props.define ?? {}); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts index 5717456608a56..bb59af6c34eaa 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts @@ -111,20 +111,3 @@ export function extractDependencies(pkgPath: string, modules: string[]): { [key: return dependencies; } -/** - * Returns compilerOptions from tsconfig.json - */ -export function extractCompilerOptions(tsconfigPath: string, previous?: { [key: string]: string }): any | undefined { - try { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const { extends: extendedConfig, compilerOptions: current } = require(tsconfigPath); - const merged = { ...previous, ...current }; - if (extendedConfig) { - return extractCompilerOptions(path.resolve(tsconfigPath.replace(/[^\/]+$/, ''), extendedConfig), merged); - } - return merged; - - } catch (err) { - return; - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts index f4aa1eaee7693..af627c8f102a7 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts @@ -1,6 +1,6 @@ import * as child_process from 'child_process'; import * as path from 'path'; -import { callsites, exec, extractDependencies, extractCompilerOptions, findUp } from '../lib/util'; +import { callsites, exec, extractDependencies, findUp } from '../lib/util'; beforeEach(() => { jest.clearAllMocks(); @@ -121,10 +121,3 @@ describe('extractDependencies', () => { }); }); -describe('tsconfig', () => { - test('extract compiler options from tsconfig.json', () => { - expect(extractCompilerOptions( - path.join(__dirname, '../tsconfig.json'), - )).toBeDefined(); - }); -}); From dc51d9ce1a293eff1822f79a7d217a8d8e9b4f4c Mon Sep 17 00:00:00 2001 From: Hassan Azhar <57677979+hassanazharkhan@users.noreply.github.com> Date: Wed, 13 Oct 2021 20:53:15 +0500 Subject: [PATCH 12/15] Update util.test.ts --- packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts index af627c8f102a7..4962ed203b31f 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts @@ -120,4 +120,3 @@ describe('extractDependencies', () => { )).toThrow(/Cannot extract version for module 'unknown'/); }); }); - From a5eaef2374f9fa40f44db1c43fd1ae0b45d0a676 Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Fri, 15 Oct 2021 18:28:19 +0500 Subject: [PATCH 13/15] Integ test --- .../aws-lambda-nodejs/lib/bundling.ts | 20 +- .../@aws-cdk/aws-lambda-nodejs/lib/util.ts | 1 - .../aws-lambda-nodejs/test/bundling.test.ts | 16 +- .../integ-handlers/ts-decorator-handler.ts | 23 ++ .../test/integ.compilations.expected.json | 198 ++++++++++++++++++ .../test/integ.compilations.ts | 39 ++++ 6 files changed, 278 insertions(+), 19 deletions(-) create mode 100644 packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/ts-decorator-handler.ts create mode 100644 packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.expected.json create mode 100644 packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.ts diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index a62b30aaf28af..e2d84e3f3e262 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -150,24 +150,24 @@ export class Bundling implements cdk.BundlingOptions { private createBundlingCommand(options: BundlingCommandOptions): string { const pathJoin = osPathJoin(options.osPlatform); - let tscCommand: string[] = []; + let tscCommand: string = ''; let relativeEntryPath = this.relativeEntryPath; if (this.props.preCompilation) { - const tsconfig = this.relativeTsconfigPath ? - pathJoin(options.inputDir, this.relativeTsconfigPath) : - findUp('tsconfig.json', path.dirname(this.props.entry)); + let tsconfig = this.relativeTsconfigPath; if (!tsconfig) { - throw new Error('Unable to find tsconfig, please specify the prop: tsconfig'); + const findConfig = findUp('tsconfig.json', path.dirname(this.props.entry)); + if (!findConfig) { + throw new Error('Cannot find a tsconfig.json, please specify the prop: tsconfig'); + } + tsconfig = path.relative(this.projectRoot, findConfig); } relativeEntryPath = relativeEntryPath.replace(/\.ts(x?)$/, '.js$1'); if (!Bundling.tscCompiled) { - tscCommand = [ - // Intentionally Setting rootDir and outDir, so that the compiled js file always end up next ts file. - ...[`${options.tscRunner} --project ${tsconfig} --rootDir ./ --outDir ./`], - ]; + // Intentionally Setting rootDir and outDir, so that the compiled js file always end up next ts file. + tscCommand = `${options.tscRunner} --project ${pathJoin(options.inputDir, tsconfig)} --rootDir ./ --outDir ./`; Bundling.tscCompiled = true; } } @@ -228,7 +228,7 @@ export class Bundling implements cdk.BundlingOptions { return chain([ ...this.props.commandHooks?.beforeBundling(options.inputDir, options.outputDir) ?? [], - tscCommand.join(' '), + tscCommand, esbuildCommand.join(' '), ...(this.props.nodeModules && this.props.commandHooks?.beforeInstall(options.inputDir, options.outputDir)) ?? [], depsCommand, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts index bb59af6c34eaa..5ead91793e93b 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts @@ -110,4 +110,3 @@ export function extractDependencies(pkgPath: string, modules: string[]): { [key: return dependencies; } - diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index fbe005e502c63..fe6ce1de29786 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -613,12 +613,12 @@ test('esbuild bundling with pre compilations ( Should skip preCompilation as alr test('esbuild bundling with pre compilations with undefined tsconfig ( Should find in root directory )', () => { Bundling.clearTscCompilationCache(); - jest.spyOn(util, 'findUp').mockReturnValueOnce('/asset-input/lib/custom-tsconfig.ts'); + const packageLock = path.join(__dirname, '..', 'package-lock.json'); Bundling.bundle({ - entry, - projectRoot, - depsLockFilePath, + entry: __filename.replace('.js', '.ts'), + projectRoot: path.dirname(packageLock), + depsLockFilePath: packageLock, runtime: Runtime.NODEJS_14_X, forceDockerBundling: true, preCompilation: true, @@ -626,14 +626,14 @@ test('esbuild bundling with pre compilations with undefined tsconfig ( Should fi }); // Correctly bundles with esbuild - expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(depsLockFilePath), { + expect(Code.fromAsset).toHaveBeenCalledWith(path.dirname(packageLock), { assetHashType: AssetHashType.OUTPUT, bundling: expect.objectContaining({ command: [ 'bash', '-c', [ - 'tsc --project /asset-input/lib/custom-tsconfig.ts --rootDir ./ --outDir ./ &&', - 'esbuild --bundle \"/asset-input/lib/handler.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', + 'tsc --project /asset-input/tsconfig.json --rootDir ./ --outDir ./ &&', + 'esbuild --bundle \"/asset-input/test/bundling.test.js\" --target=node14 --platform=node --outfile=\"/asset-output/index.js\"', '--external:aws-sdk', ].join(' '), ], @@ -652,6 +652,6 @@ test('esbuild bundling with pre compilations and undefined tsconfig ( Should thr preCompilation: true, architecture: Architecture.X86_64, }); - }).toThrow('Unable to find tsconfig, please specify the prop: tsconfig'); + }).toThrow('Cannot find a tsconfig.json, please specify the prop: tsconfig'); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/ts-decorator-handler.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/ts-decorator-handler.ts new file mode 100644 index 0000000000000..99ca5ee8ceec1 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ-handlers/ts-decorator-handler.ts @@ -0,0 +1,23 @@ +function enumerable(value: boolean) { + return function (_target: any, _propertyKey: string, descriptor: PropertyDescriptor) { + descriptor.enumerable = value; + }; +} + +class Greeter { + greeting: string; + constructor(message: string) { + this.greeting = message; + } + + @enumerable(false) + greet() { + return 'Hello, ' + this.greeting; + } +} + + +export async function handler(): Promise { + const message = new Greeter('World').greet(); + console.log(message); // eslint-disable-line no-console +} diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.expected.json b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.expected.json new file mode 100644 index 0000000000000..de56ccb33b617 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.expected.json @@ -0,0 +1,198 @@ +{ + "Resources": { + "tsdecoratorhandlerServiceRole61E9E52C": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "tsdecoratorhandlerC8E2F076": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3Bucket2B516B38" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3VersionKey4B530CD7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3VersionKey4B530CD7" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "tsdecoratorhandlerServiceRole61E9E52C", + "Arn" + ] + }, + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + } + }, + "Handler": "index.handler", + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "tsdecoratorhandlerServiceRole61E9E52C" + ] + }, + "tsdecoratorhandlertsconfigServiceRoleC4AE481E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "tsdecoratorhandlertsconfig68EC191E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3Bucket2B516B38" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3VersionKey4B530CD7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3VersionKey4B530CD7" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "tsdecoratorhandlertsconfigServiceRoleC4AE481E", + "Arn" + ] + }, + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + } + }, + "Handler": "index.handler", + "Runtime": "nodejs14.x" + }, + "DependsOn": [ + "tsdecoratorhandlertsconfigServiceRoleC4AE481E" + ] + } + }, + "Parameters": { + "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3Bucket2B516B38": { + "Type": "String", + "Description": "S3 bucket for asset \"482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283f\"" + }, + "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fS3VersionKey4B530CD7": { + "Type": "String", + "Description": "S3 key for asset version \"482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283f\"" + }, + "AssetParameters482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283fArtifactHash237F55B4": { + "Type": "String", + "Description": "Artifact hash for asset \"482454f5e1d850303130cf1cdbee1376fca84deaf9ccfa2c4cf8a246d415283f\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.ts new file mode 100644 index 0000000000000..2c78bde18bbf7 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/integ.compilations.ts @@ -0,0 +1,39 @@ +/// !cdk-integ pragma:ignore-assets +import * as path from 'path'; +import { Runtime } from '@aws-cdk/aws-lambda'; +import { App, Stack, StackProps } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as lambda from '../lib'; + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + new lambda.NodejsFunction(this, 'ts-decorator-handler', { + entry: path.join(__dirname, 'integ-handlers/ts-decorator-handler.ts'), + bundling: { + minify: true, + sourceMap: true, + sourceMapMode: lambda.SourceMapMode.BOTH, + preCompilation: true, + }, + runtime: Runtime.NODEJS_14_X, + }); + + new lambda.NodejsFunction(this, 'ts-decorator-handler-tsconfig', { + entry: path.join(__dirname, 'integ-handlers/ts-decorator-handler.ts'), + bundling: { + minify: true, + sourceMap: true, + sourceMapMode: lambda.SourceMapMode.BOTH, + tsconfig: path.join(__dirname, '..', 'tsconfig.json'), + preCompilation: true, + }, + runtime: Runtime.NODEJS_14_X, + }); + } +} + +const app = new App(); +new TestStack(app, 'cdk-integ-compilations-lambda-nodejs'); +app.synth(); From b1cdca6769ded1d601ede06a413094bc13a084c1 Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Sat, 16 Oct 2021 16:47:24 +0500 Subject: [PATCH 14/15] PR feedback! --- .../aws-lambda-nodejs/test/bundling.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index fe6ce1de29786..02b6030ae3e12 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -9,7 +9,6 @@ import { PackageInstallation } from '../lib/package-installation'; import { LogLevel, SourceMapMode } from '../lib/types'; import * as util from '../lib/util'; -jest.mock('@aws-cdk/aws-lambda'); let detectPackageInstallationMock: jest.SpyInstance; beforeEach(() => { @@ -19,17 +18,19 @@ beforeEach(() => { Bundling.clearEsbuildInstallationCache(); Bundling.clearTscInstallationCache(); + jest.spyOn(Code, 'fromAsset'); + + detectPackageInstallationMock = jest.spyOn(PackageInstallation, 'detect').mockReturnValue({ + isLocal: true, + version: '0.8.8', + }); + jest.spyOn(DockerImage, 'fromBuild').mockReturnValue({ image: 'built-image', cp: () => 'dest-path', run: () => {}, toJSON: () => 'built-image', }); - - detectPackageInstallationMock = jest.spyOn(PackageInstallation, 'detect').mockReturnValue({ - isLocal: true, - version: '0.8.8', - }); }); let projectRoot = '/project'; @@ -582,9 +583,7 @@ test('esbuild bundling with pre compilations', () => { ], }), }); -}); -test('esbuild bundling with pre compilations ( Should skip preCompilation as already compiled )', () => { Bundling.bundle({ entry, projectRoot, @@ -609,6 +608,7 @@ test('esbuild bundling with pre compilations ( Should skip preCompilation as alr ], }), }); + }); test('esbuild bundling with pre compilations with undefined tsconfig ( Should find in root directory )', () => { From 3d2535b36112302942a1603fb2bb3a3f841337e7 Mon Sep 17 00:00:00 2001 From: Hassan Azhar Khan Date: Tue, 26 Oct 2021 02:47:30 +0500 Subject: [PATCH 15/15] PR feedback! --- packages/@aws-cdk/aws-lambda-nodejs/README.md | 8 ++++---- packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts | 2 +- packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index f82e72e07443e..6e901612539d8 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -227,9 +227,9 @@ container for Docker bundling or on the host OS for local bundling. ## Pre Compilation with TSC -It is possible to run compilation using typescript compiler before bundling, -this is for adding support of `emitDecoratorMetadata` which `esbuild` doesn't support it natively. -see . +In some cases, `esbuild` may not yet support some newer features of the typescript language, such as, +[`emitDecoratorMetadata`](https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata). +In such cases, it is possible to run pre-compilation using `tsc` by setting the `preCompilation` flag. ```ts new lambda.NodejsFunction(this, 'my-handler', { @@ -239,7 +239,7 @@ new lambda.NodejsFunction(this, 'my-handler', { }); ``` -`tsconfig` is required to enable `preCompilation` as `tsc` will use your `tsconfig` for transpilation. +Note: A [`tsconfig.json` file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) is required ## Customizing Docker bundling diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index e2d84e3f3e262..e53e5e092de7c 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -39,7 +39,7 @@ export interface BundlingProps extends BundlingOptions { readonly projectRoot: string; /** - * Force pre-transpilation using TSC before bundling + * Run compilation using `tsc` before bundling */ readonly preCompilation?: boolean diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts index d217601fcd4b2..6bd26c846c6fe 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts @@ -198,7 +198,7 @@ export interface BundlingOptions { readonly forceDockerBundling?: boolean; /** - * Force pre-transpilation using TSC before running file through bundling step. + * Run compilation using tsc before running file through bundling step. * This usually is not required unless you are using new experimental features that * are only supported by typescript's `tsc` compiler. * One example of such feature is `emitDecoratorMetadata`.