{"id":21087,"date":"2018-04-30T13:26:23","date_gmt":"2018-04-30T11:26:23","guid":{"rendered":"http:\/\/www.inovex.de\/blog\/?p=12955"},"modified":"2022-11-29T08:02:52","modified_gmt":"2022-11-29T07:02:52","slug":"webassembly-production","status":"publish","type":"post","link":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/","title":{"rendered":"WebAssembly in Production"},"content":{"rendered":"<p>WebAssembly (or wasm for short) is a relatively new way to efficiently execute code in a browser. Since late 2017, it is supported in all modern major browsers (Chrome, Firefox, Safari and Edge) which makes it worth a look for real-world applications. It is the successor of Mozillas asm.js project, which compiled C or C++ code directly to JavaScript. WebAssembly goes a step further and defines a new binary format for a machine-near bytecode representation which can easily be translated to executable assembly instructions by the execution engine while maintaining a safe execution context for untrusted code. This skips the computation-intensive parsing and JIT-compiling of JavaScript which has a bunch of different advantages shown in the Evaluation chapter.<!--more--><\/p>\n<p>Browser vendors did an amazing job in running code in a language as dynamic as JavaScript as fast as somehow possible, but by doing so the JS-engines became very complicated with multiple stages a completely separate optimizing compilers, multiple implementations of the same thing for different scenarios and generally a big portion of <a href=\"http:\/\/www.reactiongifs.com\/r\/mgc.gif\" target=\"_blank\" rel=\"noopener\">black magic<\/a>. This causes JS-programs to have highly varying performance in different browsers. The WebAssembly representation on the other hand is straightforward to compile to native assembly instructions and produces much more consistent performance characteristics across different browsers. Keep in mind that this probably doesn&#8217;t matter for a simple React app showing cute kitten images, but comes into play in larger and\/or computation-heavy applications executed in the browser. Besides advantages in runtime behavior, WebAssembly can be a compile time target for a lot of different languages which opens up the web platform for much more developers. While support for compiler is still evolving, C, C++ and Rust can already be <a href=\"https:\/\/github.com\/appcypher\/awesome-wasm-langs\" target=\"_blank\" rel=\"noopener\">compiled<\/a> to WebAssembly.<\/p>\n<p>Wrapping up, there are three main reasons to use WebAssembly:<\/p>\n<ul>\n<li>Delivering existing C\/C++ applications over the web (Talking about things like <a href=\"https:\/\/hacks.mozilla.org\/2017\/07\/webassembly-for-native-games-on-the-web\/\" target=\"_blank\" rel=\"noopener\">games<\/a>, <a href=\"https:\/\/app.formit.autodesk.com\/\" target=\"_blank\" rel=\"noopener\">3D Graphics<\/a> and <a href=\"https:\/\/github.com\/kripken\/emscripten\/wiki\/Porting-Examples-and-Demos\" target=\"_blank\" rel=\"noopener\">more<\/a>).<\/li>\n<li>Developing in your language of choice (for example <a href=\"https:\/\/github.com\/aspnet\/Blazor\" target=\"_blank\" rel=\"noopener\">.NET<\/a> or <a href=\"https:\/\/github.com\/konsoletyper\/teavm\" target=\"_blank\" rel=\"noopener\">Java<\/a>).<\/li>\n<li>Accelerating hot code portions of ordinary JavaScript apps.<\/li>\n<\/ul>\n<p>WebAssembly hasn&#8217;t become &#8222;mainstream&#8220; yet which means tools and frameworks are in most cases not stable and still developed on; .NET and Java support is still highly experimental. For simple programs which mainly do computation and don&#8217;t rely much on external APIs however the available tooling is completely sufficient even in real-world environments.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\"><p class=\"ez-toc-title\" style=\"cursor:inherit\"><\/p>\n<\/div><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#Implementing-AES-with-WebAssembly\" >Implementing AES with WebAssembly<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#Compiling\" >Compiling<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#Initializing\" >Initializing<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#JS-ify\" >JS-ify<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#Evaluation\" >Evaluation<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#Summary\" >Summary<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Implementing-AES-with-WebAssembly\"><\/span>Implementing AES with WebAssembly<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>This article describes how the third use case can be covered by tools available today. The client side encryption and decryption using the widespread AES algorithm serves as an example to show how such code packages can be made accessible from ordinary JavaScript applications. Since AES is a standard algorithm and not something application specific, the resulting JavaScript package will be published as a standalone npm package which can be used in arbitrary web applications. The goal here is to make WebAssembly accelerated modules available and consumable as easy and simple as conventional JavaScript modules without the developer being aware of wasm-specific quirks. Of course the advantages of WebAssembly (a small file size and high performance) shouldn&#8217;t get lost in the process. A project following a related but due to the size of the project more complex approach is <a href=\"https:\/\/github.com\/jedisct1\/libsodium.js\" target=\"_blank\" rel=\"noopener\">libsodium.js<\/a> which delivers the whole sodium crypto library as a WebAssembly module.<\/p>\n<p>There are <a href=\"https:\/\/github.com\/ricmoo\/aes-js\" target=\"_blank\" rel=\"noopener\">various<\/a> <a href=\"https:\/\/github.com\/bitwiseshiftleft\/sjcl\" target=\"_blank\" rel=\"noopener\">implementations<\/a> of AES in plain JavaScript which can serve as a baseline to compare the WebAssembly approach from a performance perspective.<\/p>\n<p>For the library a slightly simplified version of <a href=\"https:\/\/www.ghostscript.com\/doc\/base\/aes.c\" target=\"_blank\" rel=\"noopener\">this implementation<\/a> will be used for the actual computation. It doesn&#8217;t have external dependencies which promises a small build size and doesn&#8217;t use features <a href=\"http:\/\/webassembly.org\/docs\/future-features\/\" target=\"_blank\" rel=\"noopener\">not available yet<\/a> in WebAssembly like SIMD or multithreading.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Compiling\"><\/span>Compiling<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>To turn the C code into WebAssembly, a working compiler infrastructure is necessary. This is provided by the <a href=\"https:\/\/github.com\/WebAssembly\/binaryen\" target=\"_blank\" rel=\"noopener\">binaryen project<\/a> which in turn depends on LLVM (a C\/C++ compiler). Binaryen provides tools to transform the output of LLVM into WebAssembly. It is possible to manually invoke LLVM and Binaryen with the correct parameters, but fortunately the emscripten compiler of the\u00a0WebAssembly\u00a0predecessor asm.js also <a href=\"https:\/\/github.com\/kripken\/emscripten\/wiki\/WebAssembly\" target=\"_blank\" rel=\"noopener\">supports<\/a>\u00a0WebAssembly and neatly integrates the different tools behind a simple command line utility.<\/p>\n<p>If you are using a Mac and Homebrew, installing emscripten is as simple as running <span class=\"lang:sh decode:true crayon-inline \">brew install emscripten<\/span>, for other environments please refer to\u00a0the <a href=\"https:\/\/kripken.github.io\/emscripten-site\/docs\/getting_started\/downloads.html\" target=\"_blank\" rel=\"noopener\">emscipten website<\/a>.<\/p>\n<p>To trigger the compile process, run <span class=\"lang:sh decode:true crayon-inline \">emcc aes.c -s WASM=1 -o aes.js<\/span>. This tells emscripten to compile the aes.c file using the binaryen wasm backend. The <span class=\"lang:sh decode:true crayon-inline \">-o<\/span> flag specifies the output file. Besides the <span class=\"lang:sh decode:true crayon-inline \">aes.js<\/span>\u00a0file, a <span class=\"lang:sh decode:true crayon-inline \">wasm<\/span>\u00a0file with the same name is created containing the actual WebAssembly byte code. The JavaScript file contains a generic wrapper generated by emscripten to initialize the module and call the provided WebAssembly functions from JavaScript. Because this wrapper offers way too many things (with a more than four times bigger file size than the WebAssembly module it is wrapping) which aren&#8217;t necessary for the use case at hand, we won&#8217;t use this wrapper but write our own in the next section.<\/p>\n<p>Per default, emscripten is creating an unoptimized debug build. To optimize for runtime performance, we have to pass the <span class=\"lang:sh decode:true crayon-inline \">-O3<\/span> flag. Also, we have to specify which methods of the C program have to be included in the WebAssembly build. A look into aes.c shows that there are multiple methods necessary to control the encryption\/decryption process:<\/p>\n<p><span class=\"lang:c decode:true crayon-inline \">aes_setkey_dec<\/span> and <span class=\"lang:c decode:true crayon-inline \">aes_setkey_enc<\/span> to initialize an encryption\/decryption context and <span class=\"lang:c decode:true crayon-inline \">aes_crypt_cfb<\/span> and <span class=\"lang:c decode:true crayon-inline \">aes_crypt_cbc<\/span> to perform the actual encryption\/decryption in <a href=\"https:\/\/en.wikipedia.org\/wiki\/Block_cipher_mode_of_operation#Output_Feedback_(OFB)\" target=\"_blank\" rel=\"noopener\">output feedback mode<\/a> respectively <a href=\"https:\/\/en.wikipedia.org\/wiki\/Block_cipher_mode_of_operation#Cipher_Block_Chaining_(CBC)\" target=\"_blank\" rel=\"noopener\">cipher block chaining mode<\/a>. For simplicity of the example, we will focus on CBC mode in this implementation. Emscripten can be instructed to only export the necessary methods by providing the <span class=\"lang:c decode:true crayon-inline \">EXPORTED_FUNCTIONS<\/span> array as an additional flag: <span class=\"lang:sh decode:true crayon-inline \">-S &#8222;EXPORTED_FUNCTIONS=[&#8218;_aes_setkey_dec&#8216;, &#8218;_aes_setkey_enc&#8216;, &#8218;_aes_crypt_cbc&#8216;]&#8220;<\/span>. Note the leading underscores in the function names; emscripten automatically adds these to all C functions.<\/p>\n<p>This means the final compilation command looks like this:<\/p>\n<pre class=\"lang:sh decode:true\">emcc aes.c \\\r\n\r\n  -s WASM=1 \\\r\n\r\n  -O3 \\\r\n\r\n  -s \"EXPORTED_FUNCTIONS=['_aes_setkey_dec', '_aes_setkey_enc', '_aes_crypt_cbc']\" \\\r\n\r\n  -o aes.js<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Initializing\"><\/span>Initializing<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>To execute the compiled program, the WebAssembly byte code has to be loaded into the browser and initialized. Generally the <span class=\"lang:js decode:true crayon-inline\">WebAssembly.instantiateStreaming <\/span>\u00a0<a href=\"https:\/\/hacks.mozilla.org\/2018\/01\/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler\/\" target=\"_blank\" rel=\"noopener\">API<\/a> is the best choice to do so as it starts compiling and instantiating the module while downloading which can significantly reduce startup times for large modules. However, for small modules as in this case, the benefit isn&#8217;t big enough to justify the additional overhead in setting up everything correctly\u2014the web server has to serve the WebAssembly code with the correct Content-Type header which isn&#8217;t part of the default configuration yet in most environments and widely used bundling setups (e.g. create-react-app) don&#8217;t support WebAssembly yet. This could change in the next few months as e.g. webpack 4 already supports WebAssembly modules and actively works on <a href=\"https:\/\/github.com\/webpack\/webpack\/issues?q=is%3Aissue+is%3Aopen+label%3A%22Web+Assembly%22\" target=\"_blank\" rel=\"noopener\">improving the integration<\/a>. For now though the best results for small modules like this one can be achieved by inlining the WebAssembly byte code into the regular JavaScript using the <span class=\"lang:sh decode:true crayon-inline \">wasm-loader<\/span> webpack plugin. By doing so the WebAssembly module becomes a regular JavaScript module which ensures compatibility with existing JavaScript tools like older webpack versions, alternative bundlers like RollupJS and gulp workflows.<\/p>\n<p>By using webpack and the wasm-loader, the compiled module can by initialized like this:<\/p>\n<pre class=\"lang:js decode:true\">import aesWasm from '.\/aes.wasm';\r\n\r\nconst imports = {\/* ... *\/};\r\n\r\naesWasm(imports).then((aesWasmInstance) =&gt; {\r\n\r\n  \/\/ call exported functions\r\n\r\n  aesWasmInstance.instance.exports._aes_setkey_dec(\/* ... *\/);\r\n\r\n});<\/pre>\n<p>The wasm module returns a function as default export which initializes a new instance of the WebAssembly module. The parameter of this function defines the imports which are passed to the WebAssembly module. Like an ES6 module a WebAssembly module can define things like APIs and constants it depends on. In difference to regular ES6 modules the WebAssembly code has no access to Browser APIs for network fetching, DOM manipulation etc. It runs in a completely separated sandbox with the only communication channels to the outside world being the things being passed in as the imports object and the things being exported in the <span class=\"lang:js decode:true crayon-inline \">instance.exports<\/span> object. Even the memory the module operates on has to be passed in as an import. A WebAssembly module can be thought of as a separate VM running inside JavaScript which means the JavaScript environment has to simulate the context of this VM. If for instance the C code compiled to WebAssembly tries to access the file system, the resulting WebAssembly module will require the respective syscall implementations to be passed in via the imports. In this case the file system would have to be emulated by the JavaScript context.<\/p>\n<p>In our case the WebAssembly module is fairly simple and doesn&#8217;t call any system APIs which results in a simple imports object. The AES module needs a local memory to buffer intermediate results of the encryption and a pointer to know where in this local memory to store these.<\/p>\n<p>The resulting code looks like this:<\/p>\n<pre class=\"lang:js decode:true\">import aesWasm from '.\/aes.wasm';\r\n\r\nconst imports = {\r\n\r\n  memory: new WebAssembly.Memory({ initial: 256, maximum: 256 }),\r\n\r\n  STACKTOP: 0;\r\n\r\n};\r\n\r\nconst byteView = new Uint8Array(memory.buffer);\r\n\r\nconst dataToEncrypt = [\/* ... *\/];\r\n\r\naesWasm(imports).then((aesWasmInstance) =&gt; {\r\n\r\n  \/\/ transfer data into the wasm memory\r\n\r\n  byteView.set(dataToEncrypt, 0x1234567);\r\n\r\n  \/\/ call exported functions\r\n\r\n  aesWasmInstance.instance.exports._aes_crypt_cbc(\/* ... *\/);\r\n\r\n  \/\/ transfer results back to JavaScript\r\n\r\n  const encryptedData = byteView.subarray(0x1234567, 0x1234567 + dataToEncrypt.length)\r\n\r\n})<\/pre>\n<p><span class=\"lang:js decode:true crayon-inline \">new WebAssembly.Memory({ initial: 256, maximum: 256 })<\/span> allocates a new portion of memory (256 being the number of 64kb pages) which is used by the WebAssembly module. The memory object can also be used to transfer data in and out of the WebAssembly VM.<\/p>\n<p>In our case for instance the data to be encrypted has to be made accessible by the WebAssembly code. But you can&#8217;t pass in a JS array into a WebAssembly function because in a C program arrays are just pointers to an address in memory. To hand the data array over to WebAssembly, we have to copy it into the memory object. <span class=\"lang:c decode:true crayon-inline \">memory.buffer<\/span> is an ArrayBuffer reference to the WebAssembly memory which can be used to transfer data. With <span class=\"lang:c decode:true crayon-inline \">const byteView = new Uint8Array(memory.buffer)<\/span>, the ArrayBuffer is made accessible as a TypedArray of bytes. By calling <span class=\"lang:c decode:true crayon-inline \">byteView.set(myData, pointerToTheData)<\/span>, the <span class=\"lang:c decode:true crayon-inline \">myData<\/span> array is copied to address <span class=\"lang:c decode:true crayon-inline\">pointerToTheData<\/span> inside the WebAssembly controlled memory. <span class=\"lang:c decode:true crayon-inline \">pointerToTheData<\/span> in this case is a simple integer value representing the address in the memory. Now this memory address can be given to an exported WebAssembly function which in turn can use it to access the data placed at this position.<\/p>\n<p>To extract the decrypted data, the same trick can be applied\u2014by using <span class=\"lang:c decode:true crayon-inline \">const extractedData = byteView.subarray(pointerToTheData, pointerToTheData + dataLength)<\/span>, the data can be transferred back into ordinary JavaScript memory.<\/p>\n<p>Of course this method of copying data around is annoying to use for a JavaScript-accustomed developer. To make the module more convenient to use, some glue code is necessary to hide these implementation details behind a clean, JavaScript-like API.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"JS-ify\"><\/span>JS-ify<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>It is not safe to manipulate the data in the WebAssembly memory as the WebAssembly code itself doesn&#8217;t expect it. If <span class=\"lang:c decode:true crayon-inline \">pointerToTheData<\/span> points to an address where WebAssembly stores internal data, it would be overwritten which could lead to errors in the calculation. To prevent this, it would be possible to also export the functions <span class=\"lang:c decode:true crayon-inline \">malloc<\/span> and <span class=\"lang:c decode:true crayon-inline \">free<\/span> of the standard library. By calling these exported methods we can tell the WebAssembly which memory ranges are blocked and safely use these. However, this dynamic memory management comes at a cost because the implementation of <span class=\"lang:c decode:true crayon-inline \">malloc<\/span> and <span class=\"lang:c decode:true crayon-inline \">free<\/span>\u00a0would also have to be included into the WebAssembly module.<\/p>\n<p>In simple modules like the one at hand we can do without a fully fledged memory management to keep the bundle small and just lay out our memory statically. The used AES implementation requires the following portions of memory:<\/p>\n<ul>\n<li>The encryption key (32 bytes in case of AES256)<\/li>\n<li>The IV (16 bytes),<\/li>\n<li>An encryption context holding various internal information (a struct containing <span class=\"lang:c decode:true crayon-inline \">int<\/span>, a pointer and an array of 68 <span class=\"lang:c decode:true crayon-inline \">long<\/span>s which adds up to 276 bytes)<\/li>\n<li>A decryption context of the same size as the encryption context.<\/li>\n<\/ul>\n<p>The rest of the memory can be used to hold the data which has to be encrypted or decrypted. The pointers to these memory ranges can be created like this:<\/p>\n<pre class=\"lang:c decode:true \">let currentTop = 2**14;\r\n\r\nconst encryptionContextPointer = currentTop;\r\n\r\ncurrentTop += 276;\r\n\r\nconst decryptionContextPointer = currentTop;\r\n\r\ncurrentTop += 276;\r\n\r\nconst keyPointer = currentTop;\r\n\r\ncurrentTop += 32;\r\n\r\nconst ivPointer = currentTop;\r\n\r\ncurrentTop += 16;\r\n\r\nconst blockPointer = currentTop;<\/pre>\n<p>The static memory allocation starts at memory address 16384 (2^14) which leaves the first memory page to WebAssembly for intermediary results saved on the stack.<\/p>\n<p>Our module will export three functions: <span class=\"lang:c decode:true crayon-inline \">init<\/span>\u00a0to initialize the WebAssembly instance with key and iv and <span class=\"lang:c decode:true crayon-inline \">encrypt<\/span> and <span class=\"lang:c decode:true crayon-inline \">decrypt<\/span> for the actual data handling. As expected in a JavaScript context, data, key and iv arrays can be passed directly as parameters of the three functions which will handle the WebAssembly memory management:<\/p>\n<pre class=\"lang:js decode:true\">{\r\n\r\n  init: (key, iv) =&gt; {\r\n\r\n    byteView.set(iv, ivPointer);\r\n\r\n    byteView.set(key, keyPointer);\r\n\r\n    instance.exports._aes_setkey_enc(encryptionContextPointer, keyPointer, keySize);\r\n\r\n    instance.exports._aes_setkey_dec(decryptionContextPointer, keyPointer, keySize);\r\n\r\n  },\r\n\r\n  encrypt: data =&gt; {\r\n\r\n    byteView.set(data, blockPointer);\r\n\r\n    \/\/ use the exported function aes_crypt_cbc(context, isEncrypt, dataLength, iv, dataIn, dataOut)\r\n\r\n    instance.exports._aes_crypt_cbc(encryptionContextPointer, 1, data.length, ivPointer, blockPointer, blockPointer);\r\n\r\n    return byteView.subarray(blockPointer, blockPointer + data.length).slice();\r\n\r\n  },\r\n\r\n  decrypt: data =&gt; {\r\n\r\n    byteView.set(data, blockPointer);\r\n\r\n    \/\/ use the exported function aes_crypt_cbc(context, isEncrypt, dataLength, iv, dataIn, dataOut)\r\n\r\n    instance.exports._aes_crypt_cbc(decryptionContextPointer, 0, data.length, ivPointer, blockPointer, blockPointer);\r\n\r\n    return byteView.subarray(blockPointer, blockPointer + data.length).slice();\r\n\r\n  }\r\n\r\n}<\/pre>\n<p>And that&#8217;s it\u2014by putting the parts together and compiling the JavaScript glue module with webpack, the former C implementation of AES is opaquely wrapped into a regular JavaScript package which can be used in any web application as long as the executing <a href=\"https:\/\/caniuse.com\/#search=webassembly\" target=\"_blank\" rel=\"noopener\">browser supports<\/a>\u00a0WebAssembly.<\/p>\n<p>The code shown here with a few extra bells and whistles is <a href=\"https:\/\/github.com\/flash1293\/aes-wasm\" target=\"_blank\" rel=\"noopener\">available at Github<\/a> and <a href=\"https:\/\/www.npmjs.com\/package\/aes-wasm\" target=\"_blank\" rel=\"noopener\">npm<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Evaluation\"><\/span>Evaluation<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>To evaluate whether the development overhead is worth the more complicated implementation, the average computation time for different sizes of input data has to be measured. The benchmark test can be found <a href=\"https:\/\/flash1293.github.io\/aes-wasm-benchmark\/\" target=\"_blank\" rel=\"noopener\">here<\/a> with the <a href=\"https:\/\/github.com\/flash1293\/aes-wasm-benchmark\" target=\"_blank\" rel=\"noopener\">source code on Github<\/a>.<\/p>\n<p>To get an idea of the performance of a comparable plain JavaScript library, the AES implementation of the <a href=\"https:\/\/github.com\/bitwiseshiftleft\/sjcl\" target=\"_blank\" rel=\"noopener\">Stanford JavaScript Crypto Library<\/a> was used. Because of inefficient handling of arrays, the <a href=\"https:\/\/github.com\/ricmoo\/aes-js\" target=\"_blank\" rel=\"noopener\">aes-js<\/a> is significantly slower and not included in the benchmark.<\/p>\n<p>The following charts show the throughput in ms\/MB (lower is better) for decreasing sizes of payload in different browsers. In all browsers, the WebAssembly implementation beats the JavaScript implementation by a wide margin. It has to be noted that the JavaScript performance varies significantly across the different browsers while the WebAssembly performance is very similar, except for small payloads. This indicates that the bridging mechanism integrating JavaScript and WebAssembly execution contexts varies in performance between the browsers. Especially Firefox seems to have a performant communication method between both languages.<\/p>\n<p><a href=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/WebAssembly-Performance-Firefox.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-13012 size-large\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/WebAssembly-Performance-Firefox-1024x625.png\" alt=\"Comparison Performance WebAssembly and JavaScript in Firefox\" width=\"1024\" height=\"625\" \/><\/a> <a href=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/WebAssembly-Performance-Chrome.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-13013 size-medium\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/WebAssembly-Performance-Chrome-300x181.png\" alt=\"Comparison Performance WebAssembly and JavaScript in Chrome\" width=\"300\" height=\"181\" \/><\/a> <a href=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/WebAssembly-Performance-Safari.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-13014 size-medium\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/WebAssembly-Performance-Safari-300x182.png\" alt=\"Comparison Performance WebAssembly and JavaScript in Safari\" width=\"300\" height=\"182\" \/><\/a><\/p>\n<p>When plotting the individual runtime in ms of consecutive runs of small payloads (16 kB in this measurement), the increasingly performant JavaScript implementation shows the work of the optimizing compiler which optimizes the AES loop as it runs more often. The WebAssembly implementation on the other hand shows a steady high performance from the first run because the assembly code generated by the WebAssembly instructions is already maximally optimized.<\/p>\n<p><a href=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/WebAssembly-Performance-Consecutive-Runs.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-13015 size-large\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/WebAssembly-Performance-Consecutive-Runs-1024x622.png\" alt=\"Comparison Performance WebAssembly and JavaScript on Consecutive Runs\" width=\"1024\" height=\"622\" \/><\/a><\/p>\n<p>The aes-wasm dependency adds ~25 kB (~14 kB after gzip) to the benchmark bundle while the Stanford Crypto Library grows the bundle by ~365 kB (~109 kB after gzip). This is not due to an inefficient implementation but because the whole library with lots of different algorithms is delivered as a single JavaScript module which can&#8217;t be split up by the bundler. Unfortunately this makes a direct size comparison infeasible. To put this into perspective, the httparchive project measures a median <a href=\"https:\/\/httparchive.org\/reports\/state-of-javascript?start=2017_03_15&amp;end=latest\" target=\"_blank\" rel=\"noopener\">367 kB of JavaScript per webpage<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Summary\"><\/span>Summary<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>WebAssembly can execute small, computation intensive programs reliably fast across different browsers. The approach is definitely worth a shot if you have a relatively small portion of your code which takes the majority of the computation time (encryption, compression, parsing, neural network stuff, physics, audio\/video processing, &#8230;). However, it is important to take into account the transfer of data in and out of the WebAssembly VM. In case of encrypting and decrypting byte arrays, the transformation is relatively simple but can become complicated and costly for more complex data structures.<\/p>\n<p>Even though the support for WebAssembly and\u00a0accompanying tooling has come a long way, there are still major hurdles to use native modules from JavaScript. But the finalization of WebAssembly support in webpack and widespread adoption of version 4 together with projects aiming to automate the binding process like <a href=\"https:\/\/hacks.mozilla.org\/2018\/04\/javascript-to-rust-and-back-again-a-wasm-bindgen-tale\/\" target=\"_blank\" rel=\"noopener\">wasm-bindgen for Rust<\/a> steadily wears them down which promises a future of faster and richer web apps using JavaScript and WebAssembly modules interchangeably.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>WebAssembly (or wasm for short) is a relatively new way to efficiently execute code in a browser. Since late 2017, it is supported in all modern major browsers (Chrome, Firefox, Safari and Edge) which makes it worth a look for real-world applications. It is the successor of Mozillas asm.js project, which compiled C or C++ [&hellip;]<\/p>\n","protected":false},"author":38,"featured_media":13426,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"ep_exclude_from_search":false,"footnotes":""},"tags":[70],"service":[425,444],"coauthors":[{"id":38,"display_name":"Johannes Reuter","user_nicename":"jreuter"}],"class_list":["post-21087","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-web","service-backend","service-frontend"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>WebAssembly in Production<\/title>\n<meta name=\"description\" content=\"This article describes how WebAssembly can help you accelerating hot code portions of ordinary JavaScript apps with tools available today.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"WebAssembly in Production\" \/>\n<meta property=\"og:description\" content=\"This article describes how WebAssembly can help you accelerating hot code portions of ordinary JavaScript apps with tools available today.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/\" \/>\n<meta property=\"og:site_name\" content=\"inovex GmbH\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/inovexde\" \/>\n<meta property=\"article:published_time\" content=\"2018-04-30T11:26:23+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2022-11-29T07:02:52+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/Zeichenfla\u0308che-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1080\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Johannes Reuter\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/Zeichenfla\u0308che-1-1024x576.png\" \/>\n<meta name=\"twitter:creator\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:site\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Johannes Reuter\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"14\u00a0Minuten\" \/>\n\t<meta name=\"twitter:label3\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data3\" content=\"Johannes Reuter\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/\"},\"author\":{\"name\":\"Johannes Reuter\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/2e7c8f474580d46832a7666b61f8c1ec\"},\"headline\":\"WebAssembly in Production\",\"datePublished\":\"2018-04-30T11:26:23+00:00\",\"dateModified\":\"2022-11-29T07:02:52+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/\"},\"wordCount\":2588,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2018\\\/04\\\/Zeichenfla\u0308che-1.png\",\"keywords\":[\"Web\"],\"articleSection\":[\"Applications\",\"English Content\",\"General\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/\",\"name\":\"WebAssembly in Production\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2018\\\/04\\\/Zeichenfla\u0308che-1.png\",\"datePublished\":\"2018-04-30T11:26:23+00:00\",\"dateModified\":\"2022-11-29T07:02:52+00:00\",\"description\":\"This article describes how WebAssembly can help you accelerating hot code portions of ordinary JavaScript apps with tools available today.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2018\\\/04\\\/Zeichenfla\u0308che-1.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2018\\\/04\\\/Zeichenfla\u0308che-1.png\",\"width\":1920,\"height\":1080,\"caption\":\"The Webassembly logo\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/webassembly-production\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"WebAssembly in Production\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"name\":\"inovex GmbH\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\",\"name\":\"inovex GmbH\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"width\":1921,\"height\":1081,\"caption\":\"inovex GmbH\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/inovexde\",\"https:\\\/\\\/x.com\\\/inovexgmbh\",\"https:\\\/\\\/www.instagram.com\\\/inovexlife\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/inovex\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UC7r66GT14hROB_RQsQBAQUQ\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/2e7c8f474580d46832a7666b61f8c1ec\",\"name\":\"Johannes Reuter\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/4cd215069ed19f8429692365d40f5a8e94a674eea57579c97b182c853c9cd0d0?s=96&d=retro&r=g6e76acaf394b88ba3914670bfc4db231\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/4cd215069ed19f8429692365d40f5a8e94a674eea57579c97b182c853c9cd0d0?s=96&d=retro&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/4cd215069ed19f8429692365d40f5a8e94a674eea57579c97b182c853c9cd0d0?s=96&d=retro&r=g\",\"caption\":\"Johannes Reuter\"},\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/author\\\/jreuter\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"WebAssembly in Production","description":"This article describes how WebAssembly can help you accelerating hot code portions of ordinary JavaScript apps with tools available today.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/","og_locale":"de_DE","og_type":"article","og_title":"WebAssembly in Production","og_description":"This article describes how WebAssembly can help you accelerating hot code portions of ordinary JavaScript apps with tools available today.","og_url":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/","og_site_name":"inovex GmbH","article_publisher":"https:\/\/www.facebook.com\/inovexde","article_published_time":"2018-04-30T11:26:23+00:00","article_modified_time":"2022-11-29T07:02:52+00:00","og_image":[{"width":1920,"height":1080,"url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/Zeichenfla\u0308che-1.png","type":"image\/png"}],"author":"Johannes Reuter","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/Zeichenfla\u0308che-1-1024x576.png","twitter_creator":"@inovexgmbh","twitter_site":"@inovexgmbh","twitter_misc":{"Verfasst von":"Johannes Reuter","Gesch\u00e4tzte Lesezeit":"14\u00a0Minuten","Written by":"Johannes Reuter"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#article","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/"},"author":{"name":"Johannes Reuter","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/2e7c8f474580d46832a7666b61f8c1ec"},"headline":"WebAssembly in Production","datePublished":"2018-04-30T11:26:23+00:00","dateModified":"2022-11-29T07:02:52+00:00","mainEntityOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/"},"wordCount":2588,"commentCount":0,"publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/Zeichenfla\u0308che-1.png","keywords":["Web"],"articleSection":["Applications","English Content","General"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/","url":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/","name":"WebAssembly in Production","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#primaryimage"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/Zeichenfla\u0308che-1.png","datePublished":"2018-04-30T11:26:23+00:00","dateModified":"2022-11-29T07:02:52+00:00","description":"This article describes how WebAssembly can help you accelerating hot code portions of ordinary JavaScript apps with tools available today.","breadcrumb":{"@id":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#primaryimage","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/Zeichenfla\u0308che-1.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2018\/04\/Zeichenfla\u0308che-1.png","width":1920,"height":1080,"caption":"The Webassembly logo"},{"@type":"BreadcrumbList","@id":"https:\/\/www.inovex.de\/de\/blog\/webassembly-production\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.inovex.de\/de\/"},{"@type":"ListItem","position":2,"name":"WebAssembly in Production"}]},{"@type":"WebSite","@id":"https:\/\/www.inovex.de\/de\/#website","url":"https:\/\/www.inovex.de\/de\/","name":"inovex GmbH","description":"","publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.inovex.de\/de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/www.inovex.de\/de\/#organization","name":"inovex GmbH","url":"https:\/\/www.inovex.de\/de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","width":1921,"height":1081,"caption":"inovex GmbH"},"image":{"@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/inovexde","https:\/\/x.com\/inovexgmbh","https:\/\/www.instagram.com\/inovexlife\/","https:\/\/www.linkedin.com\/company\/inovex","https:\/\/www.youtube.com\/channel\/UC7r66GT14hROB_RQsQBAQUQ"]},{"@type":"Person","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/2e7c8f474580d46832a7666b61f8c1ec","name":"Johannes Reuter","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/secure.gravatar.com\/avatar\/4cd215069ed19f8429692365d40f5a8e94a674eea57579c97b182c853c9cd0d0?s=96&d=retro&r=g6e76acaf394b88ba3914670bfc4db231","url":"https:\/\/secure.gravatar.com\/avatar\/4cd215069ed19f8429692365d40f5a8e94a674eea57579c97b182c853c9cd0d0?s=96&d=retro&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/4cd215069ed19f8429692365d40f5a8e94a674eea57579c97b182c853c9cd0d0?s=96&d=retro&r=g","caption":"Johannes Reuter"},"url":"https:\/\/www.inovex.de\/de\/blog\/author\/jreuter\/"}]}},"_links":{"self":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/21087","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/users\/38"}],"replies":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/comments?post=21087"}],"version-history":[{"count":1,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/21087\/revisions"}],"predecessor-version":[{"id":33821,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/21087\/revisions\/33821"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media\/13426"}],"wp:attachment":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media?parent=21087"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/tags?post=21087"},{"taxonomy":"service","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/service?post=21087"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/coauthors?post=21087"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}