본문으로 건너뛰기

Cursor에서 WebAssembly 개발: Rust, AssemblyScript 및 브라우저 통합

Cursor에서 WebAssembly 개발

WebAssembly(WASM)는 컴파일된 코드를 JavaScript와 함께 실행하여 브라우저에 네이티브에 가까운 성능을 제공합니다. AI 기반 IDE 에디터인 Cursor는 Rust와 AssemblyScript 프로젝트를 스캐폴드하고, wasm-pack 구성을 생성하며, AI 지원으로 핫 루프를 최적화하여 WebAssembly 개발을 가속화합니다. 이 가이드는 프로젝트 설정부터 브라우저 통합까지의 전체 워크플로를 다룹니다.

사전 요구 사항​

시작하기 전에 다음이 설치되어 있는지 확인하세요:

  • Cursor(최신 버전 권장)를 IDE 에디터로 사용
  • Rust 툴체인(rustup, cargo) - Rust에서 WASM 컴파일용
  • Node.js 18 이상 - JavaScript 툴링 및 AssemblyScript용
  • wasm-pack - Rust를 WebAssembly 패키지로 빌드용
  • 최신 브라우저(Chrome, Firefox 또는 Edge) - 테스트용
팁

Rust 툴체인이 없다면 Cursor의 통합 터미널에서 설치할 수 있습니다. Rust 설치 프로그램을 실행한 후 rustup target add wasm32-unknown-unknown으로 WASM 타겟을 추가하세요. cargo install wasm-pack 또는 공식 설치 프로그램 스크립트로 wasm-pack을 설치하세요.

Cursor에서 Rust를 WebAssembly로​

Rust는 성능, 안전성 및 최고 수준의 툴링으로 인해 WebAssembly를 타겟으로 하는 가장 인기 있는 언어입니다. Cursor는 Rust WASM 모듈을 스캐폴드, 빌드 및 디버그하는 데 도움을 줍니다.

Rust WASM 프로젝트 생성​

Cursor에서 새 폴더를 열고 AI 채팅을 사용하여 프로젝트를 생성하세요:

WebAssembly용 Rust 프로젝트를 스캐폴드해주세요:
- wasm-bindgen을 사용하여 Rust와 JavaScript 연결
- 두 숫자를 더하는 함수 내보내기
- 문자열을 뒤집는 함수 내보내기
- cdylib용으로 구성된 Cargo.toml 포함
- 단위 테스트 파일 포함

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 실행을 원활하게 만듭니다. AI에게 빌드 스크립트를 생성해 달라고 요청하세요:

올바른 타겟과 출력 디렉터리로 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
경고

WebAssembly 테스트는 네이티브 Rust 테스트가 아닌 브라우저 엔진 내에서 실행됩니다. wasm-pack test를 실행할 때 --chrome, --firefox 또는 --safari로 브라우저를 지정해야 합니다. 브라우저가 설치되어 있지 않으면 테스트가 연결 오류로 조용히 실패합니다. 오류 출력을 붙여넣으면 Cursor의 AI가 실패를 진단하는 데 도움을 줄 수 있습니다.

Cursor에서 AssemblyScript 개발​

AssemblyScript는 TypeScript와 유사한 구문을 WebAssembly로 직접 컴파일하므로, 이미 TypeScript에 익숙한 개발자에게 훌륭한 선택입니다. Cursor는 구문이 TypeScript와 겹치므로 AssemblyScript에 대한 강력한 지원을 제공합니다.

AssemblyScript 프로젝트 설정​

새 폴더를 만들고 Cursor에게 AssemblyScript 프로젝트를 스캐폴드해 달라고 요청하세요:

AssemblyScript 프로젝트를 스캐폴드해주세요:
- assembly/index.ts 진입 파일
- 이미지 처리용 함수 내보내기(그레이스케일, 반전)
- 릴리스 빌드용 asconfig.json 포함
- assemblyscript 종속성이 있는 package.json 포함

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]; // 알파 보존
}
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와 매우 유사하므로 Cursor의 TypeScript 언어 서버가 유용한 인라인 진단을 제공합니다. 주요 차이점은 AssemblyScript가 i32, u8, f64와 같은 명시적 숫자 타입을 요구한다는 것입니다. Cursor에게 "이 TypeScript 함수를 유효한 AssemblyScript로 변환해 달라"고 요청하면 타입을 올바르게 주석 처리합니다.

브라우저 통합​

