초보자를 위한 웹어셈블리 4부: 웹어셈블리와 자바스크립트 동반자

WebAssembly 입문자 가이드 4부에서는 WebAssembly와 JavaScript 간의 상호 작용을 깊이 있게 분석합니다.

이 섹션에서는 JavaScript 환경에서 WebAssembly를 활용하는 방법을 배우고, WebAssembly JavaScript API에 대한 통찰력을 얻습니다.

WebAssembly는 개발자가 브라우저에서 네이티브에 가까운 성능으로 애플리케이션을 실행할 수 있도록 설계된 바이너리 형식의 개방형 표준입니다. 아직 이전 가이드를 읽어보지 않았다면 먼저 읽어보시는 것을 권장합니다.

이제 시작해 보겠습니다.

JavaScript와 함께 WebAssembly를 효과적으로 사용하기

WebAssembly 파트 1 튜토리얼에서는 WASM의 기본 작동 원리를 다루었습니다. 웹 애플리케이션을 위한 고성능 코드를 구현하려면 JavaScript 내에서 WASM API와 기능을 활용해야 합니다. 또한 JavaScript 프레임워크가 WASM을 활용하여 고성능 앱을 만드는 방법에 대해 논의했습니다.

현재로서는 <script type=”module”>을 사용하여 ES6 모듈과 유사한 방식으로 WASM 모듈을 직접 로드할 수는 없습니다. 여기서 JavaScript의 역할이 중요해집니다. JavaScript는 브라우저 내에서 WASM을 로드하고 컴파일하는 데 필요한 다리를 놓습니다. 이러한 프로세스에는 다음 단계가 포함됩니다.

  • .wasm 바이트코드를 ArrayBuffer 또는 형식화된 배열로 가져옵니다.
  • WebAssembly.Module을 사용하여 바이트코드를 컴파일합니다.
  • 컴파일된 모듈을 WebAssembly.Module로 인스턴스화하여 호출 가능한 익스포트를 얻습니다.

따라서 미리 컴파일된 WASM 모듈로 시작해야 합니다. 여기서 다양한 선택지가 있습니다. Rust, C/C++, AssemblyScript, 그리고 TinyGo(Go)와 같은 언어로 코드를 작성한 다음 .wasm 모듈로 변환할 수 있습니다.

기술적인 관점에서 볼 때 WebAssembly는 다양한 언어의 컴파일 대상입니다. 즉, 원하는 언어로 코드를 작성한 다음, 생성된 바이너리 코드를 웹 애플리케이션 또는 비 웹 애플리케이션에서 사용합니다. 서버 측에서 사용하려면 WASI를 사용하여 시스템과 상호 작용해야 합니다.

WebAssembly는 확장 가능한 배열을 통해 선형 메모리를 활용하므로 JavaScript와 WASM 모두 동기식으로 메모리에 액세스할 수 있습니다. 이러한 기능을 통해 풍부하고 빠른 응용 프로그램을 만들 수 있습니다.

WebAssembly 및 JavaScript 작동 예시

예제를 통해 JavaScript로 WASM을 사용하는 방법을 알아봅시다.

위에서 언급했듯이, 미리 컴파일된 WASM 모듈이 필요합니다. 여기서는 Emscripten(C/C++)을 예시로 사용하겠습니다. WASM은 고성능 바이너리 형식을 제공하므로, 생성된 코드를 JavaScript 또는 기타 언어와 함께 실행할 수 있습니다.

도구 환경 설정

Emscripten을 사용하려면 emsdk 도구가 필요합니다. 이를 통해 C/C++ 코드를 .wasm 코드로 컴파일할 수 있습니다.

터미널에서 다음 명령을 실행하기만 하면 됩니다. GIT가 설치되어 있지 않은 경우, 오픈 소스 101: 버전 제어 시스템 및 Git 가이드에 따라 설치하십시오.

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
#Output

