1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Svelte の self-contained apps で WebAssembly を埋め込む方法

Last updated at Posted at 2025-02-04

はじめに

2024年の暮れ、Svelte の Advent of SvelteにてDay 22: self-contained appsが公開されました。これは Svelte で実装したものを1つの index.html ファイルにまとめる機能です。

便利な点は、index.html ファイルを共有するだけでアプリを配布できることです。

例えば、 Web 上で提供されるサービスは、提供者の都合で見た目や動作が変わってしまうことがあります。しかし、index.html ファイルを共有する形式であれば、利用者の都合に合わせて一定のバージョンを保持できます。(こういった需要がどの程度あるかは未知数ですが...)

動機

self-contained apps ですが、WebAssembly には対応していません(ビルドしても wasm ファイルは独立したままです)。もし WebAssembly にも対応できれば、高度な機能を備えたアプリを index.html だけで共有できるのでは?という発想から検証してみました。

この記事の前提知識

  • WebAssembly の概要程度の知識
  • Linux をかじった程度の知識
  • Svelte の基礎知識

方針検討

JavaScript で wasm ファイルを読み込む際は WebAssembly.instantiateStreaming() を使うのが一般的です。第一引数の sourceResponse 型であり、通常はfetch() を使って取得します。

fetch() の第一引数は URL なので データURL 形式を使用できます。つまり、wasm ファイルを Base64 エンコードすれば index.html 内に埋め込むことが可能になります。

const wasm = await WebAssembly.instantiateStreaming(
                 fetch(`data:application/wasm;base64,AGFzbQEAAAABkYCAgA...ICw==`)
             );

実践

以下の手順で検証します。

  1. WebAssembly 環境の構築
  2. 簡単な WebAssembly アプリを実装
  3. 簡単な Svelte アプリを実装

1. WebAssembly 環境の構築

Emscripten をインストールします。(Ubuntu環境を前提)

ターミナル
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install cmake
$ sudo apt install emscripten
$ emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.5 ()
Ubuntu clang version 13.0.1-2ubuntu2.2
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /usr/bin

2. 簡単な WebAssembly アプリを実装

C言語のコードを作成します。

sample.c
#include <stdio.h>

int add100(int num) {
  return num + 100;
}

wasmファイルを生成します。

ターミナル
$ emcc sample.c -o add100.mjs -s EXPORTED_RUNTIME_METHODS=cwrap -s EXPORTED_FUNCTIONS=['_add100'] -s ENVIRONMENT=web -s SINGLE_FILE --no-entry

ここで重要なのは -s SINGLE_FILE です。これにより wasm ファイルが Base64 文字列に変換されて add100.mjs に埋め込まれます。
参考:https://emscripten.org/docs/tools_reference/settings_reference.html#single-file

また、Svelte で import できるようにするため add100.mjs のように拡張子を mjs に設定しました。

3. 簡単な Svelte アプリを実装

あとはこれを Svelte アプリとして実装してビルドするだけです。

Svelte プロジェクトを作成します。

ターミナル
$ npx sv create myapp
Need to install the following packages:
sv@0.6.18
Ok to proceed? (y) y

┌  Welcome to the Svelte CLI! (v0.6.18)
│
◇  Which template would you like?
│  SvelteKit minimal
│
◇  Add type checking with Typescript?
│  Yes, using Typescript syntax
│
◆  Project created
│
◇  What would you like to add to your project? (use arrow keys / space bar)
│  none
│
◇  Which package manager do you want to install dependencies with?
│  npm
│
◆  Successfully installed dependencies
│
◇  Project next steps ─────────────────────────────────────────────────────╮
│                                                                          │
│  1: cd myapp                                                             │
│  2: git init && git add -A && git commit -m "Initial commit" (optional)  │
│  3: npm run dev -- --open                                                │
│                                                                          │
│  To close the dev server, hit Ctrl-C                                     │
│                                                                          │
│  Stuck? Visit us at https://svelte.dev/chat                              │
│                                                                          │
├──────────────────────────────────────────────────────────────────────────╯
│
└  You're all set!
$ cd myapp
$ npm i -D @sveltejs/adapter-static
$ npm install

self-contained apps を有効化します。

svelte.config.js
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
const config = {
        // Consult https://svelte.dev/docs/kit/integrations
        // for more information about preprocessors
        preprocess: vitePreprocess(),

        kit: {
                // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
                // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
                // See https://svelte.dev/docs/kit/adapters for more information about adapters.
                adapter: adapter(),
                output: {
                        bundleStrategy: 'inline'
                },
                router: {
                        type: 'hash'
                }
        }
};

export default config;

add100.mjs をコピーします。

ターミナル
$ cp [WebAssembly実装ディレクトリ]/add100.mjs src/routes/ 

アプリの HTML を src/routes/+page.svelte に実装します。

src/routes/+page.svelte
<script>
        import { onMount } from 'svelte';
        import initWasm from './add100.mjs';
        // 初期化関数
        async function init() {
                const module = await initWasm();
                // モジュール読み込みが完了するまで待つ
                await new Promise(resolve => {
                        if (module.onRuntimeInitialized) {
                                module.onRuntimeInitialized = resolve;
                        } else {
                                resolve();
                        }
                });
                // add100 関数を読み込む
                const add100 = module.cwrap(
                                        "add100",
                                        "number",
                                        ["number"]
                                );

                // ボタンをクリックされたら add100 を実行するように設定
                const buttonElm = document.querySelector("#button");
                buttonElm.addEventListener("click", () => {
                        const inputElm = document.querySelector("#input");
                        const result = add100(parseInt(inputElm.value));
                        inputElm.value = result;
                });
        }

        onMount(init);
</script>

<input id="input" type="number">
<button id="button">add 100</button>

ビルドします。

ターミナル
$ npm run build

index.html 単体で機能することを確認するため、あえてコピーします。

ターミナル
$ cp build/index.html [コピー先]/index.html

結果確認

index.html ファイルをダブルリックしてブラウザを起動して動作確認してみます。
スクリーンショット 2025-02-03 142737.png
「add 100」ボタンをクリックしてみます。
スクリーンショット 2025-02-03 142748.png
もう一回クリックしてみます。
スクリーンショット 2025-02-03 142753.png
ちゃんと WebAssembly が機能してますね!

まとめ

Svelte の self-contained apps で WebAssembly を実現する手順を検討しました。
結果、emcc コマンドのオプションを工夫することで実現可能であることがわかりました。

ローカル専用で WebAssembly を使ったアプリを手軽に作って共有できそうですね!:grinning:

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?