This commit is contained in:
AR Fashion
2021-12-03 02:49:17 +08:00
committed by GitHub
commit 80744fe9de
38 changed files with 6824 additions and 0 deletions

29
LICENSE Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2021, AR Fashion
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

309
README.md Normal file
View File

@@ -0,0 +1,309 @@
## 更新日志
| 日期   | 内容 |
| -- | -- |
| 2021-12-03 | 新增微信小程序运行Go语言WebAssembly的示例。也包含网页运行Go语言的示例。 |
## 介绍
将Go语言编译为WebAssembly使用微信小程序的WXWebAssembly功能运行WebAssembly。
因为Go语言能快速开发WebAssembly所以使用Go语言能为小程序快速开发WebAssembly。
[WXWebAssembly官方文档](https://developers.weixin.qq.com/miniprogram/dev/framework/performance/wasm.html)
## 网页版
在线预览和小程序版使用的是相同的wasm文件。
[https://sanyuered.github.io/WeChat-MiniProgram-AR-WASM/go_dev/lesson1.html](https://sanyuered.github.io/WeChat-MiniProgram-AR-WASM/go_dev/lesson1.html)
## 小程序版
首页
![avatar](screenshot/1.jpg)
## Go调用小程序的函数
Go运行时会调用小程序的console.log()输出信息。
![avatar](screenshot/2-1.jpg)
在调试窗口的Console面板查看
![avatar](screenshot/2-2.jpg)
## 小程序调用Go的函数
每次点击按钮次数会增加1。
![avatar](screenshot/2-3.jpg)
## 小程序调用Go的函数, Go回调小程序。
输入内容是可编辑的自定义文本。
![avatar](screenshot/3-1.jpg)
点击按钮后输出内容会返回输入的自定义文本。Go语言一侧使用了time.Sleep()和协程模拟了等待2秒的异步方法调用。
![avatar](screenshot/3-2.jpg)
## 目录结构
/:小程序一侧的源代码
/go_devGo一侧的源代码
/node_dev Node一侧的源代码
/miniprogram_npm微信开发者工具将npm包text-encoder编译后的文件。
/project.config.json小程序打包时排除node_dev、go_dev等目录。
```javascript
"packOptions": {
"ignore": [
{
"type": "folder",
"value": "screenshot"
},
{
"type": "folder",
"value": "node_dev"
},
{
"type": "folder",
"value": "go_dev"
}
]
},
```
## Go一侧的源代码
Go一侧和网页版wasm的开发完全一样没有区别。但不要调用fmt.Println()。
```javascript
func main() {
// 创建通道
channel := make(chan int)
// Go调用js的console.log()方法,在开发者工具的Consol面板中查看。
console := js.Global().Get("console")
console.Call("log", "hello, world!")
// 其它代码省略
// 通道阻塞了main()方法
<-channel
```
网页端运行Go/go_dev/lesson1.html
```javascript
const go = new global.Go();
try {
const result = await WebAssembly.instantiateStreaming(fetch(wasm_url), go.importObject)
// 运行go程序的main()方法
await go.run(result.instance);
// 其它代码省略
} catch (err) {
console.error('initGo', err)
}
```
小程序端运行Go/package_lesson1/index.js
注意wasm_url必须是小程序打包的文件不能是网络文件和下载后保存的文件。wasm文件会占用小程序打包后的大小所以小程序每个分包中的wasm文件不能大于2MB。
```javascript
const go = new global.Go();
try {
const result = await WXWebAssembly.instantiate(wasm_url, go.importObject)
// 运行go程序的main()方法
await go.run(result.instance);
// 其它代码省略
} catch (err) {
console.error('initGo', err)
}
```
## Node一侧的源代码
Node一侧是将.wasm压缩为.wasm.br文件。小程序每个分包限制大小为2MB但支持.wasm.br压缩文件。使用brotli压缩.wasm文件能压缩到原始.wasm文件大小的20%至25%。
但是brotli压缩速度很慢。1.4MB的.wasm压缩为.wasm.br花费时间约6秒。文件更大的.wasm压缩时间更长可能花费几分钟。
```javascript
// 第三方的wasm版本brotli压缩和解压缩
var brotli = require('wasm-brotli');
// 压缩文件的示例
async function compressFile(){
const content = await readFileAsync('../go_dev/sample.wasm');
const compressedContent = await brotli.compress(content);
// 其它代码省略
}
```
## 技术难点
### 1、小程序分包大小不能超过2MBGo的Hello World示例编译为wasm文件大小就超过2MB了。
解决方法1、使用brotli工具压缩wasm压缩率约22%理论上支持最大8MB的未压缩wasm。
### 2、如何修改wasm_exec.js文件
解决方法修改的位置使用了变量IsWechat判断是否是小程序环境。具体修改请见源代码。
```javascript
const IsWechat = true;
```
### 3、小程序没有crypto.getRandomValues()方法。
解决方法:
```javascript
if (!global.crypto) {
global.crypto = {
getRandomValues(b) {
let byteRange = 256;
for (var i = 0; i < b.length; i++) {
b[i] = Math.floor(byteRange * Math.random());
}
},
};
}
```
### 4、小程序没有performance.now()方法。
解决方法:
```javascript
if (!global.performance) {
global.performance = {
now() {
return Date.now()
},
};
}
```
### 5、小程序没有TextDecoder和TextDecoder对象。
解决方法:
```javascript
if (!global.TextEncoder) {
global.TextEncoder = require("text-encoder").TextEncoder;
}
if (!global.TextDecoder) {
global.TextDecoder = require("text-encoder").TextDecoder;
}
```
## Go和小程序互操作
js.Global() 获取js运行环境的global对象。
```javascript
console := js.Global().Get("console")
```
js.ValueOf Go对象的值转换为js对象。
```javascript
func addTotal(this js.Value, args []js.Value) interface{} {
// 其它省略
return js.ValueOf(totalNum)
}
```
js.Value js对象的值转换为Go对象。
```javascript
func asyncAndCallbak(this js.Value, args []js.Value) interface{} {
// js输入参数
input := args[0].String()
// js回调函数
callback := args[1]
// 其它省略
}
```
js.FuncOf Go函数转换为js函数。
```javascript
// js调用Go的addTotal()方法
js.Global().Set("addTotal", js.FuncOf(addTotal))
```
Object.Call(function1,arg1,arg2) 在js对象上调用方法function1,输入参数arg1、arg2等。
```javascript
// Go调用js的console.log()方法,在开发者工具的Consol面板中查看。
console := js.Global().Get("console")
console.Call("log", "hello, world!")
```
function1.Invoke(arg1,arg2) 调用方法function1,输入参数arg1、arg2等。
```javascript
func asyncAndCallbak(this js.Value, args []js.Value) interface{} {
// js回调函数
callback := args[1]
// 运行js回调函数
callback.Invoke(result)
// 其它省略
}
```
Object.Get(prop1) 获取js对象的子对象、属性、方法等。
```javascript
console := js.Global().Get("console")
```
注意使用js.Global().Get("console")需要在小程序基础类库的global对象上增加console对象。
```javascript
async onReady() {
// 在小程序基础类库的global对象上增加console对象。
global.console = console
// 使用小程序类库的WXWebAssembly初始化Go运行环境。
await this.initGo()
},
```
Object.Set(prop1,value1)设置js对象的子对象、属性、方法等。
```javascript
// 在js运行环境设置Go的addTotal()方法
js.Global().Set("addTotal", js.FuncOf(addTotal))
```
## Go和JavaScript的变量类型
```javascript
| Go | JavaScript |
| ---------------------- | ---------------------- |
| js.Value | [its value] |
| js.Func | function |
| nil | null |
| bool | boolean |
| integers and floats | number |
| string | string |
| []interface{} | new array |
| map[string]interface{} | new object |
```
## 已知问题
### Q1syscall/js: call of Value.Invoke on undefined
A使用fmt.Println("hello,world")会发生该错误但使用fmt.Sprintf("%d", 123) 正常。不要使用fmt.Println()方法而使用console.log()方法向小程序输出调试信息。
### Q2LinkError: WebAssembly.instantiate(): Import #4 module="go"
A: wasm_exec.js中的方法有的能去掉有的不能去掉。 如果修改wasm_exec.js则importObject.go对象中的所有方法名称需要保留。
### Q3从Go调用小程序的wx.showModal({title:"123"})等API发生错误
A如果运行wx := js.Global().Get("wx");wx.Call("showModal", "");则可以显示一个空白的对话框。Go可以调用小程序API问题出在wx.Call("showModal", object)的输入参数是Object类型。
在网页端WebAssemblyGo能正常传递Object给网页。
在小程序端WebAssemblyGo无法正常传递Object给小程序。将方法的输入参数从Object类型暂时换成其他变量类型。

7
app.js Normal file
View File

@@ -0,0 +1,7 @@
//app.js
App({
onLaunch: function () {
},
globalData: {
}
})

20
app.json Normal file
View File

@@ -0,0 +1,20 @@
{
"pages": [
"pages/index"
],
"subpackages": [
{
"root": "package_lesson1",
"pages": [
"index"
]
}
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "微信小程序运行Go",
"navigationBarTextStyle": "black"
},
"sitemapLocation": "sitemap.json"
}

40
app.wxss Normal file
View File

@@ -0,0 +1,40 @@
/**app.wxss**/
@import "style/weui.wxss"
page{
background-color: #F8F8F8;
font-size: 16px;
font-family: -apple-system-font,Helvetica Neue,Helvetica,sans-serif;
}
.page__hd {
padding: 40px;
}
.page__bd {
padding-bottom: 40px;
}
.page__bd_spacing {
padding-left: 15px;
padding-right: 15px;
}
.page__ft{
padding-bottom: 10px;
text-align: center;
}
.page__title {
text-align: left;
font-size: 20px;
font-weight: 400;
}
.page__desc {
margin-top: 5px;
color: #888888;
text-align: left;
font-size: 14px;
}
.marginTop10{
margin-top: 10px;
}

40
go_dev/assets/app.css Normal file
View File

@@ -0,0 +1,40 @@
.page{
background-color: #F8F8F8;
font-size: 16px;
}
.page__hd {
padding: 40px;
}
.page__bd {
padding-bottom: 40px;
}
.page__bd_spacing {
padding-left: 15px;
padding-right: 15px;
}
.page__ft{
padding-bottom: 10px;
text-align: center;
}
.page__title {
text-align: left;
font-size: 20px;
font-weight: 400;
}
.page__desc {
margin-top: 5px;
color: #888888;
text-align: left;
font-size: 14px;
}
.marginTop10{
margin-top: 10px;
}
.loading{
text-align: center;
}

6
go_dev/assets/vue.min.js vendored Normal file

File diff suppressed because one or more lines are too long

626
go_dev/assets/wasm_exec.js Normal file
View File

@@ -0,0 +1,626 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
(() => {
// Map multiple JavaScript environments to a single common API,
// preferring web standards over Node.js API.
//
// Environments considered:
// - Browsers
// - Node.js
// - Electron
// - Parcel
// - Webpack
if (typeof global !== "undefined") {
// global already exists
} else if (typeof window !== "undefined") {
window.global = window;
} else if (typeof self !== "undefined") {
self.global = self;
} else {
throw new Error("cannot export Go (neither global, window nor self is defined)");
}
if (!global.require && typeof require !== "undefined") {
global.require = require;
}
if (!global.fs && global.require) {
const fs = require("fs");
if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) {
global.fs = fs;
}
}
const enosys = () => {
const err = new Error("not implemented");
err.code = "ENOSYS";
return err;
};
if (!global.fs) {
let outputBuf = "";
global.fs = {
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substr(0, nl));
outputBuf = outputBuf.substr(nl + 1);
}
return buf.length;
},
write(fd, buf, offset, length, position, callback) {
if (offset !== 0 || length !== buf.length || position !== null) {
callback(enosys());
return;
}
const n = this.writeSync(fd, buf);
callback(null, n);
},
chmod(path, mode, callback) { callback(enosys()); },
chown(path, uid, gid, callback) { callback(enosys()); },
close(fd, callback) { callback(enosys()); },
fchmod(fd, mode, callback) { callback(enosys()); },
fchown(fd, uid, gid, callback) { callback(enosys()); },
fstat(fd, callback) { callback(enosys()); },
fsync(fd, callback) { callback(null); },
ftruncate(fd, length, callback) { callback(enosys()); },
lchown(path, uid, gid, callback) { callback(enosys()); },
link(path, link, callback) { callback(enosys()); },
lstat(path, callback) { callback(enosys()); },
mkdir(path, perm, callback) { callback(enosys()); },
open(path, flags, mode, callback) { callback(enosys()); },
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
readdir(path, callback) { callback(enosys()); },
readlink(path, callback) { callback(enosys()); },
rename(from, to, callback) { callback(enosys()); },
rmdir(path, callback) { callback(enosys()); },
stat(path, callback) { callback(enosys()); },
symlink(path, link, callback) { callback(enosys()); },
truncate(path, length, callback) { callback(enosys()); },
unlink(path, callback) { callback(enosys()); },
utimes(path, atime, mtime, callback) { callback(enosys()); },
};
}
if (!global.process) {
global.process = {
getuid() { return -1; },
getgid() { return -1; },
geteuid() { return -1; },
getegid() { return -1; },
getgroups() { throw enosys(); },
pid: -1,
ppid: -1,
umask() { throw enosys(); },
cwd() { throw enosys(); },
chdir() { throw enosys(); },
}
}
if (!global.crypto && global.require) {
const nodeCrypto = require("crypto");
global.crypto = {
getRandomValues(b) {
nodeCrypto.randomFillSync(b);
},
};
}
if (!global.crypto) {
throw new Error("global.crypto is not available, polyfill required (getRandomValues only)");
}
if (!global.performance) {
global.performance = {
now() {
const [sec, nsec] = process.hrtime();
return sec * 1000 + nsec / 1000000;
},
};
}
if (!global.TextEncoder && global.require) {
global.TextEncoder = require("util").TextEncoder;
}
if (!global.TextEncoder) {
throw new Error("global.TextEncoder is not available, polyfill required");
}
if (!global.TextDecoder && global.require) {
global.TextDecoder = require("util").TextDecoder;
}
if (!global.TextDecoder) {
throw new Error("global.TextDecoder is not available, polyfill required");
}
// End of polyfills for common API.
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
global.Go = class {
constructor() {
this.argv = ["js"];
this.env = {};
this.exit = (code) => {
if (code !== 0) {
console.warn("exit code:", code);
}
};
this._exitPromise = new Promise((resolve) => {
this._resolveExitPromise = resolve;
});
this._pendingEvent = null;
this._scheduledTimeouts = new Map();
this._nextCallbackTimeoutID = 1;
const setInt64 = (addr, v) => {
this.mem.setUint32(addr + 0, v, true);
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
}
const getInt64 = (addr) => {
const low = this.mem.getUint32(addr + 0, true);
const high = this.mem.getInt32(addr + 4, true);
return low + high * 4294967296;
}
const loadValue = (addr) => {
const f = this.mem.getFloat64(addr, true);
if (f === 0) {
return undefined;
}
if (!isNaN(f)) {
return f;
}
const id = this.mem.getUint32(addr, true);
return this._values[id];
}
const storeValue = (addr, v) => {
const nanHead = 0x7FF80000;
if (typeof v === "number" && v !== 0) {
if (isNaN(v)) {
this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 0, true);
return;
}
this.mem.setFloat64(addr, v, true);
return;
}
if (v === undefined) {
this.mem.setFloat64(addr, 0, true);
return;
}
let id = this._ids.get(v);
if (id === undefined) {
id = this._idPool.pop();
if (id === undefined) {
id = this._values.length;
}
this._values[id] = v;
this._goRefCounts[id] = 0;
this._ids.set(v, id);
}
this._goRefCounts[id]++;
let typeFlag = 0;
switch (typeof v) {
case "object":
if (v !== null) {
typeFlag = 1;
}
break;
case "string":
typeFlag = 2;
break;
case "symbol":
typeFlag = 3;
break;
case "function":
typeFlag = 4;
break;
}
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
this.mem.setUint32(addr, id, true);
}
const loadSlice = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
}
const loadSliceOfValues = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
const a = new Array(len);
for (let i = 0; i < len; i++) {
a[i] = loadValue(array + i * 8);
}
return a;
}
const loadString = (addr) => {
const saddr = getInt64(addr + 0);
const len = getInt64(addr + 8);
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
}
const timeOrigin = Date.now() - performance.now();
this.importObject = {
go: {
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
// This changes the SP, thus we have to update the SP used by the imported function.
// func wasmExit(code int32)
"runtime.wasmExit": (sp) => {
sp >>>= 0;
const code = this.mem.getInt32(sp + 8, true);
this.exited = true;
delete this._inst;
delete this._values;
delete this._goRefCounts;
delete this._ids;
delete this._idPool;
this.exit(code);
},
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
"runtime.wasmWrite": (sp) => {
sp >>>= 0;
const fd = getInt64(sp + 8);
const p = getInt64(sp + 16);
const n = this.mem.getInt32(sp + 24, true);
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
},
// func resetMemoryDataView()
"runtime.resetMemoryDataView": (sp) => {
sp >>>= 0;
this.mem = new DataView(this._inst.exports.mem.buffer);
},
// func nanotime1() int64
"runtime.nanotime1": (sp) => {
sp >>>= 0;
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
},
// func walltime1() (sec int64, nsec int32)
"runtime.walltime1": (sp) => {
sp >>>= 0;
const msec = (new Date).getTime();
setInt64(sp + 8, msec / 1000);
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
},
// func scheduleTimeoutEvent(delay int64) int32
"runtime.scheduleTimeoutEvent": (sp) => {
sp >>>= 0;
const id = this._nextCallbackTimeoutID;
this._nextCallbackTimeoutID++;
this._scheduledTimeouts.set(id, setTimeout(
() => {
this._resume();
while (this._scheduledTimeouts.has(id)) {
// for some reason Go failed to register the timeout event, log and try again
// (temporary workaround for https://github.com/golang/go/issues/28975)
console.warn("scheduleTimeoutEvent: missed timeout event");
this._resume();
}
},
getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
));
this.mem.setInt32(sp + 16, id, true);
},
// func clearTimeoutEvent(id int32)
"runtime.clearTimeoutEvent": (sp) => {
sp >>>= 0;
const id = this.mem.getInt32(sp + 8, true);
clearTimeout(this._scheduledTimeouts.get(id));
this._scheduledTimeouts.delete(id);
},
// func getRandomData(r []byte)
"runtime.getRandomData": (sp) => {
sp >>>= 0;
crypto.getRandomValues(loadSlice(sp + 8));
},
// func finalizeRef(v ref)
"syscall/js.finalizeRef": (sp) => {
sp >>>= 0;
const id = this.mem.getUint32(sp + 8, true);
this._goRefCounts[id]--;
if (this._goRefCounts[id] === 0) {
const v = this._values[id];
this._values[id] = null;
this._ids.delete(v);
this._idPool.push(id);
}
},
// func stringVal(value string) ref
"syscall/js.stringVal": (sp) => {
sp >>>= 0;
storeValue(sp + 24, loadString(sp + 8));
},
// func valueGet(v ref, p string) ref
"syscall/js.valueGet": (sp) => {
sp >>>= 0;
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 32, result);
},
// func valueSet(v ref, p string, x ref)
"syscall/js.valueSet": (sp) => {
sp >>>= 0;
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
},
// func valueDelete(v ref, p string)
"syscall/js.valueDelete": (sp) => {
sp >>>= 0;
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
},
// func valueIndex(v ref, i int) ref
"syscall/js.valueIndex": (sp) => {
sp >>>= 0;
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
},
// valueSetIndex(v ref, i int, x ref)
"syscall/js.valueSetIndex": (sp) => {
sp >>>= 0;
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
},
// func valueCall(v ref, m string, args []ref) (ref, bool)
"syscall/js.valueCall": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const m = Reflect.get(v, loadString(sp + 16));
const args = loadSliceOfValues(sp + 32);
const result = Reflect.apply(m, v, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 56, result);
this.mem.setUint8(sp + 64, 1);
} catch (err) {
storeValue(sp + 56, err);
this.mem.setUint8(sp + 64, 0);
}
},
// func valueInvoke(v ref, args []ref) (ref, bool)
"syscall/js.valueInvoke": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.apply(v, undefined, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueNew(v ref, args []ref) (ref, bool)
"syscall/js.valueNew": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.construct(v, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueLength(v ref) int
"syscall/js.valueLength": (sp) => {
sp >>>= 0;
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
},
// valuePrepareString(v ref) (ref, int)
"syscall/js.valuePrepareString": (sp) => {
sp >>>= 0;
const str = encoder.encode(String(loadValue(sp + 8)));
storeValue(sp + 16, str);
setInt64(sp + 24, str.length);
},
// valueLoadString(v ref, b []byte)
"syscall/js.valueLoadString": (sp) => {
sp >>>= 0;
const str = loadValue(sp + 8);
loadSlice(sp + 16).set(str);
},
// func valueInstanceOf(v ref, t ref) bool
"syscall/js.valueInstanceOf": (sp) => {
sp >>>= 0;
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
},
// func copyBytesToGo(dst []byte, src ref) (int, bool)
"syscall/js.copyBytesToGo": (sp) => {
sp >>>= 0;
const dst = loadSlice(sp + 8);
const src = loadValue(sp + 32);
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
// func copyBytesToJS(dst ref, src []byte) (int, bool)
"syscall/js.copyBytesToJS": (sp) => {
sp >>>= 0;
const dst = loadValue(sp + 8);
const src = loadSlice(sp + 16);
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
"debug": (value) => {
console.log(value);
},
}
};
}
async run(instance) {
if (!(instance instanceof WebAssembly.Instance)) {
throw new Error("Go.run: WebAssembly.Instance expected");
}
this._inst = instance;
this.mem = new DataView(this._inst.exports.mem.buffer);
this._values = [ // JS values that Go currently has references to, indexed by reference id
NaN,
0,
null,
true,
false,
global,
this,
];
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
this._ids = new Map([ // mapping from JS values to reference ids
[0, 1],
[null, 2],
[true, 3],
[false, 4],
[global, 5],
[this, 6],
]);
this._idPool = []; // unused ids that have been garbage collected
this.exited = false; // whether the Go program has exited
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let offset = 4096;
const strPtr = (str) => {
const ptr = offset;
const bytes = encoder.encode(str + "\0");
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
offset += bytes.length;
if (offset % 8 !== 0) {
offset += 8 - (offset % 8);
}
return ptr;
};
const argc = this.argv.length;
const argvPtrs = [];
this.argv.forEach((arg) => {
argvPtrs.push(strPtr(arg));
});
argvPtrs.push(0);
const keys = Object.keys(this.env).sort();
keys.forEach((key) => {
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
});
argvPtrs.push(0);
const argv = offset;
argvPtrs.forEach((ptr) => {
this.mem.setUint32(offset, ptr, true);
this.mem.setUint32(offset + 4, 0, true);
offset += 8;
});
this._inst.exports.run(argc, argv);
if (this.exited) {
this._resolveExitPromise();
}
await this._exitPromise;
}
_resume() {
if (this.exited) {
throw new Error("Go program has already exited");
}
this._inst.exports.resume();
if (this.exited) {
this._resolveExitPromise();
}
}
_makeFuncWrapper(id) {
const go = this;
return function () {
const event = { id: id, this: this, args: arguments };
go._pendingEvent = event;
go._resume();
return event.result;
};
}
}
if (
typeof module !== "undefined" &&
global.require &&
global.require.main === module &&
global.process &&
global.process.versions &&
!global.process.versions.electron
) {
if (process.argv.length < 3) {
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
process.exit(1);
}
const go = new Go();
go.argv = process.argv.slice(2);
go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
go.exit = process.exit;
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
process.on("exit", (code) => { // Node.js exits if no event handler is pending
if (code === 0 && !go.exited) {
// deadlock, make Go print error and stack traces
go._pendingEvent = { id: 0 };
go._resume();
}
});
return go.run(result.instance);
}).catch((err) => {
console.error(err);
process.exit(1);
});
}
})();

