» Mozilla Hacks ブログ翻訳

WebAssembly は今どこにあり、次に何があるのか

[この記事は”Where is WebAssembly now and what’s next?“の翻訳です]この記事は WebAssembly と何が速くしたのかのシリーズの6部です。もしまだ前の記事を読んでいない場合、最初から読むことをお勧めします。

2月28日、4つのメジャーなブラウザが WebAssembly の必要最低限の機能のみを持つ製品 (MVP) が完了したという コンセンサスを発表しました

これにより、ブラウザが出荷できる安定したコアが提供されます。このコアには、コミュニティグループが計画している機能のすべてが含まれているわけではありませんが、WebAssembly を高速かつ使いやすくするために十分な機能を備えています。

これにより、開発者は WebAssembly コードの提供を開始できます。以前のバージョンのブラウザでは、開発者は asm.js バージョンのコードを提供できます。asm.js は JavaScript のサブセットであるため、どの JS エンジンでも実行できます。Emscripten を使用すると、同じアプリケーションを WebAssembly と asm.js の両方にコンパイルできます。

初回リリースであっても WebAssmbly は高速に動くでしょう。しかし修正と新規機能を導入により将来的にさらに高速になるでしょう。

ブラウザー上の WebAssembly のパフォーマンス向上

ブラウザーエンジンが WebAssembly のサポートを向上させるとスピードが向上します。ブラウザベンダーはこれらの問題を個々に取り組んでいます。

JS と WebAssembly 間の関数呼び出しの高速化

現在 JS からの WebAssembly 関数呼び出しは求められるスピードに達していません。その理由は「トランポリン」と呼ばれることが原因です。JIT は WebAssembly をどのように扱えばよいのか知らないため、WebAssemblyを何かにルーティングする必要があります。これは最適化された WebAssembly コードを実行するためのセットアップ時のエンジン自体のコードが遅い箇所です。

Person jumping from JS on to a trampoline setup function to get to WebAssembly

これは JIT が直接処理する方法を知っていた場合よりも、最大100倍遅くなることがあります。

単一の大きなタスクを WebAssembly モジュールに渡している場合は、このオーバーヘッドに気付かないでしょう。しかし WebAssembly と JS の間で多くのやりとりがある場合 (小規模な作業と同じように)、このオーバーヘッドが目立ちます。

より速いロード時間

JIT はより速いロード時間とより速い実行時間の間のトレードオフを管理する必要があります。コンパイルや最適化に時間を費やすと実行速度が向上しますが、起動が遅くなります。

先進的なコンパイル (コードが実行されるとジャンクが発生しないようにする) と、コードのほとんどの部分がそれに見合った最適化を行うために十分な時間実行されないという基本的な事実とのバランスを取るために、多くの進行中の作業があります。

WebAssembly はどの型が使用されるかを特定する必要が無いため、エンジンは実行時に型をモニターする必要がありません。これは実行時の平行コンパイルといった例のような様々なオプションをもたらします。

さらに JavaScript API への最近の追加では WebAssembly のストリーミング・コンパイルが可能です。これは WebAssembly のデータをダウンロード中にエンジンがコンパイルを開始できることを意味します。

Firefox では二つのコンパイラーを使用するシステムになっています。一つ目のコンパイラは事前に実行し、コードを最適化を行います。コードの実行中は別のコンパイラがバックグラウンドで完全な最適化を行います。完全に最適化されたバージョンのコードへ準備が整うと切り替わります。

MVP リリース以降の機能導入

WebAssembly の目標の 1 つは、すべての機能を設計するのではなく、小さなチャンクで指定し、途中でテストを行うことです。

期待されている機能はたくさんあるが、100% の考え方はまだ存在しないことを意味しています。これらはブラウザベンダが参加している使用策定プロセスを経る必要があります。

これらの機能は将来の機能と呼ばれています。少し紹介します。

DOM を直接扱う

現在 DOM を扱う手段はありません。これは WebAssembly から element.innerHTML のようにノードを更新できないこと意味します。

代わりに、値を設定するために JS を経由する必要があります。これは値を JavaScript 呼び出し側に渡すことを意味します。一方、WebAssembly 内から JavaScript 関数を呼び出すことは、JavaScript と WebAssembly の両方の関数を WebAssembly モジュールをインポートして使用できることを意味します。

Person reaching around from WebAssembly through JS to get to the DOM

いずれにしても、JavaScript を使用することは直接アクセスよりも遅くなる可能性があります。WebAssembly の一部のアプリケーションはこれが解決されるまでが続くかもしれません。

共有メモリーの同時実行性

コードを高速化する方法の 1 つは、コードの異なる部分を同時に並列で実行できるようにすることです。しかし、スレッド間の通信のオーバーヘッドは、タスクを分けずに実行するよりも多くの時間を要する可能性があるため、時には逆効果になる場合があります。

しかしスレッド間のシェアードメモリを使用すればこのオーバーヘッドを削減できます。これを実施するため WebAssembly は JavaScript の新しい SharedArrayBuffer を使用すべきです。これがブラウザに組み込まれると、ワーキンググループは WebAssembly の動作方法を指定することができます。

SIMD

WebAssembly に関する他の投稿を読んだり、話を聞いたりすると、SIMD のサポートについて聞かれることがあります。頭字語は、単一命令、複数のデータを表します。物事を並行して運用する別の方法です。

SIMD を使用すると、異なる数のベクトルのような大きなデータ構造を取り、同じ命令を異なるパーツに同時に適用することができます。このようにして、ゲームや VR に必要な複雑な計算を大幅に高速化できます。

これらは平均的な Web 開発者にとってさほど重要ではありません。しかしゲーム開発者のようなマルチメディアを扱う開発者にとって非常に重要です。

例外ハンドリング

C++ のような言語の多くのコードは例外を使用しています。しかし例外は WebAssembly の一部としてまだ定義されていません。

あなたのコードを Emscripten でコンパイルした場合、いくつかのコンパイラ最適化レベルの例外処理をエミュレートします。これは非常に遅いため、DISABLE_EXCEPTION_CATCHING フラグを OFF にできます。

WebAssembly で例外がネイティブに処理できるようになると、このエミュレーションは必要ありません。

開発者にとってより簡単にするその他の改善

いくつかの将来の機能はパフォーマンスは改善しませんが、WebAssembly を使用するにあたり開発者がより簡単に使用できるようになるでしょう。

  • ファーストクラスのソースレベル開発ツール. 現在ブラウザ上で WebAssembly をデバッグすることは、生のアセンブリーをデバッグするようなものです。しかし、ソースコードがアセンブリにどのように変換されるかに関して精通している開発者はほとんどいません。開発者がソースコードをデバッグできるように、ツールサポートを改善する方法を検討しています。
  • ガベージコレクション. 事前に型を定義できる場合は、コードを WebAssembly に変換する必要があります。したがって TypeScript のようなコードを使用するコードは、WebAssembly にコンパイル可能である必要があります。WebAssembly は JS エンジンに組み込まれているような既存のガベージコレクタとやりとりができないというのが現状です。この将来の機能のアイデアは、WebAssembly に低レベルの GC プリミティブタイプとオペレーションのセットを使用して、組み込み GC へのファーストクラスのアクセスを与えることです。
  • ES6 モジュールの統合. ブラウザは現在、script タグを使用して JavaScript モジュールを読み込むためのサポートを追加しています。 この機能を追加すると、url が WebAssembly モジュールを指している場合でも<script src=url type="module">のようなタグが機能します。

結論

WebAssembly は今日でも高速で、そしてブラウザの新しい機能や実装の改良により、さらに高速化するはずです。

Lin Clark に関して

Lin は Mozilla Developer Relations チームのエンジニアです。 彼女は JavaScript、WebAssembly、Rust、Servo を使っています。また、コードの漫画を描きます。

Lin Clark によるその他の記事はこちら…

WebAssembly を速くするには?

この記事は WebAssembly と何が速くしたのかのシリーズの5部です。もしまだ前の記事を読んでいない場合、最初から読むことをお勧めします。前の記事では WebAssembly や JavaScript を使ったプログラミングは、どちらか一方を選択するものではないことを説明しました。多くの開発者が完全な WebAssembly コードベースを記述しているとは考えていません。開発者はアプリケーション用に WebAssembly と JavaScript のどちらかを選択する必要はありません。 しかし開発者は JavaScript コードの一部を WebAssembly に取り替えようとしています。

例えば React に取り組んでいるチームは、調整コード (別名仮想 DOM) を WebAssembly バージョンに置き換えることができます。React を使う人は WebAssembly を使用するメリットを除いて、これらのアプリはまったく同じように動作し、何もする必要はありません。

React チームのメンバーのような開発者がこの転換を行う理由は、WebAssembly が高速であるためです。しかし何が高速にしているのでしょうか?

JavaScript のパフォーマンスは現在どうなっているのか?

JavaScript と WebAssembly の間のパフォーマンスの差を理解する前に、私たちは JS エンジンがどのように動くかを理解する必要があります。

この図は今日のアプリケーションの起動時のパフォーマンスがどうなっているかの概略図です。

JS エンジンがこれらのタスクを実行する時間は、ページが使用する JavaScript により異なります。 この図は正確なパフォーマンスの数値を表すものではありません。代わりに JS と WebAssembly で同じ機能のパフォーマンスがどのように異なるかについて、高度なモデルを提供することを目的としています。

Diagram showing 5 categories of work in current JS engines

