├── .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 |
--------------------------------------------------------------------------------