4468
go_dev/assets/weui.css Normal file

File diff suppressed because one or more lines are too long

127
go_dev/lesson1.html Normal file
View File

@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Go WebAssembly</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="assets/weui.css">
<link rel="stylesheet" href="assets/app.css">
</head>
<body>
<div id="app" class="page">
<div class="page__bd">
<div class="weui-panel">
<div class="weui-panel__hd">Go调用小程序的函数</div>
<div class="weui-panel__bd">
<div class="weui-media-box">
<div v-text="notice"></div>
</div>
</div>
</div>
<div class="weui-panel">
<div class="weui-panel__hd">小程序调用Go的函数每次点击增加次数。</div>
<div class="weui-panel__bd">
<div class="weui-media-box">
点击了<span v-text="test_result1"></span>
<a href="javascript:" class="weui-btn weui-btn_primary marginTop10" v-on:click="btnRun1">点击1</a>
</div>
</div>
</div>
<div class="weui-panel">
<div class="weui-panel__hd">小程序调用Go的函数Go在等待2秒后回调小程序。</div>
<div class="weui-panel__bd">
<div class="weui-cells__group weui-cells__group_form weui-media-box">
<div class="weui-cells__title">表单</div>
<div class="weui-cells weui-cells_form">
<label for="js_input1" class="weui-cell">
<div class="weui-cell__hd"><span class="weui-label">输入内容</span></div>
<div class="weui-cell__bd">
<input id="js_input1" type="text" class="weui-input" v-model="inputText" />
</div>
</label>
<label class="weui-cell">
<div class="weui-cell__hd"><span class="weui-label">输出内容</span></div>
<div class="weui-cell__bd">
<div v-text="test_result2"></div>
</div>
</label>
<div class="loading" v-show="isLoading">
<i role="img" aria-label="加载中" class="weui-loading"></i> 请等待2秒
</div>
<a href="javascript:" class="weui-btn weui-btn_primary marginTop10"
v-on:click="btnRun2">点击2</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="assets/vue.min.js"></script>
<script src="assets/wasm_exec.js"></script>
<script>
const wasm_url = 'sample.wasm'
var app = new Vue({
el: '#app',
data: {
notice: '',
inputText: '你好Go语言。',
test_result1: '0',
test_result2: '',
isLoading: false,
},
methods: {
btnRun1() {
var _that = this;
var res = global.addTotal()
_that.test_result1 = res
console.log(res)
},
btnRun2() {
var _that = this;
_that.isLoading = true
global.asyncAndCallbak(_that.inputText, function (res) {
_that.isLoading = false
_that.test_result2 = _that.test_result2 + res + ' '
console.log(res)
})
},
async initGo() {
var _that = this;
const go = new global.Go();
try {
const result = await WebAssembly.instantiateStreaming(fetch(wasm_url), go.importObject)
var msg = 'Go初始化成功,在小程序调试窗口查看console的信息。'
_that.notice = msg
console.log('initGo', msg)
// 运行go程序的main()方法
await go.run(result.instance);
// 注意在go程序的main()方法退出之前,小程序不会运行到这个位置。
console.log('initGo', '运行完成')
} catch (err) {
console.error('initGo', err)
}
},
},
mounted: async function () {
// 在小程序基础类库的global对象上增加console对象。
global.console = console
// 使用小程序类库的WXWebAssembly初始化Go运行环境。
await this.initGo()
},
});
</script>
</body>
</html>

