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.
Files changed (42) hide show
  1. package/README.md +7 -0
  2. package/dist/index.client.d.ts +1 -0
  3. package/dist/index.d.ts +0 -5
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +26 -1
  7. package/templates/fullstack-app/README.md +37 -0
  8. package/templates/fullstack-app/build/main.css +664 -0
  9. package/templates/fullstack-app/build/main.css.map +7 -0
  10. package/templates/fullstack-app/build/main.js +682 -0
  11. package/templates/fullstack-app/build/main.js.map +7 -0
  12. package/templates/fullstack-app/build.ts +211 -0
  13. package/templates/fullstack-app/index.html +26 -3
  14. package/templates/fullstack-app/package-lock.json +2402 -438
  15. package/templates/fullstack-app/package.json +24 -9
  16. package/templates/fullstack-app/postcss.config.js +6 -0
  17. package/templates/fullstack-app/process-tailwind.js +45 -0
  18. package/templates/fullstack-app/public/_redirects +1 -0
  19. package/templates/fullstack-app/public/route-handler.js +47 -0
  20. package/templates/fullstack-app/public/spa-fix.html +17 -0
  21. package/templates/fullstack-app/public/styles.css +768 -0
  22. package/templates/fullstack-app/public/tailwind.css +15 -0
  23. package/templates/fullstack-app/server.js +101 -44
  24. package/templates/fullstack-app/server.ts +402 -39
  25. package/templates/fullstack-app/src/README.md +55 -0
  26. package/templates/fullstack-app/src/client.js +83 -16
  27. package/templates/fullstack-app/src/components/Layout.tsx +45 -0
  28. package/templates/fullstack-app/src/components/UserList.tsx +27 -0
  29. package/templates/fullstack-app/src/config.ts +42 -0
  30. package/templates/fullstack-app/src/data/api.ts +71 -0
  31. package/templates/fullstack-app/src/main.tsx +219 -7
  32. package/templates/fullstack-app/src/pages/about/index.tsx +67 -0
  33. package/templates/fullstack-app/src/pages/index.tsx +30 -60
  34. package/templates/fullstack-app/src/pages/users.tsx +60 -0
  35. package/templates/fullstack-app/src/router.ts +255 -0
  36. package/templates/fullstack-app/src/styles.css +5 -0
  37. package/templates/fullstack-app/tailwind.config.js +11 -0
  38. package/templates/fullstack-app/tsconfig.json +18 -0
  39. package/templates/fullstack-app/vite.config.js +53 -6
  40. package/templates/ssr-template/readme.md +50 -0
  41. package/templates/ssr-template/src/client.ts +46 -14
  42. package/templates/ssr-template/src/server.ts +190 -18
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../node_modules/frontend-hamroun/src/batch.ts", "../node_modules/frontend-hamroun/src/hooks.ts", "../node_modules/frontend-hamroun/src/context.ts", "../node_modules/frontend-hamroun/src/jsx-runtime.ts", "../node_modules/frontend-hamroun/src/renderer.ts", "../node_modules/frontend-hamroun/src/server-renderer.ts", "../node_modules/frontend-hamroun/src/index.client.ts", "../src/config.ts", "../src/components/Layout.tsx", "../src/pages/about/index.tsx", "../src/components/UserList.tsx", "../src/data/api.ts", "../src/pages/index.tsx", "../src/pages/users.tsx", "../src/main.tsx", "../src/router.ts"],
4
+ "sourcesContent": ["export let isBatching = false;\r\nconst queue: Function[] = [];\r\n\r\nexport function batchUpdates(fn: Function) {\r\n if (isBatching) {\r\n queue.push(fn);\r\n return;\r\n }\r\n\r\n isBatching = true;\r\n try {\r\n fn();\r\n while (queue.length > 0) {\r\n const nextFn = queue.shift();\r\n nextFn?.();\r\n }\r\n } finally {\r\n isBatching = false;\r\n }\r\n}\r\n\r\nexport function getIsBatching() {\r\n return isBatching;\r\n}\r\n", "import { createElement } from './jsx-runtime';\r\nimport { batchUpdates, isBatching } from './batch';\r\nimport { diff } from './vdom';\r\n\r\nlet currentRender: number = 0;\r\nconst states = new Map<number, any[]>();\r\nconst stateIndices = new Map<number, number>();\r\nconst effects = new Map<number, Effect[]>();\r\nconst memos = new Map<number, { value: any; deps: any[] }[]>();\r\nconst refs = new Map<number, any[]>();\r\n\r\ninterface Effect {\r\n cleanup?: () => void;\r\n deps?: any[];\r\n}\r\n\r\n// Add at the top with other declarations\r\nlet globalRenderCallback: ((element: any, container: HTMLElement) => void) | null = null;\r\nlet globalContainer: HTMLElement | null = null;\r\nlet currentElement: any = null;\r\n\r\nconst isServer = typeof window === 'undefined';\r\nconst serverStates = new Map<number, any>();\r\n\r\nexport function setRenderCallback(\r\n callback: (element: any, container: HTMLElement) => void,\r\n element: any,\r\n container: HTMLElement\r\n) {\r\n globalRenderCallback = callback;\r\n globalContainer = container;\r\n currentElement = element;\r\n}\r\n\r\nexport function prepareRender() {\r\n currentRender++;\r\n stateIndices.set(currentRender, 0);\r\n return currentRender;\r\n}\r\n\r\nexport function finishRender() {\r\n if (isServer) {\r\n serverStates.delete(currentRender);\r\n }\r\n currentRender = 0;\r\n}\r\n\r\nexport function useState<T>(initial: T): [T, (value: T | ((prev: T) => T)) => void] {\r\n if (!currentRender) {\r\n throw new Error('useState must be called within a render');\r\n }\r\n\r\n if (isServer) {\r\n // Server-side state handling\r\n if (!serverStates.has(currentRender)) {\r\n serverStates.set(currentRender, new Map());\r\n }\r\n const componentState = serverStates.get(currentRender)!;\r\n const index = stateIndices.get(currentRender) || 0;\r\n \r\n if (!componentState.has(index)) {\r\n componentState.set(index, initial);\r\n }\r\n\r\n const state = componentState.get(index);\r\n const setState = (newValue: T | ((prev: T) => T)) => {\r\n // No-op for server-side\r\n };\r\n\r\n stateIndices.set(currentRender, index + 1);\r\n return [state, setState];\r\n }\r\n\r\n if (!states.has(currentRender)) {\r\n states.set(currentRender, []);\r\n }\r\n\r\n const componentStates = states.get(currentRender)!;\r\n const index = stateIndices.get(currentRender)!;\r\n \r\n if (index >= componentStates.length) {\r\n componentStates.push(initial);\r\n }\r\n\r\n const state = componentStates[index];\r\n const setState = (newValue: T | ((prev: T) => T)) => {\r\n const nextValue = typeof newValue === 'function' \r\n ? (newValue as Function)(componentStates[index])\r\n : newValue;\r\n\r\n if (componentStates[index] === nextValue) return; // Skip if value hasn't changed\r\n \r\n componentStates[index] = nextValue;\r\n \r\n if (isBatching) {\r\n batchUpdates(() => rerender(currentRender));\r\n } else {\r\n rerender(currentRender);\r\n }\r\n };\r\n\r\n stateIndices.set(currentRender, index + 1);\r\n return [state, setState];\r\n}\r\n\r\nexport function useEffect(callback: () => (() => void) | void, deps?: any[]) {\r\n if (!currentRender) throw new Error('useEffect must be called within a render');\r\n \r\n const effectIndex = stateIndices.get(currentRender)!;\r\n \r\n if (!effects.has(currentRender)) {\r\n effects.set(currentRender, []);\r\n }\r\n\r\n const componentEffects = effects.get(currentRender)!;\r\n const prevEffect = componentEffects[effectIndex];\r\n \r\n // Run effect if deps changed\r\n if (!prevEffect || !deps || !prevEffect.deps || \r\n deps.some((dep, i) => dep !== prevEffect.deps![i])) {\r\n \r\n // Cleanup previous effect\r\n if (prevEffect?.cleanup) {\r\n prevEffect.cleanup();\r\n }\r\n\r\n // Schedule new effect\r\n queueMicrotask(() => {\r\n const cleanup = callback() || undefined;\r\n componentEffects[effectIndex] = { cleanup: cleanup, deps };\r\n });\r\n }\r\n \r\n stateIndices.set(currentRender, effectIndex + 1);\r\n}\r\n\r\nexport function useMemo<T>(factory: () => T, deps: any[]): T {\r\n if (!currentRender) throw new Error('useMemo must be called within a render');\r\n \r\n const memoIndex = stateIndices.get(currentRender)!;\r\n \r\n if (!memos.has(currentRender)) {\r\n memos.set(currentRender, []);\r\n }\r\n\r\n const componentMemos = memos.get(currentRender)!;\r\n const prevMemo = componentMemos[memoIndex];\r\n \r\n if (!prevMemo || (deps && deps.some((dep, i) => !Object.is(dep, prevMemo.deps[i])))) {\r\n const value = factory();\r\n componentMemos[memoIndex] = { value, deps };\r\n stateIndices.set(currentRender, memoIndex + 1);\r\n return value;\r\n }\r\n\r\n stateIndices.set(currentRender, memoIndex + 1);\r\n return prevMemo.value;\r\n}\r\n\r\nexport function useRef<T>(initial: T) {\r\n if (!currentRender) throw new Error('useRef must be called within a render');\r\n \r\n const refIndex = stateIndices.get(currentRender)!;\r\n \r\n if (!refs.has(currentRender)) {\r\n refs.set(currentRender, []);\r\n }\r\n\r\n const componentRefs = refs.get(currentRender)!;\r\n if (refIndex >= componentRefs.length) {\r\n // Initialize with an object that has a current property\r\n const ref = { current: initial };\r\n componentRefs.push(ref);\r\n stateIndices.set(currentRender, refIndex + 1);\r\n return ref;\r\n }\r\n\r\n const ref = componentRefs[refIndex];\r\n stateIndices.set(currentRender, refIndex + 1);\r\n return ref;\r\n}\r\n\r\n// Add a map to track component DOM nodes\r\nconst componentNodes = new Map<Function, Node>();\r\n\r\nasync function rerender(rendererId: number) {\r\n try {\r\n // Clean up effects\r\n const componentEffects = effects.get(rendererId);\r\n if (componentEffects) {\r\n componentEffects.forEach(effect => {\r\n if (effect.cleanup) effect.cleanup();\r\n });\r\n effects.set(rendererId, []);\r\n }\r\n\r\n if (globalRenderCallback && globalContainer && currentElement) {\r\n await globalRenderCallback(currentElement, globalContainer);\r\n }\r\n } catch (error) {\r\n console.error('Error during rerender:', error);\r\n }\r\n}\r\n\r\n// Add new hook for error boundaries\r\nexport function useErrorBoundary(): [Error | null, () => void] {\r\n const [error, setError] = useState<Error | null>(null);\r\n return [error, () => setError(null)];\r\n}\r\n\r\n// Remove withHooks export\r\n", "\r\n\r\nconst contexts = new Map<symbol, any>();\r\nlet currentRender: Function | null = null;\r\n\r\nexport interface Context<T> {\r\n Provider: (props: { value: T; children?: any }) => any;\r\n Consumer: (props: { children: (value: T) => any }) => any;\r\n _id: symbol;\r\n useSelector: <S>(selector: (state: T) => S) => S;\r\n}\r\n\r\nexport function createContext<T>(defaultValue: T): Context<T> {\r\n const context = {\r\n Provider: ({ value, children }: { value: T, children?: any }) => {\r\n return children;\r\n },\r\n Consumer: ({ children }: { children: (value: T) => any }) => {\r\n return children(defaultValue);\r\n },\r\n _id: Symbol(),\r\n useSelector: <S>(selector: (state: T) => S) => {\r\n return selector(defaultValue);\r\n }\r\n };\r\n\r\n return context;\r\n}\r\n\r\nexport function useContext<T>(context: any): T {\r\n return context;\r\n}\r\n", "import type { Component } from './component';\r\n\r\ninterface VNode {\r\n type: string | Function;\r\n props: Record<string, any>;\r\n}\r\n\r\nfunction jsx(type: string | Function, props: any): VNode {\r\n console.log('JSX Transform:', { type, props });\r\n const processedProps = { ...props };\r\n \r\n // Handle children properly\r\n if (arguments.length > 2) {\r\n processedProps.children = Array.prototype.slice.call(arguments, 2);\r\n }\r\n \r\n return { type, props: processedProps };\r\n}\r\n\r\nconst Fragment = ({ children }: { children: any }) => children;\r\n\r\nasync function createElement(vnode: VNode | any): Promise<Node> {\r\n console.log('Creating element from:', vnode);\r\n\r\n // Handle primitives and null\r\n if (vnode == null) {\r\n return document.createTextNode('');\r\n }\r\n \r\n if (typeof vnode === 'boolean') {\r\n return document.createTextNode('');\r\n }\r\n\r\n if (typeof vnode === 'number' || typeof vnode === 'string') {\r\n return document.createTextNode(String(vnode));\r\n }\r\n\r\n // Handle arrays\r\n if (Array.isArray(vnode)) {\r\n const fragment = document.createDocumentFragment();\r\n for (const child of vnode) {\r\n const node = await createElement(child);\r\n fragment.appendChild(node);\r\n }\r\n return fragment;\r\n }\r\n\r\n // Handle VNode\r\n if ('type' in vnode && vnode.props !== undefined) {\r\n const { type, props } = vnode;\r\n \r\n // Handle function components\r\n if (typeof type === 'function') {\r\n try {\r\n const result = await type(props || {});\r\n const node = await createElement(result);\r\n if (node instanceof Element) {\r\n node.setAttribute('data-component-id', type.name || type.toString());\r\n }\r\n return node;\r\n } catch (error) {\r\n console.error('Error rendering component:', error);\r\n return document.createTextNode('');\r\n }\r\n }\r\n\r\n // Create DOM element\r\n const element = document.createElement(type as string);\r\n \r\n // Handle props\r\n for (const [key, value] of Object.entries(props || {})) {\r\n if (key === 'children') continue;\r\n if (key.startsWith('on') && typeof value === 'function') {\r\n const eventName = key.toLowerCase().slice(2);\r\n // Remove existing event listener if any\r\n const existingHandler = (element as any).__events?.[eventName];\r\n if (existingHandler) {\r\n element.removeEventListener(eventName, existingHandler);\r\n }\r\n \r\n // Add new event listener\r\n element.addEventListener(eventName, value as EventListener);\r\n if (!(element as any).__events) {\r\n (element as any).__events = {};\r\n }\r\n (element as any).__events[eventName] = value;\r\n } else if (key === 'style' && typeof value === 'object') {\r\n Object.assign(element.style, value);\r\n } else if (key === 'className') {\r\n element.setAttribute('class', String(value));\r\n } else if (key !== 'key' && key !== 'ref') {\r\n element.setAttribute(key, String(value));\r\n }\r\n }\r\n\r\n // Handle children\r\n const children = props?.children;\r\n if (children != null) {\r\n const childArray = Array.isArray(children) ? children.flat() : [children];\r\n for (const child of childArray) {\r\n const childNode = await createElement(child);\r\n element.appendChild(childNode);\r\n }\r\n }\r\n\r\n return element;\r\n }\r\n\r\n // Handle other objects by converting to string\r\n return document.createTextNode(String(vnode));\r\n}\r\n\r\n// Export named functions and aliases without duplicates\r\nexport {\r\n jsx,\r\n jsx as jsxs,\r\n jsx as jsxDEV,\r\n Fragment,\r\n createElement\r\n};\r\n\r\n// Named exports object\r\nconst jsxRuntime = {\r\n jsx,\r\n jsxs: jsx,\r\n jsxDEV: jsx,\r\n Fragment,\r\n createElement\r\n};\r\n\r\nexport default jsxRuntime;\r\n", "import { createElement } from './jsx-runtime';\r\nimport { prepareRender, finishRender, setRenderCallback } from './hooks';\r\nimport { batchUpdates } from './batch';\r\n\r\nlet isHydrating = false;\r\n\r\nexport async function hydrate(element: any, container: HTMLElement) {\r\n isHydrating = true;\r\n try {\r\n await render(element, container);\r\n } finally {\r\n isHydrating = false;\r\n }\r\n}\r\n\r\nexport async function render(element: any, container: HTMLElement) {\r\n console.log('Rendering to:', container.id);\r\n \r\n batchUpdates(async () => {\r\n const rendererId = prepareRender();\r\n try {\r\n setRenderCallback(render, element, container);\r\n const domNode = await createElement(element);\r\n \r\n if (!isHydrating) {\r\n container.innerHTML = '';\r\n }\r\n container.appendChild(domNode);\r\n \r\n } finally {\r\n finishRender();\r\n }\r\n });\r\n}\r\n", "import { VNode, Component } from './types';\r\nimport { prepareRender, finishRender } from './hooks';\r\n\r\n/**\r\n * Renders a virtual DOM tree to an HTML string\r\n */\r\nexport function renderToString(vnode: VNode): string {\r\n // Reset hook state for this render\r\n prepareRender();\r\n \r\n // Render the tree to HTML\r\n const html = renderNodeToString(vnode);\r\n \r\n // Clean up after rendering\r\n finishRender();\r\n \r\n return html;\r\n}\r\n\r\n/**\r\n * Internal function to convert a virtual node to an HTML string\r\n */\r\nfunction renderNodeToString(vnode: VNode | string | number | boolean | null | undefined): string {\r\n // Handle primitive values\r\n if (vnode === null || vnode === undefined) return '';\r\n if (typeof vnode === 'boolean') return '';\r\n if (typeof vnode === 'number' || typeof vnode === 'string') return escapeHtml(String(vnode));\r\n \r\n // Handle function components\r\n if (typeof vnode.type === 'function') {\r\n const Component = vnode.type as Component;\r\n const renderedNode = Component(vnode.props || {});\r\n return renderNodeToString(renderedNode);\r\n }\r\n \r\n // Handle intrinsic elements (regular HTML tags)\r\n if (typeof vnode.type === 'string') {\r\n const tag = vnode.type;\r\n let attrs = '';\r\n let children = '';\r\n \r\n // Convert props to HTML attributes\r\n if (vnode.props) {\r\n for (const [key, value] of Object.entries(vnode.props)) {\r\n // Skip children prop as we handle it separately\r\n if (key === 'children') continue;\r\n \r\n // Skip event handlers (they start with 'on')\r\n if (key.startsWith('on')) continue;\r\n \r\n // Handle the className prop specially\r\n if (key === 'className') {\r\n attrs += ` class=\"${escapeHtml(value as string)}\"`;\r\n continue;\r\n }\r\n \r\n // Handle other attributes\r\n if (typeof value === 'string' || typeof value === 'number') {\r\n attrs += ` ${key}=\"${escapeHtml(String(value))}\"`;\r\n } else if (value === true) {\r\n // Boolean attributes\r\n attrs += ` ${key}`;\r\n }\r\n }\r\n }\r\n \r\n // Process children\r\n const childrenArray = (vnode.props?.children) \r\n ? Array.isArray(vnode.props.children) \r\n ? vnode.props.children \r\n : [vnode.props.children]\r\n : [];\r\n \r\n for (const child of childrenArray) {\r\n children += renderNodeToString(child);\r\n }\r\n \r\n // Self-closing tags\r\n const selfClosing = [\r\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\r\n 'link', 'meta', 'param', 'source', 'track', 'wbr'\r\n ];\r\n \r\n if (selfClosing.includes(tag)) {\r\n return `<${tag}${attrs}/>`;\r\n }\r\n \r\n // Regular tags with closing\r\n return `<${tag}${attrs}>${children}</${tag}>`;\r\n }\r\n \r\n // Handle fragments\r\n if (vnode.type === Symbol.for('react.fragment')) {\r\n let fragmentOutput = '';\r\n const children = Array.isArray(vnode.props?.children)\r\n ? vnode.props.children\r\n : vnode.props?.children ? [vnode.props.children] : [];\r\n \r\n for (const child of children) {\r\n fragmentOutput += renderNodeToString(child);\r\n }\r\n \r\n return fragmentOutput;\r\n }\r\n \r\n // Fallback for unknown node types\r\n console.warn('Unknown vnode type:', vnode.type);\r\n return '';\r\n}\r\n\r\n/**\r\n * Escape HTML special characters to prevent XSS\r\n */\r\nfunction escapeHtml(text: string): string {\r\n return text\r\n .replace(/&/g, '&amp;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/'/g, '&#039;');\r\n}\r\n", "import { createElement } from './jsx-runtime';\r\nimport { prepareRender, finishRender, setRenderCallback } from './hooks';\r\n\r\nexport { \r\n useState, \r\n useEffect, \r\n useMemo, \r\n useRef,\r\n useErrorBoundary \r\n} from './hooks';\r\n\r\nexport { createContext, useContext } from './context';\r\nexport { batchUpdates } from './batch';\r\nexport { jsx, jsxs, Fragment } from './jsx-runtime';\r\nexport { render, hydrate } from './renderer';\r\nexport { renderToString } from './server-renderer';\r\n\r\n// Re-export types for client-side usage\r\nexport type { Context } from './context';\r\nexport type { VNode } from './types';\r\n\r\n// Export a placeholder for server functionality that works in browser environments\r\nexport const server = {\r\n async getServer() {\r\n throw new Error('Server module can only be used in Node.js environment');\r\n }\r\n};\r\n\r\nlet isHydrating = false;\r\n", "// Application configuration\r\n// Modify this file to customize your application without changing core files\r\n\r\nexport const AppConfig = {\r\n // App information\r\n title: 'Frontend Hamroun App',\r\n description: 'A full-stack application built with Frontend Hamroun',\r\n \r\n // Navigation\r\n navigation: [\r\n { path: '/', label: 'Home' },\r\n { path: '/about', label: 'About' },\r\n { path: '/users', label: 'Users' }\r\n ],\r\n \r\n // API endpoints\r\n api: {\r\n baseUrl: '/api',\r\n endpoints: {\r\n users: '/users',\r\n posts: '/posts'\r\n }\r\n },\r\n \r\n // Default meta tags\r\n meta: {\r\n viewport: 'width=device-width, initial-scale=1.0',\r\n charset: 'UTF-8',\r\n author: 'Your Name',\r\n keywords: 'frontend-hamroun, fullstack, template'\r\n },\r\n \r\n // Style customization\r\n theme: {\r\n primaryColor: '#0066cc',\r\n backgroundColor: '#ffffff',\r\n textColor: '#333333',\r\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, \"Open Sans\", \"Helvetica Neue\", sans-serif'\r\n }\r\n};\r\n\r\nexport default AppConfig;\r\n", "import { jsx, Fragment } from 'frontend-hamroun';\nimport { jsx } from 'frontend-hamroun';\r\nimport AppConfig from '../config';\r\n\r\nexport interface LayoutProps {\r\n children: any;\r\n title?: string;\r\n showNavigation?: boolean;\r\n}\r\n\r\nexport default function Layout({ children, title = AppConfig.title, showNavigation = true }: LayoutProps) {\r\n const { navigation } = AppConfig;\r\n \r\n return (\r\n <div className=\"container mx-auto px-4 py-8 max-w-5xl\">\r\n <header className=\"mb-8\">\r\n <h1 className=\"text-4xl font-bold text-gray-800 mb-4\">{title}</h1>\r\n \r\n {showNavigation && (\r\n <nav className=\"mb-6\">\r\n <ul className=\"flex space-x-6\">\r\n {navigation.map(item => (\r\n <li key={item.path}>\r\n <a \r\n href={item.path} \r\n className=\"text-blue-600 hover:text-blue-800 hover:underline transition-colors font-medium\"\r\n >\r\n {item.label}\r\n </a>\r\n </li>\r\n ))}\r\n </ul>\r\n </nav>\r\n )}\r\n </header>\r\n \r\n <main className=\"min-h-[50vh]\">\r\n {children}\r\n </main>\r\n \r\n <footer className=\"mt-12 pt-6 border-t border-gray-200 text-gray-600 text-sm\">\r\n <p>{AppConfig.title} &copy; {new Date().getFullYear()}</p>\r\n </footer>\r\n </div>\r\n );\r\n}\r\n", "import { jsx, Fragment } from 'frontend-hamroun';\nimport { jsx } from 'frontend-hamroun';\r\nimport Layout from '../../components/Layout';\r\n\r\nconst AboutPage = ({ initialState }) => {\r\n return (\r\n <Layout title=\"About This App\">\r\n <div className=\"max-w-4xl mx-auto bg-white shadow-lg rounded-lg overflow-hidden\">\r\n <div className=\"p-8\">\r\n <p className=\"text-lg text-gray-700 mb-6\">\r\n This is a frontend application built with Frontend Hamroun framework and styled with Tailwind CSS.\r\n </p>\r\n <p className=\"text-gray-600 mb-8\">\r\n It features server-side rendering, client-side navigation, and websocket-based live reloading during development.\r\n </p>\r\n \r\n <div className=\"bg-gray-50 p-6 rounded-lg border border-gray-200 mb-8\">\r\n <h2 className=\"text-xl font-semibold text-gray-800 mb-4\">Key Features</h2>\r\n <ul className=\"space-y-2 text-gray-700\">\r\n <li className=\"flex items-center\">\r\n <svg className=\"w-5 h-5 text-green-500 mr-2\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M5 13l4 4L19 7\"></path>\r\n </svg>\r\n Server-side rendering\r\n </li>\r\n <li className=\"flex items-center\">\r\n <svg className=\"w-5 h-5 text-green-500 mr-2\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M5 13l4 4L19 7\"></path>\r\n </svg>\r\n Client-side navigation\r\n </li>\r\n <li className=\"flex items-center\">\r\n <svg className=\"w-5 h-5 text-green-500 mr-2\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M5 13l4 4L19 7\"></path>\r\n </svg>\r\n Component-based architecture\r\n </li>\r\n <li className=\"flex items-center\">\r\n <svg className=\"w-5 h-5 text-green-500 mr-2\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M5 13l4 4L19 7\"></path>\r\n </svg>\r\n Integrated API backend\r\n </li>\r\n <li className=\"flex items-center\">\r\n <svg className=\"w-5 h-5 text-green-500 mr-2\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M5 13l4 4L19 7\"></path>\r\n </svg>\r\n Live reload during development\r\n </li>\r\n <li className=\"flex items-center\">\r\n <svg className=\"w-5 h-5 text-green-500 mr-2\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M5 13l4 4L19 7\"></path>\r\n </svg>\r\n Tailwind CSS for styling\r\n </li>\r\n </ul>\r\n </div>\r\n \r\n <a href=\"/\" className=\"inline-block px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors\">\r\n Back to Home\r\n </a>\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n};\r\n\r\nexport default AboutPage;\r\n", "import { jsx, Fragment } from 'frontend-hamroun';\nimport { jsx } from 'frontend-hamroun';\r\n\r\nconst UserList = ({ users }) => {\r\n if (!users || users.length === 0) {\r\n return (\r\n <div className=\"p-4 bg-gray-50 rounded-lg border border-gray-200 my-4\">\r\n <p className=\"text-gray-500 italic\">No users found or still loading...</p>\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div className=\"rounded-lg overflow-hidden my-4\">\r\n <h3 className=\"text-lg font-medium mb-3\">Users ({users.length})</h3>\r\n <ul className=\"divide-y divide-gray-200 border border-gray-200 rounded-lg overflow-hidden\">\r\n {users.map(user => (\r\n <li key={user.id} className=\"flex justify-between items-center p-4 hover:bg-gray-50\">\r\n <span className=\"font-medium text-gray-800\">{user.name}</span>\r\n <span className=\"text-gray-500 text-sm\">{user.email}</span>\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n );\r\n};\r\n\r\nexport default UserList;\r\n", "// General API utility for data fetching\r\n\r\n/**\r\n * Fetch data from an API endpoint\r\n * @param endpoint The API endpoint to fetch from (without /api prefix)\r\n * @param options Additional fetch options\r\n * @returns The parsed JSON response or null if there was an error\r\n */\r\nexport async function fetchApi(endpoint: string, options = {}) {\r\n const url = endpoint.startsWith('/') ? `/api${endpoint}` : `/api/${endpoint}`;\r\n \r\n try {\r\n console.log(`[API] Fetching data from: ${url}`);\r\n const response = await fetch(url, {\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Accept': 'application/json'\r\n },\r\n ...options\r\n });\r\n \r\n if (!response.ok) {\r\n throw new Error(`API request failed: ${response.status} ${response.statusText}`);\r\n }\r\n \r\n const data = await response.json();\r\n console.log(`[API] Successfully fetched data from: ${url}`);\r\n return data;\r\n } catch (error) {\r\n console.error(`[API] Error fetching from ${url}:`, error);\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * User-related API calls\r\n */\r\nexport const UserApi = {\r\n getAll: () => fetchApi('/users'),\r\n getById: (id: number | string) => fetchApi(`/users/${id}`),\r\n create: (data: any) => fetchApi('/users', {\r\n method: 'POST',\r\n body: JSON.stringify(data)\r\n }),\r\n update: (id: number | string, data: any) => fetchApi(`/users/${id}`, {\r\n method: 'PUT',\r\n body: JSON.stringify(data)\r\n }),\r\n delete: (id: number | string) => fetchApi(`/users/${id}`, {\r\n method: 'DELETE'\r\n })\r\n};\r\n\r\n/**\r\n * Post-related API calls\r\n */\r\nexport const PostApi = {\r\n getAll: () => fetchApi('/posts'),\r\n getById: (id: number | string) => fetchApi(`/posts/${id}`),\r\n create: (data: any) => fetchApi('/posts', {\r\n method: 'POST',\r\n body: JSON.stringify(data)\r\n }),\r\n update: (id: number | string, data: any) => fetchApi(`/posts/${id}`, {\r\n method: 'PUT',\r\n body: JSON.stringify(data)\r\n }),\r\n delete: (id: number | string) => fetchApi(`/posts/${id}`, {\r\n method: 'DELETE'\r\n })\r\n};\r\n", "import { jsx, Fragment } from 'frontend-hamroun';\nimport { jsx } from 'frontend-hamroun';\r\nimport Layout from '../components/Layout';\r\nimport UserList from '../components/UserList';\r\nimport { UserApi } from '../data/api';\r\n\r\nconst HomePage = ({ initialState }) => (\r\n <Layout title=\"Home\">\r\n <div className=\"max-w-4xl mx-auto py-8\">\r\n <h1 className=\"text-3xl font-bold text-blue-600 mb-6\">Welcome to your Frontend Hamroun application!</h1>\r\n \r\n <div className=\"bg-white shadow-lg rounded-lg p-6 mb-8\">\r\n <h2 className=\"text-xl font-semibold text-gray-800 mb-4\">User List</h2>\r\n <UserList users={initialState.data?.users || []} />\r\n </div>\r\n \r\n <div className=\"bg-gray-50 rounded-lg p-6 border border-gray-200\">\r\n <h3 className=\"text-lg font-medium text-gray-700 mb-3\">Application State</h3>\r\n <pre className=\"overflow-auto p-4 bg-gray-100 rounded-md text-sm text-gray-800\">\r\n {JSON.stringify(initialState, null, 2)}\r\n </pre>\r\n </div>\r\n </div>\r\n </Layout>\r\n);\r\n\r\n// Static method to fetch initial data for this page\r\nHomePage.getInitialData = async () => {\r\n return {\r\n users: await UserApi.getAll()\r\n };\r\n};\r\n\r\nexport default HomePage;\r\n", "import { jsx, Fragment } from 'frontend-hamroun';\nimport { jsx } from 'frontend-hamroun';\r\nimport Layout from '../components/Layout';\r\nimport { UserApi } from '../data/api';\r\n\r\nconst UsersPage = ({ initialState }) => {\r\n const users = initialState.data?.users || [];\r\n\r\n return (\r\n <Layout title=\"User Management\">\r\n <div className=\"max-w-4xl mx-auto\">\r\n <div className=\"bg-blue-50 p-6 rounded-lg mb-8 border border-blue-100\">\r\n <h2 className=\"text-xl font-semibold text-blue-800 mb-2\">Data Fetching Demo</h2>\r\n <p className=\"text-blue-700\">This page demonstrates dynamic data fetching with the Users API.</p>\r\n </div>\r\n \r\n <div className=\"bg-white shadow-md rounded-lg overflow-hidden\">\r\n <div className=\"px-6 py-4 border-b border-gray-200\">\r\n <h2 className=\"text-xl font-semibold text-gray-800\">User List</h2>\r\n </div>\r\n \r\n {users.length === 0 ? (\r\n <div className=\"p-6 text-center text-gray-500\">\r\n <p>No users found.</p>\r\n </div>\r\n ) : (\r\n <div className=\"overflow-x-auto\">\r\n <table className=\"w-full\">\r\n <thead>\r\n <tr className=\"bg-gray-50\">\r\n <th className=\"text-left py-3 px-6 font-medium text-gray-600 text-sm uppercase tracking-wider border-b\">ID</th>\r\n <th className=\"text-left py-3 px-6 font-medium text-gray-600 text-sm uppercase tracking-wider border-b\">Name</th>\r\n <th className=\"text-left py-3 px-6 font-medium text-gray-600 text-sm uppercase tracking-wider border-b\">Email</th>\r\n </tr>\r\n </thead>\r\n <tbody className=\"divide-y divide-gray-200\">\r\n {users.map(user => (\r\n <tr key={user.id} className=\"hover:bg-gray-50\">\r\n <td className=\"py-4 px-6 text-sm text-gray-900\">{user.id}</td>\r\n <td className=\"py-4 px-6 text-sm font-medium text-gray-900\">{user.name}</td>\r\n <td className=\"py-4 px-6 text-sm text-gray-500\">{user.email}</td>\r\n </tr>\r\n ))}\r\n </tbody>\r\n </table>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n </Layout>\r\n );\r\n};\r\n\r\n// Static method to fetch initial data for this page\r\nUsersPage.getInitialData = async () => {\r\n return {\r\n users: await UserApi.getAll()\r\n };\r\n};\r\n\r\nexport default UsersPage;\r\n", "import { jsx, Fragment } from 'frontend-hamroun';\nimport { render, hydrate, jsx } from 'frontend-hamroun';\r\nimport { router } from './router';\r\n// Import Tailwind CSS\r\nimport './styles.css';\r\n\r\n// Import pages directly to ensure they're available\r\nimport HomePage from './pages/index';\r\nimport AboutPage from './pages/about';\r\nimport UsersPage from './pages/users';\r\n\r\n// Create a mutable variable for hydration state\r\nlet isHydrating = document.getElementById('root')?.innerHTML.trim() !== '';\r\n\r\nconsole.log('[Client] Running client-side code. Hydration state:', isHydrating);\r\n\r\n// Get initial state from server\r\nconst initialState = window.__INITIAL_STATE__ || { \r\n route: window.location.pathname,\r\n timestamp: new Date().toISOString(),\r\n serverRendered: false,\r\n data: {\r\n users: null,\r\n posts: null\r\n }\r\n};\r\n\r\nconsole.log('[Client] Initial state:', initialState);\r\n\r\n// Function to fetch data from API\r\nasync function fetchData(path) {\r\n try {\r\n console.log(`[Client] Fetching data from API: ${path}`);\r\n const response = await fetch(`/api${path}`);\r\n if (!response.ok) {\r\n throw new Error(`API request failed: ${response.status}`);\r\n }\r\n const data = await response.json();\r\n console.log(`[Client] Received data:`, data);\r\n return data;\r\n } catch (error) {\r\n console.error(`[Client] Error fetching data from ${path}:`, error);\r\n return null;\r\n }\r\n}\r\n\r\n// Initialize known routes\r\nfunction initializeRouter() {\r\n console.log('[Client] Initializing router with known pages');\r\n router.register('index', HomePage);\r\n router.register('about', AboutPage);\r\n router.register('users', UsersPage);\r\n}\r\n\r\n// Call initialization before rendering\r\ninitializeRouter();\r\n\r\n// Function to determine which page to render based on the route\r\nasync function renderApp() {\r\n try {\r\n // Get current path\r\n const currentPath = window.location.pathname;\r\n \r\n console.log(`[Client] Rendering for path: ${currentPath}`);\r\n \r\n // Find the root element\r\n const rootElement = document.getElementById('root');\r\n if (!rootElement) {\r\n console.error('[Client] Root element not found');\r\n return;\r\n }\r\n \r\n // Fetch data based on the current route\r\n console.log(`[Client] Fetching users data for ${currentPath}`);\r\n const users = await fetchData('/users');\r\n \r\n if (users) {\r\n initialState.data = {\r\n ...initialState.data,\r\n users\r\n };\r\n console.log(`[Client] Updated state with ${users.length} users`);\r\n }\r\n \r\n // Get the component for the current path\r\n let PageComponent = null;\r\n \r\n // Special cases for known routes\r\n if (currentPath === '/about') {\r\n try {\r\n const AboutPage = await import('./pages/about');\r\n PageComponent = AboutPage.default;\r\n } catch (err) {\r\n console.error('[Client] Failed to load about page:', err);\r\n }\r\n } else if (currentPath === '/users') {\r\n try {\r\n const UsersPage = await import('./pages/users');\r\n PageComponent = UsersPage.default;\r\n } catch (err) {\r\n console.error('[Client] Failed to load users page:', err);\r\n }\r\n } else {\r\n // Try the router for other paths\r\n PageComponent = await router.resolve(currentPath);\r\n }\r\n \r\n // If component wasn't found, handle special cases\r\n if (!PageComponent) {\r\n console.warn(`[Client] Page component not found for ${currentPath}, checking special cases`);\r\n \r\n // Special case for /users\r\n if (currentPath === '/users') {\r\n try {\r\n console.log('[Client] Attempting direct import of Users page');\r\n const UsersModule = await import('./pages/users');\r\n if (UsersModule.default) {\r\n PageComponent = UsersModule.default;\r\n }\r\n } catch (err) {\r\n console.error('[Client] Failed to import Users page:', err);\r\n }\r\n }\r\n }\r\n \r\n // Final fallback if still no component\r\n if (!PageComponent) {\r\n // Create a simple fallback component\r\n PageComponent = ({ initialState }) => jsx('div', { \r\n style: 'padding: 20px; max-width: 800px; margin: 0 auto;' \r\n }, [\r\n jsx('h1', {}, ['Page Not Found']),\r\n jsx('p', {}, [`No component found for path: ${initialState.route}`]),\r\n jsx('a', { href: '/', style: 'color: #0066cc;' }, ['Go to Home'])\r\n ]);\r\n }\r\n \r\n // Update state with current route and timestamp\r\n initialState.route = currentPath;\r\n initialState.timestamp = new Date().toISOString();\r\n initialState.serverRendered = false; // This is now a client render\r\n \r\n // Render or hydrate\r\n if (isHydrating) {\r\n console.log('[Client] Hydrating server-rendered content');\r\n hydrate(<PageComponent initialState={initialState} />, rootElement);\r\n // After hydration is complete, set to false for future renders\r\n isHydrating = false;\r\n } else {\r\n console.log('[Client] Rendering client-side');\r\n render(<PageComponent initialState={initialState} />, rootElement);\r\n }\r\n } catch (error) {\r\n console.error('[Client] Error rendering app:', error);\r\n \r\n // Render error message\r\n const rootElement = document.getElementById('root');\r\n if (rootElement) {\r\n rootElement.innerHTML = `\r\n <div style=\"padding: 20px; color: red;\">\r\n <h1>Error</h1>\r\n <p>${error.message}</p>\r\n <pre>${error.stack}</pre>\r\n </div>\r\n `;\r\n }\r\n }\r\n}\r\n\r\n// Initial render/hydrate\r\nrenderApp();\r\n\r\n// Set up client-side navigation\r\ndocument.addEventListener('click', (e) => {\r\n // Find if the clicked element is an anchor or inside an anchor\r\n let target = e.target;\r\n while (target && target.tagName !== 'A') {\r\n target = target.parentElement;\r\n if (!target) break;\r\n }\r\n \r\n // If we found an anchor that's for internal navigation\r\n if (target && \r\n target.tagName === 'A' && \r\n target.getAttribute('href') &&\r\n target.getAttribute('href').startsWith('/') && \r\n !target.getAttribute('href').startsWith('//') &&\r\n !target.getAttribute('target')) {\r\n e.preventDefault();\r\n const href = target.getAttribute('href');\r\n \r\n // Update history\r\n window.history.pushState(null, '', href);\r\n \r\n // Set to false since we're doing client-side navigation now\r\n isHydrating = false;\r\n \r\n // Re-render the app\r\n renderApp();\r\n }\r\n});\r\n\r\n// Handle back/forward navigation\r\nwindow.addEventListener('popstate', () => {\r\n // Not hydrating during history navigation\r\n isHydrating = false;\r\n renderApp();\r\n});\r\n\r\n// Setup live reload for development\r\nif (typeof io !== 'undefined') {\r\n const socket = io();\r\n \r\n socket.on('reload', () => {\r\n console.log('[Dev] Reloading page due to file changes');\r\n window.location.reload();\r\n });\r\n \r\n socket.on('welcome', (data) => {\r\n console.log('[Dev] Connected to development server:', data);\r\n });\r\n}\r\n", "import { jsx } from 'frontend-hamroun';\r\n// Use dynamic import to ensure it's available\r\n// import UsersPage from './pages/users'; \r\n\r\n// Type definitions for page components\r\nexport interface PageProps {\r\n initialState: any;\r\n}\r\n\r\nexport interface PageComponent {\r\n (props: PageProps): any;\r\n getInitialData?: (path: string) => Promise<any>;\r\n}\r\n\r\n// Define router interface for type safety\r\ninterface RouteParams {\r\n [key: string]: string;\r\n}\r\n\r\n// Define router class for handling routes\r\nexport class Router {\r\n private routes: Record<string, PageComponent> = {};\r\n private notFoundComponent: PageComponent | null = null;\r\n \r\n // Register a component for a specific route\r\n register(path: string, component: PageComponent): Router {\r\n const normalizedPath = path === '/' ? 'index' : path.replace(/^\\//, '');\r\n this.routes[normalizedPath] = component;\r\n console.log(`[Router] Registered component for path: ${normalizedPath}`);\r\n return this;\r\n }\r\n \r\n // Set the not found component\r\n setNotFound(component: PageComponent): Router {\r\n this.notFoundComponent = component;\r\n return this;\r\n }\r\n \r\n // Get the not found component\r\n getNotFound(): PageComponent | null {\r\n return this.notFoundComponent;\r\n }\r\n \r\n // Get all registered routes\r\n getAllRoutes(): Record<string, PageComponent> {\r\n return this.routes;\r\n }\r\n \r\n // Find component for a given path\r\n async resolve(path: string): Promise<PageComponent | null> {\r\n const normalizedPath = path === '/' ? 'index' : path.replace(/^\\//, '');\r\n \r\n console.log(`[Router] Resolving component for path: ${normalizedPath}`);\r\n \r\n // Check for exact match first\r\n if (this.routes[normalizedPath]) {\r\n return this.routes[normalizedPath];\r\n }\r\n \r\n // Check for nested routes (e.g., \"users/123\" should match a \"users/[id]\" route)\r\n const pathSegments = normalizedPath.split('/');\r\n const registeredRoutes = Object.keys(this.routes);\r\n \r\n // Try to find dynamic route matches\r\n for (const route of registeredRoutes) {\r\n const routeSegments = route.split('/');\r\n \r\n // Skip routes with different segment count\r\n if (routeSegments.length !== pathSegments.length) continue;\r\n \r\n let isMatch = true;\r\n const params: RouteParams = {};\r\n \r\n // Compare each segment\r\n for (let i = 0; i < routeSegments.length; i++) {\r\n const routeSegment = routeSegments[i];\r\n const pathSegment = pathSegments[i];\r\n \r\n // Handle dynamic segments (e.g., [id])\r\n if (routeSegment.startsWith('[') && routeSegment.endsWith(']')) {\r\n const paramName = routeSegment.slice(1, -1);\r\n params[paramName] = pathSegment;\r\n }\r\n // Regular segment, must match exactly\r\n else if (routeSegment !== pathSegment) {\r\n isMatch = false;\r\n break;\r\n }\r\n }\r\n \r\n if (isMatch) {\r\n console.log(`[Router] Found dynamic route match: ${route}`);\r\n // Return the component with params\r\n return this.routes[route];\r\n }\r\n }\r\n \r\n // If no match found yet, try to dynamically import the component\r\n try {\r\n let component = null;\r\n let resolvedPath = normalizedPath;\r\n \r\n // Next.js-style dynamic route resolution\r\n try {\r\n // First, try direct file match (e.g., ./pages/users.tsx)\r\n // Focus on .tsx since that's what this project uses\r\n try {\r\n console.log(`[Router] Trying direct import: ./pages/${resolvedPath}.tsx`);\r\n const directModule = await import(/* @vite-ignore */ `./pages/${resolvedPath}.tsx`)\r\n .catch(() => null);\r\n \r\n if (directModule) {\r\n component = directModule.default || directModule;\r\n }\r\n } catch (e) {\r\n console.warn(`[Router] Error importing ./pages/${resolvedPath}.tsx:`, e);\r\n }\r\n \r\n // Next, try index file in directory (e.g., ./pages/about/index.tsx)\r\n if (!component && !resolvedPath.endsWith('index')) {\r\n try {\r\n console.log(`[Router] Trying index file: ./pages/${resolvedPath}/index.tsx`);\r\n const indexModule = await import(/* @vite-ignore */ `./pages/${resolvedPath}/index.tsx`)\r\n .catch(() => null);\r\n \r\n if (indexModule) {\r\n component = indexModule.default || indexModule;\r\n }\r\n } catch (e) {\r\n console.warn(`[Router] Error importing ./pages/${resolvedPath}/index.tsx:`, e);\r\n }\r\n }\r\n } catch (routeError) {\r\n console.warn(`[Router] Error resolving Next.js style route:`, routeError);\r\n }\r\n \r\n // Register and return component if found\r\n if (component) {\r\n this.routes[normalizedPath] = component;\r\n return component;\r\n }\r\n } catch (error) {\r\n console.warn(`[Router] Error importing component for ${normalizedPath}:`, error);\r\n }\r\n \r\n // If we reach here, no component was found\r\n console.warn(`[Router] No component found for path: ${normalizedPath}`);\r\n return this.notFoundComponent;\r\n }\r\n\r\n // Auto-discover components in the pages directory\r\n async discoverRoutes(): Promise<Record<string, PageComponent>> {\r\n console.log('[Router] Auto-discovering routes from pages directory...');\r\n \r\n try {\r\n // Use a more conventional approach instead of relying on import.meta.glob\r\n await this.tryLoadCoreRoutes();\r\n \r\n console.log('[Router] Route discovery complete. Available routes:', Object.keys(this.routes));\r\n return this.routes;\r\n } catch (error) {\r\n console.error('[Router] Error discovering routes:', error);\r\n await this.tryLoadCoreRoutes();\r\n return this.routes;\r\n }\r\n }\r\n \r\n // Fallback method to load core routes\r\n async tryLoadCoreRoutes(): Promise<void> {\r\n // First try the auto-discovery approach with dynamic imports\r\n const pageModules = [\r\n { path: './pages/index', route: 'index' },\r\n { path: './pages/about/index', route: 'about' },\r\n { path: './pages/users', route: 'users' }\r\n ];\r\n \r\n // Try importing each module dynamically - focus on .tsx files\r\n for (const { path, route } of pageModules) {\r\n if (!this.routes[route]) {\r\n try {\r\n console.log(`[Router] Attempting to load route: ${route}`);\r\n \r\n // Only try .tsx imports since that's what the project uses\r\n const module = await import(/* @vite-ignore */ `${path}.tsx`)\r\n .catch(() => null);\r\n \r\n if (module && module.default) {\r\n this.routes[route] = module.default;\r\n console.log(`[Router] Registered route: ${route}`);\r\n }\r\n } catch (error) {\r\n console.warn(`[Router] Could not load route: ${route}`, error);\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n// Create and export a router instance\r\nexport const router = new Router();\r\n\r\n// NotFound component as fallback (using jsx function instead of JSX syntax)\r\nexport const NotFound: PageComponent = ({ initialState }) => {\r\n return jsx('div', { className: 'container mx-auto px-4 py-12 max-w-4xl' }, [\r\n jsx('div', { className: 'bg-white shadow-lg rounded-lg overflow-hidden p-8' }, [\r\n jsx('h1', { className: 'text-3xl font-bold text-gray-800 mb-4' }, ['Page Not Found']),\r\n jsx('p', { className: 'text-gray-600 mb-6' }, [`No component found for path: ${initialState?.route || 'unknown'}`]),\r\n jsx('a', { href: '/', className: 'text-blue-600 hover:text-blue-800 hover:underline transition-colors' }, ['Go to Home']),\r\n jsx('div', { className: 'mt-8 p-6 bg-gray-50 rounded-lg border border-gray-200' }, [\r\n jsx('h3', { className: 'text-lg font-medium text-gray-700 mb-3' }, ['Available Routes']),\r\n jsx('ul', { className: 'space-y-2' }, [\r\n jsx('li', {}, [jsx('a', { href: '/', className: 'text-blue-600 hover:text-blue-800 hover:underline' }, ['Home'])]),\r\n jsx('li', {}, [jsx('a', { href: '/about', className: 'text-blue-600 hover:text-blue-800 hover:underline' }, ['About'])]),\r\n jsx('li', {}, [jsx('a', { href: '/users', className: 'text-blue-600 hover:text-blue-800 hover:underline' }, ['Users'])])\r\n ])\r\n ])\r\n ])\r\n ]);\r\n};\r\n\r\n// Set NotFound as the default fallback\r\nrouter.setNotFound(NotFound);\r\n\r\n// Improved router initialization with auto-discovery\r\nexport async function initializeRouter(): Promise<Router> {\r\n try {\r\n console.log('[Router] Initializing router with auto-discovery...');\r\n \r\n // Auto-discover routes\r\n await router.discoverRoutes();\r\n \r\n // Register fallback pages for key routes if they weren't discovered\r\n const routes = router.getAllRoutes();\r\n \r\n if (!routes['index']) {\r\n router.register('index', ({ initialState }: PageProps) => jsx('div', {}, [\r\n jsx('h1', {}, ['Welcome']),\r\n jsx('p', {}, ['This is the home page.'])\r\n ]));\r\n }\r\n \r\n if (!routes['about']) {\r\n router.register('about', ({ initialState }: PageProps) => jsx('div', {}, [\r\n jsx('h1', {}, ['About']),\r\n jsx('p', {}, ['This is the about page.'])\r\n ]));\r\n }\r\n \r\n console.log('[Router] Router initialized with routes:', Object.keys(router.getAllRoutes()));\r\n return router;\r\n } catch (error) {\r\n console.error('[Router] Error initializing router:', error);\r\n return router;\r\n }\r\n}\r\n"],
5
+ "mappings": "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",
6
+ "names": ["initialState", "initialState", "initialState", "initialState", "isHydrating", "AboutPage", "UsersPage", "initialState"]
7
+ }
@@ -0,0 +1,211 @@
1
+ import * as esbuild from 'esbuild';
2
+ import path from 'path';
3
+ import fs from 'fs/promises';
4
+ import { fileURLToPath } from 'url';
5
+ import { existsSync } from 'fs';
6
+ import postcss from 'postcss';
7
+ import tailwindcss from 'tailwindcss';
8
+ import autoprefixer from 'autoprefixer';
9
+
10
+ // Get __dirname equivalent in ESM
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ // Build configuration
15
+ const clientEntry = path.join(__dirname, 'src', 'main.tsx');
16
+ const serverEntry = path.join(__dirname, 'server.ts');
17
+ const outdir = path.join(__dirname, 'dist');
18
+
19
+ // Ensure output directory exists
20
+ if (!existsSync(outdir)) {
21
+ await fs.mkdir(outdir, { recursive: true });
22
+ }
23
+
24
+ // CSS processing plugin for esbuild
25
+ const cssPlugin = {
26
+ name: 'css-processor',
27
+ setup(build) {
28
+ // Handle CSS files
29
+ build.onLoad({ filter: /\.css$/ }, async (args) => {
30
+ // Read the CSS file
31
+ const css = await fs.readFile(args.path, 'utf8');
32
+
33
+ // Process with PostCSS (Tailwind + Autoprefixer)
34
+ try {
35
+ const result = await postcss([
36
+ tailwindcss,
37
+ autoprefixer
38
+ ]).process(css, {
39
+ from: args.path,
40
+ to: path.join(outdir, 'build', 'styles.css')
41
+ });
42
+
43
+ // Return processed CSS
44
+ return {
45
+ contents: result.css,
46
+ loader: 'css'
47
+ };
48
+ } catch (error) {
49
+ console.error('Error processing CSS:', error);
50
+ return {
51
+ errors: [{ text: 'Error processing CSS with PostCSS: ' + error.message }],
52
+ loader: 'css'
53
+ };
54
+ }
55
+ });
56
+ }
57
+ };
58
+
59
+ // Copy static files
60
+ async function copyPublicFiles() {
61
+ const publicDir = path.join(__dirname, 'public');
62
+ const publicOutDir = path.join(outdir, 'public');
63
+
64
+ if (existsSync(publicDir)) {
65
+ if (!existsSync(publicOutDir)) {
66
+ await fs.mkdir(publicOutDir, { recursive: true });
67
+ }
68
+
69
+ // Read all files in public directory
70
+ const files = await fs.readdir(publicDir, { withFileTypes: true });
71
+
72
+ // Copy each file
73
+ for (const file of files) {
74
+ const srcPath = path.join(publicDir, file.name);
75
+ const destPath = path.join(publicOutDir, file.name);
76
+
77
+ if (file.isDirectory()) {
78
+ // Copy directory recursively
79
+ await fs.cp(srcPath, destPath, { recursive: true });
80
+ } else {
81
+ // Copy file
82
+ await fs.copyFile(srcPath, destPath);
83
+ }
84
+ }
85
+
86
+ console.log('✓ Public files copied to dist/public');
87
+ }
88
+
89
+ // Copy index.html
90
+ const indexPath = path.join(__dirname, 'index.html');
91
+ if (existsSync(indexPath)) {
92
+ await fs.copyFile(indexPath, path.join(outdir, 'index.html'));
93
+ console.log('✓ index.html copied to dist');
94
+ }
95
+ }
96
+
97
+ // Build client-side code
98
+ async function buildClient() {
99
+ console.log('Building client...');
100
+
101
+ try {
102
+ await esbuild.build({
103
+ entryPoints: [clientEntry],
104
+ bundle: true,
105
+ minify: true,
106
+ format: 'esm',
107
+ outfile: path.join(outdir, 'build', 'main.js'),
108
+ jsx: 'automatic',
109
+ jsxFactory: 'jsx',
110
+ jsxFragment: 'Fragment',
111
+ plugins: [
112
+ {
113
+ name: 'jsx-runtime-import',
114
+ setup(build) {
115
+ build.onLoad({ filter: /\.(tsx|jsx)$/ }, async (args) => {
116
+ const source = await fs.readFile(args.path, 'utf8');
117
+ const content = `import { jsx, Fragment } from 'frontend-hamroun';\n${source}`;
118
+ return { contents: content, loader: args.path.endsWith('tsx') ? 'tsx' : 'jsx' };
119
+ });
120
+ }
121
+ },
122
+ cssPlugin
123
+ ],
124
+ loader: {
125
+ '.tsx': 'tsx',
126
+ '.ts': 'ts',
127
+ '.jsx': 'jsx',
128
+ '.js': 'js',
129
+ '.css': 'css',
130
+ '.json': 'json',
131
+ '.png': 'file',
132
+ '.jpg': 'file',
133
+ '.svg': 'file'
134
+ },
135
+ define: {
136
+ 'process.env.NODE_ENV': '"production"'
137
+ },
138
+ sourcemap: false,
139
+ metafile: true
140
+ });
141
+
142
+ console.log('✓ Client build complete');
143
+ } catch (error) {
144
+ console.error('Client build failed:', error);
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ // Build server-side code
150
+ async function buildServer() {
151
+ console.log('Building server...');
152
+
153
+ try {
154
+ await esbuild.build({
155
+ entryPoints: [serverEntry],
156
+ bundle: true,
157
+ platform: 'node',
158
+ target: ['node16'],
159
+ outfile: path.join(outdir, 'server.js'),
160
+ format: 'esm',
161
+ jsx: 'automatic',
162
+ jsxFactory: 'jsx',
163
+ jsxFragment: 'Fragment',
164
+ plugins: [{
165
+ name: 'external-modules',
166
+ setup(build) {
167
+ // Mark node_modules as external to reduce bundle size
168
+ build.onResolve({ filter: /^[^./]/ }, args => {
169
+ return { external: true };
170
+ });
171
+ }
172
+ }],
173
+ define: {
174
+ 'process.env.NODE_ENV': '"production"'
175
+ },
176
+ sourcemap: false
177
+ });
178
+
179
+ console.log('✓ Server build complete');
180
+ } catch (error) {
181
+ console.error('Server build failed:', error);
182
+ process.exit(1);
183
+ }
184
+ }
185
+
186
+ // Run the build process
187
+ async function build() {
188
+ console.log('🔨 Starting production build...');
189
+
190
+ // Create build directory for client assets
191
+ const buildDir = path.join(outdir, 'build');
192
+ if (!existsSync(buildDir)) {
193
+ await fs.mkdir(buildDir, { recursive: true });
194
+ }
195
+
196
+ // Run build steps in parallel
197
+ await Promise.all([
198
+ copyPublicFiles(),
199
+ buildClient(),
200
+ buildServer()
201
+ ]);
202
+
203
+ console.log('✅ Production build complete!');
204
+ console.log(`Run 'npm run serve' to start the production server.`);
205
+ }
206
+
207
+ // Execute the build
208
+ build().catch(error => {
209
+ console.error('Build failed:', error);
210
+ process.exit(1);
211
+ });
@@ -3,11 +3,34 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Frontend Hamroun App</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
6
+ <title>Frontend Hamroun SSR App</title>
7
+ <!-- Include the processed Tailwind CSS file -->
8
+ <link rel="stylesheet" href="/styles.css">
9
+ <!-- Fallback styles if Tailwind isn't loaded correctly -->
10
+ <style>
11
+ body {
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
13
+ line-height: 1.6;
14
+ color: #333;
15
+ }
16
+ a {
17
+ color: #0066cc;
18
+ text-decoration: none;
19
+ }
20
+ a:hover {
21
+ text-decoration: underline;
22
+ }
23
+ pre {
24
+ background: #f5f5f5;
25
+ padding: 10px;
26
+ border-radius: 4px;
27
+ overflow: auto;
28
+ }
29
+ </style>
8
30
  </head>
9
31
  <body>
10
32
  <div id="root"></div>
11
- <script type="module" src="/src/main.tsx"></script>
33
+ <script src="/build/main.js" type="module"></script>
34
+ <script src="/socket.io/socket.io.js"></script>
12
35
  </body>
13
36
  </html>