frontend-hamroun 1.2.26 → 1.2.28
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 +7 -0
- package/dist/index.client.d.ts +1 -0
- package/dist/index.d.ts +0 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +26 -1
- package/templates/fullstack-app/README.md +37 -0
- package/templates/fullstack-app/build/main.css +664 -0
- package/templates/fullstack-app/build/main.css.map +7 -0
- package/templates/fullstack-app/build/main.js +682 -0
- package/templates/fullstack-app/build/main.js.map +7 -0
- package/templates/fullstack-app/build.ts +211 -0
- package/templates/fullstack-app/index.html +26 -3
- package/templates/fullstack-app/package-lock.json +2402 -438
- package/templates/fullstack-app/package.json +24 -9
- package/templates/fullstack-app/postcss.config.js +6 -0
- package/templates/fullstack-app/process-tailwind.js +45 -0
- package/templates/fullstack-app/public/_redirects +1 -0
- package/templates/fullstack-app/public/route-handler.js +47 -0
- package/templates/fullstack-app/public/spa-fix.html +17 -0
- package/templates/fullstack-app/public/styles.css +768 -0
- package/templates/fullstack-app/public/tailwind.css +15 -0
- package/templates/fullstack-app/server.js +101 -44
- package/templates/fullstack-app/server.ts +402 -39
- package/templates/fullstack-app/src/README.md +55 -0
- package/templates/fullstack-app/src/client.js +83 -16
- package/templates/fullstack-app/src/components/Layout.tsx +45 -0
- package/templates/fullstack-app/src/components/UserList.tsx +27 -0
- package/templates/fullstack-app/src/config.ts +42 -0
- package/templates/fullstack-app/src/data/api.ts +71 -0
- package/templates/fullstack-app/src/main.tsx +219 -7
- package/templates/fullstack-app/src/pages/about/index.tsx +67 -0
- package/templates/fullstack-app/src/pages/index.tsx +30 -60
- package/templates/fullstack-app/src/pages/users.tsx +60 -0
- package/templates/fullstack-app/src/router.ts +255 -0
- package/templates/fullstack-app/src/styles.css +5 -0
- package/templates/fullstack-app/tailwind.config.js +11 -0
- package/templates/fullstack-app/tsconfig.json +18 -0
- package/templates/fullstack-app/vite.config.js +53 -6
- package/templates/ssr-template/readme.md +50 -0
- package/templates/ssr-template/src/client.ts +46 -14
- package/templates/ssr-template/src/server.ts +190 -18
@@ -13,7 +13,7 @@ const port = 3000;
|
|
13
13
|
const getClientEntry = () => {
|
14
14
|
const assetsDir = path.join(__dirname, './');
|
15
15
|
const files = fs.readdirSync(assetsDir);
|
16
|
-
return files.find(file => file.startsWith("client")&& file.endsWith('.js'));
|
16
|
+
return files.find(file => file.startsWith("client") && file.endsWith('.js'));
|
17
17
|
};
|
18
18
|
|
19
19
|
// Serve static files from dist/assets
|
@@ -22,23 +22,195 @@ app.use('/assets', express.static(path.join(__dirname, './')));
|
|
22
22
|
// Serve static files from dist
|
23
23
|
app.use(express.static(path.join(__dirname, 'dist')));
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
25
|
+
// Auto-routing middleware - scans the pages directory for components
|
26
|
+
const getPagesDirectory = () => {
|
27
|
+
return path.join(__dirname, 'pages');
|
28
|
+
};
|
29
|
+
|
30
|
+
// Helper to check if a file exists
|
31
|
+
const fileExists = async (filePath) => {
|
32
|
+
try {
|
33
|
+
await fs.promises.access(filePath);
|
34
|
+
return true;
|
35
|
+
} catch {
|
36
|
+
return false;
|
37
|
+
}
|
38
|
+
};
|
39
|
+
|
40
|
+
// Map URL path to component path
|
41
|
+
const getComponentPath = async (urlPath) => {
|
42
|
+
const pagesDir = getPagesDirectory();
|
43
|
+
|
44
|
+
// Handle root path
|
45
|
+
if (urlPath === '/') {
|
46
|
+
const indexPath = path.join(pagesDir, 'index.js');
|
47
|
+
if (await fileExists(indexPath)) {
|
48
|
+
return {
|
49
|
+
componentPath: indexPath,
|
50
|
+
params: {}
|
51
|
+
};
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
// Try direct match (e.g., /about -> /pages/about.js)
|
56
|
+
const directPath = path.join(pagesDir, `${urlPath.slice(1)}.js`);
|
57
|
+
if (await fileExists(directPath)) {
|
58
|
+
return {
|
59
|
+
componentPath: directPath,
|
60
|
+
params: {}
|
61
|
+
};
|
62
|
+
}
|
63
|
+
|
64
|
+
// Try directory index (e.g., /about -> /pages/about/index.js)
|
65
|
+
const dirIndexPath = path.join(pagesDir, urlPath.slice(1), 'index.js');
|
66
|
+
if (await fileExists(dirIndexPath)) {
|
67
|
+
return {
|
68
|
+
componentPath: dirIndexPath,
|
69
|
+
params: {}
|
70
|
+
};
|
71
|
+
}
|
72
|
+
|
73
|
+
// Look for dynamic routes (with [param] in filename)
|
74
|
+
const segments = urlPath.split('/').filter(Boolean);
|
75
|
+
const dynamicRoutes = [];
|
76
|
+
|
77
|
+
// Recursively scan pages directory for all files
|
78
|
+
const scanDir = (dir, basePath = '') => {
|
79
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
80
|
+
|
81
|
+
for (const item of items) {
|
82
|
+
const itemPath = path.join(dir, item.name);
|
83
|
+
const routePath = path.join(basePath, item.name);
|
84
|
+
|
85
|
+
if (item.isDirectory()) {
|
86
|
+
scanDir(itemPath, routePath);
|
87
|
+
} else if (item.name.endsWith('.js') && item.name.includes('[')) {
|
88
|
+
// This is a dynamic route file
|
89
|
+
const urlPattern = routePath
|
90
|
+
.replace(/\.js$/, '')
|
91
|
+
.replace(/\[([^\]]+)\]/g, ':$1');
|
92
|
+
|
93
|
+
dynamicRoutes.push({
|
94
|
+
pattern: urlPattern,
|
95
|
+
componentPath: itemPath
|
96
|
+
});
|
97
|
+
}
|
98
|
+
}
|
99
|
+
};
|
100
|
+
|
101
|
+
scanDir(pagesDir);
|
102
|
+
|
103
|
+
// Check if any dynamic routes match
|
104
|
+
for (const route of dynamicRoutes) {
|
105
|
+
const routeSegments = route.pattern.split('/').filter(Boolean);
|
106
|
+
if (routeSegments.length !== segments.length) continue;
|
107
|
+
|
108
|
+
const params = {};
|
109
|
+
let matches = true;
|
110
|
+
|
111
|
+
for (let i = 0; i < segments.length; i++) {
|
112
|
+
const routeSeg = routeSegments[i];
|
113
|
+
const urlSeg = segments[i];
|
114
|
+
|
115
|
+
if (routeSeg.startsWith(':')) {
|
116
|
+
// This is a parameter
|
117
|
+
const paramName = routeSeg.slice(1);
|
118
|
+
params[paramName] = urlSeg;
|
119
|
+
} else if (routeSeg !== urlSeg) {
|
120
|
+
matches = false;
|
121
|
+
break;
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
if (matches) {
|
126
|
+
return {
|
127
|
+
componentPath: route.componentPath,
|
128
|
+
params
|
129
|
+
};
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
// No match found
|
134
|
+
return null;
|
135
|
+
};
|
136
|
+
|
137
|
+
// Handle all routes with auto-routing
|
138
|
+
app.get('*', async (req, res) => {
|
139
|
+
try {
|
140
|
+
const clientEntry = getClientEntry();
|
141
|
+
const routeResult = await getComponentPath(req.path);
|
142
|
+
|
143
|
+
if (!routeResult) {
|
144
|
+
return res.status(404).send(`
|
145
|
+
<!DOCTYPE html>
|
146
|
+
<html>
|
147
|
+
<head>
|
148
|
+
<title>404 - Page Not Found</title>
|
149
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
150
|
+
</head>
|
151
|
+
<body>
|
152
|
+
<div id="root">
|
153
|
+
<h1>404 - Page Not Found</h1>
|
154
|
+
<p>The page you requested could not be found.</p>
|
155
|
+
</div>
|
156
|
+
<script type="module" src="/assets/${clientEntry}"></script>
|
157
|
+
</body>
|
158
|
+
</html>
|
159
|
+
`);
|
160
|
+
}
|
161
|
+
|
162
|
+
// Import the component dynamically
|
163
|
+
const { default: PageComponent } = await import(routeResult.componentPath);
|
164
|
+
|
165
|
+
if (!PageComponent) {
|
166
|
+
throw new Error(`Invalid component in ${routeResult.componentPath}`);
|
167
|
+
}
|
168
|
+
|
169
|
+
// Render the component with params
|
170
|
+
const html = await renderToString(jsx(PageComponent, { params: routeResult.params }));
|
171
|
+
|
172
|
+
// Create route data for client hydration
|
173
|
+
const initialState = {
|
174
|
+
route: req.path,
|
175
|
+
params: routeResult.params,
|
176
|
+
timestamp: new Date().toISOString()
|
177
|
+
};
|
178
|
+
|
179
|
+
res.send(`
|
180
|
+
<!DOCTYPE html>
|
181
|
+
<html>
|
182
|
+
<head>
|
183
|
+
<title>Frontend Hamroun SSR</title>
|
184
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
185
|
+
<meta charset="UTF-8">
|
186
|
+
</head>
|
187
|
+
<body>
|
188
|
+
<div id="root">${html}</div>
|
189
|
+
<script>window.__INITIAL_STATE__ = ${JSON.stringify(initialState)}</script>
|
190
|
+
<script type="module" src="/assets/${clientEntry}"></script>
|
191
|
+
</body>
|
192
|
+
</html>
|
193
|
+
`);
|
194
|
+
} catch (error) {
|
195
|
+
console.error('Rendering error:', error);
|
196
|
+
res.status(500).send(`
|
197
|
+
<!DOCTYPE html>
|
198
|
+
<html>
|
199
|
+
<head>
|
200
|
+
<title>500 - Server Error</title>
|
201
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
202
|
+
</head>
|
203
|
+
<body>
|
204
|
+
<div id="root">
|
205
|
+
<h1>500 - Server Error</h1>
|
206
|
+
<p>Something went wrong on the server.</p>
|
207
|
+
<pre>${process.env.NODE_ENV === 'production' ? '' : error.stack}</pre>
|
208
|
+
</div>
|
209
|
+
<script type="module" src="/assets/${clientEntry}"></script>
|
210
|
+
</body>
|
211
|
+
</html>
|
212
|
+
`);
|
213
|
+
}
|
42
214
|
});
|
43
215
|
|
44
216
|
app.listen(port, () => {
|