baraqex 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +11 -9
- package/dist/browser.js.map +2 -2
- package/dist/core/jsx-runtime.d.ts +1 -1
- package/dist/core/jsx-runtime.d.ts.map +1 -1
- package/dist/index.js +12 -10
- package/dist/index.js.map +2 -2
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +2 -2
- package/package.json +1 -1
- package/templates/fullstack-app/README.md +62 -37
- package/templates/fullstack-app/build-wasm.js +80 -0
- package/templates/fullstack-app/build.mjs +133 -0
- package/templates/fullstack-app/go/main.go +53 -0
- package/templates/fullstack-app/index.html +16 -36
- package/templates/fullstack-app/package.json +41 -35
- package/templates/fullstack-app/postcss.config.js +6 -6
- package/templates/fullstack-app/public/wasm/.gitkeep +1 -0
- package/templates/fullstack-app/server.ts +220 -474
- package/templates/fullstack-app/src/App.tsx +77 -0
- package/templates/fullstack-app/src/main.tsx +31 -155
- package/templates/fullstack-app/src/pages/About.tsx +40 -0
- package/templates/fullstack-app/src/pages/Home.tsx +67 -0
- package/templates/fullstack-app/src/pages/WasmDemo.tsx +81 -0
- package/templates/fullstack-app/src/styles.css +12 -5
- package/templates/fullstack-app/tailwind.config.js +8 -11
- package/templates/fullstack-app/tsconfig.json +18 -18
- package/templates/fullstack-app/api/hello.js +0 -11
- package/templates/fullstack-app/api/hello.ts +0 -18
- package/templates/fullstack-app/api/users/[id].ts +0 -73
- package/templates/fullstack-app/api/users/index.ts +0 -32
- package/templates/fullstack-app/build/main.css +0 -874
- package/templates/fullstack-app/build/main.css.map +0 -7
- package/templates/fullstack-app/build/main.js +0 -967
- package/templates/fullstack-app/build/main.js.map +0 -7
- package/templates/fullstack-app/build.ts +0 -210
- package/templates/fullstack-app/process-tailwind.js +0 -45
- package/templates/fullstack-app/public/_redirects +0 -1
- package/templates/fullstack-app/public/route-handler.js +0 -47
- package/templates/fullstack-app/public/spa-fix.html +0 -17
- package/templates/fullstack-app/public/styles.css +0 -975
- package/templates/fullstack-app/public/tailwind.css +0 -15
- package/templates/fullstack-app/server.js +0 -267
- package/templates/fullstack-app/src/README.md +0 -55
- package/templates/fullstack-app/src/client.js +0 -87
- package/templates/fullstack-app/src/components/ClientHome.tsx +0 -0
- package/templates/fullstack-app/src/components/ErrorBoundary.tsx +0 -36
- package/templates/fullstack-app/src/components/Layout.tsx +0 -42
- package/templates/fullstack-app/src/components/StateDemo.tsx +0 -207
- package/templates/fullstack-app/src/components/UserList.tsx +0 -44
- package/templates/fullstack-app/src/config.ts +0 -42
- package/templates/fullstack-app/src/data/api.ts +0 -206
- package/templates/fullstack-app/src/middleware.ts +0 -28
- package/templates/fullstack-app/src/pages/404.tsx +0 -28
- package/templates/fullstack-app/src/pages/[id].tsx +0 -0
- package/templates/fullstack-app/src/pages/_app.tsx +0 -11
- package/templates/fullstack-app/src/pages/_document.tsx +0 -25
- package/templates/fullstack-app/src/pages/_error.tsx +0 -45
- package/templates/fullstack-app/src/pages/about/index.tsx +0 -67
- package/templates/fullstack-app/src/pages/about.tsx +0 -71
- package/templates/fullstack-app/src/pages/api/users/[id].ts +0 -73
- package/templates/fullstack-app/src/pages/api/users/index.ts +0 -43
- package/templates/fullstack-app/src/pages/index.tsx +0 -110
- package/templates/fullstack-app/src/pages/users/[id].tsx +0 -153
- package/templates/fullstack-app/src/pages/users.tsx +0 -60
- package/templates/fullstack-app/src/pages/wasm-demo.tsx +0 -1
- package/templates/fullstack-app/src/router.ts +0 -255
- package/templates/fullstack-app/vite.config.js +0 -87
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx, useState } from 'baraqex';
|
|
2
|
+
import { HomePage } from './pages/Home';
|
|
3
|
+
import { AboutPage } from './pages/About';
|
|
4
|
+
import { WasmDemo } from './pages/WasmDemo';
|
|
5
|
+
|
|
6
|
+
export interface AppProps {
|
|
7
|
+
route?: string;
|
|
8
|
+
initialState?: any;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function parseRoute(pathname: string): string {
|
|
12
|
+
const p = pathname || '/';
|
|
13
|
+
if (p.startsWith('/wasm')) return 'wasm';
|
|
14
|
+
if (p.startsWith('/about')) return 'about';
|
|
15
|
+
return 'home';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function App({ route, initialState }: AppProps) {
|
|
19
|
+
const [current, setCurrent] = useState<string>(parseRoute(route || (typeof window !== 'undefined' ? window.location.pathname : '/')));
|
|
20
|
+
|
|
21
|
+
const navigate = (target: string) => {
|
|
22
|
+
setCurrent(target);
|
|
23
|
+
if (typeof window !== 'undefined') {
|
|
24
|
+
const path = target === 'home' ? '/' : `/${target}`;
|
|
25
|
+
window.history.pushState(null, '', path);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const navItems = [
|
|
30
|
+
{ key: 'home', label: 'Home', href: '/' },
|
|
31
|
+
{ key: 'about', label: 'About', href: '/about' },
|
|
32
|
+
{ key: 'wasm', label: 'WASM (Go)', href: '/wasm' }
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="min-h-screen flex flex-col bg-gray-50">
|
|
37
|
+
<header className="bg-blue-700 text-white shadow">
|
|
38
|
+
<div className="max-w-5xl mx-auto px-4 py-4 flex items-center justify-between">
|
|
39
|
+
<button onClick={() => navigate('home')} className="text-xl font-bold hover:text-blue-100">
|
|
40
|
+
Baraqex Full-Stack
|
|
41
|
+
</button>
|
|
42
|
+
<nav>
|
|
43
|
+
<ul className="flex space-x-6">
|
|
44
|
+
{navItems.map((item) => (
|
|
45
|
+
<li key={item.key}>
|
|
46
|
+
<button
|
|
47
|
+
onClick={() => navigate(item.key)}
|
|
48
|
+
className={
|
|
49
|
+
'hover:text-blue-200 ' +
|
|
50
|
+
(current === item.key ? 'underline font-semibold' : '')
|
|
51
|
+
}
|
|
52
|
+
>
|
|
53
|
+
{item.label}
|
|
54
|
+
</button>
|
|
55
|
+
</li>
|
|
56
|
+
))}
|
|
57
|
+
</ul>
|
|
58
|
+
</nav>
|
|
59
|
+
</div>
|
|
60
|
+
</header>
|
|
61
|
+
|
|
62
|
+
<main className="flex-grow max-w-5xl w-full mx-auto px-4 py-8">
|
|
63
|
+
{current === 'home' && <HomePage initialState={initialState} />}
|
|
64
|
+
{current === 'about' && <AboutPage />}
|
|
65
|
+
{current === 'wasm' && <WasmDemo />}
|
|
66
|
+
</main>
|
|
67
|
+
|
|
68
|
+
<footer className="bg-gray-800 text-white">
|
|
69
|
+
<div className="max-w-5xl mx-auto px-4 py-6 text-center text-gray-400">
|
|
70
|
+
© {new Date().getFullYear()} Baraqex — SSR + WASM + Full-Stack in one template
|
|
71
|
+
</div>
|
|
72
|
+
</footer>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default App;
|
|
@@ -1,155 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import './styles.css';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// Update URL if needed
|
|
34
|
-
if (isPushState) {
|
|
35
|
-
window.history.pushState(null, '', path);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Dynamically load the page component based on the path
|
|
39
|
-
const normalizedPath = path === '/' ? 'index' : path.replace(/^\//, '');
|
|
40
|
-
|
|
41
|
-
let Page;
|
|
42
|
-
try {
|
|
43
|
-
// Dynamic import for the page component
|
|
44
|
-
const module = await import(`./pages/${normalizedPath}.tsx`);
|
|
45
|
-
Page = module.default;
|
|
46
|
-
} catch (error) {
|
|
47
|
-
console.warn(`[Router] Could not load page for ${path}, trying index file`);
|
|
48
|
-
try {
|
|
49
|
-
// Try loading index file in directory
|
|
50
|
-
const module = await import(`./pages/${normalizedPath}/index.tsx`);
|
|
51
|
-
Page = module.default;
|
|
52
|
-
} catch (innerError) {
|
|
53
|
-
console.error(`[Router] Failed to load page component for ${path}`);
|
|
54
|
-
|
|
55
|
-
// Try to load 404 page
|
|
56
|
-
try {
|
|
57
|
-
const notFoundModule = await import(`./pages/404.tsx`);
|
|
58
|
-
Page = notFoundModule.default;
|
|
59
|
-
} catch (notFoundError) {
|
|
60
|
-
// If all fails, render a simple not found message
|
|
61
|
-
const rootElement = document.getElementById('root');
|
|
62
|
-
if (rootElement) {
|
|
63
|
-
render(
|
|
64
|
-
<div style={{ padding: '20px', maxWidth: '800px', margin: '0 auto' }}>
|
|
65
|
-
<h1>Page Not Found</h1>
|
|
66
|
-
<p>The page you requested could not be found.</p>
|
|
67
|
-
<a href="/" style={{ color: '#0066cc' }}>Go to Home</a>
|
|
68
|
-
</div>,
|
|
69
|
-
rootElement
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Get the current page props
|
|
78
|
-
let pageProps = initialState.pageProps || {};
|
|
79
|
-
|
|
80
|
-
// If the page defines getServerSideProps, fetch data
|
|
81
|
-
if (Page.getServerSideProps) {
|
|
82
|
-
try {
|
|
83
|
-
const response = await fetch(`/api/__props${path}`);
|
|
84
|
-
if (response.ok) {
|
|
85
|
-
const data = await response.json();
|
|
86
|
-
pageProps = data.props || {};
|
|
87
|
-
}
|
|
88
|
-
} catch (error) {
|
|
89
|
-
console.error('[Router] Error fetching page props:', error);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Update the state with current route
|
|
94
|
-
const updatedState = {
|
|
95
|
-
...initialState,
|
|
96
|
-
route: path,
|
|
97
|
-
pageProps
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// Render the page
|
|
101
|
-
const rootElement = document.getElementById('root');
|
|
102
|
-
if (!rootElement) return;
|
|
103
|
-
|
|
104
|
-
if (isHydrating && path === initialState.route) {
|
|
105
|
-
console.log('[Client] Hydrating server-rendered content');
|
|
106
|
-
hydrate(<Page {...pageProps} initialState={updatedState} />, rootElement);
|
|
107
|
-
isHydrating = false;
|
|
108
|
-
} else {
|
|
109
|
-
console.log('[Client] Rendering client-side');
|
|
110
|
-
render(<Page {...pageProps} initialState={updatedState} />, rootElement);
|
|
111
|
-
}
|
|
112
|
-
} catch (error) {
|
|
113
|
-
console.error('[Router] Navigation error:', error);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Handle initial route
|
|
118
|
-
handleRouteChange(window.location.pathname, false);
|
|
119
|
-
|
|
120
|
-
// Handle client-side navigation
|
|
121
|
-
document.addEventListener('click', (e) => {
|
|
122
|
-
let target = e.target as HTMLElement | null;
|
|
123
|
-
|
|
124
|
-
// Find closest anchor element
|
|
125
|
-
while (target && target.tagName !== 'A') {
|
|
126
|
-
target = target.parentElement;
|
|
127
|
-
if (!target) break;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (target &&
|
|
131
|
-
target.tagName === 'A' &&
|
|
132
|
-
target.getAttribute('href') &&
|
|
133
|
-
target.getAttribute('href')?.startsWith('/') &&
|
|
134
|
-
!target.getAttribute('href')?.startsWith('//') &&
|
|
135
|
-
!target.getAttribute('target')) {
|
|
136
|
-
|
|
137
|
-
e.preventDefault();
|
|
138
|
-
const href = target.getAttribute('href') || '/';
|
|
139
|
-
handleRouteChange(href);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Handle back/forward navigation
|
|
144
|
-
window.addEventListener('popstate', () => {
|
|
145
|
-
handleRouteChange(window.location.pathname, false);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// Set up socket.io for live reload in development
|
|
149
|
-
if (typeof io !== 'undefined') {
|
|
150
|
-
const socket = io();
|
|
151
|
-
socket.on('reload', () => {
|
|
152
|
-
console.log('[Dev] Reloading page due to file changes');
|
|
153
|
-
window.location.reload();
|
|
154
|
-
});
|
|
155
|
-
}
|
|
1
|
+
import { hydrate, jsx } from 'baraqex';
|
|
2
|
+
import { App } from './App';
|
|
3
|
+
import './styles.css';
|
|
4
|
+
|
|
5
|
+
declare global {
|
|
6
|
+
interface Window {
|
|
7
|
+
__INITIAL_STATE__?: any;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const initialState = window.__INITIAL_STATE__ || {};
|
|
12
|
+
const route = window.location.pathname;
|
|
13
|
+
|
|
14
|
+
const root = document.getElementById('root');
|
|
15
|
+
if (root) {
|
|
16
|
+
hydrate(jsx(App, { route, initialState }), root);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Socket.IO live reload (dev)
|
|
20
|
+
const w = window as any;
|
|
21
|
+
if (w.io) {
|
|
22
|
+
const socket = w.io();
|
|
23
|
+
socket.on('reload', () => window.location.reload());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Handle back/forward navigation
|
|
27
|
+
window.addEventListener('popstate', () => {
|
|
28
|
+
const r = document.getElementById('root');
|
|
29
|
+
if (r) hydrate(jsx(App, { route: window.location.pathname, initialState }), r);
|
|
30
|
+
});
|
|
31
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx } from 'baraqex';
|
|
2
|
+
|
|
3
|
+
export function AboutPage() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="bg-white rounded-lg shadow p-6 space-y-6">
|
|
6
|
+
<h1 className="text-3xl font-bold text-gray-900">About this template</h1>
|
|
7
|
+
<div className="grid md:grid-cols-2 gap-6">
|
|
8
|
+
<div className="p-4 border border-blue-100 rounded-lg bg-blue-50">
|
|
9
|
+
<h3 className="font-semibold text-blue-800 mb-2">Frontend + SSR</h3>
|
|
10
|
+
<p className="text-sm text-blue-900">
|
|
11
|
+
The same <code className="bg-blue-100 px-1 rounded">App</code> component is rendered
|
|
12
|
+
server-side with <code>baraqex</code> and hydrated on the client. Initial page HTML
|
|
13
|
+
arrives fully formed.
|
|
14
|
+
</p>
|
|
15
|
+
</div>
|
|
16
|
+
<div className="p-4 border border-green-100 rounded-lg bg-green-50">
|
|
17
|
+
<h3 className="font-semibold text-green-800 mb-2">Backend (Express API)</h3>
|
|
18
|
+
<p className="text-sm text-green-900">
|
|
19
|
+
API endpoints under <code>/api/*</code> are served by an Express server bundled
|
|
20
|
+
alongside the app. Refresh users from <code>/api/users</code>.
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
<div className="p-4 border border-purple-100 rounded-lg bg-purple-50">
|
|
24
|
+
<h3 className="font-semibold text-purple-800 mb-2">WebAssembly (Go)</h3>
|
|
25
|
+
<p className="text-sm text-purple-900">
|
|
26
|
+
Go source in <code>go/main.go</code> compiles to WASM via{' '}
|
|
27
|
+
<code>npm run build:wasm</code>. Call Go functions straight from the browser.
|
|
28
|
+
</p>
|
|
29
|
+
</div>
|
|
30
|
+
<div className="p-4 border border-amber-100 rounded-lg bg-amber-50">
|
|
31
|
+
<h3 className="font-semibold text-amber-800 mb-2">Live reload (dev)</h3>
|
|
32
|
+
<p className="text-sm text-amber-900">
|
|
33
|
+
<code>npm run dev</code> bundles with esbuild and streams reload events over
|
|
34
|
+
Socket.IO when files change.
|
|
35
|
+
</p>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx, useState } from 'baraqex';
|
|
2
|
+
|
|
3
|
+
export interface HomeState {
|
|
4
|
+
users?: any[];
|
|
5
|
+
posts?: any[];
|
|
6
|
+
serverTime?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function HomePage({ initialState }: { initialState?: any }) {
|
|
10
|
+
const initial: HomeState = initialState || { users: [], posts: [] };
|
|
11
|
+
const [users, setUsers] = useState<any[]>(initial.users || []);
|
|
12
|
+
const [message, setMessage] = useState('');
|
|
13
|
+
|
|
14
|
+
const refresh = async () => {
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch('/api/users');
|
|
17
|
+
const data = await res.json();
|
|
18
|
+
setUsers(data);
|
|
19
|
+
setMessage('Data refreshed from /api/users');
|
|
20
|
+
} catch (e: any) {
|
|
21
|
+
setMessage('Failed to load data: ' + e.message);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="space-y-8">
|
|
27
|
+
<section>
|
|
28
|
+
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
|
29
|
+
Welcome to Baraqex
|
|
30
|
+
</h1>
|
|
31
|
+
<p className="text-gray-600">
|
|
32
|
+
This template wires together server-side rendering (SSR), a REST API, WebAssembly
|
|
33
|
+
compiled from Go, and hydration — all through the baraqex framework.
|
|
34
|
+
</p>
|
|
35
|
+
{initial.serverTime && (
|
|
36
|
+
<p className="text-sm text-gray-500 mt-2">
|
|
37
|
+
Server-rendered at: {initial.serverTime}
|
|
38
|
+
</p>
|
|
39
|
+
)}
|
|
40
|
+
</section>
|
|
41
|
+
|
|
42
|
+
<section className="bg-white rounded-lg shadow p-6">
|
|
43
|
+
<div className="flex items-center justify-between mb-4">
|
|
44
|
+
<h2 className="text-xl font-semibold text-gray-800">Users (fetched via API)</h2>
|
|
45
|
+
<button
|
|
46
|
+
onClick={refresh}
|
|
47
|
+
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
48
|
+
>
|
|
49
|
+
Refresh
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
{message && <p className="text-green-700 text-sm mb-3">{message}</p>}
|
|
53
|
+
<ul className="divide-y divide-gray-200">
|
|
54
|
+
{(users || []).map((user: any) => (
|
|
55
|
+
<li key={user.id} className="py-2 flex items-center justify-between">
|
|
56
|
+
<span className="font-medium">{user.name}</span>
|
|
57
|
+
<span className="text-sm text-gray-500">{user.email}</span>
|
|
58
|
+
</li>
|
|
59
|
+
))}
|
|
60
|
+
</ul>
|
|
61
|
+
{(users || []).length === 0 && (
|
|
62
|
+
<p className="text-gray-500 text-sm">No users loaded yet. Click Refresh.</p>
|
|
63
|
+
)}
|
|
64
|
+
</section>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx, useState, useEffect, batchUpdates } from 'baraqex';
|
|
2
|
+
import { loadGoWasm } from 'baraqex';
|
|
3
|
+
|
|
4
|
+
export function WasmDemo() {
|
|
5
|
+
const [ready, setReady] = useState(false);
|
|
6
|
+
const [status, setStatus] = useState('Loading Go WASM...');
|
|
7
|
+
const [output, setOutput] = useState('');
|
|
8
|
+
const [a, setA] = useState('5');
|
|
9
|
+
const [b, setB] = useState('7');
|
|
10
|
+
const [n, setN] = useState('10');
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
let cancelled = false;
|
|
14
|
+
(async () => {
|
|
15
|
+
try {
|
|
16
|
+
await loadGoWasm('/wasm/example.wasm');
|
|
17
|
+
if (cancelled) return;
|
|
18
|
+
batchUpdates(() => {
|
|
19
|
+
setReady(true);
|
|
20
|
+
setStatus('✅ Go WASM module loaded!');
|
|
21
|
+
setOutput('Go functions available: goAdd, goMultiply, goFibonacci, goProcessArray');
|
|
22
|
+
});
|
|
23
|
+
} catch (e: any) {
|
|
24
|
+
if (cancelled) return;
|
|
25
|
+
setStatus('❌ Failed to load WASM: ' + e.message);
|
|
26
|
+
setOutput('Run `npm run build:wasm` (requires Go) and ensure /wasm/example.wasm is served.');
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
29
|
+
return () => { cancelled = true; };
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
const call = (fn: string, ...args: any[]) => (window as any)[fn](...args);
|
|
33
|
+
|
|
34
|
+
const add = () => setOutput(`goAdd(${a}, ${b}) = ${call('goAdd', parseFloat(a), parseFloat(b))}`);
|
|
35
|
+
const multiply = () => setOutput(`goMultiply(${a}, ${b}) = ${call('goMultiply', parseFloat(a), parseFloat(b))}`);
|
|
36
|
+
const fib = () => setOutput(`goFibonacci(${n}) = ${call('goFibonacci', parseInt(n, 10))}`);
|
|
37
|
+
const processArr = () => {
|
|
38
|
+
const result = call('goProcessArray', [1, 2, 3, 4, 5]);
|
|
39
|
+
setOutput(`goProcessArray([1,2,3,4,5]) = [${Array.from(result).join(', ')}]`);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const disabled = !ready;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="bg-white rounded-lg shadow p-6 space-y-6">
|
|
46
|
+
<h1 className="text-3xl font-bold text-gray-900">Go WebAssembly Demo</h1>
|
|
47
|
+
<p className={`font-medium ${ready ? 'text-green-700' : 'text-amber-600'}`}>{status}</p>
|
|
48
|
+
<div className="grid md:grid-cols-2 gap-6">
|
|
49
|
+
<div className="p-4 border rounded-lg bg-gray-50">
|
|
50
|
+
<h3 className="font-semibold text-gray-700 mb-3">Arithmetic</h3>
|
|
51
|
+
<div className="flex items-center gap-2 mb-3">
|
|
52
|
+
<input value={a} onChange={(e: any) => setA(e.target.value)} className="w-20 px-2 py-1 border rounded" />
|
|
53
|
+
<input value={b} onChange={(e: any) => setB(e.target.value)} className="w-20 px-2 py-1 border rounded" />
|
|
54
|
+
</div>
|
|
55
|
+
<div className="flex gap-2">
|
|
56
|
+
<button onClick={add} disabled={disabled} className="px-3 py-1 bg-blue-600 text-white rounded disabled:opacity-40">Add</button>
|
|
57
|
+
<button onClick={multiply} disabled={disabled} className="px-3 py-1 bg-blue-600 text-white rounded disabled:opacity-40">Multiply</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<div className="p-4 border rounded-lg bg-gray-50">
|
|
61
|
+
<h3 className="font-semibold text-gray-700 mb-3">Fibonacci</h3>
|
|
62
|
+
<input value={n} onChange={(e: any) => setN(e.target.value)} className="w-24 px-2 py-1 border rounded mb-3" />
|
|
63
|
+
<div>
|
|
64
|
+
<button onClick={fib} disabled={disabled} className="px-3 py-1 bg-purple-600 text-white rounded disabled:opacity-40">Fibonacci(n)</button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<div className="p-4 border rounded-lg bg-gray-50">
|
|
69
|
+
<h3 className="font-semibold text-gray-700 mb-3">Arrays (Int32)</h3>
|
|
70
|
+
<button onClick={processArr} disabled={disabled} className="px-3 py-1 bg-green-600 text-white rounded disabled:opacity-40">
|
|
71
|
+
Process array
|
|
72
|
+
</button>
|
|
73
|
+
</div>
|
|
74
|
+
{output && (
|
|
75
|
+
<pre className="p-4 bg-gray-900 text-green-400 rounded-lg overflow-auto text-sm">
|
|
76
|
+
{output}
|
|
77
|
+
</pre>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
@tailwind base;
|
|
2
|
-
@tailwind components;
|
|
3
|
-
@tailwind utilities;
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
body {
|
|
6
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
|
|
7
|
+
'Open Sans', 'Helvetica Neue', sans-serif;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
code {
|
|
11
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
12
|
+
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
/** @type {import('tailwindcss').Config} */
|
|
2
|
-
|
|
3
|
-
content: [
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
plugins: [],
|
|
11
|
-
}
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
export default {
|
|
3
|
+
content: ['./index.html', './src/**/*.{ts,tsx,js,jsx}'],
|
|
4
|
+
theme: {
|
|
5
|
+
extend: {}
|
|
6
|
+
},
|
|
7
|
+
plugins: []
|
|
8
|
+
};
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "
|
|
5
|
-
"moduleResolution": "
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"jsx": "preserve",
|
|
7
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"types": ["node"]
|
|
16
|
+
},
|
|
17
|
+
"include": ["src", "server.ts", "build.mjs", "build-wasm.js"]
|
|
18
|
+
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Request, Response } from 'express';
|
|
2
|
-
|
|
3
|
-
export const get = (req: Request, res: Response) => {
|
|
4
|
-
res.json({
|
|
5
|
-
message: 'Hello from the API!',
|
|
6
|
-
timestamp: new Date().toISOString()
|
|
7
|
-
});
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export const post = (req: Request, res: Response) => {
|
|
11
|
-
const { name = 'Guest' } = req.body;
|
|
12
|
-
|
|
13
|
-
res.json({
|
|
14
|
-
message: `Hello, ${name}!`,
|
|
15
|
-
timestamp: new Date().toISOString(),
|
|
16
|
-
receivedData: req.body
|
|
17
|
-
});
|
|
18
|
-
};
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { Request, Response } from 'express';
|
|
2
|
-
|
|
3
|
-
// Mock user database - same as in index.ts for simplicity
|
|
4
|
-
const users = [
|
|
5
|
-
{ id: 1, name: 'User 1', email: 'user1@example.com' },
|
|
6
|
-
{ id: 2, name: 'User 2', email: 'user2@example.com' },
|
|
7
|
-
{ id: 3, name: 'User 3', email: 'user3@example.com' }
|
|
8
|
-
];
|
|
9
|
-
|
|
10
|
-
export const get = (req: Request, res: Response) => {
|
|
11
|
-
const userId = parseInt(req.params.id);
|
|
12
|
-
|
|
13
|
-
if (isNaN(userId)) {
|
|
14
|
-
return res.status(400).json({ error: 'Invalid user ID' });
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const user = users.find(u => u.id === userId);
|
|
18
|
-
|
|
19
|
-
if (!user) {
|
|
20
|
-
return res.status(404).json({ error: 'User not found' });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
res.json(user);
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export const put = (req: Request, res: Response) => {
|
|
27
|
-
const userId = parseInt(req.params.id);
|
|
28
|
-
|
|
29
|
-
if (isNaN(userId)) {
|
|
30
|
-
return res.status(400).json({ error: 'Invalid user ID' });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const userIndex = users.findIndex(u => u.id === userId);
|
|
34
|
-
|
|
35
|
-
if (userIndex === -1) {
|
|
36
|
-
return res.status(404).json({ error: 'User not found' });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const { name, email } = req.body;
|
|
40
|
-
|
|
41
|
-
if (!name && !email) {
|
|
42
|
-
return res.status(400).json({ error: 'Name or email must be provided' });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Update the user
|
|
46
|
-
users[userIndex] = {
|
|
47
|
-
...users[userIndex],
|
|
48
|
-
...(name && { name }),
|
|
49
|
-
...(email && { email })
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
res.json(users[userIndex]);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export const delete_ = (req: Request, res: Response) => {
|
|
56
|
-
const userId = parseInt(req.params.id);
|
|
57
|
-
|
|
58
|
-
if (isNaN(userId)) {
|
|
59
|
-
return res.status(400).json({ error: 'Invalid user ID' });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const userIndex = users.findIndex(u => u.id === userId);
|
|
63
|
-
|
|
64
|
-
if (userIndex === -1) {
|
|
65
|
-
return res.status(404).json({ error: 'User not found' });
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Remove the user
|
|
69
|
-
const deletedUser = users[userIndex];
|
|
70
|
-
users.splice(userIndex, 1);
|
|
71
|
-
|
|
72
|
-
res.json({ success: true, deletedUser });
|
|
73
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Request, Response } from 'express';
|
|
2
|
-
|
|
3
|
-
// Mock user database
|
|
4
|
-
const users = [
|
|
5
|
-
{ id: 1, name: 'User 1', email: 'user1@example.com' },
|
|
6
|
-
{ id: 2, name: 'User 2', email: 'user2@example.com' },
|
|
7
|
-
{ id: 3, name: 'User 3', email: 'user3@example.com' }
|
|
8
|
-
];
|
|
9
|
-
|
|
10
|
-
export const get = (req: Request, res: Response) => {
|
|
11
|
-
// Return users without sensitive information
|
|
12
|
-
const safeUsers = users.map(({ id, name, email }) => ({ id, name, email }));
|
|
13
|
-
res.json(safeUsers);
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export const post = (req: Request, res: Response) => {
|
|
17
|
-
const { name, email } = req.body;
|
|
18
|
-
|
|
19
|
-
if (!name || !email) {
|
|
20
|
-
return res.status(400).json({ error: 'Name and email are required' });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// In a real app, validate data and save to database
|
|
24
|
-
const newUser = {
|
|
25
|
-
id: users.length + 1,
|
|
26
|
-
name,
|
|
27
|
-
email
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
users.push(newUser);
|
|
31
|
-
res.status(201).json(newUser);
|
|
32
|
-
};
|