WebAssembly开发:真实的例子



WebAssembly的发布于2015年-但是,经过数年的发展,现在仍然很少有人可以在生产中夸耀它。 有关这些经验的材料更有价值:仍然缺少有关如何在实践中生活的第一手信息。

在HolyJS大会上,有关WebAssembly使用体验的报告在观众中获得了很高的评价,现在,该报告的文本版本已经专门为Habr编写(还附有视频)。



我的名字叫Andrey,我将向您介绍WebAssembly。 可以说,上个世纪我开始涉足网络,但是我很谦虚,所以我不会这么说。 在这段时间里,我设法同时在后端和前端工作,甚至进行了一些设计。 今天,我对WebAssembly,C ++和其他本机事物感兴趣。 我也非常喜欢排版并收集旧技术。

首先,我将讨论团队和我如何在我们的项目中实现WebAssembly,然后我们将讨论您是否需要WebAssembly的功能,并以一些技巧作为结尾,以防您希望自己实现它。

我们如何实现WebAssembly


我在Inetra工作,我们位于新西伯利亚,正在做一些自己的项目。 其中之一是ByteFog。 这是用于向用户交付视频的点对点技术。 我们的客户是分发大量视频的服务。 他们有一个问题:当某些流行事件发生时,例如某人的新闻发布会或某项体育赛事,如何不为此做准备,一大堆客户过来依靠服务器,而服务器令人难过。 客户此时的视频质量很差。

但是每个人都在观看相同的内容。 让我们要求用户的相邻设备共享视频片段,然后卸载服务器,节省带宽,用户将收到质量更好的视频。 这些云是我们的技术,我们的ByteFog代理服务器。



我们必须安装在可以显示视频的每台设备上,因此我们支持多种平台:Windows,Linux,Android,iOS,Web,Tizen。 选择在所有这些平台上使用单一代码库的哪种语言? 我们选择C ++是因为它具有最大的优势:-D更重要的是,我们在C ++方面拥有丰富的专业知识,它确实是一种快速的语言,并且在可移植性方面可能仅次于C。

我们有一个相当大的应用程序(900个类),但是运行良好。 在Windows和Linux下,我们可以编译为本机代码。 对于Android和iOS,我们建立了一个连接到应用程序的库。 我们将再次讨论Tizen,但在网络上我们曾经作为浏览器插件使用。

这是Netscape插件API技术。 顾名思义,它很旧,并且有一个缺点:它提供了对系统的广泛访问,因此用户代码可能会导致安全问题。 这可能就是为什么Chrome在2015年关闭了对该技术的支持,然后所有浏览器都加入了这一快闪族的原因。 因此,我们将近两年没有网络版本。

2017年,新的希望到来了。 您可能会想到,这是WebAssembly。 结果,我们为自己设定了将应用程序移植到浏览器的任务。 由于对Firefox和Chrome的支持已经在春季出现,并且到2017年秋季,Edge和Safari迅速崛起。

对于我们来说,使用现成的代码很重要,因为我们有很多业务逻辑,我们不想加倍,以免使错误数量加倍。 以编译器Emscripten为例。 他做了我们需要的工作-将肯定的应用程序编译到浏览器中,并在浏览器中重新创建本机应用程序熟悉的环境。 我们可以说Emscripten就是C ++代码的Browserify。 它还允许您将对象从C ++转发到JavaScript,反之亦然。 我们的第一个想法是:现在让我们来学习Emscripten,只进行编译,一切都会正常进行。 当然不是。 从此开始我们沿着耙子的旅程。

我们遇到的第一件事是成瘾。 我们的代码库中有几个库。 现在列出它们是没有意义的,但是对于那些了解的人,我们拥有Boost。 这是一个很大的库,允许您编写跨平台代码,但是用它配置编译非常困难。 我想将尽可能少的代码拖到浏览器中。

Bytefog架构


结果,我们确定了核心:可以说这是包含主要业务逻辑的代理服务器。 该代理服务器从两个来源获取数据。 第一个也是主要的一个是HTTP,即通往视频分发服务器的通道,第二个是我们的P2P网络,即从某个其他用户到另一个相同代理的通道。 我们主要将数据提供给播放器,因为我们的任务是向用户显示高质量的内容。 如果有资源,我们会将内容分发到P2P网络,以便其他用户可以下载它。 内部有一个智能缓存,可完成所有操作。