55
go_dev/main.go Normal file
View File

@@ -0,0 +1,55 @@
/*
使用方法在终端运行“go build -o sample.wasm main.go”会生成sample.wasm。
*/
package main
import (
"syscall/js"
"time"
)
var totalNum int = 0
// js调用Go。点击按钮一次增加一次计数。
func addTotal(this js.Value, args []js.Value) interface{} {
totalNum = totalNum + 1
return js.ValueOf(totalNum)
}
// js调用Go, Go回调js。Go等待2秒后才回调js。
func asyncAndCallbak(this js.Value, args []js.Value) interface{} {
// js输入参数
input := args[0].String()
// js回调函数
callback := args[1]
// 协程
go func() {
// 等待2秒
time.Sleep(2 * time.Second)
result := "回复:" + input
// 运行js回调函数
callback.Invoke(result)
}()
return nil
}
func main() {
// 创建通道
channel := make(chan int)
// 1.Go调用js的console.log()方法,在开发者工具的Consol面板中查看。
console := js.Global().Get("console")
console.Call("log", "hello, world!")
// 2.js调用Go的addTotal()方法
js.Global().Set("addTotal", js.FuncOf(addTotal))
// 3.js调用Go的asyncAndCallbak()方法, Go回调js。
js.Global().Set("asyncAndCallbak", js.FuncOf(asyncAndCallbak))
// 通道阻塞了main()方法
<-channel
// main()方法在结束之前,不会运行到这个位置。
console.Call("log", "exit")
}

