Skip to content

Commit 9f14ee5

Browse files
committed
Add example of vite running a dev server in a sandbox
1 parent 4a70512 commit 9f14ee5

File tree

21 files changed

+1711
-2485
lines changed

21 files changed

+1711
-2485
lines changed

examples/vite-sandbox/.gitignore

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# Created by https://www.toptal.com/developers/gitignore/api/macos,node,git
2+
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,node,git
3+
4+
### Git ###
5+
# Created by git for backups. To disable backups in Git:
6+
# $ git config --global mergetool.keepBackup false
7+
*.orig
8+
9+
# Created by git when using merge tools for conflicts
10+
*.BACKUP.*
11+
*.BASE.*
12+
*.LOCAL.*
13+
*.REMOTE.*
14+
*_BACKUP_*.txt
15+
*_BASE_*.txt
16+
*_LOCAL_*.txt
17+
*_REMOTE_*.txt
18+
19+
### macOS ###
20+
# General
21+
.DS_Store
22+
.AppleDouble
23+
.LSOverride
24+
25+
# Icon must end with two \r
26+
Icon
27+
28+
29+
# Thumbnails
30+
._*
31+
32+
# Files that might appear in the root of a volume
33+
.DocumentRevisions-V100
34+
.fseventsd
35+
.Spotlight-V100
36+
.TemporaryItems
37+
.Trashes
38+
.VolumeIcon.icns
39+
.com.apple.timemachine.donotpresent
40+
41+
# Directories potentially created on remote AFP share
42+
.AppleDB
43+
.AppleDesktop
44+
Network Trash Folder
45+
Temporary Items
46+
.apdisk
47+
48+
### macOS Patch ###
49+
# iCloud generated files
50+
*.icloud
51+
52+
### Node ###
53+
# Logs
54+
logs
55+
*.log
56+
npm-debug.log*
57+
yarn-debug.log*
58+
yarn-error.log*
59+
lerna-debug.log*
60+
.pnpm-debug.log*
61+
62+
# Diagnostic reports (https://nodejs.org/api/report.html)
63+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
64+
65+
# Runtime data
66+
pids
67+
*.pid
68+
*.seed
69+
*.pid.lock
70+
71+
# Directory for instrumented libs generated by jscoverage/JSCover
72+
lib-cov
73+
74+
# Coverage directory used by tools like istanbul
75+
coverage
76+
*.lcov
77+
78+
# nyc test coverage
79+
.nyc_output
80+
81+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
82+
.grunt
83+
84+
# Bower dependency directory (https://bower.io/)
85+
bower_components
86+
87+
# node-waf configuration
88+
.lock-wscript
89+
90+
# Compiled binary addons (https://nodejs.org/api/addons.html)
91+
build/Release
92+
93+
# Dependency directories
94+
node_modules/
95+
jspm_packages/
96+
97+
# Snowpack dependency directory (https://snowpack.dev/)
98+
web_modules/
99+
100+
# TypeScript cache
101+
*.tsbuildinfo
102+
103+
# Optional npm cache directory
104+
.npm
105+
106+
# Optional eslint cache
107+
.eslintcache
108+
109+
# Optional stylelint cache
110+
.stylelintcache
111+
112+
# Microbundle cache
113+
.rpt2_cache/
114+
.rts2_cache_cjs/
115+
.rts2_cache_es/
116+
.rts2_cache_umd/
117+
118+
# Optional REPL history
119+
.node_repl_history
120+
121+
# Output of 'npm pack'
122+
*.tgz
123+
124+
# Yarn Integrity file
125+
.yarn-integrity
126+
127+
# dotenv environment variable files
128+
.env
129+
.env.development.local
130+
.env.test.local
131+
.env.production.local
132+
.env.local
133+
134+
# parcel-bundler cache (https://parceljs.org/)
135+
.cache
136+
.parcel-cache
137+
138+
# Next.js build output
139+
.next
140+
out
141+
142+
# Nuxt.js build / generate output
143+
.nuxt
144+
dist
145+
146+
# Gatsby files
147+
.cache/
148+
# Comment in the public line in if your project uses Gatsby and not Next.js
149+
# https://nextjs.org/blog/next-9-1#public-directory-support
150+
# public
151+
152+
# vuepress build output
153+
.vuepress/dist
154+
155+
# vuepress v2.x temp and cache directory
156+
.temp
157+
158+
# Docusaurus cache and generated files
159+
.docusaurus
160+
161+
# Serverless directories
162+
.serverless/
163+
164+
# FuseBox cache
165+
.fusebox/
166+
167+
# DynamoDB Local files
168+
.dynamodb/
169+
170+
# TernJS port file
171+
.tern-port
172+
173+
# Stores VSCode versions used for testing VSCode extensions
174+
.vscode-test
175+
176+
# yarn v2
177+
.yarn/cache
178+
.yarn/unplugged
179+
.yarn/build-state.yml
180+
.yarn/install-state.gz
181+
.pnp.*
182+
183+
### Node Patch ###
184+
# Serverless Webpack directories
185+
.webpack/
186+
187+
# Optional stylelint cache
188+
189+
# SvelteKit build / generate output
190+
.svelte-kit
191+
192+
# End of https://www.toptal.com/developers/gitignore/api/macos,node,git
193+
194+
### Wrangler ###
195+
.wrangler/
196+
.env*
197+
!.env.example
198+
.dev.vars*
199+
!.dev.vars.example

