vite-plugin-server-actions 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +20 -0
- package/.prettierrc +7 -0
- package/README.md +192 -0
- package/examples/todo-app/.prettierrc +17 -0
- package/examples/todo-app/README.md +58 -0
- package/examples/todo-app/index.html +12 -0
- package/examples/todo-app/jsconfig.json +32 -0
- package/examples/todo-app/package.json +22 -0
- package/examples/todo-app/src/App.svelte +155 -0
- package/examples/todo-app/src/actions/auth.server.js +14 -0
- package/examples/todo-app/src/actions/todo.server.js +57 -0
- package/examples/todo-app/src/app.css +0 -0
- package/examples/todo-app/src/main.js +8 -0
- package/examples/todo-app/svelte.config.js +7 -0
- package/examples/todo-app/todos.json +27 -0
- package/examples/todo-app/vite.config.js +12 -0
- package/examples/todo-app/yarn.lock +658 -0
- package/package.json +39 -0
- package/src/index.js +192 -0
package/.editorconfig
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
end_of_line = lf
|
|
5
|
+
insert_final_newline = true
|
|
6
|
+
indent_style = tab
|
|
7
|
+
indent_size = 2
|
|
8
|
+
charset = utf-8
|
|
9
|
+
trim_trailing_whitespace = true
|
|
10
|
+
|
|
11
|
+
[test/**/expected.css]
|
|
12
|
+
insert_final_newline = false
|
|
13
|
+
|
|
14
|
+
[package.json]
|
|
15
|
+
indent_style = space
|
|
16
|
+
|
|
17
|
+
[*.md]
|
|
18
|
+
trim_trailing_whitespace = true
|
|
19
|
+
indent_size = 2
|
|
20
|
+
indent_style = space
|
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# 🚀 Vite Server Actions
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/vite-plugin-server-actions)
|
|
4
|
+
[](https://www.npmjs.com/package/vite-plugin-server-actions)
|
|
5
|
+
[](https://github.com/HelgeSverre/vite-plugin-server-actions/actions)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
> 🚧 **Experimental:** This is currently a proof of concept. Use at your own risk.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
**Vite Server Actions** is a Vite plugin that makes it easy to create functions (actions) that runs on the server, while
|
|
12
|
+
allowing you to call them from the client-side as if they were local functions.
|
|
13
|
+
|
|
14
|
+
## ✨ Features
|
|
15
|
+
|
|
16
|
+
- 🔄 Automatic API endpoint creation for server functions (e.g. `POST /api/todos/addTodo`)
|
|
17
|
+
- 🔗 Seamless client-side proxies for easy usage (e.g. `import {addTodo} from './server/todos.server.js'`)
|
|
18
|
+
- 🛠 Support for both development and production environments ( `vite build` )
|
|
19
|
+
- 🚀 Zero-config setup for instant productivity
|
|
20
|
+
|
|
21
|
+
## 🚀 Quick Start
|
|
22
|
+
|
|
23
|
+
1. Install the plugin:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Install using npm
|
|
27
|
+
npm install vite-plugin-server-actions
|
|
28
|
+
|
|
29
|
+
# Install using yarn
|
|
30
|
+
yarn add vite-plugin-server-actions
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. Add to your `vite.config.js`:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
import {defineConfig} from "vite";
|
|
37
|
+
import serverActions from "helgesverre/vite-plugin-server-actions";
|
|
38
|
+
|
|
39
|
+
export default defineConfig({
|
|
40
|
+
plugins: [serverActions()],
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
3. Create a `[whatever].server.js` file anywhere in your project:
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
// ex: src/actions/todo.server.js
|
|
48
|
+
import fs from "fs";
|
|
49
|
+
import path from "path";
|
|
50
|
+
|
|
51
|
+
const TODO_FILE_PATH = path.join(process.cwd(), "list-of-todos.json");
|
|
52
|
+
|
|
53
|
+
export async function deleteTodoById(id) {
|
|
54
|
+
const data = fs.readFileSync(TODO_FILE_PATH, "utf-8");
|
|
55
|
+
const todos = JSON.parse(data);
|
|
56
|
+
const newTodos = todos.filter((todo) => todo.id !== id);
|
|
57
|
+
fs.writeFileSync(TODO_FILE_PATH, JSON.stringify(newTodos, null, 2));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function saveTodoToJsonFile(todo) {
|
|
61
|
+
const data = fs.readFileSync(TODO_FILE_PATH, "utf-8");
|
|
62
|
+
const todos = JSON.parse(data);
|
|
63
|
+
todos.push(todo);
|
|
64
|
+
fs.writeFileSync(TODO_FILE_PATH, JSON.stringify(todos, null, 2));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function listTodos() {
|
|
68
|
+
const data = fs.readFileSync(TODO_FILE_PATH, "utf-8");
|
|
69
|
+
return JSON.parse(data);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
4. Import and use your server actions in your client-side code:
|
|
74
|
+
|
|
75
|
+
```svelte
|
|
76
|
+
<!-- ex: src/App.svelte -->
|
|
77
|
+
<script>
|
|
78
|
+
import { deleteTodoById, listTodos, saveTodoToJsonFile } from "./actions/todo.server.js";
|
|
79
|
+
|
|
80
|
+
let todos = [];
|
|
81
|
+
let newTodoText = "";
|
|
82
|
+
|
|
83
|
+
async function fetchTodos() {
|
|
84
|
+
todos = await listTodos();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function addTodo() {
|
|
88
|
+
await saveTodoToJsonFile({ id: Math.random(), text: newTodoText });
|
|
89
|
+
newTodoText = "";
|
|
90
|
+
await fetchTodos();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function removeTodo(id) {
|
|
94
|
+
await deleteTodoById(id);
|
|
95
|
+
await fetchTodos();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fetchTodos();
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
<div>
|
|
102
|
+
<h1>Todos</h1>
|
|
103
|
+
<ul>
|
|
104
|
+
{#each todos as todo}
|
|
105
|
+
<li>
|
|
106
|
+
{todo.text}
|
|
107
|
+
<button on:click="{() => removeTodo(todo.id)}">Remove</button>
|
|
108
|
+
</li>
|
|
109
|
+
{/each}
|
|
110
|
+
</ul>
|
|
111
|
+
<input type="text" bind:value="{newTodoText}" />
|
|
112
|
+
<button on:click="{addTodo}">Add Todo</button>
|
|
113
|
+
</div>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 🤯 How it works
|
|
117
|
+
|
|
118
|
+
Vite Server Actions works by creating an API endpoint for each server function you define.
|
|
119
|
+
|
|
120
|
+
When you import a server action in your client-side code, Vite Server Actions will intercept the import and return a
|
|
121
|
+
proxy function that sends a request to the server endpoint instead of executing the function locally.
|
|
122
|
+
|
|
123
|
+
In development mode, the server is run as a middleware in the Vite dev server, while in production mode, the server is
|
|
124
|
+
bundled into a single file that can be run with Node.js.
|
|
125
|
+
|
|
126
|
+
### Sequence Diagram
|
|
127
|
+
|
|
128
|
+
```mermaid
|
|
129
|
+
sequenceDiagram
|
|
130
|
+
participant Client
|
|
131
|
+
participant Vite Dev Server
|
|
132
|
+
participant Plugin Middleware
|
|
133
|
+
participant Server Function
|
|
134
|
+
participant File System
|
|
135
|
+
Client ->> Vite Dev Server: import { addTodo } from './server/todos.server.js'
|
|
136
|
+
Vite Dev Server ->> Client: Returns proxied function
|
|
137
|
+
Client ->> Client: Call addTodo({ text: 'New todo' })
|
|
138
|
+
Client ->> Vite Dev Server: POST /api/todos/addTodo
|
|
139
|
+
Vite Dev Server ->> Plugin Middleware: Handle POST request
|
|
140
|
+
Plugin Middleware ->> Server Function: Call addTodo function
|
|
141
|
+
Server Function ->> File System: Read todos.json
|
|
142
|
+
File System ->> Server Function: Return current todos
|
|
143
|
+
Server Function ->> Server Function: Add new todo
|
|
144
|
+
Server Function ->> File System: Write updated todos.json
|
|
145
|
+
File System ->> Server Function: Write confirmation
|
|
146
|
+
Server Function ->> Plugin Middleware: Return new todo
|
|
147
|
+
Plugin Middleware ->> Vite Dev Server: Send JSON response
|
|
148
|
+
Vite Dev Server ->> Client: Return new todo data
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## 🔧 Configuration
|
|
152
|
+
|
|
153
|
+
Vite Server Actions works out of the box, but you can customize it:
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
serverActions({
|
|
157
|
+
// Options (coming soon)
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## 🛠️ Configuration Options
|
|
162
|
+
|
|
163
|
+
TODO: Add configuration options and descriptions
|
|
164
|
+
|
|
165
|
+
| Option | Type | Default | Description |
|
|
166
|
+
|------------------|----------------------------------------|-------------|----------------------------------|
|
|
167
|
+
| logLevel | 'error' \| 'warn' \| 'info' \| 'debug' | 'info' | Server log level |
|
|
168
|
+
| serverPath | string | '/api' | Base path for server endpoints |
|
|
169
|
+
| serverPort | number | 3000 | Port for the server |
|
|
170
|
+
| serverHost | string | 'localhost' | Host for the server |
|
|
171
|
+
| serverMiddleware | (app: Express) => void | - | Custom middleware for the server |
|
|
172
|
+
|
|
173
|
+
## TODO
|
|
174
|
+
|
|
175
|
+
This is a proof of concept, and things are still missing, such as:
|
|
176
|
+
|
|
177
|
+
- [ ] Add configuration options
|
|
178
|
+
- [ ] Add tests
|
|
179
|
+
- [ ] Allow customizing the HTTP method for each action (e.g. `GET`, `POST`, `PUT`, `DELETE`)
|
|
180
|
+
- [ ] Make sure name collisions are handled correctly
|
|
181
|
+
- [ ] Make sure the actions are only available on the server when running in production mode.
|
|
182
|
+
- [ ] Add more examples (Vue, React, etc.)
|
|
183
|
+
- [ ] Publish to npm
|
|
184
|
+
|
|
185
|
+
## 🤝 Contributing
|
|
186
|
+
|
|
187
|
+
Contributions, issues, and feature requests are welcome! Feel free to
|
|
188
|
+
check [issues page](https://github.com/helgesverre/vite-plugin-server-actions/issues).
|
|
189
|
+
|
|
190
|
+
## 📝 License
|
|
191
|
+
|
|
192
|
+
This project is [MIT](https://opensource.org/licenses/MIT) licensed.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
|
3
|
+
"overrides": [
|
|
4
|
+
{
|
|
5
|
+
"files": "*.svelte",
|
|
6
|
+
"options": {
|
|
7
|
+
"parser": "svelte",
|
|
8
|
+
"svelteAllowShorthand": false
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"printWidth": 120,
|
|
13
|
+
"quoteProps": "consistent",
|
|
14
|
+
"semi": true,
|
|
15
|
+
"singleQuote": false,
|
|
16
|
+
"bracketSameLine": false
|
|
17
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Vite Server Actions - TODO App Example
|
|
2
|
+
|
|
3
|
+
This is an example of a simple TODO application that
|
|
4
|
+
uses [Vite Server Actions](https://github.com/HelgeSverre/vite-plugin-server-actions)
|
|
5
|
+
and [Svelte](https://svelte.dev/) to demonstrate a real-world use case, where server actions are used to save, list, and
|
|
6
|
+
delete TODOs, with data stored in a JSON file.
|
|
7
|
+
|
|
8
|
+
## 🚀 Quick Start
|
|
9
|
+
|
|
10
|
+
### Clone the repository and install dependencies
|
|
11
|
+
|
|
12
|
+
```shell
|
|
13
|
+
git clone
|
|
14
|
+
cd examples/todo-app
|
|
15
|
+
npm install
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Run in development mode
|
|
19
|
+
|
|
20
|
+
```shell
|
|
21
|
+
npm install
|
|
22
|
+
npm run dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Open [http://localhost:5173](http://localhost:5173) to view it in your browser. (Note: The port may vary, check the
|
|
26
|
+
console output for the correct port)
|
|
27
|
+
|
|
28
|
+
## Build and run in production mode
|
|
29
|
+
|
|
30
|
+
Server Actions works in production mode by bundling the server into a single file that can be run with Node.js.
|
|
31
|
+
|
|
32
|
+
Here's how you can build and run the example in production mode:
|
|
33
|
+
|
|
34
|
+
```shell
|
|
35
|
+
# Install dependencies and build the project
|
|
36
|
+
npm install
|
|
37
|
+
npm run build
|
|
38
|
+
|
|
39
|
+
# Run the generated express.js server
|
|
40
|
+
node dist/server.js
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
|
44
|
+
|
|
45
|
+
## 📁 Project Structure
|
|
46
|
+
|
|
47
|
+
The important files and directories in this example are:
|
|
48
|
+
|
|
49
|
+
- `src/` - Contains the client-side Svelte application
|
|
50
|
+
- `src/actions/` - Contains the server action files (functions that are run on the server, note the `.server.js` suffix)
|
|
51
|
+
- `src/actions/todo.server.js` - Contains server actions for managing TODOs
|
|
52
|
+
- `src/actions/auth.server.js` - Contains a dummy server action for demonstration purposes
|
|
53
|
+
- `src/App.svelte` - The main Svelte component that imports and calls the server actions to manage TODOs.
|
|
54
|
+
- `vite.config.js` - Vite configuration file that includes the Server Actions plugin `serverActions()`
|
|
55
|
+
- `dist/` - The output directory for the production build.
|
|
56
|
+
- `dist/server.js` - The express server that serves the client-side application and the server actions (automatically
|
|
57
|
+
created by the plugin)
|
|
58
|
+
- `todos.json` - The JSON file where the TODOs are stored (serves the purpose of a simple database for this example)
|
|
@@ -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>TODO Example Svelte</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.js"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"moduleResolution": "bundler",
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
/**
|
|
7
|
+
* svelte-preprocess cannot figure out whether you have
|
|
8
|
+
* a value or a type, so tell TypeScript to enforce using
|
|
9
|
+
* `import type` instead of `import` for Types.
|
|
10
|
+
*/
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
/**
|
|
15
|
+
* To have warnings / errors of the Svelte compiler at the
|
|
16
|
+
* correct position, enable source maps by default.
|
|
17
|
+
*/
|
|
18
|
+
"sourceMap": true,
|
|
19
|
+
"esModuleInterop": true,
|
|
20
|
+
"skipLibCheck": true,
|
|
21
|
+
/**
|
|
22
|
+
* Typecheck JS in `.svelte` and `.js` files by default.
|
|
23
|
+
* Disable this if you'd like to use dynamic types.
|
|
24
|
+
*/
|
|
25
|
+
"checkJs": true
|
|
26
|
+
},
|
|
27
|
+
/**
|
|
28
|
+
* Use global.d.ts instead of compilerOptions.types
|
|
29
|
+
* to avoid limiting type declarations.
|
|
30
|
+
*/
|
|
31
|
+
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "todo-app",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"format": "npx prettier --write .",
|
|
10
|
+
"preview": "vite preview",
|
|
11
|
+
"sort": "npx sort-package-json"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
|
15
|
+
"prettier": "^3.3.3",
|
|
16
|
+
"prettier-plugin-svelte": "^3.2.6",
|
|
17
|
+
"prettier-plugin-tailwindcss": "^0.6.5",
|
|
18
|
+
"svelte": "^4.2.18",
|
|
19
|
+
"vite": "^5.4.1",
|
|
20
|
+
"vite-plugin-inspect": "^0.8.7"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { addTodo, deleteTodo, getTodos, updateTodo } from "./actions/todo.server.js";
|
|
4
|
+
import { login } from "./actions/auth.server.js";
|
|
5
|
+
|
|
6
|
+
let todos = [];
|
|
7
|
+
let newTodoText = "";
|
|
8
|
+
|
|
9
|
+
onMount(() => {
|
|
10
|
+
login("admin", "admin");
|
|
11
|
+
loadTodos();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
async function loadTodos() {
|
|
15
|
+
console.log("Loading todos...");
|
|
16
|
+
todos = await getTodos();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function handleAddTodo() {
|
|
20
|
+
if (!newTodoText.trim()) return;
|
|
21
|
+
|
|
22
|
+
await addTodo({ text: newTodoText });
|
|
23
|
+
await loadTodos();
|
|
24
|
+
newTodoText = "";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function handleToggleTodo(id) {
|
|
28
|
+
const todo = todos.find((t) => t.id === id);
|
|
29
|
+
if (todo) {
|
|
30
|
+
const updatedTodo = await updateTodo(id, { completed: !todo.completed });
|
|
31
|
+
todos = todos.map((t) => (t.id === id ? updatedTodo : t));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function handleDeleteTodo(id) {
|
|
36
|
+
await deleteTodo(id);
|
|
37
|
+
await loadTodos();
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<main>
|
|
42
|
+
<h1>Todo List</h1>
|
|
43
|
+
|
|
44
|
+
<form class="todo-form" on:submit|preventDefault={handleAddTodo}>
|
|
45
|
+
<input class="todo-input" bind:value={newTodoText} placeholder="Add a new todo" />
|
|
46
|
+
<button class="todo-button" type="submit">Add</button>
|
|
47
|
+
</form>
|
|
48
|
+
|
|
49
|
+
{#if todos.length > 0}
|
|
50
|
+
<ul class="todo-list">
|
|
51
|
+
{#each todos as todo}
|
|
52
|
+
<li class="todo-item">
|
|
53
|
+
<input type="checkbox" checked={todo.completed} on:change={() => handleToggleTodo(todo.id)} />
|
|
54
|
+
<span class:completed={todo.completed}>{todo.text}</span>
|
|
55
|
+
<button class="delete-button" on:click={() => handleDeleteTodo(todo.id)}>Delete</button>
|
|
56
|
+
</li>
|
|
57
|
+
{/each}
|
|
58
|
+
</ul>
|
|
59
|
+
{/if}
|
|
60
|
+
</main>
|
|
61
|
+
|
|
62
|
+
<style>
|
|
63
|
+
/* Basic global styling */
|
|
64
|
+
main {
|
|
65
|
+
font-family: sans-serif;
|
|
66
|
+
max-width: 600px;
|
|
67
|
+
margin: 2rem auto;
|
|
68
|
+
padding: 1rem;
|
|
69
|
+
background-color: #f8f8f8;
|
|
70
|
+
border-radius: 8px;
|
|
71
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
h1 {
|
|
75
|
+
text-align: center;
|
|
76
|
+
color: #333;
|
|
77
|
+
margin-bottom: 1rem;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.todo-form {
|
|
81
|
+
display: flex;
|
|
82
|
+
gap: 0.5rem;
|
|
83
|
+
margin-bottom: 1rem;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.todo-input {
|
|
87
|
+
flex: 1;
|
|
88
|
+
padding: 0.5rem;
|
|
89
|
+
border: 1px solid #ccc;
|
|
90
|
+
border-radius: 4px;
|
|
91
|
+
font-size: 1rem;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.todo-button {
|
|
95
|
+
padding: 0.5rem 1rem;
|
|
96
|
+
font-size: 1rem;
|
|
97
|
+
color: white;
|
|
98
|
+
background-color: #333;
|
|
99
|
+
border: none;
|
|
100
|
+
border-radius: 4px;
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.todo-button:hover {
|
|
105
|
+
background-color: #555;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.todo-list {
|
|
109
|
+
list-style: none;
|
|
110
|
+
padding: 0;
|
|
111
|
+
margin: 0;
|
|
112
|
+
border-top: 1px solid #e0e0e0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.todo-item {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: space-between;
|
|
119
|
+
padding: 0.5rem;
|
|
120
|
+
border-bottom: 1px solid #e0e0e0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.todo-item:last-child {
|
|
124
|
+
border-bottom: none;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.todo-item input[type="checkbox"] {
|
|
128
|
+
margin-right: 1rem;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.todo-item span {
|
|
132
|
+
flex: 1;
|
|
133
|
+
font-size: 1rem;
|
|
134
|
+
color: #333;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.todo-item span.completed {
|
|
138
|
+
text-decoration: line-through;
|
|
139
|
+
color: #888;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.delete-button {
|
|
143
|
+
padding: 0.25rem 0.5rem;
|
|
144
|
+
font-size: 0.875rem;
|
|
145
|
+
color: #fff;
|
|
146
|
+
background-color: #cc0000;
|
|
147
|
+
border: none;
|
|
148
|
+
border-radius: 4px;
|
|
149
|
+
cursor: pointer;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.delete-button:hover {
|
|
153
|
+
background-color: #ff3333;
|
|
154
|
+
}
|
|
155
|
+
</style>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const TODO_FILE = path.join(process.cwd(), "todos.json");
|
|
5
|
+
|
|
6
|
+
async function readTodos() {
|
|
7
|
+
try {
|
|
8
|
+
const data = await fs.readFile(TODO_FILE, "utf8");
|
|
9
|
+
return JSON.parse(data);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.log(error);
|
|
12
|
+
if (error.code === "ENOENT") {
|
|
13
|
+
// File doesn't exist, return an empty array
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
console.error("Error reading todos:", error);
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function writeTodos(todos) {
|
|
22
|
+
try {
|
|
23
|
+
await fs.writeFile(TODO_FILE, JSON.stringify(todos, null, 2), "utf8");
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error("Error writing todos:", error);
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function getTodos() {
|
|
31
|
+
return await readTodos();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function addTodo(todo) {
|
|
35
|
+
const todos = await readTodos();
|
|
36
|
+
const newTodo = { id: Date.now(), text: todo.text, completed: false };
|
|
37
|
+
todos.push(newTodo);
|
|
38
|
+
await writeTodos(todos);
|
|
39
|
+
return newTodo;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function updateTodo(id, updates) {
|
|
43
|
+
const todos = await readTodos();
|
|
44
|
+
const index = todos.findIndex((todo) => todo.id === id);
|
|
45
|
+
if (index !== -1) {
|
|
46
|
+
todos[index] = { ...todos[index], ...updates };
|
|
47
|
+
await writeTodos(todos);
|
|
48
|
+
return todos[index];
|
|
49
|
+
}
|
|
50
|
+
throw new Error("Todo not found");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function deleteTodo(id) {
|
|
54
|
+
const todos = await readTodos();
|
|
55
|
+
const newTodos = todos.filter((todo) => todo.id != id);
|
|
56
|
+
await writeTodos(newTodos);
|
|
57
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": 1,
|
|
4
|
+
"text": "Finish writing documentation",
|
|
5
|
+
"completed": false
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"id": 2,
|
|
9
|
+
"text": "Implement authentication logic",
|
|
10
|
+
"completed": true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": 3,
|
|
14
|
+
"text": "Design user interface",
|
|
15
|
+
"completed": false
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": 4,
|
|
19
|
+
"text": "Test server actions",
|
|
20
|
+
"completed": true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": 5,
|
|
24
|
+
"text": "Deploy application",
|
|
25
|
+
"completed": false
|
|
26
|
+
}
|
|
27
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
|
3
|
+
import serverActions from "../../src/index.js";
|
|
4
|
+
|
|
5
|
+
// https://vitejs.dev/config/
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [
|
|
8
|
+
svelte(),
|
|
9
|
+
serverActions(),
|
|
10
|
+
// ...
|
|
11
|
+
],
|
|
12
|
+
});
|