BIN
go_dev/sample.wasm Normal file

Binary file not shown.

View File

@@ -0,0 +1,73 @@
module.exports = (function() {
var __MODS__ = {};
var __DEFINE__ = function(modId, func, req) { var m = { exports: {}, _tempexports: {} }; __MODS__[modId] = { status: 0, func: func, req: req, m: m }; };
var __REQUIRE__ = function(modId, source) { if(!__MODS__[modId]) return require(source); if(!__MODS__[modId].status) { var m = __MODS__[modId].m; m._exports = m._tempexports; var desp = Object.getOwnPropertyDescriptor(m, "exports"); if (desp && desp.configurable) Object.defineProperty(m, "exports", { set: function (val) { if(typeof val === "object" && val !== m._exports) { m._exports.__proto__ = val.__proto__; Object.keys(val).forEach(function (k) { m._exports[k] = val[k]; }); } m._tempexports = val }, get: function () { return m._tempexports; } }); __MODS__[modId].status = 1; __MODS__[modId].func(__MODS__[modId].req, m, m.exports); } return __MODS__[modId].m.exports; };
var __REQUIRE_WILDCARD__ = function(obj) { if(obj && obj.__esModule) { return obj; } else { var newObj = {}; if(obj != null) { for(var k in obj) { if (Object.prototype.hasOwnProperty.call(obj, k)) newObj[k] = obj[k]; } } newObj.default = obj; return newObj; } };
var __REQUIRE_DEFAULT__ = function(obj) { return obj && obj.__esModule ? obj.default : obj; };
__DEFINE__(1638331248814, function(require, module, exports) {
var utf8Encodings = [
'utf8',
'utf-8',
'unicode-1-1-utf-8'
];
function TextEncoder(encoding) {
if (utf8Encodings.indexOf(encoding) < 0 && typeof encoding !== 'undefined' && encoding != null) {
throw new RangeError('Invalid encoding type. Only utf-8 is supported');
} else {
this.encoding = 'utf-8';
this.encode = function(str) {
if (typeof str !== 'string') {
throw new TypeError('passed argument must be of tye string');
}
var binstr = unescape(encodeURIComponent(str)),
arr = new Uint8Array(binstr.length);
const split = binstr.split('');
for (let i = 0; i < split.length; i++) {
arr[i] = split[i].charCodeAt(0);
}
return arr;
};
}
}
function TextDecoder(encoding) {
if (utf8Encodings.indexOf(encoding) < 0 && typeof encoding !== 'undefined' && encoding != null) {
throw new RangeError('Invalid encoding type. Only utf-8 is supported');
}
else {
this.encoding = 'utf-8';
this.decode = function (view, options) {
if (typeof view === 'undefined') {
return '';
}
var stream = (typeof options !== 'undefined' && stream in options) ? options.stream : false;
if (typeof stream !== 'boolean') {
throw new TypeError('stream option must be boolean');
}
if (!ArrayBuffer.isView(view)) {
throw new TypeError('passed argument must be an array buffer view');
} else {
var arr = new Uint8Array(view.buffer, view.byteOffset, view.byteLength),
charArr = new Array(arr.length);
for (let i = 0; i < arr.length; i++) {
charArr[i] = String.fromCharCode(arr[i]);
}
return decodeURIComponent(escape(charArr.join('')));
}
}
}
}
module.exports = {
TextEncoder,
TextDecoder,
};
}, function(modId) {var map = {}; return __REQUIRE__(map[modId], modId); })
return __REQUIRE__(1638331248814);
})()
//miniprogram-npm-outsideDeps=[]
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["index.js"],"names":[],"mappings":";;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"index.js","sourcesContent":["var utf8Encodings = [\n 'utf8',\n 'utf-8',\n 'unicode-1-1-utf-8'\n];\n\nfunction TextEncoder(encoding) {\n if (utf8Encodings.indexOf(encoding) < 0 && typeof encoding !== 'undefined' && encoding != null) {\n throw new RangeError('Invalid encoding type. Only utf-8 is supported');\n } else {\n this.encoding = 'utf-8';\n this.encode = function(str) {\n if (typeof str !== 'string') {\n throw new TypeError('passed argument must be of tye string');\n }\n var binstr = unescape(encodeURIComponent(str)),\n arr = new Uint8Array(binstr.length);\n const split = binstr.split('');\n for (let i = 0; i < split.length; i++) {\n arr[i] = split[i].charCodeAt(0);\n }\n return arr;\n };\n }\n}\n\nfunction TextDecoder(encoding) {\n if (utf8Encodings.indexOf(encoding) < 0 && typeof encoding !== 'undefined' && encoding != null) {\n throw new RangeError('Invalid encoding type. Only utf-8 is supported');\n }\n else {\n this.encoding = 'utf-8';\n this.decode = function (view, options) {\n if (typeof view === 'undefined') {\n return '';\n }\n\n var stream = (typeof options !== 'undefined' && stream in options) ? options.stream : false;\n if (typeof stream !== 'boolean') {\n throw new TypeError('stream option must be boolean');\n }\n\n if (!ArrayBuffer.isView(view)) {\n throw new TypeError('passed argument must be an array buffer view');\n } else {\n var arr = new Uint8Array(view.buffer, view.byteOffset, view.byteLength),\n charArr = new Array(arr.length);\n for (let i = 0; i < arr.length; i++) {\n charArr[i] = String.fromCharCode(arr[i]);\n }\n return decodeURIComponent(escape(charArr.join('')));\n }\n }\n }\n}\n\nmodule.exports = {\n TextEncoder,\n TextDecoder,\n};\n"]}

