-
Notifications
You must be signed in to change notification settings - Fork 47.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Copy examples from https://github.com/parcel-bundler/rsc-examples/blo…
…b/main/examples/server/ This allows us to test using the local build.
- Loading branch information
1 parent
68dba2d
commit de0fe68
Showing
16 changed files
with
2,812 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.parcel-cache | ||
.DS_Store | ||
node_modules | ||
dist | ||
todos.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"extends": "@parcel/config-default", | ||
"runtimes": ["...", "@parcel/runtime-rsc"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
{ | ||
"name": "flight-parcel", | ||
"private": true, | ||
"workspaces": [ | ||
"examples/*" | ||
], | ||
"server": "dist/server.js", | ||
"targets": { | ||
"server": { | ||
"source": "src/server.tsx", | ||
"context": "react-server", | ||
"outputFormat": "commonjs", | ||
"includeNodeModules": { | ||
"express": false | ||
} | ||
} | ||
}, | ||
"scripts": { | ||
"prewatch": "cp -r ../../build/oss-experimental/* ./node_modules/", | ||
"prebuild": "cp -r ../../build/oss-experimental/* ./node_modules/", | ||
"watch": "parcel watch", | ||
"build": "parcel build", | ||
"start": "node dist/server.js" | ||
}, | ||
"@parcel/resolver-default": { | ||
"packageExports": true | ||
}, | ||
"dependencies": { | ||
"@parcel/config-default": "2.0.0-dev.1789", | ||
"@parcel/runtime-rsc": "2.13.3-dev.3412", | ||
"@types/parcel-env": "^0.0.6", | ||
"@types/express": "*", | ||
"@types/node": "^22.10.1", | ||
"@types/react": "^19", | ||
"@types/react-dom": "^19", | ||
"express": "^4.18.2", | ||
"parcel": "2.0.0-dev.1787", | ||
"process": "^0.11.10", | ||
"react": "^19", | ||
"react-dom": "^19", | ||
"react-server-dom-parcel": "^0.0.1", | ||
"rsc-html-stream": "^0.0.4", | ||
"ws": "^8.8.1" | ||
}, | ||
"@parcel/bundler-default": { | ||
"minBundleSize": 0 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
'use client'; | ||
|
||
import {ReactNode, useRef} from 'react'; | ||
|
||
export function Dialog({ | ||
trigger, | ||
children, | ||
}: { | ||
trigger: ReactNode; | ||
children: ReactNode; | ||
}) { | ||
let ref = useRef<HTMLDialogElement | null>(null); | ||
return ( | ||
<> | ||
<button onClick={() => ref.current?.showModal()}>{trigger}</button> | ||
<dialog ref={ref} onSubmit={() => ref.current?.close()}> | ||
{children} | ||
</dialog> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import {createTodo} from './actions'; | ||
|
||
export function TodoCreate() { | ||
return ( | ||
<form action={createTodo}> | ||
<label> | ||
Title: <input name="title" /> | ||
</label> | ||
<label> | ||
Description: <textarea name="description" /> | ||
</label> | ||
<label> | ||
Due date: <input type="date" name="dueDate" /> | ||
</label> | ||
<button>Add todo</button> | ||
</form> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import {getTodo, updateTodo} from './actions'; | ||
|
||
export async function TodoDetail({id}: {id: number}) { | ||
let todo = await getTodo(id); | ||
if (!todo) { | ||
return <p>Todo not found</p>; | ||
} | ||
|
||
return ( | ||
<form className="todo" action={updateTodo.bind(null, todo.id)}> | ||
<label> | ||
Title: <input name="title" defaultValue={todo.title} /> | ||
</label> | ||
<label> | ||
Description:{' '} | ||
<textarea name="description" defaultValue={todo.description} /> | ||
</label> | ||
<label> | ||
Due date:{' '} | ||
<input type="date" name="dueDate" defaultValue={todo.dueDate} /> | ||
</label> | ||
<button type="submit">Update todo</button> | ||
</form> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
'use client'; | ||
|
||
import {startTransition, useOptimistic} from 'react'; | ||
import {deleteTodo, setTodoComplete, type Todo as ITodo} from './actions'; | ||
|
||
export function TodoItem({ | ||
todo, | ||
isSelected, | ||
}: { | ||
todo: ITodo; | ||
isSelected: boolean; | ||
}) { | ||
let [isOptimisticComplete, setOptimisticComplete] = useOptimistic( | ||
todo.isComplete, | ||
); | ||
|
||
return ( | ||
<li data-selected={isSelected || undefined}> | ||
<input | ||
type="checkbox" | ||
checked={isOptimisticComplete} | ||
onChange={e => { | ||
startTransition(async () => { | ||
setOptimisticComplete(e.target.checked); | ||
await setTodoComplete(todo.id, e.target.checked); | ||
}); | ||
}} | ||
/> | ||
<a | ||
href={`/todos/${todo.id}`} | ||
aria-current={isSelected ? 'page' : undefined}> | ||
{todo.title} | ||
</a> | ||
<button onClick={() => deleteTodo(todo.id)}>x</button> | ||
</li> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import {TodoItem} from './TodoItem'; | ||
import {getTodos} from './actions'; | ||
|
||
export async function TodoList({id}: {id: number | undefined}) { | ||
let todos = await getTodos(); | ||
return ( | ||
<ul className="todo-list"> | ||
{todos.map(todo => ( | ||
<TodoItem key={todo.id} todo={todo} isSelected={todo.id === id} /> | ||
))} | ||
</ul> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
body { | ||
font-family: system-ui; | ||
color-scheme: light dark; | ||
} | ||
|
||
form { | ||
display: grid; | ||
grid-template-columns: auto 1fr; | ||
flex-direction: column; | ||
max-width: 400px; | ||
gap: 8px; | ||
} | ||
|
||
label { | ||
display: contents; | ||
} | ||
|
||
main { | ||
display: flex; | ||
gap: 32px; | ||
} | ||
|
||
.todo-column { | ||
width: 250px; | ||
} | ||
|
||
header { | ||
display: flex; | ||
align-items: center; | ||
justify-content: space-between; | ||
max-width: 250px; | ||
padding: 8px; | ||
padding-right: 40px; | ||
box-sizing: border-box; | ||
} | ||
|
||
.todo-list { | ||
max-width: 250px; | ||
padding: 0; | ||
list-style: none; | ||
padding-right: 32px; | ||
border-right: 1px solid gray; | ||
|
||
li { | ||
display: flex; | ||
gap: 8px; | ||
padding: 8px; | ||
border-radius: 8px; | ||
accent-color: light-dark(black, white); | ||
|
||
a { | ||
color: inherit; | ||
text-decoration: none; | ||
width: 100%; | ||
} | ||
|
||
&[data-selected] { | ||
background-color: light-dark(#222, #ddd); | ||
color: light-dark(#ddd, #222); | ||
accent-color: light-dark(white, black); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
'use server-entry'; | ||
|
||
import './client'; | ||
import './Todos.css'; | ||
import {Resources} from '@parcel/runtime-rsc'; | ||
import {Dialog} from './Dialog'; | ||
import {TodoDetail} from './TodoDetail'; | ||
import {TodoCreate} from './TodoCreate'; | ||
import {TodoList} from './TodoList'; | ||
|
||
export async function Todos({id}: {id?: number}) { | ||
return ( | ||
<html style={{colorScheme: 'dark light'}}> | ||
<head> | ||
<title>Todos</title> | ||
<Resources /> | ||
</head> | ||
<body> | ||
<header> | ||
<h1>Todos</h1> | ||
<Dialog trigger="+"> | ||
<h2>Add todo</h2> | ||
<TodoCreate /> | ||
</Dialog> | ||
</header> | ||
<main> | ||
<div className="todo-column"> | ||
<TodoList id={id} /> | ||
</div> | ||
{id != null ? <TodoDetail key={id} id={id} /> : <p>Select a todo</p>} | ||
</main> | ||
</body> | ||
</html> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
'use server'; | ||
|
||
import fs from 'fs/promises'; | ||
|
||
export interface Todo { | ||
id: number; | ||
title: string; | ||
description: string; | ||
dueDate: string; | ||
isComplete: boolean; | ||
} | ||
|
||
export async function getTodos(): Promise<Todo[]> { | ||
try { | ||
let contents = await fs.readFile('todos.json', 'utf8'); | ||
return JSON.parse(contents); | ||
} catch { | ||
await fs.writeFile('todos.json', '[]'); | ||
return []; | ||
} | ||
} | ||
|
||
export async function getTodo(id: number): Promise<Todo | undefined> { | ||
let todos = await getTodos(); | ||
return todos.find(todo => todo.id === id); | ||
} | ||
|
||
export async function createTodo(formData: FormData) { | ||
let todos = await getTodos(); | ||
let title = formData.get('title'); | ||
let description = formData.get('description'); | ||
let dueDate = formData.get('dueDate'); | ||
let id = todos.length > 0 ? Math.max(...todos.map(todo => todo.id)) + 1 : 0; | ||
todos.push({ | ||
id, | ||
title: typeof title === 'string' ? title : '', | ||
description: typeof description === 'string' ? description : '', | ||
dueDate: typeof dueDate === 'string' ? dueDate : new Date().toISOString(), | ||
isComplete: false, | ||
}); | ||
await fs.writeFile('todos.json', JSON.stringify(todos)); | ||
} | ||
|
||
export async function updateTodo(id: number, formData: FormData) { | ||
let todos = await getTodos(); | ||
let title = formData.get('title'); | ||
let description = formData.get('description'); | ||
let dueDate = formData.get('dueDate'); | ||
let todo = todos.find(todo => todo.id === id); | ||
if (todo) { | ||
todo.title = typeof title === 'string' ? title : ''; | ||
todo.description = typeof description === 'string' ? description : ''; | ||
todo.dueDate = | ||
typeof dueDate === 'string' ? dueDate : new Date().toISOString(); | ||
await fs.writeFile('todos.json', JSON.stringify(todos)); | ||
} | ||
} | ||
|
||
export async function setTodoComplete(id: number, isComplete: boolean) { | ||
let todos = await getTodos(); | ||
let todo = todos.find(todo => todo.id === id); | ||
if (todo) { | ||
todo.isComplete = isComplete; | ||
await fs.writeFile('todos.json', JSON.stringify(todos)); | ||
} | ||
} | ||
|
||
export async function deleteTodo(id: number) { | ||
let todos = await getTodos(); | ||
let index = todos.findIndex(todo => todo.id === id); | ||
if (index >= 0) { | ||
todos.splice(index, 1); | ||
await fs.writeFile('todos.json', JSON.stringify(todos)); | ||
} | ||
} |
Oops, something went wrong.