رشته‌بندی وب با کارگران ماژول

اکنون با واحدهای جاوا اسکریپت در وب‌ورکرها، انتقال کارهای سنگین به رشته‌های پس‌زمینه آسان‌تر شده است.

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

در پلاتفرم وب، اصلی‌ترین عنصر برای رشته‌بندی و موازی‌سازی Web Workers API است. Workers یک انتزاع سبک در بالای رشته‌های سیستم‌عامل است که یک API ارسال پیام را برای ارتباط بین رشته‌ها نشان می‌دهد. این امر می‌تواند هنگام انجام محاسبات پرهزینه یا کار روی مجموعه‌داده‌های بزرگ بسیار مفید باشد، زیرا به رشته اصلی اجازه می‌دهد تا به‌طور روان اجرا شود درحالی‌که عملیات پرهزینه روی یک یا چند رشته پس‌زمینه انجام می‌شود.

در اینجا نمونه‌ای از کاربرد معمول worker آورده شده است که در آن یک اسکریپت worker به پیام‌های رشته اصلی گوش می‌دهد و با ارسال پیام‌های خودش پاسخ می‌دهد:

page.js:

const worker = new Worker('worker.js');
worker.addEventListener('message', e => {
  console.log(e.data);
});
worker.postMessage('hello');

worker.js:

addEventListener('message', e => {
  if (e.data === 'hello') {
    postMessage('world');
  }
});

Web Worker API بیش‌از ده سال است که در اکثر مرورگرها دردسترس است. While that means workers have excellent browser support and are well-optimized, it also means they long predate JavaScript modules. ازآنجایی‌که هنگام طراحی کارگران سیستم ماژول وجود نداشت، میانای برنامه‌سازی کاربردی برای بار کردن کد در کارگر و ترکیب کردن اسکریپت‌ها مشابه رویکردهای بار کردن اسکریپت همگام رایج در سال ۲۰۰۹ باقی مانده است.

سابقه: کارگران کلاسیک

سازنده Worker نشانی وب اسکریپت کلاسیک را می‌گیرد که نسبت‌به نشانی وب سند است. این متد بلافاصله ارجاعی به نمونه worker جدید برمی‌گرداند که میانای پیام‌رسانی و همچنین متد terminate() را نشان می‌دهد. این متد بلافاصله worker را متوقف و نابود می‌کند.

const worker = new Worker('worker.js');

تابع importScripts() در وب‌ورکرها برای بار کردن کد اضافی دردسترس است، اما اجرای وب‌ورکر را برای واکشی و ارزیابی هر اسکریپت موقتاً متوقف می‌کند. همچنین اسکریپت‌ها را در دامنه سراسری مانند برچسب کلاسیک <script> اجرا می‌کند، به این معنی که متغیرهای یک اسکریپت می‌توانند توسط متغیرهای اسکریپت دیگر بازنویسی شوند.

worker.js:

importScripts('greet.js');
// ^ could block for seconds
addEventListener('message', e => {
  postMessage(sayHello());
});

greet.js:

// global to the whole worker
function sayHello() {
  return 'world';
}

به همین دلیل، وب‌ورکرها از گذشته تأثیر بسیار زیادی بر معماری برنامه داشته‌اند. توسعه‌دهندگان مجبور بوده‌اند ابزارها و راه‌حل‌های هوشمندانه‌ای ایجاد کنند تا بتوانند از وب‌ورکرها بدون کنار گذاشتن روش‌های توسعه مدرن استفاده کنند. به‌عنوان مثال، بسته‌بندی‌کننده‌هایی مانند webpack یک پیاده‌سازی کوچک بارکننده ماژول را در کد تولیدشده جاسازی می‌کنند که از importScripts() برای بارگیری کد استفاده می‌کند، اما ماژول‌ها را در توابع می‌پیچد تا از تداخل متغیرها جلوگیری کند و واردات و صادرات وابستگی را شبیه‌سازی کند.

وارد کردن کارگران واحد

حالت جدیدی برای وب‌ورکرها با ارگونومی و مزایای عملکردی ماژول‌های جاوا اسکریپت در Chrome 80 ارائه می‌شود که ماژول‌ورکر نام دارد. سازنده Worker اکنون گزینه جدید {type:"module"} را می‌پذیرد که بار کردن و اجرای اسکریپت را تغییر می‌دهد تا با <script type="module"> مطابقت داشته باشد.

const worker = new Worker('worker.js', {
  type: 'module'
});

ازآنجایی‌که کارگران ماژول ماژول‌های استاندارد جاوا اسکریپت هستند، می‌توانند از دستورات واردات و صادرات استفاده کنند. As with all JavaScript modules, dependencies are only executed once in a given context (main thread, worker, etc.), and all future imports reference the already-executed module instance. بار کردن و اجرای ماژول‌های جاوا اسکریپت نیز توسط مرورگرها بهینه‌سازی می‌شود. وابستگی‌های یک ماژول را می‌توان قبل‌از اجرای ماژول بارگیری کرد، که این امر امکان بارگیری موازی کل درخت‌های ماژول را فراهم می‌کند. بار کردن ماژول همچنین کد تجزیه‌شده را در حافظه نهان ذخیره می‌کند، یعنی ماژول‌هایی که در رشته اصلی و در یک کارگر استفاده می‌شوند فقط باید یک بار تجزیه شوند.