45
node_dev/main.js Normal file
View File

@@ -0,0 +1,45 @@
/*
使用方法在终端运行“node main.js”会将../go_dev/sample.wasm压缩为../package_lesson1/assets/sample.wasm.br。
*/
// 第三方的wasm版本brotli压缩和解压缩安装方法npm install wasm-brotli
var brotli = require('wasm-brotli');
// node自带的库
var fs = require('fs');
// node自带的库
var util = require('util');
// 将方法包装成promise风格以使用async/await。
const writeFileAsync = util.promisify(fs.writeFile);
const readFileAsync = util.promisify(fs.readFile);
// 压缩的示例
async function compress(){
const content = Buffer.from('Hello, world!', 'utf8');
const compressedContent = await brotli.compress(content);
await writeFileAsync('./hello_world.txt.br', compressedContent);
console.log('compress completed')
}
// 解压缩的示例
async function decompress(){
const compressedContent = await readFileAsync('./hello_world.txt.br');
const contenArray = await brotli.decompress(compressedContent);
const content = Buffer.from(contenArray).toString('utf8');
console.log('decompress result',content)
}
// 压缩文件的示例
async function compressFile(){
console.log('compress start...')
var start = Date.now()
const content = await readFileAsync('../go_dev/sample.wasm');
const compressedContent = await brotli.compress(content);
await writeFileAsync('../package_lesson1/assets/sample.wasm.br', compressedContent);
var end = Date.now()-start
console.log('compress completed. cost',end/1000,'seconds.')
}
compressFile()

13
node_dev/package-lock.json generated Normal file
View File

