├── .editorconfig ├── .gitattributes ├── .gitignore ├── .npmignore ├── .vscode ├── settings.json └── tasks.json ├── License.md ├── README.md ├── ReleaseNotes.md ├── azure-pipelines.pr.yml ├── azure-pipelines.yml ├── example ├── node │ ├── package.json │ └── src │ │ ├── index.js │ │ └── main.go └── web │ ├── .babelrc │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── App.jsx │ ├── NumberInput.jsx │ ├── index.html │ ├── index.jsx │ ├── main.css │ └── main.go │ └── webpack.config.js ├── gobridge └── gobridge.go ├── lib └── wasm_exec.js ├── package-lock.json ├── package.json ├── src ├── global.d.ts ├── gobridge.ts └── index.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | ; editor configuration powered by http://editorconfig.org/ 2 | ; Top-most EditorConfig file 3 | root = true 4 | 5 | ; Windows newlines 6 | [*] 7 | end_of_line = crlf 8 | indent_style = space 9 | indent_size = 2 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.wasm 3 | dist 4 | .gocache -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | .editorconfig 3 | tsconfig.json 4 | .gitignore 5 | .gitattributes 6 | example -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.toolsEnvVars": { 3 | "GOOS": "js", 4 | "GOARCH": "wasm" 5 | } 6 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "type": "shell", 9 | "command": "npm run build", 10 | "problemMatcher": [ 11 | "$tsc" 12 | ], 13 | "group": { 14 | "kind": "build", 15 | "isDefault": true 16 | }, 17 | "windows": { 18 | "options": { 19 | "shell": { 20 | "executable": "powershell.exe" 21 | } 22 | } 23 | } 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018 Aaron Powell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status][build]][build-url] 2 | [![npm][npm]][npm-url] 3 | [![node][node]][node-url] 4 | 5 |
6 | 7 | 9 | 10 |

Golang WebAssembly Async Loader

11 |

Generates a WASM package from Golang and provides an async interface for working with it

12 |
13 | 14 |

Install

