frontend-hamroun 1.2.69 → 1.2.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontend-hamroun",
3
- "version": "1.2.69",
3
+ "version": "1.2.70",
4
4
  "description": "A lightweight full-stack JavaScript framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,22 @@
1
+ # Frontend Hamroun - Go WebAssembly Template with SSR
2
+
3
+ This template demonstrates integration between Frontend Hamroun, Go WebAssembly, and Server-Side Rendering for high-performance web applications.
4
+
5
+ ## Features
6
+
7
+ - 🔄 Go + WebAssembly integration
8
+ - 🌐 Server-Side Rendering with client hydration
9
+ - ⚡ High-performance computation in both browser and server
10
+ - 🚀 Same Go code runs in both environments
11
+ - 🔍 SEO-friendly with pre-rendered HTML
12
+ - 🧩 Example Go WASM modules and usage patterns
13
+
14
+ ## Prerequisites
15
+
16
+ - [Node.js](https://nodejs.org/) (v14+)
17
+ - [Go](https://golang.org/dl/) (v1.16+)
18
+
19
+ ## Getting Started
20
+
21
+ 1. Install dependencies:
22
+
@@ -45,13 +45,19 @@ async function buildWasmModules() {
45
45
  // Get the Go root directory
46
46
  const goRoot = execSync('go env GOROOT', { encoding: 'utf8' }).trim();
47
47
 
48
- // Copy the wasm_exec.js file to the output directory
48
+ // Copy the wasm_exec.js files to the output directory (for both browser and Node.js)
49
49
  const wasmExecJsPath = join(goRoot, 'misc', 'wasm', 'wasm_exec.js');
50
50
  const wasmExecJsDest = join(wasmOutputDir, 'wasm_exec.js');
51
51
 
52
+ const wasmExecNodeJsPath = join(goRoot, 'misc', 'wasm', 'wasm_exec_node.js');
53
+ const wasmExecNodeJsDest = join(wasmOutputDir, 'wasm_exec_node.js');
54
+
52
55
  console.log(`Copying ${wasmExecJsPath} to ${wasmExecJsDest}`);
53
56
  fs.copyFileSync(wasmExecJsPath, wasmExecJsDest);
54
57
 
58
+ console.log(`Copying ${wasmExecNodeJsPath} to ${wasmExecNodeJsDest}`);
59
+ fs.copyFileSync(wasmExecNodeJsPath, wasmExecNodeJsDest);
60
+
55
61
  // Build all Go files in the WASM directory
56
62
  if (fs.existsSync(goSourceDir)) {
57
63
  const goFiles = fs.readdirSync(goSourceDir)
@@ -1,20 +1,26 @@
1
1
  {
2
2
  "name": "go-wasm-app",
3
3
  "version": "1.0.0",
4
- "description": "WebAssembly integration with Go for Frontend Hamroun",
4
+ "description": "WebAssembly integration with Go for Frontend Hamroun with SSR",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "scripts": {
8
8
  "build:wasm": "node build-wasm.js",
9
- "dev": "npm run build:wasm && vite",
10
- "build": "npm run build:wasm && vite build",
11
- "serve": "vite preview",
9
+ "dev": "npm run build:wasm && concurrently \"node --watch server.js\" \"vite\"",
10
+ "build": "npm run build:wasm && vite build && tsc -p tsconfig.server.json",
11
+ "serve": "NODE_ENV=production node server.js",
12
+ "start": "npm run build && npm run serve",
12
13
  "test": "echo \"Error: no test specified\" && exit 1"
13
14
  },
14
15
  "dependencies": {
15
- "frontend-hamroun": "^1.2.68"
16
+ "express": "^4.18.2",
17
+ "frontend-hamroun": "^1.2.69"
16
18
  },
17
19
  "devDependencies": {
20
+ "@types/express": "^4.17.18",
21
+ "@types/node": "^18.16.19",
22
+ "concurrently": "^8.2.1",
23
+ "typescript": "^5.0.0",
18
24
  "vite": "^4.4.9"
19
25
  }
20
26
  }
@@ -0,0 +1,197 @@
1
+ :root {
2
+ --primary-color: #0070f3;
3
+ --secondary-color: #0051cc;
4
+ --background: #f9f9f9;
5
+ --text-color: #333;
6
+ --card-background: #fff;
7
+ --border-color: #eaeaea;
8
+ --error-color: #f44336;
9
+ --success-color: #4caf50;
10
+ }
11
+
12
+ * {
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ body {
17
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
18
+ background-color: var(--background);
19
+ color: var(--text-color);
20
+ margin: 0;
21
+ padding: 0;
22
+ line-height: 1.6;
23
+ }
24
+
25
+ .app {
26
+ max-width: 900px;
27
+ margin: 0 auto;
28
+ padding: 20px;
29
+ }
30
+
31
+ header {
32
+ text-align: center;
33
+ margin-bottom: 2rem;
34
+ }
35
+
36
+ header h1 {
37
+ margin-bottom: 0.5rem;
38
+ color: var(--primary-color);
39
+ }
40
+
41
+ header p {
42
+ color: #666;
43
+ }
44
+
45
+ .rendering-info {
46
+ display: flex;
47
+ gap: 10px;
48
+ justify-content: center;
49
+ margin-top: 10px;
50
+ }
51
+
52
+ .badge {
53
+ display: inline-block;
54
+ padding: 4px 8px;
55
+ border-radius: 4px;
56
+ background-color: #eaeaea;
57
+ color: #666;
58
+ font-size: 0.8rem;
59
+ }
60
+
61
+ .badge.active {
62
+ background-color: var(--success-color);
63
+ color: white;
64
+ }
65
+
66
+ footer {
67
+ margin-top: 3rem;
68
+ text-align: center;
69
+ color: #666;
70
+ padding: 1rem 0;
71
+ border-top: 1px solid var(--border-color);
72
+ }
73
+
74
+ footer a {
75
+ color: var(--primary-color);
76
+ text-decoration: none;
77
+ }
78
+
79
+ .loading {
80
+ text-align: center;
81
+ padding: 2rem;
82
+ }
83
+
84
+ .error {
85
+ background-color: rgba(244, 67, 54, 0.1);
86
+ border: 1px solid var(--error-color);
87
+ border-radius: 8px;
88
+ padding: 1rem;
89
+ margin: 1rem 0;
90
+ }
91
+
92
+ .error-message {
93
+ background-color: rgba(244, 67, 54, 0.1);
94
+ color: var(--error-color);
95
+ padding: 1rem;
96
+ border-radius: 8px;
97
+ margin-bottom: 1rem;
98
+ }
99
+
100
+ .card {
101
+ background-color: var(--card-background);
102
+ border: 1px solid var(--border-color);
103
+ border-radius: 8px;
104
+ padding: 1.5rem;
105
+ margin-bottom: 2rem;
106
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
107
+ }
108
+
109
+ .demo-section {
110
+ background-color: var(--card-background);
111
+ border: 1px solid var(--border-color);
112
+ border-radius: 8px;
113
+ padding: 1.5rem;
114
+ margin-bottom: 2rem;
115
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
116
+ }
117
+
118
+ .input-row {
119
+ display: flex;
120
+ align-items: center;
121
+ gap: 0.5rem;
122
+ margin-bottom: 1rem;
123
+ }
124
+
125
+ .input-row input {
126
+ width: 80px;
127
+ padding: 0.5rem;
128
+ border: 1px solid var(--border-color);
129
+ border-radius: 4px;
130
+ font-size: 1rem;
131
+ }
132
+
133
+ .input-row .operator {
134
+ font-size: 1.5rem;
135
+ margin: 0 0.5rem;
136
+ }
137
+
138
+ button {
139
+ background-color: var(--primary-color);
140
+ color: white;
141
+ border: none;
142
+ border-radius: 4px;
143
+ padding: 0.5rem 1rem;
144
+ cursor: pointer;
145
+ font-size: 1rem;
146
+ transition: background-color 0.2s;
147
+ }
148
+
149
+ button:hover {
150
+ background-color: var(--secondary-color);
151
+ }
152
+
153
+ .json-editor {
154
+ display: flex;
155
+ flex-direction: column;
156
+ gap: 1rem;
157
+ }
158
+
159
+ .json-editor textarea {
160
+ width: 100%;
161
+ padding: 0.5rem;
162
+ border: 1px solid var(--border-color);
163
+ border-radius: 4px;
164
+ font-family: monospace;
165
+ font-size: 0.9rem;
166
+ resize: vertical;
167
+ }
168
+
169
+ .result {
170
+ background-color: #f0f7ff;
171
+ padding: 1rem;
172
+ border-radius: 4px;
173
+ margin-top: 1rem;
174
+ }
175
+
176
+ .result pre {
177
+ margin: 0;
178
+ white-space: pre-wrap;
179
+ font-family: monospace;
180
+ font-size: 0.9rem;
181
+ }
182
+
183
+ .info-section {
184
+ background-color: #f0f7ff;
185
+ border: 1px solid #e1e7fd;
186
+ border-radius: 8px;
187
+ padding: 1.5rem;
188
+ }
189
+
190
+ .info-section h2 {
191
+ color: var(--primary-color);
192
+ margin-top: 0;
193
+ }
194
+
195
+ .info-section ol {
196
+ padding-left: 1.5rem;
197
+ }
@@ -0,0 +1,123 @@
1
+ import express from 'express';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { renderToString } from 'frontend-hamroun/ssr';
6
+ import { loadGoWasmFromFile } from 'frontend-hamroun/server/wasm';
7
+
8
+ // ESM __dirname equivalent
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ // Configuration
13
+ const PORT = process.env.PORT || 3000;
14
+ const isProduction = process.env.NODE_ENV === 'production';
15
+ const WASM_PATH = path.join(__dirname, isProduction ? 'dist' : 'public', 'wasm', 'example.wasm');
16
+ const WASM_EXEC_PATH = path.join(__dirname, isProduction ? 'dist' : 'public', 'wasm', 'wasm_exec_node.js');
17
+
18
+ // Express app setup
19
+ const app = express();
20
+
21
+ // Serve static assets
22
+ if (isProduction) {
23
+ app.use(express.static('dist', { index: false }));
24
+ } else {
25
+ app.use(express.static('public', { index: false }));
26
+ }
27
+
28
+ // Setup a route to serve wasm files with correct MIME type
29
+ app.get('*.wasm', (req, res, next) => {
30
+ res.set('Content-Type', 'application/wasm');
31
+ next();
32
+ });
33
+
34
+ // Initialize WASM for server-side processing
35
+ let serverWasmInstance = null;
36
+
37
+ async function initServerWasm() {
38
+ try {
39
+ // Check if WASM file exists
40
+ if (fs.existsSync(WASM_PATH)) {
41
+ serverWasmInstance = await loadGoWasmFromFile(WASM_PATH, {
42
+ goWasmPath: WASM_EXEC_PATH,
43
+ debug: !isProduction
44
+ });
45
+ console.log('✅ Server WASM module loaded successfully');
46
+ } else {
47
+ console.warn('⚠️ WASM file not found at:', WASM_PATH);
48
+ }
49
+ } catch (error) {
50
+ console.error('❌ Failed to load server WASM module:', error);
51
+ }
52
+ }
53
+
54
+ // Handle all requests with server-side rendering
55
+ app.get('*', async (req, res) => {
56
+ try {
57
+ // In production, use the pre-built App; in dev, dynamically import
58
+ const AppModule = isProduction
59
+ ? await import('./dist/server/App.js')
60
+ : { default: (props) => ({ type: 'div', props: { children: 'Loading...' } }) };
61
+
62
+ // Initial state to hydrate the app
63
+ const initialState = {
64
+ path: req.path,
65
+ wasmAvailable: serverWasmInstance !== null,
66
+ ssrRendered: true
67
+ };
68
+
69
+ // Process data with WASM if available
70
+ if (serverWasmInstance) {
71
+ try {
72
+ // Example of server-side WASM processing
73
+ const processDataFn = serverWasmInstance.functions.goProcessData;
74
+ const processedData = processDataFn({
75
+ source: 'server',
76
+ timestamp: new Date().toISOString(),
77
+ values: [10, 20, 30, 40, 50]
78
+ });
79
+
80
+ initialState.processedData = JSON.parse(processedData);
81
+ } catch (error) {
82
+ console.error('Error using WASM on server:', error);
83
+ initialState.wasmError = error.message;
84
+ }
85
+ }
86
+
87
+ // Render the app to HTML
88
+ const content = renderToString(AppModule.default({ initialState }));
89
+
90
+ // Read the HTML template
91
+ let html = fs.readFileSync(
92
+ path.resolve(__dirname, isProduction ? './dist/index.html' : './public/index.html'),
93
+ 'utf8'
94
+ );
95
+
96
+ // Inject rendered content and initial state
97
+ html = html.replace(
98
+ '<div id="root"></div>',
99
+ `<div id="root">${content}</div>
100
+ <script>window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};</script>`
101
+ );
102
+
103
+ res.setHeader('Content-Type', 'text/html');
104
+ res.send(html);
105
+ } catch (error) {
106
+ console.error('Server rendering error:', error);
107
+ res.status(500).send(`
108
+ <h1>Server Error</h1>
109
+ <p>${error.message}</p>
110
+ <pre>${error.stack}</pre>
111
+ `);
112
+ }
113
+ });
114
+
115
+ // Initialize server
116
+ async function startServer() {
117
+ await initServerWasm();
118
+ app.listen(PORT, () => {
119
+ console.log(`\n🚀 Server running at http://localhost:${PORT}\n`);
120
+ });
121
+ }
122
+
123
+ startServer();
@@ -0,0 +1,79 @@
1
+ import { jsx, useState, useEffect } from 'frontend-hamroun';
2
+ import { loadGoWasm } from 'frontend-hamroun';
3
+ import WasmDemo from './components/WasmDemo';
4
+ import Header from './components/Header';
5
+ import Footer from './components/Footer';
6
+
7
+ export default function App({ initialState }) {
8
+ const [wasmLoaded, setWasmLoaded] = useState(initialState?.wasmAvailable || false);
9
+ const [wasmInstance, setWasmInstance] = useState(null);
10
+ const [error, setError] = useState(initialState?.wasmError || null);
11
+ const [ssrProcessedData, setSsrProcessedData] = useState(initialState?.processedData || null);
12
+ const [isClientSide, setIsClientSide] = useState(false);
13
+
14
+ // Detect client-side rendering
15
+ useEffect(() => {
16
+ setIsClientSide(true);
17
+ }, []);
18
+
19
+ // Load the WASM module on client-side only
20
+ useEffect(() => {
21
+ if (typeof window === 'undefined') return; // Skip on server
22
+
23
+ async function loadWasm() {
24
+ try {
25
+ console.log('Loading WASM module in browser...');
26
+ const instance = await loadGoWasm('/wasm/example.wasm', {
27
+ goWasmPath: '/wasm/wasm_exec.js',
28
+ debug: true
29
+ });
30
+
31
+ console.log('WASM module loaded successfully');
32
+ setWasmInstance(instance);
33
+ setWasmLoaded(true);
34
+ } catch (err) {
35
+ console.error('Failed to load WASM:', err);
36
+ setError(err.message);
37
+ }
38
+ }
39
+
40
+ if (!wasmLoaded || !wasmInstance) {
41
+ loadWasm();
42
+ }
43
+ }, [wasmLoaded, wasmInstance]);
44
+
45
+ return (
46
+ <div className="app">
47
+ <Header
48
+ isSSR={initialState?.ssrRendered}
49
+ isHydrated={isClientSide}
50
+ />
51
+
52
+ <main>
53
+ {ssrProcessedData && (
54
+ <div className="card">
55
+ <h2>Server-Processed Data</h2>
56
+ <p>This data was processed by Go WASM on the server:</p>
57
+ <pre className="result">{JSON.stringify(ssrProcessedData, null, 2)}</pre>
58
+ </div>
59
+ )}
60
+
61
+ {error ? (
62
+ <div className="error">
63
+ <h2>Failed to load WebAssembly</h2>
64
+ <p>{error}</p>
65
+ <p>Make sure you've built the WASM modules with <code>npm run build:wasm</code></p>
66
+ </div>
67
+ ) : wasmLoaded && wasmInstance ? (
68
+ <WasmDemo wasm={wasmInstance} />
69
+ ) : (
70
+ <div className="loading">
71
+ <p>Loading WebAssembly module{isClientSide ? '...' : ' on client after hydration'}</p>
72
+ </div>
73
+ )}
74
+ </main>
75
+
76
+ <Footer />
77
+ </div>
78
+ );
79
+ }
@@ -0,0 +1,13 @@
1
+ import { jsx } from 'frontend-hamroun';
2
+
3
+ export default function Footer() {
4
+ return (
5
+ <footer>
6
+ <p>
7
+ Built with <a href="https://github.com/hamroun/frontend-hamroun" target="_blank" rel="noopener noreferrer">
8
+ Frontend Hamroun
9
+ </a> and Go WebAssembly
10
+ </p>
11
+ </footer>
12
+ );
13
+ }
@@ -0,0 +1,19 @@
1
+ import { jsx } from 'frontend-hamroun';
2
+
3
+ export default function Header({ isSSR, isHydrated }) {
4
+ return (
5
+ <header>
6
+ <h1>Frontend Hamroun + Go WebAssembly</h1>
7
+ <p>A powerful combination for high-performance web applications</p>
8
+
9
+ <div className="rendering-info">
10
+ <span className={`badge ${isSSR ? 'active' : ''}`}>
11
+ Server Rendered
12
+ </span>
13
+ <span className={`badge ${isHydrated ? 'active' : ''}`}>
14
+ Hydrated
15
+ </span>
16
+ </div>
17
+ </header>
18
+ );
19
+ }
@@ -0,0 +1,120 @@
1
+ import { jsx, useState } from 'frontend-hamroun';
2
+
3
+ export default function WasmDemo({ wasm }) {
4
+ // Addition demo state
5
+ const [num1, setNum1] = useState(5);
6
+ const [num2, setNum2] = useState(7);
7
+ const [sum, setSum] = useState(null);
8
+
9
+ // Data processing demo state
10
+ const [processInput, setProcessInput] = useState(JSON.stringify({
11
+ name: 'Test data',
12
+ source: 'client',
13
+ values: [10, 20, 30, 40, 50],
14
+ timestamp: new Date().toISOString()
15
+ }, null, 2));
16
+ const [processResult, setProcessResult] = useState(null);
17
+
18
+ // Error state
19
+ const [error, setError] = useState(null);
20
+
21
+ // Handle addition with Go WASM
22
+ const handleCalculate = () => {
23
+ try {
24
+ setError(null);
25
+ // Get the goAdd function from the WASM instance
26
+ const goAdd = wasm.functions.goAdd;
27
+ // Call the Go function and get the result
28
+ const result = goAdd(parseInt(num1), parseInt(num2));
29
+ setSum(result);
30
+ } catch (err) {
31
+ console.error('Error calling Go function:', err);
32
+ setError(`Error: ${err.message}`);
33
+ }
34
+ };
35
+
36
+ // Handle complex data processing with Go WASM
37
+ const handleProcessData = () => {
38
+ try {
39
+ setError(null);
40
+ // Parse the input JSON
41
+ const inputData = JSON.parse(processInput);
42
+ // Get the goProcessData function from the WASM instance
43
+ const goProcessData = wasm.functions.goProcessData;
44
+ // Call the Go function with the data
45
+ const result = goProcessData(inputData);
46
+ // Parse the returned JSON and format it
47
+ setProcessResult(JSON.parse(result));
48
+ } catch (err) {
49
+ console.error('Error processing data with Go:', err);
50
+ setError(`Error: ${err.message}`);
51
+ }
52
+ };
53
+
54
+ return (
55
+ <div className="wasm-demo">
56
+ {error && (
57
+ <div className="error-message">
58
+ {error}
59
+ </div>
60
+ )}
61
+
62
+ <section className="demo-section">
63
+ <h2>Simple Addition with Go</h2>
64
+ <p>Call a Go WASM function to add two numbers:</p>
65
+ <div className="input-row">
66
+ <input
67
+ type="number"
68
+ value={num1}
69
+ onChange={(e) => setNum1(e.target.value)}
70
+ />
71
+ <span className="operator">+</span>
72
+ <input
73
+ type="number"
74
+ value={num2}
75
+ onChange={(e) => setNum2(e.target.value)}
76
+ />
77
+ <button onClick={handleCalculate}>Calculate</button>
78
+ </div>
79
+
80
+ {sum !== null && (
81
+ <div className="result">
82
+ <h3>Result:</h3>
83
+ <pre>{sum}</pre>
84
+ </div>
85
+ )}
86
+ </section>
87
+
88
+ <section className="demo-section">
89
+ <h2>Complex Data Processing with Go</h2>
90
+ <p>Process JSON data using a Go WASM function:</p>
91
+ <div className="json-editor">
92
+ <textarea
93
+ value={processInput}
94
+ onChange={(e) => setProcessInput(e.target.value)}
95
+ rows={10}
96
+ />
97
+ <button onClick={handleProcessData}>Process Data</button>
98
+ </div>
99
+
100
+ {processResult && (
101
+ <div className="result">
102
+ <h3>Processed Result:</h3>
103
+ <pre>{JSON.stringify(processResult, null, 2)}</pre>
104
+ </div>
105
+ )}
106
+ </section>
107
+
108
+ <section className="info-section">
109
+ <h2>How It Works</h2>
110
+ <p>This demo demonstrates the integration between Frontend Hamroun and Go WebAssembly with SSR:</p>
111
+ <ol>
112
+ <li>Server renders the initial HTML using the same React-like components</li>
113
+ <li>Server can process data with Go WASM before sending the response</li>
114
+ <li>Browser hydrates the app and loads its own WASM module</li>
115
+ <li>The same Go code runs in both server and browser environments</li>
116
+ </ol>
117
+ </section>
118
+ </div>
119
+ );
120
+ }
@@ -0,0 +1,12 @@
1
+ import { hydrate } from 'frontend-hamroun';
2
+ import App from './App';
3
+
4
+ // Get initial state from server
5
+ const initialState = window.__INITIAL_STATE__ || {
6
+ path: window.location.pathname,
7
+ ssrRendered: false,
8
+ wasmAvailable: false
9
+ };
10
+
11
+ // Hydrate the application
12
+ hydrate(<App initialState={initialState} />, document.getElementById('root'));
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "dist/server",
10
+ "jsx": "react",
11
+ "jsxFactory": "jsx",
12
+ "jsxFragmentFactory": "Fragment",
13
+ "allowJs": true,
14
+ "declaration": false
15
+ },
16
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.jsx"],
17
+ "exclude": ["node_modules"]
18
+ }