各バーには、特定のタスクを行うのに費やされた時間が表示されます。

  • 構文解析 — ソースコードをインタプリタが実行できるものに処理するまでにかかる時間。
  • コンパイル + 最適化 — ベースラインコンパイラと最適化コンパイラで費やされる時間。最適化コンパイラの作業の幾つかはメインスレッドに含まれていないため、ここでは加えていません。
  • 再最適化 — JITが前提条件が満たされていないときに再調整を行う時間。コードの再最適化と最適化されたコードからのベースラインコードへの復帰。
  • 実行 — コードを実行するのにかかる時間。
  • ガベージコレクション — メモリのクリーンアップに費やされた時間。

注目すべき重要な点:これらのタスクが個別のチャンクまたは特定の順序で実行されないことです。代わりにインターリーブされます。ほんの少しの解析が行われ、次にいくつかの実行が行われ、次にコンパイルされ、さらに解析され、さらに実行されるなどします。

このような内訳によるパフォーマンスのは JavaScript の初期段階からの大きく改善され、以下のようになっています:

Diagram showing 3 categories of work in past JS engines (parse, execute, and garbage collection) with times being much longer than previous diagram

初期の JavaScript 実行時の変換処理だけだった頃は実行速度は非常に遅かったです。JIT が導入されると、実行時間が大幅に短縮されました。

トレードオフはコードの監視とコンパイルです。もし JavaScript の開発者が今までと同じように JavaScript を書き続けた場合、解析とコンパイル時間が非常に短くなります。改善されたパフォーマンスにより開発者はより大きな JavaScript アプリケーションを作成することができるようになりました。

これはまだ改善の余地があることを意味します。

WebAssembly をどのように比較しますか?

典型的な Web アプリケーションにおける WebAssembly の比較方法を概説します。

Diagram showing 3 categories of work in WebAssembly (decode, compile + optimize, and execute) with times being much shorter than either of the previous diagrams

ブラウザがこれらのフェーズをどのように処理するかは、ブラウザにより若干の違いがあります。ここでは SpiderMonkey を使用します。

フェッチ

これは図には表示していませんが、時間がかかるのはサーバーからファイルを取得することだけです。

WebAssembly は JavaScript よりもコンパクトなため、フェッチはより速くなります。コンパクションアルゴリズムは JavaScript バンドルのサイズを大幅に縮小することができますが、WebAssembly の圧縮バイナリ表現をまだまだ小さくすることは可能です。

これはサーバーとクライアント間の転送時間をより短くできることを意味します。これは特に低速ネットワークでは当てはまります。

解析

ブラウザへ届くと JavaScript ソースが抽象構文木に解析されます。

ブラウザはたいていこれを遅延させ、最初は本当に必要なものを解析し、まだ呼び出されていない関数のスタブを作成するだけです。

そこから、AST はその JS エンジンに固有の中間表現 (バイトコードと呼ばれる) に変換されます。

対照的に、WebAssembly はすでに中間表現であるため、変換を行う必要はありません。デコードしてエラーがないか検証するのみです。

Diagram comparing parsing in current JS engine with decoding in WebAssembly, which is shorter

コンパイル + 最適化

JIT についての記事で説明したように、JavaScript はコードの実行時にコンパイルされます。実行時に使用されるランタイムに応じて、同じコードの複数のバージョンをコンパイルする必要があります。

ブラウザが異なると WebAssembly のコンパイル方法も異なります。WebAssembly を実行する前にベースラインのコンパイルを行うブラウザもあれば、JIT を使用するものもあります。

どちらにしても、WebAssembly は機械語に非常に近い状態で始まります。例えば、型はプログラムの一部です。これはいくつかの理由により高速です:

  1. コンパイラは最適化されたコードのコンパイルを開始する前に、コードにどの型を使用されているかを判断するために時間を費やす必要はありません。
  2. コンパイラは異なる種類のコードに基づいて同じコードの異なるバージョンをコンパイルする必要はありません。
  3. LLVM では、より多くの最適化が事前に行われています。それによりコンパイルと最適化に必要な作業が少なくて済みます。

Diagram comparing compiling + optimizing, with WebAssembly being shorter

再最適化

場合によっては、JIT は最適化されたバージョンのコードをスローして再試行する必要があります。

これは、JIT が実行中のコードに基づいて行う前提が正しくないことが判明した時に発生します。例えばループに入ってくる変数が以前の反復時の変数と異なる場合や、新しい関数がプロトタイプチェーンに挿入された場合に、最適化解除が実施されます。

最適化解除には二つのコストがあります。一つ目は最適化されたコードを取り除いてベースラインのバージョンに戻すのに時間がかかります。二つ目はその関数が依然として多く呼び出されている場合、JIT は最適化コンパイラを介して再度送信する場合があります。そのため2回コンパイルするコストがあります。

WebAssembly では、型のようなものは明白であるため、JIT は実行時に収集するデータに基づいた型の仮定を行う必要はありません。つまり再最適化サイクルを行う必要はありません。

Diagram showing that reoptimization happens in JS, but is not required for WebAssembly

実行

効率よく実行可能な JavaScript を書くことができます。これを行うには JIT が行う最適化について知る必要があります。例えば JIT の記事で説明したように、コンパイラが行う型の特殊化が行う事ができるようなコードを書く方法を知る必要があります。

しかしほとんどの開発者が JIT の内部構造は知りません。このような JIT の内部構造を知っている開発者であってもスイートスポットをヒットすることは難しいでしょう。コードをより読みやすくするために使用する多くのコーディングパターン (一般的なタスクをタイプ間で機能する関数に抽象化するなど) は、コンパイラーがコードを最適化するときに邪魔になります。

さらに、JIT が使用する最適化はブラウザによって異なっているため、あるブラウザの内部構造に合わせてコーディングすると、別のブラウザのでのードのパフォーマンスが低下する可能性があります。

このため、WebAssembly でコードを実行する方が一般的に高速です。WebAssembly では JIT が JavaScript に行う最適化の多く (型の特殊化など) は必要ありません。

また、WebAssembly はコンパイラのターゲットとして設計されています。これはコンパイラーが生成するように設計されており、人間のプログラマーが書くようには設計されていません。

人間のプログラマーは直接 WebAssembly を直接プログラミングする必要が無いため、WebAssembly は機械にとって理想的な一連の命令を提供できます。コードが動作する内容次第で、これらの命令は 10% から 800% 速く実行することができます。
Diagram comparing execution, with WebAssembly being shorter

ガベージコレクション

JavaScript では開発者は必要なくなった古い変数をメモリからクリアすることについて心配する必要はありません。代わりに JS エンジンは自動的にガベージコレクタと呼ばれるものを使用します。

ただし予測可能なパフォーマンスが必要な場合は問題になる可能性があります。ガベージコレクタがいつ動作するのかは制御できないため、都合の悪い時間に実施することがあります。ほとんどのブラウザではスケジューリングをかなりうまく行っていますが、コードの実行の途中でオーバーヘッドになることがあります。

少なくとも今のところ、WebAssembly はガベージコレクションをサポートしていません。メモリは C や C++ のように手動で管理されます。 これは開発者にとってプログラミングを難しくする一方で、パフォーマンスの一貫性も向上させます。

Diagram showing that garbage collection happens in JS, but is not required for WebAssembly

結論

WebAssembly は JavaScript よりも多くのケースで高速です:

  • WebAssembly のフェッチは圧縮された状態であっても JavaScript より時間がかかりません。
  • WebAssembly のデコードは JavaScript を解析するよりも時間がかかりません。
  • WebAssembly は JavaScript よりも機械語に近いため、コンパイルと最適化に要する時間が短縮され、すでにサーバー側で最適化が行われています。
  • WebAssembly には型やその他の情報が組み込まれているため、再最適化を行う必要はありません。そのため JS エンジンは JavaScript を使用方法を最適化するときに推測を行う必要はありません。
  • 開発者が効率良いコードを書くために知っておく必要があるコンパイラのトリックや落とし穴が少ないために、実行時間は短く済み、また WebAssembly の命令セットが機械にとってより理想的です。
  • ガベージコレクションはメモリーを手動で管理するため必要としません。

このため、多くの場合、WebAssembly は同じタスクを実行するときに JavaScript より優れたパフォーマンスを発揮します。

WebAssembly が期待通りの性能を発揮しない場合もありますし、より速くする範囲にもいくつかの変更があります。水平方向にもいくつかの変更があり、高速化する場合もあります。次の記事でこれらをカバーします。

Lin Clark に関して

Lin は Mozilla Developer Relations チームのエンジニアです。 彼女は JavaScript、WebAssembly、Rust、Servo を使っています。また、コードの漫画を描きます。

Lin Clark によるその他の記事はこちら…

WebAssembly モジュールの作成と操作

この記事は WebAssembly と何が速くしたのかのシリーズの3部です。もしまだ前の記事を読んでいない場合、最初から読むことをお勧めします。WebAssembly は、Web ページ上で JavaScript 以外のプログラミング言語を実行する方法です。これまで Web ページの様々な部分と対話するためにブラウザでコードを実行したいとき、唯一のオプションは JavaScript でした。WebAssembly が高速かどうかの話題の時は比較の対象は JavaScript になります。実際は、WebAssembly を使用しているか JavaScript を使用しているかのどちらか一方になるわけではありません。

実際、開発者は同じアプリケーションで WebAssembly と JavaScript の両方を使用することになります。WebAssembly を自分で作成しないなくても、それを利用可能です。

WebAssembly のモジュールは JavaScript から使用できるように関数を定義することができます。したがって、npm today から lodashの ようなモジュールをダウンロードし、その API の一部である関数を呼び出すように、将来、WebAssembly モジュールをダウンロードすることができるようになるでしょう。

