إنتقل إلى المحتوى الرئيسي

تطوير WebAssembly في Cursor: Rust و AssemblyScript وتكامل المتصفح

تطوير WebAssembly في Cursor

يجلب WebAssembly (WASM) أداءً قريبًا من الأصلي إلى المتصفح من خلال السماح بتشغيل الكود المُجمّع بجانب JavaScript. يُسرّع Cursor، وهو محرر IDE مدعوم بالذكاء الاصطناعي، تطوير WebAssembly من خلال إنشاء مشاريع Rust و AssemblyScript، وتوليد تكوينات wasm-pack، وتحسين الحلقات الساخنة بمساعدة الذكاء الاصطناعي. يغطي هذا الدليل سير العمل الكامل من إعداد المشروع إلى تكامل المتصفح.

المتطلبات الأساسية​

تأكد من تثبيت ما يلي قبل البدء:

  • Cursor (يُنصح بأحدث إصدار) كمحرر IDE الخاص بك
  • سلسلة أدوات Rust (rustup، cargo) لتجميع Rust إلى WASM
  • Node.js 18 أو أعلى لأدوات JavaScript و AssemblyScript
  • wasm-pack لبناء Rust في حزم WebAssembly
  • متصفح حديث (Chrome أو Firefox أو Edge) للاختبار
تلميح

إذا لم يكن لديك سلسلة أدوات Rust، يمكن للطرفية المدمجة في Cursor تثبيتها. شغّل مثبّت Rust، ثم أضف هدف WASM باستخدام rustup target add wasm32-unknown-unknown. ثبّت wasm-pack باستخدام cargo install wasm-pack أو استخدم نص التثبيت الرسمي.

Rust إلى WebAssembly مع Cursor​

Rust هي اللغة الأكثر شيوعًا لاستهداف WebAssembly بسبب أدائها وسلامتها وأدواتها من الدرجة الأولى. يساعدك Cursor في إنشاء وبناء وتصحيح أخطاء وحدات Rust WASM.

إنشاء مشروع Rust WASM​

افتح مجلدًا جديدًا في Cursor واستخدم محادثة الذكاء الاصطناعي لإنشاء مشروع:

أنشئ مشروع Rust لـ WebAssembly يقوم بـ:
- استخدام wasm-bindgen للربط بين Rust و JavaScript
- تصدير دالة تجمع رقمين
- تصدير دالة تعكس سلسلة
- تضمين Cargo.toml مُكوّن لـ cdylib
- امتلاك ملف اختبار وحدة

يُنشئ Cursor هيكل Rust WASM القياسي:

rust-wasm-app/
src/
lib.rs
utils.rs
tests/
web.rs
Cargo.toml

ملف Cargo.toml المُنشأ:

[package]
name = "rust-wasm-app"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1", features = ["derive"] }
serde-wasm-bindgen = "0.6"

[dev-dependencies]
wasm-bindgen-test = "0.3"

[profile.release]
opt-level = "s"
lto = true
codegen-units = 1

ملف src/lib.rs المُنشأ:

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

#[wasm_bindgen]
pub fn reverse_string(input: &str) -> String {
input.chars().rev().collect()
}

#[wasm_bindgen]
pub fn factorial(n: u32) -> u64 {
(1..=n as u64).product()
}

البناء باستخدام wasm-pack​

تجعل طرفية Cursor تشغيل wasm-pack سلسًا. اطلب من الذكاء الاصطناعي إنشاء نص بناء:

أنشئ نص بناء يقوم بتشغيل wasm-pack بالهدف ودليل الإخراج
الصحيحين، ثم ينسخ مجلد pkg المُنشأ إلى مجلد web.

يُنشئ Cursor ملف build.sh:

#!/usr/bin/env bash
set -e

echo "Building Rust to WebAssembly..."
wasm-pack build --target web --out-dir pkg

