Skip to content

Commit feb4441

Browse files
committed
✨ Add with-webassembly example
1 parent e123b3f commit feb4441

27 files changed

+964
-2
lines changed

Diff for: README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ Some examples in Nullstack full-stack framework 🗃️ 💖 🚀
1313
|[with-dotenv](./examples/with-dotenv)|[![Deploy with Vercel](https://vercel.com/button)](https://nullstack-new.vercel.app/with-dotenv?vercel)|[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://nullstack-new.vercel.app/with-dotenv)|
1414
|[with-mdx](./examples/with-mdx)|[![Deploy with Vercel](https://vercel.com/button)](https://nullstack-new.vercel.app/with-mdx?vercel)|[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://nullstack-new.vercel.app/with-mdx)|
1515
|[with-tailwind](./examples/with-tailwind)|[![Deploy with Vercel](https://vercel.com/button)](https://nullstack-new.vercel.app/with-tailwind?vercel)|[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://nullstack-new.vercel.app/with-tailwind)|
16-
|[with-videojs](./examples/with-videojs)|[![Deploy with Vercel](https://vercel.com/button)](https://nullstack-new.vercel.app/with-videojs?vercel)|[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://nullstack-new.vercel.app/with-videojs)|
16+
|[with-videojs](./examples/with-videojs)|[![Deploy with Vercel](https://vercel.com/button)](https://nullstack-new.vercel.app/with-videojs?vercel)|[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://nullstack-new.vercel.app/with-videojs)|
17+
|[with-webassembly](./examples/with-webassembly)|[![Deploy with Vercel](https://vercel.com/button)](https://nullstack-new.vercel.app/with-webassembly?vercel)|[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://nullstack-new.vercel.app/with-webassembly)|

Diff for: examples/with-webassembly/.eslintrc.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = {
2+
extends: 'plugin:nullstack/recommended',
3+
rules: {
4+
'nullstack/prettier': [
5+
'warn',
6+
{
7+
// More options at https://prettier.io/docs/en/options
8+
trailingComma: 'all',
9+
tabWidth: 2,
10+
semi: false,
11+
singleQuote: true,
12+
printWidth: 80,
13+
},
14+
{
15+
usePrettierrc: false,
16+
},
17+
],
18+
},
19+
}

Diff for: examples/with-webassembly/.gitignore

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
package-lock.json
8+
yarn.lock
9+
pnpm-lock.yaml
10+
11+
# testing
12+
/coverage
13+
14+
# misc
15+
.DS_Store
16+
*.pem
17+
18+
# debug
19+
npm-debug.log*
20+
yarn-debug.log*
21+
yarn-error.log*
22+
.pnpm-debug.log*
23+
24+
# local env files
25+
.env
26+
27+
# vercel
28+
.vercel
29+
30+
# bundle folders
31+
.development/
32+
.production/

Diff for: examples/with-webassembly/README.md

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# WebAssembly example
2+
3+
This example shows how to import WebAssembly files (`.wasm`) and use them inside of a Nullstack component in both server and client environments. In the case of this example we're showing [Rust](https://rust-lang.org/) and [Go](https://go.dev/) compiled to WebAssembly.
4+
5+
## Deploy your own
6+
7+
Deploy it now with [Vercel](https://vercel.com) or preview on [StackBlitz](https://stackblitz.com):
8+
9+
[![Deploy with Vercel](https://vercel.com/button)](https://nullstack-new.vercel.app/with-webassembly?vercel)
10+
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://nullstack-new.vercel.app/with-webassembly)
11+
12+
## How to use
13+
14+
Execute [`nulla create`](https://github.com/GuiDevloper/nulla) with [npm](https://docs.npmjs.com/cli/init):
15+
16+
```bash
17+
npx nulla create --example with-webassembly with-webassembly-app
18+
```
19+
20+
Then deploy it with [Vercel](https://github.com/GuiDevloper/nulla/blob/main/docs/en-US/deploy-vercel.md), [Heroku](https://github.com/GuiDevloper/nulla/blob/main/docs/en-US/deploy-heroku.md) or [Netlify](https://github.com/GuiDevloper/nulla/blob/main/docs/en-US/deploy-netlify.md).
21+
22+
## Compiling to WASM
23+
24+
### Rust
25+
26+
The WASM file is included in the example, but to compile your own Rust code you'll have to [install](https://www.rust-lang.org/learn/get-started) Rust.
27+
28+
To compile `src/rust/src/main.rs` to `src/rust/main.wasm` run:
29+
30+
```bash
31+
npm run build:rust
32+
# or
33+
yarn build:rust
34+
# or
35+
pnpm build:rust
36+
```
37+
38+
### Go
39+
40+
The WASM file is included in the example, but to compile your own Go code you'll have to install both [Go](https://go.dev/doc/install) and [TinyGo](https://tinygo.org/getting-started/install/) (used to generate optimized Go binaries).
41+
42+
To compile `src/go/src/main.rs` to `src/go/main.wasm` run:
43+
44+
```bash
45+
npm run build:go
46+
# or
47+
yarn build:go
48+
# or
49+
pnpm build:go
50+
```

Diff for: examples/with-webassembly/client.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Nullstack from 'nullstack'
2+
3+
import Application from './src/Application'
4+
5+
export default Nullstack.start(Application)

Diff for: examples/with-webassembly/package.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"private": true,
3+
"devDependencies": {
4+
"@types/express": "^4.17.17",
5+
"@types/node": "^20.2.3",
6+
"nullstack": "0.20.0"
7+
},
8+
"scripts": {
9+
"start": "npx nullstack start",
10+
"build": "npx nullstack build",
11+
"build:go": "tinygo build -o ./src/go/main.wasm -target=wasi ./src/go/src/main.go && node ./src/go/update-bindings.js",
12+
"build:rust": "rustc --target wasm32-unknown-unknown -O --crate-type=cdylib src/rust/src/main.rs -o src/rust/main.wasm",
13+
"vercel-build": "npm run build && npx nullstack-adapt-vercel"
14+
},
15+
"stackblitz": {
16+
"installDependencies": false,
17+
"startCommand": "npm i [email protected] -D && nullstack-adapt-babel && npm start",
18+
"compileTrigger": "save"
19+
}
20+
}

Diff for: examples/with-webassembly/public/favicon-96x96.png

930 Bytes
Loading

Diff for: examples/with-webassembly/server.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Nullstack, { NullstackServerContext } from 'nullstack'
2+
3+
import type { Request, Response } from 'express'
4+
5+
import Application from './src/Application'
6+
import { readWASMFile } from './src/go/load-wasm'
7+
8+
const context = Nullstack.start(Application) as NullstackServerContext
9+
10+
context.server.get('/:id.wasm', (req: Request, res: Response) => {
11+
res.send(readWASMFile(`${req.params.id}.wasm`))
12+
})
13+
14+
export default context

Diff for: examples/with-webassembly/src/Application.css

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
* {
2+
padding: 0;
3+
margin: 0;
4+
text-decoration: none;
5+
list-style: none;
6+
box-sizing: border-box;
7+
}
8+
9+
body {
10+
background-color: #111827;
11+
color: #fff;
12+
font-family: 'Roboto', sans-serif;
13+
}
14+
15+
main {
16+
display: flex;
17+
justify-content: space-evenly;
18+
margin-top: 5vh;
19+
}
20+
21+
a {
22+
color: #f15d9f;
23+
margin-left: .3em;
24+
}
25+
26+
.multiplier {
27+
margin-bottom: 2rem;
28+
}

Diff for: examples/with-webassembly/src/Application.tsx

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import './Application.css'
2+
import Nullstack from 'nullstack'
3+
4+
import MultiplyGo from './MultiplyGo'
5+
import MultiplyRust from './MultiplyRust'
6+
7+
class Application extends Nullstack {
8+
9+
render() {
10+
return (
11+
<main>
12+
<MultiplyGo />
13+
<MultiplyRust />
14+
</main>
15+
)
16+
}
17+
18+
}
19+
20+
export default Application

Diff for: examples/with-webassembly/src/MultiplyGo.tsx

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Nullstack from 'nullstack'
2+
3+
import loadWASM from './go/load-wasm'
4+
5+
class MultiplyGo extends Nullstack {
6+
7+
messageClient: number
8+
messageServer: number
9+
amount = 12
10+
static serverAmount = 12
11+
12+
static async multiplyWASM({ x }: { x: number }) {
13+
return (await loadWASM()).multiply(x, ++MultiplyGo.serverAmount)
14+
}
15+
16+
async hydrate() {
17+
await this.multiplyAtServer()
18+
await this.multiplyAtClient()
19+
}
20+
21+
async multiplyAtServer() {
22+
this.messageServer = await MultiplyGo.multiplyWASM({ x: 10 })
23+
}
24+
25+
async multiplyAtClient() {
26+
this.messageClient = (await loadWASM()).multiply(10, ++this.amount)
27+
}
28+
29+
render() {
30+
return (
31+
<div>
32+
<h1>Golang</h1>
33+
<section class="multiplier">
34+
<h3>Call at server</h3>
35+
<h2>{this.messageServer}</h2>
36+
<button onclick={this.multiplyAtServer}>Multiply!</button>
37+
</section>
38+
<section class="multiplier">
39+
<h3>Call at client</h3>
40+
<h2>{this.messageClient}</h2>
41+
<button onclick={this.multiplyAtClient}>Multiply!</button>
42+
</section>
43+
</div>
44+
)
45+
}
46+
47+
}
48+
49+
export default MultiplyGo

Diff for: examples/with-webassembly/src/MultiplyRust.tsx

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Nullstack from 'nullstack'
2+
3+
import loadWASM from './rust/load-wasm'
4+
5+
class MultiplyRust extends Nullstack {
6+
7+
messageClient: number
8+
messageServer: number
9+
amount = 12
10+
static serverAmount = 12
11+
12+
static async multiplyWASM({ x }: { x: number }) {
13+
return (await loadWASM()).multiply(x, ++MultiplyRust.serverAmount)
14+
}
15+
16+
async hydrate() {
17+
await this.multiplyAtServer()
18+
await this.multiplyAtClient()
19+
}
20+
21+
async multiplyAtServer() {
22+
this.messageServer = await MultiplyRust.multiplyWASM({ x: 10 })
23+
}
24+
25+
async multiplyAtClient(context?: never) {
26+
this.messageClient = (await loadWASM(context)).multiply(10, ++this.amount)
27+
}
28+
29+
render() {
30+
return (
31+
<div>
32+
<h1>Rust</h1>
33+
<section class="multiplier">
34+
<h3>Call at server</h3>
35+
<h2>{this.messageServer}</h2>
36+
<button onclick={this.multiplyAtServer}>Multiply!</button>
37+
</section>
38+
<section class="multiplier">
39+
<h3>Call at client</h3>
40+
<h2>{this.messageClient}</h2>
41+
<button onclick={this.multiplyAtClient}>Multiply!</button>
42+
</section>
43+
</div>
44+
)
45+
}
46+
47+
}
48+
49+
export default MultiplyRust

Diff for: examples/with-webassembly/src/go/load-wasm.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
4+
require('./wasm_exec')
5+
const GO = new Go()
6+
7+
// eslint-disable-next-line import/no-unresolved
8+
import mainWASM from './main.wasm?resource'
9+
import type { GoModuleExports } from './types'
10+
11+
let wasmBuffer
12+
let wasmModule
13+
14+
export function readWASMFile(filename: string) {
15+
const pureFilename = filename.replace(/\?resource$/, '')
16+
return fs.readFileSync(path.join(__dirname, pureFilename))
17+
}
18+
19+
async function saveBuffer() {
20+
if (window?.document) {
21+
wasmBuffer = await (await fetch(mainWASM)).arrayBuffer()
22+
} else {
23+
wasmBuffer = readWASMFile(mainWASM)
24+
}
25+
}
26+
27+
export default async function () {
28+
if (!wasmBuffer) await saveBuffer()
29+
if (!wasmModule) {
30+
wasmModule = await WebAssembly.instantiate(wasmBuffer, GO.importObject)
31+
GO.run(wasmModule.instance)
32+
}
33+
return wasmModule.instance.exports as GoModuleExports
34+
}

Diff for: examples/with-webassembly/src/go/main.wasm

76.4 KB
Binary file not shown.

Diff for: examples/with-webassembly/src/go/src/go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/GuiDevloper/tst-overlay
2+
3+
go 1.20

Diff for: examples/with-webassembly/src/go/src/main.go

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package main
2+
3+
func main() {
4+
println("Golang executed!")
5+
}
6+
7+
//export multiply
8+
func multiply(x int16, y int16) int16 {
9+
return x * y
10+
}

Diff for: examples/with-webassembly/src/go/types.d.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* eslint-disable */
2+
declare global {
3+
class Go {
4+
run(instance: WebAssembly.WebAssemblyInstantiatedSource['instance']): void
5+
importObject: Record<string, any>
6+
}
7+
}
8+
9+
export type GoModuleExports = {
10+
multiply(x: number, y: number): number
11+
}
12+
13+
export {}

Diff for: examples/with-webassembly/src/go/update-bindings.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const { spawnSync } = require('child_process')
2+
const fs = require('fs')
3+
const path = require('path')
4+
const GOROOT = spawnSync('tinygo', ['env', 'TINYGOROOT'], {
5+
encoding: 'utf8',
6+
}).stdout.replace('\n', '')
7+
8+
fs.copyFileSync(
9+
path.join(GOROOT, '/targets/wasm_exec.js'),
10+
path.join(__dirname, 'wasm_exec.js'),
11+
)

0 commit comments

Comments
 (0)