Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/chilled-frogs-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/aws": minor
---

Add a new option to install native dependencies on every lambda
6 changes: 6 additions & 0 deletions packages/open-next/src/build/compileTagCacheProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from "node:path";

import { openNextResolvePlugin } from "../plugins/resolve.js";
import * as buildHelper from "./helper.js";
import { installDependencies } from "./installDeps.js";

export async function compileTagCacheProvider(
options: buildHelper.BuildOptions,
Expand Down Expand Up @@ -30,4 +31,9 @@ export async function compileTagCacheProvider(
},
options,
);

installDependencies(
providerPath,
options.config.initializationFunction?.install,
);
}
33 changes: 8 additions & 25 deletions packages/open-next/src/build/createImageOptimizationBundle.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import cp from "node:child_process";
import fs from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";
Expand All @@ -7,6 +6,7 @@ import logger from "../logger.js";
import { openNextReplacementPlugin } from "../plugins/replacement.js";
import { openNextResolvePlugin } from "../plugins/resolve.js";
import * as buildHelper from "./helper.js";
import { installDependencies } from "./installDeps.js";

const require = createRequire(import.meta.url);

Expand All @@ -15,7 +15,7 @@ export async function createImageOptimizationBundle(
) {
logger.info(`Bundling image optimization function...`);

const { appPath, appBuildOutputPath, config, outputDir } = options;
const { appBuildOutputPath, config, outputDir } = options;

// Create output folder
const outputPath = path.join(outputDir, "image-optimization-function");
Expand Down Expand Up @@ -108,29 +108,12 @@ export async function createImageOptimizationBundle(
// Target should be same as used by Lambda, see https://github.com/sst/sst/blob/ca6f763fdfddd099ce2260202d0ce48c72e211ea/packages/sst/src/constructs/NextjsSite.ts#L114
// For SHARP_IGNORE_GLOBAL_LIBVIPS see: https://github.com/lovell/sharp/blob/main/docs/install.md#aws-lambda

const nodeOutputPath = path.resolve(outputPath);
const sharpVersion = process.env.SHARP_VERSION ?? "0.32.6";

const arch = config.imageOptimization?.arch ?? "arm64";
const nodeVersion = config.imageOptimization?.nodeVersion ?? "18";

//check if we are running in Windows environment then set env variables accordingly.
try {
cp.execSync(
// We might want to change the arch args to cpu args, it seems to be the documented way
`npm install --arch=${arch} --platform=linux --target=${nodeVersion} --libc=glibc --prefix="${nodeOutputPath}" sharp@${sharpVersion}`,
{
stdio: "pipe",
cwd: appPath,
env: {
...process.env,
SHARP_IGNORE_GLOBAL_LIBVIPS: "1",
},
},
);
} catch (e: any) {
logger.error(e.stdout.toString());
logger.error(e.stderr.toString());
logger.error("Failed to install sharp.");
}
installDependencies(
outputPath,
config.imageOptimization?.install ?? {
packages: [`sharp@${sharpVersion}`],
},
);
}
3 changes: 3 additions & 0 deletions packages/open-next/src/build/createMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import logger from "../logger.js";
import { type MiddlewareManifest } from "../types/next-types.js";
import { buildEdgeBundle } from "./edge/createEdgeBundle.js";
import * as buildHelper from "./helper.js";
import { installDependencies } from "./installDeps.js";

export async function createMiddleware(options: buildHelper.BuildOptions) {
logger.info(`Bundling middleware function...`);
Expand Down Expand Up @@ -58,6 +59,8 @@ export async function createMiddleware(options: buildHelper.BuildOptions) {
includeCache: config.dangerous?.enableCacheInterception,
additionalExternals: config.edgeExternals,
});

installDependencies(outputPath, config.middleware?.install);
} else {
await buildEdgeBundle({
entrypoint: path.join(
Expand Down
3 changes: 3 additions & 0 deletions packages/open-next/src/build/createRevalidationBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from "node:path";
import logger from "../logger.js";
import { openNextResolvePlugin } from "../plugins/resolve.js";
import * as buildHelper from "./helper.js";
import { installDependencies } from "./installDeps.js";

export async function createRevalidationBundle(
options: buildHelper.BuildOptions,
Expand Down Expand Up @@ -41,6 +42,8 @@ export async function createRevalidationBundle(
options,
);

installDependencies(outputPath, config.revalidate?.install);

// Copy over .next/prerender-manifest.json file
fs.copyFileSync(
path.join(appBuildOutputPath, ".next", "prerender-manifest.json"),
Expand Down
3 changes: 3 additions & 0 deletions packages/open-next/src/build/createServerBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { compileCache } from "./compileCache.js";
import { copyTracedFiles } from "./copyTracedFiles.js";
import { generateEdgeBundle } from "./edge/createEdgeBundle.js";
import * as buildHelper from "./helper.js";
import { installDependencies } from "./installDeps.js";

const require = createRequire(import.meta.url);

Expand Down Expand Up @@ -267,6 +268,8 @@ async function generateBundle(
addMonorepoEntrypoint(outputPath, packagePath);
}

installDependencies(outputPath, fnOptions.install);

if (fnOptions.minify) {
await minifyServerBundle(outputPath);
}
Expand Down
3 changes: 3 additions & 0 deletions packages/open-next/src/build/createWarmerBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from "node:path";
import logger from "../logger.js";
import { openNextResolvePlugin } from "../plugins/resolve.js";
import * as buildHelper from "./helper.js";
import { installDependencies } from "./installDeps.js";

export async function createWarmerBundle(options: buildHelper.BuildOptions) {
logger.info(`Bundling warmer function...`);
Expand Down Expand Up @@ -48,4 +49,6 @@ export async function createWarmerBundle(options: buildHelper.BuildOptions) {
},
options,
);

installDependencies(outputPath, config.warmer?.install);
}
55 changes: 55 additions & 0 deletions packages/open-next/src/build/installDeps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

import { execSync } from "child_process";
import { InstallOptions } from "types/open-next";

import logger from "../logger.js";

export function installDependencies(
outputDir: string,
installOptions?: InstallOptions,
) {
try {
if (!installOptions) {
return;
}
const name = outputDir.split("/").pop();
// First we create a tempDir
const tempInstallDir = fs.mkdtempSync(
path.join(os.tmpdir(), `open-next-install-${name}`),
);
logger.info(`Installing dependencies for ${name}...`);
// We then need to run install in the tempDir
// We don't install in the output dir directly because it could contain a package.json, and npm would then try to reinstall not complete deps from tracing the files
const installCommand = `npm install --arch=${
installOptions.arch ?? "arm64"
} --platform=linux --target=${installOptions.nodeVersion ?? "18"} --libc=${
installOptions.libc ?? "glibc"
} ${installOptions.packages.join(" ")}`;
execSync(installCommand, {
stdio: "pipe",
cwd: tempInstallDir,
env: {
...process.env,
SHARP_IGNORE_GLOBAL_LIBVIPS: "1",
},
});

// Copy the node_modules to the outputDir
fs.cpSync(
path.join(tempInstallDir, "node_modules"),
path.join(outputDir, "node_modules"),

{ recursive: true, force: true, dereference: true },
);

// Cleanup tempDir
fs.rmSync(tempInstallDir, { recursive: true, force: true });
logger.info(`Dependencies installed for ${name}`);
} catch (e: any) {
logger.error(e.stdout.toString());
logger.error("Could not install dependencies");
}
}
42 changes: 33 additions & 9 deletions packages/open-next/src/types/open-next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,31 @@ export interface OverrideOptions extends DefaultOverrideOptions {
queue?: IncludedQueue | LazyLoadedOverride<Queue>;
}

export interface InstallOptions {
/**
* List of packages to install
* @example
* ```ts
* install: {
* packages: ["[email protected]"]
* }
* ```
*/
packages: string[];
/**
* @default "arm64"
*/
arch?: "x64" | "arm64";
/**
* @default "18"
*/
nodeVersion?: "18" | "20" | "22";
/**
* @default "glibc"
*/
libc?: "glibc" | "musl";
}

export interface DefaultFunctionOptions<
E extends BaseEventOrResult = InternalEvent,
R extends BaseEventOrResult = InternalResult,
Expand All @@ -202,6 +227,14 @@ export interface DefaultFunctionOptions<
* Enable overriding the default lambda.
*/
override?: DefaultOverrideOptions<E, R>;

/**
* Install options for the function.
* This is used to install additional packages to this function.
* For image optimization, it will install sharp by default.
* @default undefined
*/
install?: InstallOptions;
}

export interface FunctionOptions extends DefaultFunctionOptions {
Expand Down Expand Up @@ -318,15 +351,6 @@ export interface OpenNextConfig {
* @default "s3"
*/
loader?: IncludedImageLoader | LazyLoadedOverride<ImageLoader>;
/**
* @default "arm64"
*/
arch?: "x64" | "arm64";
/**
* @default "18"
*/

nodeVersion?: "18" | "20";
};

/**
Expand Down