کامپایل mkbitmap به WebAssembly

در WebAssembly چیست و از کجا آمده است؟ ، توضیح دادم که چگونه به WebAssembly امروز رسیدیم. در این مقاله، من روش خود را برای کامپایل کردن یک برنامه C موجود، mkbitmap ، به WebAssembly به شما نشان خواهم داد. از مثال hello world پیچیده‌تر است، زیرا شامل کار با فایل‌ها، برقراری ارتباط بین WebAssembly و جاوا اسکریپت و کشیدن روی بوم می‌شود، اما همچنان به اندازه‌ای قابل مدیریت است که شما را تحت تأثیر قرار ندهد.

این مقاله برای توسعه دهندگان وب نوشته شده است که می خواهند WebAssembly را یاد بگیرند و گام به گام نشان می دهد که اگر می خواهید چیزی مانند mkbitmap را در WebAssembly کامپایل کنید چگونه می توانید ادامه دهید. به عنوان یک هشدار منصفانه، دریافت نکردن یک برنامه یا کتابخانه برای کامپایل در اولین اجرا کاملاً طبیعی است، به همین دلیل است که برخی از مراحل شرح داده شده در زیر کار نمی‌کنند، بنابراین من باید به عقب برگردم و دوباره امتحان کنم. این مقاله فرمان کامپایل نهایی جادویی را به گونه‌ای نشان نمی‌دهد که انگار از آسمان افتاده است، بلکه پیشرفت واقعی من را توصیف می‌کند که شامل برخی ناامیدی‌ها نیز می‌شود.

درباره mkbitmap

برنامه mkbitmap C یک تصویر را می خواند و یک یا چند مورد از عملیات زیر را به ترتیب بر روی آن اعمال می کند: وارونگی، فیلتر بالاگذر، مقیاس گذاری و آستانه گذاری. هر عملیات را می توان به صورت جداگانه کنترل و روشن یا خاموش کرد. کاربرد اصلی mkbitmap تبدیل تصاویر رنگی یا خاکستری به فرمتی مناسب به عنوان ورودی برای برنامه های دیگر است، به ویژه ردیابی برنامه potrace که اساس SVGcode را تشکیل می دهد. به عنوان یک ابزار پیش پردازش، mkbitmap به ویژه برای تبدیل هنر خط اسکن شده، مانند کارتون یا متن دست نویس، به تصاویر دوسطحی با وضوح بالا مفید است.

شما از mkbitmap با ارسال تعدادی گزینه و یک یا چند نام فایل به آن استفاده می کنید. برای همه جزئیات، به صفحه مرد ابزار مراجعه کنید:

$ mkbitmap [options] [filename...]
تصویر کارتونی رنگی.
تصویر اصلی ( منبع ).
تصویر کارتونی پس از پیش پردازش به مقیاس خاکستری تبدیل شد.
ابتدا مقیاس، سپس آستانه: mkbitmap -f 2 -s 2 -t 0.48 ( منبع ).

کد را دریافت کنید

اولین قدم دریافت کد منبع mkbitmap است. می توانید آن را در وب سایت پروژه پیدا کنید. در زمان نگارش این مطلب، potrace-1.16.tar.gz آخرین نسخه است.

کامپایل و به صورت محلی نصب کنید

گام بعدی این است که ابزار را به صورت محلی کامپایل و نصب کنید تا احساس کنید که چگونه رفتار می کند. فایل INSTALL حاوی دستورالعمل های زیر است:

  1. cd به دایرکتوری حاوی کد منبع بسته وارد کنید و ./configure را تایپ کنید تا بسته را برای سیستم خود پیکربندی کنید.

    اجرای configure ممکن است کمی طول بکشد. در حین اجرا، پیام هایی را چاپ می کند که نشان می دهد کدام ویژگی ها را بررسی می کند.

  2. برای کامپایل کردن بسته make را تایپ کنید.

  3. به صورت اختیاری، make check تایپ کنید تا آزمایش‌های خودکاری که با بسته ارائه می‌شود، اجرا شود، معمولاً با استفاده از باینری‌های حذف‌شده تازه ساخته‌شده.

  4. برای نصب برنامه ها و هر فایل داده و اسنادی make install تایپ کنید. هنگام نصب در پیشوندی که متعلق به root است، توصیه می شود که بسته به عنوان یک کاربر معمولی پیکربندی و ساخته شود و فقط مرحله make install با حقوق ریشه اجرا شود.