编译完所有这些内容后,我们将面临在浏览器沙箱中执行WebAssembly的事实。 这意味着它不能完成比JavaScript更强大的功能。 虽然本机应用程序使用许多特定于平台的内容,例如文件系统,网络或随机数。 所有这些功能都必须使用浏览器提供的功能在JavaScript中实现。 该板列出了列出的相当明显的替代品。



为了使之成为可能,有必要在本机应用程序中实现本机功能的实现,并在其中插入一个接口,即画出一定的边界。 然后,您可以使用JavaScript实现此功能,并保留本机实现,并且在组装过程中已经选择了必要的实现。 因此,我们查看了我们的建筑,发现了可以绘制边界的所有地方。 巧合的是,这是一个运输子系统。



对于每个这样的地方,我们都定义了一个规范,即,我们确定了一项合同:什么方法将是什么,它们将具有哪些参数,什么数据类型。 完成此操作后,您可以并行工作,每个开发人员都在他身边。

结果如何? 我们用通常的AJAX替换了提供商的主要视频传输渠道。 我们通过流行的HLS.js库向播放器发布数据,但如有必要,存在与其他播放器集成的根本可能性。 我们用WebRTC替换了整个P2P层。



作为编译的结果,获得了几个文件。 最重要的是二进制.wasm。 它包含浏览器将执行的已编译字节码,并且包含您所有的C ++旧版。 但是它本身不起作用,所谓的“胶水代码”是必需的,它也是由编译器生成的。 粘合代码正在下载一个二进制文件,然后将这两个文件都上传到生产环境。 出于调试目的,您可以生成汇编程序的文本表示形式-.wast文件和sourcemap。 您需要了解它们可能很大。 在我们的情况下,它们达到了100兆字节或更多。

收集捆绑


让我们仔细看一下粘合代码。 这是通常使用的老式ES5,组装成一个文件。 当我们将其连接到网页时,我们有一个包含所有实例化wasm模块的全局变量,该变量准备接受对其API的请求。

但是,对于用户将使用的库而言,包括一个单独的文件是一个相当严重的麻烦。 我们希望将所有内容打包在一起。 为此,我们使用Webpack和特殊的编译选项MODULARIZE。

它将粘合代码包装在“模块”模式中,我们可以选择它:如果我们在ES5上编写,则导入或使用require-Webpack冷静地理解了这种依赖性。 Babel遇到了一个问题-他不喜欢大量的代码,但这是ES5代码,不需要进行转置,只需添加一下即可忽略。

为了追求文件数量,我决定使用SINGLE_FILE选项。 它将编译产生的所有二进制文件转换为Base64形式,并将其作为字符串推送到粘合代码中。 听起来是个好主意,但之后捆绑包的大小变成了100兆字节。 Webpack,Babel甚至浏览器都无法在这样的卷上工作。 无论如何,我们不会强迫用户加载100兆字节?

如果您考虑一下,则不需要此选项。 粘性代码自行下载二进制文件。 他通过HTTP执行此操作,因此我们可以开箱即用地进行缓存,可以设置所需的任何标头,例如,启用压缩,并且WebAssembly文件可以完美压缩。

但是最酷的技术是流式编译。 也就是说,从服务器下载WebAssembly文件时,可以在数据到达时在浏览器中对其进行编译,这大大加快了应用程序的加载速度。 通常,所有WebAssembly技术都着眼于大型代码库的快速启动。

可以


该模块的另一个问题是它是Thenable对象,即它具有.then()方法。 此功能允许您在模块启动时挂起回调,这非常方便。 但我希望该界面与Promise相匹配。 thenable不是Promise,但没关系,让我们自己包装一下。 让我们编写这样一个简单的代码:

return new Promise((resolve, reject) => { Module(config).then((module) => { resolve(module); }); }); 

