乐趣区

2019-Google-IO-大会面向Web开发人员的WebAssembly-上

特别说明

这是一个由 simviso 团队对 2019 Google I/O 大会中关于 面向 Web 开发人员的 WebAssembly相关话题进行翻译的文档,内容并非直译,其中有一些是译者自身的思考。Surma 是 Google 公司 WEB 基础的贡献者,也是 open web 平台的开发倡导者。
视频地址:面向 Web 开发人员的 WebAssembly 2019 Google I/O 上
视频同时也获得了谷歌大佬 Surma 的官方推特分享与点赞

视频翻译文字版权归 simviso 所有

本次参与翻译人员

前言

我是 Surma,我是 open web 平台的开发倡导者,在伦敦与谷歌的 Chrome 团队合作。今天很高兴可以跟大家谈一谈我最近发现的一个让我充满激情的东西,那就是 WebAssembly。


你可能对它有所耳闻,如果你以后有什么问题的话,可以在 Twitter 上联系我。稍后我的同事,来自 web 工程团队的 Deepti 会分享一些关于 WebAssembly 未来的话题。

定义

在我们开始讲之前,我想让大家一起看下这张图。


因为 WebAssembly 通常与 C++ 紧密相连,以至于很多人都认为它都是关于 C++ 的,事实上,WebAssembly 远不止于此。你可以在网上找到很多关于 C++ 和 Emscripten 的 demo。这很有意义,因为 Emscripten 是一个令人惊叹的工具。但对 Web 开发人员而言,意识到 WebAssembly 不仅限于 C ++,这点很关键!

WebAssembly 本身其实是一个非常有用的工具,你值得拥有!这就是我想在这次演讲中谈论的内容。我想展示一些支持 WebAssembly 的其他语言,以此来让你在不学习新语言的情况下使用 WebAssembly。然后,就像我说的,Deepti 接下来将讨论 WebAssembly 的未来。


所以要确保每个人都知道或者希望在场的人都知道这个网站 (WebAssembly 官网),它上面解释了什么是 WebAssembly, 它是一个基于堆栈的虚拟机

如果你不知道什么是基于 Web 堆栈的虚拟机,那也没有关系。
重要的是,你能意识到它是一个虚拟机,意味着它不是一个实际存在的处理器,它的设计理念在于 我们可以很容易的将通用代码编译到真实的运行环境下可执行的代码,这就是所谓的可移植性。因此,对虚拟机而言,设计之初需要优先考虑可移植性。

因此,当你使用任意语言编写某些代码并将其编译到 WebAssembly 时,也就是说这些代码会被编译成虚拟机可执行的指令集,然后将这些指令存储到以二进制格式存储到 .wasm 文件中。


因为该虚拟机可以很轻易的根据对应平台下的处理器进行代码编译,所以 .wasm 文件可以在运行时被读取,此时我们这里运行的上下文极有可能是浏览器。
浏览器能将 .wasm 文件转换成当前机器可执行的机器码,并在浏览器上执行该代码。

在一开始,WebAssembly 就是为了面向过程安全而设计的。你可以在裸机上运行该代码,但这并不意味着它是不安全的。

实际上我们已经在上次的 Google I/O 大会上讨论过 WebAssembly 了。
作为一项技术,它以惊人的速度快速成长,并占据了一席之地。之前我们也讨论了一些大公司是如何使用 WebAssembly 来运行他们之前已经存在的一些产品,这些产品很可能是用 C ++ 来编写的。

举个例子,就拿我们使用多年的 AutoCAD 来讲,这是一个非常知名的产品,但现在他们正努力将它编译到 WebAssembly 上,当你想用它时,你就可以立即在浏览器上运行它,想想都令人感到不可思议。

另一个例子则是 Unity 游戏引擎以及虚幻引擎,它们现在都已经开始支持 WebAssembly。
通常这些游戏引擎已经内建了一套抽象,因为你需要将你的游戏构建并编译到 PlayStation,XBox 或者其他游戏平台之上。

但现在 WebAssembly 已经成为它们的另一个编译目标,那给我们印象很深的就是通过浏览器和 WebAssembly 就可以提供这些游戏运行所需的性能。

例子讲解

这让我觉得很神奇,这些神奇的事情还在继续发生着。

二维码扫描