با دنبال کردن این مراحل، باید به دو فایل اجرایی، potrace و mkbitmap برسید - مورد دوم تمرکز این مقاله است. می توانید با اجرای mkbitmap --version بررسی کنید که درست کار کرده است. در اینجا خروجی هر چهار مرحله از دستگاه من است که برای اختصار به شدت کوتاه شده است:

مرحله 1، ./configure :

 $ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking whether make sets $(MAKE)... yes
[…]
config.status: executing libtool commands

مرحله 2، make :

$ make
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive
Making all in src
clang -DHAVE_CONFIG_H -I. -I..     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
[…]
make[2]: Nothing to be done for `all-am'.

مرحله 3، make check :

$ make check
Making check in src
make[1]: Nothing to be done for `check'.
Making check in doc
make[1]: Nothing to be done for `check'.
[…]
============================================================================
Testsuite summary for potrace 1.16
============================================================================
# TOTAL: 8
# PASS:  8
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================
make[1]: Nothing to be done for `check-am'.

مرحله 4، sudo make install :

$ sudo make install
Password:
Making install in src
 .././install-sh -c -d '/usr/local/bin'
  /bin/sh ../libtool   --mode=install /usr/bin/install -c potrace mkbitmap '/usr/local/bin'
[…]
make[2]: Nothing to be done for `install-data-am'.

برای بررسی اینکه آیا کار می کند، mkbitmap --version اجرا کنید:

$ mkbitmap --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.

اگر جزئیات نسخه را دریافت کردید، mkbitmap با موفقیت کامپایل و نصب کرده اید. در مرحله بعد، معادل این مراحل را با WebAssembly کار کنید.

mkbitmap در WebAssembly کامپایل کنید

Emscripten ابزاری برای کامپایل برنامه های C/C++ در WebAssembly است. مستندات پروژه های ساختمانی Emscripten موارد زیر را بیان می کند:

ساخت پروژه های بزرگ با Emscripten بسیار آسان است. Emscripten دو اسکریپت ساده را ارائه می‌کند که فایل‌های ساخت شما را به گونه‌ای پیکربندی می‌کند که emcc به عنوان جایگزینی برای gcc استفاده کنند — در بیشتر موارد بقیه سیستم ساخت فعلی پروژه شما بدون تغییر باقی می‌ماند.

سپس مستندات ادامه می‌یابد (برای اختصار کمی ویرایش شده است):

حالتی را در نظر بگیرید که معمولاً با دستورات زیر می‌سازید:

./configure
make

برای ساخت با Emscripten، در عوض از دستورات زیر استفاده کنید:

emconfigure ./configure
emmake make

بنابراین اساسا ./configure تبدیل emconfigure ./configure و make تبدیل به emmake make می شود. در زیر نحوه انجام این کار با mkbitmap را نشان می دهد.

مرحله 0، make clean :

$ make clean
Making clean in src
 rm -f potrace mkbitmap
test -z "" || rm -f
rm -rf .libs _libs
[…]
rm -f *.lo

مرحله 1، emconfigure ./configure :

$ emconfigure ./configure
configure: ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
[…]
config.status: executing libtool commands

مرحله 2، emmake make :

$ emmake make
make: make
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive
Making all in src
/opt/homebrew/Cellar/emscripten/3.1.36/libexec/emcc -DHAVE_CONFIG_H -I. -I..     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
[…]
make[2]: Nothing to be done for `all'.

