Skip to content

Commit

Permalink
feat: support copyDir prop to copy additional files into the output
Browse files Browse the repository at this point in the history
  • Loading branch information
mrgrain committed Mar 13, 2021
1 parent e41757b commit 1dccb25
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 62 deletions.
13 changes: 12 additions & 1 deletion esbuild/esbuild-js
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
#!/usr/bin/env node
const { execSync } = require("child_process");
const { resolve } = require("path");
const esbuild = require("esbuild");

try {
const options = JSON.parse(process.argv[2]);
const { copyDir, outputDirectory, options } = JSON.parse(process.argv[2]);

if (copyDir) {
console.log(`Copy additional files from '${copyDir}'`);
const srcDir = resolve(copyDir) + '/.';
const copyCommand = `cp -r ${srcDir} ${outputDirectory}`;
console.log(copyCommand);
execSync(copyCommand)
}

console.log("Bundling with the following options...", options);
esbuild.buildSync(options);
} catch (error) {
Expand Down
23 changes: 18 additions & 5 deletions lib/asset.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Asset as S3Asset } from "@aws-cdk/aws-s3-assets";
import { AssetHashType, Construct, ConstructNode, IAsset } from "@aws-cdk/core";
import { isAbsolute } from "path";
import { BuildOptions, EsbuildBundling } from "./bundling";
import { BuildOptions } from "./bundlers";
import { EsbuildBundling } from "./bundling";
import { findProjectRoot } from "./util";
export interface EsbuildAssetProps extends Partial<IAsset> {
/**
Expand All @@ -26,6 +27,13 @@ export interface EsbuildAssetProps extends Partial<IAsset> {
*/
projectRoot?: string;

/**
* Relative path to a directory copied to the output BEFORE esbuild is run (i.e esbuild will overwrite existing files).
*
* @experimental Likely to change once esbuild supports this natively
*/
copyDir?: string;

/**
* Force the asset to use Docker bundling (and skip local bundling).
*/
Expand Down Expand Up @@ -76,10 +84,15 @@ abstract class Asset<Props extends EsbuildAssetProps> extends S3Asset {
assetHash,
assetHashType: assetHash ? AssetHashType.CUSTOM : AssetHashType.OUTPUT,
bundling: new EsbuildBundling(
projectRoot,
entryPoints,
buildOptions,
!forceDockerBundling
{
...buildOptions,
entryPoints,
absWorkingDir: projectRoot,
},
{
localBundling: !forceDockerBundling,
copyDir: props.copyDir,
}
),
});
}
Expand Down
70 changes: 49 additions & 21 deletions lib/bundlers.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,41 @@
import { BundlingOptions, DockerImage, ILocalBundling } from "@aws-cdk/core";
import { BuildOptions, buildSync } from "esbuild";
import { join } from "path";
import {
BundlingOptions,
DockerImage,
FileSystem,
ILocalBundling,
} from "@aws-cdk/core";
import { buildSync, BuildOptions as EsbuildOptions } from "esbuild";
import { join, resolve } from "path";
import { esbuildVersion, findUp } from "./util";

export type BuildOptions = Omit<EsbuildOptions, "outfile" | "entryPoints"> &
Required<Pick<EsbuildOptions, "entryPoints">>;

export interface BundlerProps {
copyDir?: string;

esbuildVersion?: string;
}

