اخبار وب

WebAssembly در مرورگر؛ کاربردها، محدودیت‌ها و مثال کامل

WebAssembly چگونه کنار JavaScript اجرا می‌شود؟ مثال کامل بدون فایل گم‌شده، ماژول و حافظه، محدودیت Threads و SIMD، Sandbox و تفاوت WASI با مرورگر.

1404/11/05 3 دقیقه مطالعه 1,621 بازدید
WebAssembly در مرورگر؛ کاربردها، محدودیت‌ها و مثال کامل

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

شماره‌ای مثل «WebAssembly 2.0» را نباید نام یک بسته واحد فرض کرد که تمام قابلیت‌هایش هم‌زمان در همه مرورگرها فعال شده‌اند. مشخصات Core، APIهای اتصال به JavaScript و قابلیت‌هایی مثل SIMD یا Threads مسیر و وضعیت پشتیبانی خودشان را دارند. در این مقاله، به جای نسبت دادن هر امکان تازه به یک شماره کلی، کاربرد واقعی، محدودیت محیط و روش استفاده را بررسی می‌کنیم. منابع اصلی پروژه در وب‌سایت WebAssembly و مشخصات W3C منتشر می‌شوند.

Wasm چه رابطه‌ای با JavaScript دارد؟

JavaScript همچنان برای هماهنگی با رابط مرورگر و APIهای وب نقش مهمی دارد. یک ماژول Wasm می‌تواند تابعی صادر کند و کد JavaScript آن را فراخوانی کند. در جهت دیگر، میزبان می‌تواند تابع‌ها یا منابع مشخصی را به ماژول وارد کند. این مرز کمک می‌کند بخش محاسباتی را جدا نگه دارید و نمایش نتیجه و تعامل کاربر را در لایه رابط انجام دهید.

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

ماژول، Instance و Import

ماژول Wasm نسخه کامپایل‌شده کد و تعریف‌های مربوط به آن را نگه می‌دارد. Instance نمونه قابل اجرای آن در یک محیط مشخص است. Export امکاناتی است که ماژول به میزبان ارائه می‌دهد و Import امکاناتی است که از میزبان می‌گیرد. این واژه‌ها را با moduleهای معمول JavaScript یکسان فرض نکنید؛ ابزار اتصال میان دو محیط، نقش هرکدام را مشخص می‌کند.

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

آیا Wasm همیشه سریع‌تر است؟

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

کارهایی که روی مجموعه بزرگی از داده انجام می‌شوند یا از کتابخانه موجود یک زبان دیگر استفاده می‌کنند، گزینه‌های مناسب‌تری برای بررسی هستند. حتی در این حالت، نتیجه را برای همان داده و همان سناریو بسنجید. عبارت «نزدیک به سرعت بومی» یک توصیف کلی از هدف طراحی است، نه تضمین عددی برای هر الگوریتم در هر دستگاه. پیش از تغییر معماری، مسئله عملکرد را دقیق تعریف کنید.

مثال کامل بدون فایل گم‌شده یا ابزار کامپایل

برای دیدن اتصال JavaScript و Wasm، نمونه زیر ماژول کوچکی را داخل همان فایل HTML قرار می‌دهد. تابع add دو عدد صحیح ۳۲بیتی را جمع می‌کند. این مثال برای آموزش مرز اجراست و ادعای برتری عملکرد ندارد؛ جمع ساده را می‌توان مستقیم با JavaScript انجام داد. قرار گرفتن byteهای ماژول در فایل باعث می‌شود خواننده برای اجرای نمونه به فایل .wasm معرفی‌نشده نیاز نداشته باشد.

کد را در فایل wasm-add.html ذخیره کنید و در مرورگر پشتیبانی‌کننده باز کنید. کتابخانه بیرونی، npm یا سرور اختصاصی لازم نیست. ورودی‌های پیش‌فرض ۷ و ۸ هستند و نتیجه ۱۵ خواهد بود. button تا آماده شدن ماژول غیرفعال می‌ماند. خطای آماده‌سازی و ورودی خارج از محدوده، پیام مستقل دارند و تمام idهای استفاده‌شده در JavaScript در markup آمده‌اند.