では WebAssembly のモジュールをどのように作成し、JavaScript からどのように呼び出せるのかを見てみましょう。

WebAssembly はどこに適合するのか?

Assembly についての記事の中で、コンパイラが高水準のプログラミング言語をどのようにして機械語に翻訳するのかを話しました。

Diagram showing an intermediate representation between high level languages and assembly languages, with arrows going from high level programming languages to intermediate representation, and then from intermediate representation to assembly language

WebAssembly はこの図のどこに収まるのですか?

ターゲットアセンブリ言語の単なる別のものだと考えるかもしれません。これらの言語 (x86、ARM) のそれぞれが特定のマシンアーキテクチャに対応している点を除いて、それは事実です。

Web 上でユーザーのマシン上で実行されるコードを提供しているときは、コードが実行されるターゲットアーキテクチャはわかりません。

したがって、WebAssembly は他のアセンブリとは少し異なります。実際の物理マシンではなく、仮想マシンの機械語になっています。

このため、WebAssembly の命令は仮想命令と呼ばれることもあります。それらは、JavaScript のソースコードよりもはるかに機械語に直接マッピングされています。ごく一般的なハードウェアで効率的に行うことができることの一種です しかし、特定のハードウェアに対する特定の機械語への直接のマッピングではありません。

Same diagram as above with WebAssembly inserted between the intermediate representation and assembly

ブラウザが WebAssembly をダウンロードします。次に、WebAssembly からターゲットマシンのアセンブリコードまでの短い期間に使用します。

.wasm 形式へコンパイルする

WebAssembly を現在最もサポートしているコンパイラツールチェーンは LLVM です。LLVM に接続できる様々なフロントエンドとバックエンドがあります。

注記: ほとんどの WebAssembly のモジュール開発者は、C や Rust といった言語でコードを作成し、WebAssembly にコンパイルしますが、WebAssembly のモジュールを作成するには他の方法もあります。例えばTypeScript を使用して WebAssembly のモジュールを構築するのに役立つ実験的なツールや、WebAssemblyのテキスト表現を直接コーディングする方法もあります。

C から WebAssembly に変換したいとしましょう。clangフロントエンドを使用して、C から LLVM IR (中間コード) に変換することができます。LLVM は LLVM IR を理解します。そのため、LLVM はいくつかの最適化を実行できます。

LLVM IR (中間コード) から WebAssembly に変換するには、バックエンドが必要です。LLVM プロジェクトには現在進行中のものがあります。バックエンドはその大部分であり、すぐに終了するものです。しかし現在はこれを動作させるのはトリッキーかもしれません。

Emscripten と呼ばれる別のツールがありますが、これは現時点では少し使いやすくなっています。それは別のターゲット (asm.js と呼ばれる) にコンパイルし、それを WebAssembly に変換することで WebAssembly を生成できる独自のバックエンドを持っています。これは内部で LLVM を使用するので、Emscripten から 2 つのバックエンドを切り替えることができます。

Diagram of the compiler toolchain

Emscriptenには、C/C++ コードベース全体を移植可能にする多くのツールとライブラリが含まれているため、コンパイラよりもソフトウェア開発キット (SDK) のほうが多くなっています。例えば、システム開発者は、読み書きが可能なファイルシステムに慣れているため、Emscripten は IndexedDB を使用してファイルシステムをシミュレートできます。

使用したツールチェーンにかかわらず、最終的にはファイルは .wasm 形式になります。以下で .wasm ファイルの構造について詳しく説明します。まず、JS でどのように使用できるかを見てみましょう。

JavaScript で .wasm モジュールを読み込む

.wasm ファイルは WebAssembly のファイルであり、JavaScript から読み込むことができます。現在のところ、読み込む処理は少し複雑です。

function fetchAndInstantiate(url, importObject) {
  return fetch(url).then(response =&amp;gt;
    response.arrayBuffer()
  ).then(bytes =&amp;gt;
    WebAssembly.instantiate(bytes, importObject)
  ).then(results =&amp;gt;
    results.instance
  );
}

詳細はこのドキュメントを確認してください。

私たちはこのプロセスをより簡単にしようと取り組んでいます。私たちはツールチェーンを改善し、Webpack のようなモジュールバンドルや SystemJS のようなローダーと統合することを期待しています。WebAssembly モジュールの読み込みは、JavaScript の読み込みと同じくらい簡単です。

WebAssembly モジュールと JS モジュールの間には大きな違いがあります。現在 WebAssembly の関数では、数値 (整数または浮動小数点数) のみをパラメータまたは戻り値として使用できます。

Diagram showing a JS function calling a C function and passing in an integer, which returns an integer in response

文字列のように複雑なデータ型の場合、WebAssembly モジュールのメモリを使用する必要があります。

もし JavaScript を使って作業していたのであれば、メモリに直接アクセスすることはあまり馴染みがありません。 C、C ++、Rust のようなパフォーマンスの高い言語は、手作業によるメモリ管理を行う必要があります。WebAssembly モジュールのメモリは、これらの言語で見られるヒープをシミュレートします。

これを使用するには、ArrayBuffer という JavaScript の型を使用します。配列バッファはバイトの配列です。したがって、配列のインデックスはメモリアドレスとして機能します。

JavaScript と WebAssembly の間に文字列を渡す場合は、文字を文字コードに変換します。次にメモリ配列に書き込みます。インデックスは整数なので、WebAssembly 関数にインデックスを渡すことができます。したがって、文字列の最初の文字のインデックスをポインタとして使用できます。

メモリへのポインタを表す整数を持つ C 関数を呼び出す JS 関数と、次にメモリに書き込むC関数を示す図

WebAssembly モジュールを開発している Web 開発者の場合、そのモジュール周辺にラッパーを作成すべきでしょう。そうすることによってモジュールの使用者はメモリ管理について知る必要はありません。

詳細を知りたい場合は、WebAssembly のメモリの動作を確認してください。

.wasm ファイルの構造

高級言語でコードを記述し、WebAssembly にコンパイルする場合、WebAssembly モジュールの構造を知る必要はありませんが、基本を理解するのに役立ちます。

まだ知らない場合はWebAssembly の記事 (シリーズの第3部) を読むことをお勧めします。

WebAssembly に変換する C 関数を次に示します:

int add42(int num) {
  return num + 42;
}

WASM Explorer を使用してこの機能をコンパイルすることができます。

.wasmファイルを開くと (エディタで表示がサポートされている場合) 次のように表示されます。

00 61 73 6D 0D 00 00 00 01 86 80 80 80 00 01 60
01 7F 01 7F 03 82 80 80 80 00 01 00 04 84 80 80
80 00 01 70 00 00 05 83 80 80 80 00 01 00 01 06
81 80 80 80 00 00 07 96 80 80 80 00 02 06 6D 65
6D 6F 72 79 02 00 09 5F 5A 35 61 64 64 34 32 69
00 00 0A 8D 80 80 80 00 01 87 80 80 80 00 00 20
00 41 2A 6A 0B

これは “バイナリ” 表現のモジュールです。バイナリは通常 16 進表記で表示されますが、簡単にバイナリ表記や人間が読める形式に変換できるため、バイナリの周りに引用符を入れました

例えば、num + 42 のようになります。

Table showing hexadecimal representation of 3 instructions (20 00 41 2A 6A), their binary representation, and then the text representation (get_local 0, i32.const 42, i32.add)

コードの仕組み:スタックマシン

あなたが疑問に思っている場合、ここではそれらの指示で何をするのでしょうか。

Diagram showing that get_local 0 gets value of first param and pushes it on the stack, i32.const 42 pushes a constant value on the stack, and i32.add adds the top two values from the stack and pushes the result

add 操作でその値がどこから来るのかはわかりませんでした。これは WebAssembly がスタックマシンと呼ばれるものの例であるためです。これは操作が必要とするすべての値が、操作が実行される前にスタックにキューイングされていることを意味します。

add のような操作は、必要な値の数を知っています。add は 2 つ必要なため、スタックの先頭から 2 つの値をとります。これは命令がソースレジスタまたはデスティネーションレジスタを指定する必要がないため、add 命令が短く (1Byte) できることを意味します。これにより、.wasm ファイルのサイズが縮小され、ダウンロードに要する時間が短縮されます。

WebAssembly はスタックマシンの観点から指定されていますが、実際のマシンでは動作しません。ブラウザが実行中のマシンのマシンコードにブラウザが WebAssembly を変換すると、レジスタが使用されます。 WebAssembly コードではレジスタが指定されていないため、ブラウザにはそのマシンに最適なレジスタ割り当てを柔軟に使用できます。

モジュールのセクション

add42 関数自体に加えて、.wasm ファイルには他の部分もあります。これらはセクションと呼ばれます。セクションのいくつかは任意のモジュールに必要であり、いくつかはオプションです。

必須:

  1. Type. モジュールおよびインポートされた関数で定義された関数の関数シグネチャが含まれます。
  2. Function. モジュールで定義された各関数へのインデックスを与えます。
  3. Code. モジュール内の各関数の本体。

オプション:

  1. Export. 関数、メモリ、テーブル、グローバルを他の WebAssembly モジュールや JavaScript で使用できるようにします。これにより、別々にコンパイルされたモジュールを動的にリンクすることができます。これは WebAssembly の .dll バージョンです。
  2. Import. 他の WebAssembly モジュールまたは JavaScript からインポートする関数、メモリ、テーブル、およびグローバルを指定します。
  3. Start. WebAssembly モジュールがロードされたときに自動的に実行される関数 (基本的にメイン関数のような)。
  4. Global. モジュールのグローバル変数を宣言します。
  5. Memory. メモリをモジュールでどのように使うか定義します。
  6. Table. JavaScript オブジェクトなど WebAssembly モジュールの外部にある値にマップすることができます。これは間接的な関数呼び出しを許可する場合に特に便利です。
  7. Data. インポートした、またはローカルのメモリを初期化します。
  8. Element. インポートした、またはローカルのテーブルを初期化します。

