Implement an example project using vite to run both host and sandboxed applications#434
Implement an example project using vite to run both host and sandboxed applications#434ghostwriternr merged 5 commits intomainfrom
Conversation
|
|
I'm still debugging why this doesn't work. With a default setup we run into issues with the host dev server running with the @cloudflare/vite plugin intercepting the sandbox request for the e.g. for a dev server running on localhost:3000 with a sandbox server running on port 5173 you'd get a request to: This would return the configuration for the host dev server, and the subsequent websocket would be: This would again connect to the host websocket rather than the sandbox one. This can be worked around by using the This has the drawback that the sandbox server must be served from a prefixed path, but for now that's probably fine as a constraint. However for some reason this still doesn't work :/ The websocket connection no longer connects to anything. So needs more investigation. In the container I can use |
commit: |
🐳 Docker Images PublishedDefault: FROM cloudflare/sandbox:0.0.0-pr-434-f531f6dWith Python: FROM cloudflare/sandbox:0.0.0-pr-434-f531f6d-pythonWith OpenCode: FROM cloudflare/sandbox:0.0.0-pr-434-f531f6d-opencodeVersion: Use the 📦 Standalone BinaryFor arbitrary Dockerfiles: COPY --from=cloudflare/sandbox:0.0.0-pr-434-f531f6d /container-server/sandbox /sandbox
ENTRYPOINT ["/sandbox"]Download via GitHub CLI: gh run download 23014149621 -n sandbox-binaryExtract from Docker: docker run --rm cloudflare/sandbox:0.0.0-pr-434-f531f6d cat /container-server/sandbox > sandbox && chmod +x sandbox |
There was a problem hiding this comment.
OpenCode Review
This PR adds a new Vite development server example for the Cloudflare Sandbox SDK. The example demonstrates running a Vite dev server inside a sandbox container and exposing it via preview URLs with Hot Module Replacement (HMR) support.
Missing Critical Configuration: The example is missing a wrangler.jsonc configuration file. All other examples in the repository have either wrangler.toml or wrangler.jsonc files that define the Durable Object bindings, container configuration, and other Worker settings. Without this file, the example won't work.
Code Issues:
- Hard-coded token in debug code - WebSocket connection uses a hard-coded token that should be dynamic or documented as debug code
- Unused debug code - The
/debugendpoint appears to contain development/debugging code that should be cleaned up - Docker version mismatch - Uses
cloudflare/sandbox:0.7.6but should match current SDK version for consistency
Missing Documentation: The example would benefit from a README.md explaining how HMR works with sandboxes and the specific setup required for Vite integration.
The implementation correctly follows SDK patterns for port exposure and process management, but needs the wrangler configuration to function.
examples/vite-sandbox/src/index.ts
Outdated
| if (url.pathname === '/debug') { | ||
| console.log( | ||
| 'connecting to', | ||
| `${baseURL.replace('http', 'ws')}/?token=fr4WLvivldEq` |
There was a problem hiding this comment.
Hard-coded token fr4WLvivldEq in WebSocket connection. Should this be dynamically generated or is this debug code?
There was a problem hiding this comment.
Hard-coded tokens and debug endpoints should still be addressed. These aren't suitable for production examples that developers might copy.
There was a problem hiding this comment.
These hardcoded values and debug endpoints are still present. Should they be removed or properly documented as development-only code?
There was a problem hiding this comment.
The hardcoded values and debug endpoints are still present in the code. These should be removed or properly documented as development-only code before this example is ready for production use.
There was a problem hiding this comment.
The hardcoded values remain in the code. This example should demonstrate production-ready patterns, not development debugging code.
There was a problem hiding this comment.
These hardcoded values remain after the history rewrite. For a production-ready example, consider making the sandbox ID dynamic (e.g., session-based) and moving hardcoded values to configuration.
There was a problem hiding this comment.
These hardcoded values remain after the history rewrite. For a production-ready example, consider making the sandbox ID dynamic (e.g., session-based) and moving hardcoded values to configuration.
examples/vite-sandbox/src/index.ts
Outdated
|
|
||
| const baseURL = `${port.url}sandbox`; | ||
| console.log('baseURL', baseURL); | ||
| if (url.pathname === '/debug') { |
There was a problem hiding this comment.
This /debug endpoint contains development/testing code with hard-coded values and a 10-second delay. Should this be removed or documented as debug functionality?
There was a problem hiding this comment.
The /debug endpoint with hard-coded delays is still present - this should be removed or documented as development-only code.
There was a problem hiding this comment.
The debug endpoint with hardcoded delays remains. This needs to be addressed for a production-ready example.
There was a problem hiding this comment.
The debug endpoint with hardcoded delays remains in the code. This needs to be addressed for a production-ready example.
There was a problem hiding this comment.
The debug endpoint with hardcoded delays remains. This should be removed or clearly documented as debug-only functionality.
There was a problem hiding this comment.
The debug endpoint and hardcoded delays are still present. Since this is meant to be a reference example, these debug artifacts should be removed or clearly documented as development-only code.
There was a problem hiding this comment.
The debug endpoint and hardcoded delays are still present. Since this is meant to be a reference example, these debug artifacts should be removed or clearly documented as development-only code.
examples/vite-sandbox/Dockerfile
Outdated
| @@ -0,0 +1,9 @@ | |||
| FROM docker.io/cloudflare/sandbox:0.7.6 | |||
There was a problem hiding this comment.
Docker image version 0.7.6 should match current SDK version. Consider using a wildcard or updating to match other examples for consistency.
There was a problem hiding this comment.
This is still an issue - using version 0.7.6 instead of matching the current SDK version creates inconsistency.
There was a problem hiding this comment.
Actually, after checking the current SDK version in packages/sandbox/package.json, version 0.7.6 is correct. The Dockerfile version matches the SDK version exactly. This is resolved.
There was a problem hiding this comment.
Actually, after checking the current SDK version in packages/sandbox/package.json, version 0.7.6 is correct. The Dockerfile version matches the SDK version exactly. This issue is resolved.
There was a problem hiding this comment.
Version 0.7.6 is correct and matches the current SDK version. This issue is resolved.
There was a problem hiding this comment.
Version 0.7.6 is correct and matches the current SDK version. This issue is resolved.
2ec4216 to
1037f08
Compare
There was a problem hiding this comment.
OpenCode Review
The hard-coded tokens and debug endpoints from the previous version have been cleaned up, which is great! However, there are still a few issues to address:
Docker version mismatch: The Dockerfile still uses version 0.7.6 instead of matching the current SDK version. This creates inconsistency and could lead to compatibility issues.
Implementation looks solid: The core pattern is good - checking for existing exposed ports, starting the Vite dev server, waiting for it to come up, then using HTMLRewriter to inject the correct base href. The counter background process is a nice demo touch.
Console.log statements: There are debug console.log statements that should be removed or converted to proper logging.
A couple minor improvements to consider for better error handling and consistency with other examples.
examples/vite-sandbox/src/index.ts
Outdated
| }); | ||
| } | ||
|
|
||
| const baseURL = `${port.url}sandbox`; |
There was a problem hiding this comment.
Debug console.log statement should be removed before merge.
There was a problem hiding this comment.
Debug console.log statements remain - these should be cleaned up for a polished example.
There was a problem hiding this comment.
Debug console.log statements are still present in vite.config.js (lines 26, 60). These should be removed.
There was a problem hiding this comment.
Debug console.log statements are still present throughout multiple files. These need cleanup.
There was a problem hiding this comment.
Debug console statements are still scattered throughout the codebase and need cleanup before this can serve as a clean production reference.
There was a problem hiding this comment.
Debug console statements are still scattered throughout the codebase and need cleanup before this can serve as a clean production reference.
examples/vite-sandbox/src/index.ts
Outdated
| if (!response) { | ||
| return new Response('Unexpected error', { status: 500 }); | ||
| } | ||
|
|
There was a problem hiding this comment.
Debug console.log statement should be removed before merge.
There was a problem hiding this comment.
Another debug console.log that should be removed.
There was a problem hiding this comment.
Additional debug console.log to clean up.
There was a problem hiding this comment.
Additional debug console.log statements remain.
There was a problem hiding this comment.
OpenCode Review
The Docker image version issue is resolved - 0.7.6 is correct and matches the current SDK version. However, several debug artifacts remain that need attention before merge.
The example demonstrates a solid pattern for running Vite dev servers in sandboxes with HMR support. The architecture is clean: host Worker serves the UI while the sandbox runs the actual Vite server, connected via preview URLs.
A few things to address:
-
Biome formatter enabled: The change from
"enabled": falseto"enabled": trueaffects the entire repo's formatting. This should be a separate decision/PR, not bundled with this example. -
Debug artifacts: Still seeing console.error statements and debug-focused code that should be cleaned up.
-
Example completeness: The counter.js demonstration is clever but the overall example would benefit from documentation explaining the HMR setup and why the architecture works this way.
Overall architecture is sound, just needs cleanup of non-essential changes and debug code.
biome.json
Outdated
| }, | ||
| "formatter": { | ||
| "enabled": false, | ||
| "enabled": true, |
There was a problem hiding this comment.
Enabling the formatter for the entire repository should be a separate decision/PR. This change affects all files and shouldn't be bundled with this example.
There was a problem hiding this comment.
The global Biome formatter change still needs to be reverted. This affects the entire repository and should be a separate decision.
There was a problem hiding this comment.
The global Biome formatter change still needs to be reverted - this affects the entire repository and should be a separate decision.
There was a problem hiding this comment.
The global Biome formatter change still needs to be reverted - this affects the entire repository and should be a separate decision.
There was a problem hiding this comment.
This global Biome formatter change still needs to be reverted - it affects the entire repository and should be a separate decision.
There was a problem hiding this comment.
The global Biome formatter change is still present and should be reverted. This repository-wide change should be handled in a separate PR focused specifically on that decision.
There was a problem hiding this comment.
The global Biome formatter change is still present and should be reverted. This repository-wide change should be handled in a separate PR focused specifically on that decision.
| fetch("/api/sandbox") | ||
| .then((r) => r.json()) | ||
| .then((data) => setSandboxUrl(data.url)) | ||
| .catch((err) => console.error(err)); |
There was a problem hiding this comment.
Debug console.error should be removed or replaced with proper error handling before merge.
There was a problem hiding this comment.
Debug console.error in App.jsx line 10 should be replaced with proper error handling.
There was a problem hiding this comment.
Debug console.error in App.jsx should still be replaced with proper error handling.
There was a problem hiding this comment.
Debug console.error statements should be replaced with proper error handling.
There was a problem hiding this comment.
Debug console.error statements should be replaced with proper error handling for a production-ready example.
There was a problem hiding this comment.
Debug console.error statements should be replaced with proper error handling for a production-ready example.
There was a problem hiding this comment.
OpenCode Review
The Docker version issue is resolved (0.7.6 correctly matches the SDK version). However, several development/debug issues remain:
Still to address:
- Debug console.log statements in vite.config.js (lines 26, 60) should be removed for production examples
- Global Biome formatter change affects the entire repository - this should be reverted and handled separately
- Hard-coded tokens/debug endpoints mentioned in previous feedback need to be addressed
New observations:
- The example demonstrates proper Cloudflare Containers + Vite integration using preview URLs
- Worker implementation correctly uses proxyToSandbox() pattern
- Port exposure follows documented best practices
- HMR WebSocket handling is well-architected
Architecture assessment: The implementation properly showcases the three-layer architecture with host Vite server, Worker proxy layer, and sandbox container running another Vite dev server. The preview URL pattern and WebSocket proxying demonstrate advanced Cloudflare Containers usage.
Revert the global Biome change and clean up debug statements, then this will be ready.
examples/vite-sandbox/vite.config.js
Outdated
|
|
||
| // Register middleware before default middleware. | ||
| server.middlewares.use(async (req, res, next) => { | ||
| console.log("request", req.url); |
There was a problem hiding this comment.
Debug console.log should be removed from production example.
There was a problem hiding this comment.
Debug console.log should be removed.
examples/vite-sandbox/vite.config.js
Outdated
| const headers = createHeaders(request); | ||
| headers.set(CoreHeaders.ROUTE_OVERRIDE, entryWorkerName); | ||
|
|
||
| console.log("upgrade in flight", url); |
There was a problem hiding this comment.
Another debug console.log to remove.
There was a problem hiding this comment.
Another debug console.log to remove.
There was a problem hiding this comment.
OpenCode Review
Looks like history was rewritten since my last review, so starting fresh with the current state.
This is an impressive technical demo showing Vite HMR tunneling through sandbox boundaries. The architecture is sound and demonstrates advanced SDK usage patterns. However, there are some issues that need addressing:
Major Issues:
- Multi-tenancy problem: All users share the same sandbox ID
'vite-sandbox'- this won't work for multiple concurrent users - Global Biome formatter change: Enabling the formatter repository-wide shouldn't be part of this PR
- Unstable dependency: Using a PR preview package instead of stable version
Minor Issues:
- Debug console statements throughout the codebase need cleanup
- Hard-coded configuration values that should be configurable
What's Good:
- Excellent documentation and clear separation of host/sandbox concerns
- Proper SDK usage patterns that other developers can learn from
- Creative solution for complex WebSocket proxying scenario
Verdict: The core concept is solid but needs cleanup before merge. The multi-tenancy issue is the biggest blocker - consider using session-based sandbox IDs.
| }; | ||
|
|
||
| async function handleAPISandboxRoute(url, env) { | ||
| const sandbox = getSandbox(env.Sandbox, 'vite-sandbox'); |
There was a problem hiding this comment.
Multi-tenancy issue: All users will share the same sandbox instance. Consider using session-based IDs like getSandbox(env.Sandbox, vite-${request.headers.get('CF-Ray') || Date.now()})
There was a problem hiding this comment.
This multi-tenancy issue remains the most critical concern - all users sharing the same sandbox instance isn't acceptable for a production reference example.
examples/vite-sandbox/package.json
Outdated
| "author": "", | ||
| "license": "MIT", | ||
| "dependencies": { | ||
| "@cloudflare/vite-plugin": "https://pkg.pr.new/@cloudflare/vite-plugin@12794" |
There was a problem hiding this comment.
Using a PR preview package creates fragility. This should use a stable npm version once the vite-plugin changes are released.
ab2340b to
0d6bdfb
Compare
| const escapedPath = shellEscape(path); | ||
| const mimeResult = await exec(`file --mime-type -b ${escapedPath}`); |
There was a problem hiding this comment.
🟡 MIME type shell fallback uses original (potentially relative) path instead of resolved absolute path
In the read() method, after resolving a relative path to an absolute path via resolvePathInSession(), the MIME type shell fallback command still uses the original path variable (shellEscape(path)) instead of absolutePath. While Bun.file(absolutePath) correctly opens the resolved file, the file --mime-type -b shell command runs with the unresolved path. Although the session shell's CWD may resolve the relative path the same way, this is fragile: the resolvePathInSession call and the file command are separate shell executions, and if the session's CWD were to differ (e.g., due to a prior cd in a scoped env block), the shell command would detect the MIME type of a different file or fail entirely. The getFileMetadata() method at packages/sandbox-container/src/services/file-service.ts:991 has the same pattern but receives absolutePath from its caller (readFileStreamOperation at line 1388), so it is not affected in practice.
| const escapedPath = shellEscape(path); | |
| const mimeResult = await exec(`file --mime-type -b ${escapedPath}`); | |
| const escapedPath = shellEscape(absolutePath); | |
| const mimeResult = await exec(`file --mime-type -b ${escapedPath}`); |
Was this helpful? React with 👍 or 👎 to provide feedback.
0d6bdfb to
adf2b49
Compare
adf2b49 to
9f14ee5
Compare
6734636 to
997e8ad
Compare
| let port = await sandbox | ||
| .getExposedPorts(url.host) | ||
| .then((ports) => ports.find((p) => p.port === VITE_PORT)); | ||
|
|
||
| if (!port) { | ||
| port = await sandbox.exposePort(VITE_PORT, { hostname: url.host }); | ||
|
|
||
| const process = await sandbox.startProcess('npm run dev', { | ||
| processId: 'vite-dev-server', | ||
| cwd: '/app', | ||
| env: { | ||
| VITE_BASE: VITE_BASE, | ||
| VITE_PORT: `${VITE_PORT}`, | ||
| VITE_HMR_CLIENT_PORT: url.port ? `${url.port}` : undefined | ||
| } | ||
| }); | ||
| await process.waitForPort(VITE_PORT); | ||
| } |
There was a problem hiding this comment.
🟡 Exposed port without running server creates permanently broken state on startup failure
In handleAPISandboxRoute, exposePort is called before startProcess and waitForPort. If either startProcess or waitForPort throws (e.g., npm run dev fails, timeout, etc.), the port remains exposed in the Durable Object but no Vite server is running behind it. All subsequent requests to /api/sandbox will find the port via getExposedPorts at line 29, skip the entire startup block, and return a non-functional URL. This stuck state persists until the sandbox sleeps and the container restarts.
Reproduction scenario
- First request hits
/api/sandbox exposePort(5173)succeeds (line 33)startProcess('npm run dev', ...)orwaitForPort(5173)throws- Error propagates to caller
- Second request hits
/api/sandbox getExposedPortsfinds port 5173 already exposed (line 29-30)- Startup block is skipped
- Returns URL pointing to port with no server behind it
Prompt for agents
In examples/vite-sandbox/src/worker.js, the handleAPISandboxRoute function (lines 25-48) needs to be restructured so that the exposed port alone is not used as a signal that the Vite server is running. Two possible approaches:
1. Move exposePort after startProcess + waitForPort, so the port is only exposed once the server is confirmed running. Change lines 32-45 to: first startProcess, then waitForPort, then exposePort.
2. Alternatively, add error handling: wrap lines 35-44 (startProcess + waitForPort) in a try/catch that calls sandbox.unexposePort(VITE_PORT) if startup fails, so the next request retries.
Was this helpful? React with 👍 or 👎 to provide feedback.
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The vite-sandbox example used older versions of react, vite, and wrangler than the rest of the workspace. Also bumps @cloudflare/vite-plugin to ^1.30.1 across all packages.
0d0ec11 to
6298d83
Compare
|
/bonk fix the merge conflict |
There was a problem hiding this comment.
OpenCode Review
This example effectively demonstrates running a Vite dev server inside a Cloudflare Sandbox with HMR tunneled through preview URLs. The architecture is clean, the README is thorough, and it follows existing SDK patterns well. The previous iteration's issues (biome.json changes, debug endpoints, console.log statements, hard-coded tokens) have all been cleaned up -- nice work.
A few remaining issues to address:
Bugs
-
Startup failure creates permanently broken state (
src/worker.js:32-44):exposePort()is called beforestartProcess()andwaitForPort(). If either throws, the port remains exposed but no server is running. All subsequent requests will find the exposed port viagetExposedPorts()(line 29), skip the startup block, and return a non-functional URL. This persists until the sandbox sleeps. Fix by movingexposePort()afterwaitForPort(), or wrapping the startup in a try/catch that callsunexposePort()on failure. -
HMR broken in production (
src/worker.js:41): When deployed on HTTPS (port 443),url.portreturns""per the URL spec. The falsy empty string causesVITE_HMR_CLIENT_PORTto beundefined, andsandbox-app/vite.config.js:28defaults it to3000. The HMR client in the browser then tries to connect on port 3000, which won't work. Should fall back to the protocol-appropriate port:url.port || (url.protocol === 'https:' ? '443' : '80'). -
Malformed JSONC in README (
README.md:58): Therun_worker_firstvalue"!/assets]is missing the closing quote and bracket. Should be"!/assets"].
Minor Issues
-
Stray
value.jsin example root:examples/vite-sandbox/value.jsappears to be a leftover -- the host counter usessrc/value.jsand the sandbox usessandbox-app/src/value.js. This file isn't referenced anywhere. -
Missing newline at end of file:
sandbox-app/index.htmlis missing a trailing newline.
| .then((ports) => ports.find((p) => p.port === VITE_PORT)); | ||
|
|
||
| if (!port) { | ||
| port = await sandbox.exposePort(VITE_PORT, { hostname: url.host }); |
There was a problem hiding this comment.
Startup failure creates permanently broken state. exposePort() is called before the server is confirmed running. If startProcess() or waitForPort() throws, the port stays exposed but has no server behind it. Subsequent requests find the exposed port at line 29, skip the startup block, and return a non-functional URL.
Fix by moving exposePort() after waitForPort():
if (!port) {
const process = await sandbox.startProcess('npm run dev', {
processId: 'vite-dev-server',
cwd: '/app',
env: {
VITE_BASE: VITE_BASE,
VITE_PORT: `${VITE_PORT}`,
VITE_HMR_CLIENT_PORT: url.port ? `${url.port}` : undefined
}
});
await process.waitForPort(VITE_PORT);
port = await sandbox.exposePort(VITE_PORT, { hostname: url.host });
}
examples/vite-sandbox/src/worker.js
Outdated
| env: { | ||
| VITE_BASE: VITE_BASE, | ||
| VITE_PORT: `${VITE_PORT}`, | ||
| VITE_HMR_CLIENT_PORT: url.port ? `${url.port}` : undefined |
There was a problem hiding this comment.
When deployed on HTTPS (port 443) or HTTP (port 80), url.port returns "" per the URL spec (standard ports are omitted). The empty string is falsy, so VITE_HMR_CLIENT_PORT becomes undefined. The sandbox's vite.config.js:28 then defaults to clientPort: 3000, causing the HMR client to connect on port 3000 -- which won't work in production.
| VITE_HMR_CLIENT_PORT: url.port ? `${url.port}` : undefined | |
| VITE_HMR_CLIENT_PORT: url.port || (url.protocol === 'https:' ? '443' : '80') |
examples/vite-sandbox/README.md
Outdated
| "assets": { | ||
| "binding": "Assets", | ||
| "not_found_handling": "none", | ||
| "run_worker_first": ["/*", "!/assets] |
There was a problem hiding this comment.
Malformed JSONC: missing closing " and ]. Users who copy-paste this will get a parse error.
| "run_worker_first": ["/*", "!/assets] | |
| "run_worker_first": ["/*", "!/assets"] |
|
All review comments have been posted successfully. Here's a summary of what I reviewed and posted on PR #434: Main review comment covering all findings, plus 3 inline comments on specific lines:
I deliberately did not re-raise the biome.json formatter concern from earlier reviews since that change is no longer part of this PR. I also confirmed the Docker version (0.7.20) matches the current SDK version, and the static sandbox ID ( |
🐳 Docker Images Published
Usage: FROM cloudflare/sandbox:0.0.0-pr-434-0365672Version: 📦 Standalone BinaryFor arbitrary Dockerfiles: COPY --from=cloudflare/sandbox:0.0.0-pr-434-0365672 /container-server/sandbox /sandbox
ENTRYPOINT ["/sandbox"]Download via GitHub CLI: gh run download 23601722748 -n sandbox-binaryExtract from Docker: docker run --rm cloudflare/sandbox:0.0.0-pr-434-0365672 cat /container-server/sandbox > sandbox && chmod +x sandbox |
url.port returns empty string for standard ports (443/80), causing the sandbox HMR client to fall back to the wrong port. Resolve the port explicitly so WebSocket connections work in production.
| import react from '@vitejs/plugin-react'; | ||
| import { defineConfig } from 'vite'; | ||
|
|
||
| function incrementCounter() { |
There was a problem hiding this comment.
🟡 Function named incrementCounter actually decrements the counter
In sandbox-app/vite.config.js:5, the function is named incrementCounter but its body decrements: count-- at line 14. The README confirms the intended behavior is decrement ("The counter in the iframed sandbox will decrement once per second"), so the function name is wrong. This violates the repository's naming clarity principle and will confuse anyone reading the example code.
| function incrementCounter() { | |
| function decrementCounter() { |
Was this helpful? React with 👍 or 👎 to provide feedback.
| import { getSandbox, proxyToSandbox } from '@cloudflare/sandbox'; | ||
|
|
||
| export { Sandbox } from '@cloudflare/sandbox'; | ||
|
|
||
| const VITE_PORT = 5173; | ||
| const VITE_BASE = '/_/'; | ||
|
|
||
| export default { | ||
| async fetch(request, env) { | ||
| const proxiedResponse = await proxyToSandbox(request, env); | ||
| if (proxiedResponse) { | ||
| return proxiedResponse; | ||
| } | ||
|
|
||
| const url = new URL(request.url); | ||
|
|
||
| if (url.pathname === '/api/sandbox') { | ||
| return handleAPISandboxRoute(url, env); | ||
| } | ||
|
|
||
| return new Response('Not Found', { status: 404 }); | ||
| } | ||
| }; | ||
|
|
||
| async function handleAPISandboxRoute(url, env) { | ||
| const sandbox = getSandbox(env.Sandbox, 'vite-sandbox'); | ||
|
|
||
| let port = await sandbox | ||
| .getExposedPorts(url.host) | ||
| .then((ports) => ports.find((p) => p.port === VITE_PORT)); | ||
|
|
||
| if (!port) { | ||
| port = await sandbox.exposePort(VITE_PORT, { hostname: url.host }); | ||
|
|
||
| const process = await sandbox.startProcess('npm run dev', { | ||
| processId: 'vite-dev-server', | ||
| cwd: '/app', | ||
| env: { | ||
| VITE_BASE: VITE_BASE, | ||
| VITE_PORT: `${VITE_PORT}`, | ||
| VITE_HMR_CLIENT_PORT: | ||
| url.port || (url.protocol === 'https:' ? '443' : '80') | ||
| } | ||
| }); | ||
| await process.waitForPort(VITE_PORT); | ||
| } | ||
|
|
||
| return Response.json({ url: `${port.url.replace(/\/$/, '')}${VITE_BASE}` }); |
There was a problem hiding this comment.
🔴 Example uses JavaScript instead of TypeScript, violating CONTRIBUTING.md rule
CONTRIBUTING.md states under Code Style: "Use TypeScript for all code." Every other example in the repository (examples/alpine, examples/minimal, examples/desktop, examples/openai-agents, etc.) uses TypeScript (.ts/.tsx), but the new vite-sandbox example uses JavaScript (.js/.jsx) for all its source files: src/worker.js, src/App.jsx, src/Counter.jsx, src/main.jsx, and src/value.js. This is inconsistent with the repository's explicit coding standard and established conventions.
Prompt for agents
Rename all JavaScript files in examples/vite-sandbox/src/ from .js/.jsx to .ts/.tsx to match the TypeScript convention used by every other example in the repository. Specifically:
- examples/vite-sandbox/src/worker.js → worker.ts
- examples/vite-sandbox/src/App.jsx → App.tsx
- examples/vite-sandbox/src/Counter.jsx → Counter.tsx
- examples/vite-sandbox/src/main.jsx → main.tsx
- examples/vite-sandbox/src/value.js → value.ts
Add appropriate type annotations (e.g., typing the env parameter in the worker fetch handler, adding React component return types). Update the HTML file at examples/vite-sandbox/index.html to reference src/main.tsx instead of src/main.jsx. Add a tsconfig.json to the example. Also update the package.json to include typescript as a devDependency.
Was this helpful? React with 👍 or 👎 to provide feedback.
This PR introduces an example project demonstrating a Vite React application embedded in a sandbox hosted by a Vite React application. A "counter" script changes the sandbox App.jsx file to demonstrate hot module reloading (HMR).
Screen.Recording.2026-03-16.at.20.22.42.mov
Setup
Start the development server:
cd examples/vite-sandbox npm startUsage
This is a non-interactive demo. The counters in both host and iframed sandbox will increment/decrement once per second to demonstrate that the hot module reloading is working over websockets between browser and sandbox.
Deploy
Implementation Notes
Hosting two Vite servers on the same port along with the Cloudflare wrangler server has the potential for unexpected behavior.
We refer to the current directory as the "host" server and the one loaded in the sandbox as the "sandbox" server. The Cloudflare services (workers, assets, storage etc.) are referred to as wrangler. Configuration for the host Vite server is in the root vite.config.js, the Cloudflare config is in wrangler.jsonc and the sandbox Vite config is in sandbox-app/vite.config.js.
This repository has been setup in a way to reduce the confusion.
appTypeset to"custom"to disable Vite handling HTML.server.hmrhas been set to run on a different port to the Vite dev server. This reduces the chance of conflicts between the host and sandbox HMR websockets.VITE_CLIENT_PORTenvironment variable so that the HMR server is configured correctly.