اگر همه چیز به خوبی پیش رفت، اکنون باید فایل های .wasm در جایی در دایرکتوری وجود داشته باشد. با اجرای find . -name "*.wasm" :

$ find . -name "*.wasm"
./a.wasm
./src/mkbitmap.wasm
./src/potrace.wasm

دو مورد آخر امیدوارکننده به نظر می رسند، بنابراین cd به دایرکتوری src/ وارد کنید. اکنون دو فایل متناظر جدید نیز وجود دارد، mkbitmap و potrace . برای این مقاله، فقط mkbitmap مرتبط است. این واقعیت که آنها پسوند .js ندارند کمی گیج کننده است، اما در واقع فایل های جاوا اسکریپت هستند که با یک head سریع قابل تأیید هستند:

$ cd src/
$ head -n 20 mkbitmap
// include: shell.js
// The Module object: Our interface to the outside world. We import
// and export values on it. There are various ways Module can be used:
// 1. Not defined. We create it here
// 2. A function parameter, function(Module) { ..generated code.. }
// 3. pre-run appended it, var Module = {}; ..generated code..
// 4. External script tag defines var Module.
// We need to check if Module already exists (e.g. case 3 above).
// Substitution will be replaced with actual code on later stage of the build,
// this way Closure Compiler will not mangle it (e.g. case 4. above).
// Note that if you want to run closure, and also to use Module
// after the generated code, you will need to define   var Module = {};
// before the code. Then that object will be used in the code, and you
// can continue to use Module afterwards as well.
var Module = typeof Module != 'undefined' ? Module : {};

// --pre-jses are emitted after the Module integration code, so that they can
// refer to Module (if they choose; they can also define Module)

با فراخوانی mv mkbitmap mkbitmap.js (و در صورت تمایل به ترتیب mv potrace potrace.js ) نام فایل جاوا اسکریپت را به mkbitmap.js تغییر دهید. اکنون نوبت اولین آزمایش است تا ببینیم با اجرای فایل با Node.js در خط فرمان با اجرای node mkbitmap.js --version کار می کند یا خیر:

$ node mkbitmap.js --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.

شما با موفقیت mkbitmap در WebAssembly کامپایل کردید. اکنون مرحله بعدی این است که آن را در مرورگر کار کنید.

mkbitmap با WebAssembly در مرورگر

فایل های mkbitmap.js و mkbitmap.wasm را در دایرکتوری جدیدی به نام mkbitmap کپی کنید و یک فایل HTML boilerplate index.html ایجاد کنید که فایل جاوا اسکریپت mkbitmap.js را بارگیری می کند.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>mkbitmap</title>
  </head>
  <body>
    <script src="mkbitmap.js"></script>
  </body>
</html>

یک سرور محلی که دایرکتوری mkbitmap ارائه می دهد راه اندازی کنید و آن را در مرورگر خود باز کنید. باید اعلانی را ببینید که از شما ورودی می خواهد. این همان چیزی است که انتظار می رود، زیرا طبق صفحه man ابزار ، "[i]اگر هیچ آرگومان نام فایلی داده نشود، mkbitmap به عنوان یک فیلتر عمل می کند و از ورودی استاندارد می خواند" ، که برای Emscripten به طور پیش فرض یک prompt() است.

برنامه mkbitmap درخواستی را نشان می دهد که ورودی می خواهد.

جلوگیری از اجرای خودکار

برای توقف فوری اجرای mkbitmap و در عوض منتظر ماندن آن برای ورودی کاربر، باید شی Module Emscripten را درک کنید. Module یک شی جاوا اسکریپت جهانی با ویژگی هایی است که کد تولید شده توسط Emscripten در نقاط مختلف اجرای آن فراخوانی می کند. شما می توانید پیاده سازی Module را برای کنترل اجرای کد ارائه دهید. هنگامی که یک برنامه Emscripten راه اندازی می شود، به مقادیر موجود در شی Module نگاه می کند و آنها را اعمال می کند.

