Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
HelgeSverre committed Sep 20, 2024
1 parent 28e915c commit ee13761
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 119 deletions.
10 changes: 5 additions & 5 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"printWidth": 120,
"quoteProps": "consistent",
"semi": true,
"singleQuote": false,
"bracketSameLine": false
"printWidth": 120,
"quoteProps": "consistent",
"semi": true,
"singleQuote": false,
"bracketSameLine": false
}
33 changes: 17 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,29 @@ npm install vite-plugin-server-actions
2. Add to your `vite.config.js`:

```javascript
import {defineConfig} from 'vite'
import serverActions from 'helgesverre/vite-plugin-server-actions'
import { defineConfig } from "vite";
import serverActions from "helgesverre/vite-plugin-server-actions";

export default defineConfig({
plugins: [
serverActions(),
]
})
plugins: [serverActions()],
});
```

3. Create a `.server.js` file with your server functions:

```javascript
// api.server.js
export async function getData() {
return {message: 'Hello from the server! 👋'}
return { message: "Hello from the server! 👋" };
}
```

4. Use in your client code:

```javascript
import {getData} from './api.server.js'
import { getData } from "./api.server.js";

getData().then(data => console.log(data))
getData().then((data) => console.log(data));
```

## 🛠 Usage
Expand All @@ -62,12 +60,15 @@ Create functions in `.server.js` files:
// users.server.js
export async function getUsers() {
// Fetch users from database
return [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}]
return [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
}

export async function createUser(userData) {
// Create a new user
return {id: 3, ...userData}
return { id: 3, ...userData };
}
```

Expand All @@ -76,13 +77,13 @@ export async function createUser(userData) {
Import and use as if they were local functions:

```javascript
import {getUsers, createUser} from './users.server.js'
import { getUsers, createUser } from "./users.server.js";

// Get users
const users = await getUsers()
const users = await getUsers();