@@ -0,0 +1,13 @@
{
"name": "node_dev",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"wasm-brotli": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/wasm-brotli/download/wasm-brotli-2.0.2.tgz",
"integrity": "sha1-9TF6+w0+Ll/pCma21h5Ck5KE8cc="
}
}
}

16
node_dev/package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "node_dev",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"start": "node main.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"wasm-brotli": "^2.0.2"
}
}

13
package-lock.json generated Normal file
View File

@@ -0,0 +1,13 @@
{
"name": "wechat-ar-wasm",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"text-encoder": {
"version": "0.0.4",
"resolved": "https://registry.nlark.com/text-encoder/download/text-encoder-0.0.4.tgz",
"integrity": "sha1-++5Mb7JR3QLi9A+Zg93WjFwWbiw="
}
}
}

15
package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "wechat-ar-wasm",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"text-encoder": "0.0.4"
},
"devDependencies": {}
}

Binary file not shown.

View File

@@ -0,0 +1,663 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
(() => {
// Map multiple JavaScript environments to a single common API,
// preferring web standards over Node.js API.
//
// Environments considered:
// - Browsers
// - Node.js
// - Electron
// - Parcel
// - Webpack
const IsWechat = true;
if (!IsWechat) {
if (typeof global !== "undefined") {
// global already exists
} else if (typeof window !== "undefined") {
window.global = window;
} else if (typeof self !== "undefined") {
self.global = self;
} else {
throw new Error("cannot export Go (neither global, window nor self is defined)");
}
if (!global.require && typeof require !== "undefined") {
global.require = require;
}
if (!global.fs && global.require) {
const fs = require("fs");
if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) {
global.fs = fs;
}
}
}
const enosys = () => {
const err = new Error("not implemented");
err.code = "ENOSYS";
return err;
};
if (!global.fs) {
let outputBuf = "";
global.fs = {
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substr(0, nl));
outputBuf = outputBuf.substr(nl + 1);
}
return buf.length;
},
write(fd, buf, offset, length, position, callback) {
if (offset !== 0 || length !== buf.length || position !== null) {
callback(enosys());
return;
}
const n = this.writeSync(fd, buf);
callback(null, n);
},
chmod(path, mode, callback) { callback(enosys()); },
chown(path, uid, gid, callback) { callback(enosys()); },
close(fd, callback) { callback(enosys()); },
fchmod(fd, mode, callback) { callback(enosys()); },
fchown(fd, uid, gid, callback) { callback(enosys()); },
fstat(fd, callback) { callback(enosys()); },
fsync(fd, callback) { callback(null); },
ftruncate(fd, length, callback) { callback(enosys()); },
lchown(path, uid, gid, callback) { callback(enosys()); },
link(path, link, callback) { callback(enosys()); },
lstat(path, callback) { callback(enosys()); },
mkdir(path, perm, callback) { callback(enosys()); },
open(path, flags, mode, callback) { callback(enosys()); },
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
readdir(path, callback) { callback(enosys()); },
readlink(path, callback) { callback(enosys()); },
rename(from, to, callback) { callback(enosys()); },
rmdir(path, callback) { callback(enosys()); },
stat(path, callback) { callback(enosys()); },
symlink(path, link, callback) { callback(enosys()); },
truncate(path, length, callback) { callback(enosys()); },
unlink(path, callback) { callback(enosys()); },
utimes(path, atime, mtime, callback) { callback(enosys()); },
};
}
if (!global.process) {
global.process = {
getuid() { return -1; },
getgid() { return -1; },
geteuid() { return -1; },
getegid() { return -1; },
getgroups() { throw enosys(); },
pid: -1,
ppid: -1,
umask() { throw enosys(); },
cwd() { throw enosys(); },
chdir() { throw enosys(); },
}
}
if (!IsWechat) {
if (!global.crypto && global.require) {
const nodeCrypto = require("crypto");
global.crypto = {
getRandomValues(b) {
nodeCrypto.randomFillSync(b);
},
};
}
if (!global.crypto) {
throw new Error("global.crypto is not available, polyfill required (getRandomValues only)");
}
} else {
if (!global.crypto) {
global.crypto = {
getRandomValues(b) {
let byteRange = 256;
for (var i = 0; i < b.length; i++) {
b[i] = Math.floor(byteRange * Math.random());
}
},
};
}
}
if (!IsWechat) {
if (!global.performance) {
global.performance = {
now() {
const [sec, nsec] = process.hrtime();
return sec * 1000 + nsec / 1000000;
},
};
}
} else {
if (!global.performance) {
global.performance = {
now() {
return Date.now()
},
};
}
}
if (!IsWechat) {
if (!global.TextEncoder && global.require) {
global.TextEncoder = require("util").TextEncoder;
}
if (!global.TextEncoder) {
throw new Error("global.TextEncoder is not available, polyfill required");
}
if (!global.TextDecoder && global.require) {
global.TextDecoder = require("util").TextDecoder;
}
if (!global.TextDecoder) {
throw new Error("global.TextDecoder is not available, polyfill required");
}
} else {
if (!global.TextEncoder) {
global.TextEncoder = require("text-encoder").TextEncoder;
}
if (!global.TextDecoder) {
global.TextDecoder = require("text-encoder").TextDecoder;
}
}
// End of polyfills for common API.
const encoder = new global.TextEncoder("utf-8");
const decoder = new global.TextDecoder("utf-8");
global.Go = class {
constructor() {
this.argv = ["js"];
this.env = {};
this.exit = (code) => {
if (code !== 0) {
console.warn("exit code:", code);
}
};
this._exitPromise = new Promise((resolve) => {
this._resolveExitPromise = resolve;
});
this._pendingEvent = null;
this._scheduledTimeouts = new Map();
this._nextCallbackTimeoutID = 1;
const setInt64 = (addr, v) => {
this.mem.setUint32(addr + 0, v, true);
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
}
const getInt64 = (addr) => {
const low = this.mem.getUint32(addr + 0, true);
const high = this.mem.getInt32(addr + 4, true);
return low + high * 4294967296;
}
const loadValue = (addr) => {
const f = this.mem.getFloat64(addr, true);
if (f === 0) {
return undefined;
}
if (!isNaN(f)) {
return f;
}
const id = this.mem.getUint32(addr, true);
return this._values[id];
}
const storeValue = (addr, v) => {
const nanHead = 0x7FF80000;
if (typeof v === "number" && v !== 0) {
if (isNaN(v)) {
this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 0, true);
return;
}
this.mem.setFloat64(addr, v, true);
return;
}
if (v === undefined) {
this.mem.setFloat64(addr, 0, true);
return;
}
let id = this._ids.get(v);
if (id === undefined) {
id = this._idPool.pop();
if (id === undefined) {
id = this._values.length;
}
this._values[id] = v;
this._goRefCounts[id] = 0;
this._ids.set(v, id);
}
this._goRefCounts[id]++;
let typeFlag = 0;
switch (typeof v) {
case "object":
if (v !== null) {
typeFlag = 1;
}
break;
case "string":
typeFlag = 2;
break;
case "symbol":
typeFlag = 3;
break;
case "function":
typeFlag = 4;
break;
}
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
this.mem.setUint32(addr, id, true);
}
const loadSlice = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
}
const loadSliceOfValues = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
const a = new Array(len);
for (let i = 0; i < len; i++) {
a[i] = loadValue(array + i * 8);
}
return a;
}
const loadString = (addr) => {
const saddr = getInt64(addr + 0);
const len = getInt64(addr + 8);
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
}
const timeOrigin = Date.now() - global.performance.now();
this.importObject = {
go: {
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
// This changes the SP, thus we have to update the SP used by the imported function.
// func wasmExit(code int32)
"runtime.wasmExit": (sp) => {
sp >>>= 0;
const code = this.mem.getInt32(sp + 8, true);
this.exited = true;
delete this._inst;
delete this._values;
delete this._goRefCounts;
delete this._ids;
delete this._idPool;
this.exit(code);
},
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
"runtime.wasmWrite": (sp) => {
sp >>>= 0;
const fd = getInt64(sp + 8);
const p = getInt64(sp + 16);
const n = this.mem.getInt32(sp + 24, true);
global.fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
},
// func resetMemoryDataView()
"runtime.resetMemoryDataView": (sp) => {
sp >>>= 0;
this.mem = new DataView(this._inst.exports.mem.buffer);
},
// func nanotime1() int64
"runtime.nanotime1": (sp) => {
sp >>>= 0;
setInt64(sp + 8, (timeOrigin + global.performance.now()) * 1000000);
},
// func walltime1() (sec int64, nsec int32)
"runtime.walltime1": (sp) => {
sp >>>= 0;
const msec = (new Date).getTime();
setInt64(sp + 8, msec / 1000);
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
},
// func scheduleTimeoutEvent(delay int64) int32
"runtime.scheduleTimeoutEvent": (sp) => {
sp >>>= 0;
const id = this._nextCallbackTimeoutID;
this._nextCallbackTimeoutID++;
this._scheduledTimeouts.set(id, setTimeout(
() => {
this._resume();
while (this._scheduledTimeouts.has(id)) {
// for some reason Go failed to register the timeout event, log and try again
// (temporary workaround for https://github.com/golang/go/issues/28975)
console.warn("scheduleTimeoutEvent: missed timeout event");
this._resume();
}
},
getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
));
this.mem.setInt32(sp + 16, id, true);
},
// func clearTimeoutEvent(id int32)
"runtime.clearTimeoutEvent": (sp) => {
sp >>>= 0;
const id = this.mem.getInt32(sp + 8, true);
clearTimeout(this._scheduledTimeouts.get(id));
this._scheduledTimeouts.delete(id);
},
// func getRandomData(r []byte)
"runtime.getRandomData": (sp) => {
sp >>>= 0;
global.crypto.getRandomValues(loadSlice(sp + 8));
},
// func finalizeRef(v ref)
"syscall/js.finalizeRef": (sp) => {
sp >>>= 0;
const id = this.mem.getUint32(sp + 8, true);
this._goRefCounts[id]--;
if (this._goRefCounts[id] === 0) {
const v = this._values[id];
this._values[id] = null;
this._ids.delete(v);
this._idPool.push(id);
}
},
// func stringVal(value string) ref
"syscall/js.stringVal": (sp) => {
sp >>>= 0;
storeValue(sp + 24, loadString(sp + 8));
},
// func valueGet(v ref, p string) ref
"syscall/js.valueGet": (sp) => {
sp >>>= 0;
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 32, result);
},
// func valueSet(v ref, p string, x ref)
"syscall/js.valueSet": (sp) => {
sp >>>= 0;
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
},
// func valueDelete(v ref, p string)
"syscall/js.valueDelete": (sp) => {
sp >>>= 0;
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
},
// func valueIndex(v ref, i int) ref
"syscall/js.valueIndex": (sp) => {
sp >>>= 0;
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
},
// valueSetIndex(v ref, i int, x ref)
"syscall/js.valueSetIndex": (sp) => {
sp >>>= 0;
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
},
// func valueCall(v ref, m string, args []ref) (ref, bool)
"syscall/js.valueCall": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const m = Reflect.get(v, loadString(sp + 16));
const args = loadSliceOfValues(sp + 32);
const result = Reflect.apply(m, v, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 56, result);
this.mem.setUint8(sp + 64, 1);
} catch (err) {
storeValue(sp + 56, err);
this.mem.setUint8(sp + 64, 0);
}
},
// func valueInvoke(v ref, args []ref) (ref, bool)
"syscall/js.valueInvoke": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.apply(v, undefined, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueNew(v ref, args []ref) (ref, bool)
"syscall/js.valueNew": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.construct(v, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueLength(v ref) int
"syscall/js.valueLength": (sp) => {
sp >>>= 0;
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
},
// valuePrepareString(v ref) (ref, int)
"syscall/js.valuePrepareString": (sp) => {
sp >>>= 0;
const str = encoder.encode(String(loadValue(sp + 8)));
storeValue(sp + 16, str);
setInt64(sp + 24, str.length);
},
// valueLoadString(v ref, b []byte)
"syscall/js.valueLoadString": (sp) => {
sp >>>= 0;
const str = loadValue(sp + 8);
loadSlice(sp + 16).set(str);
},
// func valueInstanceOf(v ref, t ref) bool
"syscall/js.valueInstanceOf": (sp) => {
sp >>>= 0;
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
},
// func copyBytesToGo(dst []byte, src ref) (int, bool)
"syscall/js.copyBytesToGo": (sp) => {
sp >>>= 0;
const dst = loadSlice(sp + 8);
const src = loadValue(sp + 32);
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
// func copyBytesToJS(dst ref, src []byte) (int, bool)
"syscall/js.copyBytesToJS": (sp) => {
sp >>>= 0;
const dst = loadValue(sp + 8);
const src = loadSlice(sp + 16);
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
"debug": (value) => {
console.log(value);
},
}
};
}
async run(instance) {
if (!IsWechat) {
if (!(instance instanceof WebAssembly.Instance)) {
throw new Error("Go.run: WebAssembly.Instance expected");
}
}
this._inst = instance;
this.mem = new DataView(this._inst.exports.mem.buffer);
this._values = [ // JS values that Go currently has references to, indexed by reference id
NaN,
0,
null,
true,
false,
global,
this,
];
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
this._ids = new Map([ // mapping from JS values to reference ids
[0, 1],
[null, 2],
[true, 3],
[false, 4],
[global, 5],
[this, 6],
]);
this._idPool = []; // unused ids that have been garbage collected
this.exited = false; // whether the Go program has exited
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let offset = 4096;
const strPtr = (str) => {
const ptr = offset;
const bytes = encoder.encode(str + "\0");
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
offset += bytes.length;
if (offset % 8 !== 0) {
offset += 8 - (offset % 8);
}
return ptr;
};
const argc = this.argv.length;
const argvPtrs = [];
this.argv.forEach((arg) => {
argvPtrs.push(strPtr(arg));
});
argvPtrs.push(0);
const keys = Object.keys(this.env).sort();
keys.forEach((key) => {
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
});
argvPtrs.push(0);
const argv = offset;
argvPtrs.forEach((ptr) => {
this.mem.setUint32(offset, ptr, true);
this.mem.setUint32(offset + 4, 0, true);
offset += 8;
});
this._inst.exports.run(argc, argv);
if (this.exited) {
this._resolveExitPromise();
}
await this._exitPromise;
}
_resume() {
if (this.exited) {
throw new Error("Go program has already exited");
}
this._inst.exports.resume();
if (this.exited) {
this._resolveExitPromise();
}
}
_makeFuncWrapper(id) {
const go = this;
return function () {
const event = { id: id, this: this, args: arguments };
go._pendingEvent = event;
go._resume();
return event.result;
};
}
}
if (
typeof module !== "undefined" &&
global.require &&
global.require.main === module &&
global.process &&
global.process.versions &&
!global.process.versions.electron
) {
if (process.argv.length < 3) {
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
process.exit(1);
}
const go = new Go();
go.argv = process.argv.slice(2);
go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
go.exit = process.exit;
WebAssembly.instantiate(global.fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
process.on("exit", (code) => { // Node.js exits if no event handler is pending
if (code === 0 && !go.exited) {
// deadlock, make Go print error and stack traces
go._pendingEvent = { id: 0 };
go._resume();
}
});
return go.run(result.instance);
}).catch((err) => {
console.error(err);
process.exit(1);
});
}
})();