你通过 WebKeynote 就可以看到,我的同事 Paul Lewis 建立了一个感知工具包,它可以帮你建立一种沉浸式的体验。他们希望通过二维码和图像检测的形式来和我们的现实世界建立联系。

所以浏览器能够通过使用图形检测 API(Shape Detection API)检测二维码,

这一功能。

所以他们要做的就是该通过什么途径使用这个图形检测 API。如果不可用,他们可以通过将二维码库编译到 WebAssembly 上,然后就可以按需加载。这样我们就可以找到它。

并且图像检测根本不在 Web 平台上。所以他们需要自己去构建,并使用 WebAssembly 为浏览器提供新的功能。

QT Lib

UI 工具包 QT(QT 是跨平台的软件开发工具包)宣布他们现在也支持 WebAssembly。因此这也意味着其实你现在可以使用一个旧版本的 Lib QT 应用程序,并将其编译为 WebAssembly。然后在浏览器标签体验中有一个奇怪的窗口,这看起来不太理想,这里只是为了表明它起作用了。

但 QT 是一个强大且通用的 UI 库。所以,他们网站底部有很多 Demo,实际上他们使用 Lib QT 和 WebAssembly 构建了一种优良且原生的 UI。

Emscripten


所以如果你对 WebAssembly 不甚了解,那么你可能会问它们是怎么做到的?
针对这些例子,给出的答案是Emscripten

Emscripten 的目标是替代 C 或 C ++ 编译器,取代将代码编译成你所需的本地机器码,而是将代码编译到 WebAssembly 上。
他们真的尝试着进行这方面的取代工作。无论你编写什么语言的代码,都可以跑在一个系统上,即可以很神奇地在 web 上面运行。这也是两种编译器之间最明显的区别。

为了实现这一目标,Emscripten 在幕后做了很多繁重的工作。

我之所以这么思考的原因,在于它在这么多种情况下都可以很紧密和 WebAssembly 一起很好的工作。


最初,Emscripten 是一个 asm.js 编译器。这是 Mozilla 的一个概念,他们编写了这个编译器,用于将 C 语言代码并将其转换为 JavaScript。

因此可以看到,右手边的这个就是 asm.js。
它只是普通的 JavaScript,也就是说,只要可以执行 JavaScript 的浏览器都可以执行 asm.js。

但这个计划旨在让其它浏览器支持 asm.js,从而让它们运行这类程序的时候更加快速。
所以你需要分配一块内存,同时接收一些变量。瞬间,你的 C ++ 代码就能够运行在你的 JavaScript 引擎上!

但 C 和 C++ 经常使用其他 API,比如 fileopen 和 OpenGL。
因此,Emscripten 可以使用 WebGL 伪装成 OpenGL,也可以通过模拟一个文件系统,使你看起来好像在处理真实的文件一样。
基本上,他们是真模仿一整套 POSIX 操作系统来使代码运行在 Web 上,而这些代码从来都不是为 Web 编写的。
所以他们做到了!

所以当 WebAssembly 出现时,对 Emscripten 来讲,只是添加了一种新的输出格式,依然保留着他们在进行模拟时可以做到的所有功能。
因此,Emscripten 可以按照之前的经验进行使用。

我们知道,将 POSIX 代码通过 WebAssembly 在 Web 上运行。
这样,他们就能够以极快的速度围绕 WebAssembly 来提供非常令人印象深刻并且成熟的 Demo 和工具。

他们值得得到大家的称赞,因为他们,所有其他语言有了这样一个平台。
我想这就是为什么 WebAssembly 与 C++ 如此紧密的缘故,在于 Emscripten 的快速成熟的发展。

Web 开发人员

但是,对于 Web 开发人员呢?


可能你是在 Web 机构工作或者你也可能是一个自由开发者,那么 WebAssembly 对你会有什么样的帮助?

你必须要学 C ++ 吗?NO!

当你是一个 Web 开发人员,你会想,Oh,我应该需要学习 C++ 的,只有这样你才可以使用 WebAssembly,可能很多人都会这样想。


因为当你知道 JavaScript 的时候,你在想 C ++ 到底是什么?有趣的是,反过来也是一样。当我看到 C ++ 开发人员第一次看到或写 JavaScript 代码时,他们的表情一模一样。

我不是说因为一种语言比另一种语言好,仅仅是因为他们需要这样一种截然不同的思维方式来编写代码。
这两种语言我写的都很专业。但每当我稍微切换一下,总是需要一些时间才能想到(思维方式的不同)。我想说的是,对于 Web 开发人员来说,至今还没有去学习 C++ 的动力。