examples/vite-sandbox/Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM docker.io/cloudflare/sandbox:0.7.6
2+
3+
WORKDIR /app
4+
COPY sandbox-app/package.json ./
5+
RUN npm install
6+
COPY sandbox-app/ ./
7+
8+
# Required during local development to access exposed ports
9+
EXPOSE 5173

examples/vite-sandbox/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Vite dev server with Cloudflare Sandbox
2+
3+
An example 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).
4+
5+
## Setup
6+
7+
Start the development server:
8+
9+
```bash
10+
npm start
11+
```
12+
13+
## Usage
14+
15+
This is a non-interactive demo. The counter in the iframed sandbox will increment once per second to demonstrate that the hot module reloading is working over websockets between browser and sandbox.
16+
17+
## Deploy
18+
19+
```bash
20+
npm run deploy
21+
```
22+
23+
## Implementation Notes
24+
25+
Hosting two Vite servers on the same port along with the Cloudflare wrangler server has the potential for unexpected behavior.
26+
27+
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.
28+
29+
This repository has been setup in a way to reduce the confusion.
30+
31+
1. We assume static assets will be served by Cloudflare. The host Vite server has `appType` set to `"custom"` to disable Vite handling HTML.
32+
2. The hot module reloading server is configured under `server.hmr` has 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.
33+
3. Wrangler has been configured to pass all requests through to the worker rather than serving static assets first. This ensures that we have the opportunity to proxy requests to the sandbox before serving assets.
34+
35+
The code looks like:
36+
37+
```ts
38+
async fetch(request, env) {
39+
// 1. Attempt to proxy the request to a Sandbox.
40+
const proxiedResponse = await proxyToSandbox(request, env);
41+
if (proxiedResponse) {
42+
return proxiedResponse;
43+
}
44+
45+
// 2. Worker specific code follows...
46+
47+
// 3. Otherwise fallback to serving static assets (including index.html)
48+
const url = new URL(request.url);
49+
if (url.pathname.endsWith("/")) {
50+
url.pathname = `${url.pathname}index.html`;
51+
}
52+
return env.Assets.fetch(new Request(url.href, request));
53+
}
54+
```
55+
4. We pass the host port via the `VITE_CLIENT_PORT` environment variable so that the HMR server is configured correctly.

examples/vite-sandbox/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Vite Sandbox Host</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/main.jsx"></script>
11+
</body>
12+
</html>

examples/vite-sandbox/package.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "@cloudflare/sandbox-vite-example",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"private": true,
6+
"description": "An example of running a Vite dev server inside a Cloudflare Sandbox",
7+
"scripts": {
8+
"deploy": "vite build && wrangler deploy",
9+
"dev": "vite",
10+
"start": "vite"
11+
},
12+
"devDependencies": {
13+
"@cloudflare/sandbox": "*",
14+
"@vitejs/plugin-react": "^4.0.0",
15+
"react": "^18.0.0",
16+
"react-dom": "^18.0.0",
17+
"vite": "^6.0.0",
18+
"wrangler": "^4.63.0"
19+
},
20+
"author": "",
21+
"license": "MIT",
22+
"dependencies": {
23+
"@cloudflare/vite-plugin": "^1.29.0"
24+
}
25+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!doctype html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite Sandbox</title>
8+
</head>
9+
10+
<body>
11+
<div id="sandbox-app"></div>
12+
<script type="module" src="./src/main.jsx"></script>
13+
</body>
14+
15+
</html>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "vite-app",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"scripts": {
6+
"dev": "vite --host 0.0.0.0 --port 5173"
7+
},
8+
"dependencies": {
9+
"react": "^18.0.0",
10+
"react-dom": "^18.0.0"
11+
},
12+
"devDependencies": {
13+
"@vitejs/plugin-react": "^4.0.0",
14+
"vite": "^6.0.0"
15+
}
16+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Counter } from "./Counter.jsx";
2+
3+
const panelStyle = {
4+
display: "flex",
5+
flexDirection: "column",
6+
alignItems: "center",
7+
justifyContent: "center",
8+
gap: 16,
9+
height: "100dvh",
10+
padding: 24,
11+
boxSizing: "border-box",
12+
fontFamily: "sans-serif",
13+
background: "#111",
14+
color: "#eee",
15+
};
16+
17+
const labelStyle = {
18+
fontSize: 18,
19+
color: "#888",
20+
margin: 0,
21+
textAlign: "center",
22+
maxWidth: 400,
23+
};
24+
25+
export default function App() {
26+
return (
27+
<div style={panelStyle}>
28+
<p style={labelStyle}>
29+
This is the Sandbox frame. The counter is decremented every second by
30+
hot module reloading.
31+
</p>
32+
<Counter />
33+
</div>
34+
);
35+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { count } from "./value.js";
2+
3+
const countStyle = {
4+
fontSize: 96,
5+
fontWeight: 700,
6+
margin: 0,
7+
lineHeight: 1,
8+
color: "#eee",
9+
};
10+
11+
export function Counter() {
12+
return <p style={countStyle}>{count}</p>;
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { StrictMode } from "react";
2+
import { createRoot } from "react-dom/client";
3+
import App from "./App.jsx";
4+
5+
createRoot(document.getElementById("sandbox-app")).render(
6+
<StrictMode>
7+
<App />
8+
</StrictMode>,
9+
);

0 commit comments

Comments
 (0)