export class LocalBundler implements ILocalBundling {
public constructor(public readonly options: BuildOptions) {}
public constructor(
public readonly buildOptions: BuildOptions,
public readonly props: BundlerProps = {}
) {}

tryBundle(outputDir: string, _options: BundlingOptions): boolean {
try {
const outdir = join(
...([outputDir, this.options.outdir].filter(Boolean) as string[])
if (this.props.copyDir) {
FileSystem.copyDirectory(this.props.copyDir, outputDir);
}

const bundleOutdir = join(
...([outputDir, this.buildOptions.outdir].filter(Boolean) as string[])
);

buildSync({
...this.options,
outdir,
...this.buildOptions,
outdir: bundleOutdir,
});

return true;
Expand All @@ -24,36 +46,42 @@ export class LocalBundler implements ILocalBundling {
}
}

const getEsbuildVersion = (): string => {
return (
esbuildVersion(findUp("package-lock.json"), null) ??
esbuildVersion(findUp("package.json"), null) ??
esbuildVersion()
);
};

export class DockerBundler implements BundlingOptions {
public get image(): DockerImage {
return DockerImage.fromBuild(join(__dirname, "..", "esbuild"), {
return DockerImage.fromBuild(resolve(__dirname, "..", "esbuild"), {
buildArgs: {
version: getEsbuildVersion(),
version: this.props.esbuildVersion ?? "*",
},
});
}

public get command(): string[] {
return [JSON.stringify(this.options)];
return [
JSON.stringify({
...this.props,
outputDirectory: this.outputDirectory,
options: this.options,
}),
];
}

public readonly workingDirectory = "/asset-input";

public readonly outputDirectory = "/asset-output";

public readonly options: BuildOptions;

public constructor(options: BuildOptions) {
const outdir = ["/asset-output", options.outdir].filter(Boolean).join("/");
public constructor(
options: BuildOptions,
public readonly props: BundlerProps = {}
) {
const outdir = [this.outputDirectory, options.outdir]
.filter(Boolean)
.join("/");

this.options = {
...options,
absWorkingDir: this.workingDirectory,
outdir,
};
}
Expand Down
63 changes: 45 additions & 18 deletions lib/bundling.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,58 @@
import { BundlingOptions } from "@aws-cdk/core";
import { BuildOptions as EsbuildBuildOptions } from "esbuild";
import { DockerBundler, LocalBundler } from "./bundlers";
import { getAbsolutePath } from "./util";
import { join, resolve } from "path";
import {
BuildOptions,
BundlerProps,
DockerBundler,
LocalBundler,
} from "./bundlers";
import { esbuildVersion, getAbsolutePath } from "./util";

export type BuildOptions = Omit<EsbuildBuildOptions, "outfile">;
interface BundlingProps extends BundlerProps {
/**
* Use local bundling over Docker bundling.
*
* @default true
*/
localBundling?: boolean;
}

const getEsbuildVersion = (projectRoot: string): string => {
return (
esbuildVersion(join(projectRoot, "package-lock.json"), null) ??
esbuildVersion(join(projectRoot, "package.json"), null) ??
esbuildVersion(resolve(__dirname, "..", "package.json"))
);
};

export class EsbuildBundling extends DockerBundler implements BundlingOptions {
public readonly local?: LocalBundler;

public constructor(
projectRoot: string,
entryPoints: string[],
options: BuildOptions,
tryLocalBundling = true
buildOptions: BuildOptions,
{ localBundling = true, copyDir, esbuildVersion }: BundlingProps
) {
super({
...options,
entryPoints,
const absWorkingDir = buildOptions.absWorkingDir ?? process.cwd();

super(buildOptions, {
copyDir,
esbuildVersion: esbuildVersion ?? getEsbuildVersion(absWorkingDir),
});

if (tryLocalBundling) {
this.local = new LocalBundler({
...options,
entryPoints: entryPoints.map((entryPoint) =>
getAbsolutePath(projectRoot, entryPoint)
),
});
if (localBundling) {
this.local = new LocalBundler(
{
...buildOptions,
entryPoints: this.options.entryPoints.map((entryPoint: string) =>
getAbsolutePath(absWorkingDir, entryPoint)
),
},
{
copyDir: copyDir
? getAbsolutePath(absWorkingDir, copyDir)
: undefined,
}
);
}
}
}
4 changes: 2 additions & 2 deletions lib/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import {
ResourceBindOptions,
} from "@aws-cdk/aws-lambda";
import { CfnResource, Construct, Stack } from "@aws-cdk/core";
import { BuildOptions } from "./bundling";
import { nodeMajorVersion } from "./util";
import {
EsbuildAssetProps,
JavaScriptAsset as JSAsset,
TypeScriptAsset as TSAsset,
} from "./asset";
import { BuildOptions } from "./bundlers";

type CodeProps = Omit<EsbuildAssetProps, "entryPoints">;

Expand Down Expand Up @@ -41,7 +41,7 @@ abstract class Code<
constructor(entryPoints: string | string[], props: Props) {
super();

const defaultOptions: BuildOptions = {
const defaultOptions: Partial<BuildOptions> = {
...(!props.buildOptions?.platform ||
props.buildOptions?.platform === "node"
? { platform: "node", target: "node" + nodeMajorVersion() }
Expand Down
4 changes: 2 additions & 2 deletions lib/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from "@aws-cdk/aws-s3-deployment";
import { Construct, Stack } from "@aws-cdk/core";
import { EsbuildAssetProps, JavaScriptAsset, TypeScriptAsset } from "./asset";
import { BuildOptions } from "./bundling";
import { BuildOptions } from "./bundlers";

type SourceProps = Omit<EsbuildAssetProps, "entryPoints">;

Expand All @@ -32,7 +32,7 @@ abstract class Source<
* @param props - Source properties.
*/
constructor(entryPoints: string | string[], props: Props) {
const defaultOptions: BuildOptions = {
const defaultOptions: Partial<BuildOptions> = {
platform: "browser",
...(!props.buildOptions?.define
? {
Expand Down
6 changes: 3 additions & 3 deletions lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ export function findProjectRoot(
* Returns the version of esbuild installation
*/
export function esbuildVersion<T>(
packageJsonPath = resolve(join(__dirname, "..", "package.json")),
packageJsonPath: string,
defaultVersion: string | T = "*"
): string | T {
const contents = readFileSync(packageJsonPath).toString();

try {
const contents = readFileSync(packageJsonPath).toString();

const pkg: {
dependencies?: { esbuild?: string & { version?: string } };
} = JSON.parse(contents);
Expand Down
22 changes: 12 additions & 10 deletions test/bundling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,31 @@ jest.mock("esbuild", () => ({
describe("Bundling", () => {
describe("Given a project root path", () => {
it("should append the entry path to the project root for the local bundler", () => {
const bundler = new EsbuildBundling("/project", ["index.ts"], {}, true);
expect(bundler.local?.options?.entryPoints).toContain(
const bundler = new EsbuildBundling(
{ absWorkingDir: "/project", entryPoints: ["index.ts"] },
{ localBundling: true }
);
expect(bundler.local?.buildOptions?.entryPoints).toContain(
"/project/index.ts"
);
});

it("should keep the relative entry path for the docker bundler", () => {
const bundler = new EsbuildBundling("/project", ["index.ts"], {}, true);
const bundler = new EsbuildBundling(
{ absWorkingDir: "/project", entryPoints: ["index.ts"] },
{ localBundling: true }
);
expect(bundler?.options?.entryPoints).toContain("index.ts");
});
});

describe("Given an outdir", () => {
it("should append outdir behind the cdk asset directory", () => {
const bundler = new EsbuildBundling(
"/project",
["index.ts"],
{
outdir: "js",
},
true
{ absWorkingDir: "/project", entryPoints: ["index.ts"], outdir: "js" },
{ localBundling: true }
);
// expect(bundler.local?.options?.outdir).toMatch(/cdk.out\/.*\/js$/);

expect(bundler.options?.outdir).toBe("/asset-output/js");

bundler.local?.tryBundle("cdk.out/123456", bundler);
Expand Down

0 comments on commit 1dccb25

Please sign in to comment.