Repository: zackradisic/go-playground-wasm Branch: master Commit: 9b10f9789f53 Files: 23 Total size: 15.8 KB Directory structure: gitextract_qgzyq9_k/ ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .gitmodules ├── .prettierignore ├── .prettierrc ├── README.md ├── lib/ │ ├── RunGo.ts │ ├── fibonacci.ts │ ├── go_syntax.ts │ └── load_wasm.ts ├── netlify.toml ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages/ │ ├── _app.ts │ └── index.tsx ├── postcss.config.js ├── public/ │ └── wasm.wasm ├── styles/ │ └── global.css ├── tailwind.config.js ├── tsconfig.json └── type/ └── types.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintignore ================================================ node_modules .next/ out_publish/ out_function/ ================================================ FILE: .eslintrc ================================================ { "env": { "browser": true, "es6": true, "node": true }, "extends": [ "react-app", "plugin:react/recommended", "plugin:react-hooks/recommended", "prettier-standard" ], "parserOptions": { "project": "./tsconfig.json" }, "plugins": [ "react", "@typescript-eslint", "react-hooks", "prettier", "simple-import-sort" ], "rules": { "react/jsx-no-undef": ["error", { "allowGlobals": true }], "no-use-before-define": "off", "react/no-children-prop": "off", "prettier/prettier": [ "error", { "endOfLine": "auto" } ], "jsx-a11y/anchor-is-valid": "off", "simple-import-sort/imports": "error", "simple-import-sort/exports": "error" } } ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env.local .env.development.local .env.test.local .env.production.local # vercel .vercel /out_publish/ /out_functions/ # Local Netlify folder .netlify .vscode/ ================================================ FILE: .gitmodules ================================================ [submodule "goscript"] path = goscript url = https://github.com/zackradisic/goscript branch = "wasm-experiment" ================================================ FILE: .prettierignore ================================================ node_modules .next/ out/ out_publish/ out_functions/ .netlify/ .vercel/ ================================================ FILE: .prettierrc ================================================ "prettier-config-standard" ================================================ FILE: README.md ================================================ # Go Playground WASM This is an experiment in trying to make a version of play.golang.org that runs completely in the browser by compiling [goscript](https://github.com/oxfeeefeee/goscript) (by [oxfeeefeee](https://github.com/oxfeeefeee)) to WASM. Most of Go's language features are supported, notably channels/goroutines/select. ## Building and running The wasm binary is pre-compiled and available in the `public/` directory so you should be good to go to if you just want to run the Next.js server. You will need to install node and yarn of course. ``` # Run in development mode yarn dev # Build and serve optimized production build yarn build yarn start ``` If you want to play around with modifying the wasm build, `cd` into `goscript/wasm` and you can mess around with the code. When you want to build the wasm binary run: ``` # Build wasm cargo build --release --target=wasm32-wasi # Copy into Next.js's public folder cp target/wasm32-wasi/release/wasm.wasm ../public ``` ### Disclaimer goscript only makes guarantees that the syntax will be identical to Go's, there are implementation details that will cause discrepancies from running actual Go code with the actual Go compiler. ================================================ FILE: lib/RunGo.ts ================================================ import { WasmAlloc, WasmDealloc, WasmRunFn } from '@type/types' import { WasmFs } from '@wasmer/wasmfs' const SCRIPT_PATH = '/script.gos' export class RunGo { memory: WebAssembly.Memory wasmFs: WasmFs runFn: WasmRunFn alloc: WasmAlloc dealloc: WasmDealloc scriptPathPtr: number constructor( memory: WebAssembly.Memory, wasmFs: WasmFs, runFn: WasmRunFn, alloc: WasmAlloc, dealloc: WasmDealloc ) { this.memory = memory this.wasmFs = wasmFs this.runFn = runFn this.alloc = alloc this.dealloc = dealloc // Write script path now since it's constant const strBuf = new TextEncoder().encode(SCRIPT_PATH) const ptr = this.alloc(strBuf.length) const memBuf = new Uint8Array(this.memory.buffer, ptr, strBuf.length) memBuf.set(strBuf) this.scriptPathPtr = ptr } run(code: string) { this.wasmFs.fs.writeFileSync(SCRIPT_PATH, new TextEncoder().encode(code)) console.log('Running') return this.runFn(this.scriptPathPtr) } } ================================================ FILE: lib/fibonacci.ts ================================================ export const fibonnaci = `package main import "fmt" func fibonacci(c, quit chan int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } func main() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 12; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) } ` ================================================ FILE: lib/go_syntax.ts ================================================ import { languages } from 'prismjs' export const goSyntax = languages.extend('clike', { string: { pattern: /(["'`])(?:\\[\s\S]|(?!\1)[^\\])*\1/, greedy: true }, keyword: /\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/, boolean: /\b(?:_|iota|nil|true|false)\b/, number: /(?:\b0x[a-f\d]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[-+]?\d+)?)i?/i, operator: /[*/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./, builtin: /\b(?:bool|byte|complex(?:64|128)|error|float(?:32|64)|rune|string|u?int(?:8|16|32|64)?|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(?:ln)?|real|recover)\b/ }) ================================================ FILE: lib/load_wasm.ts ================================================ /* eslint-disable camelcase */ import { WasmAlloc, WasmDealloc, WasmRunFn } from '@type/types' import { WASI } from '@wasmer/wasi' import { lowerI64Imports } from '@wasmer/wasm-transformer' import { WasmFs } from '@wasmer/wasmfs' import * as fflate from 'fflate' import * as path from 'isomorphic-path' import { RunGo } from './RunGo' const instantiateWasm = async (wasi: WASI) => { const binary = await (await fetch('/wasm.wasm')).arrayBuffer() const loweredBinary = await lowerI64Imports(new Uint8Array(binary)) const module = await WebAssembly.compile(loweredBinary) const imports = wasi.getImports(module) const instance = await WebAssembly.instantiate(module, { ...imports }) wasi.start(instance) return instance.exports } const setupFs = async ( fs: WasmFs, stdout: (input: string) => void, stderr: (input: string) => void ) => { // Fetch and write stdlib to FS const zipped = await (await fetch('/stdlib.zip')).arrayBuffer() const unzipped = fflate.unzipSync(new Uint8Array(zipped)) for (const [key, val] of Object.entries(unzipped)) { if (val.length !== 0) { console.log(key) const dir = path.dirname(key) if (!fs.fs.existsSync(dir)) { fs.fs.mkdirpSync(dir) } fs.fs.writeFileSync(key, val) } } // Intercept stdout and stderr const oldWrite = fs.fs.writeSync const textDecoder = new TextDecoder() // @ts-ignore fs.fs.writeSync = (fd, buffer, offset, length, position) => { if (fd === 1) { stdout(textDecoder.decode(buffer).replace('\n', '
')) return buffer.length } if (fd === 2) { stderr(textDecoder.decode(buffer).replace('\n', '
')) return buffer.length } return oldWrite(fd, buffer, offset, length, position) } } export const initWasm = async ( stdin: (input: string) => void, stdout: (inupt: string) => void ) => { const wasmFs = new WasmFs() await setupFs(wasmFs, stdin, stdout) const wasi = new WASI({ args: [], env: {}, bindings: { ...WASI.defaultBindings, fs: wasmFs.fs, // https://github.com/wasmerio/wasmer-js/issues/253 path: WASI.defaultBindings.path.default }, preopens: { '/': '/', '.': '.' } }) const exports = await instantiateWasm(wasi) const memory = exports.memory as WebAssembly.Memory const { run_go, alloc, dealloc } = exports as { run_go: WasmRunFn alloc: WasmAlloc dealloc: WasmDealloc } return new RunGo(memory, wasmFs, run_go, alloc, dealloc) } ================================================ FILE: netlify.toml ================================================ [build] command = "yarn build" ================================================ FILE: next-env.d.ts ================================================ /// /// /// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. ================================================ FILE: next.config.js ================================================ module.exports = { // Target must be serverless target: 'serverless', async headers() { // return [] return [ { source: '/', headers: [ { key: 'Cross-Origin-Opener-Policy', value: 'unsafe-none' }, { key: 'Cross-Origin-Embedder-Policy', value: 'unsafe-none' }, { key: 'Cross-Origin-Resource-Policy', value: 'cross-origin' } ] } ] }, } ================================================ FILE: package.json ================================================ { "name": "go-playground-wasm", "author": "zackradisic", "version": "1.0.0", "scripts": { "dev": "next", "build": "next build", "start": "next start", "type-check": "tsc", "lint": "eslint . --ext .ts,.tsx", "lint:fix": "yarn lint --fix", "prettier": "prettier --check **/*.ts*", "prettier:fix": "prettier --write **/*.ts*", "verify": "yarn prettier && yarn lint", "verify:fix": "yarn prettier:fix && yarn lint:fix", "add-article": "node scripts/addArticle.js" }, "dependencies": { "@headlessui/react": "^1.4.1", "@heroicons/react": "^1.0.1", "@tailwindcss/aspect-ratio": "^0.2.1", "@wasmer/wasi": "^0.12.0", "@wasmer/wasm-terminal": "^0.12.0", "@wasmer/wasm-transformer": "^0.12.0", "@wasmer/wasmfs": "^0.12.0", "autoprefixer": "^10.2.4", "classnames": "^2.3.1", "fflate": "^0.7.1", "isomorphic-path": "^2.0.1", "next": "latest", "postcss": "^8.2.4", "prismjs": "^1.25.0", "react": "^17.0.1", "react-dom": "^16.12.0", "react-markdown": "^7.0.0", "react-simple-code-editor": "^0.11.0", "tailwindcss": "^2.2.9" }, "devDependencies": { "@types/node": "^12.12.21", "@types/prismjs": "^1.16.6", "@types/react": "^16.9.16", "@types/react-dom": "^16.9.4", "@typescript-eslint/eslint-plugin": "^4.14.2", "@typescript-eslint/parser": "^4.14.2", "babel-eslint": "^10.1.0", "eslint": "^7.19.0", "eslint-config-prettier": "^7.2.0", "eslint-config-prettier-standard": "^3.0.1", "eslint-config-react-app": "^6.0.0", "eslint-config-standard": "^16.0.2", "eslint-plugin-flowtype": "^5.2.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.3.1", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-react": "^7.22.0", "eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-simple-import-sort": "^7.0.0", "eslint-plugin-standard": "^5.0.0", "husky": "^4.3.8", "prettier": "^2.2.1", "prettier-config-standard": "^1.0.1", "slugify": "^1.6.0", "typescript": "~4.3.5" }, "license": "MIT", "husky": { "hooks": { "pre-commit": "yarn prettier", "pre-push": "yarn verify" } } } ================================================ FILE: pages/_app.ts ================================================ import '@styles/global.css' import App from 'next/app' export default App ================================================ FILE: pages/index.tsx ================================================ // eslint-disable-next-line simple-import-sort/imports import { fibonnaci } from 'lib/fibonacci' import { goSyntax } from 'lib/go_syntax' import { initWasm } from 'lib/load_wasm' import { RunGo } from 'lib/RunGo' import { highlight } from 'prismjs' import 'prismjs/components/prism-clike' import 'prismjs/themes/prism-dark.css' // Example style, you can use another import React, { useEffect, useRef, useState } from 'react' import Editor from 'react-simple-code-editor' const Index = () => { const [code, setCode] = useState(fibonnaci) const [terminalText, setTerminalText] = useState( "Click 'Run' to get started! " ) const [loaded, setLoaded] = useState(false) const runGoRef = useRef(undefined) useEffect(() => { const run = async () => { const runGo = await initWasm( input => setTerminalText(text => text + input), input => setTerminalText(text => text + input) ) runGoRef.current = runGo setLoaded(true) } run() }, []) return (

