Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f51557c
feat(image): add shared image config and prop type contract
ogawa0071 Oct 16, 2026
73755ba
feat(image): add void/image core library and /_void/image endpoint
ogawa0071 Oct 16, 2026
1890a67
feat(image): infer the Images binding and accept it in native deploys
ogawa0071 Oct 16, 2026
2bf7432
feat(image): add Image and getImageProps to the Pages adapters
ogawa0071 Oct 16, 2026
953dba5
docs(image): document Image, void/image and the images config
ogawa0071 Oct 16, 2026
453cfb5
feat(image): wire image config, /_void/image and ?image imports into …
ogawa0071 Oct 16, 2026
5af5a55
fix(image): allow ?image imports with custom localPatterns in dev and…
ogawa0071 Oct 16, 2026
2f4c671
feat(image): infer next/image imports and keep preloads in generated …
ogawa0071 Oct 16, 2026
f4ce86f
test(image): cover Image in the Pages playgrounds
ogawa0071 Oct 16, 2026
a510004
fix(image): address review findings
ogawa0071 Oct 16, 2026
9b04f9d
test(image): remove the Image playground pages
ogawa0071 Oct 16, 2026
a233b0f
Merge branch 'voidzero-dev:main' into feat/image-optimization
ogawa0071 Oct 18, 2026
4f98720
fix(image): tighten the plugin, source loading and inference after re…
ogawa0071 Oct 19, 2026
f7e8c09
Merge remote-tracking branch 'origin/main' into feat/image-optimization
ogawa0071 Oct 19, 2026
eeb54bb
Merge remote-tracking branch 'origin/feat/image-optimization' into fe…
ogawa0071 Oct 19, 2026
9a9cb7c
Merge upstream/main into feat/image-optimization
ogawa0071 Nov 3, 2026
c0bf511
fix(image): keep ?image files out of the hashed-file Worker bypass
ogawa0071 Nov 3, 2026
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
32 changes: 23 additions & 9 deletions .github/scripts/packed-consumer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -155,26 +155,27 @@ assert(frameworks.every((framework) => ['react', 'vue', 'svelte', 'solid'].inclu
const pages = {
react: `import { useState } from 'react';
import { Link } from '@void/react';
import Image from '@void/react/image';
export default function Page({ message }: { message: string }) {
const [count, setCount] = useState(0);
return <><h1>{message}</h1><button onClick={() => setCount(count + 1)}>Count {count}</button><Link href="/next">Next page</Link></>;
return <><h1>{message}</h1><Image src="/packed.png" alt="Packed" width={32} height={32} /><button onClick={() => setCount(count + 1)}>Count {count}</button><Link href="/next">Next page</Link></>;
}`,
vue: `<script setup lang="ts">
import { ref } from 'vue';
import { Link } from '@void/vue';
import { Image, Link } from '@void/vue';
defineProps<{ message: string }>();
const count = ref(0);
</script><template><h1>{{ message }}</h1><button @click="count++">Count {{ count }}</button><Link href="/next">Next page</Link></template>`,
</script><template><h1>{{ message }}</h1><Image src="/packed.png" alt="Packed" :width="32" :height="32" /><button @click="count++">Count {{ count }}</button><Link href="/next">Next page</Link></template>`,
svelte: `<script lang="ts">
import { Link } from '@void/svelte';
import { Image, Link } from '@void/svelte';
let { message }: { message: string } = $props();
let count = $state(0);
</script><h1>{message}</h1><button onclick={() => count++}>Count {count}</button><Link href="/next">Next page</Link>`,
</script><h1>{message}</h1><Image src="/packed.png" alt="Packed" width={32} height={32} /><button onclick={() => count++}>Count {count}</button><Link href="/next">Next page</Link>`,
solid: `import { createSignal } from 'solid-js';
import { Link } from '@void/solid';
import { Image, Link } from '@void/solid';
export default function Page(props: { message: string }) {
const [count, setCount] = createSignal(0);
return <><h1>{props.message}</h1><button onClick={() => setCount(count() + 1)}>Count {count()}</button><Link href="/next">Next page</Link></>;
return <><h1>{props.message}</h1><Image src="/packed.png" alt="Packed" width={32} height={32} /><button onClick={() => setCount(count() + 1)}>Count {count()}</button><Link href="/next">Next page</Link></>;
}`,
};

Expand Down Expand Up @@ -331,6 +332,14 @@ try {
);
mkdirSync(join(fixture, 'public'), { recursive: true });
writeFileSync(join(fixture, 'public/packed.txt'), label);
// A 1x1 PNG for `Image`, which renders through the installed void/image and /_void/image.
writeFileSync(
join(fixture, 'public/packed.png'),
Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
'base64',
),
);
const vite = join(
fixture,
useVitePlus ? 'node_modules/vite-plus/bin/vp' : 'node_modules/vite/bin/vite.js',
Expand Down Expand Up @@ -389,7 +398,12 @@ if (runtime.manifest.platformVersion !== ${JSON.stringify(version)}) throw new E
await new Promise((resolve) => setTimeout(resolve, 200));
}
assert(response?.ok, task.failure());
assert((await response.text()).includes(`Packed ${label}`));
const html = await response.text();
assert(html.includes(`Packed ${label}`));
assert(html.includes('/_void/image?url=%2Fpacked.png&amp;w=32&amp;q=75 1x'), html);
const image = await fetch(`${base}/_void/image?url=%2Fpacked.png&w=32&q=75`);
assert.equal(image.status, 200, task.failure());
assert.match(image.headers.get('content-type') ?? '', /^image\//);
assert.equal(await (await fetch(`${base}/packed.txt`)).text(), label);
const apiResponse = await fetch(`${base}/api/packed`);
const apiBody = await apiResponse.text();
Expand Down Expand Up @@ -496,7 +510,7 @@ if (runtime.manifest.platformVersion !== ${JSON.stringify(version)}) throw new E
}
}
console.log(
`Passed packed ${label} (${suite}): init, frozen install, ${useVitePlus ? 'Vite+' : 'public Vite'} build/typecheck, assets, KV, email, runtime integrity${full ? ', dev/preview, hydration/navigation' : ', dev HTTP'}${framework === 'react' ? ', Node target' : ''}`,
`Passed packed ${label} (${suite}): init, frozen install, ${useVitePlus ? 'Vite+' : 'public Vite'} build/typecheck, assets, KV, email, Image, runtime integrity${full ? ', dev/preview, hydration/navigation' : ', dev HTTP'}${framework === 'react' ? ', Node target' : ''}`,
);
} catch (error) {
failures.push({ fixture: label, error: String(error.stack ?? error) });
Expand Down
2 changes: 2 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const referenceItems = [
{ text: 'target', link: '/reference/config#target' },
{ text: 'routing', link: '/reference/config#routing' },
{ text: 'inference', link: '/reference/config#inference' },
{ text: 'images', link: '/reference/config#images' },
{ text: 'output', link: '/reference/config#output' },
{ text: 'remote', link: '/reference/config#remote' },
{ text: 'sandbox', link: '/reference/config#sandbox' },
Expand Down Expand Up @@ -259,6 +260,7 @@ export default extendConfig(
{ text: 'Rewrites', link: '/guide/edge/rewrites' },
],
},
{ text: 'Images', link: '/guide/images' },
{ text: 'Custom SSR', link: '/guide/ssr' },
{ text: 'SSG', link: '/guide/ssg' },
],
Expand Down
Loading