[email protected]:~/Projects/WASM2$ git clone https://github.com/emscripten-core/emsdk.git
Cloning into 'emsdk'...
remote: Enumerating objects: 3566, done.
remote: Counting objects: 100% (62/62), done.
remote: Compressing objects: 100% (49/49), done.
remote: Total 3566 (delta 31), reused 38 (delta 13), pack-reused 3504
Receiving objects: 100% (3566/3566), 2.09 MiB | 2.24 MiB/s, done.
Resolving deltas: 100% (2334/2334), done.
[email protected]:~/Projects/WASM2$ cd emsdk
[email protected]:~/Projects/WASM2/emsdk$

emsdk 폴더에서 다른 명령을 호출하여 바로 사용할 수 있는 최신 Emscripten 빌드를 가져옵니다.

이를 위해서는 다음 명령을 실행해야 합니다.

./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
#Output

[email protected]:~/Projects/WASM2/emsdk$ ./emsdk install latest
Resolving SDK alias 'latest' to '3.1.31'
Resolving SDK version '3.1.31' to 'sdk-releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'
Installing SDK 'sdk-releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'..
Installing tool 'node-14.18.2-64bit'..
Downloading: /home/nitt/Projects/WASM2/emsdk/zips/node-v14.18.2-linux-x64.tar.xz from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/node-v14.18.2-linux-x64.tar.xz, 21848416 Bytes
Unpacking '/home/nitt/Projects/WASM2/emsdk/zips/node-v14.18.2-linux-x64.tar.xz' to '/home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit'
Done installing tool 'node-14.18.2-64bit'.
Installing tool 'releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'..
Downloading: /home/nitt/Projects/WASM2/emsdk/zips/1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-wasm-binaries.tbz2 from https://storage.googleapis.com/webassembly/emscripten-releases-builds/linux/1eec24930cb2f56f6d9cd10ffcb031e27ea4157a/wasm-binaries.tbz2, 349224945 Bytes
Unpacking '/home/nitt/Projects/WASM2/emsdk/zips/1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-wasm-binaries.tbz2' to '/home/nitt/Projects/WASM2/emsdk/upstream'
Done installing tool 'releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'.
Done installing SDK 'sdk-releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'.
[email protected]:~/Projects/WASM2/emsdk$ ./emsdk activate latest
Resolving SDK alias 'latest' to '3.1.31'
Resolving SDK version '3.1.31' to 'sdk-releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'
Setting the following tools as active:
   node-14.18.2-64bit
   releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit

Next steps:
- To conveniently access emsdk tools from the command line,
  consider adding the following directories to your PATH:
    /home/nitt/Projects/WASM2/emsdk
    /home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit/bin
    /home/nitt/Projects/WASM2/emsdk/upstream/emscripten
- This can be done for the current shell by running:
    source "/home/nitt/Projects/WASM2/emsdk/emsdk_env.sh"
- Configure emsdk in your shell startup scripts by running:
    echo 'source "/home/nitt/Projects/WASM2/emsdk/emsdk_env.sh"' >> $HOME/.bash_profile

마지막으로 “source ./emsdk_env.sh” 명령을 실행하면 emcc Emscripten 컴파일러 도구의 경로가 설정됩니다. 이를 통해 코드를 컴파일할 수 있습니다.

#Output

[email protected]:~/Projects/WASM2/emsdk$ source ./emsdk_env.sh
Setting up EMSDK environment (suppress these messages with EMSDK_QUIET=1)
Adding directories to PATH:
PATH += /home/nitt/Projects/WASM2/emsdk
PATH += /home/nitt/Projects/WASM2/emsdk/upstream/emscripten
PATH += /home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit/bin

Setting environment variables:
PATH = /home/nitt/Projects/WASM2/emsdk:/home/nitt/Projects/WASM2/emsdk/upstream/emscripten:/home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
EMSDK = /home/nitt/Projects/WASM2/emsdk
EMSDK_NODE = /home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit/bin/node
[email protected]:~/Projects/WASM2/emsdk$ 

이제 다음 명령을 실행하여 wasm 코드를 생성해야 합니다.

emcc hello-koreantech.org.c -o hello-koreantech.org.js
#Output

