ويب أسمبلي في تايب سكريبت
تشرح هذه المقالة ويب أسمبلي في تايب سكريبت۔
سنشرح طرقًا عملية وسهلة الفهم لدمج تايب سكريبت مع ويب أسمبلي۔
YouTube Video
ويب أسمبلي في تايب سكريبت
ويب أسمبلي (Wasm) هو بيئة تشغيل ثنائية تعمل بسرعة قريبة من الأصلية داخل المتصفح۔ من خلال استدعاء Wasm من تايب سكريبت، يمكنك الاستفادة بكفاءة من العمليات المكثفة والمعالجات والمكتبات الأصلية المكتوبة بلغة C/C++ أو Rust۔
تسلسل التنفيذ الأساسي
هنا، سنشرح تسلسل التنفيذ الأساسي لـWasm۔ يقوم تايب سكريبت (أو المتصفح) بجلب ملف .wasm، ثم إنشائه واستدعاء الدوال المصدرة منه۔
- ١. أنشئ ملف .wasm ثنائي باستخدام AssemblyScript أو Rust أو C++، أو حضّر ملفاً موجوداً بالفعل۔
- ٢. احصل على ملف .wasm في تايب سكريبت (أو المتصفح) وفعّل الإنشاء بشكل متزامن أو غير متزامن۔
- ٣. استدعِ الدوال المصدرة وشارك الذاكرة باستخدام
WebAssembly.Memoryعند الحاجة۔
WebAssembly.instantiateStreaming
بعد ذلك، سنُظهر مثالًا بسيطًا حول تحميل ملف Wasm واستدعاء دالة مصدرة۔ يجب أن يدعم المتصفح instantiateStreaming۔
الكود التالي هو مثال على جلب simple.wasm من الخادم واستدعاء دالة add۔
1// TypeScript: load-and-call.ts
2// Fetch and instantiate a wasm module and call its exported `add` function.
3async function run() {
4 const response = await fetch('http://localhost:3000/simple.wasm');
5 // Use instantiateStreaming when available for efficiency.
6 const { instance } = await WebAssembly.instantiateStreaming(response, {});
7 // @ts-ignore
8 const result = instance.exports.add(2, 3);
9 console.log('2 + 3 =', result);
10}
11run().catch(console.error);- يتم تخزين الدوال داخل Wasm في
instance.exports۔ - نظرًا لأن تايب سكريبت لا يستقبل معلومات الأنواع، عليك استعمال
@ts-ignoreأو إنشاء تعريفات الأنواع الخاصة بك۔
سير العمل باستخدام AssemblyScript
AssemblyScript يمكّنك من كتابة Wasm بصياغة مشابهة لـ تايب سكريبت، مما يجعله خيارًا مناسبًا لمطوري تايب سكريبت۔ هنا، نقوم بتحضير دالة بسيطة وتحويلها إلى ملفات .wasm و .d.ts، ثم نستدعيها من TypeScript۔
1// assembly/index.ts (AssemblyScript)
2// npm install --save-dev assemblyscript
3
4// Export a simple function to add two integers.
5export function add(a: i32, b: i32): i32 {
6 return a + b;
7}- باستخدام
asc(مترجم AssemblyScript)، يمكنك توليد ملف.wasmوبشكل اختياري، ملف تعريف النوع.d.ts۔ لتجربته محليًا، قم بتثبيتassemblyscriptعبر npm وقم بالبناء۔
1# build commands
2# npm install --save-dev assemblyscript
3npx asc assembly/index.ts -o build/simple.wasm -t build/simple.wat --bindings esm --exportTable --sourceMap
4
5# optionally generate d.ts with --exportRuntime or use as-bind / loader toolsإليك مثال على الجلب والاستدعاء من جانب تايب سكريبت۔
1// ts client that loads AssemblyScript-generated wasm
2async function runAssemblyScript() {
3 const res = await fetch('http://localhost:3000/build/simple.wasm');
4 const { instance } = await WebAssembly.instantiateStreaming(res, {});
5 // @ts-ignore
6 console.log('AssemblyScript add:', instance.exports.add(10, 7));
7}
8runAssemblyScript().catch(console.error);- يتطلب AssemblyScript التعامل بعناية مع نماذج الذاكرة والسلاسل النصية، لكنه سهل للغاية في العمليات الحسابية البسيطة۔
Rust + wasm-bindgen (خيار قوي وشائع الاستخدام)
يشرح هذا القسم سير العمل الخاص بكتابة Wasm بلغة Rust وربطها مع JavaScript أو TypeScript باستخدام wasm-bindgen۔ هنا، نستخدم دالة فيبوناتشي بسيطة كمثال لشرح كيفية استيراد الوحدة الناتجة كوحدة ES۔
قم بتصدير الدوال من جانب Rust باستخدام wasm-bindgen۔
1// src/lib.rs (Rust)
2// install wasm-pack from https://drager.github.io/wasm-pack/installer/
3use wasm_bindgen::prelude::*;
4
5// Export a function to JavaScript using wasm-bindgen.
6#[wasm_bindgen]
7pub fn fib(n: u32) -> u32 {
8 if n <= 1 { return n; }
9 let mut a = 0;
10 let mut b = 1;
11 for _ in 2..=n {
12 let tmp = a + b;
13 a = b;
14 b = tmp;
15 }
16 b
17}- عند البناء باستخدام
wasm-packأو CLI لـwasm-bindgen، يتم توليد تعريفات الأنواع الخاصة بـ تايب سكريبت وملفات التفاف JS، مما يمكّنك من استيرادها مباشرة كوحدات ESM۔
1# build with wasm-pack
2# install wasm-pack from https://drager.github.io/wasm-pack/installer/
3wasm-pack build --target nodejs --out-dir pkgعلى جانب تايب سكريبت، قم باستيراد واستخدام وحدة ES من مجلد pkg۔
1// Node.js: import WASM module built with --target web
2// import init, { fib } from '../pkg/my_wasm_module.js';
3// Node.js: import WASM module built with --target nodejs
4import wasm from '../pkg/my_wasm_module.js';
5
6async function run() {
7 //await init(); // --target web
8 console.log('fib(10)=', wasm.fib(10));
9}
10
11run().catch(console.error);wasm-packيولد ملفات تغليف JavaScript وملفات تعريف أنواع .d.ts، مما يسهل استخدامه من تايب سكريبت۔ يرجى ملاحظة أنه عند تحديدwebلخيار--targetفي أمرwasm-pack، فإنه يتطلب تهيئة غير متزامنة۔
مثال عملي على مشاركة الذاكرة: تمرير ومعالجة المصفوفات (المستوى المنخفض)
عند تبادل كميات كبيرة من البيانات مع Wasm، من المهم مشاركة ArrayBuffer لتبادل البيانات بكفاءة۔ هنا نوضح مثالًا باستخدام AssemblyScript، لكن نفس المبدأ ينطبق على wasm-bindgen في Rust۔
على جانب AssemblyScript، أعد دالة مصدرة للكتابة على الذاكرة۔ على سبيل المثال، دالة لتربيع كل عنصر في المصفوفة ستكون كالتالي۔
1// assembly/array_ops.ts (AssemblyScript)
2// Square values in place in the wasm linear memory starting at `ptr` for `len` elements.
3export function square_in_place(ptr: usize, len: i32): void {
4 // Treat memory as a pointer to 32-bit integers.
5 for (let i = 0; i < len; i++) {
6 let offset = ptr + (i << 2); // i * 4 bytes
7 let value = load<i32>(offset);
8 store<i32>(offset, value * value);
9 }
10}لتحديد إعدادات الذاكرة المستخدمة بواسطة AssemblyScript، قم بتحضير ملف asconfig.json التالي۔
1{
2 "options": {
3 "memoryBase": 0,
4 "importMemory": false,
5 "initialMemory": 1,
6 "maximumMemory": 10
7 }
8}1 npx asc assembly/array_ops.ts -o build/array_ops.wasm -t build/array_ops.wat --bindings esm --exportTable --sourceMap- لاستدعاء هذه الدالة، عليك نسخ
ArrayBufferإلى مساحة ذاكرة Wasm وتمرير المؤشر۔
فيما يلي مثال لاستخدام WebAssembly.Memory في تايب سكريبت لنسخ البيانات واستدعاء الدالة۔
1// TypeScript: use memory to pass array to wasm
2async function runArrayOps() {
3 const res = await fetch('http://localhost:3000/build/array_ops.wasm');
4 const { instance } = await WebAssembly.instantiateStreaming(res, {});
5 // @ts-ignore
6 const memory: WebAssembly.Memory = instance.exports.memory;
7 // Create a view into wasm memory.
8 const i32View = new Int32Array(memory.buffer);
9
10 // Example data
11 const input = new Int32Array([1, 2, 3, 4]);
12 // Choose an offset (in i32 elements) to copy data to (simple example: at index 0).
13 const offset = 0;
14 i32View.set(input, offset);
15
16 // Call wasm function: ptr in bytes, len in elements
17 // @ts-ignore
18 instance.exports.square_in_place(offset * 4, input.length);
19
20 // Read back result
21 const result = i32View.slice(offset, offset + input.length);
22 console.log('squared:', result);
23}
24runArrayOps().catch(console.error);memory.bufferهو الذاكرة الخطية المشتركة؛ كلما قللت النسخ، زادت سرعة المعالجة۔ لاحظ أيضًا أن المؤشرات تشير إلى مواقع بالبايت، بينما تتم إدارةTypedArrayبحسب عدد العناصر، لذا احذر من الخلط بين هذه الاختلافات۔
معالجة آمنة للأنواع: حضّر تعريفات أنواع لتايب سكريبت
صادرات Wasm هي كائنات JavaScript، لذا فإن توفير تعريفات الأنواع في جانب تايب سكريبت يُسهّل عملية التطوير۔ إليك مثال بسيط لملف تعريف أنواع۔
يظهر أدناه الحد الأدنى لتعريف النوع الذي يمكنك إنشاؤه يدويًا باسم simple.d.ts۔
1// simple.d.ts
2export function add(a: number, b: number): number;
3export const memory: WebAssembly.Memory;- وضع هذا الملف في
typeRootsداخلtsconfig.jsonأو استخدامdeclare moduleسيمكن من التحقق من الأنواع۔wasm-packيولد ملفات .d.ts تلقائيًا، لذا من المفيد الاستفادة منها۔
أنماط التهيئة وقت التشغيل: التزامن أم اللاتزامن
نظرًا لأن وحدات Wasm تتطلب عمليات إدخال/إخراج (جلب) وتجميع، فإن التهيئة غير المتزامنة أمر شائع۔ ومع ذلك، هناك نمط أيضًا حيث تقوم بتخزين WebAssembly.Module مؤقتًا مسبقًا وإنشائه بطريقة متزامنة۔
فيما يلي الهيكل الأساسي للكود لتهيئة WebAssembly بشكل غير متزامن۔ في المشاريع الفعلية، يُوصى بهذا النمط۔
1// async init pattern
2async function initWasm(url: string) {
3 const res = await fetch(url);
4 const { instance, module } = await WebAssembly.instantiateStreaming(res, {});
5 return instance;
6}- التهيئة غير المتزامنة تسهل دمج معالجة الأخطاء والتحميل البطيء بمرونة، مما يجعلها الأنسب في التطوير العملي۔ إضافة إلى ذلك، الكود الناتج عن
wasm-packيحتوي على دالةinit()للتهيئة، لذا التعود على هذا التسلسل سيسهّل عملك۔
اعتبارات عملية للأداء
فيما يلي بعض النقاط التي يجب وضعها في الاعتبار لتحقيق تحسين كبير في الأداء۔ يرجى الرجوع إلى هذه النصائح عند دمج تايب سكريبت مع ويب أسمبلي۔
- عند كثرة استدعاء الدوال، قد تصبح تكلفة الانتقال بين JavaScript وWasm عنق زجاجة۔ ننصح بتجميع البيانات ومعالجتها دفعة واحدة قدر الإمكان۔
- تخصيص ونسخ الذاكرة يزيد من عبء المعالجة۔ استخدم الذاكرات المشتركة والمؤشرات للحد من هذه العمليات۔
- كن حذرًا عند التعامل مع الأعداد العشرية۔ في تايب سكريبت، تصبح الأرقام ذات النوع
number، لكن يمكنك معالجتها بدقة عند مطابقة الأنواع في جانب Wasm۔
الملخص
عن طريق الجمع بين تايب سكريبت وويب أسمبلي، يمكنك تحقيق أداء قريب من الأداء الأصلي في المتصفح۔ وهذا فعال بشكل خاص في المهام الحسابية المكثفة أو عند الرغبة في استغلال الأصول الأصلية المتوفرة۔ هذا الدمج يعد خيارًا قويًا جدًا عندما ترغب في تحسين أداء تطبيقك على الويب۔
يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