セクションの詳細については、これらのセクションが詳しく説明しています。

次の記事

WebAssembly モジュールを使用する方法を勉強したので、WebAssembly を速くするには?を見てみましょう。

Lin Clark に関して

Lin は Mozilla Developer Relations チームのエンジニアです。 彼女は JavaScript、WebAssembly、Rust、Servo を使っています。また、コードの漫画を描きます。

Lin Clark によるその他の記事はこちら…

WebAssembly の短期集中コース

この記事は WebAssembly と何が速くしたのかのシリーズの 3 部です。もしまだ前の記事を読んでいない場合、最初から読むことをお勧めします。どのように WebAssembly が動いているかを理解するために、アセンブリが何でありどのようにアセンブリを生成するのかを理解することが役に立ちます。

JIT の記事では、機械とのやりとりがエイリアンとのコミュニケーションのようなものだと話しました。

A person holding a sign with source code on it, and an alien responding in binary

エイリアンの脳がどのように働いているのか、機械の脳はどのように解析してそこに入ってくるコミュニケーションを理解しているのかを見てみたいと思います。

この脳の一部は、思考に専念しています。つまり、加減算や論理演算などです。短期記憶を提供する脳の部分と、長期記憶を提供するもう一つの部分もあります。

これらの異なる部分には名前があります。

  • 思考を行う部分は算術論理ユニット (ALU) です。
  • 短期記憶はレジスタによって提供されます。
  • 長期記憶はランダムアクセスメモリ (もしくはRAM) です。

A diagram showing the CPU, including ALU and Registers, and RAM

マシン語の文章は命令と呼びます。

これらの命令の 1 つが脳に入ったらどうなりますか?それは異なることを意味する異なる部分に分割されます。

この命令が分割される方法はこの脳の配線に固有のものです。

例えばこのように配線された脳は、常に最初の 6 ビットを取り、それを ALU にパイプすることができます。 ALU は 1 と 0 の位置に基づいて、2 つのものを一緒に追加する必要があることを理解します。

この 塊は ALU にどのような操作を実行するかを指示するため、「オペコード」またはオペレーションコードと呼ばれます。

6-bits being taken from a 16-bit instruction and being piped into the ALU

次に、この脳は次の 2 つのチャンクをそれぞれ 3 ビットずつ取って、どの 2 つの数字を追加するかを決定します。 これらはレジスタのアドレスになります。

Two 3-bit chunks being decoded to determine source registers

ここで機械語の上に注釈があることに注意してください。これは人間が何が起こっているのかを理解しやすくします。これがアセンブリです。シンボリックマシンコードとも呼ばれており、人間が機械語を理解する方法です。

ここには、この機械のためのアセンブリと機械語の間にかなり直接的な関係があることがわかります。このため、さまざまな種類の機械のアーキテクチャー向けにに異なる種類のアセンブリがあります。機械の内部で別のアーキテクチャを使用している場合は、独自の方言のアセンブリが必要になる可能性があります。

だから私たちは翻訳の目標を一つだけ持っているわけではありません。これは、機械語と呼ばれる単なる言語ではありません。これは、さまざまな種類の機械語です。私たちが人としてさまざまな言語を話すように、機械は異なる言語を話します。

人からエイリアンへの翻訳では英語、ロシア語、または北京語からエイリアン語Aまたはエイリアン語Bに移行している可能性があります。プログラミング上、これはC、C ++、Rustからx86またはARMへの変換に似ています。

これらの高レベルのプログラミング言語のいずれかをこれらのアセンブリ言語のいずれかに翻訳したいと考えています (異なるアーキテクチャに対応しています)。これを行うための 1 つの方法は、各言語から各アセンブリに変換することができるさまざまな翻訳者をたくさん作成することです。

Diagram showing programming languages C, C++, and Rust on the left and assembly languages x86 and ARM on the right, with arrows between every combination

それはかなり非効率的になるでしょう。これを解決するため、ほとんどのコンパイラは少なくとも 1 つのレイヤを中間に配置します。コンパイラはこの高水準のプログラミング言語をとり、これを高レベルではないものに変換しますが、機械語のレベルでは動作しません。 それは中間表現 (IR) と呼ばれています。

Diagram showing an intermediate representation between high level languages and assembly languages, with arrows going from high level programming languages to intermediate representation, and then from intermediate representation to assembly language

これはコンパイラがこれらの上位レベルの言語のいずれかを取り、それを 1 つの IR 言語に翻訳できることを意味します。そこから、コンパイラの別の部分がその IR を取得し、ターゲットアーキテクチャに固有のものにコンパイルすることができます。

コンパイラのフロントエンドは、上位レベルのプログラミング言語を IR に変換します。コンパイラのバックエンドは、IR からターゲットアーキテクチャのアセンブリコードに移行します。

Same diagram as above, with labels for front-end and back-end

Conclusion

これはアセンブリの仕組みと、コンパイラがどのように高水準のプログラミング言語をアセンブリに変換するかです。次の記事では、WebAssembly がこれにどのように適合するかを見ていきます。

Lin Clark に関して

Lin は Mozilla Developer Relations チームのエンジニアです。 彼女は JavaScript、WebAssembly、Rust、Servo を使っています。また、コードの漫画を描きます。

Lin Clark によるその他の記事はこちら…

ジャスト・イン・タイム (JIT) コンパイラの短期集中コース

この記事は WebAssembly と何が速くしたのかのシリーズの2部です。もしまだ前の記事を読んでいない場合、最初から読むことをお勧めします。JavaScript は最初は遅かったですが、JIT と呼ばれるもののお陰で速くなりました。JIT はどのように機能しているのでしょうか?

JavaScript はブラウザ上でどのように動作しているか

開発者として JavaScript を追加すると、ゴールと問題が出てきます。

ゴール : コンピュータへ何かを伝えたいと思います

問題 : あなたとコンピュータは別の言語を話します

あなたは人間の言語を話し、コンピュータは機械語を話します。JavaScript や他の高レベルのプログラミング言語を人間の言語として考えていなくても、実際には人間の言葉です。これらは人間の認知のために開発されたものであり、コンピュータの認知のために考えられたものではありません。

JavaScript のエンジンの仕事は人間の言葉を機械が理解できるように変換することです。

人間とエイリアンがお互いに話そうとしている映画、「メッセージ」のように考えています。

A person holding a sign with source code on it, and an alien responding in binary

この映画では、人間とエイリアンは一語一語の翻訳をするわけではありません。2つのグループは世界に関して別の考え方を持っています。そして人間と機械に関してもそうです (次の記事でこの映画に関してはもっと説明します) 。

では翻訳はどのように行いますか?

プログラミングでは、機械語に翻訳するには一般的に 2 つの方法があります。

インタープリタでは、この翻訳は直ちに一行一行実施します。

A person standing in front of a whiteboard, translating source code to binary as they go

一方コンパイラは直ちに翻訳を行いません。翻訳を作成して、それを書きとめるために、事前に動作します。

A person holding up a page of translated binary

これらの翻訳処理方法にはそれぞれ長所と短所があります。

インタープリタの長所と短所

インタープリタはすぐに起動して実行できます。コードの実行を開始する前に、コンパイルの手順全体を踏む必要はありません。 最初の行を翻訳して実行するだけです。

このため、インタプリタは JavaScript のようなものに自然にフィットするように見えます。Web 開発者がコードをすぐに実行できるようにすることは重要です。

そしてそれが様々なブラウザが最初から JavaScript インタープリタを使用していた理由です。

しかし同じコードを何度も実行するときに毎回翻訳が必要というインタープリタの問題があります。例えば、ループを使用するときがそうです。この時同じ翻訳を何度も何度も実行する必要があります。

コンパイラの長所と短所

コンパイラは対照的なトレードオフを持っています。

開始時にコンパイルを行うステップを経ないといけないため、起動に少し時間がかかります。 しかし、ループ内のコードパスはそのループを通過するたびに翻訳を繰り返す必要がないため、実行速度が速くなります。

もう一つの違いはコンパイラはコードの中身を確認し、より速く動作するように編集を行う時間があることです。これらの編集は最適化と呼ばれています。

インタープリタは実行時に作業を行っているため、これらの最適化を理解するために翻訳フェーズで時間をかけられません。

ジャスト・イン・タイム コンパイラ : 2つの世界の最も優れたもの

インタプリタの非効率性を取り除く方法として、インタプリタがループを通過するたびにコードの再翻訳を続ける必要がある場合に、ブラウザがコンパイラを混在させることを開始しました。

別のブラウザではこの方法はやや異なっていますが、基本的なアイデアは同じです。モニター (別名プロファイラ) と呼ばれる 新しい機能を JavaScript エンジンへ追加しました。そのモニターは実行中のコードを監視し、何回動作したかとどのようなタイプで使われるのかを記録します。

まず最初にモニターはインタープリタを通してすべてを実行します。

Monitor watching code execution and signaling that code should be interpreted

もし同じ行を何度か通った場合、コードのその部分は “warm” と呼ばれます。もし何度も動作する場合、ここは “hot” と呼ばれます。

ベースラインコンパイラ

関数が “warm” な状態になると、JIT はそれを送信しコンパイルします。そしてコンパイル結果を保存します。