59
package_lesson1/index.js Normal file
View File

@@ -0,0 +1,59 @@
require('./assets/wasm_exec.js');
const wasm_url = '/package_lesson1/assets/sample.wasm.br'
Page({
data: {
notice: '',
inputText: '你好Go语言。',
test_result1: '0',
test_result2: '',
},
async onReady() {
// 在小程序基础类库的global对象上增加console对象。
global.console = console
// 使用小程序类库的WXWebAssembly初始化Go运行环境。
await this.initGo()
},
async initGo() {
var _that = this;
const go = new global.Go();
try {
const result = await WXWebAssembly.instantiate(wasm_url, go.importObject)
var msg = 'Go初始化成功,在小程序调试窗口查看console的信息。'
_that.setData({
notice: msg,
})
console.log('initGo', msg)
// 运行go程序的main()方法
await go.run(result.instance);
// 注意在go程序的main()方法退出之前,小程序不会运行到这个位置。
console.log('initGo', '运行完成')
} catch (err) {
console.error('initGo', err)
}
},
btnRun1() {
var _that = this;
var res = global.addTotal()
_that.setData({
test_result1: res,
})
console.log(res)
},
btnRun2() {
var _that = this;
wx.showLoading({
title: '请等待2秒',
mask:true,
})
global.asyncAndCallbak(_that.data.inputText, function (res) {
wx.hideLoading()
_that.setData({
test_result2: _that.data.test_result2+res+' ',
})
console.log(res)
})
},
})