我们创建Promise,启动我们的模块,并作为回调我们调用resolve函数并传递我们在那里安装的模块。 一切似乎都很明显,一切都很好,我们正在启动-出了点问题,浏览器被冻结,DevTools挂起,并且处理器在计算机上升温。 我们什么都不懂-某种递归或无限循环。 调试非常困难,并且当我们中断JavaScript时,我们最终进入Emscripten模块中的Then函数。

 Module['then'] = function(func) { if (Module['calledRun']) { func(Module); } else { Module['onRuntimeInitialized'] = function() { func(Module); }; }; return Module; }; 

让我们更详细地看一下。 情节

 Module['onRuntimeInitialized'] = function() { func(Module); }; 

负责挂起回调。 一切都清楚了:一个异步函数调用了我们的回调。 我们想要的一切。 此功能还有另一部分。

 if (Module['calledRun']) { func(Module); 

模块已启动时将调用它。 然后立即立即同步调用回调,并将模块通过参数传递给它。 这模仿了Promise的行为,这似乎是我们所期望的。 但是那怎么了?

如果您仔细阅读该文档,可能会发现关于Promise有一个非常微妙的要点。 当我们使用Thenable解析Promise时,浏览器将解开该thenable的值,并为此执行.then()方法。 结果,我们解决了Promise,将模块传递给它。 浏览器问:那么这是一个对象吗? 是的,这是一个《神变》。 然后,在模块上调用.then()函数,并将resolve函数本身作为回调传递。

该模块检查其是否正在运行。 它已经在运行,因此回调被立即调用,并且相同的模块再次传递给它。 作为回调,我们具有resolve函数,浏览器会询问:这是Thenable对象吗? 是的,这是一个《神变》。 一切又重新开始。 结果,我们陷入了一个无尽的循环,浏览器从此永不返回。



我没有找到解决此问题的理想方法。 结果,我只需要在解析之前删除.then()方法即可,并且可以正常工作。

脚本


因此,我们编译了模块,组装了JS,但是缺少了一些东西。 我们可能需要做一些有用的工作。 为此,传输数据并连接JS和C ++这两个世界。 怎么做? Emscripten提供了三个选项:

  • 第一个是ccall和cwrap函数。 通常,您会在WebAssembly的一些教程中遇到它们,但是它们不适合实际工作,因为它们不支持C ++的功能。
  • 第二个是WebIDL活页夹。 它已经支持C ++函数,您已经可以使用它了。 这是一种严肃的界面描述语言,例如,W3C使用它们作为文档。 但是我们不想将其带入我们的项目,而是使用了第三个选项
  • 嵌入 可以说,这是Emscripten连接对象的一种本机方式,它基于C ++模板,并允许您通过将不同的实体从C ++转发到JS,反之亦然来做很多事情。


Embind可让您:

  • 从JavaScript代码调用C ++函数
  • 从C ++类创建JS对象
  • 从C ++代码转到浏览器API(如果出于某些原因,例如,可以用C ++编写整个前端框架)。
  • 对我们来说主要的事情是:实现C ++中描述的JavaScript接口。


资料交换


最后一点很重要,因为这正是您在移植应用程序时将不断执行的操作。 因此,我想更详细地介绍它。 现在将有C ++代码,但请不要害怕,它几乎就像TypeScript :-D

该方案如下:



在C ++方面,我们要向内核提供访问权限(例如,访问外部网络)以上传视频。 它曾经使用本机套接字来执行此操作,曾经有某种HTTP客户端执行了此操作,但是WebAssembly中没有本机套接字。 我们需要以某种方式退出,因此我们切断了旧的HTTP客户端,将接口插入此位置,并以任何方式使用常规AJAX在JavaScript中实现此接口。 之后,我们会将结果对象传递回C ++,内核将在其中使用它。

让我们做一个最简单的HTTP客户端,它只能发出get请求:

 class HTTPClient { public: virtual std::string get(std::string url) = 0; }; 

对于输入,它接收到带有要下载的URL的字符串,并输出到输出
请求结果的字符串。 在C ++中,字符串可以具有二进制数据,因此适合视频。 Emscripten让我们在这里写
如此恐怖的包装:



在其中,主要是两件事-C ++一侧的函数名称(我将其标记为绿色),以及JavaScript一侧的相应名称(由蓝色表示)。 结果,我们编写了一个通信声明:



它就像乐高积木一样工作,我们从中组装它。 我们有一个类,该类有一个方法,并且我们想从该类继承以实现接口。 仅此而已。 我们使用JavaScript并继承。 这可以通过两种方式完成。 首先是扩展。 这非常类似于Backbone的旧扩展。



该模块包含Emscripten编译的所有内容,并且具有带有导出接口的属性。 我们调用extend方法,并通过该方法的实现将一个对象传递给该方法,即,某些方法将在get函数中实现。
使用AJAX获取信息。

在输出时,extend为我们提供了常规的JavaScript构造函数。 我们可以根据需要多次调用它,并生成所需数量的对象。 但是在某些情况下,我们只有一个对象,而我们只想将其传递给C ++端。



为此,以某种方式将此对象绑定到C ++将理解的类型。 这是实现功能的作用。 在输出时,它不提供构造函数,而是一个现成的对象,即我们的客户端,我们可以将其返回给C ++。 例如,您可以这样做:

 var app = Module.makeApp(client, …) 

假设我们有一个工厂来创建我们的应用程序,并将其依赖项转换为参数,例如client和其他内容。 当此函数起作用时,我们将获得应用程序的对象,该对象已包含我们所需的API。 您可以执行相反的操作:

 val client = val::global(″client″); client.call<std::string>(″get″, val(...) ); 

直接从C ++,使我们的客户端脱离全局浏览器范围。 此外,代替客户端,可以有任何浏览器API,从控制台开始,以DOM API,WebRTC结尾-无论您喜欢什么。 接下来,我们调用该对象具有的方法,并将所有值包装在Emscripten为我们提供的魔术类val中。

装订错误


通常,仅此而已,但是当您开始开发时,绑定错误将等待您。 他们看起来像这样:



Emscripten试图帮助我们并解释问题所在。 如果所有这些都加起来,那么您需要确保它们是一致的(很容易将其密封起来并获得绑定错误):

  • 名字
  • 种类
  • 参数数量

Embind语法不仅对于前端供应商,而且对于处理C ++的人都是不同寻常的。 这是一种很容易出错的DSL,您需要遵循这一点。 说到接口,在JavaScript中实现某种接口时,有必要使其与合同中所述的接口完全匹配。

我们有一个有趣的案例。 我的同事Jura(曾参与C ++方面的项目)使用Extend测试了他的模块。 他们为他工作得很完美,所以他把他们交给了我。 我使用实现将这些模块集成到JS项目中。 他们停止为我工作。 当我们弄清楚时,结果发现当绑定函数名称时,我们会遇到错字。

顾名思义,Extend是接口的扩展,因此,如果将其密封在某个地方,Extend不会抛出错误,它将决定您只是添加了一个新方法,就可以了。

也就是说,它将隐藏绑定错误,直到调用方法本身为止。 我建议在适合您的所有情况下使用Implement,因为它会立即检查转发接口的正确性。 但是,如果需要扩展,则必须用测试覆盖每个方法的调用,以免弄乱它。

扩展和ES6


Extend的另一个问题是它不支持ES6类。 当您继承从ES6类派生的对象时,Extend期望其中的所有属性都是可枚举的,但使用ES6则不是。 这些方法在原型中,并且具有可枚举的值:false。 我使用这样的拐杖,在其中检查原型并打开可枚举的值:true:

 function enumerateProto(obj) { Object.getOwnPropertyNames(obj.prototype) .forEach(prop => Object.defineProperty(obj.prototype, prop, {enumerable: true}) ) } 

我希望有一天我能摆脱它,因为Emscripten社区中有关于改善对ES6的支持的讨论。

内存


说到C ++,就不得不提到内存。 当我们检查了标清质量的视频中的所有内容时,一切都很好,效果很好! 一旦我们完成了FullHD测试,就没有内存错误。 没关系,有TOTAL_MEMORY选项,它设置模块的起始内存值。 我们腾出了半GB的存储空间,一切都很好,但是对于用户来说这是不人道的,因为我们为所有人保留了内存,但并不是每个人都订阅了FullHD内容。

还有另一个选项-ALLOW_MEMORY_GROWTH。 它可以让您增加记忆力
根据需要逐步。 它的工作方式如下:默认情况下,Emscripten为模块提供16 MB的运行空间。 当您全部使用它们时,将分配新的内存。 所有旧数据都将复制到此处,而新数据仍具有相同的空间。 直到达到4 GB为止,这种情况都会发生。

假设您分配了256 MB的内存,但是可以肯定地知道您认为应用程序有192 MB。那么其余的内存将被低效地使用。 您突出显示了它,从用户那里拿走了它,但是什么也不做。 我想以某种方式避免这种情况。 有一个小技巧:我们将内存增加一倍半开始工作。 然后,在第三步中,我们达到192 MB,这正是我们所需要的。 我们减少了剩余的内存消耗,节省了不必要的内存分配,而且花费的时间也更长。 因此,我建议同时使用这两个选项。

依赖注入


似乎仅此而已,但随后耙数增加了一点。 依赖注入存在问题。 我们编写最简单的需要依赖的类。

 class App { constructor(httpClient) { this.httpClient = httpClient } } 

例如,我们将HTTP客户端传递给应用程序。 我们保存在class属性中。 看起来一切都会很好。

 Module.App.extend( ″App″, new App(client) ) 

我们从C ++接口继承,首先创建我们的对象,将依赖项传递给它,然后继承。 在继承时,Emscripten对对象进行了一些不可思议的操作。 最容易想到的是,它杀死了一个旧对象,根据其模板创建了一个新对象,并将所有公共方法拖到那里。 但是同时,对象的状态会丢失,并且您会得到一个未形成且无法正常工作的对象。 解决这个问题非常简单。 必须使用在继承阶段之后起作用的构造函数。

 class App { _construct(httpClient) { this.httpClient = httpClient this._parent._construct.call(this) } } 

我们几乎做同样的事情:我们将依赖项存储在对象的字段中,但这是继承后出现的对象。 我们一定不要忘记将构造函数调用转发给位于C ++端的父对象。 最后一行是ES6中super()方法的类似物。 在这种情况下,继承是这样发生的:

 const appConstr = Module.App.extend( ″App″, new App() ) const app = new appConstr(client) 

首先,我们继承,然后创建一个新对象,该依赖已经传递到该对象中,并且可以正常工作。

指针技巧


另一个问题是通过指针将对象从C ++传递到JavaScript。 我们已经做了一个HTTP客户端。 为了简单起见,我们错过了一个重要的细节。

 std::string get(std::string url) 

该方法立即返回该值,也就是说,结果表明请求应该是同步的。 但是毕竟,AJAX请求AJAX并且它们是异步的,因此在现实生活中,该方法将不返回任何内容,或者我们可以返回请求ID。 但是为了让某人返回答案,我们将侦听器作为第二个参数传递,其中将包含来自C ++的回调。

 void get(std::string url, Listener listener) 

在JS中,它看起来像这样:

 function get(url, listener) { fetch(url).then(result) => { listener.onResult(result) }) } 