در مورد mkbitmap ، Module.noInitialRun را روی true تنظیم کنید تا از اجرای اولیه که باعث ظاهر شدن درخواست شده است جلوگیری کنید. یک اسکریپت به نام script.js ایجاد کنید، آن را قبل از <script src="mkbitmap.js"></script> در index.html قرار دهید و کد زیر را به script.js اضافه کنید. هنگامی که اکنون برنامه را مجدداً بارگیری می کنید، اعلان باید ناپدید شود.

var Module = {
  // Don't run main() at page load
  noInitialRun: true,
};

یک ساخت مدولار با چند پرچم ساخت دیگر ایجاد کنید

برای ارائه ورودی به برنامه، می‌توانید از پشتیبانی سیستم فایل Emscripten در Module.FS استفاده کنید. در بخش پشتیبانی از فایل سیستم در اسناد آمده است:

Emscripten تصمیم می گیرد که آیا پشتیبانی سیستم فایل را به طور خودکار شامل شود یا خیر. بسیاری از برنامه‌ها به فایل‌ها نیازی ندارند و پشتیبانی از سیستم فایل از نظر اندازه ناچیز نیست، بنابراین Emscripten وقتی دلیلی برای این کار نمی‌بیند از قرار دادن آن اجتناب می‌کند. این بدان معناست که اگر کد C/C++ شما به فایل‌ها دسترسی نداشته باشد، شی FS و سایر APIهای سیستم فایل در خروجی گنجانده نمی‌شوند. و از سوی دیگر، اگر کد C/C++ شما از فایل‌ها استفاده می‌کند، پشتیبانی سیستم فایل به‌طور خودکار شامل می‌شود.

متأسفانه mkbitmap یکی از مواردی است که Emscripten به طور خودکار پشتیبانی از سیستم فایل را شامل نمی شود، بنابراین باید صریحاً به آن بگویید که این کار را انجام دهد. این بدان معنی است که شما باید مراحل emconfigure و emmake را که قبلا توضیح داده شد، با چند پرچم دیگر از طریق یک آرگومان CFLAGS دنبال کنید. پرچم‌های زیر ممکن است برای پروژه‌های دیگر نیز مفید باشند.

  • -sFILESYSTEM=1 تنظیم کنید تا پشتیبانی سیستم فایل گنجانده شود.
  • -sEXPORTED_RUNTIME_METHODS=FS,callMain تنظیم کنید تا Module.FS و Module.callMain صادر شوند.
  • -sMODULARIZE=1 و -sEXPORT_ES6 را برای تولید یک ماژول مدرن ES6 تنظیم کنید.
  • -sINVOKE_RUN=0 برای جلوگیری از اجرای اولیه که باعث ظاهر شدن درخواست شده است تنظیم کنید.

همچنین، در این مورد خاص، باید پرچم --host را روی wasm32 تنظیم کنید تا به اسکریپت configure که برای WebAssembly کامپایل می کنید بگویید.

دستور نهایی emconfigure به شکل زیر است:

$ emconfigure ./configure --host=wasm32 CFLAGS='-sFILESYSTEM=1 -sEXPORTED_RUNTIME_METHODS=FS,callMain -sMODULARIZE=1 -sEXPORT_ES6 -sINVOKE_RUN=0'

فراموش نکنید که emmake make دوباره اجرا کنید و فایل های تازه ایجاد شده را در پوشه mkbitmap کپی کنید.

index.html طوری تغییر دهید که فقط ماژول ES script.js بارگیری کند و سپس ماژول mkbitmap.js را از آن وارد کنید.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>mkbitmap</title>
  </head>
  <body>
    <!-- No longer load `mkbitmap.js` here -->
    <script src="script.js" type="module"></script>
  </body>
</html>
// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  console.log(Module);
};

run();

