frontend-hamroun 1.2.21 → 1.2.22
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/bin/cli.js +13 -1
- package/package.json +11 -2
- package/templates/ssr-template/client.js +30 -0
- package/templates/ssr-template/package.json +11 -7
- package/templates/ssr-template/public/index.html +26 -0
- package/templates/ssr-template/server.ts +46 -0
- package/templates/ssr-template/src/client.tsx +15 -6
- package/templates/ssr-template/src/pages/index.tsx +51 -0
- package/templates/ssr-template/tsconfig.json +13 -15
- package/templates/ssr-template/tsconfig.server.json +14 -6
- package/templates/ssr-template/vite.config.js +44 -0
package/bin/cli.js
CHANGED
@@ -466,12 +466,13 @@ import { server } from 'frontend-hamroun';
|
|
466
466
|
async function startServer() {
|
467
467
|
try {
|
468
468
|
// Dynamically import server components
|
469
|
-
const { Server, Database, AuthService } = await server.getServer();
|
469
|
+
const { Server, Database, AuthService, renderComponent } = await server.getServer();
|
470
470
|
|
471
471
|
// Create and configure the server
|
472
472
|
const app = new Server({
|
473
473
|
port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
|
474
474
|
apiDir: './api',
|
475
|
+
pagesDir: './src/pages', // For SSR pages
|
475
476
|
staticDir: './public',
|
476
477
|
|
477
478
|
// Enable CORS
|
@@ -483,6 +484,12 @@ async function startServer() {
|
|
483
484
|
}
|
484
485
|
});
|
485
486
|
|
487
|
+
// Connect to database if configured
|
488
|
+
if (app.getDatabase()) {
|
489
|
+
await app.getDatabase().connect();
|
490
|
+
console.log('Connected to database');
|
491
|
+
}
|
492
|
+
|
486
493
|
// Start the server
|
487
494
|
await app.start();
|
488
495
|
|
@@ -492,7 +499,12 @@ async function startServer() {
|
|
492
499
|
// Handle graceful shutdown
|
493
500
|
process.on('SIGINT', async () => {
|
494
501
|
console.log('Shutting down server...');
|
502
|
+
if (app.getDatabase()) {
|
503
|
+
await app.getDatabase().disconnect();
|
504
|
+
console.log('Database connection closed');
|
505
|
+
}
|
495
506
|
await app.stop();
|
507
|
+
console.log('Server stopped');
|
496
508
|
process.exit(0);
|
497
509
|
});
|
498
510
|
} catch (error) {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "frontend-hamroun",
|
3
|
-
"version": "1.2.
|
3
|
+
"version": "1.2.22",
|
4
4
|
"description": "A lightweight full-stack JavaScript framework",
|
5
5
|
"type": "module",
|
6
6
|
"main": "./dist/index.js",
|
@@ -20,7 +20,16 @@
|
|
20
20
|
"require": "./dist/index.js",
|
21
21
|
"default": "./dist/index.js"
|
22
22
|
},
|
23
|
-
"./server":
|
23
|
+
"./server": {
|
24
|
+
"types": "./dist/server/index.d.ts",
|
25
|
+
"import": "./dist/server/index.js",
|
26
|
+
"default": "./dist/server/index.js"
|
27
|
+
},
|
28
|
+
"./ssr": {
|
29
|
+
"types": "./dist/server-renderer.d.ts",
|
30
|
+
"import": "./dist/server-renderer.js",
|
31
|
+
"default": "./dist/server-renderer.js"
|
32
|
+
}
|
24
33
|
},
|
25
34
|
"bin": {
|
26
35
|
"frontend-hamroun": "./bin/cli.js",
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { hydrate } from 'frontend-hamroun';
|
2
|
+
|
3
|
+
// Dynamically import the page component that matches the current URL
|
4
|
+
async function hydratePage() {
|
5
|
+
try {
|
6
|
+
// Get the current path
|
7
|
+
const path = window.location.pathname;
|
8
|
+
|
9
|
+
// Import the corresponding page component
|
10
|
+
const pagePath = `/src/pages${path === '/' ? '/index' : path}.jsx`;
|
11
|
+
|
12
|
+
try {
|
13
|
+
const { default: PageComponent } = await import(pagePath);
|
14
|
+
|
15
|
+
// Find the root element to hydrate
|
16
|
+
const rootElement = document.getElementById('app');
|
17
|
+
if (rootElement) {
|
18
|
+
// Hydrate the server-rendered HTML with the client component
|
19
|
+
hydrate(<PageComponent />, rootElement);
|
20
|
+
}
|
21
|
+
} catch (error) {
|
22
|
+
console.error(`Error importing page component for path ${path}:`, error);
|
23
|
+
}
|
24
|
+
} catch (error) {
|
25
|
+
console.error('Error during hydration:', error);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
// Start hydration when the DOM is ready
|
30
|
+
document.addEventListener('DOMContentLoaded', hydratePage);
|
@@ -1,14 +1,16 @@
|
|
1
1
|
{
|
2
|
-
"name": "ssr-
|
2
|
+
"name": "ssr-app",
|
3
3
|
"private": true,
|
4
|
-
"version": "0.
|
4
|
+
"version": "0.1.0",
|
5
5
|
"type": "module",
|
6
6
|
"scripts": {
|
7
|
-
"dev": "vite",
|
7
|
+
"dev:client": "vite",
|
8
|
+
"dev:server": "node --loader ts-node/esm server.ts",
|
9
|
+
"dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"",
|
8
10
|
"build:client": "vite build",
|
9
|
-
"build:server": "
|
11
|
+
"build:server": "tsc --project tsconfig.server.json",
|
10
12
|
"build": "npm run build:client && npm run build:server",
|
11
|
-
"start": "node dist/server.js"
|
13
|
+
"start": "node dist/server/server.js"
|
12
14
|
},
|
13
15
|
"dependencies": {
|
14
16
|
"express": "^4.18.2",
|
@@ -16,8 +18,10 @@
|
|
16
18
|
},
|
17
19
|
"devDependencies": {
|
18
20
|
"@types/express": "^4.17.17",
|
19
|
-
"@types/node": "^
|
20
|
-
"
|
21
|
+
"@types/node": "^18.16.19",
|
22
|
+
"concurrently": "^8.2.1",
|
23
|
+
"ts-node": "^10.9.1",
|
24
|
+
"typescript": "^5.2.2",
|
21
25
|
"vite": "^4.4.9"
|
22
26
|
}
|
23
27
|
}
|
@@ -0,0 +1,26 @@
|
|
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>Frontend Hamroun SSR</title>
|
7
|
+
<!-- Import Tailwind-like styles for quick styling -->
|
8
|
+
<link href="https://cdn.jsdelivr.net/npm/daisyui@3.7.4/dist/full.css" rel="stylesheet" type="text/css" />
|
9
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
10
|
+
<!-- Client-side script for hydration -->
|
11
|
+
<script type="module" src="/src/client.tsx"></script>
|
12
|
+
</head>
|
13
|
+
<body>
|
14
|
+
<!-- App will be rendered here by SSR -->
|
15
|
+
<div id="app">
|
16
|
+
<!-- This comment will be replaced with server-rendered HTML -->
|
17
|
+
<!-- If you see this text, server-side rendering failed -->
|
18
|
+
<div class="flex items-center justify-center min-h-screen">
|
19
|
+
<div class="text-center">
|
20
|
+
<h1 class="text-xl font-bold">Loading...</h1>
|
21
|
+
<p>If this message persists, make sure your server is running.</p>
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import { Server, renderComponent } from 'frontend-hamroun/server';
|
2
|
+
import { join } from 'path';
|
3
|
+
import express from 'express';
|
4
|
+
|
5
|
+
async function startServer() {
|
6
|
+
try {
|
7
|
+
// Create and configure the server
|
8
|
+
const app = new Server({
|
9
|
+
port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
|
10
|
+
pagesDir: './src/pages',
|
11
|
+
staticDir: './public',
|
12
|
+
|
13
|
+
// Enable CORS for API endpoints
|
14
|
+
enableCors: true
|
15
|
+
});
|
16
|
+
|
17
|
+
// Add server-side data endpoints (optional)
|
18
|
+
const expressApp = app.getExpressApp();
|
19
|
+
|
20
|
+
// Example API endpoint that provides data to SSR pages
|
21
|
+
expressApp.get('/api/page-data', (req, res) => {
|
22
|
+
res.json({
|
23
|
+
title: 'Server-side Data',
|
24
|
+
content: 'This data was fetched from the server',
|
25
|
+
timestamp: new Date().toISOString()
|
26
|
+
});
|
27
|
+
});
|
28
|
+
|
29
|
+
// Start the server
|
30
|
+
await app.start();
|
31
|
+
|
32
|
+
console.log(`Server running at http://localhost:${process.env.PORT || 3000}`);
|
33
|
+
|
34
|
+
// Handle graceful shutdown
|
35
|
+
process.on('SIGINT', async () => {
|
36
|
+
console.log('Shutting down server...');
|
37
|
+
await app.stop();
|
38
|
+
process.exit(0);
|
39
|
+
});
|
40
|
+
} catch (error) {
|
41
|
+
console.error('Failed to start server:', error);
|
42
|
+
process.exit(1);
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
startServer();
|
@@ -1,9 +1,18 @@
|
|
1
|
-
import { hydrate } from 'frontend-hamroun';
|
2
|
-
import { App } from './App';
|
1
|
+
import { hydrate, createElement } from 'frontend-hamroun';
|
3
2
|
|
3
|
+
// For simplicity in this example, we just hydrate the root component
|
4
|
+
// In a more complex app, you might use a router
|
5
|
+
import HomePage from './pages/index';
|
6
|
+
|
7
|
+
// When the DOM is ready, hydrate the server-rendered HTML
|
4
8
|
document.addEventListener('DOMContentLoaded', () => {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
+
const rootElement = document.getElementById('app');
|
10
|
+
|
11
|
+
if (rootElement) {
|
12
|
+
// Hydrate the app with the same component that was rendered on the server
|
13
|
+
hydrate(<HomePage />, rootElement);
|
14
|
+
console.log('Hydration complete');
|
15
|
+
} else {
|
16
|
+
console.error('Could not find root element with id "app"');
|
17
|
+
}
|
9
18
|
});
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import { useState, useEffect } from 'frontend-hamroun';
|
2
|
+
|
3
|
+
// This component will be rendered on both server and client
|
4
|
+
export default function HomePage() {
|
5
|
+
const [count, setCount] = useState(0);
|
6
|
+
const [serverTime, setServerTime] = useState('');
|
7
|
+
|
8
|
+
// This effect only runs on the client after hydration
|
9
|
+
useEffect(() => {
|
10
|
+
// Set server render time (only when hydrating)
|
11
|
+
if (!serverTime) {
|
12
|
+
setServerTime('Client-side hydration complete');
|
13
|
+
}
|
14
|
+
|
15
|
+
// Simple cleanup function
|
16
|
+
return () => {
|
17
|
+
console.log('Component unmounting');
|
18
|
+
};
|
19
|
+
}, []);
|
20
|
+
|
21
|
+
return (
|
22
|
+
<div id="app">
|
23
|
+
<div className="hero min-h-screen bg-base-200">
|
24
|
+
<div className="hero-content text-center">
|
25
|
+
<div className="max-w-md">
|
26
|
+
<h1 className="text-5xl font-bold">Frontend Hamroun SSR</h1>
|
27
|
+
<p className="py-6">
|
28
|
+
This page was rendered on the server and hydrated on the client.
|
29
|
+
</p>
|
30
|
+
|
31
|
+
{/* Interactive counter demonstrates client-side hydration */}
|
32
|
+
<div className="my-4 p-4 bg-base-300 rounded-lg">
|
33
|
+
<p>Counter: {count}</p>
|
34
|
+
<button
|
35
|
+
className="btn btn-primary mt-2"
|
36
|
+
onClick={() => setCount(count + 1)}
|
37
|
+
>
|
38
|
+
Increment
|
39
|
+
</button>
|
40
|
+
</div>
|
41
|
+
|
42
|
+
{/* Shows server render time or hydration status */}
|
43
|
+
<div className="text-sm opacity-70 mt-4">
|
44
|
+
{serverTime ? serverTime : `Server rendered at: ${new Date().toISOString()}`}
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
</div>
|
50
|
+
);
|
51
|
+
}
|
@@ -1,25 +1,23 @@
|
|
1
1
|
{
|
2
2
|
"compilerOptions": {
|
3
|
-
"target": "
|
4
|
-
"
|
5
|
-
"lib": ["
|
6
|
-
"
|
7
|
-
"jsx": "preserve",
|
8
|
-
"jsxFactory": "jsx",
|
9
|
-
"jsxFragmentFactory": "Fragment",
|
10
|
-
"strict": true,
|
11
|
-
"esModuleInterop": true,
|
3
|
+
"target": "ESNext",
|
4
|
+
"useDefineForClassFields": true,
|
5
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
6
|
+
"allowJs": false,
|
12
7
|
"skipLibCheck": true,
|
13
|
-
"
|
8
|
+
"esModuleInterop": true,
|
14
9
|
"allowSyntheticDefaultImports": true,
|
10
|
+
"strict": true,
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
12
|
+
"module": "ESNext",
|
13
|
+
"moduleResolution": "Node",
|
15
14
|
"resolveJsonModule": true,
|
16
15
|
"isolatedModules": true,
|
17
16
|
"noEmit": true,
|
18
|
-
"
|
19
|
-
"
|
20
|
-
|
21
|
-
}
|
17
|
+
"jsx": "react",
|
18
|
+
"jsxFactory": "createElement",
|
19
|
+
"jsxFragmentFactory": "Fragment"
|
22
20
|
},
|
23
21
|
"include": ["src"],
|
24
|
-
"
|
22
|
+
"references": [{ "path": "./tsconfig.server.json" }]
|
25
23
|
}
|
@@ -1,11 +1,19 @@
|
|
1
1
|
{
|
2
2
|
"extends": "./tsconfig.json",
|
3
3
|
"compilerOptions": {
|
4
|
-
"
|
5
|
-
"
|
6
|
-
"
|
7
|
-
"
|
8
|
-
"
|
4
|
+
"target": "ES2020",
|
5
|
+
"module": "NodeNext",
|
6
|
+
"moduleResolution": "NodeNext",
|
7
|
+
"esModuleInterop": true,
|
8
|
+
"outDir": "./dist/server",
|
9
|
+
"declaration": true,
|
10
|
+
"sourceMap": true,
|
11
|
+
"strict": true,
|
12
|
+
"skipLibCheck": true,
|
13
|
+
"jsx": "react",
|
14
|
+
"jsxFactory": "createElement",
|
15
|
+
"jsxFragmentFactory": "Fragment"
|
9
16
|
},
|
10
|
-
"include": ["
|
17
|
+
"include": ["server.ts", "src/pages/**/*.tsx"],
|
18
|
+
"exclude": ["node_modules", "dist"]
|
11
19
|
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import { defineConfig } from 'vite';
|
2
|
+
import { resolve } from 'path';
|
3
|
+
|
4
|
+
export default defineConfig({
|
5
|
+
// Configure JSX
|
6
|
+
esbuild: {
|
7
|
+
jsxFactory: 'createElement',
|
8
|
+
jsxFragment: 'Fragment',
|
9
|
+
jsxInject: `import { createElement, Fragment } from 'frontend-hamroun'`
|
10
|
+
},
|
11
|
+
|
12
|
+
// Configure build
|
13
|
+
build: {
|
14
|
+
outDir: 'dist/client',
|
15
|
+
emptyOutDir: true,
|
16
|
+
minify: process.env.NODE_ENV === 'production',
|
17
|
+
rollupOptions: {
|
18
|
+
input: {
|
19
|
+
client: resolve(__dirname, 'src/client.tsx')
|
20
|
+
},
|
21
|
+
output: {
|
22
|
+
entryFileNames: 'assets/[name]-[hash].js',
|
23
|
+
chunkFileNames: 'assets/[name]-[hash].js',
|
24
|
+
assetFileNames: 'assets/[name]-[hash].[ext]'
|
25
|
+
}
|
26
|
+
}
|
27
|
+
},
|
28
|
+
|
29
|
+
// Optimize dependencies
|
30
|
+
optimizeDeps: {
|
31
|
+
include: ['frontend-hamroun']
|
32
|
+
},
|
33
|
+
|
34
|
+
// Development server
|
35
|
+
server: {
|
36
|
+
proxy: {
|
37
|
+
// Forward API requests to the actual Node.js server during development
|
38
|
+
'/api': {
|
39
|
+
target: 'http://localhost:3000',
|
40
|
+
changeOrigin: true
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
});
|