{"id":13602146,"url":"https://github.com/sanyuered/WeChat-MiniProgram-AR-WASM","last_synced_at":"2025-04-11T08:31:43.924Z","repository":{"id":46527757,"uuid":"434337201","full_name":"sanyuered/WeChat-MiniProgram-AR-WASM","owner":"sanyuered","description":"微信小程序Go、微信小程序OpenCV。","archived":false,"fork":false,"pushed_at":"2022-08-29T06:31:07.000Z","size":4090,"stargazers_count":119,"open_issues_count":7,"forks_count":23,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-11-07T05:38:40.450Z","etag":null,"topics":["go","go-wechat","golang","javascript","opencv","opencv-js","opencv-wechat","wasm","webassembly","webassembly-wechat","wechat-mini-program"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sanyuered.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-12-02T18:48:07.000Z","updated_at":"2024-09-25T05:53:34.000Z","dependencies_parsed_at":"2022-07-19T21:59:39.010Z","dependency_job_id":null,"html_url":"https://github.com/sanyuered/WeChat-MiniProgram-AR-WASM","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sanyuered%2FWeChat-MiniProgram-AR-WASM","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sanyuered%2FWeChat-MiniProgram-AR-WASM/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sanyuered%2FWeChat-MiniProgram-AR-WASM/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sanyuered%2FWeChat-MiniProgram-AR-WASM/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sanyuered","download_url":"https://codeload.github.com/sanyuered/WeChat-MiniProgram-AR-WASM/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248361566,"owners_count":21090932,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","go-wechat","golang","javascript","opencv","opencv-js","opencv-wechat","wasm","webassembly","webassembly-wechat","wechat-mini-program"],"created_at":"2024-08-01T18:01:15.241Z","updated_at":"2025-04-11T08:31:42.134Z","avatar_url":"https://github.com/sanyuered.png","language":"JavaScript","readme":"## 更新日志\n\n| 日期　　　| 内容 |\n| -- | -- |\n| 2022-08-29 | 修复：如果微信开发者工具设置“将JS编译成ES5”，则opencv_exec.js文件提示“Module未定义”的问题。 |\n| 2021-12-05 | 新增：微信小程序运行OpenCV的示例。也包含网页运行OpenCV的示例。 |\n| 2021-12-03 | 新增：微信小程序运行Go语言的示例。也包含网页运行Go语言的示例。 |\n\n## 介绍\n\n1、将Go语言编译为WebAssembly，使用微信小程序的WXWebAssembly功能运行WebAssembly。\n\n因为Go语言能快速开发WebAssembly，所以使用Go语言能为小程序快速开发WebAssembly。\n\n2、在微信小程序中使用WebAssembly版的OpenCV，让我们开发图像视觉。\n\n[WXWebAssembly官方文档](https://developers.weixin.qq.com/miniprogram/dev/framework/performance/wasm.html)\n\n## 网页版\n\n在线预览，和小程序版使用的是相同的wasm文件。\n\n运行Go\n\n[https://sanyuered.github.io/WeChat-MiniProgram-AR-WASM/go_dev/lesson1.html](https://sanyuered.github.io/WeChat-MiniProgram-AR-WASM/go_dev/lesson1.html)\n\n运行OpenCV\n\n[https://sanyuered.github.io/WeChat-MiniProgram-AR-WASM/opencv_dev/lesson2.html](https://sanyuered.github.io/WeChat-MiniProgram-AR-WASM/opencv_dev/lesson2.html)\n\n## 小程序版\n\n首页\n\n![avatar](screenshot/1.jpg)\n\n## Go调用小程序的函数\n\nGo运行时，会调用小程序的console.log()输出信息。\n\n![avatar](screenshot/2-1.jpg)\n\n在调试窗口的Console面板查看\n\n![avatar](screenshot/2-2.jpg)\n\n## 小程序调用Go的函数\n\n每次点击按钮，次数会增加1。\n\n![avatar](screenshot/2-3.jpg)\n\n## 小程序调用Go的函数, Go回调小程序。\n\n输入内容是可编辑的自定义文本。\n\n![avatar](screenshot/3-1.jpg)\n\n点击按钮后，输出内容会返回输入的自定义文本。Go语言一侧使用了time.Sleep()和协程，模拟了等待2秒的异步方法调用。\n\n![avatar](screenshot/3-2.jpg)\n\n## 小程序调用OpenCV的函数。\n\n灰度化\n\n![avatar](screenshot/4-1.jpg)\n\n边缘检测\n\n![avatar](screenshot/4-2.jpg)\n\n特征点检测\n\n![avatar](screenshot/4-3.jpg)\n\n## 目录结构\n\n/：小程序一侧的源代码\n\n/go_dev：Go一侧的源代码\n\n/node_dev： Node一侧的源代码\n\n/miniprogram_npm：微信开发者工具将npm包text-encoder编译后的文件。\n\n/project.config.json：小程序打包时，排除node_dev、go_dev等目录。\n\n```javascript\n    \"packOptions\": {\n        \"ignore\": [\n            {\n                \"type\": \"folder\",\n                \"value\": \"screenshot\"\n            },\n            {\n                \"type\": \"folder\",\n                \"value\": \"node_dev\"\n            },\n            {\n                \"type\": \"folder\",\n                \"value\": \"go_dev\"\n            }\n        ]\n    },\n    \n```\n\n## Go一侧的源代码\n\nGo一侧和网页版wasm的开发完全一样，没有区别。但不要调用fmt.Println()。\n\n```javascript\nfunc main() {\n\t// 创建通道\n\tchannel := make(chan int)\n\t// Go调用js的console.log()方法,在开发者工具的Consol面板中查看。\n\tconsole := js.Global().Get(\"console\")\n    console.Call(\"log\", \"hello, world!\")\n    // 其它代码省略\n\n\t// 通道阻塞了main()方法\n    \u003c-channel\n    \n```\n\n网页端运行Go：/go_dev/lesson1.html\n\n```javascript\n    const go = new global.Go();\n\ttry {\n        const result = await WebAssembly.instantiateStreaming(fetch(wasm_url), go.importObject)\n        // 运行go程序的main()方法\n        await go.run(result.instance);\n        // 其它代码省略\n    } catch (err) {\n        console.error('initGo', err)\n    }\n\n```\n\n小程序端运行Go：/package_lesson1/index.js\n\n注意：wasm_url必须是小程序打包的文件，不能是网络文件和下载后保存的文件。wasm文件会占用小程序打包后的大小，所以小程序每个分包中的wasm文件不能大于2MB。\n\n```javascript\n   const go = new global.Go();\n    try {\n        const result = await WXWebAssembly.instantiate(wasm_url, go.importObject)\n        // 运行go程序的main()方法\n        await go.run(result.instance);\n        // 其它代码省略\n    } catch (err) {\n        console.error('initGo', err)\n    }\n\n```\n\n## Node一侧的源代码\n\nNode一侧是将.wasm压缩为.wasm.br文件。小程序每个分包限制大小为2MB，但支持.wasm.br压缩文件。使用brotli压缩.wasm文件，能压缩到原始.wasm文件大小的20%至25%。\n\n但是brotli压缩速度很慢。1.4MB的.wasm，压缩为.wasm.br，花费时间约6秒。文件更大的.wasm，压缩时间更长，可能花费几分钟。\n\n```javascript\n// 第三方的wasm版本brotli压缩和解压缩\nvar brotli = require('wasm-brotli');\n// 压缩文件的示例\nasync function compressFile(){\n    const content = await readFileAsync('../go_dev/sample.wasm');\n    const compressedContent = await brotli.compress(content);\n    // 其它代码省略\n}\n\n```\n\n## 技术难点\n\n### 1、小程序分包大小不能超过2MB，Go的Hello World示例编译为wasm文件大小就超过2MB了。\n\n解决方法：1、使用brotli工具压缩wasm，压缩率约22%，理论上支持最大8MB的未压缩wasm。\n\n### 2、如何修改wasm_exec.js文件？\n\n解决方法：修改的位置使用了变量IsWechat，判断是否是小程序环境。具体修改请见源代码。\n```javascript\nconst IsWechat = true;\n```\n\n### 3、小程序没有crypto.getRandomValues()方法。\n解决方法：\n```javascript\n    if (!global.crypto) {\n        global.crypto = {\n            getRandomValues(b) {\n                let byteRange = 256;\n                for (var i = 0; i \u003c b.length; i++) {\n                    b[i] = Math.floor(byteRange * Math.random());\n                }\n            },\n        };\n    }\n```\n\n### 4、小程序没有performance.now()方法。\n解决方法：\n```javascript\n   if (!global.performance) {\n        global.performance = {\n            now() {\n                return Date.now()\n            },\n        };\n    }\n```\n\n### 5、小程序没有TextDecoder和TextDecoder对象。\n解决方法：\n```javascript\n  \tif (!global.TextEncoder) {\n        global.TextEncoder = require(\"text-encoder\").TextEncoder;\n    }\n    if (!global.TextDecoder) {\n        global.TextDecoder = require(\"text-encoder\").TextDecoder;\n    }\n```\n\n## Go和小程序互操作\n\njs.Global() ：获取js运行环境的global对象。\n```javascript\nconsole := js.Global().Get(\"console\")\n```\n\njs.ValueOf ：Go对象的值转换为js对象。\n```javascript\nfunc addTotal(this js.Value, args []js.Value) interface{} {\n    // 其它省略\n    return js.ValueOf(totalNum)\n}\n```\n\njs.Value  ：js对象的值转换为Go对象。\n```javascript\nfunc asyncAndCallbak(this js.Value, args []js.Value) interface{} {\n\t// js输入参数\n    input := args[0].String()\n    // js回调函数\n    callback := args[1]\n    // 其它省略\n}\n```\n\njs.FuncOf  ：Go函数转换为js函数。\n```javascript\n    // js调用Go的addTotal()方法\n\tjs.Global().Set(\"addTotal\", js.FuncOf(addTotal))\n```\n\nObject.Call(function1,arg1,arg2) ：在js对象上调用方法function1,输入参数arg1、arg2等。\n```javascript\n    // Go调用js的console.log()方法,在开发者工具的Consol面板中查看。\n    console := js.Global().Get(\"console\")\n    console.Call(\"log\", \"hello, world!\")\n```\n\nfunction1.Invoke(arg1,arg2) ：调用方法function1,输入参数arg1、arg2等。\n```javascript\nfunc asyncAndCallbak(this js.Value, args []js.Value) interface{} {\n    // js回调函数\n    callback := args[1]\n    // 运行js回调函数\n\tcallback.Invoke(result)\n    // 其它省略\n}\n```\n\nObject.Get(prop1) ：获取js对象的子对象、属性、方法等。\t\n```javascript\nconsole := js.Global().Get(\"console\")\n```\n\n注意：使用js.Global().Get(\"console\")，需要在小程序基础类库的global对象上，增加console对象。\n```javascript\n\tasync onReady() {\n\t\t// 在小程序基础类库的global对象上，增加console对象。\n\t\tglobal.console = console\n\t\t// 使用小程序类库的WXWebAssembly，初始化Go运行环境。\n\t\tawait this.initGo()\n\t},\n```\n\t\nObject.Set(prop1,value1)：设置js对象的子对象、属性、方法等。\n```javascript\n// 在js运行环境，设置Go的addTotal()方法\njs.Global().Set(\"addTotal\", js.FuncOf(addTotal))\n```\n\n\n## Go和JavaScript的变量类型\n\n```javascript\n| Go                     | JavaScript             |\n| ---------------------- | ---------------------- |\n| js.Value               | [its value]            |\n| js.Func                | function               |\n| nil                    | null                   |\n| bool                   | boolean                |\n| integers and floats    | number                 |\n| string                 | string                 |\n| []interface{}          | new array              |\n| map[string]interface{} | new object             |\n\n```\n\n## 已知问题\n\n### Q1：syscall/js: call of Value.Invoke on undefined\n\nA：使用fmt.Println(\"hello,world\")会发生该错误，但使用fmt.Sprintf(\"%d\", 123) 正常。不要使用fmt.Println()方法，而使用console.log()方法向小程序输出调试信息。\n\n### Q2：LinkError: WebAssembly.instantiate(): Import #4 module=\"go\" \n\nA: wasm_exec.js中的方法有的能去掉，有的不能去掉。 如果修改wasm_exec.js，则importObject.go对象中的所有方法名称需要保留。\n\n### Q3：从Go调用小程序的wx.showModal({title:\"123\"})等API发生错误\n\nA：如果运行wx := js.Global().Get(\"wx\");wx.Call(\"showModal\", \"\");，则可以显示一个空白的对话框。Go可以调用小程序API，问题出在wx.Call(\"showModal\", object)的输入参数是Object类型。\n\n在网页端WebAssembly，Go能正常传递Object给网页。\n\n在小程序端WebAssembly，Go无法正常传递Object给小程序。将方法的输入参数从Object类型暂时换成其他变量类型。\n\n### Q4：作者的wasm_exec.js与读者的Go版本不匹配\n\nA：作者的wasm_exec.js来自 “/Go安装目录/misc/wasm/wasm_exec.js”，版本号是1.16.3。如果与作者的Go版本号不同，则可以根据“\\package_lesson1\\assets\\wasm_exec.js”文件中的变量“IsWechat”，修改不同版本号的wasm_exec.js文件中6处代码即可。","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsanyuered%2FWeChat-MiniProgram-AR-WASM","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsanyuered%2FWeChat-MiniProgram-AR-WASM","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsanyuered%2FWeChat-MiniProgram-AR-WASM/lists"}