Monitor sees function is called multiple times, signals that it should go to the baseline compiler to have a stub created

関数の各行は “スタブ” にコンパイルが行われます。スタブは行番号と変数タイプによって索引付けします (なぜ重要なのかは後で説明します) 。 モニタにより同じ変数の型で同じコードを実行していることが確認した場合、コンパイルしバージョンを取り出します。

それはスピードアップするのに役立ちます。しかし、先程述べたように、コンパイラができることはもっとあります。最適化を行うための最も効率的なやり方を理解するまでには時間がかかることがあります。

ベースラインコンパイラはこれらの最適化の幾つかを行います (以下に例を示します )。長い時間作業を保留にしたくないため、これらに多くの時間をかけたくありません。

しかしコードが本当に “hot” な場合、—もしそれがいっぱい実行されていたら— このような時にさらなる最適化を実施する時間を余分にとる価値があります。

コンパイラの最適化

コードの一部がとても “hot” な時、モニターは最適化コンパイラに対してそれを送信します。これにより、保存される関数の別のより高速なものが作成されます。

Monitor sees function is called even more times, signals that it should be fully optimized

コードのより高速なバージョンを作るために、最適化コンパイラはいくつかの過程を行う必要があります。

例えば、特定のコンストラクタによって作成されたすべてのオブジェクトが同じ形状を持っていると仮定することができます。つまり、それらは常に同じプロパティ名を持ち、それらのプロパティは同じ順序で追加され、それに基づいていくつかのコーナーをカットすることができます。

最適化コンパイラはモニターが監視したコードの実行状態の情報を使用します。ループが通る前のすべてのパスが真だった場合、それらは引き続き真だとみなされます。

もちろん JavaScript を使用した場合、それらの保証はありません。あなたが 99 個の同じ形状のオブジェクトを持ってしたとしても、100 個目のオブジェクトはプロパティが無いかもしれません。

従ってコンパイルされたコードは仮定が正しいかどうかを動作させる前に確認する必要があります。もし正しければコンパイルされたコードを動作させます。もし正しくなければ、JIT は誤った仮定とみなし、最適化したコードを破棄します。

Monitor sees that types don't match expectations, and signals to go back to interpreter. Optimizer throws out optimized code

その後、実行するコードはインタプリタまたはベースラインのコンパイルされたバージョンに戻ります。このプロセスは最適化解除 (または救済) と呼ばれます。

たいていの最適化コンパイラはコードを速く動くようにしますが、時々期待されないパフォーマンスの問題が発生します。最適化した上で最適化解除することを続けるコードがある場合、ベースラインのコンパイル済みバージョンを実行するよりも遅くなります。

ほとんどのブラウザでは最適化/最適化解除のサイクルに問題が発生した時、終了させる制限を加えています。もし JIT が最適化を 10 回以上試行しそれを捨て続けた場合、試行をやめます。

最適化の例 : 型の特殊化

さまざまな種類の最適化がありますが、1 つの型を見て最適化がどのように起こるかを感じることができます。コンパイラの最適化で最も大きな成果を得たのは、型の特殊と呼ばれるものです。

JavaScriptが利用する動的型システムでは、実行時に余分な作業が必要です。例えば、次のコードで考えてみましょう。

function arraySum(arr) {
  var sum = 0;
  for (var i = 0; i &lt; arr.length; i++) {
    sum += arr;
  }
}
ループ内の += ステップは単純に思えるかもしれません。これを 1 ステップで計算できるように思えるかもしれませんが、動的な型付けのために予想以上に多くのステップが必要となります。

arr が 100 個の整数の配列であるとしましょう。コードが “warm up” すると、ベースラインコンパイラは関数内の各演算のスタブを作成します。そのため sum += arr のスタブがあり、これは += 演算を整数加算として扱います。

しかし、sum arr は整数であるとは限りません。 JavaScript では型が動的なので、ループの後の反復で、arr が文字列になる可能性があります。 整数の加算と文字列の連結は非常に異なる2つの操作なので、非常に異なる機械語にコンパイルされます。

JIT がこれを処理する方法は、複数のベースライン・スタブをコンパイルすることです。 コードの一部がモノモーフィックである (つまり、常に同じ型で呼び出される) 場合、それは 1 つのスタブを取得します。 それがポリモーフィックである場合 (あるコードから別のコードへと異なるタイプで呼び出されます) 、その操作を経たタイプの各組み合わせに対してスタブを取得します。

これは JIT はスタブを選ぶ前に多くの質問をする必要が有ることを意味します。

Decision tree showing 4 type checks

コードの各行はベースラインコンパイラに独自のスタブセットを持つため、JIT はコードの各行が実行されるたびに型のチェックを実施し続ける必要があります。そしてループを通るそれぞれのイテレーションのために、同じ質問をする必要があります。

Code looping with JIT asking what types are being used in each loop

JIT がこれらのチェックを繰り返す必要がない場合、コードはより高速に実行されます。 そして、これは最適化コンパイラが行うことの 1 つです。

最適化コンパイラでは、関数全体が一緒にコンパイルされます。 型チェックは、ループの前に実施するように移動されます。

Code looping with questions being asked ahead of time

一部の JIT ではこれをさらに最適化します。 例えば、Firefox には、整数だけを含む配列の特別な分類があります。 arr がこれらの配列のいずれかである場合、JIT は arr が整数かどうかを確認する必要はありません。これは、JIT がループに入る前にすべての型チェックを実行できることを意味します。

結論

以上が JIT の簡潔な説明です。コードを実行しながらコードを監視し、”hot” なコードパスを送信して最適化することで、JavaScript をより速く実行できます。 これにより、ほとんどの JavaScript アプリケーションのパフォーマンスが数倍に向上しました。

しかし、これらの改良によっても、JavaScript のパフォーマンスは予測できません。さらに高速化するために、JIT は実行時にいくつかのオーバーヘッドが増えています :

  • 最適化と最適化解除
  • 救済措置が起こったときのモニタの記録と復旧情報に使用されるメモリ
  • 関数のベースラインバージョンと最適化バージョンを格納するために使用されるメモリ
ここに改善の余地があります : オーバーヘッドが取り除かれ、パフォーマンスが予測可能になります。それが WebAssembly が行うことの 1 つです。

次の記事では、アセンブリとコンパイラがどのように動作するかについて、さらに詳しく説明します。

Lin Clark に関して

Lin は Mozilla Developer Relations チームのエンジニアです。 彼女は JavaScript、WebAssembly、Rust、Servo を使っています。また、コードの漫画を描きます。

Lin Clark によるその他の記事はこちら…

WebAssembly の漫画での紹介

[この記事は”A cartoon into to WebAssembly“の翻訳です]

WebAssembly は高速です。あなたはおそらくこのことは聞いたことがあるでしょう。しかし何が WebAssembly を高速に動作することをできるようにしているのでしょうか?

このシリーズでは、なぜ WebAssembly が高速に動作するのかの説明を行いたいと思います。

WebAssembly とは何ですか?

WebAssembly は JavaScript 以外のプログラミング言語で書かれたコードをブラウザ上で実行する方法です。人々が WebAssembly が高速と言っているときは、JavaScript と比較しています。

これが二者択一の状況-つまり WebAssembly を使うか JavaScript を使うかのどちらか一方-だとは暗示したくありません。実際、開発者が同じアプリケーションで WebAssembly と JavaScript の両方を使用することを期待しています。

しかし、これら2つを比較することは有益であり、WebAssembly が持つであろうインパクトを理解することができるでしょう。

パフォーマンスの歴史を少し

JavaScript は1995年に作成されました。しかし高速に動くように設計されてはおらず、最初の10年は速くありませんでした。

その後ブラウザはより競争的になりました。

2008年、人々がパフォーマンス戦争と呼ぶ期間が始まりました。複数のブラウザに JIT とも呼ばれるジャストインタイムコンパイラが追加されました。JavaScript が実行されている最中、JIT はパターンを見て、そのパターンに基づいてコードをより高速に実行することができます。

これらの JIT の導入により、JavaScript のパフォーマンスに変革がもたらされました。 JavaScript の実行速度は 10 倍になりました。

A graph showing JS execution performance increasing sharply in 2008

パフォーマンスが改良されたことにより、JavaScript は Node.js を使ったサーバーサイドプログラミングのように、これまでに使用されることが予想されなかったものに対して使用され始めました。 パフォーマンスの向上により、全く新しい種類の問題に JavaScript を使用することが可能になりました。

もしかすると我々は今、WebAssembly によって新たな変曲点にいるのかもしれません。

A graph showing another performance spike in 2017 with a question mark next to it

では、WebAssembly を高速化する要素を理解するために詳細を掘り下げてみましょう。

背景:

過去の WebAssembly:

未来の WebAssembly:

Lin Clark に関して

Lin は Mozilla Developer Relations チームのエンジニアです。 彼女は JavaScript、WebAssembly、Rust、Servo を使っています。また、コードの漫画を描きます。

Lin Clark によるその他の記事はこちら…

JavaScript ゲームにおける操作方法

[この記事は、Control mechanisms in JavaScript games の翻訳です。]

ラップトップやデスクトップをはじめ、スマートフォンやタブレット、TV や冷蔵庫でさえも、共通してブラウザを持つようになりました。ということは、それらの端末上で HTML5 ゲームをプレイできるようになったという風にも思えますが、実際にプレイするためには、ゲーム画面を描画するのと同時に、何らかの方法でゲームを操作できなければなりません。キーボードやマウスはもちろん、タッチ操作、ゲームパッド、TV リモコン、さらにはバナナに至るまで、それぞれのプラットフォームごとに適した選択肢はたくさん存在します(MDN では、control mechanisms に関する一連の記事でそれらのインターフェースを取り上げています)。