15 | 16 | ```bash 17 | npm install --save-dev golang-wasm-async-loader 18 | ``` 19 | 20 | This is a loader for [webpack](https://webpack.js.org/) that is used for generating [WebAssembly](https://webassembly.org/) (aka WASM) bundles from [Go](https://golang.org). 21 | 22 | The JavaScript bridge that is then generated for webpack will expose the WebAssembly functions as a Promise for interacting with. 23 | 24 | Note: It works with `Go 1.12` for now. Stay tuned for updates :) 25 | 26 | ## webpack config 27 | 28 | ```js 29 | module.exports = { 30 | ... 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.go/, 35 | use: ['golang-wasm-async-loader'] 36 | } 37 | ] 38 | }, 39 | node: { 40 | fs: 'empty' 41 | } 42 | }; 43 | ``` 44 | 45 | # Using in your code 46 | 47 | You import your Go code just like any other JavaScript module you might be working with. The webpack loader will export a default export that has the functions you registered in Go on it. Unfortunately it currently doesn't provide autocomplete of those function names as they are runtime defined. 48 | 49 | ```js 50 | import wasm from './main.go' 51 | 52 | async function init() { 53 | const result = await wasm.add(1, 2); 54 | console.log(result); 55 | 56 | const someValue = await wasm.someValue(); 57 | console.log(someValue); 58 | } 59 | ``` 60 | 61 | Here's the `main.go` file: 62 | 63 | ```go 64 | package main 65 | 66 | import ( 67 | "strconv" 68 | "syscall/js" 69 | "github.com/aaronpowell/webpack-golang-wasm-async-loader/gobridge" 70 | ) 71 | 72 | func add(i []js.Value) (interface{},error) { 73 | ret := 0 74 | 75 | for _, item := range i { 76 | val, _ := strconv.Atoi(item.String()) 77 | ret += val 78 | } 79 | 80 | return ret, nil 81 | } 82 | 83 | func main() { 84 | c := make(chan struct{}, 0) 85 | 86 | gobridge.RegisterCallback("add", add) 87 | gobridge.RegisterValue("someValue", "Hello World") 88 | 89 | <-c 90 | } 91 | ``` 92 | 93 | ## How does it work? 94 | 95 | As part of this repository a Go package has been created to improve the interop between the Go WASM runtime and work with the async pattern the loader defines. 96 | 97 | To do this a function is exported from the package called `RegisterCallback` which takes two arguments: 98 | 99 | * A `string` representing the name to register it as in JavaScript (and what you'll call it using) 100 | * The `func` to register as a callback 101 | * The `func` must has a signature of `(args js.Value) (interface{}, error)` so you can raise an error if you need 102 | 103 | If you want to register a static value that's been created from Go to be available in JavaScript you can do that with `RegisterValue`, which takes a name and a value. Values are converted to functions that return a Promise so they can be treated asynchronously like function invocations. 104 | 105 | In JavaScript a global object is registered as `__gobridge__` which the registrations happen against. 106 | 107 | ## Example 108 | 109 | You'll find an example of this in action in the [`example`](https://github.com/aaronpowell/webpack-golang-wasm-async-loader/tree/master/example) folder. 110 | 111 | # Licence 112 | 113 | MIT 114 | 115 | # Credit 116 | 117 | Aaron Powell 118 | 119 | [build]: https://aaronpowell.visualstudio.com/webpack-golang-wasm-async-loader/_apis/build/status/aaronpowell.webpack-golang-wasm-async-loader?branchName=master&label=Built%20on%20Azure%20🐱%E2%80%8D💻 120 | [build-url]: https://aaronpowell.visualstudio.com/webpack-golang-wasm-async-loader/_build/latest?definitionId=16?branchName=master 121 | 122 | [npm]: https://img.shields.io/npm/v/golang-wasm-async-loader.svg 123 | [npm-url]: https://npmjs.com/package/golang-wasm-async-loader 124 | 125 | [node]: https://img.shields.io/node/v/golang-wasm-async-loader.svg 126 | [node-url]: https://nodejs.org 127 | -------------------------------------------------------------------------------- /ReleaseNotes.md: -------------------------------------------------------------------------------- 1 | * Fixed problem where GOCACHE path wasn't absolute which Go 1.12.1 requires -------------------------------------------------------------------------------- /azure-pipelines.pr.yml: -------------------------------------------------------------------------------- 1 | pr: 2 | - master 3 | 4 | pool: 5 | vmImage: 'Ubuntu-16.04' 6 | 7 | steps: 8 | - task: NodeTool@0 9 | inputs: 10 | versionSpec: '10.x' 11 | displayName: 'Install Node.js' 12 | 13 | - script: | 14 | npm install 15 | npm run build 16 | displayName: 'npm install and build' 17 | 18 | - script: | 19 | npm pack 20 | displayName: 'Package for npm release' 21 | 22 | - task: CopyFiles@2 23 | inputs: 24 | sourceFolder: '$(Build.SourcesDirectory)/dist' 25 | contents: '**' 26 | targetFolder: $(Build.ArtifactStagingDirectory)/dist 27 | displayName: 'Copy dist output' 28 | 29 | - task: PublishBuildArtifacts@1 30 | inputs: 31 | pathtoPublish: '$(Build.ArtifactStagingDirectory)/dist' 32 | artifactName: dist 33 | displayName: 'Publish dist files as artifacts' 34 | 35 | - task: CopyFiles@2 36 | inputs: 37 | sourceFolder: '$(Build.SourcesDirectory)' 38 | contents: '*.tgz' 39 | targetFolder: $(Build.ArtifactStagingDirectory)/npm 40 | displayName: 'Copy npm package' 41 | 42 | - task: CopyFiles@2 43 | inputs: 44 | sourceFolder: '$(Build.SourcesDirectory)' 45 | contents: 'package.json' 46 | targetFolder: $(Build.ArtifactStagingDirectory)/npm 47 | displayName: 'Copy package.json' 48 | 49 | - task: PublishBuildArtifacts@1 50 | inputs: 51 | pathtoPublish: '$(Build.ArtifactStagingDirectory)/npm' 52 | artifactName: npm 53 | displayName: 'Publish npm artifact' 54 | 55 | - task: CopyFiles@2 56 | inputs: 57 | sourceFolder: '$(Build.SourcesDirectory)' 58 | contents: 'ReleaseNotes.md' 59 | targetFolder: $(Build.ArtifactStagingDirectory)/release-notes 60 | displayName: 'Copy Release Notes' 61 | 62 | - task: PublishBuildArtifacts@1 63 | inputs: 64 | pathtoPublish: '$(Build.ArtifactStagingDirectory)/release-notes' 65 | artifactName: release-notes 66 | displayName: 'Publish Release Notes artifact' -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - master 5 | 6 | pool: 7 | vmImage: 'Ubuntu-16.04' 8 | 9 | steps: 10 | - task: NodeTool@0 11 | inputs: 12 | versionSpec: '10.x' 13 | displayName: 'Install Node.js' 14 | 15 | - script: | 16 | npm install 17 | npm run build 18 | displayName: 'npm install and build' 19 | 20 | - script: | 21 | npm pack 22 | displayName: 'Package for npm release' 23 | 24 | - task: CopyFiles@2 25 | inputs: 26 | sourceFolder: '$(Build.SourcesDirectory)/dist' 27 | contents: '**' 28 | targetFolder: $(Build.ArtifactStagingDirectory)/dist 29 | displayName: 'Copy dist output' 30 | 31 | - task: PublishBuildArtifacts@1 32 | inputs: 33 | pathtoPublish: '$(Build.ArtifactStagingDirectory)/dist' 34 | artifactName: dist 35 | displayName: 'Publish dist files as artifacts' 36 | 37 | - task: CopyFiles@2 38 | inputs: 39 | sourceFolder: '$(Build.SourcesDirectory)' 40 | contents: '*.tgz' 41 | targetFolder: $(Build.ArtifactStagingDirectory)/npm 42 | displayName: 'Copy npm package' 43 | 44 | - task: CopyFiles@2 45 | inputs: 46 | sourceFolder: '$(Build.SourcesDirectory)' 47 | contents: 'package.json' 48 | targetFolder: $(Build.ArtifactStagingDirectory)/npm 49 | displayName: 'Copy package.json' 50 | 51 | - task: PublishBuildArtifacts@1 52 | inputs: 53 | pathtoPublish: '$(Build.ArtifactStagingDirectory)/npm' 54 | artifactName: npm 55 | displayName: 'Publish npm artifact' 56 | 57 | - task: CopyFiles@2 58 | inputs: 59 | sourceFolder: '$(Build.SourcesDirectory)' 60 | contents: 'ReleaseNotes.md' 61 | targetFolder: $(Build.ArtifactStagingDirectory)/release-notes 62 | displayName: 'Copy Release Notes' 63 | 64 | - task: PublishBuildArtifacts@1 65 | inputs: 66 | pathtoPublish: '$(Build.ArtifactStagingDirectory)/release-notes' 67 | artifactName: release-notes 68 | displayName: 'Publish Release Notes artifact' -------------------------------------------------------------------------------- /example/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "golang-wasm-async-loader-example-nodejs", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "src/index.js", 7 | "dependencies": { 8 | }, 9 | "devDependencies": { 10 | }, 11 | "scripts": { 12 | "predemo": "GOOS=js GOARCH=wasm go build -o ./src/main.wasm ./src/main.go", 13 | "demo": "node ./src/index.js" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /example/node/src/index.js: -------------------------------------------------------------------------------- 1 | const gobridge = require('../../../dist/gobridge'); 2 | const { join } = require('path'); 3 | require('../../../lib/wasm_exec.js'); 4 | require('isomorphic-fetch'); 5 | const { readFileSync } = require('fs'); 6 | 7 | global.requestAnimationFrame = global.setImmediate; 8 | 9 | let p = new Promise(resolve => 10 | resolve(readFileSync(join(__dirname, 'main.wasm'))) 11 | ); 12 | const wasm = gobridge.default(p); 13 | 14 | async function run() { 15 | let result = await wasm.add(1,2); 16 | 17 | console.log(result); 18 | }; 19 | 20 | run(); -------------------------------------------------------------------------------- /example/node/src/main.go: -------------------------------------------------------------------------------- 1 | //+ build js,wasm 2 | 3 | package main 4 | 5 | import ( 6 | "errors" 7 | "strconv" 8 | "syscall/js" 9 | 10 | "../../../gobridge" 11 | ) 12 | 13 | var global = js.Global() 14 | 15 | func add(this js.Value, args []js.Value) (interface{}, error) { 16 | ret := 0 17 | 18 | for _, item := range args { 19 | val, _ := strconv.Atoi(item.String()) 20 | ret += val 21 | } 22 | 23 | return ret, nil 24 | } 25 | 26 | func err(this js.Value, args []js.Value) (interface{}, error) { 27 | return nil, errors.New("This is an error") 28 | } 29 | 30 | func main() { 31 | c := make(chan struct{}, 0) 32 | println("Web Assembly is ready") 33 | gobridge.RegisterCallback("add", add) 34 | gobridge.RegisterCallback("raiseError", err) 35 | gobridge.RegisterValue("someValue", "Hello World") 36 | 37 | <-c 38 | } 39 | -------------------------------------------------------------------------------- /example/web/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /example/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "golang-wasm-async-loader-example-web", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "dist/index.js", 7 | "dependencies": { 8 | "@babel/polyfill": "^7.2.5", 9 | "react": "^16.7.0", 10 | "react-dom": "^16.7.0" 11 | }, 12 | "devDependencies": { 13 | "@babel/core": "^7.2.2", 14 | "@babel/preset-env": "^7.3.1", 15 | "@babel/preset-react": "^7.0.0", 16 | "babel-loader": "^8.0.5", 17 | "css-loader": "^2.1.0", 18 | "html-webpack-plugin": "^3.2.0", 19 | "source-map-loader": "^0.2.4", 20 | "style-loader": "^0.23.1", 21 | "webpack": "^4.29.0", 22 | "webpack-cli": "^3.2.1", 23 | "webpack-dev-server": "^3.1.14" 24 | }, 25 | "scripts": { 26 | "start": "webpack-dev-server", 27 | "build": "webpack" 28 | }, 29 | "keywords": [], 30 | "author": "", 31 | "license": "MIT" 32 | } 33 | -------------------------------------------------------------------------------- /example/web/src/App.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import wasm from './main.go'; 3 | import NumberInput from './NumberInput'; 4 | 5 | const { add, raiseError, someValue } = wasm; 6 | 7 | class App extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | value: [0, 0], 13 | result: '0', 14 | error: undefined 15 | }; 16 | } 17 | 18 | async componentWillMount() { 19 | let value = await someValue(); 20 | this.setState({ 21 | someValue: value 22 | }); 23 | } 24 | 25 | async updateValue(index, value) { 26 | let newValues = this.state.value.slice(); 27 | newValues[index] = value 28 | let result = await add(...newValues); 29 | this.setState({ value: newValues, result }); 30 | } 31 | 32 | async raiseError() { 33 | try { 34 | let _ = await raiseError(); 35 | } catch (e) { 36 | this.setState({ 37 | error: e 38 | }); 39 | } 40 | } 41 | 42 | render() { 43 | return ( 44 |
45 |

Enter a number in the box below, on change it will add all the numbers together. Click the button to add more input boxes.

46 | {this.state.value.map((value, index) => 47 | this.updateValue(index, i)} /> 48 | )} 49 | 50 |