<!doctype html>
<html lang="fa" dir="rtl">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>جمع دو عدد با WebAssembly</title>
  <style>
    body { font-family: sans-serif; max-width: 36rem; margin: 2rem auto; padding: 1rem; color: #173d35; }
    form { display: grid; gap: .75rem; }
    input, button { padding: .75rem; font: inherit; }
    output { display: block; margin-top: 1rem; }
  </style>
</head>
<body>
  <h1>جمع با WebAssembly</h1>
  <form id="calculator">
    <label for="first">عدد اول</label>
    <input id="first" type="number" value="7" required step="1" min="-2147483648" max="2147483647">
    <label for="second">عدد دوم</label>
    <input id="second" type="number" value="8" required step="1" min="-2147483648" max="2147483647">
    <button id="submit" type="submit" disabled>محاسبه</button>
  </form>
  <output id="result" role="status">در حال آماده‌سازی ماژول…</output>
  <script>
    const form = document.querySelector('#calculator');
    const first = document.querySelector('#first');
    const second = document.querySelector('#second');
    const button = document.querySelector('#submit');
    const output = document.querySelector('#result');
    const bytes = new Uint8Array([
      0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,
      0x01,0x07,0x01,0x60,0x02,0x7f,0x7f,0x01,0x7f,
      0x03,0x02,0x01,0x00,
      0x07,0x07,0x01,0x03,0x61,0x64,0x64,0x00,0x00,
      0x0a,0x09,0x01,0x07,0x00,0x20,0x00,0x20,0x01,0x6a,0x0b
    ]);

    async function initialize() {
      if (!('WebAssembly' in window)) {
        output.textContent = 'این محیط از WebAssembly پشتیبانی نمی‌کند.';
        return;
      }
      try {
        const { instance } = await WebAssembly.instantiate(bytes);
        const add = instance.exports.add;
        if (typeof add !== 'function') throw new Error('Missing add export');
        button.disabled = false;
        output.textContent = 'ماژول آماده است.';
        form.addEventListener('submit', event => {
          event.preventDefault();
          if (!form.reportValidity()) return;
          const a = Number(first.value);
          const b = Number(second.value);
          const inRange = n => Number.isInteger(n) && n >= -2147483648 && n <= 2147483647;
          if (!inRange(a) || !inRange(b) || !inRange(a + b)) {
            output.textContent = 'ورودی‌ها و مجموع باید در محدوده عدد صحیح ۳۲بیتی باشند.';
            return;
          }
          output.textContent = `نتیجه: ${add(a, b)}`;
        });
      } catch (error) {
        output.textContent = 'آماده‌سازی ماژول انجام نشد.';
        console.error(error);
      }
    }
    initialize();
  </script>
</body>
</html>

آرایه bytes یک ماژول واقعی با Export به نام add است. پس از instantiate، تابع از instance.exports خوانده می‌شود. عملیات i32 در Wasm محدوده ۳۲بیتی دارد و جمع می‌تواند در آن محدوده wrap شود؛ نمونه برای جلوگیری از نتیجه مبهم، مجموع خارج از محدوده را قبل از فراخوانی رد می‌کند. این تصمیم یک قاعده رابط نمونه است، نه تغییر رفتار عددی خود Wasm.

برای پروژه بزرگ، کد زبان مبدأ و فایل خروجی را با ابزار کامپایل مناسب مدیریت کنید. آرایه دستی این نمونه جای زنجیره ساخت واقعی را نمی‌گیرد. مسیر خروجی، نام Exportها و نوع داده باید در قرارداد پروژه روشن باشند. اگر یک نمونه فقط فراخوانی تابع را نشان دهد ولی روش تولید فایل یا وابستگی‌ها را نگوید، برای خواننده قابل اجرا نخواهد بود.

حافظه و هزینه جابه‌جایی داده

بسیاری از برنامه‌های Wasm از Linear Memory برای نگهداری داده استفاده می‌کنند. وقتی یک آرایه تصویر یا متن بزرگ را میان JavaScript و این حافظه منتقل می‌کنید، تبدیل و کپی می‌توانند هزینه داشته باشند. انتخاب Wasm فقط بر اساس سرعت داخل حلقه، ممکن است هزینه کل عملیات را پنهان کند. قرارداد داده را هم‌زمان با الگوریتم طراحی کنید و تعداد عبورهای غیرضروری از مرز را کم کنید.

برای مثال، به جای فراخوانی هزارباره یک تابع برای یک مقدار، ممکن است پردازش یک batch از داده مناسب‌تر باشد. اما این تصمیم به API ماژول و نیاز رابط بستگی دارد. نگه داشتن داده برای مدت طولانی نیز حافظه بیشتری مصرف می‌کند. چرخه مالکیت و آزادسازی داده را مطابق ابزار زبان و کتابخانه مورد استفاده مشخص کنید؛ یک عدد درباره سرعت، این مسئولیت را توضیح نمی‌دهد.

Threads، SIMD و اجرای خارج از مسیر رابط

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

همچنین Wasm به‌خودی‌خود کار طولانی روی thread اصلی را غیرمسدود نمی‌کند. اگر محاسبه سنگین را در مسیر رابط اجرا کنید، تعامل کاربر همچنان می‌تواند عقب بیفتد. استفاده از Worker و طراحی پیام‌ها یکی از راه‌های جداسازی کار است. برای UI، وضعیت پیشرفت، لغو عملیات و خطا اهمیت دارند؛ کاربر نباید با صفحه‌ای روبه‌رو شود که بدون توضیح واکنش نشان نمی‌دهد.

Sandbox چه چیزی را تضمین نمی‌کند؟

مدل امنیتی WebAssembly اجرای کد را در محدوده امکانات میزبان توضیح می‌دهد. Sandbox به معنی معتبر بودن ورودی یا درست بودن منطق برنامه نیست. یک ماژول همچنان می‌تواند داده نادرست تولید کند، منابع زیادی مصرف کند یا از Importهایی که دریافت کرده به شکل نامناسب استفاده کند. انتخاب فایل از منبع ناشناس را فقط با شنیدن واژه Sandbox بی‌خطر فرض نکنید.

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

WASI و مرورگر محیط یکسانی ندارند

WASI به رابط‌های سیستمی در محیط‌های مناسب مربوط می‌شود. برنامه‌ای که برای یک Runtime دارای این امکانات ساخته شده، لزوماً مستقیم در مرورگر اجرا نمی‌شود. دسترسی فایل، شبکه و فرایندها باید با امکانات میزبان هماهنگ شود. اگر می‌خواهید یک ابزار CLI را به وب بیاورید، ابتدا وابستگی‌های آن به سیستم‌عامل را فهرست کنید؛ فقط کامپایل موفق برای Wasm کافی نیست.

این تفکیک برای انتخاب معماری مهم است. پردازش محلی در مرورگر می‌تواند در بعضی محصولات انتقال فایل به سرور را کم کند، اما مصرف CPU و حافظه دستگاه کاربر را بالا می‌برد. اجرای سروری کنترل محیط بیشتری می‌دهد، ولی هزینه و انتقال داده دارد. تصمیم را با اندازه فایل، نیاز حریم خصوصی و انتظار پاسخ کاربر هماهنگ کنید.

چه زمانی استفاده از Wasm منطقی است؟

وقتی یک نیاز محاسباتی مشخص، کتابخانه ارزشمند موجود یا الزام پردازش محلی دارید، Wasm ارزش بررسی دارد. مسیر آزمایش را کوچک نگه دارید: یک ورودی واقعی، یک خروجی قابل سنجش و یک قرارداد اتصال روشن. پیش از مهاجرت تمام برنامه، هزینه بارگذاری و پردازش را در همان سناریو حساب کنید. برای تفکیک هزینه شبکه از پردازش رابط، مقاله PageSpeed Insights مفید است.

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

واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!