captainrogers2-cover

今回は、実際のプロジェクトにおける例として、Phaser を用いて作成された Captain Rogers: Battle at Andromeda のデモを使ってぞれぞれの操作方法の実装方法について紹介します。プラットフォームの違いによってゲームの操作性がどう変わっていくかをご確認ください。また、異なるプラットフォームに対応する際に別々にビルドする必要がないということもお見せします。ウェブのマルチプラットフォームという特性によって、コーディングに多くの労力を費やすことなく複数のデバイスに対応できることがお分かりいただけると思います。

controls-purejsgame

また、GitHub には、小さいですが純粋な JavaScript によるデモがオープンソースとして公開されています。操作方法がどのように実装されているかを実際に確認できるので、ご自身のゲーム開発プロジェクトでもお使いいただけます。以下に、主な項目をハイライトでご紹介しますので、このままお読みいただいても良いですし、今すぐコーディングを開始しても良いでしょう。

モバイル タッチ

HTML5 ゲームではモバイルファースト開発が人気ですので、まずはモバイル タッチへの対応から見ていきましょう。

document.addEventListener("touchstart", touchHandler);
document.addEventListener("touchmove", touchHandler);
function touchHandler(e) {
    if(e.touches) {
        playerX = e.touches[0].pageX - canvas.offsetLeft - playerWidth / 2;
        playerY = e.touches[0].pageY - canvas.offsetTop - playerHeight / 2;
    }
}

たった数行の JavaScript コードですが、これがゲーム内でタッチ操作を実現するために必要な最低限のコードです。これだけでモバイル端末上で機能します。最初の 2 行は、タッチ操作に対するイベントリスナーの設定です。それぞれ、画面をタッチしたとき、および、指で画面をなぞったときに対応します。関数内では、タッチ操作が行われたことを確認し、プレイヤーの座標を設定しています。これにより、ゲームの Canvas 上でプレイヤーの機体が正しい位置に描画されます。

上記はベースとなるコードですので、ここからマルチタッチやジェスチャーなど様々な発展形を実装することができます(むしろ、実装した方がいいでしょう)。ゲームの種類や、何をどのように操作するかということを考慮して、実装する操作方法を決めましょう。また、移動用の矢印や発射ボタンなど、特定のアクションを起こすためのボタンを画面上に設置することもできます。詳しくは、Mobile touch controls をご覧ください。

デスクトップ マウスとキーボード

移動用の矢印について触れましたが、モバイル端末向けに画面上に矢印を表示するのはもちろん、デスクトップ向けにそれらを実装することも可能です。ゲーム内のキャラクターを動かす方法としては、カーソルキーや WASD キーがよく使われます。例えば、以下のコードではカーソルキーを使用しています。

document.addEventListener("keydown", keyDownHandler);
function keyDownHandler(e) {
    if(e.keyCode == 39) {
        rightPressed = true;
    }
    else if(e.keyCode == 37) {
        leftPressed = true;
    }
    if(e.keyCode == 40) {
        downPressed = true;
    }
    else if(e.keyCode == 38) {
        upPressed = true;
    }
}

押されたキーを定常的に検出しその情報を保存するということですので、これは描画ループとして以下のように処理することが可能です。

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    if(rightPressed) {
        playerX += 5;
    }
    else if(leftPressed) {
        playerX -= 5;
    }
    if(downPressed) {
        playerY += 5;
    }
    else if(upPressed) {
        playerY -= 5;
    }
    ctx.drawImage(img, playerX, playerY);
    requestAnimationFrame(draw);
}

プレイヤーの位置 x および y を表す変数が調整され、機体画像が新たな位置に表示されます。

controls-howtoplay

一方、デスクトップにおけるマウスは、コーディングにおいてモバイルのタッチと非常に似ています。タッチやクリックが起こった場所に関する情報を取得し、プレイヤーの位置を更新するだけです。

document.addEventListener("mousemove", mouseMoveHandler);
function mouseMoveHandler(e) {
    playerX = e.pageX - canvas.offsetLeft - playerWidth / 2;
    playerY = e.pageY - canvas.offsetTop - playerHeight / 2;
}

マウスポインターの位置が変化するたびに mousemove イベントが検出されるので、プレイヤーの位置がマウスポインターの中央の位置に来るように調整します。こうして、デスクトップのキーボードとマウスの両方で操作可能なゲームを実現することができます。さらに詳しく知りたい方は、Desktop mouse and keyboard controls をご覧ください。

ゲームパッド

個人的に大好きな操作方法です。プレゼンテーションを行う際は、HTML5 ベースの自分のスライドを Gamepad APIを使って操作しているくらいです。ゲームパッドについては、すでに数回お話したこともありますし、ゲームもいくつか実装したこともあります。詳しくは、Gamepad API Content Kit に全て記載しています。 とにかく、コンピューター上でコンソール気分を体験できることは本当に素晴らしいと思いますし、それがウェブ技術によって実現されているということも素晴らしいと思います!ゲームパッドを使用すれば、Captain Rogers のプレイ体験がさらに向上しますし、単純に良い気分になります。

controls-gamepadinfo

Gamepad API への対応はタッチやキーボードよりもやや複雑になりますが、それでも非常に簡単です。

window.addEventListener("gamepadconnected", gamepadHandler);
function gamepadHandler(e) {
    controller = e.gamepad;
}
function gamepadUpdateHandler() {
    buttonsPressed = [];
    if(controller.buttons) {
        for(var b=0; b<controller.buttons.length; b++) {
            if(controller.buttons[i].pressed) {
                buttonsPressed.push(b);
            }
        }
    }
}
function gamepadButtonPressedHandler(button) {
    var press = false;
    for(var i=0; i<buttonsPressed.length; i++) {
        if(buttonsPressed[i] == button) {
            press = true;
        }
    }
    return press;
}

ゲームパッドが接続されるとイベントが発生し、ゲームパッドに関するデータの参照が変数に保存されます。以降は、その変数を使用してゲームパッドに関するデータにアクセスします。更新が起こると、押されたボタンの配列が新たに生成されるため、常に最新の状態が保持されます。また、あるボタンが押されたかどうかを判定するために配列をループ処理する関数もあります。これらはキーボードチェックと同様に、描画ループの中で使用できます。

function draw() {
    // ...
    gamepadUpdateHandler();
    if(gamepadButtonPressedHandler(0)) {
        playerY -= 5;
    }
    else if(gamepadButtonPressedHandler(1)) {
        playerY += 5;
    }
    if(gamepadButtonPressedHandler(2)) {
        playerX -= 5;
    }
    else if(gamepadButtonPressedHandler(3)) {
        playerX += 5;
    }
    if(gamepadButtonPressedHandler(11)) {
        alert('BOOM!');
    }
    // ...
}

こうすることで、プレイヤーの機体を DPad ボタン(十字キー)で操作したり、A ボタンで爆弾を爆発させることができるようになります。このほか、スティック操作を検知することもできますし、独自の小さいライブラリを作ってゲームパッドからの入力を処理することもできます。詳しくは、Desktop gamepad controls をご覧ください。

特殊な操作方法

さらにもっと進んで、リビングにある大型 TV のリモコンを使ったり、ラップトップの前で手を振ったり、食材とPCを線で繋いでその食材を押したりすることで、ゲームをプレイすることも可能です。

controls-tvremote

例えば、Panasonic 製の TV リモコンはキーボードイベントを再利用しているので、対応が非常に簡単です。リモコンの方向矢印はキーボードのカーソルキーと全く同じコード(37383940)を使用しているので、すぐに対応できます。リモコン特有のボタンについては、こちらに一覧がありますので、詳細情報とともにご覧ください。

controls-leapmotion

また、リモコンのボタンを押す代わりに、Leap Motion 機器の機能を使用して自分の手の位置や他のパラメーターを検知することで、何にも触れることなくプレイヤーの機体を操作することもできます。あらかじめ定義されている loop 内で、手に関する情報を取得できるので、

Leap.loop({
    hand: function(hand) {
        horizontalDegree = Math.round(hand.roll() * toDegrees);
        verticalDegree = Math.round(hand.pitch() * toDegrees);
        grabStrength = hand.grabStrength;
    }
});

それを使用してプレイヤーの位置を更新します。

function draw() {
    // ...
    if(horizontalDegree > degreeThreshold) {
        playerX -= 5;
    }
    else if(horizontalDegree < -degreeThreshold) {
        playerX += 5;
    }
    if(verticalDegree > degreeThreshold) {
        playerY += 5;
    }
    else if(verticalDegree < -degreeThreshold) {
        playerY -= 5;
    }
    if(grabStrength == 1) {
        alert('BOOM!');
    }
    // ...
}

その他にも、Unconventional controlsでは、 ドップラー効果や Proximity API、MaKey MaKeyなど興味深い操作方法の実装方法を紹介しています。

まとめ

最近は、HTML5 ゲームをプレイするのに使える機器がますます増えています。ご自身の腕時計も使えるかもしれませんし、はたまた音声認識によるウェブゲームも良いかもしれません。まさに、可能性は無限です。覚えておいて頂きたいのは、多くの操作方法に対応しているほど、ゲームにとっては良いことだということです。なぜなら、幅広い種類の機器を用いて、どんなプラットフォームでもプレイ可能になるからです。ぜひ、ブラウザがもたらす可能性を存分に活用しましょう。

debugger.html のご紹介

[この記事は “Introducing debugger.html” の抄訳です]