我们有一个使用此侦听器对象的get函数。 我们开始下载文件并挂断回调。 下载文件后,我们从侦听器中提取所需的功能并将结果传递给它。

看起来这个计划不错,但是当get函数完成时,所有局部变量都将被销毁,并且函数参数(即指针)将与它们一起销毁,并且运行时脚本将销毁C ++端的对象。

结果,当调用行listener.onResult(结果)时,监听器将不再存在,并且在访问它时,将发生内存访问错误,这将导致应用程序崩溃。

我想避免这种情况,并且有一个解决方案,但是花了好几个星期才找到它。

 function get(url, listener) { const listenerCopy = listener.clone() fetch(url).then((result) => { listenerCopy.onResult(result) listenerCopy.delete() }) } 

事实证明,有一种克隆指针的方法。 出于某种原因,它没有记录,但是可以正常工作,并且允许您增加Emscripten指针中的引用计数。 这使我们可以将其挂起在关闭中,然后,当我们启动回调时,此指针将可以访问我们的侦听器,并且可以根据需要进行工作。

最重要的是不要忘记删除该指针,否则将导致内存泄漏错误,这是非常糟糕的。

快速写入内存


当我们下载视频时,这些信息是相对大量的,我想减少来回复制数据的数量,以节省内存和时间。 如何从JavaScript直接将大量信息写入WebAssembly内存,这是一个技巧。

 var newData = new Uint8Array(…); var size = newData.byteLength; var ptr = Module._malloc(size); var memory = new Uint8Array( Module.buffer, ptr, size ); memory.set(newData); 