Go playground WASM

setCode(code)} highlight={code => highlight(code, goSyntax, 'go')} padding={10} style={{ fontFamily: '"Fira code", "Fira Mono", monospace', fontSize: 12 }} />
) } export default Index ================================================ FILE: postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {} } } ================================================ FILE: styles/global.css ================================================ /* ./styles/globals.css */ @tailwind base; @tailwind components; @tailwind utilities; html, body, body > div:first-child, div#__next, div#__next > div { height: 100%; } /* @font-face { font-family: 'Fira Code'; src: url('woff2/FiraCode-Light.woff2') format('woff2'), url("woff/FiraCode-Light.woff") format("woff"); font-weight: 300; font-style: normal; } */ @font-face { font-family: 'Fira Code'; src: url('/FiraCode-Regular.woff2') format('woff2'), url("/FiraCode-Regular.woff") format("woff"); font-weight: 400; font-style: normal; } /* @font-face { font-family: 'Fira Code'; src: url('woff2/FiraCode-Medium.woff2') format('woff2'), url("woff/FiraCode-Medium.woff") format("woff"); font-weight: 500; font-style: normal; } @font-face { font-family: 'Fira Code'; src: url('woff2/FiraCode-SemiBold.woff2') format('woff2'), url("woff/FiraCode-SemiBold.woff") format("woff"); font-weight: 600; font-style: normal; } @font-face { font-family: 'Fira Code'; src: url('woff2/FiraCode-Bold.woff2') format('woff2'), url("woff/FiraCode-Bold.woff") format("woff"); font-weight: 700; font-style: normal; } @font-face { font-family: 'Fira Code VF'; src: url('woff2/FiraCode-VF.woff2') format('woff2-variations'), url('woff/FiraCode-VF.woff') format('woff-variations'); font-weight: 300 700; font-style: normal; } */ ================================================ FILE: tailwind.config.js ================================================ const colors = require('tailwindcss/colors') module.exports = { purge: ['**/*.tsx'], mode: 'jit', darkMode: false, // or 'media' or 'class' theme: { extend: { colors: { orange: colors.orange, coolGray: colors.coolGray, blueGray: colors.blueGray } }, fontFamily: { sans: ['Fira code', 'Fira Mono', 'monospace'] } }, variants: { extend: {} }, plugins: [require('@tailwindcss/aspect-ratio')] } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "allowJs": true, "alwaysStrict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "isolatedModules": true, "jsx": "preserve", "lib": ["dom", "es2017"], "module": "esnext", "moduleResolution": "node", "noEmit": true, "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, "noUnusedParameters": true, "resolveJsonModule": true, "skipLibCheck": true, "strict": true, "target": "esnext", "baseUrl": ".", "paths": { "@styles*": ["styles*"], "@components*": ["components*"], "@type*": ["type*"], "@utils*": ["utils*"] } }, "exclude": ["node_modules", "goscript/"], "include": ["**/*.ts", "**/*.tsx"] } ================================================ FILE: type/types.ts ================================================ export type WasmRunFn = (pathPtr: number) => number export type WasmAlloc = (size: number) => number export type WasmDealloc = (ptr: number) => number