├── LICENSE.md ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── main.go ├── mainwasm.ts ├── mod.js └── wasm_exec.js /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present syumai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: generate 2 | generate: 3 | go generate 4 | 5 | .PHONY: run 6 | run: 7 | deno run --allow-net="0.0.0.0:8000,github.com,raw.githubusercontent.com" --allow-env=DENO_REGION ./mod.js 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deno-deploy-scale-image 2 | 3 | * image scaling app built for Deno Deploy using Go WebAssembly. 4 | - this app fetches image from [syumai's GitHub Repo](https://github.com/syumai/images). 5 | 6 | ## Requirements 7 | 8 | * tinygo 9 | * Deno 10 | * [deployctl](https://deno.com/deploy/docs/deployctl) 11 | 12 | ## Usage 13 | 14 | * Visit URL: https://scale-image.deno.dev/image?path=${imagePath}&width=${widthNumber} 15 | - Example: https://scale-image.deno.dev/image?path=landscape.jpg&width=200 16 | 17 | available images 18 | 19 | * syumai.png 20 | * landscape.jpg 21 | * ramen.jpg 22 | 23 | ## Development 24 | 25 | ### Run app locally 26 | 27 | ``` 28 | make run 29 | ``` 30 | 31 | ### Scale image 32 | 33 | show scaled image on browser 34 | 35 | `http://0.0.0.0:8080/image?path=landscape.jpg&width=200` 36 | 37 | ## Author 38 | 39 | syumai 40 | 41 | ## License 42 | 43 | MIT 44 | 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/syumai/deno-deploy-scale-image 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/syumai/denoio v0.0.0-20210409155630-29d4113b803d 7 | golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/syumai/denoio v0.0.0-20210409155630-29d4113b803d h1:u9P13ToIj4PqOZxNIW8LIbMpxz73gU+AmoyXQfyCJxI= 2 | github.com/syumai/denoio v0.0.0-20210409155630-29d4113b803d/go.mod h1:IQDVh7/KNJpYxn+Q0Drflye/6XBeNETNP3jqNG2mxSg= 3 | golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE= 4 | golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 5 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 6 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 7 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | //go:generate sh -c "tinygo build -opt=s -o main.wasm -target wasm ./ && cat main.wasm | deno run https://denopkg.com/syumai/binpack/mod.ts > mainwasm.ts && rm main.wasm" 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "image" 8 | "image/gif" 9 | "image/jpeg" 10 | "image/png" 11 | _ "image/png" 12 | "io" 13 | "syscall/js" 14 | 15 | "github.com/syumai/denoio" 16 | "golang.org/x/image/draw" 17 | ) 18 | 19 | var global = js.Global() 20 | 21 | func main() { 22 | scaleImageCallback := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { 23 | if len(args) < 2 { 24 | panic("two args must be given") 25 | } 26 | // convert Deno.Reader to Go's io.Reader. 27 | // - Deno.Reader must be used in Promise in Go side. 28 | r := denoio.NewReader(args[0]) 29 | width := args[1].Int() 30 | if width > 2048 { 31 | panic("width larger than 2048 is not allowed") 32 | } 33 | 34 | // create callback for Promise 35 | var cb js.Func 36 | cb = js.FuncOf(func(_ js.Value, args []js.Value) interface{} { 37 | resolve := args[0] 38 | go func() { 39 | defer cb.Release() 40 | rd, err := scaleImage(r, width) 41 | if err != nil { 42 | panic(err) 43 | } 44 | // convert Go's image reader (io.Reader) to Deno.Reader 45 | result := denoio.NewJSReader(rd) 46 | resolve.Invoke(result) 47 | }() 48 | return js.Undefined() 49 | }) 50 | return newPromise(cb) 51 | }) 52 | global.Set("scaleImage", scaleImageCallback) 53 | select {} 54 | } 55 | 56 | func scaleImage(rd io.Reader, width int) (io.Reader, error) { 57 | m, t, err := image.Decode(rd) 58 | if err != nil { 59 | return nil, err 60 | } 61 | b := m.Bounds() 62 | dst := image.NewRGBA(image.Rect(0, 0, width, int(float64(b.Dy())/float64(b.Dx())*float64(width)))) 63 | draw.BiLinear.Scale(dst, dst.Bounds(), m, m.Bounds(), draw.Over, nil) 64 | 65 | var buf bytes.Buffer 66 | switch t { 67 | case "jpeg": 68 | if err := jpeg.Encode(&buf, dst, &jpeg.Options{Quality: 100}); err != nil { 69 | return nil, err 70 | } 71 | case "gif": 72 | if err := gif.Encode(&buf, dst, nil); err != nil { 73 | return nil, err 74 | } 75 | case "png": 76 | if err := png.Encode(&buf, dst); err != nil { 77 | return nil, err 78 | } 79 | default: 80 | return nil, fmt.Errorf("unknown format: %s", t) 81 | } 82 | return &buf, nil 83 | } 84 | 85 | func newPromise(fn js.Func) js.Value { 86 | p := global.Get("Promise") 87 | return p.New(fn) 88 | } 89 | -------------------------------------------------------------------------------- /mod.js: -------------------------------------------------------------------------------- 1 | import mainwasm from "./mainwasm.ts"; 2 | import { Go } from "./wasm_exec.js"; 3 | import { decode } from "https://deno.land/std@0.139.0/encoding/base64.ts"; 4 | import { readerFromStreamReader, readableStreamFromReader } from "https://deno.land/std@0.139.0/io/streams.ts"; 5 | import { serve } from "https://deno.land/x/sift@0.5.0/mod.ts"; 6 | 7 | const bytes = decode(mainwasm); 8 | 9 | const urlBase = "https://github.com/syumai/images/raw/main/"; 10 | 11 | const handler = async (req, params) => { 12 | const reqUrl = new URL(req.url); 13 | const path = reqUrl.searchParams.get("path"); 14 | if (!path) { 15 | return new Response("path parameter must be given", { 16 | status: 400, 17 | }) 18 | } 19 | 20 | const widthStr = reqUrl.searchParams.get("width"); 21 | if (!widthStr) { 22 | return new Response("width parameter must be given", { 23 | status: 400, 24 | }) 25 | } 26 | 27 | let width; 28 | try { 29 | width = parseInt(widthStr, 10); 30 | } catch { 31 | return new Response("width parameter format is invalid", { 32 | status: 400, 33 | }) 34 | } 35 | 36 | if (width > 1200) { 37 | return new Response("width parameter must be smaller than 1200", { 38 | status: 400, 39 | }) 40 | } 41 | 42 | const go = new Go(); 43 | const result = await WebAssembly.instantiate(bytes, go.importObject); 44 | go.run(result.instance); 45 | 46 | const url = new URL(path, urlBase); 47 | const res = await fetch(url); 48 | 49 | const scaled = await scaleImage(readerFromStreamReader(res.body.getReader()), width); 50 | const stream = readableStreamFromReader(scaled); 51 | return new Response(stream, { 52 | status: 200, 53 | headers: { 54 | server: "denosr", 55 | "content-type": res.headers.get("content-type"), 56 | }, 57 | }); 58 | }; 59 | 60 | serve({ 61 | "/image": handler, 62 | }); 63 | -------------------------------------------------------------------------------- /wasm_exec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // This file has been modified for use by the TinyGo compiler. 6 | 7 | export let Go; 8 | 9 | (() => { 10 | // Map multiple JavaScript environments to a single common API, 11 | // preferring web standards over Node.js API. 12 | // 13 | // Environments considered: 14 | // - Browsers 15 | // - Node.js 16 | // - Electron 17 | // - Parcel 18 | 19 | if (typeof global !== "undefined") { 20 | // global already exists 21 | } else if (typeof window !== "undefined") { 22 | window.global = window; 23 | } else if (typeof self !== "undefined") { 24 | self.global = self; 25 | } else { 26 | throw new Error("cannot export Go (neither global, window nor self is defined)"); 27 | } 28 | 29 | if (!global.require && typeof require !== "undefined") { 30 | global.require = require; 31 | } 32 | 33 | if (!global.fs && global.require) { 34 | global.fs = require("fs"); 35 | } 36 | 37 | const enosys = () => { 38 | const err = new Error("not implemented"); 39 | err.code = "ENOSYS"; 40 | return err; 41 | }; 42 | 43 | if (!global.fs) { 44 | let outputBuf = ""; 45 | global.fs = { 46 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused 47 | writeSync(fd, buf) { 48 | outputBuf += decoder.decode(buf); 49 | const nl = outputBuf.lastIndexOf("\n"); 50 | if (nl != -1) { 51 | console.log(outputBuf.substr(0, nl)); 52 | outputBuf = outputBuf.substr(nl + 1); 53 | } 54 | return buf.length; 55 | }, 56 | write(fd, buf, offset, length, position, callback) { 57 | if (offset !== 0 || length !== buf.length || position !== null) { 58 | callback(enosys()); 59 | return; 60 | } 61 | const n = this.writeSync(fd, buf); 62 | callback(null, n); 63 | }, 64 | chmod(path, mode, callback) { callback(enosys()); }, 65 | chown(path, uid, gid, callback) { callback(enosys()); }, 66 | close(fd, callback) { callback(enosys()); }, 67 | fchmod(fd, mode, callback) { callback(enosys()); }, 68 | fchown(fd, uid, gid, callback) { callback(enosys()); }, 69 | fstat(fd, callback) { callback(enosys()); }, 70 | fsync(fd, callback) { callback(null); }, 71 | ftruncate(fd, length, callback) { callback(enosys()); }, 72 | lchown(path, uid, gid, callback) { callback(enosys()); }, 73 | link(path, link, callback) { callback(enosys()); }, 74 | lstat(path, callback) { callback(enosys()); }, 75 | mkdir(path, perm, callback) { callback(enosys()); }, 76 | open(path, flags, mode, callback) { callback(enosys()); }, 77 | read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, 78 | readdir(path, callback) { callback(enosys()); }, 79 | readlink(path, callback) { callback(enosys()); }, 80 | rename(from, to, callback) { callback(enosys()); }, 81 | rmdir(path, callback) { callback(enosys()); }, 82 | stat(path, callback) { callback(enosys()); }, 83 | symlink(path, link, callback) { callback(enosys()); }, 84 | truncate(path, length, callback) { callback(enosys()); }, 85 | unlink(path, callback) { callback(enosys()); }, 86 | utimes(path, atime, mtime, callback) { callback(enosys()); }, 87 | }; 88 | } 89 | 90 | if (!global.process) { 91 | global.process = { 92 | getuid() { return -1; }, 93 | getgid() { return -1; }, 94 | geteuid() { return -1; }, 95 | getegid() { return -1; }, 96 | getgroups() { throw enosys(); }, 97 | pid: -1, 98 | ppid: -1, 99 | umask() { throw enosys(); }, 100 | cwd() { throw enosys(); }, 101 | chdir() { throw enosys(); }, 102 | } 103 | } 104 | 105 | if (!global.crypto) { 106 | const nodeCrypto = require("crypto"); 107 | global.crypto = { 108 | getRandomValues(b) { 109 | nodeCrypto.randomFillSync(b); 110 | }, 111 | }; 112 | } 113 | 114 | if (!global.performance) { 115 | global.performance = { 116 | now() { 117 | const [sec, nsec] = process.hrtime(); 118 | return sec * 1000 + nsec / 1000000; 119 | }, 120 | }; 121 | } 122 | 123 | if (!global.TextEncoder) { 124 | global.TextEncoder = require("util").TextEncoder; 125 | } 126 | 127 | if (!global.TextDecoder) { 128 | global.TextDecoder = require("util").TextDecoder; 129 | } 130 | 131 | // End of polyfills for common API. 132 | 133 | const encoder = new TextEncoder("utf-8"); 134 | const decoder = new TextDecoder("utf-8"); 135 | var logLine = []; 136 | 137 | Go = class { 138 | constructor() { 139 | this._callbackTimeouts = new Map(); 140 | this._nextCallbackTimeoutID = 1; 141 | 142 | const mem = () => { 143 | // The buffer may change when requesting more memory. 144 | return new DataView(this._inst.exports.memory.buffer); 145 | } 146 | 147 | const setInt64 = (addr, v) => { 148 | mem().setUint32(addr + 0, v, true); 149 | mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); 150 | } 151 | 152 | const getInt64 = (addr) => { 153 | const low = mem().getUint32(addr + 0, true); 154 | const high = mem().getInt32(addr + 4, true); 155 | return low + high * 4294967296; 156 | } 157 | 158 | const loadValue = (addr) => { 159 | const f = mem().getFloat64(addr, true); 160 | if (f === 0) { 161 | return undefined; 162 | } 163 | if (!isNaN(f)) { 164 | return f; 165 | } 166 | 167 | const id = mem().getUint32(addr, true); 168 | return this._values[id]; 169 | } 170 | 171 | const storeValue = (addr, v) => { 172 | const nanHead = 0x7FF80000; 173 | 174 | if (typeof v === "number") { 175 | if (isNaN(v)) { 176 | mem().setUint32(addr + 4, nanHead, true); 177 | mem().setUint32(addr, 0, true); 178 | return; 179 | } 180 | if (v === 0) { 181 | mem().setUint32(addr + 4, nanHead, true); 182 | mem().setUint32(addr, 1, true); 183 | return; 184 | } 185 | mem().setFloat64(addr, v, true); 186 | return; 187 | } 188 | 189 | switch (v) { 190 | case undefined: 191 | mem().setFloat64(addr, 0, true); 192 | return; 193 | case null: 194 | mem().setUint32(addr + 4, nanHead, true); 195 | mem().setUint32(addr, 2, true); 196 | return; 197 | case true: 198 | mem().setUint32(addr + 4, nanHead, true); 199 | mem().setUint32(addr, 3, true); 200 | return; 201 | case false: 202 | mem().setUint32(addr + 4, nanHead, true); 203 | mem().setUint32(addr, 4, true); 204 | return; 205 | } 206 | 207 | let id = this._ids.get(v); 208 | if (id === undefined) { 209 | id = this._idPool.pop(); 210 | if (id === undefined) { 211 | id = this._values.length; 212 | } 213 | this._values[id] = v; 214 | this._goRefCounts[id] = 0; 215 | this._ids.set(v, id); 216 | } 217 | this._goRefCounts[id]++; 218 | let typeFlag = 1; 219 | switch (typeof v) { 220 | case "string": 221 | typeFlag = 2; 222 | break; 223 | case "symbol": 224 | typeFlag = 3; 225 | break; 226 | case "function": 227 | typeFlag = 4; 228 | break; 229 | } 230 | mem().setUint32(addr + 4, nanHead | typeFlag, true); 231 | mem().setUint32(addr, id, true); 232 | } 233 | 234 | const loadSlice = (array, len, cap) => { 235 | return new Uint8Array(this._inst.exports.memory.buffer, array, len); 236 | } 237 | 238 | const loadSliceOfValues = (array, len, cap) => { 239 | const a = new Array(len); 240 | for (let i = 0; i < len; i++) { 241 | a[i] = loadValue(array + i * 8); 242 | } 243 | return a; 244 | } 245 | 246 | const loadString = (ptr, len) => { 247 | return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len)); 248 | } 249 | 250 | const timeOrigin = Date.now() - performance.now(); 251 | this.importObject = { 252 | wasi_snapshot_preview1: { 253 | // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write 254 | fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) { 255 | let nwritten = 0; 256 | if (fd == 1) { 257 | for (let iovs_i=0; iovs_i 0, // dummy 283 | fd_fdstat_get: () => 0, // dummy 284 | fd_seek: () => 0, // dummy 285 | "proc_exit": (code) => { 286 | if (global.process) { 287 | // Node.js 288 | process.exit(code); 289 | } else { 290 | // Can't exit in a browser. 291 | throw 'trying to exit with code ' + code; 292 | } 293 | }, 294 | random_get: (bufPtr, bufLen) => { 295 | crypto.getRandomValues(loadSlice(bufPtr, bufLen)); 296 | return 0; 297 | }, 298 | }, 299 | env: { 300 | // func ticks() float64 301 | "runtime.ticks": () => { 302 | return timeOrigin + performance.now(); 303 | }, 304 | 305 | // func sleepTicks(timeout float64) 306 | "runtime.sleepTicks": (timeout) => { 307 | // Do not sleep, only reactivate scheduler after the given timeout. 308 | setTimeout(this._inst.exports.go_scheduler, timeout); 309 | }, 310 | 311 | // func finalizeRef(v ref) 312 | "syscall/js.finalizeRef": (sp) => { 313 | // Note: TinyGo does not support finalizers so this should never be 314 | // called. 315 | console.error('syscall/js.finalizeRef not implemented'); 316 | }, 317 | 318 | // func stringVal(value string) ref 319 | "syscall/js.stringVal": (ret_ptr, value_ptr, value_len) => { 320 | const s = loadString(value_ptr, value_len); 321 | storeValue(ret_ptr, s); 322 | }, 323 | 324 | // func valueGet(v ref, p string) ref 325 | "syscall/js.valueGet": (retval, v_addr, p_ptr, p_len) => { 326 | let prop = loadString(p_ptr, p_len); 327 | let value = loadValue(v_addr); 328 | let result = Reflect.get(value, prop); 329 | storeValue(retval, result); 330 | }, 331 | 332 | // func valueSet(v ref, p string, x ref) 333 | "syscall/js.valueSet": (v_addr, p_ptr, p_len, x_addr) => { 334 | const v = loadValue(v_addr); 335 | const p = loadString(p_ptr, p_len); 336 | const x = loadValue(x_addr); 337 | Reflect.set(v, p, x); 338 | }, 339 | 340 | // func valueDelete(v ref, p string) 341 | "syscall/js.valueDelete": (v_addr, p_ptr, p_len) => { 342 | const v = loadValue(v_addr); 343 | const p = loadString(p_ptr, p_len); 344 | Reflect.deleteProperty(v, p); 345 | }, 346 | 347 | // func valueIndex(v ref, i int) ref 348 | "syscall/js.valueIndex": (ret_addr, v_addr, i) => { 349 | storeValue(ret_addr, Reflect.get(loadValue(v_addr), i)); 350 | }, 351 | 352 | // valueSetIndex(v ref, i int, x ref) 353 | "syscall/js.valueSetIndex": (v_addr, i, x_addr) => { 354 | Reflect.set(loadValue(v_addr), i, loadValue(x_addr)); 355 | }, 356 | 357 | // func valueCall(v ref, m string, args []ref) (ref, bool) 358 | "syscall/js.valueCall": (ret_addr, v_addr, m_ptr, m_len, args_ptr, args_len, args_cap) => { 359 | const v = loadValue(v_addr); 360 | const name = loadString(m_ptr, m_len); 361 | const args = loadSliceOfValues(args_ptr, args_len, args_cap); 362 | try { 363 | const m = Reflect.get(v, name); 364 | storeValue(ret_addr, Reflect.apply(m, v, args)); 365 | mem().setUint8(ret_addr + 8, 1); 366 | } catch (err) { 367 | storeValue(ret_addr, err); 368 | mem().setUint8(ret_addr + 8, 0); 369 | } 370 | }, 371 | 372 | // func valueInvoke(v ref, args []ref) (ref, bool) 373 | "syscall/js.valueInvoke": (ret_addr, v_addr, args_ptr, args_len, args_cap) => { 374 | try { 375 | const v = loadValue(v_addr); 376 | const args = loadSliceOfValues(args_ptr, args_len, args_cap); 377 | storeValue(ret_addr, Reflect.apply(v, undefined, args)); 378 | mem().setUint8(ret_addr + 8, 1); 379 | } catch (err) { 380 | storeValue(ret_addr, err); 381 | mem().setUint8(ret_addr + 8, 0); 382 | } 383 | }, 384 | 385 | // func valueNew(v ref, args []ref) (ref, bool) 386 | "syscall/js.valueNew": (ret_addr, v_addr, args_ptr, args_len, args_cap) => { 387 | const v = loadValue(v_addr); 388 | const args = loadSliceOfValues(args_ptr, args_len, args_cap); 389 | try { 390 | storeValue(ret_addr, Reflect.construct(v, args)); 391 | mem().setUint8(ret_addr + 8, 1); 392 | } catch (err) { 393 | storeValue(ret_addr, err); 394 | mem().setUint8(ret_addr+ 8, 0); 395 | } 396 | }, 397 | 398 | // func valueLength(v ref) int 399 | "syscall/js.valueLength": (v_addr) => { 400 | return loadValue(v_addr).length; 401 | }, 402 | 403 | // valuePrepareString(v ref) (ref, int) 404 | "syscall/js.valuePrepareString": (ret_addr, v_addr) => { 405 | const s = String(loadValue(v_addr)); 406 | const str = encoder.encode(s); 407 | storeValue(ret_addr, str); 408 | setInt64(ret_addr + 8, str.length); 409 | }, 410 | 411 | // valueLoadString(v ref, b []byte) 412 | "syscall/js.valueLoadString": (v_addr, slice_ptr, slice_len, slice_cap) => { 413 | const str = loadValue(v_addr); 414 | loadSlice(slice_ptr, slice_len, slice_cap).set(str); 415 | }, 416 | 417 | // func valueInstanceOf(v ref, t ref) bool 418 | "syscall/js.valueInstanceOf": (v_addr, t_addr) => { 419 | return loadValue(v_addr) instanceof loadValue(t_addr); 420 | }, 421 | 422 | // func copyBytesToGo(dst []byte, src ref) (int, bool) 423 | "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, source_addr) => { 424 | let num_bytes_copied_addr = ret_addr; 425 | let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable 426 | 427 | const dst = loadSlice(dest_addr, dest_len); 428 | const src = loadValue(source_addr); 429 | if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { 430 | mem().setUint8(returned_status_addr, 0); // Return "not ok" status 431 | return; 432 | } 433 | const toCopy = src.subarray(0, dst.length); 434 | dst.set(toCopy); 435 | setInt64(num_bytes_copied_addr, toCopy.length); 436 | mem().setUint8(returned_status_addr, 1); // Return "ok" status 437 | }, 438 | 439 | // copyBytesToJS(dst ref, src []byte) (int, bool) 440 | // Originally copied from upstream Go project, then modified: 441 | // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416 442 | "syscall/js.copyBytesToJS": (ret_addr, dest_addr, source_addr, source_len, source_cap) => { 443 | let num_bytes_copied_addr = ret_addr; 444 | let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable 445 | 446 | const dst = loadValue(dest_addr); 447 | const src = loadSlice(source_addr, source_len); 448 | if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { 449 | mem().setUint8(returned_status_addr, 0); // Return "not ok" status 450 | return; 451 | } 452 | const toCopy = src.subarray(0, dst.length); 453 | dst.set(toCopy); 454 | setInt64(num_bytes_copied_addr, toCopy.length); 455 | mem().setUint8(returned_status_addr, 1); // Return "ok" status 456 | }, 457 | } 458 | }; 459 | } 460 | 461 | async run(instance) { 462 | this._inst = instance; 463 | this._values = [ // JS values that Go currently has references to, indexed by reference id 464 | NaN, 465 | 0, 466 | null, 467 | true, 468 | false, 469 | global, 470 | this, 471 | ]; 472 | this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id 473 | this._ids = new Map(); // mapping from JS values to reference ids 474 | this._idPool = []; // unused ids that have been garbage collected 475 | this.exited = false; // whether the Go program has exited 476 | 477 | const mem = new DataView(this._inst.exports.memory.buffer) 478 | 479 | while (true) { 480 | const callbackPromise = new Promise((resolve) => { 481 | this._resolveCallbackPromise = () => { 482 | if (this.exited) { 483 | throw new Error("bad callback: Go program has already exited"); 484 | } 485 | setTimeout(resolve, 0); // make sure it is asynchronous 486 | }; 487 | }); 488 | this._inst.exports._start(); 489 | if (this.exited) { 490 | break; 491 | } 492 | await callbackPromise; 493 | } 494 | } 495 | 496 | _resume() { 497 | if (this.exited) { 498 | throw new Error("Go program has already exited"); 499 | } 500 | this._inst.exports.resume(); 501 | if (this.exited) { 502 | this._resolveExitPromise(); 503 | } 504 | } 505 | 506 | _makeFuncWrapper(id) { 507 | const go = this; 508 | return function () { 509 | const event = { id: id, this: this, args: arguments }; 510 | go._pendingEvent = event; 511 | go._resume(); 512 | return event.result; 513 | }; 514 | } 515 | } 516 | 517 | if ( 518 | global.require && 519 | global.require.main === module && 520 | global.process && 521 | global.process.versions && 522 | !global.process.versions.electron 523 | ) { 524 | if (process.argv.length != 3) { 525 | console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); 526 | process.exit(1); 527 | } 528 | 529 | const go = new Go(); 530 | WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { 531 | return go.run(result.instance); 532 | }).catch((err) => { 533 | console.error(err); 534 | process.exit(1); 535 | }); 536 | } 537 | })(); 538 | --------------------------------------------------------------------------------