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 +1 -1
- package/templates/go-wasm-app/README.md +22 -0
- package/templates/go-wasm-app/build-wasm.js +7 -1
- package/templates/go-wasm-app/package.json +11 -5
- package/templates/go-wasm-app/public/styles.css +197 -0
- package/templates/go-wasm-app/server.js +123 -0
- package/templates/go-wasm-app/src/App.jsx +79 -0
- package/templates/go-wasm-app/src/components/Footer.jsx +13 -0
- package/templates/go-wasm-app/src/components/Header.jsx +19 -0
- package/templates/go-wasm-app/src/components/WasmDemo.jsx +120 -0
- package/templates/go-wasm-app/src/main.jsx +12 -0
- package/templates/go-wasm-app/tsconfig.server.json +18 -0
package/package.json
CHANGED
@@ -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
|
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": "
|
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
|
-
"
|
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
|
+
}
|