انتقال به ماژول‌های جاوا اسکریپت همچنین امکان استفاده از وارد کردن پویا را برای کد بارکردن کُند بدون مسدود کردن اجرای کارگر فراهم می‌کند. وارد کردن پویا بسیار صریح‌تر از استفاده از importScripts() برای بار کردن وابستگی‌ها است، زیرا صادرات ماژول واردشده به‌جای تکیه بر متغیرهای سراسری برگردانده می‌شود.

worker.js:

import { sayHello } from './greet.js';
addEventListener('message', e => {
  postMessage(sayHello());
});

greet.js:

import greetings from './data.js';
export function sayHello() {
  return greetings.hello;
}

برای اطمینان از عملکرد عالی، روش قدیمی importScripts() در کارگران ماژول دردسترس نیست. تغییر دادن کارگران برای استفاده از ماژول‌های جاوا اسکریپت به این معنی است که همه کدها در حالت سخت‌گیرانه بار می‌شوند. تغییر قابل‌توجه دیگر این است که مقدار this در دامنه سطح بالای یک ماژول جاوا اسکریپت undefined است، درحالی‌که در workerهای کلاسیک، مقدار همان دامنه سراسری worker است. خوشبختانه، همیشه یک self جهانی وجود داشته است که مرجعی برای دامنه جهانی ارائه می‌دهد. این ویژگی در همه انواع Worker ازجمله Service Worker و همچنین در DOM دردسترس است.

پیش‌بار کردن کارگران با modulepreload

یکی از بهبودهای عملکردی قابل‌توجهی که با کارگران ماژول ارائه می‌شود، توانایی پیش‌بارگیری کارگران و وابستگی‌های آن‌ها است. با کارگران ماژول، دستورگان‌ها به‌عنوان ماژول‌های استاندارد جاوااسکریپت بارگیری و اجرا می‌شوند، یعنی می‌توان آن‌ها را بااستفاده از modulepreload پیش‌بارگیری و حتی پیش‌تجزیه کرد:

<!-- preloads worker.js and its dependencies: -->
<link rel="modulepreload" href="worker.js">

<script>
  addEventListener('load', () => {
    // our worker code is likely already parsed and ready to execute!
    const worker = new Worker('worker.js', { type: 'module' });
  });
</script>

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

قبلاً، گزینه‌های دردسترس برای پیش‌بارگذاری اسکریپت‌های وب‌ورکر محدود بودند و لزوماً قابل‌اعتماد نبودند. کارگران کلاسیک نوع منبع «کارگر» خود را برای پیش‌بارگذاری داشتند، اما هیچ مرورگری <link rel="preload" as="worker"> را پیاده‌سازی نکرد. درنتیجه، تکنیک اصلی موجود برای پیش‌بارگیری وب‌ورکرها استفاده از <link rel="prefetch"> بود که کاملاً به حافظه پنهان HTTP متکی بود. وقتی این ویژگی در ترکیب با سرایندهای ذخیره‌سازی صحیح استفاده می‌شود، امکان اجتناب از نمونه‌سازی کارگر را فراهم می‌کند که باید منتظر بارگیری اسکریپت کارگر بماند. اما برخلاف modulepreload این تکنیک از پیش‌بارگیری وابستگی‌ها یا پیش‌تجزیه پشتیبانی نمی‌کرد.

درباره کارگران مشترک چطور؟

Shared workers از Chrome 83 با پشتیبانی از ماژول‌های جاوا اسکریپت به‌روز شده است. مانند کارگران اختصاصی، ساختن یک کارگر مشترک با گزینه {type:"module"} اکنون اسکریپت کارگر را به عنوان یک ماژول به جای یک اسکریپت کلاسیک بارگذاری می کند:

const worker = new SharedWorker('/worker.js', {
  type: 'module'
});

پیش‌از پشتیبانی از ماژول‌های جاوا اسکریپت، سازنده SharedWorker() فقط انتظار یک نشانی وب و یک آرگومان اختیاری name را داشت. این ویژگی همچنان برای استفاده از کارگر مشترک کلاسیک کار خواهد کرد؛ اما ایجاد کارگران مشترک پیمانه مستلزم استفاده از آرگومان جدید options است. گزینه‌های دردسترس همان گزینه‌های مربوط به کارگر اختصاصی است، ازجمله گزینه name که جایگزین آرگومان name قبلی می‌شود.

درباره کارگر خدماتی چطور؟

مشخصات سرویس‌کار قبلاً به‌روز شده است تا از پذیرش یک ماژول جاوا اسکریپت به‌عنوان نقطه ورود پشتیبانی کند، بااستفاده از همان گزینه {type:"module"} به‌عنوان کارگران ماژول، اما این تغییر هنوز در مرورگرها پیاده‌سازی نشده است. وقتی این اتفاق بیفتد، می‌توان بااستفاده از کد زیر، یک سرویس‌کار را بااستفاده از یک ماژول جاوا اسکریپت نمونه‌سازی کرد:

navigator.serviceWorker.register('/sw.js', {
  type: 'module'
});

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

منابع تکمیلی و مطالعه بیشتر