frontend-hamroun 1.2.24 → 1.2.26
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/README.md +3 -0
- package/dist/index.d.ts +6 -1
- package/package.json +1 -1
- package/templates/fullstack-app/api/hello.js +11 -0
- package/templates/fullstack-app/index.html +13 -0
- package/templates/fullstack-app/package-lock.json +3130 -0
- package/templates/fullstack-app/package.json +8 -19
- package/templates/fullstack-app/server.js +210 -0
- package/templates/fullstack-app/server.ts +70 -31
- package/templates/fullstack-app/src/client.js +20 -0
- package/templates/fullstack-app/src/main.tsx +9 -0
- package/templates/fullstack-app/src/pages/index.tsx +40 -36
- package/templates/fullstack-app/vite.config.js +40 -0
- package/templates/ssr-template/package-lock.json +95 -1161
- package/templates/ssr-template/package.json +6 -4
- package/templates/ssr-template/readme.md +112 -37
- package/templates/ssr-template/server.js +364 -549
- package/templates/ssr-template/server.ts +166 -14
- package/templates/ssr-template/tsconfig.json +2 -3
@@ -3,29 +3,18 @@
|
|
3
3
|
"version": "0.1.0",
|
4
4
|
"type": "module",
|
5
5
|
"scripts": {
|
6
|
-
"dev": "
|
7
|
-
"
|
8
|
-
"
|
6
|
+
"dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"",
|
7
|
+
"dev:server": "node server.js",
|
8
|
+
"dev:client": "vite",
|
9
|
+
"build": "vite build",
|
10
|
+
"start": "node server.js"
|
9
11
|
},
|
10
12
|
"dependencies": {
|
11
|
-
"bcryptjs": "^2.4.3",
|
12
|
-
"cors": "^2.8.5",
|
13
13
|
"express": "^4.18.2",
|
14
|
-
"frontend-hamroun": "latest"
|
15
|
-
"jsonwebtoken": "^9.0.2",
|
16
|
-
"mongodb": "^5.7.0"
|
14
|
+
"frontend-hamroun": "latest"
|
17
15
|
},
|
18
16
|
"devDependencies": {
|
19
|
-
"
|
20
|
-
"
|
21
|
-
"@types/express": "^4.17.18",
|
22
|
-
"@types/jsonwebtoken": "^9.0.3",
|
23
|
-
"@types/node": "^18.16.19",
|
24
|
-
"autoprefixer": "^10.4.15",
|
25
|
-
"postcss": "^8.4.29",
|
26
|
-
"tailwindcss": "^3.3.3",
|
27
|
-
"ts-node": "^10.9.1",
|
28
|
-
"typescript": "^5.2.2",
|
29
|
-
"vite": "^4.4.9"
|
17
|
+
"concurrently": "^8.0.1",
|
18
|
+
"vite": "^4.3.9"
|
30
19
|
}
|
31
20
|
}
|
@@ -0,0 +1,210 @@
|
|
1
|
+
import express from 'express';
|
2
|
+
import path from 'path';
|
3
|
+
import { fileURLToPath } from 'url';
|
4
|
+
import fs from 'fs';
|
5
|
+
import dotenv from 'dotenv';
|
6
|
+
import { createServer } from 'http';
|
7
|
+
import { Server as SocketServer } from 'socket.io';
|
8
|
+
import compression from 'compression';
|
9
|
+
import cors from 'cors';
|
10
|
+
|
11
|
+
// Load environment variables
|
12
|
+
dotenv.config();
|
13
|
+
|
14
|
+
// Get __dirname equivalent in ESM
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
16
|
+
const __dirname = path.dirname(__filename);
|
17
|
+
|
18
|
+
// Create Express app
|
19
|
+
const app = express();
|
20
|
+
const PORT = process.env.PORT || 3000;
|
21
|
+
const httpServer = createServer(app);
|
22
|
+
|
23
|
+
// Create socket.io server
|
24
|
+
const io = new SocketServer(httpServer, {
|
25
|
+
cors: {
|
26
|
+
origin: process.env.NODE_ENV === 'production' ? false : '*',
|
27
|
+
methods: ['GET', 'POST']
|
28
|
+
}
|
29
|
+
});
|
30
|
+
|
31
|
+
// Middleware setup
|
32
|
+
app.use(express.json());
|
33
|
+
app.use(express.urlencoded({ extended: true }));
|
34
|
+
app.use(compression()); // Add compression for better performance
|
35
|
+
app.use(cors()); // Enable CORS for API access
|
36
|
+
|
37
|
+
// Add request logging in development mode
|
38
|
+
if (process.env.NODE_ENV !== 'production') {
|
39
|
+
app.use((req, res, next) => {
|
40
|
+
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
|
41
|
+
next();
|
42
|
+
});
|
43
|
+
}
|
44
|
+
|
45
|
+
// For production, serve the built files
|
46
|
+
if (process.env.NODE_ENV === 'production') {
|
47
|
+
app.use(express.static(path.join(__dirname, 'dist')));
|
48
|
+
} else {
|
49
|
+
// In development, we'll let Vite handle the frontend
|
50
|
+
console.log('Running in development mode - Vite handles the frontend');
|
51
|
+
}
|
52
|
+
|
53
|
+
// Global in-memory store for demo purposes
|
54
|
+
// In a real app, you would use a database
|
55
|
+
const store = {
|
56
|
+
users: [
|
57
|
+
{ id: 1, name: 'User 1', email: 'user1@example.com' },
|
58
|
+
{ id: 2, name: 'User 2', email: 'user2@example.com' },
|
59
|
+
{ id: 3, name: 'User 3', email: 'user3@example.com' }
|
60
|
+
],
|
61
|
+
posts: [
|
62
|
+
{ id: 1, title: 'Post 1', content: 'Content for post 1', authorId: 1 },
|
63
|
+
{ id: 2, title: 'Post 2', content: 'Content for post 2', authorId: 2 },
|
64
|
+
{ id: 3, title: 'Post 3', content: 'Content for post 3', authorId: 1 }
|
65
|
+
]
|
66
|
+
};
|
67
|
+
|
68
|
+
// API endpoints - Core data
|
69
|
+
app.get('/api/hello', (req, res) => {
|
70
|
+
res.json({
|
71
|
+
message: "Hello from the API!",
|
72
|
+
time: new Date().toISOString(),
|
73
|
+
features: [
|
74
|
+
"Server-side rendering",
|
75
|
+
"API routes",
|
76
|
+
"Component-based UI",
|
77
|
+
"React-like development experience",
|
78
|
+
"WebSocket integration",
|
79
|
+
"Database connectivity",
|
80
|
+
"Authentication"
|
81
|
+
]
|
82
|
+
});
|
83
|
+
});
|
84
|
+
|
85
|
+
// Dynamic API route loading
|
86
|
+
const apiDir = path.join(__dirname, 'api');
|
87
|
+
if (fs.existsSync(apiDir)) {
|
88
|
+
console.log('Loading API routes from directory...');
|
89
|
+
|
90
|
+
// Function to map file paths to API routes
|
91
|
+
const registerApiRoutes = async (dir, routePrefix = '') => {
|
92
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
93
|
+
|
94
|
+
for (const entry of entries) {
|
95
|
+
const fullPath = path.join(dir, entry.name);
|
96
|
+
|
97
|
+
if (entry.isDirectory()) {
|
98
|
+
// Process directories recursively
|
99
|
+
await registerApiRoutes(fullPath, `${routePrefix}/${entry.name}`);
|
100
|
+
} else if (entry.name.endsWith('.js') || entry.name.endsWith('.ts')) {
|
101
|
+
// Process API file
|
102
|
+
try {
|
103
|
+
const fileName = entry.name.replace(/\.[^/.]+$/, ""); // Remove extension
|
104
|
+
const routePath = fileName === 'index'
|
105
|
+
? routePrefix
|
106
|
+
: fileName.startsWith('[') && fileName.endsWith(']')
|
107
|
+
? `${routePrefix}/:${fileName.slice(1, -1)}` // Convert [param] to :param
|
108
|
+
: `${routePrefix}/${fileName}`;
|
109
|
+
|
110
|
+
// Import the route module
|
111
|
+
const module = await import(`file://${fullPath}`);
|
112
|
+
|
113
|
+
// Register handlers for HTTP methods
|
114
|
+
['get', 'post', 'put', 'delete', 'patch'].forEach(method => {
|
115
|
+
if (typeof module[method] === 'function') {
|
116
|
+
console.log(` Registered ${method.toUpperCase()} ${routePath}`);
|
117
|
+
app[method](`/api${routePath}`, module[method]);
|
118
|
+
}
|
119
|
+
});
|
120
|
+
|
121
|
+
// Special case for 'delete' which might be named 'delete_' in some files
|
122
|
+
if (typeof module['delete_'] === 'function') {
|
123
|
+
console.log(` Registered DELETE ${routePath}`);
|
124
|
+
app.delete(`/api${routePath}`, module['delete_']);
|
125
|
+
}
|
126
|
+
} catch (err) {
|
127
|
+
console.error(`Error loading API route ${fullPath}:`, err);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
};
|
132
|
+
|
133
|
+
// Start registering API routes
|
134
|
+
try {
|
135
|
+
await registerApiRoutes(apiDir);
|
136
|
+
console.log('API routes loaded successfully');
|
137
|
+
} catch (err) {
|
138
|
+
console.error('Error loading API routes:', err);
|
139
|
+
}
|
140
|
+
} else {
|
141
|
+
console.log('API directory not found, skipping API route loading');
|
142
|
+
}
|
143
|
+
|
144
|
+
// WebSocket setup
|
145
|
+
io.on('connection', (socket) => {
|
146
|
+
console.log('Client connected:', socket.id);
|
147
|
+
|
148
|
+
// Send welcome message
|
149
|
+
socket.emit('welcome', {
|
150
|
+
message: 'Connected to WebSocket server',
|
151
|
+
time: new Date().toISOString()
|
152
|
+
});
|
153
|
+
|
154
|
+
// Handle client messages
|
155
|
+
socket.on('message', (data) => {
|
156
|
+
console.log('Received message:', data);
|
157
|
+
// Broadcast to all clients
|
158
|
+
io.emit('broadcast', {
|
159
|
+
from: socket.id,
|
160
|
+
data,
|
161
|
+
time: new Date().toISOString()
|
162
|
+
});
|
163
|
+
});
|
164
|
+
|
165
|
+
// Handle disconnect
|
166
|
+
socket.on('disconnect', () => {
|
167
|
+
console.log('Client disconnected:', socket.id);
|
168
|
+
});
|
169
|
+
});
|
170
|
+
|
171
|
+
// In production or if using SSR, handle all requests
|
172
|
+
app.get('*', (req, res, next) => {
|
173
|
+
// Skip API routes
|
174
|
+
if (req.path.startsWith('/api/')) {
|
175
|
+
return next();
|
176
|
+
}
|
177
|
+
|
178
|
+
// In production, serve the index.html
|
179
|
+
if (process.env.NODE_ENV === 'production') {
|
180
|
+
const indexPath = path.join(__dirname, 'dist', 'index.html');
|
181
|
+
if (fs.existsSync(indexPath)) {
|
182
|
+
res.sendFile(indexPath);
|
183
|
+
} else {
|
184
|
+
res.status(404).send('Not found');
|
185
|
+
}
|
186
|
+
} else {
|
187
|
+
// In development, redirect to Vite dev server
|
188
|
+
res.redirect(`http://localhost:5173${req.path}`);
|
189
|
+
}
|
190
|
+
});
|
191
|
+
|
192
|
+
// Error handling middleware
|
193
|
+
app.use((err, req, res, next) => {
|
194
|
+
console.error('Server error:', err);
|
195
|
+
res.status(500).json({
|
196
|
+
error: process.env.NODE_ENV === 'production'
|
197
|
+
? 'Internal server error'
|
198
|
+
: err.message
|
199
|
+
});
|
200
|
+
});
|
201
|
+
|
202
|
+
// Start server
|
203
|
+
httpServer.listen(PORT, () => {
|
204
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
205
|
+
|
206
|
+
if (process.env.NODE_ENV !== 'production') {
|
207
|
+
console.log(`Frontend development server will run on http://localhost:5173`);
|
208
|
+
console.log(`Run 'npm run dev' to start both servers`);
|
209
|
+
}
|
210
|
+
});
|
@@ -1,38 +1,77 @@
|
|
1
|
-
import
|
1
|
+
import express from 'express';
|
2
|
+
import path from 'path';
|
3
|
+
import { fileURLToPath } from 'url';
|
4
|
+
import fs from 'fs';
|
2
5
|
|
3
|
-
|
6
|
+
// Get __dirname equivalent in ESM
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
8
|
+
const __dirname = path.dirname(__filename);
|
9
|
+
|
10
|
+
// Create Express app
|
11
|
+
const app = express();
|
12
|
+
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000;
|
13
|
+
|
14
|
+
// Serve static files from current directory
|
15
|
+
app.use(express.static(__dirname));
|
16
|
+
|
17
|
+
// Parse JSON and URL-encoded data
|
18
|
+
app.use(express.json());
|
19
|
+
app.use(express.urlencoded({ extended: true }));
|
20
|
+
|
21
|
+
// Basic API endpoint
|
22
|
+
app.get('/api/hello', (req, res) => {
|
23
|
+
res.json({
|
24
|
+
message: 'Hello from the API!',
|
25
|
+
time: new Date().toISOString(),
|
26
|
+
environment: process.env.NODE_ENV || 'development'
|
27
|
+
});
|
28
|
+
});
|
29
|
+
|
30
|
+
// Serve index.html for all other routes
|
31
|
+
app.get('*', (req, res) => {
|
4
32
|
try {
|
5
|
-
//
|
6
|
-
const
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
});
|
33
|
+
// Read index.html
|
34
|
+
const indexPath = path.join(__dirname, 'index.html');
|
35
|
+
const html = fs.readFileSync(indexPath, 'utf8');
|
36
|
+
|
37
|
+
// Add server-rendered content
|
38
|
+
const serverContent = `
|
39
|
+
<div class="server-content">
|
40
|
+
<h1>Frontend Hamroun Fullstack</h1>
|
41
|
+
<p>Server-side rendered content</p>
|
42
|
+
<div class="timestamp">Rendered at: ${new Date().toISOString()}</div>
|
43
|
+
<div class="api-container"></div>
|
44
|
+
</div>
|
45
|
+
`;
|
19
46
|
|
20
|
-
//
|
21
|
-
|
47
|
+
// Replace placeholder with server content
|
48
|
+
const modifiedHtml = html.replace(
|
49
|
+
/<div class="placeholder">[\s\S]*?<\/div>/,
|
50
|
+
serverContent
|
51
|
+
);
|
22
52
|
|
23
|
-
|
24
|
-
(process.env.PORT || 3000));
|
25
|
-
|
26
|
-
// Handle graceful shutdown
|
27
|
-
process.on('SIGINT', async () => {
|
28
|
-
console.log('Shutting down server...');
|
29
|
-
await app.stop();
|
30
|
-
process.exit(0);
|
31
|
-
});
|
53
|
+
res.send(modifiedHtml);
|
32
54
|
} catch (error) {
|
33
|
-
console.error('
|
34
|
-
|
55
|
+
console.error('Error serving HTML:', error);
|
56
|
+
res.status(500).send(`
|
57
|
+
<html>
|
58
|
+
<head><title>Server Error</title></head>
|
59
|
+
<body>
|
60
|
+
<h1>500 - Server Error</h1>
|
61
|
+
<p>${error.message}</p>
|
62
|
+
</body>
|
63
|
+
</html>
|
64
|
+
`);
|
35
65
|
}
|
36
|
-
}
|
66
|
+
});
|
67
|
+
|
68
|
+
// Start the server
|
69
|
+
app.listen(PORT, () => {
|
70
|
+
console.log(`Server running at http://localhost:${PORT}`);
|
71
|
+
});
|
37
72
|
|
38
|
-
|
73
|
+
// Handle graceful shutdown
|
74
|
+
process.on('SIGINT', () => {
|
75
|
+
console.log('Shutting down server...');
|
76
|
+
process.exit(0);
|
77
|
+
});
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { hydrate, createElement } from 'frontend-hamroun';
|
2
|
+
|
3
|
+
// Simple component for testing
|
4
|
+
function App() {
|
5
|
+
return createElement('div', null, [
|
6
|
+
createElement('h1', null, 'Hello from Frontend Hamroun'),
|
7
|
+
createElement('p', null, 'This is a fullstack app with SSR')
|
8
|
+
]);
|
9
|
+
}
|
10
|
+
|
11
|
+
// Wait for DOM to be ready
|
12
|
+
document.addEventListener('DOMContentLoaded', () => {
|
13
|
+
const root = document.getElementById('root');
|
14
|
+
|
15
|
+
// Hydrate the application
|
16
|
+
if (root) {
|
17
|
+
hydrate(createElement(App), root);
|
18
|
+
console.log('App hydrated successfully');
|
19
|
+
}
|
20
|
+
});
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { jsx, render } from 'frontend-hamroun';
|
2
|
+
import HomePage from './pages/index.tsx';
|
3
|
+
|
4
|
+
// Add global jsx for Vite to use
|
5
|
+
window.jsx = jsx;
|
6
|
+
|
7
|
+
// Fix: Pass the component properly to render
|
8
|
+
// Don't call HomePage() directly - pass it as a component
|
9
|
+
render(jsx(HomePage, {}, null), document.getElementById('root'));
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { useState, useEffect } from 'frontend-hamroun';
|
1
|
+
import { jsx, useState, useEffect } from 'frontend-hamroun';
|
2
2
|
|
3
3
|
export default function HomePage() {
|
4
4
|
const [apiData, setApiData] = useState(null);
|
@@ -21,39 +21,43 @@ export default function HomePage() {
|
|
21
21
|
fetchData();
|
22
22
|
}, []);
|
23
23
|
|
24
|
-
return (
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
24
|
+
return jsx('div', { className: "container mx-auto p-4" }, [
|
25
|
+
jsx('h1', { className: "text-3xl font-bold mb-4" }, "Frontend Hamroun Full Stack"),
|
26
|
+
|
27
|
+
jsx('div', { className: "mb-6" }, [
|
28
|
+
jsx('h2', { className: "text-xl font-semibold mb-2" }, "API Response:"),
|
29
|
+
loading
|
30
|
+
? jsx('p', {}, "Loading...")
|
31
|
+
: error
|
32
|
+
? jsx('p', { className: "text-red-500" }, `Error: ${error}`)
|
33
|
+
: jsx('pre', { className: "bg-gray-100 p-4 rounded" },
|
34
|
+
JSON.stringify(apiData, null, 2)
|
35
|
+
)
|
36
|
+
]),
|
37
|
+
|
38
|
+
jsx('div', { className: "mb-6" }, [
|
39
|
+
jsx('h2', { className: "text-xl font-semibold mb-2" }, "Features:"),
|
40
|
+
jsx('ul', { className: "list-disc pl-6" }, [
|
41
|
+
jsx('li', {}, "Server-side rendering"),
|
42
|
+
jsx('li', {}, "API routes with Express"),
|
43
|
+
jsx('li', {}, "Database integration"),
|
44
|
+
jsx('li', {}, "Authentication and authorization"),
|
45
|
+
jsx('li', {}, "File-based routing")
|
46
|
+
])
|
47
|
+
]),
|
48
|
+
|
49
|
+
jsx('div', {}, [
|
50
|
+
jsx('h2', { className: "text-xl font-semibold mb-2" }, "Get Started:"),
|
51
|
+
jsx('p', {}, [
|
52
|
+
"Edit ",
|
53
|
+
jsx('code', { className: "bg-gray-100 px-2 py-1 rounded" }, "src/pages/index.tsx"),
|
54
|
+
" to customize this page"
|
55
|
+
]),
|
56
|
+
jsx('p', {}, [
|
57
|
+
"API routes are in the ",
|
58
|
+
jsx('code', { className: "bg-gray-100 px-2 py-1 rounded" }, "api"),
|
59
|
+
" directory"
|
60
|
+
])
|
61
|
+
])
|
62
|
+
]);
|
59
63
|
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { defineConfig } from 'vite';
|
2
|
+
import { resolve } from 'path';
|
3
|
+
|
4
|
+
export default defineConfig({
|
5
|
+
// Server configuration
|
6
|
+
server: {
|
7
|
+
port: 5173,
|
8
|
+
proxy: {
|
9
|
+
'/api': 'http://localhost:3000'
|
10
|
+
}
|
11
|
+
},
|
12
|
+
|
13
|
+
// Using jsx transform with a different configuration
|
14
|
+
esbuild: {
|
15
|
+
jsxFactory: 'jsx',
|
16
|
+
jsxFragment: 'Fragment'
|
17
|
+
},
|
18
|
+
|
19
|
+
// Build configuration
|
20
|
+
build: {
|
21
|
+
outDir: 'dist',
|
22
|
+
rollupOptions: {
|
23
|
+
input: {
|
24
|
+
main: resolve(__dirname, 'index.html')
|
25
|
+
}
|
26
|
+
}
|
27
|
+
},
|
28
|
+
|
29
|
+
// Resolve aliases
|
30
|
+
resolve: {
|
31
|
+
alias: {
|
32
|
+
'@': resolve(__dirname, 'src')
|
33
|
+
}
|
34
|
+
},
|
35
|
+
|
36
|
+
// Optimize dependencies
|
37
|
+
optimizeDeps: {
|
38
|
+
include: ['frontend-hamroun']
|
39
|
+
}
|
40
|
+
});
|