LoginSignup
15
15

More than 3 years have passed since last update.

Typescript + WebAssembly(Rust)の環境設定

Last updated at Posted at 2019-05-12

TypescriptプロジェクトにWebAssemblyを導入してみたのですが、何か所かハマったポイントがあったのでメモしておきます。OSはWindows10です。

プロジェクト構成例

src/ts/main.ts を webpack + ts-loaderを用いて build/js/main.js にbuildし、index.htmlから読み込む構成を想定します。tsconfig.jsonwebpack.config.js については、自分で作って配置します。


[Project Home]
|_  build
|    |_  js
|         |_  main.js
|
|_  src
|    |_  ts
|         |_  main.ts
|
|_  index.html
|_  package.json
|_ tsconfig.json
|_  webpack.config.js

npm各ライブラリのインストール

webpackを、グローバルインストール

  npm i -g webpack

typescript ほか必要なライブラリをインストール

 npm i -D path ts-loader typescript

package.jsonのdevDependenciesは以下のようになります。

package.json
  "devDependencies": {
    "path": "^0.12.7",
    "ts-loader": "^5.4.5",
    "typescript": "^3.4.5",
  },

Cargoのインストール

RustのパッケージマネージャであるCargoをインストールします。

 https://www.rust-lang.org/tools/install

から、RUSTUP-INIT.EXEをダウンロードし、管理者権限で実行します。 インストールが終わったら,以下のコマンドでPATHが通っていることを確認します。

rustup -V
cargo -V

wasm-packのインストール

今回はwasm-packを使ってWebAssemblyの環境を構築します。次のコマンドでインストールします。

 cargo install wasm-pack

crateの作成

WebAssemblyのパッケージのことをcrateと呼んだりするようです。
次のコマンドでsrcフォルダ内にcrateを作成します。今回はcrateをモジュールとして使いたいので、--libオプションをつけました。crate名は適当につけます。今回はwasmとしました。(ちなみにcrate名を「crate」とすると怒られました。)

cargo new src/wasm --lib 

現時点で、プロジェクト構成は以下のようになります。


[Project Home]
|_  build
|    |_  js
|         |_  main.js
|
|_  src
|    |_  ts
|    |    |_  main.ts
|    |
|    |_  wasm
|         |_  src
|         |    |_  lib.rs  *
|         |
|         |_  Cargo.toml  *
|
|_  index.html
|_  package.json
| tsconfig.json
|  webpack.config.js

Cargo.tomlとlib.rsの編集

cargo newで生成された2ファイルを以下のように書き換えます。Cargo.tomlは設定ファイルで、lib.rsには実際の処理を記述します。

Cargo.toml
[package]
name = "wasm"
version = "0.1.0"
authors = ["ユーザー名<メールアドレス>"]
edition = "2018"

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

[dependencies]
wasm-bindgen = "0.2"
lib.rs
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

今回の設定例は、MDNのチュートリアルをそのまま引用しています。
javascript側から関数alertをインポートし、関数greetでHello,の後に引数で指定した文字列をalertするという処理です。

Rustのコンパイル

wasm-pack buildコマンドでコンパイルします。
Cargo.tomlの存在するパスを指定する必要があるため、今回はsrc/wasmを指定します。

wasm-pack build src/wasm

コンパイルに成功すると、以下のようにpkgフォルダとtargetフォルダとCargo.lockというファイルが生成されます。


[Project Home]
|_  build
|    |_  js
|         |_  main.js
|
|_  src
|    |_  ts
|    |    |_  main.ts
|    |
|    |_  wasm
|         |_  pkg *
|         |
|         |_  src
|         |    |_  lib.rs
|         |
|         |_  target *
|         |
|         |_  Cargo.lock  *
|         |_  Cargo.toml
|
|_  index.html
|_  package.json
| tsconfig.json
|  webpack.config.js

wasmの読み込み

pkgフォルダ内にwasm.jsというファイルが作成されているので、これをmain.tsからDynamic importして使用します。

main.ts
 const wasm = import('../wasm/pkg/wasm.js');
 wasm.then(mod => {
     mod.greet('WebAssembly');
 });

webpack.config.jsとtsconfig.jsonの設定

WebpackでTypescriptとWebAssemblyが正しくトランスパイルできるように設定します。

webpack.config.js
const path = require('path');

module.exports = {
    // mode: 'production',
    mode: 'development',
    entry: './src/ts/main.ts',
    output: {
      path: path.resolve(__dirname, "build/js"),
      publicPath: "./build/js/", 
      filename: 'main.js',
    },
    resolve: {
      // Add `.ts` and `.tsx` as a resolvable extension.
      extensions: ['.ts', '.tsx', '.js','.wasm']
    },
    module: {
      rules: [
        {
        // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
          test: /\.ts$/,
          use: 'ts-loader',
        },
        {
          test: /\.wasm$/,
          type: "webassembly/experimental"
        }
      ]
    }
  }
tsconfig.json
{
    "compilerOptions": {
        "sourceMap": false,
        "target": "es5",
        "lib": ["dom","es6","es2015.core"],
        "outDir": "build/js",
        "module": "esNext"
    },
    "include": [
        "src/ts/**.ts"
    ],
    "exclude": [
        "node_modules",
        "**/*.spec.ts"
    ]
}

上記の設定をして、後はwebpackコマンドを実行するだけなのですが、ここで何度かハマりました。

1. Module not found: Error: Can't resolve './wasm_bg' ...

wasmモジュールが見つからないというエラーです。webpack.config.jsの resolve-> extensionsに'.wasm' を追加することで解消しました。

webpack.config.js
    resolve: {
      // Add `.ts` and `.tsx` as a resolvable extension.
      extensions: ['.ts', '.tsx', '.js', '.wasm'] // <-- 重要!
    },

2. WebAssembly module is included in initial chunk. This is not allowed, because WebAssembly download and compilation must happen asynchronous. ...

WebAssemblyは非同期読み込みする必要があるらしく、同期読み込みした場合に出るエラーです。tsconfig.jsonのコンパイラオプションのモジュールにesNextが設定されていない場合も発生します。

tsconfig.json
{
    "compilerOptions": {
        "sourceMap": false,
        "target": "es5",
        "lib": ["dom","es6","es2015.core"],
        "outDir": "build/ts",
        "module": "esNext"  // <-- 重要!
    },
}

3. Uncaught (in promise) TypeError: Failed to execute 'compile' on 'WebAssembly': HTTP status code is not ok

ローカルサーバでindex.htmlを開くと、ブラウザコンソールに表示されるエラーです。
これは、今回のプロジェクト構成例のようにindex.htmlmain.jsのパスが異なる場合に表示されるエラーのようです。

webpack.config.js
    output: {
      path: path.resolve(__dirname, "build/js"),
      publicPath: "./build/js/",   //  <--重要!
      filename: 'main.js',
    },

これを解決するには、webpack.config.jsで、webpackの公開パスを、ビルドされたファイルの出力先パスとは別に、明示的に設定する必要があります。

結果

Hello,WebAssembly.JPG

ちゃんとalertが表示されました。めでたしめでたし。

後はTypescript側からlib.rsに重そうな処理をどんどん引っ越ししていきたいと思います。

参考にさせていただいたサイト

https://developer.mozilla.org/ja/docs/WebAssembly/Rust_to_wasm
https://qiita.com/mizchi/items/dc089c28e4d3afa78207
https://qiita.com/ito-hiroki/items/aaaf66e082f917baacc9
https://github.com/rustwasm/rust-webpack-template/issues/43#issuecomment-426597176

15
15
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
15
15