因此,这两种编程语言都掌握非常好的人非常少。这就导致,WebAssembly 似乎是一个非常小众的技术。实际上,它是一个非常有用且值得你拥有的工具!

WebAssembly 与 Javascript

这里,我想说的是,每当我想到 WebAssembly 时,我通常会通过两个主要例子来讨论。
一方面,我想谈谈在 JS 应用程序中微小模块的替换、热传递和 WebAssembly 的瓶颈,同时也想谈谈 WebAssembly 比 JavaScript 更快的神奇之处。

但首先,我想谈谈关于生态系统的其他方面。

这看起来有点奇怪,因为当我说 JavaScript 生态系统非常庞大时,没人会反对。
我的意思是,仅仅只是在 NPM 上看,它就非常巨大。
但事实上并不是每个主题的首选都是 JavaScript,也有可能是其他语言。

所以有时候你可能会去面临一个问题,你需要寻找库去解决这些问题。你可以在 C 中或者 Rust 中找到这些答案,而不是在 JavaScript 中找到。

因此,你要么坐下来编写自己的 JavaScript 对应实现,要么通过 WebAssembly 来使用其他语言的相关实现。
这正是我们对 Squoosh 所做的。


Squoosh 是一个完全可以在浏览器中运行的图像压缩应用程序,可以在脱机状态下工作。你可以放入图片,然后可以使用不同的编解码器压缩它们。
以观察这些不同的编解码器如何对你的图片的视觉质量产生不同的影响。
如果你了解的话,可以发现浏览器现在已经提供了这一性能。


通过使用 Canvas,你可以决定你要对图片进行编码的图片格式。
你甚至可以控制图片质量!

但事实证明,浏览器通过对这些编解码器的优化而不是优化压缩质量或视觉质量来提高压缩速度。
老实说,有点不尽人意。
而且,你有点受浏览器支持的编解码器的约束。

所以直到最近,也只有 Chrome 可以编码为 WebP 格式,其他浏览器却没有。
所以这对我们来说还不够。

我们利用谷歌查阅了一些相关资料,并找到了一些使用 JS 写的有关 JPEG 的编解码器
但有点奇怪,我们并没有找到一个使用 JavaScript 写的专门针对 WebP 的编码器。
因此,我们认为我们得看看其他东西。。

所以我们从其他地方查阅了一番,并在 C 和 C ++ 中找到了大量的相关编码器。
因此,我们选择 WebAssembly。
所以呢,我们要做的就是编译。

举个例子,将 MozJPEG 这个库编译到 WebAssembly 上,并加载到浏览器中,然后将浏览器的 JPEG 编码器替换成我们自己加载的这个

这样我们在相同视觉质量下得到了更小体积的图片,酷吧!
不仅如此,WebAssembly 还允许我们加载这个库所暴露的专业选项,而这些在浏览器中显然是不可能暴露的。

因此,配置诸如色度子采样或者不同的量化算法等选项,不仅有助于将最后几个字节从图片中舍弃,而且还可以作为一种学习工具,方便我们去查看这些选项究竟是怎么影响我们的图片视觉和文件大小的。

这里的重点是我们采用了一段旧代码,仿佛 MozJPEG 又回到了 1991 年(mozjpeg 里面有个头文件是在 1991 开始的)

它绝对不是以 Web 为目的写的,但我们还是将它使用在了 Web 上,并用它来改善 Web 平台。

Emscripten 操作举例

还有我们用过的 Emscripten。
所以将它与 Emscripten 一起向你展示它是如何工作的。

我通常会分两步进行。

第一步是编译库,以便稍后你可以链接到它。
图片编解码器通常会用到多线程和 SIMD(单指令多数据流),因为图片压缩是一个高并发任务。
但不管是 JavaScript 还是 WebAssembly 都还不支持多线程或 SIMD。

稍后 Deepti 会带着大家来讨论这方面的内容。

但是这里我们通过禁用 SIMD 以确保在运行过程中不会出现任何问题


在第二步中,我们需要写一段桥接代码

这个函数我稍后将使用 JavaScript 来调用,
所以,它需要接收图片、图片尺寸,然后通过 MozJPEG 来对它进行压缩,并返回一个包含 JPEG 图片的数组类型 Buffer。