newData是我们作为类型化数组的数据。 我们可以获取其长度,并从WebAssembly模块请求分配所需大小的内存。 malloc函数将返回一个指向我们的指针,该指针只是包含WebAssembly中所有内存的数组的索引。 从JavaScript方面来看,它看起来像ArrayBuffer。

下一步,我们将从某个位置切入一个大小合适的ArrayBuffer窗口,然后将数据复制到那里。 尽管set操作具有复制语义,但是当我在探查器中查看此部分时,并没有看到很长的过程。 我认为浏览器借助移动语义来优化此操作,即将内存的所有权从一个对象转移到另一个对象。

在我们的应用程序中,我们还依靠移动语义来节省内存复制。

Adblock


一个有趣的问题,就是Adblock的变化。 事实证明,在俄罗斯,所有流行的阻止程序都收到RU Adlist的订阅,并且它有一条很棒的规则,禁止从第三方站点下载WebAssembly。 例如,带有CDN。



出路不是使用CDN,而是将所有内容存储在您的域中(这不适合我们)。 或重命名.wasm文件,使其不符合此规则。 您仍然可以前往这些同志的论坛,并尝试说服他们删除此规则。 我认为他们通过与矿工打架为自己辩护,尽管我不知道为什么矿工无法猜测重命名文件的原因。