WebAssembly는 브라우저에서 로드하고 호출할 수 있을 때만 유용합니다. Cursor는 Rust와 AssemblyScript 모듈 모두에 대한 JavaScript 글루 코드를 생성할 수 있습니다.

Rust WASM 모듈 로드​

Cursor에게 Rust 패키지를 로드하는 HTML 페이지를 생성해 달라고 요청하세요:

web/index.html을 만들어주세요:
- web 타겟을 사용하여 wasm 모듈 로드
- 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;
}
경고

WebAssembly 선형 메모리는 할당 중에 커질 수 있으며, 이는 보유한 모든 JavaScript TypedArray 뷰를 무효화합니다. 메모리를 할당할 수 있는 WASM 함수 이후에는 항상 wasmExports.memory.buffer에서 뷰를 다시 생성하세요. 출력에 무작위 가비지가 보이면 오래된 메모리 뷰가 거의 항상 원인입니다.

AI 지원 WASM 최적화​

Cursor의 AI는 메모리 레이아웃, SIMD 명령 및 알고리즘 복잡도에 대해 추론할 수 있으므로 성능이 중요한 WASM 코드를 최적화하는 데 특히 효과적입니다.

핫 루프 최적화​

Rust 또는 AssemblyScript 코드에서 루프를 선택하고 Cursor에게 요청하세요:

이 루프를 WebAssembly용으로 최적화해주세요. 가능한 곳에서 SIMD를
사용하고 루프 내 메모리 할당을 최소화해주세요.

Cursor가 스칼라 루프를 Rust에서 std::simd를 사용하는 벡터화된 버전으로 다시 작성합니다:

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 MB입니다. 웹 전달을 위해 바이너리
크기를 줄이기 위한 Cargo.toml 및 코드 변경 사항을 제안해주세요.

Cursor가 프로필 조정 및 데드 코드 제거를 권장합니다:

[profile.release]
opt-level = "z" # 크기 최적화
lto = true # 링크 타임 최적화
codegen-units = 1 # 단일 코드젠 유닛
panic = "abort" # 언와인딩 테이블 없음
strip = true # 심볼 제거
// 핫 패스에서 포맷팅 및 패닉 코드 피하기
#[cfg(not(feature = "debug"))]
#[panic_handler]
fn panic_handler(_: &core::panic::PanicInfo) -> ! {
loop {}
}
팁

추가 크기 감소를 위해 Post-build 단계로 wasm-opt를 사용하세요. Cursor에게 "내 빌드 스크립트에 -Oz로 wasm-opt 패스를 추가해 달라"고 요청하면 wasm-pack 단계 이후에 명령을 삽입합니다. 이는 일반적으로 최종 바이너리를 추가로 10~20% 줄입니다.

Cursor에서 WebAssembly 디버깅​

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"

AI 지원 오류 진단​

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 });
경고

전송 목록([result.buffer])으로 ArrayBuffer를 전송하는 것이 메인 스레드와 워커 간에 대용량 버퍼를 복사하지 않는 유일한 방법입니다. 수 메가바이트 이미지에서 전송 목록을 잊으면 모든 메시지마다 전체 복사가 발생하여 WebAssembly를 사용하는 성능 이점을 무효화합니다.

모범 사례 요약​

Cursor에서 WebAssembly 애플리케이션을 구축할 때 다음 원칙을 따르세요:

  • 올바른 언어 선택: 성능과 안전성을 위한 Rust, TypeScript와 유사한 학습 곡선을 위한 AssemblyScript.
  • JS-WASM 경계 최소화: 적고 큰 호출이 많고 작은 호출보다 성능이 좋습니다.
  • 메모리 공유: 이미지와 오디오 같은 대용량 데이터의 경우 버퍼를 복사하는 대신 메모리를 공유하세요.
  • 크기 최적화 활성화: 릴리스 프로필에서 활성화하고 Post-build로 wasm-opt를 실행하세요.
  • Web Worker 사용: UI를 차단하지 않기 위해 몇 밀리초 이상 걸리는 모든 계산에 사용하세요.
  • 패닉 훅 설치: 런타임 오류가 불투명한 트랩 대신 읽을 수 있는 메시지를 표시하도록 하세요.

WebAssembly의 성능과 Cursor의 AI 기반 코드 생성을 결합하면 일반적인 보일러플레이트의 극히 일부로 고성능 브라우저 모듈을 배포할 수 있습니다. 의도를 명확히 설명하고, Cursor가 프로젝트와 글루 코드를 스캐폴드하도록 한 다음, 중요한 알고리즘에 에너지를 집중하세요.