[email protected]:~/Projects/WASM2$ emcc hello-koreantech.org.c -o hello-koreantech.org.js
shared:INFO: (Emscripten: Running sanity checks)
cache:INFO: generating system asset: symbol_lists/1c683af19e290d0b5ca7a8747d74a76f63dcb362.txt... (this will be cached in "/home/nitt/Projects/WASM2/emsdk/upstream/emscripten/cache/symbol_lists/1c683af19e290d0b5ca7a8747d74a76f63dcb362.txt" for subsequent builds)
cache:INFO:  - ok
[email protected]:~/Projects/WASM2$ dir
emsdk  hello-koreantech.org.c  hello-koreantech.org.js  hello-koreantech.org.wasm
[email protected]:~/Projects/WASM2$ 

보시다시피 “hello-koreantech.org.js” 및 hello-koreantech.org.wasm의 결과물을 얻습니다. 프로젝트 디렉토리에서 dir을 실행하여 파일을 확인할 수 있습니다.

이 두 파일은 모두 필수입니다. hello-koreantech.org.wasm에는 컴파일된 코드가 포함되어 있으며, hello-koreantech.org.js 파일에는 이를 실행하는 데 필요한 JavaScript 코드가 포함되어 있습니다. Emscripten은 웹 및 node.js 실행을 모두 지원하므로 Node를 사용하여 테스트할 수 있습니다.

node hello-koreantech.org.js
#Output

[email protected]p:~/Projects/WASM2$ node hello-koreantech.org.js
Hello, koreantech.org! 
[email protected]:~/Projects/WASM2$ 

웹에서 실행되는 것을 확인하고 싶다면 Emscripten을 사용하여 HTML 파일을 생성할 수 있습니다. 다음 명령을 실행합니다.

emcc hello-koreantech.org.c -o hello-koreantech.org.html
#Output

[email protected]:~/Projects/WASM2$ emcc hello-koreantech.org.c -o hello-koreantech.org.html
[email protected]:~/Projects/WASM2$ 

HTML 파일을 실행하려면 다음 명령을 실행하여 Python 3 HTTPServer를 사용할 수 있습니다.

python3 -m http.server 8000

이제 http://localhost:8000/hello-koreantech.org.html로 이동하여 출력을 확인합니다.

참고: 대부분의 시스템에는 Python이 사전 설치되어 있습니다. 그렇지 않은 경우 Python3 서버를 실행하기 전에 쉽게 설치할 수 있습니다.

JavaScript API를 사용하여 WASM 상호 작용

이 섹션에서는 JavaScript WASM API를 자세히 살펴봅니다. 이를 통해 WASM 코드를 로드하고 실행하는 방법을 배울 수 있습니다. 다음 코드를 살펴보겠습니다.

fetch('hello-koreantech.org.wasm').then( response =>
   response.arrayBuffer())
   .then (bytes =>
       WebAssembly.instantiate(bytes))
       .then(result=>
           alert(result.instance.exports.answer()))

위의 코드는 다음 JavaScript API를 사용합니다.

  • fetch() 브라우저 API
  • WebAssembly.instantiate

이 외에도 주목할 만한 다른 API가 있습니다. 여기에는 다음이 포함됩니다.

  • WebAssembly.compile
  • WebAssembly.Instance
  • WebAssembly.instantiate
  • WebAssembly.instantiateStreaming

fetch() 브라우저 API

fetch() API는 .wasm 네트워크 리소스를 가져옵니다. 로컬에서 로드하려는 경우 네트워크 리소스를 로드하려면 CORS(Cross-Origin Resource Sharing)를 비활성화해야 합니다. 또는 노드 서버를 사용하여 이를 처리할 수 있습니다. 노드 서버를 설치하고 실행하려면 다음 명령을 실행하십시오.

sudo apt install npm

다음 명령을 실행하여 서버를 시작합니다.

npx http-server -o
#Output

http-server version: 14.1.1

http-server settings: 
CORS: disabled
Cache: 3600 seconds
Connection Timeout: 120 seconds
Directory Listings: visible
AutoIndex: visible
Serve GZIP Files: false
Serve Brotli Files: false
Default File Extension: none

Available on:
  http://127.0.0.1:8080
  http://192.168.0.107:8080