生产量


结果,我们投入了生产。 是的,这并不容易,花了八个月的时间,我想问问自己是否值得。 我认为-值得:

无需安装


我们知道我们的代码无需安装任何程序即可交付给用户。 当我们有了浏览器插件时,用户必须下载并安装它,这对于技术分发来说是一个巨大的过滤器。 现在,用户只是在现场观看视频,甚至不知道整个机械都是在引擎盖下工作的,那里的一切都很复杂。 浏览器仅下载带有代码的其他文件,例如图片或.css。

在不同平台上的统一代码库和调试


同时,我们能够维护我们的单一代码库。 我们可以在不同的平台上扭曲相同的代码,并且反复发生在一个平台上看不见的错误出现在另一个平台上。 因此,我们可以使用不同平台上的不同工具来检测隐藏的错误。

快速释放


我们得到了快速发布,因为我们可以将其发布为简单的Web应用程序,并在每个新版本中更新C ++代码。 它与发布新插件,移动应用程序或SmartTV应用程序的方式无法相比。 版本仅取决于我们:当我们需要时,它将被释放。

快速反馈


这意味着快速反馈:如果出现问题,我们可以在白天发现问题并做出响应。

我相信所有这些问题都值得这些优势。 并非每个人都有C ++应用程序,但是如果您有一个C ++应用程序,并且希望将其包含在浏览器中-WebAssembly对您来说是100%用例。

申请地点


并非每个人都用C ++编写。 但是,不仅C ++可用于WebAssembly。 是的,从历史上看,这是早期Mozilla技术asm.js中仍然可用的第一个平台。 顺便说一下,因此,它有很好的工具, 它们比技术本身还老。

铁锈


Mozilla也正在开发新的Rust语言,现在在工具方面正在赶超C ++。 一切都到了使他们成为WebAssembly最酷的开发过程的地步。

Lua,Perl,Python,PHP等


几乎所有可以解释的语言都已在WebAssembly中提供,因为它们的解释器是用C ++编写的,因此它们可以简单地编译为WebAssembly,现在您可以在浏览器中扭曲PHP。

去吧