// Create a new user
const newUser = await createUser({name: 'Charlie'})
const newUser = await createUser({ name: "Charlie" });
```

## 🔧 Configuration
Expand All @@ -92,13 +93,13 @@ Vite Server Actions works out of the box, but you can customize it:
```javascript
serverActions({
// Options (coming soon)
})
});
```

## Configuration options:

| Option | Type | Default | Description |
|--------------------|----------------------------------------|------------------|--------------------------------------------|
| ------------------ | -------------------------------------- | ---------------- | ------------------------------------------ |
| serverFunctionsDir | string | 'src/server' | Directory containing server function files |
| serverOutputFile | string | 'dist/server.js' | Output file for generated server |
| cors | boolean | false | Enable CORS for all routes |
Expand Down
27 changes: 21 additions & 6 deletions examples/todo-app/todos.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
[
{
"id": 2,
"text": "Build an app",
"id": 1726556789250,
"text": "a new todo",
"completed": true
},
{
"id": 1726556786081,
"text": "test",
"id": 1726802630575,
"text": "new todo",
"completed": false
},
{
"id": 1726556789250,
"text": "a new todo",
"id": 1726802632329,
"text": "another one",
"completed": false
},
{
"id": 1726804643450,
"text": "testing 123",
"completed": false
},
{
"id": 1726804643450,
"text": "lorem ipsum",
"completed": true
},
{
"id": 1726806390358,
"text": "testing lanana",
"completed": false
}
]
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vite-plugin-server-actions",
"description": "Seamlessly integrate server-side functions into your Vite projects with automatic API endpoints and client-side proxies.",
"description": "Automatically proxy imported backend functions to a server endpoint in Vite",
"version": "0.1.0",
"type": "module",
"keywords": [
Expand Down
148 changes: 57 additions & 91 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,121 +1,86 @@
import fs from 'fs/promises';
import path from 'path';
import express from 'express';
import fs from "fs/promises";
import path from "path";
import express from "express";

export default function viteServerFunctionsPlugin() {
const serverFunctions = new Map();
let app;
let logger;
const chunks = new Map();

return {
name: 'vite-plugin-server-actions',
name: "vite-plugin-server-actions",

configureServer(server) {
logger = server.config.logger;
// logger.info('🔧 Configuring vite-plugin-server-actions');
app = express();
app.use(express.json()); // Add this line to parse JSON bodies
app.use(express.json());
server.middlewares.use(app);
// logger.info('✅ Express middleware registered with Vite server');
},

async resolveId(source, importer) {
if (source.endsWith('.server.js') && importer) {
// logger.info(`🔍 Resolving server file: ${source} imported in ${importer}`);
if (source.endsWith(".server.js") && importer) {
return source;
}
},

async load(id) {
if (id.endsWith('.server.js')) {
// logger.info(`📂 Loading server file: ${id}`);
const moduleName = path.basename(id, '.server.js');
if (id.endsWith(".server.js")) {
const moduleName = path.basename(id, ".server.js");
const code = await fs.readFile(id, "utf-8");

// logger.info(`📄 Reading content of ${id}`);
const code = await fs.readFile(id, 'utf-8');

// logger.info(`🔎 Extracting exported functions from ${moduleName}`);
const exportRegex = /export\s+(async\s+)?function\s+(\w+)/g;
const functions = [];
let match;
while ((match = exportRegex.exec(code)) !== null) {
functions.push(match[2]);
}
// logger.info(`📊 Found ${functions.length} exported functions in ${moduleName}: ${functions.join(', ')}`);

// Store the functions for this module
serverFunctions.set(moduleName, functions);
console.log(`Found server functions for ${moduleName}: ${functions.join(", ")}`);
serverFunctions.set(moduleName, { functions, id });

// logger.info(`🛠 Creating API endpoints for ${moduleName}`);
if (process.env.NODE_ENV !== 'production') {
functions.forEach(functionName => {
// TODO: extract this to a separate function
if (process.env.NODE_ENV !== "production") {
functions.forEach((functionName) => {
const endpoint = `/api/${moduleName}/${functionName}`;
// logger.info(`🔗 Creating endpoint: ${endpoint}`);
const pad = " ".repeat(4);


app.post(endpoint, async (req, res) => {
// logger.info(pad + `→ Received request at ${endpoint}`);
try {
const module = await import(id);
// logger.info(pad + `✨ Executing ${functionName} in ${moduleName}`);
const result = await module[functionName](...req.body);
// logger.info(pad + `✅ Successfully executed ${functionName}`);
res.json(result || "* No response *");
} catch (error) {
logger.error(pad + `❌ Error in ${functionName}: ${error.message}`);
res.status(500).json({error: error.message});
console.error(`Error in ${functionName}: ${error.message}`);
res.status(500).json({ error: error.message });
}
});
});
}

// logger.info(`🔄 Generating client-side proxy for ${moduleName}`);
let clientProxy = `
// Client-side proxy for ${moduleName}
`;
return generateClientProxy(moduleName, functions);
}
},

functions.forEach(functionName => {
// logger.info(`📝 Adding proxy function for ${functionName}`);
clientProxy += `
export async function ${functionName}(...args) {
console.log('🚀 Calling server function: ${functionName}');
const response = await fetch('/api/${moduleName}/${functionName}', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(args)
});
if (!response.ok) {
console.error('❌ Server request failed for ${functionName}');
throw new Error('Server request failed');
}
console.log('✅ Server request successful for ${functionName}');
return response.json();
}
`;
buildStart() {
serverFunctions.forEach(({ id }, moduleName) => {
const chunkRefId = this.emitFile({
type: "chunk",
id,
name: `${moduleName}.server`,
});

// logger.info(`✅ Generated client-side proxy for ${moduleName}`);
return clientProxy;
}
chunks.set(moduleName, chunkRefId);
});
},

async generateBundle() {
// logger.info('📦 Generating production server.js file');
async generateBundle(options, bundle) {
let serverCode = `
import express from 'express';
const app = express();
app.use(express.json());
`;

serverFunctions.forEach((functions, moduleName) => {
serverCode += `import * as ${moduleName}Module from './${moduleName}.server.js';\n`;
});

serverFunctions.forEach((functions, moduleName) => {
// logger.info(`📄 Adding endpoints for ${moduleName} to production server`);
// TODO: generate imports for server functions
serverCode += "\n// TODO: generate imports for server functions here\n";

functions.forEach(functionName => {
serverFunctions.forEach(({ functions }, moduleName) => {
functions.forEach((functionName) => {
serverCode += `
app.post('/api/${moduleName}/${functionName}', async (req, res) => {
try {
Expand All @@ -134,45 +99,46 @@ export default function viteServerFunctionsPlugin() {
app.listen(port, () => console.log(\`Server listening on port \${port}\`));
`;

// logger.info('💾 Writing production server.js file');
this.emitFile({
type: 'asset',
fileName: 'server.js',
source: serverCode
type: "asset",
fileName: "server.js",
source: serverCode,
});
// logger.info('✅ Production server.js file generated');

// Generate client proxy
const clientProxy = Array.from(serverFunctions.entries())
.map(([moduleName, { functions }]) => generateClientProxy(moduleName, functions))
.join("\n");

this.emitFile({
type: "asset",
fileName: "client.js",
source: clientProxy,
});
},
};
}

let clientProxy = "";
serverFunctions.forEach((functions, moduleName) => {
functions.forEach(functionName => {
// logger.info(`📝 Adding proxy function for ${functionName}`);
clientProxy += `
function generateClientProxy(moduleName, functions) {
let clientProxy = `\n// vite-server-actions client proxy for ${moduleName} module`;
// TODO: Improve this
functions.forEach((functionName) => {
clientProxy += `
export async function ${functionName}(...args) {
console.log('🚀 Calling server function: ${functionName}');
const response = await fetch('/api/${moduleName}/${functionName}', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(args)
});
if (!response.ok) {
console.error('❌ Server request failed for ${functionName}');
throw new Error('Server request failed');
}
console.log('✅ Server request successful for ${functionName}');
return response.json();
}
`;
});
});
// logger.info(`✅ Generated client-side proxy for ${moduleName}`);
this.emitFile({
type: 'asset',
fileName: 'client.js',
source: clientProxy
});
`;
});

}
};
return clientProxy;
}

0 comments on commit ee13761

Please sign in to comment.