@emberkit/core 0.3.8 → 0.4.1

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/dist/index.d.ts CHANGED
@@ -17,7 +17,7 @@ export { Head } from './meta/index.js';
17
17
  export type { HeadProps } from './meta/index.js';
18
18
  export { generateMeta, generateBreadcrumbs, generateArticleSchema, generateProductSchema, } from './meta/index.js';
19
19
  export type { MetaData, OpenGraphData, TwitterCardData } from './meta/index.js';
20
- export type { FC, RouteComponent, RouteChildren } from './runtime/types.js';
20
+ export type { FC, RouteComponent, RouteChildren, RouteParams } from './runtime/types.js';
21
21
  export type { Logger, LoggerOptions, LogLevel, RequestLog, ResponseLog } from './logger/types.js';
22
22
  export declare function defineConfig(config: Record<string, unknown>): Record<string, unknown>;
23
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,KAAK,EACL,OAAO,EACP,MAAM,EACN,QAAQ,EACR,MAAM,GACP,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnF,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC1E,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAEjE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEzF,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEhF,YAAY,EAAE,EAAE,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAE5E,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAElG,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAErF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,KAAK,EACL,OAAO,EACP,MAAM,EACN,QAAQ,EACR,MAAM,GACP,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnF,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC1E,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAEjE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEzF,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEhF,YAAY,EAAE,EAAE,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEzF,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAElG,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAErF"}
@@ -126,10 +126,10 @@ export function emberkitVitePlugin(userOptions = {}) {
126
126
  }
127
127
  try {
128
128
  const ssrModule = await server.ssrLoadModule(VIRTUAL_SSR_ENTRY);
129
- const html = await ssrModule.render(url, server);
130
- res.statusCode = 200;
129
+ const result = await ssrModule.render(url, server);
130
+ res.statusCode = result.status || 200;
131
131
  res.setHeader('Content-Type', 'text/html');
132
- res.end(html);
132
+ res.end(result.html || result);
133
133
  }
