typed-channel؛ postMessage تایپشده با کمتر از ۳۵۰ بایت
خلاصهٔ کاملتر
تو این مقاله تیم Evil Martians از typed-channel میگه، کتابخونهای که موقع ساخت Harmonizer (یه ابزار ساخت پالت رنگ) نوشتن. Harmonizer محاسبات رنگ رو تو یه Web Worker انجام میده، یه event bus داخلی داره و بهصورت پلاگین Figma هم عرضه میشه. نویسندهها نمیخواستن کد پیامرسانی رو سه بار جدا بنویسن، پس یه API تایپشدهٔ واحد برای همهشون ساختن.
به گفتهٔ مقاله، postMessage خام چند تا مشکل داره. event.data از نوع any هست و اسم پیامها رشتههاییان که TypeScript چکشون نمیکنه، پس یه غلط تایپی تا زمان اجرا معلوم نمیشه. هیچ جای واحدی نیست که بگه هر طرف چه پیامهایی میفرسته. addEventListener تابع حذف listener برنمیگردونه و listenerها راحت نشت میکنن. هر مرز هم (iframe، BroadcastChannel، event bus) کد تکراری خودش رو میخواد.
typed-channel کار رو دو تیکه میکنه: transport (یعنی لایهای که پیام رو از یه مرز رد میکنه) و channel که میدونه چه پیامهایی وجود دارن و چی حمل میکنن. پیامهای هر جهت یه بار تو یه type map تعریف میشن:
export type WorkerMessages = {
"generated:color": GeneratedColor;
done: never; // no payload
};بعد سمت صفحه با همون mapها یه channel ساخته میشه و on و emit کاملاً تایپشدهان. Worker هم همین mapها رو برعکس استفاده میکنه:
const channel = createTypedChannel(
createPostMessageTransport<WorkerMessages, ClientMessages>(worker),
);
channel.on("generated:color", renderColor);
channel.emit("generate:colors", { levels, hues });حالا emit("generated:colors", ...) با غلط تایپی کامپایل نمیشه و on خودش تابع لغو اشتراک برمیگردونه. هستهٔ کتابخونه با minify و brotli فقط ۲۷۱ بایته و با transport مربوط به postMessage به ۳۴۱ بایت میرسه. برای پلاگین Figma transport آمادهای نیست، ولی چون transport فقط یه شیء با دو تابع on و emit هست، نوشتنش حدود ده خط کد میخواد. یه channel میتونه چند transport رو با هم بگیره، مثلاً EventTarget و BroadcastChannel، تا یه emit هم به تب فعلی برسه هم به بقیهٔ تبها.
نسخهٔ 0.11 بخش گمشده رو اضافه کرده: درخواستها. با createTypedRpcChannel علاوه بر emit/on، request/handle هم داری: فرستنده منتظر جواب تایپشده میمونه و abort signal کار طرف مقابل رو هم متوقف میکنه. به گفتهٔ نویسندهها نسخهٔ دستی این الگو مشکل زیاد داره: بعد از timeout، worker به محاسبه ادامه میده، راهی برای لغو نیست، خطاهای worker به فراخواننده نمیرسه و اگه worker کرش کنه، درخواستها تا آخر timeout معطل میمونن. بخش RPC جداست و فقط وقتی ازش استفاده کنی هزینهش رو میدی.
نکات کلیدی:
- هستهٔ typed-channel با minify و brotli حدود ۲۷۱ بایته و با transport مربوط به postMessage به ۳۴۱ بایت میرسه
- هیچ وابستگی خارجی نداره
- با Web Worker، iframe، BroadcastChannel، MessagePort و event bus مبتنی بر EventTarget کار میکنه
- متد on خودش تابع حذف listener رو برمیگردونه
- نسخهٔ 0.11 تابع createTypedRpcChannel با request/handle و پشتیبانی از abort signal رو آورده
- transport سفارشی فقط یه شیء با دو تابع on و emit هست، مثل transport پلاگین Figma در Harmonizer