هنگامی که اکنون برنامه را در مرورگر باز می کنید، باید شی Module را مشاهده کنید که در کنسول DevTools ثبت شده است، و اعلان از بین می رود، زیرا تابع main() mkbitmap دیگر در ابتدا فراخوانی نمی شود.

برنامه mkbitmap با یک صفحه سفید که شی ماژول وارد شده به کنسول DevTools را نشان می دهد.

تابع اصلی را به صورت دستی اجرا کنید

مرحله بعدی فراخوانی دستی تابع main() mkbitmap با اجرای Module.callMain() است. تابع callMain() آرایه‌ای از آرگومان‌ها را می‌گیرد که با آنچه در خط فرمان ارسال می‌کنید، یک به یک مطابقت دارند. اگر در خط فرمان mkbitmap -v اجرا کنید، Module.callMain(['-v']) در مرورگر فراخوانی کنید. این شماره نسخه mkbitmap را در کنسول DevTools ثبت می کند.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  Module.callMain(['-v']);
};

run();

برنامه mkbitmap با یک صفحه سفید که شماره نسخه mkbitmap ثبت شده در کنسول DevTools را نشان می دهد.

خروجی استاندارد را تغییر مسیر دهید

خروجی استاندارد ( stdout ) به طور پیش فرض کنسول است. با این حال، می توانید آن را به چیز دیگری هدایت کنید، به عنوان مثال، تابعی که خروجی را در یک متغیر ذخیره می کند. این بدان معنی است که می توانید با تنظیم ویژگی Module.print خروجی را به HTML اضافه کنید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  let consoleOutput = 'Powered by ';
  const Module = await loadWASM({
    print: (text) => (consoleOutput += text),
  });
  Module.callMain(['-v']);
  document.body.textContent = consoleOutput;
};

run();

برنامه mkbitmap شماره نسخه mkbitmap را نشان می دهد.

فایل ورودی را در سیستم فایل حافظه دریافت کنید

برای دریافت فایل ورودی به سیستم فایل حافظه، به mkbitmap filename در خط فرمان نیاز دارید. برای درک نحوه برخورد من با این موضوع، ابتدا پیش زمینه ای در مورد اینکه چگونه mkbitmap ورودی خود را انتظار دارد و خروجی خود را ایجاد می کند، بپردازید.

فرمت های ورودی پشتیبانی شده mkbitmap PNM ( PBM ، PGM ، PPM ) و BMP هستند. فرمت های خروجی PBM برای نقشه های بیتی و PGM برای نقشه های خاکستری هستند. اگر آرگومان filename داده شود، mkbitmap به طور پیش فرض یک فایل خروجی ایجاد می کند که نام آن از نام فایل ورودی با تغییر پسوند آن به .pbm به دست می آید. به عنوان مثال، برای نام فایل ورودی example.bmp ، نام فایل خروجی example.pbm خواهد بود.

Emscripten یک سیستم فایل مجازی را ارائه می‌کند که سیستم فایل محلی را شبیه‌سازی می‌کند، به طوری که کد بومی با استفاده از APIهای فایل همزمان می‌تواند کامپایل شده و با تغییر کم یا بدون تغییر اجرا شود. برای اینکه mkbitmap بتواند یک فایل ورودی را بخواند که گویی به عنوان آرگومان خط فرمان filename ارسال شده است، باید از شی FS که Emscripten ارائه می دهد استفاده کنید.

شی FS توسط یک سیستم فایل در حافظه (که معمولاً به عنوان MEMFS نامیده می شود) پشتیبانی می شود و دارای تابع writeFile() است که از آن برای نوشتن فایل ها در سیستم فایل مجازی استفاده می کنید. همانطور که در نمونه کد زیر نشان داده شده است از writeFile() استفاده می کنید.

برای تایید عملکرد نوشتن فایل، تابع readdir() شی FS را با پارامتر '/' اجرا کنید. شما example.bmp و تعدادی فایل پیش فرض را خواهید دید که همیشه به صورت خودکار ایجاد می شوند .