Value now is {this.state.result}

51 |
52 |

Click this button to simulate an error:

53 | {this.state.error ?
54 |

{this.state.error}

55 | 56 |
: null } 57 |
58 |
59 |

Here's a static value: {this.state.someValue}

60 |
61 |
62 | ); 63 | } 64 | } 65 | 66 | export default App; -------------------------------------------------------------------------------- /example/web/src/NumberInput.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const NumberInput = ({ value, onChange }) => ( 4 | onChange(parseInt(e.target.value, 10))} /> 5 | ); 6 | 7 | export default NumberInput; -------------------------------------------------------------------------------- /example/web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Demo using Go WASM loader 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /example/web/src/index.jsx: -------------------------------------------------------------------------------- 1 | import "@babel/polyfill"; 2 | import './main.css'; 3 | import * as React from 'react'; 4 | import * as ReactDOM from 'react-dom'; 5 | import App from './App'; 6 | 7 | ReactDOM.render(, document.getElementById('main')); 8 | -------------------------------------------------------------------------------- /example/web/src/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | margin: 20px; 3 | } 4 | 5 | body { 6 | margin: 0 auto; 7 | width: 778px; 8 | } -------------------------------------------------------------------------------- /example/web/src/main.go: -------------------------------------------------------------------------------- 1 | //+ build js,wasm 2 | 3 | package main 4 | 5 | import ( 6 | "errors" 7 | "strconv" 8 | "syscall/js" 9 | 10 | "../../../gobridge" 11 | ) 12 | 13 | var global = js.Global() 14 | 15 | func add(this js.Value, args []js.Value) (interface{}, error) { 16 | ret := 0 17 | 18 | for _, item := range args { 19 | val, _ := strconv.Atoi(item.String()) 20 | ret += val 21 | } 22 | 23 | return ret, nil 24 | } 25 | 26 | func err(this js.Value, args []js.Value) (interface{}, error) { 27 | return nil, errors.New("This is an error") 28 | } 29 | 30 | func main() { 31 | c := make(chan struct{}, 0) 32 | println("Web Assembly is ready") 33 | gobridge.RegisterCallback("add", add) 34 | gobridge.RegisterCallback("raiseError", err) 35 | gobridge.RegisterValue("someValue", "Hello World") 36 | 37 | <-c 38 | } 39 | -------------------------------------------------------------------------------- /example/web/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebPackPlugin = require("html-webpack-plugin"); 3 | 4 | const htmlPlugin = new HtmlWebPackPlugin({ 5 | template: "./src/index.html", 6 | filename: "./index.html" 7 | }); 8 | 9 | module.exports = { 10 | entry: "./src/index.jsx", 11 | mode: "development", 12 | output: { 13 | path: path.resolve(__dirname, "dist"), 14 | filename: "bundle.js" 15 | }, 16 | devServer: { 17 | contentBase: path.join(__dirname, "dist"), 18 | compress: true, 19 | port: 9000 20 | }, 21 | devtool: "source-map", 22 | resolve: { 23 | extensions: [".go", ".jsx", ".js", ".json"] 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.jsx?$/, 29 | exclude: /node_modules/, 30 | use: ["source-map-loader", "babel-loader"] 31 | }, 32 | { 33 | test: /\.go/, 34 | use: [ 35 | { 36 | loader: path.join(__dirname, "..", "..", "dist", "index.js") 37 | } 38 | ] 39 | }, 40 | { 41 | test: /\.css$/, 42 | loader: ["style-loader", "css-loader"] 43 | } 44 | ] 45 | }, 46 | node: { 47 | fs: "empty" 48 | }, 49 | plugins: [htmlPlugin] 50 | }; 51 | -------------------------------------------------------------------------------- /gobridge/gobridge.go: -------------------------------------------------------------------------------- 1 | //+ build js,wasm 2 | 3 | package gobridge 4 | 5 | import ( 6 | "syscall/js" 7 | ) 8 | 9 | var bridgeRoot js.Value 10 | 11 | const ( 12 | bridgeJavaScriptName = "__gobridge__" 13 | ) 14 | 15 | func registrationWrapper(fn func(this js.Value, args []js.Value) (interface{}, error)) func(this js.Value, args []js.Value) interface{} { 16 | return func(this js.Value, args []js.Value) interface{} { 17 | cb := args[len(args)-1] 18 | 19 | ret, err := fn(this, args[:len(args)-1]) 20 | 21 | if err != nil { 22 | cb.Invoke(err.Error(), js.Null()) 23 | } else { 24 | cb.Invoke(js.Null(), ret) 25 | } 26 | 27 | return ret 28 | } 29 | } 30 | 31 | // RegisterCallback registers a Go function to be a callback used in JavaScript 32 | func RegisterCallback(name string, callback func(this js.Value, args []js.Value) (interface{}, error)) { 33 | bridgeRoot.Set(name, js.FuncOf(registrationWrapper(callback))) 34 | } 35 | 36 | // RegisterValue registers a static value output from Go for access in JavaScript 37 | func RegisterValue(name string, value interface{}) { 38 | bridgeRoot.Set(name, value) 39 | } 40 | 41 | func init() { 42 | global := js.Global() 43 | 44 | bridgeRoot = global.Get(bridgeJavaScriptName) 45 | } 46 | -------------------------------------------------------------------------------- /lib/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 | (() => { 6 | if (typeof global !== "undefined") { 7 | // global already exists 8 | } else if (typeof window !== "undefined") { 9 | window.global = window; 10 | } else if (typeof self !== "undefined") { 11 | self.global = self; 12 | } else { 13 | throw new Error("cannot export Go (neither global, window nor self is defined)"); 14 | } 15 | 16 | // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API). 17 | const isNodeJS = global.process && global.process.title === "node"; 18 | if (isNodeJS) { 19 | global.require = require; 20 | global.fs = require("fs"); 21 | 22 | const nodeCrypto = require("crypto"); 23 | global.crypto = { 24 | getRandomValues(b) { 25 | nodeCrypto.randomFillSync(b); 26 | }, 27 | }; 28 | 29 | global.performance = { 30 | now() { 31 | const [sec, nsec] = process.hrtime(); 32 | return sec * 1000 + nsec / 1000000; 33 | }, 34 | }; 35 | 36 | const util = require("util"); 37 | global.TextEncoder = util.TextEncoder; 38 | global.TextDecoder = util.TextDecoder; 39 | } else { 40 | let outputBuf = ""; 41 | global.fs = { 42 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused 43 | writeSync(fd, buf) { 44 | outputBuf += decoder.decode(buf); 45 | const nl = outputBuf.lastIndexOf("\n"); 46 | if (nl != -1) { 47 | console.log(outputBuf.substr(0, nl)); 48 | outputBuf = outputBuf.substr(nl + 1); 49 | } 50 | return buf.length; 51 | }, 52 | write(fd, buf, offset, length, position, callback) { 53 | if (offset !== 0 || length !== buf.length || position !== null) { 54 | throw new Error("not implemented"); 55 | } 56 | const n = this.writeSync(fd, buf); 57 | callback(null, n); 58 | }, 59 | open(path, flags, mode, callback) { 60 | const err = new Error("not implemented"); 61 | err.code = "ENOSYS"; 62 | callback(err); 63 | }, 64 | read(fd, buffer, offset, length, position, callback) { 65 | const err = new Error("not implemented"); 66 | err.code = "ENOSYS"; 67 | callback(err); 68 | }, 69 | fsync(fd, callback) { 70 | callback(null); 71 | }, 72 | }; 73 | } 74 | 75 | const encoder = new TextEncoder("utf-8"); 76 | const decoder = new TextDecoder("utf-8"); 77 | 78 | global.Go = class { 79 | constructor() { 80 | this.argv = ["js"]; 81 | this.env = {}; 82 | this.exit = (code) => { 83 | if (code !== 0) { 84 | console.warn("exit code:", code); 85 | } 86 | }; 87 | this._exitPromise = new Promise((resolve) => { 88 | this._resolveExitPromise = resolve; 89 | }); 90 | this._pendingEvent = null; 91 | this._scheduledTimeouts = new Map(); 92 | this._nextCallbackTimeoutID = 1; 93 | 94 | const mem = () => { 95 | // The buffer may change when requesting more memory. 96 | return new DataView(this._inst.exports.mem.buffer); 97 | } 98 | 99 | const setInt64 = (addr, v) => { 100 | mem().setUint32(addr + 0, v, true); 101 | mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); 102 | } 103 | 104 | const getInt64 = (addr) => { 105 | const low = mem().getUint32(addr + 0, true); 106 | const high = mem().getInt32(addr + 4, true); 107 | return low + high * 4294967296; 108 | } 109 | 110 | const loadValue = (addr) => { 111 | const f = mem().getFloat64(addr, true); 112 | if (f === 0) { 113 | return undefined; 114 | } 115 | if (!isNaN(f)) { 116 | return f; 117 | } 118 | 119 | const id = mem().getUint32(addr, true); 120 | return this._values[id]; 121 | } 122 | 123 | const storeValue = (addr, v) => { 124 | const nanHead = 0x7FF80000; 125 | 126 | if (typeof v === "number") { 127 | if (isNaN(v)) { 128 | mem().setUint32(addr + 4, nanHead, true); 129 | mem().setUint32(addr, 0, true); 130 | return; 131 | } 132 | if (v === 0) { 133 | mem().setUint32(addr + 4, nanHead, true); 134 | mem().setUint32(addr, 1, true); 135 | return; 136 | } 137 | mem().setFloat64(addr, v, true); 138 | return; 139 | } 140 | 141 | switch (v) { 142 | case undefined: 143 | mem().setFloat64(addr, 0, true); 144 | return; 145 | case null: 146 | mem().setUint32(addr + 4, nanHead, true); 147 | mem().setUint32(addr, 2, true); 148 | return; 149 | case true: 150 | mem().setUint32(addr + 4, nanHead, true); 151 | mem().setUint32(addr, 3, true); 152 | return; 153 | case false: 154 | mem().setUint32(addr + 4, nanHead, true); 155 | mem().setUint32(addr, 4, true); 156 | return; 157 | } 158 | 159 | let ref = this._refs.get(v); 160 | if (ref === undefined) { 161 | ref = this._values.length; 162 | this._values.push(v); 163 | this._refs.set(v, ref); 164 | } 165 | let typeFlag = 0; 166 | switch (typeof v) { 167 | case "string": 168 | typeFlag = 1; 169 | break; 170 | case "symbol": 171 | typeFlag = 2; 172 | break; 173 | case "function": 174 | typeFlag = 3; 175 | break; 176 | } 177 | mem().setUint32(addr + 4, nanHead | typeFlag, true); 178 | mem().setUint32(addr, ref, true); 179 | } 180 | 181 | const loadSlice = (addr) => { 182 | const array = getInt64(addr + 0); 183 | const len = getInt64(addr + 8); 184 | return new Uint8Array(this._inst.exports.mem.buffer, array, len); 185 | } 186 | 187 | const loadSliceOfValues = (addr) => { 188 | const array = getInt64(addr + 0); 189 | const len = getInt64(addr + 8); 190 | const a = new Array(len); 191 | for (let i = 0; i < len; i++) { 192 | a[i] = loadValue(array + i * 8); 193 | } 194 | return a; 195 | } 196 | 197 | const loadString = (addr) => { 198 | const saddr = getInt64(addr + 0); 199 | const len = getInt64(addr + 8); 200 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 201 | } 202 | 203 | const timeOrigin = Date.now() - performance.now(); 204 | this.importObject = { 205 | go: { 206 | // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) 207 | // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported 208 | // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). 209 | // This changes the SP, thus we have to update the SP used by the imported function. 210 | 211 | // func wasmExit(code int32) 212 | "runtime.wasmExit": (sp) => { 213 | const code = mem().getInt32(sp + 8, true); 214 | this.exited = true; 215 | delete this._inst; 216 | delete this._values; 217 | delete this._refs; 218 | this.exit(code); 219 | }, 220 | 221 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 222 | "runtime.wasmWrite": (sp) => { 223 | const fd = getInt64(sp + 8); 224 | const p = getInt64(sp + 16); 225 | const n = mem().getInt32(sp + 24, true); 226 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 227 | }, 228 | 229 | // func nanotime() int64 230 | "runtime.nanotime": (sp) => { 231 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 232 | }, 233 | 234 | // func walltime() (sec int64, nsec int32) 235 | "runtime.walltime": (sp) => { 236 | const msec = (new Date).getTime(); 237 | setInt64(sp + 8, msec / 1000); 238 | mem().setInt32(sp + 16, (msec % 1000) * 1000000, true); 239 | }, 240 | 241 | // func scheduleTimeoutEvent(delay int64) int32 242 | "runtime.scheduleTimeoutEvent": (sp) => { 243 | const id = this._nextCallbackTimeoutID; 244 | this._nextCallbackTimeoutID++; 245 | this._scheduledTimeouts.set(id, setTimeout( 246 | () => { this._resume(); }, 247 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early 248 | )); 249 | mem().setInt32(sp + 16, id, true); 250 | }, 251 | 252 | // func clearTimeoutEvent(id int32) 253 | "runtime.clearTimeoutEvent": (sp) => { 254 | const id = mem().getInt32(sp + 8, true); 255 | clearTimeout(this._scheduledTimeouts.get(id)); 256 | this._scheduledTimeouts.delete(id); 257 | }, 258 | 259 | // func getRandomData(r []byte) 260 | "runtime.getRandomData": (sp) => { 261 | crypto.getRandomValues(loadSlice(sp + 8)); 262 | }, 263 | 264 | // func stringVal(value string) ref 265 | "syscall/js.stringVal": (sp) => { 266 | storeValue(sp + 24, loadString(sp + 8)); 267 | }, 268 | 269 | // func valueGet(v ref, p string) ref 270 | "syscall/js.valueGet": (sp) => { 271 | const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); 272 | sp = this._inst.exports.getsp(); // see comment above 273 | storeValue(sp + 32, result); 274 | }, 275 | 276 | // func valueSet(v ref, p string, x ref) 277 | "syscall/js.valueSet": (sp) => { 278 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 279 | }, 280 | 281 | // func valueIndex(v ref, i int) ref 282 | "syscall/js.valueIndex": (sp) => { 283 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 284 | }, 285 | 286 | // valueSetIndex(v ref, i int, x ref) 287 | "syscall/js.valueSetIndex": (sp) => { 288 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 289 | }, 290 | 291 | // func valueCall(v ref, m string, args []ref) (ref, bool) 292 | "syscall/js.valueCall": (sp) => { 293 | try { 294 | const v = loadValue(sp + 8); 295 | const m = Reflect.get(v, loadString(sp + 16)); 296 | const args = loadSliceOfValues(sp + 32); 297 | const result = Reflect.apply(m, v, args); 298 | sp = this._inst.exports.getsp(); // see comment above 299 | storeValue(sp + 56, result); 300 | mem().setUint8(sp + 64, 1); 301 | } catch (err) { 302 | storeValue(sp + 56, err); 303 | mem().setUint8(sp + 64, 0); 304 | } 305 | }, 306 | 307 | // func valueInvoke(v ref, args []ref) (ref, bool) 308 | "syscall/js.valueInvoke": (sp) => { 309 | try { 310 | const v = loadValue(sp + 8); 311 | const args = loadSliceOfValues(sp + 16); 312 | const result = Reflect.apply(v, undefined, args); 313 | sp = this._inst.exports.getsp(); // see comment above 314 | storeValue(sp + 40, result); 315 | mem().setUint8(sp + 48, 1); 316 | } catch (err) { 317 | storeValue(sp + 40, err); 318 | mem().setUint8(sp + 48, 0); 319 | } 320 | }, 321 | 322 | // func valueNew(v ref, args []ref) (ref, bool) 323 | "syscall/js.valueNew": (sp) => { 324 | try { 325 | const v = loadValue(sp + 8); 326 | const args = loadSliceOfValues(sp + 16); 327 | const result = Reflect.construct(v, args); 328 | sp = this._inst.exports.getsp(); // see comment above 329 | storeValue(sp + 40, result); 330 | mem().setUint8(sp + 48, 1); 331 | } catch (err) { 332 | storeValue(sp + 40, err); 333 | mem().setUint8(sp + 48, 0); 334 | } 335 | }, 336 | 337 | // func valueLength(v ref) int 338 | "syscall/js.valueLength": (sp) => { 339 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 340 | }, 341 | 342 | // valuePrepareString(v ref) (ref, int) 343 | "syscall/js.valuePrepareString": (sp) => { 344 | const str = encoder.encode(String(loadValue(sp + 8))); 345 | storeValue(sp + 16, str); 346 | setInt64(sp + 24, str.length); 347 | }, 348 | 349 | // valueLoadString(v ref, b []byte) 350 | "syscall/js.valueLoadString": (sp) => { 351 | const str = loadValue(sp + 8); 352 | loadSlice(sp + 16).set(str); 353 | }, 354 | 355 | // func valueInstanceOf(v ref, t ref) bool 356 | "syscall/js.valueInstanceOf": (sp) => { 357 | mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); 358 | }, 359 | 360 | "debug": (value) => { 361 | console.log(value); 362 | }, 363 | } 364 | }; 365 | } 366 | 367 | async run(instance) { 368 | this._inst = instance; 369 | this._values = [ // TODO: garbage collection 370 | NaN, 371 | 0, 372 | null, 373 | true, 374 | false, 375 | global, 376 | this._inst.exports.mem, 377 | this, 378 | ]; 379 | this._refs = new Map(); 380 | this.exited = false; 381 | 382 | const mem = new DataView(this._inst.exports.mem.buffer) 383 | 384 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 385 | let offset = 4096; 386 | 387 | const strPtr = (str) => { 388 | let ptr = offset; 389 | new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0")); 390 | offset += str.length + (8 - (str.length % 8)); 391 | return ptr; 392 | }; 393 | 394 | const argc = this.argv.length; 395 | 396 | const argvPtrs = []; 397 | this.argv.forEach((arg) => { 398 | argvPtrs.push(strPtr(arg)); 399 | }); 400 | 401 | const keys = Object.keys(this.env).sort(); 402 | argvPtrs.push(keys.length); 403 | keys.forEach((key) => { 404 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 405 | }); 406 | 407 | const argv = offset; 408 | argvPtrs.forEach((ptr) => { 409 | mem.setUint32(offset, ptr, true); 410 | mem.setUint32(offset + 4, 0, true); 411 | offset += 8; 412 | }); 413 | 414 | this._inst.exports.run(argc, argv); 415 | if (this.exited) { 416 | this._resolveExitPromise(); 417 | } 418 | await this._exitPromise; 419 | } 420 | 421 | _resume() { 422 | if (this.exited) { 423 | throw new Error("Go program has already exited"); 424 | } 425 | this._inst.exports.resume(); 426 | if (this.exited) { 427 | this._resolveExitPromise(); 428 | } 429 | } 430 | 431 | _makeFuncWrapper(id) { 432 | const go = this; 433 | return function () { 434 | const event = { id: id, this: this, args: arguments }; 435 | go._pendingEvent = event; 436 | go._resume(); 437 | return event.result; 438 | }; 439 | } 440 | } 441 | })(); -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "golang-wasm-async-loader", 3 | "version": "0.3.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/anymatch": { 8 | "version": "1.3.0", 9 | "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.0.tgz", 10 | "integrity": "sha512-7WcbyctkE8GTzogDb0ulRAEw7v8oIS54ft9mQTU7PfM0hp5e+8kpa+HeQ7IQrFbKtJXBKcZ4bh+Em9dTw5L6AQ==" 11 | }, 12 | "@types/node": { 13 | "version": "10.12.18", 14 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", 15 | "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" 16 | }, 17 | "@types/tapable": { 18 | "version": "1.0.4", 19 | "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.4.tgz", 20 | "integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==" 21 | }, 22 | "@types/uglify-js": { 23 | "version": "3.0.4", 24 | "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz", 25 | "integrity": "sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==", 26 | "requires": { 27 | "source-map": "^0.6.1" 28 | } 29 | }, 30 | "@types/webassembly-js-api": { 31 | "version": "0.0.2", 32 | "resolved": "https://registry.npmjs.org/@types/webassembly-js-api/-/webassembly-js-api-0.0.2.tgz", 33 | "integrity": "sha512-htlxJRag6RUiMYUkS8Fjup+TMHO0VarpiF9MrqYaGJ0wXtIraQFz40rfA8VIeCiWy8sgpv3RLmigpgicG8fqGA==", 34 | "dev": true 35 | }, 36 | "@types/webpack": { 37 | "version": "4.4.23", 38 | "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.4.23.tgz", 39 | "integrity": "sha512-WswyG+2mRg0ul/ytPpCSWo+kOlVVPW/fKCBEVwqmPVC/2ffWEwhsCEQgnFbWDf8EWId2qGcpL623EjLfNTRk9A==", 40 | "requires": { 41 | "@types/anymatch": "*", 42 | "@types/node": "*", 43 | "@types/tapable": "*", 44 | "@types/uglify-js": "*", 45 | "source-map": "^0.6.0" 46 | } 47 | }, 48 | "source-map": { 49 | "version": "0.6.1", 50 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 51 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 52 | }, 53 | "typescript": { 54 | "version": "3.2.4", 55 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", 56 | "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", 57 | "dev": true 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "golang-wasm-async-loader", 3 | "version": "0.4.1", 4 | "description": "Golang WebAssembly loader for webpack that generates an async pattern for working with Go's WebAssembly output", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "build:watch": "tsc --watch" 9 | }, 10 | "keywords": [ 11 | "go", 12 | "golang", 13 | "wasm", 14 | "webassembly", 15 | "webpack" 16 | ], 17 | "author": "Aaron Powell", 18 | "license": "MIT", 19 | "devDependencies": { 20 | "@types/webassembly-js-api": "0.0.2", 21 | "typescript": "^3.2.4" 22 | }, 23 | "dependencies": { 24 | "@types/webpack": "^4.4.23" 25 | }, 26 | "peerDependencies": { 27 | "webpack": "^4.29.0" 28 | }, 29 | "engines": { 30 | "node": ">= 8.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module NodeJS { 2 | interface Global { 3 | __gobridge__: any 4 | Go: any 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/gobridge.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | __gobridge__: any 4 | 5 | Go: any 6 | } 7 | } 8 | 9 | const g = global || window || self 10 | 11 | if (!g.__gobridge__) { 12 | g.__gobridge__ = {}; 13 | } 14 | 15 | const bridge = g.__gobridge__; 16 | 17 | function sleep() { 18 | return new Promise(requestAnimationFrame); 19 | } 20 | 21 | export default function (getBytes: Promise) { 22 | let ready = false; 23 | 24 | async function init() { 25 | const go = new g.Go(); 26 | let bytes = await getBytes 27 | let result = await WebAssembly.instantiate(bytes, go.importObject); 28 | go.run(result.instance); 29 | ready = true; 30 | } 31 | 32 | init(); 33 | 34 | let proxy = new Proxy( 35 | {}, 36 | { 37 | get: (_, key) => { 38 | return (...args: any) => { 39 | return new Promise(async (resolve, reject) => { 40 | let run = () => { 41 | let cb = (err: any, ...msg: any[]) => (err ? reject(err) : resolve(...msg)); 42 | bridge[key].apply(undefined, [...args, cb]); 43 | }; 44 | 45 | while (!ready) { 46 | await sleep(); 47 | } 48 | 49 | if (!(key in bridge)) { 50 | reject(`There is nothing defined with the name "${key.toString()}"`); 51 | return; 52 | } 53 | 54 | if (typeof bridge[key] !== 'function') { 55 | resolve(bridge[key]); 56 | return; 57 | } 58 | 59 | run(); 60 | }); 61 | }; 62 | } 63 | } 64 | ); 65 | 66 | return proxy; 67 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as webpack from "webpack"; 2 | import {readFileSync, unlinkSync} from "fs"; 3 | import {basename, join} from "path"; 4 | import {execFile} from "child_process"; 5 | 6 | const proxyBuilder = (filename: string) => ` 7 | export default gobridge(fetch('${filename}').then(response => response.arrayBuffer())); 8 | `; 9 | 10 | const getGoBin = (root: string) => `${root}/bin/go`; 11 | 12 | function loader(this: webpack.loader.LoaderContext, contents: string) { 13 | const cb = this.async(); 14 | 15 | const opts = { 16 | env: { 17 | GOPATH: process.env.GOPATH, 18 | GOROOT: process.env.GOROOT, 19 | GOCACHE: join(__dirname, "./.gocache"), 20 | GOOS: "js", 21 | GOARCH: "wasm" 22 | } 23 | }; 24 | 25 | const goBin = getGoBin(opts.env.GOROOT); 26 | const outFile = `${this.resourcePath}.wasm`; 27 | const args = ["build", "-o", outFile, this.resourcePath]; 28 | 29 | execFile(goBin, args, opts, (err) => { 30 | if (err) { 31 | cb(err); 32 | return; 33 | } 34 | 35 | let out = readFileSync(outFile); 36 | unlinkSync(outFile); 37 | const emittedFilename = basename(this.resourcePath, ".go") + ".wasm"; 38 | this.emitFile(emittedFilename, out, null); 39 | 40 | cb( 41 | null, 42 | [ 43 | "require('!", 44 | join(__dirname, "..", "lib", "wasm_exec.js"), 45 | "');", 46 | "import gobridge from '", 47 | join(__dirname, "..", "dist", "gobridge.js"), 48 | "';", 49 | proxyBuilder(emittedFilename) 50 | ].join("") 51 | ); 52 | }); 53 | } 54 | 55 | export default loader; 56 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "outDir": "dist", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "noUnusedLocals": true, 9 | "noUnusedParameters": false, 10 | "lib": [ "es6", "dom" ], 11 | "declaration": true 12 | }, 13 | "include": [ 14 | "src/**/*.ts" 15 | ] 16 | } 17 | --------------------------------------------------------------------------------