debugger.html は Mozilla の作成したモダンな JavaScript デバッガで、React と Redux を使って実装されています。これは Firefox の開発ツールに組み込まれているデバッガを置き換えるためのプロジェクトで、今年の初めに始まりました。また複数のターゲットに対してデバッグできることや、単体での動作も目的としています。

debugger.html のスクリーンショット

現在は Firefox、そして実験的な機能として Chrome、そして Node に接続し、デバッグを行えます。Firefox への接続には Mozilla のリモートデバッグプロトコル (RDP) を、Chrome と Node への接続には Chrome の RDP を利用しています。

このプロジェクトは GitHub で公開されています。またモダンなフレームワークと、ツールチェーンを利用しています。多くの開発者にとって魅力的で、簡単に見つけられるようになっています。

debugger.html

debugger.html のユーザーインタフェースは、ソースパネル、エディタパネル、サイドバーの 3 つに分かれています。

  • ソースパネルには、ソースコードがツリー状に表示されます
  • エディタパネルには様々なソースファイルの表示が可能です。またソースコードを整形して見やすく表示する機能やブレークポイントの設定機能も持っています
  • サイドバーには、現在設定されているブレークポイントが表示されます。プログラムがブレークした時には、その時点でのコールスタックと、変数が表示されます
    • 一時停止、ステップオーバー、ステップイン、ステップアウト、復帰といった、デバッグのための機能を提供します
    • 実行が一時停止されると、その時点でのコールスタックがコールスタックパネルに、スコープパネルには変数がツリー状に、それぞれ表示されます

デバッグ中の画面

はじめかた

debugger.html を使うには、まず GitHub からコードをチェックアウトし、 Getting Started を読むところから始めましょう。

すぐ使い始めたいなら、次のコマンドを実行してください:

npm install - Install dependencies
npm start - Start development web server
open http://localhost:8000 - Open in any modern browser

8000 番ポートへアクセスすると、デバッガーが表示されます。デバッグ対象が選択されると、そのターゲットに対し接続し、リモートデバッグを開始します。これには、ターゲットにいくつかの項目を設定しなければなりません。例えば MacOS 上で動作する Firefox に接続する場合には、次のコマンドを実行してリモートデバッグを許可する必要があります。

$ /Applications/Firefox.app/Contents/MacOS/firefox-bin --start-debugger-server 6080 -P development

Chrome や Firefox で利用するその他オプションはこちらを参照してください。

Node 上で動くコードをデバッグするには、v6.3.0 以上の node.js が必要です。inspect オプションをつけて Node を起動すると、デバッグができます。例えば myserver.js をデバッグするには、次のように実行します。

$ node --inspect myserver.js

詳細は Getting Started を参照してください。

Firefox の開発ツールとの関係

Firefox に組み込まれている開発ツールとの統合を進めています。最初の統合が終わり、Nightly 版で利用できるようになっています。

Nigntly 版に統合された debugger.html

開発に参加するには

このプロジェクトは開発の途中です。皆様の参加は心から歓迎します。一緒に最高のデバッガーをつくりましょう。開発へ興味をお持ちの方は、まず貢献のための手引きをごらんください。

Bryan Clark について

Firefox 開発ツールのプロダクトマネージャ。

Twitter:@clarkbw

Bryan Clark によるその他のドキュメントはこちら

What’s new in Web Audio?

この記事は “What’s new in Web Audio?” の抄訳です。

Web Audio API は以前開発中です。つまり新しいメソッドや属性の追加、名称の変更、入れ替え、そして削除もありうるということです。

この記事では 2015 年初頭の時点からの Web Audio と、その Firefox における実装との変更点についてまとめます。参照しているデモは Firefox の Nightly 版で動作します。リリース版や Developer Edition では対応していない変更点もありますから、ぜひ Nightly 版で試してください。

API の変更

破壊的なもの

DynamicsCompressorNodereduction 属性は AudioParam ではなく float となりました。その結果、compressor.reduction.value ではなく compressor.reduction を参照することで、値を読み出せるようになりました。

この値は入力シグナルに対して適用される減衰量を表します。以前よりリードオンリーの値であり、スケジュールされた値の変更が必要ないため、AudioPArame ではなく float への変更は妥当なものでしょう。

ブラウザのサポートするのが AudioParam なのか、それとも float なのかは、reduction の value 属性を参照することで判断できます。

if(compressor.reduction.value !== undefined) {
  // old style
} else {
  // new style
}

こちらでは、ドラムループが再生され、それに応じて reduction 属性の値が変化する様子がみられます。この値はトラックの音の大きさに合わせて変化しています。またブラウザがサポートする API のバージョンの判定方法も、併せて確認できます。

新しい属性とメソッド

AudioContext へ追加されたライフサイクル管理用メソッド

AudioContext の生成のコストは高かいため、suspend()resume()close() の 3 メソッドが追加されました。

これらのメソッドを利用することで、音声処理を必要になるまでサスペンドしたり、AudioContext が必要なくなった場合に close() を呼んでリソースを解放するといったことが可能になりました。

AudioContext がサスペンドされた場合、音声は再生されません。レジューム時にはサスペンドされた位置から再生されます。詳細については、suspend() の解説を参照してください。

これらのメソッドの利用例は、こちらのデモで見られます。

正確になった AudioNode の disconnect()

disconnect() を呼ぶと、接続されている全てのノードから切り離されてしまったため、特定のノードだけを選択的に切断することはできませんでした。

望まれる切断方法が多様化したため、使い方にあわせて disconnect() メソッドをオーバロードできるようになりました。

  • disconnect():全てのノードへの接続を切る(既存の機能)
  • disconnect(outputNumber):指定された出力チャンネルから、ノードへの接続を全て切る
  • disconnect(anotherNode):指定されたノードへの接続を全て切る
  • disconnect(anotherNode, outputNumber):このノードの outputNumber で指定されたチャンネルから anotherNode への接続を全て切る
  • disconnect(anotherNode, outputNumber, inputNumber):このノードの outputNumber で指定されたチャンネルから、anotherNodeinputNumber で指定されるチャンネルへの接続を全て切る
  • disconnect(audioParam):このノードから指定された audioParam への接続を全て切る
  • disconnect(audioParam, outputNumber):このノードの outputNumber で指定されたチャンネルから、audioParam への接続を全て切る

これらの切断に関する処理について理解するには、AudioNode の仕様を読むことを強くお勧めします。また変更が行われた理由については、こちらの議論を参照してください。

OfflineAudioContext への length 属性の追加

この新しい属性の値は、OfflineAudioContext の初期化時に実行されるコンストラクタの引数によって決まります。そのため、その値を別の変数に保存する必要がなくなりました。

var oac = new OfflineAudioContext(1, 1000, 44100);
console.log(oac.length);
>> 1000

こちらのデモではこの属性の使用例と、ゲインエンベロープを利用した音声の生成例が見られます。

AudioBufferSourceNode への detune 属性の追加

これは OscillatorNodedetune 属性と同様のものです。既存の playbackRate 属性よりも正確にサンプリングした音声を調整できます。

PannerNode に AudioParam 型の属性 position と orientation の追加

AudioParam 型の属性が追加されました。これらを利用することで、setPoistion()setOrientation() を呼び続けなくても、それらの変化を自動化することができます。

StereoPannerNodepan 属性はすでに AudioParam になっています。そのため音声のパンニングを扱うノードのは全て、自動的にその位置に関する属性を変更できるようになりました。これはモジュラーシンセを作るのに、とても便利なものたちです。

しかし AudioListener の位置と向きに関する属性を自動的に変化させることは、まだできません。つまりこれらの値を時間に応じて変化させるには setPosition()setOrientation() を定期的に呼び出す必要があるのです。この問題は Bug #1283029 にファイルされています。

PeriodicWave の初期値指定

PeriodicWave オブジェクトを作成する際に、オプションをオブジェクトで渡せるようになりました。

var wave = audioContext.createPeriodicWave(real, imag, { disableNormalization: false });

比較のため、以前の書き方を載せておきます:

var wave = audioContext.createPeriodicWave(real, imag);
wave.disableNormalization = false;

将来的には、ノードを作成する全てのメソッドで初期値を渡せるようになります。またコンストラクタも利用できるようになるため、GainNode の作成もこのように行えます:new GainNode(anAudioContext, {gain: 0.5});。 これで初期化が必要な Web Audio のコードをより簡潔に書けるようになります。メンテナンスするコード量が減ることは、常にいいことですよね!

新しいノードの追加:IIRFilterNode

IIRFilterNode を利用した例のスクリーンショット

BiquadFilterNode では力不足と感じた場合、IIRFilterNode を使ってカスタムフィルタを作成すると良いでしょう。

AudioContextcreateIIFilter() メソッドに、フィードフォワードとフィードバックの二つの係数を配列で与えることで、フィルタを作成できます。

var customFilter = audioContext.createIIRFilter([ 0.1, 0.2, ...], [0.4, 0.3, ...]);

このフィルタノードのパラメータを自動的に変化させることはできません。つまり一度作成したら、その値は変更できません。そのような変化をさせたいのであれば、BiquadFilter を利用し、AudioParam で指定できる Q / detune / frequency / gain の各属性を適切に設定することになります。

これらの違いについては仕様を読むと、より理解できるでしょう。また Digital Filter Design も便利なツールです。ここではフィルタのデザインとフィルタの可視化が可能で、feedforwardfeedback を含む使用可能なコードが生成されます。

メソッドチェーン

書きやすさのために、いくつかのシンタックスシュガーも追加されました。

