frontend-hamroun 1.2.80 → 1.2.83

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 (128) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.client.cjs +1 -1
  4. package/dist/index.client.js +2 -2
  5. package/dist/index.js +11 -7
  6. package/dist/index.js.map +1 -1
  7. package/dist/{renderer-Din1y3YM.cjs → renderer-BL3gq8cW.cjs} +2 -2
  8. package/dist/{renderer-Din1y3YM.cjs.map → renderer-BL3gq8cW.cjs.map} +1 -1
  9. package/dist/{renderer-Bo9zkUZ_.js → renderer-Dyy-o05F.js} +2 -2
  10. package/dist/{renderer-Bo9zkUZ_.js.map → renderer-Dyy-o05F.js.map} +1 -1
  11. package/dist/{server-renderer-QHt45Ip2.js → server-renderer-C1WXH-zV.js} +99 -73
  12. package/dist/server-renderer-C1WXH-zV.js.map +1 -0
  13. package/dist/server-renderer-Chs-nmJm.cjs +2 -0
  14. package/dist/server-renderer-Chs-nmJm.cjs.map +1 -0
  15. package/dist/server-renderer.cjs +1 -1
  16. package/dist/server-renderer.js +1 -1
  17. package/package.json +1 -1
  18. package/templates/basic-app/src/App.jsx +16 -0
  19. package/templates/basic-app/src/client.jsx +5 -0
  20. package/templates/basic-app/src/components/Counter.jsx +13 -0
  21. package/templates/basic-app/src/jsx-shim.js +3 -0
  22. package/templates/basic-app/src/jsx-shim.ts +7 -0
  23. package/templates/basic-app/src/main.jsx +98 -0
  24. package/templates/basic-app/src/server.js +47 -0
  25. package/templates/complete-app/api/hello.js +0 -0
  26. package/templates/complete-app/lib/frontend-hamroun.js +182 -0
  27. package/templates/complete-app/package.json +18 -0
  28. package/templates/complete-app/pages/about.js +119 -0
  29. package/templates/complete-app/pages/about.jsx +0 -0
  30. package/templates/complete-app/pages/index.js +157 -0
  31. package/templates/complete-app/pages/index.jsx +0 -0
  32. package/templates/complete-app/pages/wasm-demo.js +290 -0
  33. package/templates/complete-app/pages/wasm-demo.jsx +0 -0
  34. package/templates/complete-app/public/client.js +89 -0
  35. package/templates/complete-app/public/index.html +118 -0
  36. package/templates/complete-app/public/styles.css +76 -0
  37. package/templates/complete-app/server.js +226 -0
  38. package/templates/complete-app/src/App.tsx +59 -0
  39. package/templates/complete-app/src/client.tsx +18 -0
  40. package/templates/complete-app/src/server.ts +218 -0
  41. package/templates/complete-app/tsconfig.json +22 -0
  42. package/templates/complete-app/tsconfig.server.json +19 -0
  43. package/templates/{ssr-template → complete-app}/vite.config.js +16 -5
  44. package/templates/complete-app/vite.config.ts +30 -0
  45. package/templates/complete-app/wasm/build.bat +0 -0
  46. package/templates/complete-app/wasm/build.sh +0 -0
  47. package/templates/complete-app/wasm/example.go +0 -0
  48. package/templates/fullstack-app/build/main.css +874 -874
  49. package/templates/fullstack-app/build/main.css.map +7 -7
  50. package/templates/fullstack-app/build/main.js +996 -967
  51. package/templates/fullstack-app/build/main.js.map +7 -7
  52. package/templates/fullstack-app/package-lock.json +6301 -0
  53. package/templates/fullstack-app/public/styles.css +768 -768
  54. package/templates/go/example.go +154 -99
  55. package/templates/ssr-template/dist/client/assets/main-D-VH3xOb.js +1 -0
  56. package/templates/ssr-template/dist/client/index.html +23 -0
  57. package/templates/ssr-template/dist/client.js +951 -0
  58. package/templates/ssr-template/dist/server.js +739 -0
  59. package/templates/ssr-template/esbuild.config.js +33 -0
  60. package/templates/ssr-template/jsx-shim.js +1 -0
  61. package/templates/ssr-template/package.json +14 -8
  62. package/templates/ssr-template/src/App.tsx +847 -49
  63. package/templates/ssr-template/src/client.tsx +3 -17
  64. package/templates/ssr-template/src/server.ts +21 -204
  65. package/templates/ssr-template/tsconfig.json +9 -8
  66. package/templates/ssr-template/tsconfig.server.json +6 -14
  67. package/templates/ssr-template/vite.config.ts +19 -17
  68. package/templates/wasm/build-wasm.js +228 -0
  69. package/templates/wasm/dist/assets/index-BNqTDBdE.js +295 -0
  70. package/templates/wasm/dist/example.wasm +0 -0
  71. package/templates/wasm/dist/index.html +53 -0
  72. package/templates/{go-wasm-app/public/wasm → wasm/dist}/wasm_exec.js +572 -561
  73. package/templates/wasm/esbuild.config.js +63 -0
  74. package/templates/wasm/go/main.go +256 -0
  75. package/templates/wasm/go/wasm_exec.js +0 -0
  76. package/templates/wasm/index.html +97 -0
  77. package/templates/wasm/jsx-shim.js +9 -0
  78. package/templates/wasm/package-lock.json +4577 -0
  79. package/templates/wasm/package.json +25 -0
  80. package/templates/wasm/public/example.wasm +0 -0
  81. package/templates/wasm/public/wasm_exec.js +572 -0
  82. package/templates/wasm/src/App.tsx +550 -0
  83. package/templates/wasm/src/client.tsx +220 -0
  84. package/templates/wasm/src/index.tsx +21 -0
  85. package/templates/wasm/src/server.ts +145 -0
  86. package/templates/wasm/tsconfig.json +21 -0
  87. package/templates/wasm/tsconfig.node.json +13 -0
  88. package/templates/wasm/tsconfig.server.json +23 -0
  89. package/templates/wasm/vite.config.ts +38 -0
  90. package/templates/wasm/wasm-loader.js +103 -0
  91. package/dist/server-renderer-CqIpQ-od.cjs +0 -2
  92. package/dist/server-renderer-CqIpQ-od.cjs.map +0 -1
  93. package/dist/server-renderer-QHt45Ip2.js.map +0 -1
  94. package/templates/basic-app/bun.lock +0 -196
  95. package/templates/basic-app/docs/rapport_pfe.aux +0 -27
  96. package/templates/basic-app/docs/rapport_pfe.out +0 -10
  97. package/templates/basic-app/docs/rapport_pfe.pdf +0 -0
  98. package/templates/basic-app/docs/rapport_pfe.tex +0 -68
  99. package/templates/basic-app/docs/rapport_pfe.toc +0 -14
  100. package/templates/basic-app/package-lock.json +0 -4185
  101. package/templates/go-wasm-app/README.md +0 -38
  102. package/templates/go-wasm-app/babel.config.js +0 -15
  103. package/templates/go-wasm-app/build-client.js +0 -49
  104. package/templates/go-wasm-app/build-wasm.js +0 -237
  105. package/templates/go-wasm-app/package.json +0 -23
  106. package/templates/go-wasm-app/public/index.html +0 -128
  107. package/templates/go-wasm-app/public/styles.css +0 -197
  108. package/templates/go-wasm-app/public/wasm/example.wasm +0 -0
  109. package/templates/go-wasm-app/public/wasm/wasm_exec_node.js +0 -39
  110. package/templates/go-wasm-app/server.js +0 -521
  111. package/templates/go-wasm-app/src/App.jsx +0 -38
  112. package/templates/go-wasm-app/src/app.js +0 -153
  113. package/templates/go-wasm-app/src/client.js +0 -57
  114. package/templates/go-wasm-app/src/components/Footer.jsx +0 -13
  115. package/templates/go-wasm-app/src/components/Header.jsx +0 -19
  116. package/templates/go-wasm-app/src/components/WasmDemo.jsx +0 -120
  117. package/templates/go-wasm-app/src/main.jsx +0 -12
  118. package/templates/go-wasm-app/src/wasm/example.go +0 -75
  119. package/templates/go-wasm-app/tsconfig.server.json +0 -18
  120. package/templates/go-wasm-app/vite.config.js +0 -34
  121. package/templates/ssr-template/package-lock.json +0 -2478
  122. package/templates/ssr-template/public/index.html +0 -47
  123. package/templates/ssr-template/server.js +0 -369
  124. /package/templates/{ssr-template → complete-app}/client.js +0 -0
  125. /package/templates/{ssr-template → complete-app}/readme.md +0 -0
  126. /package/templates/{ssr-template → complete-app}/server.ts +0 -0
  127. /package/templates/{ssr-template → complete-app}/src/client.ts +0 -0
  128. /package/templates/{ssr-template → complete-app}/src/pages/index.tsx +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-renderer-C1WXH-zV.js","sources":["../src/batch.ts","../src/hooks.ts","../src/server-renderer.ts"],"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.js';\r\nimport { batchUpdates, isBatching } from './batch.js';\r\nimport { diff } from './vdom.js';\r\nimport { createContext, useContext } from './context.js';\r\n\r\n// Current render ID counter\r\nlet currentRender = 0;\r\nlet isServerRender = false;\r\n\r\n// State storage\r\nconst states = new Map<number, any[]>();\r\nconst stateIndices = new Map<number, number>();\r\nconst effects = new Map<number, any[]>();\r\nconst memos = new Map<number, any[]>();\r\nconst refs = new Map<number, any[]>();\r\n\r\n// Server-side rendering detection\r\nconst isServer = typeof window === 'undefined';\r\nconst serverStates = new Map<number, Map<number, any>>();\r\n\r\n// Rendering callbacks\r\nlet globalRenderCallback: any = null;\r\nlet globalContainer: any = null;\r\nlet currentElement: any = null;\r\n\r\nexport function setRenderCallback(callback: any, element: any, container: any): void {\r\n globalRenderCallback = callback;\r\n globalContainer = container;\r\n currentElement = element;\r\n}\r\n\r\nexport function prepareRender(isSSR: boolean = false): number {\r\n currentRender++;\r\n isServerRender = isSSR;\r\n stateIndices.set(currentRender, 0);\r\n return currentRender;\r\n}\r\n\r\nexport function finishRender(): void {\r\n if (isServer || isServerRender) {\r\n serverStates.delete(currentRender);\r\n }\r\n isServerRender = false;\r\n currentRender = 0;\r\n}\r\n\r\nexport function useState<T>(initial: T): [T, (newValue: T | ((prev: T) => T)) => void] {\r\n if (!currentRender && !isServerRender) {\r\n throw new Error(\"useState must be called within a render\");\r\n }\r\n\r\n // Handle server-side rendering separately\r\n if (isServer || isServerRender) {\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 // In SSR, setState is a no-op\r\n const setState = (_newValue: T | ((prev: T) => T)) => {};\r\n \r\n stateIndices.set(currentRender, index + 1);\r\n return [state, setState];\r\n }\r\n\r\n // Client-side implementation\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) || 0;\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 \r\n const setState = (newValue: T | ((prev: T) => T)) => {\r\n const nextValue = typeof newValue === 'function'\r\n ? (newValue as ((prev: T) => T))(componentStates[index])\r\n : newValue;\r\n \r\n if (componentStates[index] === nextValue) return;\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[]): void {\r\n if (!currentRender && !isServerRender) throw new Error(\"useEffect must be called within a render\");\r\n\r\n // Skip effects on server\r\n if (isServer || isServerRender) {\r\n const effectIndex = stateIndices.get(currentRender) || 0;\r\n stateIndices.set(currentRender, effectIndex + 1);\r\n return;\r\n }\r\n\r\n const effectIndex = stateIndices.get(currentRender) || 0;\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 if (!prevEffect || !deps || !prevEffect.deps || deps.some((dep, i) => dep !== prevEffect.deps[i])) {\r\n if (prevEffect?.cleanup) {\r\n prevEffect.cleanup();\r\n }\r\n \r\n // Schedule effect execution after render is complete\r\n queueMicrotask(() => {\r\n const cleanup = callback() || undefined;\r\n componentEffects[effectIndex] = { cleanup, deps: 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 && !isServerRender) throw new Error(\"useMemo must be called within a render\");\r\n \r\n const memoIndex = stateIndices.get(currentRender) || 0;\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: 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): { current: T } {\r\n if (!currentRender && !isServerRender) throw new Error(\"useRef must be called within a render\");\r\n \r\n const refIndex = stateIndices.get(currentRender) || 0;\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 \r\n if (refIndex >= componentRefs.length) {\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\nasync function rerender(rendererId: number): Promise<void> {\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 // Trigger re-render\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\nexport function useErrorBoundary() {\r\n const [error, setError] = useState(null);\r\n return [error, () => setError(null)];\r\n}\r\n\r\n// Re-export from context to match index.js\r\nexport { createContext, useContext };\r\n","import { VNode } from './types.js';\r\nimport { prepareRender, finishRender } from './hooks.js';\r\n\r\nexport async function renderToString(element: any): Promise<string> {\r\n const renderId = prepareRender(true); // Mark as SSR\r\n \r\n try {\r\n const html = await renderNodeToString(element);\r\n return html;\r\n } finally {\r\n finishRender();\r\n }\r\n}\r\n\r\nasync function renderNodeToString(node: any): Promise<string> {\r\n // Handle null, undefined, boolean\r\n if (node == null || typeof node === 'boolean') {\r\n return '';\r\n }\r\n\r\n // Handle primitives\r\n if (typeof node === 'string' || typeof node === 'number') {\r\n return escapeHtml(String(node));\r\n }\r\n\r\n // Handle arrays\r\n if (Array.isArray(node)) {\r\n const results = await Promise.all(node.map(child => renderNodeToString(child)));\r\n return results.join('');\r\n }\r\n\r\n // Handle objects with type and props (React-like elements)\r\n if (node && typeof node === 'object' && 'type' in node) {\r\n const { type, props = {} } = node;\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 return await renderNodeToString(result);\r\n } catch (error:any) {\r\n console.error('Error rendering component:', error);\r\n return `<!-- Error rendering component: ${error.message} -->`;\r\n }\r\n }\r\n\r\n // Handle DOM elements\r\n if (typeof type === 'string') {\r\n return await renderDOMElement(type, props);\r\n }\r\n }\r\n\r\n // Fallback for other objects\r\n if (typeof node === 'object') {\r\n return escapeHtml(JSON.stringify(node));\r\n }\r\n\r\n return escapeHtml(String(node));\r\n}\r\n\r\nasync function renderDOMElement(tagName: string, props: any): Promise<string> {\r\n const { children, ...attrs } = props;\r\n \r\n // Self-closing tags\r\n const voidElements = new Set([\r\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\r\n 'link', 'meta', 'param', 'source', 'track', 'wbr'\r\n ]);\r\n\r\n // Build attributes string\r\n const attributeString = Object.entries(attrs)\r\n .filter(([key, value]) => {\r\n // Filter out React-specific props and event handlers\r\n if (key.startsWith('on') || key === 'key' || key === 'ref') return false;\r\n if (value == null || value === false) return false;\r\n return true;\r\n })\r\n .map(([key, value]) => {\r\n // Handle className -> class\r\n if (key === 'className') key = 'class';\r\n \r\n // Handle boolean attributes\r\n if (value === true) return key;\r\n \r\n // Handle style objects\r\n if (key === 'style' && typeof value === 'object' && value !== null) {\r\n const styleString = Object.entries(value)\r\n .map(([prop, val]) => `${kebabCase(prop)}:${val}`)\r\n .join(';');\r\n return `style=\"${escapeHtml(styleString)}\"`;\r\n }\r\n \r\n return `${key}=\"${escapeHtml(String(value))}\"`;\r\n })\r\n .join(' ');\r\n\r\n const openTag = `<${tagName}${attributeString ? ' ' + attributeString : ''}>`;\r\n \r\n // Self-closing elements\r\n if (voidElements.has(tagName)) {\r\n return openTag.slice(0, -1) + '/>';\r\n }\r\n\r\n // Elements with children\r\n const closeTag = `</${tagName}>`;\r\n \r\n if (children != null) {\r\n const childrenString = await renderNodeToString(children);\r\n return openTag + childrenString + closeTag;\r\n }\r\n \r\n return openTag + closeTag;\r\n}\r\n\r\nfunction escapeHtml(text: string): string {\r\n const htmlEscapes: Record<string, string> = {\r\n '&': '&amp;',\r\n '<': '&lt;',\r\n '>': '&gt;',\r\n '\"': '&quot;',\r\n \"'\": '&#x27;',\r\n '/': '&#x2F;'\r\n };\r\n \r\n return text.replace(/[&<>\"'/]/g, (match) => htmlEscapes[match]);\r\n}\r\n\r\nfunction kebabCase(str: string): string {\r\n return str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);\r\n}\r\n"],"names":["index","state","setState","effectIndex","ref"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";exports.isBatching=!1;const e=[];function t(t){if(exports.isBatching)e.push(t);else{exports.isBatching=!0;try{for(t();e.length>0;){const t=e.shift();t?.()}}finally{exports.isBatching=!1}}}let n=0,r=!1;const s=new Map,o=new Map,i=new Map,c=new Map,a=new Map,u="undefined"==typeof window,p=new Map;let l=null,f=null,g=null;function h(e=!1){return n++,r=e,o.set(n,0),n}function d(){(u||r)&&p.delete(n),r=!1,n=0}function w(e){if(!n&&!r)throw new Error("useState must be called within a render");if(u||r){p.has(n)||p.set(n,new Map);const t=p.get(n),r=o.get(n)||0;t.has(r)||t.set(r,e);const s=t.get(r),i=e=>{};return o.set(n,r+1),[s,i]}s.has(n)||s.set(n,[]);const i=s.get(n),c=o.get(n)||0;c>=i.length&&i.push(e);const a=i[c];return o.set(n,c+1),[a,e=>{const r="function"==typeof e?e(i[c]):e;i[c]!==r&&(i[c]=r,exports.isBatching?t((()=>y(n))):y(n))}]}async function y(e){try{const t=i.get(e);t&&(t.forEach((e=>{e.cleanup&&e.cleanup()})),i.set(e,[])),l&&f&&g&&await l(g,f)}catch(t){console.error("Error during rerender:",t)}}async function m(e){if(null==e||"boolean"==typeof e)return"";if("string"==typeof e||"number"==typeof e)return x(String(e));if(Array.isArray(e)){return(await Promise.all(e.map((e=>m(e))))).join("")}if(e&&"object"==typeof e&&"type"in e){const{type:n,props:r={}}=e;if("function"==typeof n)try{const e=await n(r);return await m(e)}catch(t){return console.error("Error rendering component:",t),`\x3c!-- Error rendering component: ${t.message} --\x3e`}if("string"==typeof n)return await async function(e,t){const{children:n,...r}=t,s=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),o=Object.entries(r).filter((([e,t])=>!e.startsWith("on")&&"key"!==e&&"ref"!==e&&(null!=t&&!1!==t))).map((([e,t])=>{if("className"===e&&(e="class"),!0===t)return e;if("style"===e&&"object"==typeof t&&null!==t){return`style="${x(Object.entries(t).map((([e,t])=>{return`${n=e,n.replace(/[A-Z]/g,(e=>`-${e.toLowerCase()}`))}:${t}`;var n})).join(";"))}"`}return`${e}="${x(String(t))}"`})).join(" "),i=`<${e}${o?" "+o:""}>`;if(s.has(e))return i.slice(0,-1)+"/>";const c=`</${e}>`;if(null!=n){return i+await m(n)+c}return i+c}(n,r)}return x("object"==typeof e?JSON.stringify(e):String(e))}function x(e){const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"};return e.replace(/[&<>"'/]/g,(e=>t[e]))}exports.batchUpdates=t,exports.finishRender=d,exports.getIsBatching=function(){return exports.isBatching},exports.prepareRender=h,exports.renderToString=async function(e){h(!0);try{return await m(e)}finally{d()}},exports.setRenderCallback=function(e,t,n){l=e,f=n,g=t},exports.useEffect=function(e,t){if(!n&&!r)throw new Error("useEffect must be called within a render");if(u||r){const e=o.get(n)||0;return void o.set(n,e+1)}const s=o.get(n)||0;i.has(n)||i.set(n,[]);const c=i.get(n),a=c[s];a&&t&&a.deps&&!t.some(((e,t)=>e!==a.deps[t]))||(a?.cleanup&&a.cleanup(),queueMicrotask((()=>{const n=e()||void 0;c[s]={cleanup:n,deps:t||[]}}))),o.set(n,s+1)},exports.useErrorBoundary=function(){const[e,t]=w(null);return[e,()=>t(null)]},exports.useMemo=function(e,t){if(!n&&!r)throw new Error("useMemo must be called within a render");const s=o.get(n)||0;c.has(n)||c.set(n,[]);const i=c.get(n),a=i[s];if(!a||t&&t.some(((e,t)=>!Object.is(e,a.deps[t])))){const r=e();return i[s]={value:r,deps:t||[]},o.set(n,s+1),r}return o.set(n,s+1),a.value},exports.useRef=function(e){if(!n&&!r)throw new Error("useRef must be called within a render");const t=o.get(n)||0;a.has(n)||a.set(n,[]);const s=a.get(n);if(t>=s.length){const r={current:e};return s.push(r),o.set(n,t+1),r}const i=s[t];return o.set(n,t+1),i},exports.useState=w;
2
+ //# sourceMappingURL=server-renderer-Chs-nmJm.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-renderer-Chs-nmJm.cjs","sources":["../src/batch.ts","../src/hooks.ts","../src/server-renderer.ts"],"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.js';\r\nimport { batchUpdates, isBatching } from './batch.js';\r\nimport { diff } from './vdom.js';\r\nimport { createContext, useContext } from './context.js';\r\n\r\n// Current render ID counter\r\nlet currentRender = 0;\r\nlet isServerRender = false;\r\n\r\n// State storage\r\nconst states = new Map<number, any[]>();\r\nconst stateIndices = new Map<number, number>();\r\nconst effects = new Map<number, any[]>();\r\nconst memos = new Map<number, any[]>();\r\nconst refs = new Map<number, any[]>();\r\n\r\n// Server-side rendering detection\r\nconst isServer = typeof window === 'undefined';\r\nconst serverStates = new Map<number, Map<number, any>>();\r\n\r\n// Rendering callbacks\r\nlet globalRenderCallback: any = null;\r\nlet globalContainer: any = null;\r\nlet currentElement: any = null;\r\n\r\nexport function setRenderCallback(callback: any, element: any, container: any): void {\r\n globalRenderCallback = callback;\r\n globalContainer = container;\r\n currentElement = element;\r\n}\r\n\r\nexport function prepareRender(isSSR: boolean = false): number {\r\n currentRender++;\r\n isServerRender = isSSR;\r\n stateIndices.set(currentRender, 0);\r\n return currentRender;\r\n}\r\n\r\nexport function finishRender(): void {\r\n if (isServer || isServerRender) {\r\n serverStates.delete(currentRender);\r\n }\r\n isServerRender = false;\r\n currentRender = 0;\r\n}\r\n\r\nexport function useState<T>(initial: T): [T, (newValue: T | ((prev: T) => T)) => void] {\r\n if (!currentRender && !isServerRender) {\r\n throw new Error(\"useState must be called within a render\");\r\n }\r\n\r\n // Handle server-side rendering separately\r\n if (isServer || isServerRender) {\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 // In SSR, setState is a no-op\r\n const setState = (_newValue: T | ((prev: T) => T)) => {};\r\n \r\n stateIndices.set(currentRender, index + 1);\r\n return [state, setState];\r\n }\r\n\r\n // Client-side implementation\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) || 0;\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 \r\n const setState = (newValue: T | ((prev: T) => T)) => {\r\n const nextValue = typeof newValue === 'function'\r\n ? (newValue as ((prev: T) => T))(componentStates[index])\r\n : newValue;\r\n \r\n if (componentStates[index] === nextValue) return;\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[]): void {\r\n if (!currentRender && !isServerRender) throw new Error(\"useEffect must be called within a render\");\r\n\r\n // Skip effects on server\r\n if (isServer || isServerRender) {\r\n const effectIndex = stateIndices.get(currentRender) || 0;\r\n stateIndices.set(currentRender, effectIndex + 1);\r\n return;\r\n }\r\n\r\n const effectIndex = stateIndices.get(currentRender) || 0;\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 if (!prevEffect || !deps || !prevEffect.deps || deps.some((dep, i) => dep !== prevEffect.deps[i])) {\r\n if (prevEffect?.cleanup) {\r\n prevEffect.cleanup();\r\n }\r\n \r\n // Schedule effect execution after render is complete\r\n queueMicrotask(() => {\r\n const cleanup = callback() || undefined;\r\n componentEffects[effectIndex] = { cleanup, deps: 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 && !isServerRender) throw new Error(\"useMemo must be called within a render\");\r\n \r\n const memoIndex = stateIndices.get(currentRender) || 0;\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: 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): { current: T } {\r\n if (!currentRender && !isServerRender) throw new Error(\"useRef must be called within a render\");\r\n \r\n const refIndex = stateIndices.get(currentRender) || 0;\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 \r\n if (refIndex >= componentRefs.length) {\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\nasync function rerender(rendererId: number): Promise<void> {\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 // Trigger re-render\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\nexport function useErrorBoundary() {\r\n const [error, setError] = useState(null);\r\n return [error, () => setError(null)];\r\n}\r\n\r\n// Re-export from context to match index.js\r\nexport { createContext, useContext };\r\n","import { VNode } from './types.js';\r\nimport { prepareRender, finishRender } from './hooks.js';\r\n\r\nexport async function renderToString(element: any): Promise<string> {\r\n const renderId = prepareRender(true); // Mark as SSR\r\n \r\n try {\r\n const html = await renderNodeToString(element);\r\n return html;\r\n } finally {\r\n finishRender();\r\n }\r\n}\r\n\r\nasync function renderNodeToString(node: any): Promise<string> {\r\n // Handle null, undefined, boolean\r\n if (node == null || typeof node === 'boolean') {\r\n return '';\r\n }\r\n\r\n // Handle primitives\r\n if (typeof node === 'string' || typeof node === 'number') {\r\n return escapeHtml(String(node));\r\n }\r\n\r\n // Handle arrays\r\n if (Array.isArray(node)) {\r\n const results = await Promise.all(node.map(child => renderNodeToString(child)));\r\n return results.join('');\r\n }\r\n\r\n // Handle objects with type and props (React-like elements)\r\n if (node && typeof node === 'object' && 'type' in node) {\r\n const { type, props = {} } = node;\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 return await renderNodeToString(result);\r\n } catch (error:any) {\r\n console.error('Error rendering component:', error);\r\n return `<!-- Error rendering component: ${error.message} -->`;\r\n }\r\n }\r\n\r\n // Handle DOM elements\r\n if (typeof type === 'string') {\r\n return await renderDOMElement(type, props);\r\n }\r\n }\r\n\r\n // Fallback for other objects\r\n if (typeof node === 'object') {\r\n return escapeHtml(JSON.stringify(node));\r\n }\r\n\r\n return escapeHtml(String(node));\r\n}\r\n\r\nasync function renderDOMElement(tagName: string, props: any): Promise<string> {\r\n const { children, ...attrs } = props;\r\n \r\n // Self-closing tags\r\n const voidElements = new Set([\r\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\r\n 'link', 'meta', 'param', 'source', 'track', 'wbr'\r\n ]);\r\n\r\n // Build attributes string\r\n const attributeString = Object.entries(attrs)\r\n .filter(([key, value]) => {\r\n // Filter out React-specific props and event handlers\r\n if (key.startsWith('on') || key === 'key' || key === 'ref') return false;\r\n if (value == null || value === false) return false;\r\n return true;\r\n })\r\n .map(([key, value]) => {\r\n // Handle className -> class\r\n if (key === 'className') key = 'class';\r\n \r\n // Handle boolean attributes\r\n if (value === true) return key;\r\n \r\n // Handle style objects\r\n if (key === 'style' && typeof value === 'object' && value !== null) {\r\n const styleString = Object.entries(value)\r\n .map(([prop, val]) => `${kebabCase(prop)}:${val}`)\r\n .join(';');\r\n return `style=\"${escapeHtml(styleString)}\"`;\r\n }\r\n \r\n return `${key}=\"${escapeHtml(String(value))}\"`;\r\n })\r\n .join(' ');\r\n\r\n const openTag = `<${tagName}${attributeString ? ' ' + attributeString : ''}>`;\r\n \r\n // Self-closing elements\r\n if (voidElements.has(tagName)) {\r\n return openTag.slice(0, -1) + '/>';\r\n }\r\n\r\n // Elements with children\r\n const closeTag = `</${tagName}>`;\r\n \r\n if (children != null) {\r\n const childrenString = await renderNodeToString(children);\r\n return openTag + childrenString + closeTag;\r\n }\r\n \r\n return openTag + closeTag;\r\n}\r\n\r\nfunction escapeHtml(text: string): string {\r\n const htmlEscapes: Record<string, string> = {\r\n '&': '&amp;',\r\n '<': '&lt;',\r\n '>': '&gt;',\r\n '\"': '&quot;',\r\n \"'\": '&#x27;',\r\n '/': '&#x2F;'\r\n };\r\n \r\n return text.replace(/[&<>\"'/]/g, (match) => htmlEscapes[match]);\r\n}\r\n\r\nfunction kebabCase(str: string): string {\r\n return str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);\r\n}\r\n"],"names":["isBatching","queue","batchUpdates","fn","push","length","nextFn","shift","currentRender","isServerRender","states","Map","stateIndices","effects","memos","refs","isServer","window","serverStates","globalRenderCallback","globalContainer","currentElement","prepareRender","isSSR","set","finishRender","delete","useState","initial","Error","has","componentState","get","index","state","setState","_newValue","componentStates","newValue","nextValue","rerender","async","rendererId","componentEffects","forEach","effect","cleanup","error","console","renderNodeToString","node","escapeHtml","String","Array","isArray","Promise","all","map","child","join","type","props","result","message","tagName","children","attrs","voidElements","Set","attributeString","Object","entries","filter","key","value","startsWith","prop","val","str","replace","match","toLowerCase","openTag","slice","closeTag","renderDOMElement","JSON","stringify","text","htmlEscapes","element","callback","container","deps","effectIndex","prevEffect","some","dep","i","queueMicrotask","setError","factory","memoIndex","componentMemos","prevMemo","is","refIndex","componentRefs","ref","current"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./server-renderer-CqIpQ-od.cjs");exports.renderToString=e.renderToString;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./server-renderer-Chs-nmJm.cjs");exports.renderToString=e.renderToString;
2
2
  //# sourceMappingURL=server-renderer.cjs.map
@@ -1,4 +1,4 @@
1
- import { r } from "./server-renderer-QHt45Ip2.js";
1
+ import { r } from "./server-renderer-C1WXH-zV.js";
2
2
  export {
3
3
  r as renderToString
4
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontend-hamroun",
3
- "version": "1.2.80",
3
+ "version": "1.2.83",
4
4
  "description": "A lightweight full-stack JavaScript framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -0,0 +1,16 @@
1
+ import { useState } from 'frontend-hamroun';
2
+ export function App() {
3
+ // Initialize with a default state that works on both server and client
4
+ const [count, setCount] = useState(0);
5
+ return (<div>
6
+ <h1>Server-Side Rendered App</h1>
7
+ <div>
8
+ <button onClick={() => setCount(count - 1)} data-action="decrement">-</button>
9
+ <span style={{ margin: '0 10px' }}>{count}</span>
10
+ <button onClick={() => setCount(count + 1)} data-action="increment">+</button>
11
+ </div>
12
+ <script dangerouslySetInnerHTML={{
13
+ __html: `window.__INITIAL_STATE__ = ${JSON.stringify({ count: 0 })};`
14
+ }}/>
15
+ </div>);
16
+ }
@@ -0,0 +1,5 @@
1
+ import { hydrate } from 'frontend-hamroun';
2
+ import { App } from './App';
3
+ document.addEventListener('DOMContentLoaded', () => {
4
+ hydrate(<App />, document.getElementById('root'));
5
+ });
@@ -0,0 +1,13 @@
1
+ import { useState, useEffect } from 'frontend-hamroun';
2
+ export function Counter({ initial = 0 }) {
3
+ const [count, setCount] = useState(initial);
4
+ useEffect(() => {
5
+ document.title = `Count: ${count}`;
6
+ }, [count]);
7
+ return (<div>
8
+ <h2>Counter Component</h2>
9
+ <button onClick={() => setCount(count - 1)}>-</button>
10
+ <span style={{ margin: '0 10px' }}>{count}</span>
11
+ <button onClick={() => setCount(count + 1)}>+</button>
12
+ </div>);
13
+ }
@@ -0,0 +1,3 @@
1
+ import { jsx as createElement, Fragment } from 'frontend-hamroun';
2
+ window.createElement = createElement;
3
+ window.Fragment = Fragment;
@@ -1,4 +1,11 @@
1
1
  import { jsx as createElement, Fragment } from 'frontend-hamroun';
2
2
 
3
+ declare global {
4
+ interface Window {
5
+ createElement: typeof createElement;
6
+ Fragment: typeof Fragment;
7
+ }
8
+ }
9
+
3
10
  window.createElement = createElement;
4
11
  window.Fragment = Fragment;
@@ -0,0 +1,98 @@
1
+ import { render, useState, useEffect, useMemo, useRef, useErrorBoundary, createContext, useContext } from 'frontend-hamroun';
2
+ import './main.css';
3
+ // Create a theme context
4
+ const ThemeContext = createContext('light');
5
+ // Create a component that will throw an error when clicked
6
+ function BuggyComponent() {
7
+ const [shouldError, setShouldError] = useState(false);
8
+ if (shouldError) {
9
+ throw new Error("Test error from BuggyComponent");
10
+ }
11
+ return (<button onClick={() => setShouldError(true)} className="bg-red-600 text-white px-4 py-2 rounded mt-4 hover:bg-red-700">
12
+ Trigger Error
13
+ </button>);
14
+ }
15
+ function App() {
16
+ const [count, setCount] = useState(0);
17
+ const [theme, setTheme] = useState('light');
18
+ const renderCount = useRef(0);
19
+ const [error, resetError] = useErrorBoundary();
20
+ // Demonstrate useEffect
21
+ useEffect(() => {
22
+ document.title = `Count: ${count}`;
23
+ renderCount.current += 1;
24
+ return () => {
25
+ console.log('Cleanup effect');
26
+ };
27
+ }, [count]);
28
+ // Demonstrate useMemo
29
+ const doubled = useMemo(() => count * 2, [count]);
30
+ if (error) {
31
+ return (<div className="p-8 max-w-md mx-auto bg-red-50 rounded-lg shadow-lg">
32
+ <h1 className="text-2xl font-bold text-red-600 mb-4">Something went wrong!</h1>
33
+ <button onClick={resetError} className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">
34
+ Try again
35
+ </button>
36
+ </div>);
37
+ }
38
+ return (<ThemeContext.Provider value={theme}>
39
+ <div className={`p-8 max-w-4xl mx-auto rounded-lg shadow-lg transition-colors ${theme === 'dark'
40
+ ? 'bg-gray-800 text-white'
41
+ : 'bg-white text-gray-900'}`}>
42
+ <h1 className="text-3xl font-bold mb-6">Welcome to Frontend Hamroun</h1>
43
+
44
+ <div className="mb-6">
45
+ <button onClick={() => setCount(count - 1)} className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">-</button>
46
+ <span className="mx-4 text-xl">{count}</span>
47
+ <button onClick={() => setCount(count + 1)} className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">+</button>
48
+ </div>
49
+
50
+ <p className="mb-2 text-lg">Doubled value: <span className="font-semibold">{doubled}</span></p>
51
+ <p className="mb-4 text-lg">Render count: <span className="font-semibold">{renderCount.current}</span></p>
52
+
53
+ <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')} className={`px-4 py-2 rounded mb-8 ${theme === 'dark'
54
+ ? 'bg-yellow-400 text-gray-900 hover:bg-yellow-300'
55
+ : 'bg-gray-800 text-white hover:bg-gray-700'}`}>
56
+ Toggle Theme ({theme})
57
+ </button>
58
+
59
+ <div className="mt-8 border-t pt-6">
60
+ <h2 className="text-2xl font-bold mb-4">Test Error Boundary</h2>
61
+ <BuggyComponent />
62
+ </div>
63
+
64
+ <div className="mt-8 border-t pt-6">
65
+ <h2 className="text-2xl font-bold mb-4">Test Context</h2>
66
+ <ContextConsumer />
67
+ </div>
68
+ </div>
69
+ </ThemeContext.Provider>);
70
+ }
71
+ // Component to test context
72
+ function ContextConsumer() {
73
+ // Get the full context object for debugging
74
+ const themeContext = useContext(ThemeContext);
75
+ console.log('Theme context object:', themeContext);
76
+ // Access the current theme from the parent component instead
77
+ // This is a workaround until context is properly implemented
78
+ return (<div className={`mt-4 p-4 rounded-md border ${themeContext === 'dark' ? 'border-gray-600' : 'border-gray-300'}`}>
79
+ <p className="mb-2"><strong>Note:</strong> Context API needs a fix in the framework.</p>
80
+ <p className="mb-1">Context object type: {typeof themeContext}</p>
81
+ <p className="mb-4">Context object keys: {Object.keys(themeContext).join(', ') || 'none'}</p>
82
+
83
+ <button onClick={() => {
84
+ // Since we can't get the value directly from context,
85
+ // Let's add a button to demonstrate we can still use the Provider
86
+ const newTheme = document.body.style.backgroundColor === 'rgb(51, 51, 51)'
87
+ ? 'light' : 'dark';
88
+ console.log('Manually changing theme to:', newTheme);
89
+ // This won't work yet, but demonstrates the intent
90
+ if (ThemeContext) {
91
+ console.log('Provider exists, would set value to:', newTheme);
92
+ }
93
+ }} className="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded">
94
+ Check Context Implementation
95
+ </button>
96
+ </div>);
97
+ }
98
+ render(<App />, document.getElementById('root'));
@@ -0,0 +1,47 @@
1
+ import express from 'express';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { renderToString, jsx } from 'frontend-hamroun';
5
+ import { App } from './App.js';
6
+ import fs from 'fs';
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const app = express();
9
+ const port = 3000;
10
+ // Find the client entry file
11
+ function getClientEntry() {
12
+ const assetsDir = path.join(__dirname, './');
13
+ const files = fs.readdirSync(assetsDir);
14
+ const entry = files.find(file => file.startsWith("client") && file.endsWith('.js'));
15
+ if (!entry) {
16
+ throw new Error('Client entry file not found');
17
+ }
18
+ return entry;
19
+ }
20
+ // Static file serving
21
+ app.use('/assets', express.static(path.join(__dirname, './')));
22
+ app.use(express.static(path.join(__dirname, 'dist')));
23
+ // Main route handler
24
+ app.get('/', async (_req, res) => {
25
+ try {
26
+ const clientEntry = getClientEntry();
27
+ const html = await renderToString({
28
+ element: jsx(App, null),
29
+ title: 'SSR App',
30
+ scripts: [`/assets/${clientEntry}`],
31
+ initialState: {
32
+ // Add any initial state here
33
+ },
34
+ meta: {
35
+ 'viewport': 'width=device-width, initial-scale=1.0',
36
+ }
37
+ });
38
+ res.send(html);
39
+ }
40
+ catch (error) {
41
+ console.error('Rendering error:', error);
42
+ res.status(500).send('Server Error');
43
+ }
44
+ });
45
+ app.listen(port, () => {
46
+ console.log(`Server running at http://localhost:${port}`);
47
+ });
File without changes
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Simple JSX implementation for server-side rendering
3
+ */
4
+
5
+ // Render a virtual DOM node to HTML string
6
+ export function renderToString(vnode) {
7
+ // Handle null or undefined
8
+ if (vnode == null) return '';
9
+
10
+ // Handle primitive values
11
+ if (typeof vnode === 'string' || typeof vnode === 'number')
12
+ return escapeHtml(String(vnode));
13
+
14
+ // Handle arrays (like fragments)
15
+ if (Array.isArray(vnode))
16
+ return vnode.map(renderToString).join('');
17
+
18
+ // Handle functional components
19
+ if (typeof vnode.type === 'function') {
20
+ try {
21
+ const result = vnode.type(vnode.props || {});
22
+ return renderToString(result);
23
+ } catch (err) {
24
+ console.error('Error rendering component:', err);
25
+ return `<div class="error">Error: ${escapeHtml(err.message)}</div>`;
26
+ }
27
+ }
28
+
29
+ // Handle Fragment
30
+ if (vnode.type === Symbol.for('react.fragment')) {
31
+ return renderToString(vnode.props.children);
32
+ }
33
+
34
+ // Handle regular DOM elements
35
+ if (typeof vnode.type === 'string') {
36
+ let props = vnode.props || {};
37
+ let children = props.children || [];
38
+ if (!Array.isArray(children)) {
39
+ children = [children];
40
+ }
41
+
42
+ // Build opening tag with attributes
43
+ let html = `<${vnode.type}`;
44
+
45
+ for (const [key, value] of Object.entries(props)) {
46
+ if (key === 'children' || key === 'dangerouslySetInnerHTML') continue;
47
+
48
+ // Handle event handlers (they should be ignored in SSR)
49
+ if (key.startsWith('on')) continue;
50
+
51
+ // Handle className -> class
52
+ if (key === 'className') {
53
+ html += ` class="${escapeHtml(value)}"`;
54
+ continue;
55
+ }
56
+
57
+ // Handle style objects
58
+ if (key === 'style' && typeof value === 'object') {
59
+ const styleStr = Object.entries(value)
60
+ .map(([k, v]) => `${kebabCase(k)}:${v}`)
61
+ .join(';');
62
+ html += ` style="${escapeHtml(styleStr)}"`;
63
+ continue;
64
+ }
65
+
66
+ // Handle boolean attributes
67
+ if (value === true) {
68
+ html += ` ${key}`;
69
+ continue;
70
+ }
71
+
72
+ // Skip false boolean attributes
73
+ if (value === false) continue;
74
+
75
+ // Regular attributes
76
+ if (value != null) {
77
+ html += ` ${key}="${escapeHtml(value)}"`;
78
+ }
79
+ }
80
+
81
+ // Handle self-closing tags
82
+ const selfClosing = ['img', 'input', 'br', 'hr', 'meta', 'link', 'area', 'base', 'col', 'embed', 'param', 'source', 'track', 'wbr'];
83
+ if (selfClosing.includes(vnode.type)) {
84
+ return `${html} />`;
85
+ }
86
+
87
+ // Add closing bracket for opening tag
88
+ html += '>';
89
+
90
+ // Handle dangerouslySetInnerHTML
91
+ if (props.dangerouslySetInnerHTML) {
92
+ html += props.dangerouslySetInnerHTML.__html || '';
93
+ } else {
94
+ // Add children
95
+ for (const child of children) {
96
+ html += renderToString(child);
97
+ }
98
+ }
99
+
100
+ // Add closing tag
101
+ html += `</${vnode.type}>`;
102
+
103
+ return html;
104
+ }
105
+
106
+ // Unknown node type
107
+ console.warn('Unknown vnode type:', vnode.type);
108
+ return '';
109
+ }
110
+
111
+ // Create a JSX element
112
+ export function jsx(type, props, ...children) {
113
+ props = props || {};
114
+ if (children.length) {
115
+ props.children = children.length === 1 ? children[0] : children;
116
+ }
117
+ return { type, props };
118
+ }
119
+
120
+ // Helper for escaping HTML
121
+ function escapeHtml(str) {
122
+ return String(str)
123
+ .replace(/&/g, '&amp;')
124
+ .replace(/</g, '&lt;')
125
+ .replace(/>/g, '&gt;')
126
+ .replace(/"/g, '&quot;')
127
+ .replace(/'/g, '&#039;');
128
+ }
129
+
130
+ // Helper to convert camelCase to kebab-case for CSS properties
131
+ function kebabCase(str) {
132
+ return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
133
+ }
134
+
135
+ // Create a basic requestLogger middleware
136
+ export function requestLogger(req, res, next) {
137
+ console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
138
+ next();
139
+ }
140
+
141
+ // Create a basic error handler middleware
142
+ export function errorHandler(err, req, res, next) {
143
+ console.error('Server error:', err);
144
+ res.status(500).send('Server Error');
145
+ }
146
+
147
+ // Create a basic 404 handler middleware
148
+ export function notFoundHandler(req, res) {
149
+ res.status(404).send('Not Found');
150
+ }
151
+
152
+ // Create a basic rate limiter middleware
153
+ export function rateLimit(options = { windowMs: 60000, max: 100 }) {
154
+ const requests = new Map();
155
+
156
+ return (req, res, next) => {
157
+ const ip = req.ip || 'unknown';
158
+ const now = Date.now();
159
+
160
+ if (!requests.has(ip)) {
161
+ requests.set(ip, []);
162
+ }
163
+
164
+ const reqs = requests.get(ip);
165
+ const validReqs = reqs.filter(time => now - time < options.windowMs);
166
+
167
+ validReqs.push(now);
168
+ requests.set(ip, validReqs);
169
+
170
+ if (validReqs.length > options.max) {
171
+ return res.status(429).send('Too Many Requests');
172
+ }
173
+
174
+ next();
175
+ };
176
+ }
177
+
178
+ // Export other necessary functions
179
+ export const Fragment = Symbol.for('react.fragment');
180
+ export function loadGoWasmFromFile() {
181
+ throw new Error('WASM not supported in this environment');
182
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "ssr-app",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "node server.js",
7
+ "start": "node server.js",
8
+ "build": "echo 'No build step required for this template'"
9
+ },
10
+ "dependencies": {
11
+ "compression": "^1.8.0",
12
+ "cors": "^2.8.5",
13
+ "dotenv": "^16.0.3",
14
+ "express": "^4.18.2",
15
+ "frontend-hamroun": "^1.2.79",
16
+ "node-fetch": "^3.3.1"
17
+ }
18
+ }
@@ -0,0 +1,119 @@
1
+ import { jsx, useState, useEffect } from 'frontend-hamroun';
2
+
3
+ // Simple animation component to demonstrate useEffect
4
+ function FadeIn({ children, delay = 0 }) {
5
+ const [opacity, setOpacity] = useState(0);
6
+
7
+ useEffect(() => {
8
+ const timer = setTimeout(() => {
9
+ setOpacity(1);
10
+ }, delay);
11
+
12
+ return () => clearTimeout(timer);
13
+ }, [delay]);
14
+
15
+ return jsx('div', {
16
+ style: `opacity: ${opacity}; transition: opacity 0.5s ease-in-out;`
17
+ }, children);
18
+ }
19
+
20
+ // Main about page component
21
+ export default function AboutPage(props) {
22
+ const [activeSection, setActiveSection] = useState('overview');
23
+
24
+ return jsx('div', { className: 'container' }, [
25
+ jsx('header', { className: 'header' }, [
26
+ jsx('h1', {}, 'About Frontend Hamroun'),
27
+ jsx('a', { href: '/', className: 'back-link' }, '← Back to Home')
28
+ ]),
29
+
30
+ jsx('main', {}, [
31
+ jsx('div', { className: 'about-nav' }, [
32
+ jsx('button', {
33
+ className: activeSection === 'overview' ? 'active' : '',
34
+ onClick: () => setActiveSection('overview')
35
+ }, 'Overview'),
36
+ jsx('button', {
37
+ className: activeSection === 'features' ? 'active' : '',
38
+ onClick: () => setActiveSection('features')
39
+ }, 'Features'),
40
+ jsx('button', {
41
+ className: activeSection === 'team' ? 'active' : '',
42
+ onClick: () => setActiveSection('team')
43
+ }, 'Team')
44
+ ]),
45
+
46
+ // Overview section
47
+ activeSection === 'overview' && jsx(FadeIn, {}, [
48
+ jsx('section', { className: 'about-section' }, [
49
+ jsx('h2', {}, 'Framework Overview'),
50
+ jsx('p', {}, 'Frontend Hamroun is a lightweight JavaScript framework for building modern web applications with server-side rendering and client-side hydration.'),
51
+ jsx('p', {}, 'Inspired by React and other modern frameworks, it provides a simple yet powerful component model with hooks for state management and side effects.'),
52
+ jsx('p', {}, `This page was rendered on the server at ${props.api?.serverTime} and then hydrated on the client to provide interactivity.`)
53
+ ])
54
+ ]),
55
+
56
+ // Features section
57
+ activeSection === 'features' && jsx(FadeIn, {}, [
58
+ jsx('section', { className: 'about-section' }, [
59
+ jsx('h2', {}, 'Key Features'),
60
+ jsx('ul', { className: 'feature-list expanded' }, [
61
+ jsx('li', {}, [
62
+ jsx('strong', {}, 'Server-Side Rendering (SSR):'),
63
+ jsx('p', {}, 'Render pages on the server for faster initial load and improved SEO.')
64
+ ]),
65
+ jsx('li', {}, [
66
+ jsx('strong', {}, 'Client-Side Hydration:'),
67
+ jsx('p', {}, 'Add interactivity to server-rendered HTML without rebuilding the DOM.')
68
+ ]),
69
+ jsx('li', {}, [
70
+ jsx('strong', {}, 'React-like Hooks:'),
71
+ jsx('p', {}, 'Use useState, useEffect, useMemo, and useRef for managing component state and lifecycle.')
72
+ ]),
73
+ jsx('li', {}, [
74
+ jsx('strong', {}, 'Context API:'),
75
+ jsx('p', {}, 'Share state between components without prop drilling using createContext and useContext.')
76
+ ]),
77
+ jsx('li', {}, [
78
+ jsx('strong', {}, 'WebAssembly Integration:'),
79
+ jsx('p', {}, 'Leverage high-performance Go code compiled to WebAssembly for computationally intensive tasks.')
80
+ ]),
81
+ jsx('li', {}, [
82
+ jsx('strong', {}, 'Automatic Route Handling:'),
83
+ jsx('p', {}, 'File-based routing system similar to Next.js for intuitive page organization.')
84
+ ])
85
+ ])
86
+ ])
87
+ ]),
88
+
89
+ // Team section
90
+ activeSection === 'team' && jsx(FadeIn, {}, [
91
+ jsx('section', { className: 'about-section' }, [
92
+ jsx('h2', {}, 'The Team'),
93
+ jsx('div', { className: 'team-grid' }, [
94
+ jsx('div', { className: 'team-member' }, [
95
+ jsx('div', { className: 'avatar' }, 'MH'),
96
+ jsx('h3', {}, 'Mohamed Hamroun'),
97
+ jsx('p', { className: 'title' }, 'Lead Framework Developer'),
98
+ jsx('p', { className: 'bio' }, 'Creator of Frontend Hamroun and full-stack JavaScript enthusiast.')
99
+ ]),
100
+ jsx('div', { className: 'team-member' }, [
101
+ jsx('div', { className: 'avatar' }, 'AI'),
102
+ jsx('h3', {}, 'AI Assistant'),
103
+ jsx('p', { className: 'title' }, 'Documentation & Support'),
104
+ jsx('p', { className: 'bio' }, 'Helps with documentation, examples, and framework support.')
105
+ ])
106
+ ])
107
+ ])
108
+ ])
109
+ ]),
110
+
111
+ jsx('footer', {}, [
112
+ jsx('p', {}, '© 2025 Frontend Hamroun Framework')
113
+ ])
114
+ ]);
115
+ }
116
+
117
+ // Static metadata for SEO
118
+ AboutPage.getTitle = () => 'About - Frontend Hamroun Framework';
119
+ AboutPage.getDescription = () => 'Learn about the Frontend Hamroun framework, its features, and the team behind it.';
File without changes