当我们写完这段桥接代码,我们调用 EMCC(Emscripten Compiler Frontend)命令,Emscripten C 编译器将编译我们的 C ++ 文件和库文件,然后链接到一起供后续使用。

这个过程没有报错,然后得到输出文件:一个已经配置好了的 JS 文件和对应的 WebAssembly 文件。

这里要记住,Emscription 作为一个替代者,已经将 Emulations 相关的复杂工作全都帮你做了(Emulations 是 C ++11 的一个库)
你需要一直关注你文件大小,因为这种文件系统 emulations 相关 api 代码需要其他的相关底层输入。
因此如果你使用了大量的 C 语言的 API,那么生成的文件将会很大,尤其是 JS 文件。

我们一直在与 Emscripten 团队密切合作,为了让生成的文件尽可能的小。但是如果你想让 Emscripten 成为一个替代品,你只能尽你所能做的更多。所以还是要注意生成文件的大小。

关于 WebAssembly 的另外一个例子就是使用 Squoosh 进行图片的缩放。
为了将一张图片放大或者缩小,有很多方式可以通过许多不同的视觉效果和视觉输出来实现这一点。
可以通过多种不同的视觉效果和视觉输出进行实现。

因此对于浏览器,如果你只是使用浏览器缩放图片,你会得到一张缩放的图片。
它可能会很快,可能看起来也不错。
但有时候通过对这张图片缩放操作进行不同的变量控制真的可以产生巨大的视觉冲击力。


所以,在这个视频上,你可以看到我在 Lanczos3 和浏览器拥有的算法之间来回切换。
你可以看到,通过 Lanczos3 算法,我会有一个线性 RGB 色彩空间转换。

实际上,我对这张图片中的亮度有更真实的感知。
所以在这种情况下,它实际上是一段非常有用的代码。
我们在 Squoosh 中使用的这些图像缩放算法,实际上是从 Rust 生态系统中提取的。

Mozilla 为 Rust 生态系统进行了大力投资,
他们的团队为 Rust 编写了 WebAssembly 工具,同时社区也将这些东西抽象为通用工具。
其中一个工具是 wasm-pack,它真的很趁手,可以帮你将你的 Rust 代码转化为
WebAssembly 模块,一个现代化形式的 JavaScript(译者注:JS 可以调用.wasm 文件,变相对功能拓展,所以称之现代化),并且它非常小。
我觉得这种东西真的很有趣。

所以对于 Rust,同样的道理,我们有一个库,用来编写我们这么一小点桥接代码。

在这个例子中,我想通过 JS 调用这个 resize 函数。它通过接收图片、我输入的图片大小和输出的大小,然后通过这个函数我会得到调整后的图片。

然后你就可以将所有这些代码通过 wasm-pack 转换为一个我们可用的 WebAssembly 模块,
现在比较文件大小有失公平,因为它是一个不同的库,而且这个库相对较小。

所以不能直接从文件大小层面比较。但是就平均而言,讲道理说,Rust 往往会产生更小的胶水代码。因为 Rust 没有做任何关于 POSIX 的文件系统模拟

你不能在 rust 使用关于文件的函数,因为它根本就没有做文件系统模拟。
如果你想要的话,也可以放入一些它们提供的封装模块。它更像是一种可插拔的配置。

因此,最低我们也要用到 Squoosh,也就是我们需要使用至少来自两种不同语言的四个不同的库。
这些库与 web 无关,但我们仍然继续要在 web 上使用它们。

这正是我想要你从这整件事中得到的东西,如果你在 Web 平台上发现一个(技术)空白,并且该空白其他语言已经实现了很多次,但却不是用于 Web 上或者不是通过 JavaScript 实现的。

两种语言的比较

WebAssembly 可能是一个工具。

但现在让我们来谈谈在你的 JavaScript 中关于 Hot Path(有大量循环迭代的代码实现)的替换,以及 WebAssembly 比 JavaScript 更快的神奇之处。

现在这对我来说非常重要,而这就是为什么我想出了使用这张图来表达我的意思。

JavaScript 和 WebAssembly 都有相同的峰值性能。
它们运行都很快,但是在运行比较稳定的代码(fast path)这点上,使用 WebAssembly 比 JavaScript 快得多(译者注:fast path 是已经编译后的代码,slow path 是需要临时进行编译的代码)