echo "Copying package to web folder..."
mkdir -p web/pkg
cp -r pkg/* web/pkg/

echo "Build complete. Open web/index.html in a browser."

شغّله من طرفية Cursor:

chmod +x build.sh
./build.sh

كتابة الاختبارات لـ WASM​

يمكن اختبار دوال Rust WASM باستخدام wasm-bindgen-test. يُنشئ Cursor ملف الاختبار:

use rust_wasm_app::{add, reverse_string, factorial};
use wasm_bindgen_test::*;

#[wasm_bindgen_test]
fn test_add() {
assert_eq!(add(2, 3), 5);
assert_eq!(add(-1, 1), 0);
}

#[wasm_bindgen_test]
fn test_reverse_string() {
assert_eq!(reverse_string("cursor"), "rosruc");
assert_eq!(reverse_string(""), "");
}

#[wasm_bindgen_test]
fn test_factorial() {
assert_eq!(factorial(0), 1);
assert_eq!(factorial(5), 120);
assert_eq!(factorial(10), 3628800);
}

شغّل الاختبارات في طرفية Cursor:

wasm-pack test --headless --firefox
warning

تعمل اختبارات WebAssembly داخل محرك متصفح، وليس كاختبارات Rust أصلية. يجب تحديد متصفح باستخدام --chrome أو --firefox أو --safari عند تشغيل wasm-pack test. إذا لم يكن المتصفح مثبتًا، تفشل الاختبارات بصمت مع خطأ اتصال. يمكن لذكاء Cursor الاصطناعي المساعدة في تشخيص هذه الإخفاقات إذا لصقت مخرجات الخطأ.

تطوير AssemblyScript في Cursor​

يُجمّع AssemblyScript بناء جملة شبيهًا بـ TypeScript مباشرة إلى WebAssembly، مما يجعله خيارًا ممتازًا للمطورين المعتادين بالفعل على TypeScript. يوفر Cursor دعمًا قويًا لـ AssemblyScript لأن بناء الجملة يتداخل مع TypeScript.

إعداد مشروع AssemblyScript​

أنشئ مجلدًا جديدًا واطلب من Cursor إنشاء مشروع AssemblyScript:

أنشئ مشروع AssemblyScript يقوم بـ:
- امتلاك ملف دخول assembly/index.ts
- تصدير دوال لمعالجة الصور (تدرج الرمادي، عكس)
- تضمين asconfig.json لبناءات الإصدار
- امتلاك package.json مع تبعية assemblyscript

يُنشئ Cursor:

assemblyscript-app/
assembly/
index.ts
image.ts
asconfig.json
package.json
index.html

ملف assembly/index.ts:

export function grayscale(
pixels: Uint8Array,
width: i32,
height: i32
): Uint8Array {
const result = new Uint8Array(pixels.length);
for (let i = 0; i < pixels.length; i += 4) {
const r = pixels[i];
const g = pixels[i + 1];
const b = pixels[i + 2];
const gray = <u8>((r * 77 + g * 150 + b * 29) >> 8);
result[i] = gray;
result[i + 1] = gray;
result[i + 2] = gray;
result[i + 3] = pixels[i + 3]; // الحفاظ على alpha
}
return result;
}

export function invert(pixels: Uint8Array): Uint8Array {
const result = new Uint8Array(pixels.length);
for (let i = 0; i < pixels.length; i += 4) {
result[i] = 255 - pixels[i];
result[i + 1] = 255 - pixels[i + 1];
result[i + 2] = 255 - pixels[i + 2];
result[i + 3] = pixels[i + 3];
}
return result;
}

ملف asconfig.json:

{
"entries": ["assembly/index.ts"],
"targets": {
"release": {
"optimizeLevel": 3,
"shrinkLevel": 0,
"converge": false,
"noAssert": false
},
"debug": {
"debug": true
}
},
"options": {
"exportRuntime": true,
"exportMemory": true
}
}

بناء AssemblyScript​

ثبّت التبعيات وابنِ باستخدام طرفية Cursor:

npm install
npx asc assembly/index.ts --target release --outFile build/release.wasm
تلميح

بناء جملة AssemblyScript قريب جدًا من TypeScript، لذا يوفر خادم لغة TypeScript في Cursor تشخيصات مضمّنة مفيدة. الفرق الرئيسي هو أن AssemblyScript يتطلب أنواعًا رقمية صريحة مثل i32 و u8 و f64. اطلب من Cursor: "حوّل دالة TypeScript هذه إلى AssemblyScript صالح،" وسيُعلّم الأنواع بشكل صحيح.

تكامل المتصفح​

WebAssembly مفيد فقط عندما يمكنك تحميله واستدعائه من المتصفح. يمكن لـ Cursor إنشاء كود الغراء JavaScript لكل من وحدات Rust و AssemblyScript.

تحميل وحدة Rust WASM​

اطلب من Cursor إنشاء صفحة HTML تحمل حزمة Rust:

أنشئ web/index.html يقوم بـ:
- تحميل وحدة wasm باستخدام هدف web
- استدعاء دوال add و reverse_string
- عرض النتائج على الصفحة
- تضمين معالجة الأخطاء لفشل تحميل الوحدة

يُنشئ Cursor ملف web/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Cursor WASM Demo</title>
</head>
<body>
<h1>WebAssembly in Cursor</h1>
<p>Add result: <span id="add-result"></span></p>
<p>Reverse result: <span id="reverse-result"></span></p>
<p>Factorial result: <span id="factorial-result"></span></p>

<script type="module">
import init, { add, reverse_string, factorial } from './pkg/rust_wasm_app.js';

async function run() {
try {
await init();
document.getElementById('add-result').textContent = add(40, 2);
document.getElementById('reverse-result').textContent = reverse_string('hello');
document.getElementById('factorial-result').textContent = factorial(10);
} catch (err) {
console.error('Failed to load WASM module:', err);
}
}

run();
</script>
</body>
</html>

تحميل وحدة AssemblyScript​

يُنتج AssemblyScript ملف .wasm مستقلًا تقوم بنسخه يدويًا. يُنشئ Cursor المحمّل:

async function loadAssemblyScriptModule() {
const response = await fetch('build/release.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {
env: {
abort: (msg, file, line, column) => {
console.error(`Abort at ${file}:${line}:${column}: ${msg}`);
},
},
});
return instance.exports;
}

const exports = await loadAssemblyScriptModule();
const grayPixels = exports.grayscale(rgbaPixels, width, height);

مشاركة الذاكرة بين JavaScript و WASM​

للعمليات الحرجة بالأداء مثل معالجة الصور، تجنب نسخ البيانات. شارك الذاكرة مباشرة باستخدام الذاكرة الخطية لـ WebAssembly. يُنشئ Cursor جسر ذاكرة:

// جانب JavaScript
function processImageWithWasm(wasmExports, imageData) {
const { __new, __pin, __unpin, grayscale } = wasmExports;
const ptr = __new(imageData.length, 1); // 1 = Uint8Array id
const wasmMemory = new Uint8Array(wasmExports.memory.buffer);
wasmMemory.set(imageData, ptr);
const resultPtr = grayscale(ptr, width, height);
__pin(resultPtr);
const result = wasmMemory.slice(
resultPtr,
resultPtr + imageData.length
);
__unpin(resultPtr);
return result;
}
warning

يمكن أن تنمو الذاكرة الخطية لـ WebAssembly أثناء التخصيص، مما يُبطل أي عروض TypedArray في JavaScript تحتفظ بها. أعد دائمًا إنشاء العروض من wasmExports.memory.buffer بعد أي دالة WASM قد تخصص ذاكرة. إذا رأيت بيانات عشوائية في مخرجاتك، فإن عرض الذاكرة القديم هو السبب دائمًا تقريبًا.

تحسين WASM بمساعدة الذكاء الاصطناعي​

ذكاء Cursor الاصطناعي فعال بشكل خاص في تحسين كود WASM الحرج بالأداء لأنه يمكنه التفكير في تخطيط الذاكرة وتعليمات SIMD والتعقيد الخوارزمي.

تحسين الحلقات الساخنة​

حدد حلقة في كود Rust أو AssemblyScript الخاص بك واطلب من Cursor:

حسّن هذه الحلقة لـ WebAssembly. استخدم SIMD حيثما أمكن
وقلل تخصيصات الذاكرة داخل الحلقة.

يعيد Cursor كتابة حلقة قياسية إلى نسخة موجهة باستخدام std::simd في Rust:

use std::simd::{f32x4, SimdFloat};

#[wasm_bindgen]
pub fn dot_product_simd(a: &[f32], b: &[f32]) -> f32 {
let mut sum = f32x4::splat(0.0);
let chunks = a.chunks_exact(4).zip(b.chunks_exact(4));
for (a_chunk, b_chunk) in chunks {
let va = f32x4::from_slice(a_chunk);
let vb = f32x4::from_slice(b_chunk);
sum += va * vb;
}
let mut total = sum.reduce_sum();
let remainder = a.len() % 4;
for i in (a.len() - remainder)..a.len() {
total += a[i] * b[i];
}
total
}

تقليل حجم الملف الثنائي​

الملفات الثنائية WASM الكبيرة تبطئ تحميل الصفحة. اطلب من Cursor تحليل ملفك الثنائي:

ملف WebAssembly الثنائي الخاص بي بحجم 1.2 ميجابايت. اقترح تغييرات
على Cargo.toml والكود لتقليل حجم الملف الثنائي للتسليم عبر الويب.

يوصي Cursor بتعديلات الملف الشخصي وإزالة الكود الميت:

[profile.release]
opt-level = "z" # التحسين للحجم
lto = true # تحسين وقت الرابط
codegen-units = 1 # وحدة codegen واحدة
panic = "abort" # لا جداول فك
strip = true # إزالة الرموز
// تجنب كود التنسيق والذعر في المسارات الساخنة
#[cfg(not(feature = "debug"))]
#[panic_handler]
fn panic_handler(_: &core::panic::PanicInfo) -> ! {
loop {}
}
تلميح

استخدم wasm-opt كخطوة ما بعد البناء لتقليل الحجم الإضافي. اطلب من Cursor: "أضف مرحلة wasm-opt إلى نص البناء الخاص بي باستخدام -Oz،" وسيُدرج الأمر بعد مرحلة wasm-pack. هذا يقلل عادةً 10 إلى 20 بالمائة أخرى من الملف الثنائي النهائي.

تصحيح أخطاء WebAssembly في Cursor​

تصحيح أخطاء WASM أصعب من تصحيح أخطاء JavaScript لأن الأخطاء تظهر كأفخاخ خام. يوفر Cursor عدة استراتيجيات.

خرائط المصدر لـ Rust WASM​

فعّل خرائط المصدر حتى يقوم Cursor وأدوات مطور المتصفح بتعيين إطارات WASM إلى مصدر Rust:

wasm-pack build --target web -- --features console_error_panic_hook

في ملف lib.rs الخاص بك، ثبّت خطاف ذعر يطبع إلى وحدة تحكم المتصفح:

use console_error_panic_hook::set_once as set_panic_hook;

#[wasm_bindgen(start)]
pub fn init() {
set_panic_hook();
console_log::init_with_level(log::Level::Info).ok();
}

أضف هذه التبعيات إلى Cargo.toml:

[dependencies]
console_error_panic_hook = "0.1"
console_log = "1"
log = "0.4"

تشخيص الأخطاء بالذكاء الاصطناعي​

عندما تُلقي وحدة WASM خطأ RuntimeError: unreachable، الصق تتبع المكدس في محادثة Cursor:

وحدة WASM الخاصة بي تتعطل مع "RuntimeError: unreachable executed"
عند معالجة مصفوفات كبيرة. إليك مصدر Rust ومحمّل JS.
ابحث عن الخطأ.

يحدد Cursor أخطاء الفهرسة وتجاوز الأعداد الصحيحة والوصول غير المحاذي للذاكرة التي تسبب الأفخاخ.

أنماط شائعة ومقاطع برمجية​

إليك أنماط قابلة لإعادة الاستخدام يمكن لـ Cursor إنشاؤها عند الطلب لمشاريع WASM الخاصة بك.

تمرير البيانات المنظمة​

لتمرير كائنات معقدة بين JavaScript و Rust، استخدم serde-wasm-bindgen:

use serde::{Serialize, Deserialize};
use serde_wasm_bindgen::{from_value, to_value};
use wasm_bindgen::prelude::*;

#[derive(Serialize, Deserialize)]
pub struct Point {
pub x: f64,
pub y: f64,
}

#[wasm_bindgen]
pub fn compute_centroid(points_js: JsValue) -> Result<JsValue, JsValue> {
let points: Vec<Point> = from_value(points_js)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
if points.is_empty() {
return Err(JsValue::from_str("Cannot compute centroid of empty set"));
}
let (sum_x, sum_y) = points.iter()
.fold((0.0, 0.0), |(sx, sy), p| (sx + p.x, sy + p.y));
let centroid = Point {
x: sum_x / points.len() as f64,
y: sum_y / points.len() as f64,
};
to_value(&centroid)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
}

تحديد مكالمات JavaScript​

دوال WASM المُستدعاة في حلقات رسوم متحركة ضيقة يمكن أن تظل تحظر الخيط الرئيسي. استخدم Web Worker لتفريغ الحساب الثقيل. يُنشئ Cursor هيكل العامل:

// worker.js
import init, { process_image } from './pkg/rust_wasm_app.js';

self.onmessage = async (event) => {
await init();
const { pixels, width, height } = event.data;
const result = process_image(pixels, width, height);
self.postMessage({ result }, [result.buffer]);
};
// main.js
const worker = new Worker(new URL('./worker.js', import.meta.url), {
type: 'module',
});
worker.onmessage = (event) => {
drawProcessedPixels(event.data.result);
};
worker.postMessage({ pixels, width, height });
warning

نقل ArrayBuffers مع قائمة النقل ([result.buffer]) هو الطريقة الوحيدة لتجنب نسخ المخازن المؤقتة الكبيرة بين الخيط الرئيسي و Worker. نسيان قائمة النقل على صورة بحجم عدة ميجابايت يسبب نسخة كاملة في كل رسالة، مما يُدمر فائدة الأداء من استخدام WebAssembly في المقام الأول.

ملخص أفضل الممارسات​

اتبع هذه المبادئ عند بناء تطبيقات WebAssembly في Cursor:

  • اختر اللغة الصحيحة: Rust للأداء والسلامة، AssemblyScript لمنحنى تعلم شبيه بـ TypeScript.
  • قلل حدود JS-WASM: استدعاءات أقل وأكبر تتفوق على العديد من الاستدعاءات الصغيرة.
  • شارك الذاكرة بدلاً من نسخ المخازن المؤقتة للبيانات الكبيرة مثل الصور والصوت.
  • فعّل تحسين الحجم في ملف الإصدار الشخصي وشغّل wasm-opt بعد البناء.
  • استخدم Web Workers لأي حساب يستغرق أكثر من بضعة أجزاء من الثانية لتجنب حظر واجهة المستخدم.
  • ثبّت خطافات الذعر حتى تُظهر أخطاء وقت التشغيل رسائل مقروءة بدلاً من أفخاخ معتمة.

من خلال الجمع بين أداء WebAssembly وتوليد الكود المدعوم بالذكاء الاصطناعي في Cursor، يمكنك تسليم وحدات متصفح عالية الأداء بجزء من الكود المتكرر المعتاد. صِف نيتك بوضوح، ودع Cursor ينشئ المشروع وكود الغراء، وركّز طاقتك على الخوارزميات التي تهم.