134
134
  catch (error) {
135
135
  server.ssrFixStacktrace(error);
@@ -1131,7 +1131,7 @@ function processParagraphs(html, breaks) {
1131
1131
  }
1132
1132
  function generateSSREntry() {
1133
1133
  return `
1134
- import { routes } from 'virtual:emberkit-routes';
1134
+ import { routes, notFoundRoute, errorRoute } from 'virtual:emberkit-routes';
1135
1135
  import { createElement } from '@emberkit/core';
1136
1136
 
1137
1137
  const matchRoute = (routes, pathname) => {
@@ -1170,12 +1170,12 @@ const renderToString = (element) => {
1170
1170
  if (typeof element === 'string') return escapeHtml(element);
1171
1171
  if (typeof element === 'number') return String(element);
1172
1172
  if (Array.isArray(element)) return element.map(renderToString).join('');
1173
-
1173
+
1174
1174
  if (typeof element !== 'object' || !element.type) return '';
1175
-
1175
+
1176
1176
  let { type, props } = element;
1177
1177
  props = props || {};
1178
-
1178
+
1179
1179
  // Resolve function components
1180
1180
  let depth = 0;
1181
1181
  while (typeof type === 'function' && depth < 50) {
@@ -1197,22 +1197,22 @@ const renderToString = (element) => {
1197
1197
  return '';
1198
1198
  }
1199
1199
  }
1200
-
1200
+
1201
1201
  if (type === 'Fragment' || type === 'React.Fragment') {
1202
1202
  const children = Array.isArray(props.children) ? props.children : [props.children];
1203
1203
  return children.filter(Boolean).map(renderToString).join('');
1204
1204
  }
1205
-
1205
+
1206
1206
  const SELF_CLOSING = new Set(['area','base','br','col','embed','hr','img','input','link','meta','source','track','wbr']);
1207
-
1207
+
1208
1208
  const children = Array.isArray(props.children) ? props.children : (props.children ? [props.children] : []);
1209
1209
  let childHtml = children.filter(c => c != null).map(renderToString).join('');
1210
-
1210
+
1211
1211
  // Handle dangerouslySetInnerHTML
1212
1212
  if (props.dangerouslySetInnerHTML && props.dangerouslySetInnerHTML.__html) {
1213
1213
  childHtml = props.dangerouslySetInnerHTML.__html;
1214
1214
  }
1215
-
1215
+
1216
1216
  const attrs = Object.entries(props)
1217
1217
  .filter(([k, v]) => k !== 'children' && k !== 'key' && k !== 'dangerouslySetInnerHTML' && v != null && typeof v !== 'function')
1218
1218
  .map(([k, v]) => {
@@ -1229,11 +1229,11 @@ const renderToString = (element) => {
1229
1229
  return ' ' + k + '="' + escapeHtml(String(v)) + '"';
1230
1230
  })
1231
1231
  .join('');
1232
-
1232
+
1233
1233
  if (SELF_CLOSING.has(type)) {
1234
1234
  return '<' + type + attrs + '/>';
1235
1235
  }
1236
-
1236
+
1237
1237
  return '<' + type + attrs + '>' + childHtml + '</' + type + '>';
1238
1238
  };
1239
1239
 
@@ -1249,24 +1249,25 @@ const escapeHtml = (str) => {
1249
1249
 
1250
1250
  export async function render(url, server) {
1251
1251
  const pathname = url.split('?')[0];
1252
-
1252
+
1253
1253
  // Sort routes: static first, then dynamic
1254
1254
  const sortedRoutes = [...routes].sort((a, b) => {
1255
1255
  const aScore = a.path.includes(':') ? 0 : 1;
1256
1256
  const bScore = b.path.includes(':') ? 0 : 1;
1257
1257
  return bScore - aScore;
1258
1258
  });
1259
-
1259
+
1260
1260
  const match = matchRoute(sortedRoutes, pathname);
1261
-
1261
+
1262
1262
  let appHtml = '';
1263
1263
  let headContent = '';
1264
-
1264
+ let status = 200;
1265
+
1265
1266
  if (match) {
1266
1267
  try {
1267
1268
  const mod = await match.route.component();
1268
1269
  const Component = mod.default || mod;
1269
-
1270
+
1270
1271
  // Get metadata if available
1271
1272
  if (mod.metadata) {
1272
1273
  if (mod.metadata.title) {
@@ -1276,24 +1277,56 @@ export async function render(url, server) {
1276
1277
  headContent += '<meta name="description" content="' + escapeHtml(mod.metadata.description) + '">\\n';
1277
1278
  }
1278
1279
  }
1279
-
1280
+
1280
1281
  const element = createElement(Component, { params: match.params });
1281
1282
  appHtml = renderToString(element);
1282
1283
  } catch (e) {
1283
1284
  console.error('[SSR] Failed to render route:', pathname, e);
1284
- appHtml = '<div style="color: red; padding: 20px;">SSR Error: ' + escapeHtml(String(e)) + '</div>';
1285
+ if (errorRoute) {
1286
+ try {
1287
+ status = 500;
1288
+ const mod = await errorRoute();
1289
+ const Component = mod.default || mod;
1290
+ const errorInfo = {
1291
+ status: 500,
1292
+ message: e instanceof Error ? e.message : 'Internal Server Error',
1293
+ error: e,
1294
+ };
1295
+ const element = createElement(Component, { error: errorInfo });
1296
+ appHtml = renderToString(element);
1297
+ } catch (fallbackError) {
1298
+ console.error('[SSR] Failed to render 500 page:', fallbackError);
1299
+ appHtml = '<div style="color: red; padding: 20px;">Internal Server Error</div>';
1300
+ }
1301
+ } else {
1302
+ appHtml = '<div style="color: red; padding: 20px;">SSR Error: ' + escapeHtml(String(e)) + '</div>';
1303
+ status = 500;
1304
+ }
1285
1305
  }
1286
1306
  } else {
1287
- appHtml = '<div style="padding: 20px;">404 - Page not found</div>';
1307
+ status = 404;
1308
+ if (notFoundRoute) {
1309
+ try {
1310
+ const mod = await notFoundRoute();
1311
+ const Component = mod.default || mod;
1312
+ const element = createElement(Component, { });
1313
+ appHtml = renderToString(element);
1314
+ } catch (e) {
1315
+ console.error('[SSR] Failed to render 404 page:', e);
1316
+ appHtml = '<div style="padding: 20px;">404 - Page not found</div>';
1317
+ }
1318
+ } else {
1319
+ appHtml = '<div style="padding: 20px;">404 - Page not found</div>';
1320
+ }
1288
1321
  }
1289
-
1322
+
1290
1323
  // Load and transform index.html
1291
1324
  const fs = await import('node:fs');
1292
1325
  const path = await import('node:path');
1293
1326
  const indexPath = path.join(server.config.root, 'index.html');
1294
1327
  let template = fs.readFileSync(indexPath, 'utf-8');
1295
1328
  template = await server.transformIndexHtml(url, template);
1296
-
1329
+
1297
1330
  // Inject SSR content
1298
1331
  // Look for body with id="app" or div with id="app"
1299
1332
  if (template.includes('<body id="app">')) {
@@ -1303,13 +1336,13 @@ export async function render(url, server) {
1303
1336
  } else if (template.includes('<div id="app"/>')) {
1304
1337
  template = template.replace('<div id="app"/>', '<div id="app">' + appHtml + '</div>');
1305
1338
  }
1306
-
1339
+
1307
1340
  // Inject head content if any
1308
1341
  if (headContent && template.includes('</head>')) {
1309
1342
  template = template.replace('</head>', headContent + '</head>');
1310
1343
  }
1311
-
1312
- return template;
1344
+
1345
+ return { html: template, status };
1313
1346
  }
1314
1347
  `;
1315
1348
  }
@@ -1360,10 +1393,23 @@ function scoreRoutePath(routePath) {
1360
1393
  }
1361
1394
  function generateRoutesCode(files, routeDir) {
1362
1395
  const routeEntries = [];
1396
+ let notFoundRoute = 'null';
1397
+ let errorRoute = 'null';
1363
1398
  for (const file of files) {
1364
1399
  const relativePath = relative(routeDir, file).replace(/\\/g, '/');
1365
1400
  const ext = file.split('.').pop() ?? '';
1366
1401
  const isMarkdown = ext === 'md' || ext === 'mdx';
1402
+ // Check for special error pages
1403
+ if (relativePath === '404.tsx' || relativePath === '404.ts' || relativePath === '404.jsx' || relativePath === '404.js') {
1404
+ const importPath = file.replace(/\\/g, '/');
1405
+ notFoundRoute = `() => import(${JSON.stringify(importPath)})`;
1406
+ continue;
1407
+ }
1408
+ if (relativePath === '500.tsx' || relativePath === '500.ts' || relativePath === '500.jsx' || relativePath === '500.js') {
1409
+ const importPath = file.replace(/\\/g, '/');
1410
+ errorRoute = `() => import(${JSON.stringify(importPath)})`;
1411
+ continue;
1412
+ }
1367
1413
  // Skip special files
1368
1414
  if (relativePath.includes('_layout') ||
1369
1415
  relativePath.includes('_error') ||
@@ -1394,5 +1440,7 @@ function generateRoutesCode(files, routeDir) {
1394
1440
  // Sort static routes before dynamic routes so the emitted array already has
1395
1441
  // the correct priority order (defense-in-depth alongside runtime scoring).
1396
1442
  routeEntries.sort((a, b) => scoreRoutePath(b.path) - scoreRoutePath(a.path));
1397
- return `export const routes = [\n${routeEntries.map((r) => r.entry).join(',\n')}\n];`;
1443
+ return `export const routes = [\n${routeEntries.map((r) => r.entry).join(',\n')}\n];
1444
+ export const notFoundRoute = ${notFoundRoute};
1445
+ export const errorRoute = ${errorRoute};`;
1398
1446
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emberkit/core",
3
- "version": "0.3.8",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Lightweight TypeScript-first JSX framework core",