توجه داشته باشید که تماس قبلی با Module.callMain(['-v']) برای چاپ شماره نسخه حذف شد. این به این دلیل است که Module.callMain() تابعی است که معمولاً انتظار دارد فقط یک بار اجرا شود.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  console.log(Module.FS.readdir('/'));
};

run();

برنامه mkbitmap آرایه ای از فایل ها را در سیستم فایل حافظه نشان می دهد، از جمله example.bmp.

اولین اجرای واقعی

با همه چیز در جای خود، mkbitmap با اجرای Module.callMain(['example.bmp']) اجرا کنید. محتویات پوشه MEMFS ' '/' را وارد کنید، و باید فایل خروجی example.pbm تازه ایجاد شده را در کنار فایل ورودی example.bmp ببینید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  Module.callMain(['example.bmp']);
  console.log(Module.FS.readdir('/'));
};

run();

برنامه mkbitmap آرایه ای از فایل ها را در سیستم فایل حافظه نشان می دهد، از جمله example.bmp و example.pbm.

فایل خروجی را از سیستم فایل حافظه خارج کنید

تابع readFile() شی FS امکان خروج example.pbm ایجاد شده در آخرین مرحله از سیستم فایل حافظه را فراهم می کند. این تابع یک Uint8Array را برمی گرداند که شما آن را به یک شی File تبدیل کرده و در دیسک ذخیره می کنید، زیرا مرورگرها معمولاً فایل های PBM را برای مشاهده مستقیم در مرورگر پشتیبانی نمی کنند. (روش های ظریف تری برای ذخیره یک فایل وجود دارد، اما استفاده از <a download> ایجاد شده به صورت پویا بیشترین پشتیبانی را دارد.) هنگامی که فایل ذخیره شد، می توانید آن را در نمایشگر تصویر دلخواه خود باز کنید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  Module.callMain(['example.bmp']);
  const output = Module.FS.readFile('example.pbm', { encoding: 'binary' });
  const file = new File([output], 'example.pbm', {
    type: 'image/x-portable-bitmap',
  });
  const a = document.createElement('a');
  a.href = URL.createObjectURL(file);
  a.download = file.name;
  a.click();
};

run();

macOS Finder با پیش نمایش فایل .bmp. ورودی و فایل pbm. خروجی.

یک رابط کاربری تعاملی اضافه کنید

تا این مرحله، فایل ورودی هاردکد شده و mkbitmap با پارامترهای پیش‌فرض اجرا می‌شود. مرحله آخر این است که به کاربر اجازه دهید به صورت پویا یک فایل ورودی را انتخاب کند، پارامترهای mkbitmap را تغییر دهد و سپس ابزار را با گزینه های انتخاب شده اجرا کند.

// Corresponds to `mkbitmap -o output.pbm input.bmp -s 8 -3 -f 4 -t 0.45`.
Module.callMain(['-o', 'output.pbm', 'input.bmp', '-s', '8', '-3', '-f', '4', '-t', '0.45']);

تجزیه فرمت تصویر PBM به خصوص سخت نیست، بنابراین با استفاده از کد جاوا اسکریپت ، حتی می توانید پیش نمایش تصویر خروجی را نشان دهید. کد منبع نسخه ی نمایشی تعبیه شده را برای یکی از راه های انجام این کار در زیر ببینید.

نتیجه

تبریک می گوییم، شما با موفقیت mkbitmap در WebAssembly کامپایل کردید و آن را در مرورگر کار کردید! بن بست هایی وجود داشت و شما مجبور بودید بیش از یک بار ابزار را کامپایل کنید تا زمانی که کار کند، اما همانطور که در بالا نوشتم، این بخشی از تجربه است. همچنین اگر گیر کردید ، تگ webassembly StackOverflow را به خاطر بسپارید. تدوین مبارک!

سپاسگزاریها

این مقاله توسط سام کلگ و ریچل اندرو بررسی شده است.