View File

@@ -0,0 +1,4 @@
{
"usingComponents": {},
"navigationBarTitleText": "Lesson 1"
}

View File

@@ -0,0 +1,50 @@
<view class="page">
<view class="page__bd">
<view class="weui-panel">
<view class="weui-panel__hd">Go调用小程序的函数</view>
<view class="weui-panel__bd">
<view class="weui-media-box">
<view>{{notice}}</view>
</view>
</view>
</view>
<view class="weui-panel">
<view class="weui-panel__hd">小程序调用Go的函数每次点击增加次数。</view>
<view class="weui-panel__bd">
<view class="weui-media-box">
<view>点击了{{test_result1}}次</view>
<button bindtap="btnRun1" class="marginTop10" type="primary">点击1</button>
</view>
</view>
</view>
<view class="weui-panel">
<view class="weui-panel__hd">小程序调用Go的函数Go在等待2秒后回调小程序。</view>
<view class="weui-panel__bd">
<view class="weui-cells__group weui-cells__group_form weui-media-box">
<view class="weui-cells__title">表单</view>
<view class="weui-cells weui-cells_form">
<label for="js_input1" class="weui-cell">
<view class="weui-cell__hd"><span class="weui-label">输入内容</span></view>
<view class="weui-cell__bd">
<input id="js_input1" type="text" class="weui-input" model:value="{{inputText}}" />
</view>
</label>
<label class="weui-cell">
<view class="weui-cell__hd"><span class="weui-label">输出内容</span></view>
<view class="weui-cell__bd">
<view>{{test_result2}}</view>
</view>
</label>
</view>
<button bindtap="btnRun2" class="marginTop10" type="primary">点击2</button>
</view>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,13 @@
/**index.wxss**/
.button-sp-area{
margin: 0 auto;
padding-top: 15px;
width: 60%;
}
.mini-btn{
margin-right: 5px;
}
.weui-panel__hd{
font-weight:bold;
color:#333;
}

1
pages/index.js Normal file
View File

@@ -0,0 +1 @@
Page({})

3
pages/index.json Normal file
View File

@@ -0,0 +1,3 @@
{
"usingComponents": {}
}

20
pages/index.wxml Normal file
View File

@@ -0,0 +1,20 @@
<view class="page">
<view class="page__bd">
<!-- Video Mask -->
<view class="weui-panel">
<view class="weui-panel__hd">Go和小程序互操作</view>
<view class="weui-panel__bd">
<view class="weui-media-box weui-media-box_text">
<view class="weui-media-box__desc">
使用WXWebAssembly.instantiate初始化Go运行环境。
</view>
<view class="weui-media-box__info">
<navigator url="/package_lesson1/index" class="marginTop10">
<button class="weui-btn" type="primary">Lesson 1</button>
</navigator>
</view>
</view>
</view>
</view>
</view>
</view>

13
pages/index.wxss Normal file
View File

@@ -0,0 +1,13 @@
/**index.wxss**/
.button-sp-area{
margin: 0 auto;
padding-top: 15px;
width: 60%;
}
.mini-btn{
margin-right: 5px;
}
.weui-panel__hd{
font-weight:bold;
color:#333;
}

82
project.config.json Normal file
View File

@@ -0,0 +1,82 @@
{
"description": "project config file",
"packOptions": {
"ignore": [
{
"type": "folder",
"value": "screenshot"
},
{
"type": "folder",
"value": "node_dev"
},
{
"type": "folder",
"value": "go_dev"
}
]
},
"setting": {
"urlCheck": false,
"es6": false,
"enhance": false,
"postcss": false,
"preloadBackgroundData": false,
"minified": false,
"newFeature": true,
"coverView": true,
"nodeModules": true,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"scopeDataCheck": false,
"uglifyFileName": false,
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"compileHotReLoad": false,
"lazyloadPlaceholderEnable": false,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": true,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"enableEngineNative": false,
"useIsolateContext": false,
"userConfirmedBundleSwitch": false,
"packNpmManually": false,
"packNpmRelationList": [],
"minifyWXSS": false,
"disableUseStrict": false,
"minifyWXML": false,
"showES6CompileOption": false,
"useCompilerPlugins": false
},
"compileType": "miniprogram",
"libVersion": "2.15.0",
"appid": "wxb4cdacc73d2d71c7",
"projectname": "WeChat-WebAR",
"debugOptions": {
"hidedInDevtools": []
},
"isGameTourist": false,
"simulatorType": "wechat",
"simulatorPluginLibVersion": {},
"condition": {
"search": {
"list": []
},
"conversation": {
"list": []
},
"game": {
"currentL": -1,
"list": []
},
"miniprogram": {
"list": []
}
}
}

BIN
screenshot/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
screenshot/2-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
screenshot/2-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
screenshot/2-3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
screenshot/3-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
screenshot/3-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

7
sitemap.json Normal file
View File

@@ -0,0 +1,7 @@
{
"desc": "visit https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{
"action": "allow",
"page": "*"
}]
}

6
style/weui.wxss Normal file

File diff suppressed because one or more lines are too long