fast path: Frontend -> simple code generator -> assembler
slow path: Frontend -> IR optimizer (sometimes more than one level of IR) -> code-generator -> assembler 

或者反过来讲,如果你运行一些不稳定的代码(slow path),相比 WebAssembly 而言,js 就更加容易出现一些意想不到的意外。目前,WebAssembly 正在寻找支持多线程和 SIMD 的解决方案,而这是 JavaScript 永远做不到的因此,一旦这些技术实现,WebAssembly 将有机会真正的超越 JavaScript。但在目前状态下,它们的峰值性能都是相同的。

Chrome 引擎

为了理解在 Fast Path 环节下 JS 的整体性能下降,让我们来谈谈一点关于 Chrome 的 JavaScript V8 引擎 和 WebAssembly 引擎吧。

JavaScript 文件和 WebAssembly 文件有两个不同的引擎入口点。JavaScript 文件传递给 V8 的解释器:Ignition。
所以,它可以将 JavaScript 文件作为文本读取,并解析和运行它。

当这个解释器运行时,它会搜集有关代码行为的分析数据,然后通过 Turbofan 这个优化编译器来生成机器码。

另一方面,WebAssembly 文件将会传给 Liftoff 这个流式 WebAssembly 编译器

一旦这个编译器完成操作,turbofan 就会启动并生成优化代码。
这里有一些区别。

第一个明显的区别是他们有不同的名称和不同的 Logo,但也有概念上的区别。
ignition 是一个解释器,Liftoff 是一个生成机器代码的编译器。

虽然我们不能总是以偏概全的去说机器码永远要比解释性程序代码跑得更快。
但总的来讲,确实是这么一回事。

那么,第一个区别就是速度感知方面

但更重要的是 JavaScript 的这一区别。
但对于 JavaScript 来讲,更重要的区别是优化编译器只会在最后启动,在这之前 JS 代码必须进行运行并进行观察才能被优化。

因为某些假设是根据观察结果得出的。生成机器码,然后再运行机器码。但是一旦这些假设没有办法再 Hold 住状况,你必须回到解释器。因为我们不能再保证机器是否可以正常工作。这被我们称为deopt,即负优化。

有了 WebAssembly,Turbofan 总是在 Liftoff 编译器之后起作用,并且你能一直留在 Turbofan 上输出。你能一直留在 fast path 上,并且你永远不会出现在 deopt 状况,我想这也是大家对于 WebAssembly 速度更快这个观点的在认知上的误区。但在 Javascript 上你会很容易就得到 deopt 状态,而在 WebAssembly 上就不会。

基准测试

来自 Rust WebAssembly 团队的 Nick Fitzgerald 其实做了一个非常好的基准测试,他为 JavaScript 和 WebAssembly 编写了一个基准测试。

图中 JavaScript 是红色部分,WebAssembly 是蓝色部分,运行在不同的浏览器上。
看到这里,你会说 Yes,OK,WebAssembly 更快。

但是这里主要的时间消耗可以看出 JavaScript 消耗跨度比较大。
它需要多长时间才是不可预料的,而无论在哪个浏览器上 WebAssembly 始终如一,消耗的时间基本相同。

我想这才是关键,这也是我想带给你们的。WebAssembly 为你提供了可预测性更强的性能。
它提供了比 JavaScript 可预测性更强的性能。

这就是我想通过 Squoosh 这个例子来告诉大家的东西,
我们想旋转一个图片。
所以,我们想好了,我们用 Canvas 实现吧。但是我们不能使用它,因为 Canvas 运行在主线程上。

在 Chrome 中的 OffscreenCanvas 很少运行在主线程上
(译者注:OffscreenCanvas 提供了一个可以脱离屏幕渲染的 canvas 对象,是针对上面提到的主线程的补充)

所以,我们最终手写了一段 JavaScript 来旋转它或者只需重新排列像素即可旋转图片。
它真的很管用,非常快,但事实证明,我们在其他浏览器测试得越多,它就变得有点奇怪。

所以在这个测试案例中,我们将一张 4K 分辨率的图片进行旋转。

这不是为了比较浏览器。而是关于比较 JavaScript 的。

最快的浏览器需要花费 400 毫秒,
最慢的浏览器需要 8 秒,
甚至在脱离主线程的情况下,对于用户来说,按下按钮旋转图像消耗的时间实在太长了。