在1.11版中,他们承诺在WebAssembly中进行编译的beta版,在2.0版中,他们承诺将提供发行支持。 他们的支持稍后出现,因为WebAssembly不支持垃圾收集器,而Go是一种托管内存语言。 因此,他们不得不将垃圾回收器拖到WebAssembly下。

科特林/本地人


关于Kotlin的故事也差不多。 他们的编译器具有实验支持,但是他们还必须对垃圾收集器做些事情。 我不知道这是什么状态。

3D-


? , — 3D-. , , asm.js WebAssembly . , WebAssembly.




, : , , . , .





. , , , , . , , ; — .



, Google Chrome, , WebAssembly-. npm- , Wasm, JS. , ++ - — .

HunSpell — Wasm .


— « ». , - , — OpenSSL. WebAssembly. OpenSSL — , , .


use case wotinspector.com. World of Tanks. , , , , , .

— . , , . , , - ++, WebAssembly, ( , ).

. , , . . , , , , . . .


, , ++. , FFmpeg, . , ffmpeg. . , , , , .



— . OpenCV — , WebAssembly, . PDF. SQLite, SQL. SQLite WebAssembly Emscripten, .

Node.js





WebAssembly, Node.js. , Sass — css. Ruby, ++ ( libsass). , Webpack', Node.js. node-sass , JS- .

, , . . :



, node-sass 100 . , ( ) . WebAssembly : , WebAssembly .

Node. , WebAssembly libsass-asm . , . WebAssembly …


Figma — web-. - Sketch, , . ++ ( ), asm.js. , .



WebAssembly, , 3 . , .

Visual Studio Code, , Electron, , , Node-sass. , Node, . , , , WebAssembly.





— AutoCAD. 30 , ++, . , , - JavaScript, , . WebAssembly AutoCAD - , 5 .

, , , , , , , , . FFMpeg — , — QEMU. , , KVM, .



2011 QEMU . , . , Linux , Linux-, , - .

, . bash, , Linux. — GUI . . , , …



, , - . Windows 2000 , , 18 , . , Chrome ( FireFox).

, WebAssembly , , , , .


, WebAssembly. , — , . — , .



, C++ web-. , , — . — , , , .

, . , C++, JavaScript, . , C++. , JS C++, .

— .



CI Pipeline


? JS- , Webpack. , , ( ), JS. webpack watch, , .




, . , , .

Chrome DevTools, Sources wasm-. ( - ), , , .



, , : «, , , , , !». , embedded-, , - .

: -g4 wast- , .



, 100 ( FAR). — , Chrome. E:/_work/bfg/bytefrog/… — . , ++ . , SourceMap!

SourceMap


, .
  • Firefox.
  • --sourcemap-base=http://localhost , SourceMap -, .
  • HTTP.
  • .
  • Windows «:» . .


. CMake , URL -. : wast- , . , .

, :



++ . ! , , stack trace, . , wasm- stack trace, , , , , .



, — SourceMap . , , . , .



«var0».



, . , SourceMap, , .


. Chrome, Firefox. Firefox — «» , , .



Chrome ( , , Mangled ), , , , .




. , :

  • . runtime, . ++ Rust Go.
  • JS — Wasm. , JS Wasm. -, , . , .
  • . , , , .
  • Wasm . Wasm , JS. WebAssembly , .
  • JS.


: .

  • wasp_cpp_bench
  • Chrome 65.0.3325.181 (64-bit)
  • Core i5-4690
  • 24gb ram
  • 5 ; max min;


. JS — , .



++, , - . Grayscale. C++ , . ( ), , JS. , , , ++, .


Sentry, — wasm. , traceKit, Sentry — Raven, — , , wasm . , , , pull request, npm install JS-.



. production, , . debug-, , :




  • WebAssembly , .
  • — . 8 , C++, , .
  • , , WebAssembly — .
  • — JS. JS- , «» , , .


, :
  • Emscripten Embind. .
  • - Emscripten — . , , 3000 Emscripten.
  • Sentry.
  • Firefox.


感谢您的关注! .



HolyJS, : 24-25 HolyJS . (, Node.js Ryan Dahl!), — 1 .

Source: https://habr.com/ru/post/zh-CN441140/


All Articles