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/r2-lazy-aws-sdk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"files-sdk": patch
---

Keep `@aws-sdk/*` out of the static module graph reachable from `files-sdk/r2` so binding- and fetch-mode Workers bundle without the optional peers installed. Consumer bundlers (e.g. rolldown-vite with the Cloudflare Vite plugin) resolve even dynamically-imported chunks at build time, so the s3 entry's static SDK imports behind the r2 adapter's lazy boundary hard-errored with `MISSING_EXPORT` against the optional-peer placeholder (#105). The s3 engine is now SDK-parameterized (`s3/core.js`); `files-sdk/s3` wires it from static imports as before, while the r2 aws-sdk engine loads the SDK modules via dynamic import on first use. No behavior change for `files-sdk/s3` consumers.
36 changes: 25 additions & 11 deletions packages/files-sdk/src/r2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ import { FilesError } from "../internal/errors.js";
import type { S3FetchAdapter } from "../internal/s3-fetch.js";
import { s3FetchAdapter } from "../internal/s3-fetch.js";
import { createStoredFile } from "../internal/stored-file.js";
// Note: the s3 adapter is *not* imported eagerly. The aws-sdk HTTP path
// loads it via dynamic import on first use so that a Worker bundle on the
// binding or fetch paths never pulls in @aws-sdk/client-s3 (~500KB+). See
// `lazyS3` below.
import type { S3Adapter, S3AdapterOptions } from "../s3/index.js";
// Note: the s3 engine is *not* imported eagerly (and only its types are
// referenced here). The aws-sdk HTTP path loads it via dynamic import on
// first use so that a Worker bundle on the binding or fetch paths never
// pulls in @aws-sdk/client-s3 (~500KB+). See `lazyS3` below.
import type { S3Adapter, S3AdapterOptions } from "../s3/core.js";

const DEFAULT_CONTENT_TYPE = "application/octet-stream";

Expand Down Expand Up @@ -132,17 +132,31 @@ export type R2AdapterOptions = R2BindingOptions | R2HttpOptions;

export type R2Adapter = Adapter<S3Client | R2Bucket | AwsClient>;

// Lazy-load the s3 adapter via dynamic import so a binding-only Worker
// bundle doesn't pull in @aws-sdk/client-s3 (~500KB+ minified). The
// returned function is single-shot: it builds the adapter once on first
// call and returns the same promise on subsequent calls.
// Lazy-load the s3 engine via dynamic imports so a binding-only Worker
// bundle doesn't pull in @aws-sdk/client-s3 (~500KB+ minified). This goes
// through the SDK-parameterized ../s3/core.js rather than ../s3/index.js:
// consumer bundlers resolve even dynamically-reached chunks at build time,
// so the entry's *static* `@aws-sdk/*` imports would hard-error against an
// optional-peer placeholder when the SDK isn't installed (#105). Dynamic
// specifiers stay unexecuted on the binding/fetch paths, so the placeholder
// never throws. The returned function is single-shot: it builds the adapter
// once on first call and returns the same promise on subsequent calls.
const lazyS3 = (config: S3AdapterOptions): (() => Promise<S3Adapter>) => {
let promise: Promise<S3Adapter> | null = null;
return () => {
if (!promise) {
promise = (async () => {
const { s3 } = await import("../s3/index.js");
return s3(config);
const [core, clientS3, presignedPost, requestPresigner] =
await Promise.all([
import("../s3/core.js"),
import("@aws-sdk/client-s3"),
import("@aws-sdk/s3-presigned-post"),
import("@aws-sdk/s3-request-presigner"),
]);
return core.createS3Adapter(
{ clientS3, presignedPost, requestPresigner },
config
);
})();
}
return promise;
Expand Down
Loading