从这里你切实可以看到,很明显,我们可以在一个浏览器中停留在 fast path 上,但我们在另一个浏览器中却失败了。
不是这个浏览器不够快,只是一些浏览器的优化有所不同。

所以,我们将我们写的旋转代码运行在 WebAssembly 上或通过其他语言实现这段代码并编译到 WebAssembly,然后来进行性能比较。

你能从这幅图中看到什么?几乎所有支持 WebAssembly 的语言都能使我们的运行时间降低到大约 500 毫秒左右。

我认为这是可预测的。我的意思是这些语言仍然存在一些差异,但抛开 JavaScript 的数据,其它的就没什么差距,这简直是指数级的降低。

同样,我们也可以从这里看到,我有注意到在 WebAssembly 最优性能体现这块,
WebAssembly 和 JavaScript 的最优性能几乎是一样的。

AssemblyScript


因此,如果你看图表,你可能想知道 AssemblyScript 是什么。
如果你还不了解它,我会很兴奋,因为 AssemblyScript 确实将我带回了我这节课要演讲的主题,那就是 Web 开发人员的 WebAssembly。

AssemblyScript 是一个从 TypeScript 到 WebAssembly 的编译器。

这么说可能会误导你,
因为你不能将你现有的 TypeScript 放到这个编译器中,并从中获取 WebAssembly。

因为在 WebAssembly 中你没有 Dom API,这样你就不能只使用相同的代码。
但是 AssemblyScript 可以使用 TypeScript 语法写的不同类型的库。

这也就意味着你根本不需要去学一门全新的语言用来写 WebAssembly,这也是让我感到最为惊艳的一点
所以这看起来像什么,就是 Typescript 语言,只不过有一点点区别。

就好像图中的i32,它实际上并不是 JavaScript 中的一种类型,但它却是 WebAssembly 的一种所属类型。

然后,如 load 和 store 这些内置函数,可以从内存中读取和写入值。接着,AssemblyScript 编译器会将这些转变为 WebAssembly 模块。

so,你现在完全可以在不学习一门新语言的情况下,你就能够去写 WebAssembly,并从中获益并且能够利用所有 WebAssembly 所能提供给你的好处,这是非常强大的。

有些事情需要你记住,WebAssembly 并不像 TypeScript 那样具有垃圾回收功能,至少现在还没有。Deepti 之后会关于这个进一步讨论。
至少目前而言,你需要自己做内存管理。

所以 AssemblyScript 提供给我们一系列的内存管理模块。你只需要将它们从仓库里拉下来,然后用这些 C 语言风格的代码对它们进行内存分配管理
这是我们目前需要去习惯的,但它确实十分有用。一旦 WebAssembly 支持垃圾回收,它会变得更好。AssemblyScript 已经完全开源,但它还是一个处于起步阶段的小项目。背后有许许多多充满激情的人在为它作支持。尽管它已经有了几个赞助商,但还是比不上像有 Mozilla 在背后做支持的 Rust 或 Emscripten。

所有人都说这是肯定有用,并且体验感很好。我的同事 Arron Turnner 用 AssemblyScript 写了一套完整的模拟器。如果你因为使用它感到心累,那么你可以在 GitHub 上看他是如何写这个代码的。

建议

在这里,我想向各位明确的是,目前而言,将所有的东西都编译为 wasm 并不是一个明智的决定。JavaScript 和 WebAssembly 并不是竞争对手,相反它们更应该是合作关系。在一起使用它们的同时,并不会将对方所取代。比如调试 WebAssembly 会变得更难。还有在对 WebAssembly 代码做 code split(代码拆分)的时候,会比 javascript 更加困难。

你必须来回调用函数,这并不是一个很好的体验。已经有不少人在推特告诉我说,他们想用 C ++ 来编写他们的 Web 组件。

我不知道为什么他们想这么做,如果他们真的想这么做,我也不推荐。
我想说一些适合 WebAssembly 来做的一些事,比如做一些性能审查,测量你的性能瓶颈在哪里,看看 WebAssembly 是否可以帮助你。如果你有发现平台上的空白,你可以用其他语言来弥补它。
再次强调,WebAssembly 只是你的工具。

相关链接

  • Surma 介绍
  • Qt Examples For WebAssembly
  • Squoosh App(文中的主要例子)
  • Assemblyscript
  • Wasm Pack
  • Emscripten

联系我们

欢迎加群和我们交流

如果感兴趣可以关注我们的微信公众号

退出移动版