Hit CTRL-C to stop the server
Open: http://127.0.0.1:8080

[2023-01-28T19:22:21.137Z]  "GET /" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.70"
(node:37919) [DEP0066] DeprecationWarning: OutgoingMessage.prototype._headers is deprecated
(Use `node --trace-deprecation ...` to show where the warning was created)
[2023-01-28T19:22:21.369Z]  "GET /favicon.ico" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.70"
[2023-01-28T19:22:21.369Z]  "GET /favicon.ico" Error (404): "Not found"

프로젝트 파일에 접근할 수 있는 웹 브라우저가 열립니다.

이제 hello-koreantech.org.html을 열고 웹 개발자 도구를 실행합니다. 콘솔에서 다음을 입력합니다.

fetch(“hello-koreantech.org.wasm”);

다음 약속을 반환합니다.

#Output

Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Response
body: (...)
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 200
statusText: "OK"
type: "basic"
url: "http://127.0.0.1:8080/hello-koreantech.org.wasm"
[[Prototype]]: Response

다음 스크립트를 작성하고 HTML을 통해 실행할 수도 있습니다.

서버에서 wasm 모듈을 실행하려면 Node.js에서 다음 코드를 사용해야 합니다.

const fs = require('fs');
const run = async() => {
   const buffer = fs.readFileSync("./hello-koreantech.org.wasm");
   const result = await WebAssembly.instantiate(buffer);
   console.log(result.instance.exports.answer());
};

run();

더 자세한 정보는 WebAssembly JavaScript API 문서를 참조하십시오.

JavaScript와 WASM 비교

WASM과 JavaScript 간의 관계를 이해하려면 두 기술을 비교해야 합니다. WASM은 기본적으로 더 빠르며, 대상 컴파일을 위한 바이너리 형식을 가지고 있지만 JavaScript는 고수준 언어입니다. WASM의 바이너리 코드는 배우기 어려울 수 있지만, 효과적으로 활용할 수 있는 방법이 있습니다.

WASM과 JavaScript의 주요 차이점은 다음과 같습니다.

  • WASM은 컴파일된 언어이고 JS는 해석된 언어입니다. 브라우저는 JavaScript 코드를 런타임에 다운로드하고 구문 분석해야 하지만, WASM 코드는 미리 컴파일되어 즉시 실행할 수 있습니다.
  • WebAssembly는 저수준 언어입니다. 반대로 JavaScript는 고수준 언어입니다. 고수준 JS는 작업하기 쉽지만, 저수준 WASM은 JavaScript보다 훨씬 빠르게 실행됩니다.
  • 마지막으로 JavaScript는 대규모 커뮤니티의 이점을 누립니다. 따라서 더 나은 개발 경험을 원한다면 JS가 좋은 선택입니다. 반면에 WebAssembly는 비교적 새로운 기술이므로 자료가 적습니다.

개발자로서 하나를 선택해야 할지 고민할 필요는 없습니다. JS와 WASM은 서로 대립하지 않고 함께 작동하기 때문입니다.

따라서 고성능 애플리케이션을 만들 때 WebAssembly를 사용하여 성능이 필요한 부분만 코딩할 수 있습니다. JavaScript API를 사용하면 WASM 모듈을 JavaScript 코드에 직접 로드하고 사용할 수 있습니다.

결론

결론적으로 WebAssembly는 JavaScript의 훌륭한 동반자입니다. 웹 및 비 웹 환경에서 고성능 애플리케이션을 만들 수 있도록 개발자에게 새로운 가능성을 열어줍니다. 또한, JavaScript를 대체하는 것이 아니라 보완하는 역할입니다.

그러나 WASM이 JavaScript를 완전히 대체할 수 있는 완전한 기술로 발전할까요? WebAssembly의 목표를 고려할 때 그럴 가능성은 낮아 보입니다. 하지만 미래에 WebAssembly가 JavaScript를 대체할 가능성을 완전히 배제할 수는 없습니다.

다음으로, 최신 애플리케이션 구축을 위한 최고의 JavaScript(JS) UI 라이브러리를 알아보십시오.