connect() メソッドは接続先のノードを返すようになったため、複数のノードの接続を簡単に書けるようになりました。

以前:

node0.connect(node1);
node1.connect(node2);
node2.connect(node3);

今:

node0.connect(node1).connect(node2).connect(node3);

また AudioParam の自動化メソッドも呼び出し先のノードを返すようになったため、メソッドチェーンを行えるようになりました。

以前:

gain.setValueAtTime(0, ac.currentTime);
gain.linearRampToValueAtTime(1, ac.currentTime + attackTime);

今:

gain.setValueAtTime(0, ac.currentTime)
  .linearRampToValueAtTime(1, ac.currentTime + attackTime);

これから

Web Audio ワーキンググループは AudioWorklet に関する仕様の記述をほぼ終えています。これは AudioWorker の新しい名前です。これは ScriptProcessorNode を置き換えることになっています。これは ScriptProcessorNode が開発者が定義した任意の音声処理を UI スレッドで行うため、パフォーマンスの面で改善の余地があったためです。

AudioWorklet と関連するオブジェクトを定義し、仕様に追加するプルリクエストのマージが最初に行われるでしょう。その後、各ブラウザベンダが AudioWorklet の実装に入ります。

Firefox:パフォーマンスとデバッグにおける改善

3 人のエンジニア(Karl Tomlinson, Daniel Minor and Paul Adenot)の 6 ヶ月以上の奮闘により、Firefox の Web Audio に関するパフォーマンスは改善されました。実践的な面から見ると、音声に関するコードは Chrome よりも同等以上のスピードで動作するようになりました。唯一の例外が AudioParam に関連するもので、そのパフォーマンスはまだ良いとは言えません。

同様にメインスレッドが重たい時によく起きていた ScriptProcessorNode の遅延も少なくなくなりました。これはコンソールエミュレータのようなアプリケーションには大きな改善です。低遅延はエミュレーションの信頼性を高め、その結果として多くのゲームを楽しめるようになるからです。

より深い話としては、DSP カーネルの計算にアセンブラレベルでの最適化が行われました。ARM と x86 の SIMD 命令の長所を利用して、パンニングやゲインの調整といった簡単な計算で、複数の値を並行に計算するようになりました。これによってコードの実行が高速かつ効率的になり、モバイル環境では特に重要なバッテリー消費もより少なくなりました。

また MediaElement ノードで発生する cross-origin エラーが開発ツールのコンソールへ表示されるようにもなりました。以前はこのエラーが報告されないまま失敗するため、コードが止まる理由を考えるのが大変でした。この変更によって、その原因コードの特定が簡単になりました。

これら以外にも多くのバグが修正されました。ここに書くには多すぎるくらいですが、こちらのリストでそれらを確認できます。

Soledad Penadés について

Sole は Mozilla の Developer Relation チームの一員で、Web で素晴らしいものが作られるのを助けています。なかでもリアルタイムに関するものが好みです。irc.mozilla.org の #devrel でコンタクトできます。

Soledad Penadés によるその他の記事はこちら。

Developer Edition 50:コンソール、メモリツール、ネットワークモニタ、etc

この記事は “Developer Edition 50: Console, Memory Tool, Net Monitor and more” の抄訳です。

Firefox Developer Edition のバージョン 50 が公開されました。今回の更新では、スクリプト由来のリクエストのモニタや、IndexedDB に保存されているデータの操作など、多くの点で改良がなされています。これ以外にずっと欲しかった機能も紹介します。

コンソール

待ちわびた機能がついにやってきます。その機能とはソースマップです。この機能は最終のテスト段階で標準では off になっていますが、ぜひ試してフィードバックを送ってください。

ソースマップが実現の困難な機能であった理由については、James Long による解説記事があります。そちらをご覧ください:On the Road to Better Sourcemaps in the Firefox Developer Tools

そんな困難な機能の実装がどうして始まったか、興味はありませんか?Joe Walker によると、その理由は次のようになるそうです:

インターンは背景知識を十分に持っていない場合があって、バグの難しさを見誤ることも多い。その結果、本当に難しいバグにチャレンジすることになってしまうことも、ときおりある。ま、それがインターンなんだけどね。

解決をした Firefox の開発ツール担当インターン Jaideep Bhoosreddy に大きな感謝をしたいです。

ソースマップを利用することで、ダウンロード時間の短縮のために JavaScript ファイルを圧縮したり、TypeScript や CoffeeScript といった他のフォーマットから変換して JavaScript を作成することが可能になり、しかも元のファイルとの関係性も維持されるためデバッグが簡単になります。

デバッガはすでにソースマップに対応していますが、コンソールは今まで対応していませんでした。その結果、出力されたログに含まれるファイルや行番号といった位置は、変換後の JavaScript ファイルのものとなってしまって、ほとんど利用価値がなくなってしまいます。

しかしそれも過去のことです。、コンパイル後のものではなく、オリジナルのソースファイルの位置がコンソールに表示されるようになります。次の例は CoffeeScript ファイルの位置を表示しています:

コンソール中に表示されている行番号をクリックして、対応する CoffeeScript が適切にデバッガで表示する様子

ソースマップに対応したコンソール

コンソールのソースマップ対応機能は標準ではオフになっており、有効にするには設定を変更する必要があります。これはソースマップの作成に利用したツールにごとにいくつかの実装があるため、それらの幾つかをテストしたいと思っているからです。これが現在の状況です。

コンソールをソースマップに対応させるには

ソースマップに対応したコンソールを利用するためには、以下の手順に従って設定を変更します。

  • URL バーに about:config と入力する
  • devtools.sourcemap.locations.enabled を検索する
  • その項目をダブルクリックし、値を true に変更する
  • about:config を閉じ、コンソールを再び開く

about:config の項目を変更し、コンソールのソースマップ対応を有効にしている

何かおかしい点を発見したら、Twitter で @firefoxdevtools に宛ててつぶやくか、IRC の #devtools チャンネルでご相談ください。

ネットワークスタックトレース

Firefox Developer Edition バージョン 50 では、HTTP ログからリクエストを送信した際のスタックトレースが見られるようになりました。この機能は標準で利用できます。

HTTP リクエスト送信時のスタックトレースが、コンソールに表示されている

メモリツール

メモリツールも標準で利用できるようになりました。デバッグとパフォーマンチューニングには欠かせないツールです。これを利用することで、メモリリークの発見と修正が簡単になります。このツールの詳細については、MDN の記事、もしくは Hacks の “Firefox’s New Memory Tool” という記事をご覧ください。

ネットワークモニタ

Firefox バージョン 49 で、”Cause” 列が追加されました。ここにはネットワークリクエストの発生理由の種別が表示され、可能であればスタックトレースも表示されます。スタックトレースはポップアップで表示されるのですが、今回の更新でここに、XHR や Promise、setTimeout といった非同期通信時のフレームが表示されるようになりました。ちょうどデバッガにおけるスタックトレースパネルと似たものとなっています。

非同期通信のフレーム情報が追加されたネットワークモニタ

また列のヘッダをクリックすることで、通信ログを原因ごとに並び替えられます。これは fetch によって発生したリクエストを早く探したい、といった場合に便利な機能です。

JSON ビューワ

JSON ビューワも改善され、よりスマートにデータを表示するようになりました:

ストレージインスペクタ

Mike RatcliffeJarda Snajdr の泥浴によって、ストレージインスペクタも改良されました。その結果、コンテキストメニューから IndexDB 中の項目を 1 件ずつ削除できるようになりました。

コンテキストメニューから、IndexDB 中の項目を削除する様子

about:debugging

Web 開発における次のビッグウェーブといえば、Service Worker でしょう。オフライン時の機能や、プッシュ通知といったネイティブアプリの持つ機能を備えた Web アプリである Progressive Web Apps を作るには、Service Worker の提供するツールを利用します。Firefox では、about:debugging#workers というページで登録された Service Worker の管理できます。今回の行為新で、このページで各 Service Worker が利用するプッシュ通知のエンドポイントも表示されるようになりました。またテスト用の通知を送るためのボタンも併せて追加されています。

about:debugging#workers の画面

その他の変更点

アイコン:開発ツール内のアイコンが一貫性とシャープさが出るように変更されました

Firefox 49 でのアイコン

Firefox 49 でのアイコン

Firefox 50 で変更されたアイコン

Firefox 50 でのアイコン

WebAssembly:Luke Wagner が以前のブログポストで述べています:

WebAsssembly は策定が進んでいる標準の一つで、その目的は安全性とポータビリティを維持しつつ、サイズとロード時間の面で効率的なバイナリフォーマットの定義です。

デバッガはすでに WebAssembly ファイルにも対応しています。またシンタックスハイライトもできるようになりました。

シンタックスハイライトされている WebAssembly ファイル

シンタックスハイライトされている WebAssembly ファイル

最後に小さいけれど便利な変更点を一つ:スタイルエディタを無効にできる機能が追加されました。これでスタイルエディタのアイコンが占めていたタブ上のスペースを節約できます。

以上で Developer Edition バージョン 50 の機能紹介は終了です。最新版をダウンロードして、フィードバックを送ってください。最後に今回追加された多くの改善に貢献されたすべての人に大きな感謝を示したいと思います。ありがとうございました。

開発ツールはスタンダードな HTML、JavaScript、CSS で開発されています。フロントエンジニアとしての経験があれば、その開発に参加できます。開発ツールの開発に興味をお持ちの方は、まず http://firefox-dev.tools/ にある簡単なバグから始めてみてはいかがでしょうか。参加はいつでも歓迎します。

Nicolas Chevobbe について

Nicolas Chevobbe による他の記事はこちら