Skip to content

Commit d758215

Browse files
authored
Add support for Images binding (#7945)
* Add Images binding * Add Images remote preview mode * Plumb images local mode flag through * Add Images binding local mode * Add Images E2E test * Hoist @img packages This fixes the fixture tests, perhaps because sharp does something unusual with imports, see GH comment: nuxt/image#1210 (comment) * Add local suffix when printing bindings * Swap describe/it in E2E test * Mark sharp as unbundled, rather than hoisting * Remove zod * Improve error messages
1 parent fdd1303 commit d758215

30 files changed

+935
-80
lines changed

.changeset/beige-chicken-sort.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Add Images binding (in private beta for the time being)

packages/wrangler/e2e/dev-with-resources.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,40 @@ describe.sequential.each(RUNTIMES)("Bindings: $flags", ({ runtime, flags }) => {
720720
await expect(res.text()).resolves.toBe("env.WORKFLOW is available");
721721
});
722722

723+
describe.sequential.each([
724+
{ imagesMode: "remote", extraFlags: "" },
725+
{ imagesMode: "local", extraFlags: "--experimental-images-local-mode" },
726+
] as const)("Images Binding Mode: $imagesMode", async ({ extraFlags }) => {
727+
it("exposes Images bindings", async () => {
728+
await helper.seed({
729+
"wrangler.toml": dedent`
730+
name = "my-images-demo"
731+
main = "src/index.ts"
732+
compatibility_date = "2024-12-27"
733+
734+
[images]
735+
binding = "IMAGES"
736+
`,
737+
"src/index.ts": dedent`
738+
export default {
739+
async fetch(request, env, ctx) {
740+
if (env.IMAGES === undefined) {
741+
return new Response("env.IMAGES is undefined");
742+
}
743+
744+
return new Response("env.IMAGES is available");
745+
}
746+
}
747+
`,
748+
});
749+
const worker = helper.runLongLived(`wrangler dev ${flags} ${extraFlags}`);
750+
const { url } = await worker.waitForReady();
751+
const res = await fetch(url);
752+
753+
await expect(res.text()).resolves.toBe("env.IMAGES is available");
754+
});
755+
});
756+
723757
// TODO(soon): implement E2E tests for other bindings
724758
it.skipIf(isLocal).todo("exposes send email bindings");
725759
it.skipIf(isLocal).todo("exposes browser bindings");

packages/wrangler/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"esbuild": "0.17.19",
7777
"miniflare": "workspace:*",
7878
"path-to-regexp": "6.3.0",
79+
"sharp": "^0.33.5",
7980
"unenv": "2.0.0-rc.1",
8081
"workerd": "1.20250124.0"
8182
},

packages/wrangler/scripts/deps.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export const EXTERNAL_DEPENDENCIES = [
3838

3939
// workerd contains a native binary, so must be external. Wrangler depends on a pinned version.
4040
"workerd",
41+
42+
// sharp contains native libraries
43+
"sharp",
4144
];
4245

4346
const pathToPackageJson = path.resolve(__dirname, "..", "package.json");

packages/wrangler/src/__tests__/config/configuration.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2302,6 +2302,69 @@ describe("normalizeAndValidateConfig()", () => {
23022302
});
23032303
});
23042304

2305+
// Images
2306+
describe("[images]", () => {
2307+
it("should error if images is an array", () => {
2308+
const { diagnostics } = normalizeAndValidateConfig(
2309+
{ images: [] } as unknown as RawConfig,
2310+
undefined,
2311+
undefined,
2312+
{ env: undefined }
2313+
);
2314+
2315+
expect(diagnostics.hasWarnings()).toBe(false);
2316+
expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
2317+
"Processing wrangler configuration:
2318+
- The field \\"images\\" should be an object but got []."
2319+
`);
2320+
});
2321+
2322+
it("should error if images is a string", () => {
2323+
const { diagnostics } = normalizeAndValidateConfig(
2324+
{ images: "BAD" } as unknown as RawConfig,
2325+
undefined,
2326+
undefined,
2327+
{ env: undefined }
2328+
);
2329+
2330+
expect(diagnostics.hasWarnings()).toBe(false);
2331+
expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
2332+
"Processing wrangler configuration:
2333+
- The field \\"images\\" should be an object but got \\"BAD\\"."
2334+
`);
2335+
});
2336+
2337+
it("should error if images is a number", () => {
2338+
const { diagnostics } = normalizeAndValidateConfig(
2339+
{ images: 999 } as unknown as RawConfig,
2340+
undefined,
2341+
undefined,
2342+
{ env: undefined }
2343+
);
2344+
2345+
expect(diagnostics.hasWarnings()).toBe(false);
2346+
expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
2347+
"Processing wrangler configuration:
2348+
- The field \\"images\\" should be an object but got 999."
2349+
`);
2350+
});
2351+
2352+
it("should error if ai is null", () => {
2353+
const { diagnostics } = normalizeAndValidateConfig(
2354+
{ images: null } as unknown as RawConfig,
2355+
undefined,
2356+
undefined,
2357+
{ env: undefined }
2358+
);
2359+
2360+
expect(diagnostics.hasWarnings()).toBe(false);
2361+
expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
2362+
"Processing wrangler configuration:
2363+
- The field \\"images\\" should be an object but got null."
2364+
`);
2365+
});
2366+
});
2367+
23052368
// Worker Version Metadata
23062369
describe("[version_metadata]", () => {
23072370
it("should error if version_metadata is an array", () => {

packages/wrangler/src/__tests__/deploy.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11903,6 +11903,37 @@ export default{
1190311903
});
1190411904
});
1190511905

11906+
describe("images", () => {
11907+
it("should upload images bindings", async () => {
11908+
writeWranglerConfig({
11909+
images: { binding: "IMAGES_BIND" },
11910+
});
11911+
await fs.promises.writeFile("index.js", `export default {};`);
11912+
mockSubDomainRequest();
11913+
mockUploadWorkerRequest({
11914+
expectedBindings: [
11915+
{
11916+
type: "images",
11917+
name: "IMAGES_BIND",
11918+
},
11919+
],
11920+
});
11921+
11922+
await runWrangler("deploy index.js");
11923+
expect(std.out).toMatchInlineSnapshot(`
11924+
"Total Upload: xx KiB / gzip: xx KiB
11925+
Worker Startup Time: 100 ms
11926+
Your worker has access to the following bindings:
11927+
- Images:
11928+
- Name: IMAGES_BIND
11929+
Uploaded test-name (TIMINGS)
11930+
Deployed test-name triggers (TIMINGS)
11931+
https://test-name.test-sub-domain.workers.dev
11932+
Current Version ID: Galaxy-Class"
11933+
`);
11934+
});
11935+
});
11936+
1190611937
describe("python", () => {
1190711938
it("should upload python module defined in wrangler.toml", async () => {
1190811939
writeWranglerConfig({

packages/wrangler/src/__tests__/dev.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1399,7 +1399,8 @@ describe.sequential("wrangler dev", () => {
13991399
--test-scheduled Test scheduled events by visiting /__scheduled in browser [boolean] [default: false]
14001400
--log-level Specify logging level [choices: \\"debug\\", \\"info\\", \\"log\\", \\"warn\\", \\"error\\", \\"none\\"] [default: \\"log\\"]
14011401
--show-interactive-dev-session Show interactive dev session (defaults to true if the terminal supports interactivity) [boolean]
1402-
--experimental-vectorize-bind-to-prod Bind to production Vectorize indexes in local development mode [boolean] [default: false]",
1402+
--experimental-vectorize-bind-to-prod Bind to production Vectorize indexes in local development mode [boolean] [default: false]
1403+
--experimental-images-local-mode Use a local lower-fidelity implementation of the Images binding [boolean] [default: false]",
14031404
"warn": "",
14041405
}
14051406
`);

packages/wrangler/src/__tests__/pages/pages.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ describe("pages", () => {
7777
--persist-to Specify directory to use for local persistence (defaults to .wrangler/state) [string]
7878
--log-level Specify logging level [choices: \\"debug\\", \\"info\\", \\"log\\", \\"warn\\", \\"error\\", \\"none\\"]
7979
--show-interactive-dev-session Show interactive dev session (defaults to true if the terminal supports interactivity) [boolean]
80-
--experimental-vectorize-bind-to-prod Bind to production Vectorize indexes in local development mode [boolean] [default: false]"
80+
--experimental-vectorize-bind-to-prod Bind to production Vectorize indexes in local development mode [boolean] [default: false]
81+
--experimental-images-local-mode Use a local lower-fidelity implementation of the Images binding [boolean] [default: false]"
8182
`);
8283
});
8384

packages/wrangler/src/__tests__/type-generation.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ const bindingsConfigMock: Omit<
163163
ai: {
164164
binding: "AI_BINDING",
165165
},
166+
images: {
167+
binding: "IMAGES_BINDING",
168+
},
166169
version_metadata: {
167170
binding: "VERSION_METADATA_BINDING",
168171
},

packages/wrangler/src/api/dev.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export interface Unstable_DevOptions {
8282
devEnv?: boolean;
8383
fileBasedRegistry?: boolean;
8484
vectorizeBindToProd?: boolean;
85+
imagesLocalMode?: boolean;
8586
enableIpc?: boolean;
8687
};
8788
}
@@ -126,6 +127,7 @@ export async function unstable_dev(
126127
testMode,
127128
testScheduled,
128129
vectorizeBindToProd,
130+
imagesLocalMode,
129131
// 2. options for alpha/beta products/libs
130132
d1Databases,
131133
enablePagesAssetsServiceBinding,
@@ -218,6 +220,7 @@ export async function unstable_dev(
218220
port: options?.port ?? 0,
219221
experimentalProvision: undefined,
220222
experimentalVectorizeBindToProd: vectorizeBindToProd ?? false,
223+
experimentalImagesLocalMode: imagesLocalMode ?? false,
221224
enableIpc: options?.experimental?.enableIpc,
222225
};
223226

0 commit comments

Comments
 (0)