dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

GitHubの新しいissue forms機能(ベータ)を試したメモ

こちらのツイートをしたときはまだ一部のリポジトリしか有効になってなかったんだけど、先日ついにすべてのパブリックリポジトリで利用可能になった。(まだベータという位置づけ)

というわけで自分のリポジトリで試したメモ。

ドキュメントはここにある。

https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#creating-issue-forms

issue forms機能とは

GitHub には元々 Issue のテンプレート機能 があったが、テンプレートファイルを Markdown でなく YAML で用意すると issue 作成時にフォームで表示してくれるというもの。

実際の画面キャプチャを載せる。
https://github.com/zaki-yama-labs/github-issue-forms-playground で New issue すると見れる。

f:id:dackdive:20210629235241p:plain:w480

自由記述式のテキスト項目だけでなく、ドロップダウンやチェックボックスも作れる。

設定方法

Markdown のときと同じく、リポジトリ.github/ISSUE_TEMPLATE ディレクトリを作り、その下に YAML ファイルを置く。 YAML ファイルのサンプルを 公式ドキュメント から引用する。

name: Bug Report
description: File a bug report
title: "[Bug]: "
labels: [bug, triage]
assignees:
  - octocat
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this bug report!
  - type: input
    id: contact
    attributes:
      label: Contact Details
      description: How can we get in touch with you if we need more info?
      placeholder: ex. email@example.com
    validations:
      required: false
  - type: textarea
    id: what-happened
    attributes:
      label: What happened?
      description: Also tell us, what did you expect to happen?
      placeholder: Tell us what you see!
      value: "A bug happened!"
    validations:
      required: true
  ...

トップレベルの name, description といったプロパティについては
Syntax for issue forms > Top-level syntax を参照。

メインとなるフォーム部分は body 内にフォーム要素の配列として定義する。
フォーム要素のプロパティについては
Syntax for GitHub's form schema を参照。

以下、個人的に気になったポイント。

表示できるフォーム要素のタイプ

現在は以下の5種類。

  • markdown
  • input
  • textarea
  • dropdown
  • checkboxes

markdown は文字通り Markdown で任意のメッセージを記載できるというものだが、これはフォーム上で表示されるだけで submit されない = 登録された issue には記載されない。

- type: markdown
  attributes:
    value: |
      `type: markdown` はフォームに表示されるがsubmitされない(登録されたissueには記載されない)。
      issue 登録者向けのメッセージとして使えそう。

f:id:dackdive:20210630003056p:plain

textarea に render: <言語> を指定するとコードブロックになる

textarea には render というプロパティがある(ドキュメント)。
render: javascript などのようにプログラミング言語を指定すると、結果がコードブロックとして表示され、指定した言語でシンタックスハイライトされるようになる。

- type: textarea
  id: textarea-2
  attributes:
    label: "Textarea (render: javascript)"
    description: "render: <言語> を指定するとコードブロックになり、指定した言語でシンタックスハイライトができる"
    render: javascript

f:id:dackdive:20210630004028p:plain

id はURLパラメータで値をセットするために使える

すべての type に存在する id というパラメータ。
何に使うのかと思ったけど、ドキュメントによれば

If provided, the id is the canonical identifier for the field in URL query parameter prefills.

とあるので、指定しておくとそれをURLパラメータで使うことで値をセットできる。

たとえば
https://github.com/zaki-yama-labs/github-issue-forms-playground/issues/new?assignees=zaki-yama&labels=&template=sample.yml&title=%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB%3A+&input-1=foo,%20bar
こういうURLだと最初の input 要素に foo, bar という文字列を初期値としてセットできる。

トラブルシューティング

構文エラーになってた場合、該当ファイルを開くと上部にエラーが表示される。

f:id:dackdive:20210630005030p:plain

各エラーメッセージの「Learn more」をクリックすると詳細に飛べる。

所感

ドロップダウンやコードブロックや画像添付、 必須(required)オプションなど、ベータだけどだいたい必要なものは揃ってる感じ。 従来の Markdown にコメントで案内書くのに比べて見やすいし、より厳密にフォーマットに従わせることができるのでよさそう。

Salesforce: Dynamic Interactionsとは

これのこと。5/12の記事。

TechCrunchにも情報がありますね。

読んでみたけど、今までと何が変わるのかよくわかりませんでした。

というわけで、ググるとこちらの動画が見つかります。

(動画の作成日が4月になってるので、今回のリリースとの関係性は不明)

これを見るに、ポイントとしてはこのあたりでしょうか。

f:id:dackdive:20210513141424p:plain

コンポーネントの設定画面に「Events」というタブが新たに追加されており、"Add Interaction"をクリックすると

f:id:dackdive:20210513141419p:plain

こういった形で、

  • このコンポーネントから発火されるイベントをどのコンポーネントに伝えるか
  • そのときの各種パラメータはどうするか(デモでは数式っぽい記法でイベントオブジェクトから取り出してるように見える)

といったことを「ユーザー側が」「GUIで」設定できるようになります。

従来ですと開発者がゴリゴリコーディングしないと実現できなかった部分であり、またコンポーネント開発側でなくそれを使う側がイベント部分だけを実装するということは(おそらく)不可能だったはず。

また、設定項目に "interaction" というドロップダウンが見えますが、動画の最後のスライドによると

f:id:dackdive:20210513141625p:plain

コンポーネントにイベントを伝達する以外にも、特定のURLを開く・SMSを送る・外部APIを実行する、などができそう。

いつリリースされるの?

公式記事の方では

Dynamic Interactions is expected to be generally available in Winter 2021.

また、TechCrunchの記事によれば

Dynamic Interactionsは、Salesforceからベータ版として米国時間5月12日より提供される。この製品が一般公開されるのは、秋のユーザー会議Dreamforce(ドリームフォース)の頃になる予定だ。

だそうです。

ベータって言ってるけど、少なくともSummer'21リリースノートには情報載ってないですよね...?

https://help.salesforce.com/articleView?id=release-notes.salesforce_release_notes.htm&type=5&release=232

余談

Dynamic Interactions という言葉自体は昨年の Dreamforce (DreamTX)で登場していたように見える。

RustのFromトレイトとIntoトレイト

先日こんな記事を書いたのだが、最後に登場した From トレイトというものをよく理解してなかったのでその後調べたメモ。

検索したところ Web 上では Rust by Example ぐらいしかヒットしなかった。

また、以前読んだ「実践 Rust 入門」にもさらっとだが触れられていた。

(8-6-2 std::convert::From)

概要

この2つのトレイトはどちらも、ある型から別の型に変換するためのトレイトである。

たとえば、String 型の文字列を定義するときによく使う

let s = String::from("hello");

String::from()From トレイトを利用している。

また、この2つのトレイトは密接にリンクしており、From トレイトを実装すると自動的に Into トレイトも実装されるという関係になっている。ただし逆はない。

From トレイト

From トレイトは

impl From<T> for U {
  fn from(T) -> Self;
}

というように定義されていて、これを実装すると T 型から U 型への変換ができるようになる。

以下、サンプルコード。Rust by Example と同じ。

use std::convert::From;

#[derive(Debug)]
struct Number {
    value: i32,
}

// `i32` 型から `Number` 型へ変換できるようになる
impl From<i32> for Number {
    fn from(item: i32) -> Self {
        Self { value: item }
    }
}

fn main() {
    let num = Number::from(30);
    println!("My number is {:?}", num);
}

Into トレイト

先ほどの

impl From<T> for U {
  fn from(T) -> Self;
}

を実装することによって、自動的に Into トレイトも実装されたことになり、

T型の値.into()

という書き方で U 型に変換できるようになる。

fn main() {
    // From トレイトを使うとこういう書き方だったが
    let num = Number::from(30);
    println!("My number is {:?}", num);

    // Into トレイトにより、こう書くこともできる
    // 変数の方の型を省略するとエラーになる
    let num: Number = 30.into();
    println!("My number is {:?}", num);

}

しばらく勘違いしてたのだが、 FromInto が対になってて T ←→ U 間で相互に変換可能になるわけではなく、変換の方向としてはどちらも同じ(サンプルでいうと、 i32Number)。

なお、上のサンプルでは変数の方に型を書いているが、メソッドチェーンなどでそれができないときは

let num = Into::<Number>::into(5);

のようにも書ける。

参考にさせていただいた記事:

From トレイトと Into トレイト、どちらを実装すべき?

std::convert::From - Rust

によれば

One should always prefer implementing From over Into because implementing From automatically provides one with an implementation of Into thanks to the blanket implementation in the standard library.

なので、基本は From トレイトを実装すると理解しておけばよさそう。

また、じゃあどういうときに Into トレイトの方を使うのか?についても直後に書かれており

Only implement Into when targeting a version prior to Rust 1.41 and converting to a type outside the current crate. From was not able to do these types of conversions in earlier versions because of Rust's orphaning rules. See Into for more details.

Rust のバージョンが 1.41 より前は、変換先の型が現在のクレートの外にある場合 From トレイトではコンパイルエラーになっていたらしい。

U::from(t)t.into() どちらで書くべき?

わからない。好みの問題な気がする。
Into トレイトの最後で触れたように変数の方に型を書けないケースとかを考えると U::from(t) の方が好み。

余談: TryFromTryInto

似たようなトレイトとして、TryFromTryInto というのもあるらしい。

TryFrom and TryInto - Rust By Example

これは、やっていることは同じようにある型から別の型への変換だが、失敗する可能性があるときはこちらを使う。

use std::convert::TryFrom;

#[derive(Debug, PartialEq)]
struct EvenNumber(i32);

impl TryFrom<i32> for EvenNumber {
    type Error = ();

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        if value % 2 == 0 {
            Ok(EvenNumber(value))
        } else {
            Err(())
        }
    }
}

失敗する可能性があるので try_from() の戻り値は Result 型になっている。

リファレンス

2021年1〜3月のふりかえり

完全に出遅れたが今年も3ヶ月おきにふりかえりをしていこうと思う。

やったこと

1月:「Go 言語でつくるインタプリタ入門」を途中まで Rust で頑張るものの挫折した

Rust の勉強のため & インタプリタについて知りたいというモチベーションで昨年12月ぐらいから取り組んでたものの、途中で飽きてしまい、こんなモチベーションで続けるぐらいなら気になってる Let's build a browser engine やった方がいいなーと思ってやめた。

たぶん、本格的に面白くなる前にやめてしまったというか、書かれたプログラムを字句解析器(レキサー)→構文解析器(パーサー)という手順でもって抽象構文木(AST)にするというあたりはなんとなく知っており、新しい知識が得られるとしたらこのあとそれを評価する部分のしくみだと思うので、そこまでたどり着けなかったのはもったいない気もする。

あとは、年始にこれの特集記事「Web ページが表示されるまで」を読んだ。
感想をブログにアウトプットするところまでできてなかった。


2月:「Let's build a browser engine!」をやった

というわけで自分の欲求に忠実に生きようと2月からやったのがこの連載記事。

内容・感想はここに書いたが、ブラウザのレンダリングエンジン(の超簡易的なやつ)を Rust で書いてみよう、というもの。
「Let's build a browser engine!」を読んでRustで簡易レンダリングエンジンを作った - dackdive's blog

これはなかなか面白かった。
レンダリングエンジンのおおざっぱな仕組みは理解していたが、実際書くとここまでシンプルでもレイアウトツリーの構築とかはこんなにめんどいのかー、みたいな感想を持った。


3月:なんか色々手を出した & 「ゼロからのOS自作入門」を始めた

3月の上旬は Software Design 3月号の WebAssembly の特集記事を読んだ。

メモはこちら:Software Design 2021年3月号の「WebAssembly入門」を読んだ - dackdive's blog

そして下旬にこちらの本が発売されたので、今はこれを読んでいる(通称「みかん本」)。

OS を作ることを通じて低レイヤのこともっと理解したい〜と思ってたのでとても良いタイミングだった。

また、みかん本を読みつつスキマ時間に Rust の勉強を続けたくて Exercism をやっている。

よくあるプログラミング学習サービスなんだけど、メンターがいてこちらが提出したコードに対してアドバイスとか Tips を教えてくれるのが良い。


1月〜現在:副業で Salesforce 開発をはじめた

Scalebase というサブスクリプションの契約管理 SaaS を開発しているアルプ株式会社で1月からお世話になっている。
Scalebase には Salesforce と連携する Scalebase Connect というサービスも展開しており、そこの機能追加や改善タスクを業務委託で行っている。

Salesforce 開発にがっつり関わるのは久しぶりなのでまた勉強し直してる。Lightning Web Component という Salesforce 独自のフレームワーク(※)とか。
フレームワークというと語弊があるかもしれない(参考)。でも React とか Vue みたいなものだと個人的には思ってる

副業先は(オンラインだけど)いつもワイワイやっていてとても雰囲気がよく、自分も気持ちよく仕事させてもらっている。

所感

集中力・モチベーションが上がらないと感じる時期があった

これは自分でも原因がよくわからない、かつ今も復活したかと言われると怪しいんだけど
2月頃からなんとなくしんどいと感じることが多かった。
具体的には、なんとなく朝働くぞという気持ちに切り替えられなかったり、あんまり大した成果出せてないなと感じたり、その割に仕事終わりに謎の疲れがあったり、などなど。

色々考えたけど結局なんでなのかはよくわからず。ただ自分が今こういう状況であることが自覚できたことはよかった。
在宅勤務にも慣れて、なんなら家の環境整えたし通勤なくなったしリモート快適じゃん!ぐらいに思ってたけど、そうでもないんだなと。

それから、こういった状況にあることを社内で何人かと話す機会があって、それでずいぶん楽になったこともあったので、当たり前だけど人と話すのって大事だなあと。
自分はうまくいってないときとか気分が乗らないときに人とのコミュニケーションを避ける傾向にある気がするので、このへんは気をつけたい。

大学進学について

コンピューターサイエンスの学位を取るために大学に行きたいという欲は依然としてある。
今までは University Of People(UoPeople)しか可能性がないと思っていたが、放送大学という選択肢もあることがわかった。

2月にこちらの記事を読んで、大変参考になった。

今の気持ちとしては

  • 「いつか金銭的・時間的に余裕ができたら行きたい」だとそんなタイミングは一生来ないので、動き出したほうがいいと思う
  • 一方で、大学に行く目的はもう少し整理したい
    • 体系だったカリキュラムで学べること、学んだことの証明が得られることが主な理由だが、具体的に何を学びたいのか?
  • 英語学習の必要性も感じているので、自分には UoPeople が良いと思ってる

という感じ。それもあって先月末に TOEFL を受験したりしてた。
TOEFL iBT Home Editionを受験した - dackdive's blog

UoPeople のカリキュラムや単位移行、入学手続きの方法などをきちんと調べるのが次のアクションになりそう。

次の四半期(2021年4〜6月)のテーマ

  • 「ゼロからのOS自作入門」を読む!
    • 当面はこれかなー。1,2ヶ月かもっとかかる気がしている...
  • Exercism 継続する
  • UoPeople への入学について必要なこと調べ終える
  • みかん本が終わったら「Rustで始めるTCP自作入門:ひつじ技研」とか読みたいな

TOEFL iBT Home Editionを受験した

タイトルの通り、TOEFL iBT が自宅で受験できる Home Edition というものを最近受験した。
TOEFL 自体も初めてだったので、Home Edition 受験の様子や TOEFL iBT 自体についてこんな感じでしたというのをメモしておく。

TOEFL iBT Home Edition について

より引用。

TOEFL®テスト主催団体ETSでは、新型コロナウイルス感染防止の対応策として、自宅受験「TOEFL iBT® Home Edition」を提供しています。 TOEFL iBT® Home Editionは、通常の TOEFL iBT®テストと同じ内容、フォーマット、画面のテストをProctorU®の試験監督者によるオンライン監視の下、自分のパソコンを使用して自宅で受験します。週に4日24時間受験でき、最短で申込完了の翌日に受験することが可能です。

通常テストセンターで受験する TOEFL と同じ内容のテストが自宅で受験できるというもの。
お手軽に受験できて便利、と思って勢いで申し込んだんだけど、実際に受験環境の条件などチェックするとけっこう厳しい制約があるなーと感じた。

具体的には、自分が印象に残ってるところだと以下のような点がある。

  • 以下 2 つを PC にインストールしないといけない
  • 受験環境の制約がきつい
    • ヘッドセットやイヤホンは使用不可
      • なので周囲への音漏れが気になる場合は厳しい
    • メモを取ることは認められているが、通常のノートは不可でホワイトボード or 透明なシートプロテクターに入れた紙のみ OK
    • 机周りだけでなく、部屋全体見渡して疑われそうなものは撤去しておく必要がある
    • 試験開始時に試験監督者に向けて、部屋全体をカメラで映し何もないことを証明する必要がある
    • ※モニターはOKかどうかわからなかった。自分は使わなかったら使わなかったで画面をなにかで覆うよう指示された

詳しくは

を一読することをオススメ。

また、試験開始時の流れは

  • ETS Secure Test Browser を立ち上げておく
  • Chrome 拡張をインストールした Chrome または Firefox で、試験用 URL にアクセスする
  • ProctorU 経由で試験監督者がログインしてくる(テレビ会議みたいな感じ)
  • そのまま指示に従い、身分証明書の提示や部屋の様子をカメラ越しにチェックする
  • 試験監督者が自分のPCを遠隔操作して、ETS Secure Test Browser にログイン名・パスワードを入力しログインする
  • 試験開始

こんな感じ。
試験監督者は、自分の場合は海外の方だったので指示は全部英語。

試験中はずっと試験監督者の監視があるが、何もなければ試験監督者からは何も言われないので試験に集中できる。
(自分は一度チャットで「顔が見えなくなったから確認させて」って言われた)

TOEFL iBT の試験内容・出題形式について

日本語だとこのページに概要がまとまっている。

Reading, Listening, Speaking, Writing の4つのセクションに分かれており、各30点満点、合計120点満点。
テスト時間は約3時間とあるが、Home Edition の場合最初のセットアップやガイダンスがあるので3時間半ぐらいは見込んだ方がいい。

また、

というページの「詳しく見る」のリンク先で、セクションごとの詳しい出題形式が動画で説明されているので、ざっとでも一度目を通した方がいい。

ページと動画は残念ながら英語のみ。動画1本あたり5分前後だけど、だいたい出題形式の説明は前半2,3分で終わって後半はサンプル問題や模範解答になってるので、そこまで時間はかからない。

あるいは、自分は気づくのが遅れて受けられなかったが、無料模擬テストもある。

結果と感想

f:id:dackdive:20210407174506p:plain

3/29に受験してスコアが公開されたのが4/7。

わかってはいたけど Speaking が他と比べて悪い。 Performance Descriptors for the TOEFL iBT® Test によれば Speaking でこの点は Low-Intermediate(5段階で真ん中)なのでやはり力を入れないといけない部分なんだろう。
試験の手応えとしても全然話せてる感じがしなかった。

この点数がどれくらいのレベルなのかわからないが、目安にしていた University of People(海外のオンライン大学)の英語要件によれば 71 点なのでそこはクリア。

English Proficiency | University of the People

ちなみにジョージア工科大学だと 90 点以上が必要。厳しい。

English Proficiency Requirements | Grad Studies | Georgia Institute of Technology | Atlanta, GA

感想としては、とにかくめちゃくちゃ疲れた。
TOEIC と同じ感覚で臨んで、しかも 21時スタートとかにしてたので集中力が保たなかった。
次回受けるとしたら土日か平日半休とって受けたい。

あとテスト内容としてもTOEICと比べてはるかに難しい。
Reading, Listening は感覚としては TOEIC の最後の方の難しいやつだけ出される感じ。
90点には8点足らないけど、正直これよりスコア伸ばせる気はしない。

ともあれまた英語学習再開しようかな(しないとヤバいな)という気持ちになれたので良し。

Software Design 2021年3月号の「WebAssembly入門」を読んだ

rust-jp の Slack で知り、読んでみました。
3 ~ 4 時間でさらっと読めるボリューム。

全体的に手を動かす成分はそこまで多くなかった印象で、読み物として面白かった。
以下、各章の感想など。

第1章 WebAssemblyの登場の経緯と利用方法

自分のこれまでの知識を復習しつつ、歴史的経緯などは知らなかった部分も多かった。
WAT のフォーマットについては以前読んだこちらの記事が良かった。

WebAssemblyハンズオン: 実際に動かして基礎を学ぶ(翻訳)|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社

また、印象的だったのはWasmの用途のところで

現代のブラウザが JavaScript を高速に実行するためには、大まかに「(最適化をあまりしないので)早く実行開始できるけど実行速度は遅い処理系」や「実行速度は速いけど(最適化に時間がかかるので)実行開始までが遅い処理系」といった、複数の方式の処理系を使い分けることが多いです。その関係で実行速度を制御しづらく、ブレが生じやすいのです。一方で Wasm はそうした実行時の最適化をあまり伴わない分、処理速度が比較的安定していることも、リアルタイム処理に採用される一因なのではないかと推測されます。

という記述があったが、Wasm は処理速度が安定しているという話は以前 Front-End Study でも聞いたなあ。

www.youtube.com

f:id:dackdive:20210324001919p:plain

(動画 1:14:00 あたりから)

第2章 Wasmで広がる Webアプリの可能性

元々多言語で作られたものを Wasm を通して Web アプリ上で動かす、の例として FFmpegImageMagick のデモが紹介されていた。
コードもあってわかりやすい。

第3章 WASIとProxy-Wasm

Wasm をブラウザ外で使うために定められた仕様の話だったり、ブラウザ外 Wasm の一例として Proxy-Wasm やそのリファレンス実装である Envoy の話など。

Proxy-Wasm 気になってたので概要だけでも理解できてよかった。

ちょうど先週末の Kernel/VM 探検隊 というイベントでも関連セッションがあったみたいで、スライドも見た。

www.slideshare.net

あと記事の著者は昨年末の WebAssembly night #10 - connpass でも Proxy-Wasm について登壇されてた。(観てなかったの思い出した)

メモ

読みながらとったメモ。

## 第1章

- Wasmの起源は asm.js (2013年3月 Mozilla)
    - JSには任意のオブジェクトに対しビット単位演算子OR `|` を適用すると強制的に整数に変換するという特性がある
    - これを生かして、 `foo | 0` は整数型の式とみなす、などのルールを設けることで、JSを静的型付け言語となるかのように制限する
    - このような前提を設けることで、Cなどのプログラミングからasm.jsに変換する

- asm.js はJSと高い互換性があるので多くのブラウザでサポートされた一方、プリミティブな命令群を生成する関係上コード量が増える、増えると構文解析に時間がかかるといった問題があった
- Wasmはasm.jsの問題を解決
    - バイトコードでサイズを小さく構文解析が簡単に
    - プラス、より安全にするためにJSの環境から分離させる
- WAT
    - `wat2wasm` WAT → Wasm 変換ツール。WebAssembly Binary Toolkit に含まれる
    - S式: `(宣言 .... 宣言の内容....)`
- WasmはJSを置き換えるか→NO
    - 現代の多くのプログラミング言語は重厚なランタイムが必要で、それをWasmにコンパイルするとサイズが大きくなる
    - Wasm からブラウザの API を直接呼ぶことはできなずJSのAPI経由で間接的に呼ぶことになる。ので Wasm ←→ JS 間の呼び出しオーバーヘッドがある
    - Wasm の公式見解としても、Wasmがコアのロジック担当、JSがそれとHTML/CSSベースのGUIとの橋渡し役になる、という役割分担を想定して仕様を策定しているらしい
- Wasm は速度が安定しているみたいな話
    - JSは「(最適化をあまりしないので)早く実行開始できるけど実行速度は遅い処理系」や「実行速度は速いけど(最適化に時間がかかるので)実行開始までが遅い処理系」といった、複数の方式の処理系を使い分けることが多い。その関係で実行速度を制御しづらくブレが生じやすい
    - Wasm meetup で聞いたかも
- Wasm 活用事例
    - Unity は 2018.1 で Wasm 対応
    - C言語からの移植
        - FFmpeg
        - md4c というmarkdown parser

### Wasm に触れる

- wasm-pack: Rust で作成したWasmのモジュールをフロントエンドやNode.jsなどのプロジェクトで使いやすいようビルドしてくれるツール
    - コンパイルしたWasmモジュールやwasm-bindgenが生成したラッパーをnpmパッケージとして使えるようにしてくれる
- wasm-bindgen
    - Wasm で定義した関数は通常整数や浮動小数点数といった単純な値しかやり取りできない
    - 文字列や構造体、配列といった値を扱うには「線形メモリ」と呼ばれるWasmモジュール専用の領域に書き込む必要がある
    - そのへんの面倒を担ってくれるラッパーを生成する
- "`crate-type = ["cdylib"]` は、このプロジェクトが動的リンクされるライブラリとしてビルドすることを宣言している"
    - 🤔
- `wasm-opt` (wasm-packが内部で使ってる)
    - [https://github.com/rustwasm/wasm-pack/issues/886](https://github.com/rustwasm/wasm-pack/issues/886) は wasm-bindgen v0.2.70 で fixed
        - "エクスポートする関数が文字列を返す関数となっていた場合、これが必要ですのでご注意ください。"

## 第3章 WASIとProxy-Wasm

- なぜブラウザ上でWasmが動くの?
    - Wasm そのものはスタックベースのVMおよびそこで実行されるバイナリの仕様で、ブラウザとは関係ない
    - ブラウザがWasmのVMを実装しているので動かせる、というわけ
    - Wasmの実行環境のことを「ホスト環境」と呼ぶ
- WasmはどうやってJSやブラウザと連携してるの?
    - Wasm の仕様にある Import section, Export section, Host function がカギ
    - Import section
        - Wasm のバイナリの一部
        - そのプログラムに対して外部から提供されるリソースを記述したもの
        - 例:グローバル変数や関数
        - import 時にJS側で渡すやつかな
    - Export section
        - 同じくバイナリの一部
        - プログラムの中でほかのWasmのバイナリやホスト環境で利用可能なものを記述したもの
        - `#[wasm_bindgen]` つけた関数とかかな
    - Host function
        - ホスト環境側で定義した関数を Wasm にリンクした際のオブジェクトのこと

    ### WASI

    - 複数のホスト環境それぞれに共通する、システムコールに対応するHost functionの仕様を策定したもの
        - 例:Wasmのプログラムからファイルを読み書きしたり、現在時刻を取得するためのHost function
    - WASI ランタイム:WASIを実装した、Wasmを実行可能なホスト環境のプログラム
        - Lucet, Wasmtime, Wasmer, WAVM, SSVM など
    - Wasm の応用例:Krustlet
        - Kubernetes上でコンテナとしてWasmのバイナリを実行する環境を提供するプロジェクト
        - "通常のコンテナを利用する場合と比べ、WASI を利用してユーザーのプログラムを実行した場合、システムコールはプロセスが走っている OS カーネルではなく WASI ランタイムのレイヤに発行されるため、プロセスとしての分離性が高くよりセキュアであると考えることができます"

### Proxy-Wasm

- 背景となるEnvoy Proxy
    - オープンソースのプロキシサーバ
    - Service Mesh(ざっくり言うと、マイクロサービスのネットワーク通信をプロキシ経由にする)のData Plane(そこで使われるプロキシ)
- Envoyはプロキシサーバなので個別ユースケースが無数に存在する
    - そのためプラグイン機構が存在するが、以下のような課題もあった
        - C++ で開発しなければ いけない」「Private なユースケースの場合は、 Envoy の Upstream へマージすることはできない ので、ユーザーが独自ビルド&メンテナンスを しなければいけない」「拡張機能追加後にすべて の Envoy のプロセスを入れ替えなければならな い」
    - "「複数言語をサポート」「Envoy 自体の独自ビ ルドは必要ない」「Envoy のプロセスを止めるこ となく動的にプラグインを Load 可能」「堅牢な セキュリティ」などの要求を満たした拡張機構の 開発に乗り出しました。"
- Proxy-Wasm
    - Wasm のプログラムによりプロキシを拡張するための VM とホスト環境の間の ABI の仕様策定やSDK開発を目的としたプロジェクト
    - WASIと独立しているが、実質的にWASIの一部APIと各種プログラミング言語のWASI向けのツールチェインに依存しているため、WASIのサブプロジェクトになるのではという話もある
- Proxy-Wasm の仕様と実装
    - 双方向的なのが特徴。WASIはWasmの中からホスト環境へ発行するHost functionの仕様なので一方向
    - 例:VM→ホスト環境
        - `proxy_send_http_response`
    - 例:ホスト環境→VM
        - `proxy_on_request_headers`
- Proxy-Wasm の課題
    - ホスト環境の実装の難しさ
        - Wasm のプログラムによってホスト環境がクラッシュしないようにしないといけない
        - 悪意のある Wasm バイナリがロードされるかもしれない
    - パフォーマンス:50%ほどネイティブと比較して遅い
    - VMの実装の選び方

「Let's build a browser engine!」を読んでRustで簡易レンダリングエンジンを作った

Rust を勉強したらやってみたいなと思ってた記事。

記事について

タイトルの通り、簡単なブラウザのレンダリングエンジンを Rust で作る、という趣旨の記事。
著者は Servo という Mozilla が開発しているレンダリングエンジンのチームに所属(していたらしい。現在は不明)。

最終的にできるもの

たとえば、以下のような HTML と CSS

<html>
  <head>
    <title>Test</title>
  </head>
  <p class="inner">
    Hello, <span id="name">world!</span>
  </p>
  <p class="inner" id="bye">
    Goodbye!
  </p>
</html>
* {
  display: block;
}

span {
  display: inline;
}

html {
  width: 600px;
  padding: 10px;
  border-width: 1px;
  margin: auto;
  background: #ffffff;
}

head {
  display: none;
}

.outer {
  background: #00ccff;
  border-color: #666666;
  border-width: 2px;
  margin: 50px;
  padding: 50px;
}

.inner {
  border-color: #cc0000;
  border-width: 4px;
  height: 100px;
  margin-bottom: 20px;
  width: 500px;
}

.inner#bye {
  background: #ffff00;
}

span#name {
  background: red;
  color: white;
}

をパースし、↓ のような画像を出力できるようになる。

見てわかるように、テキストの描画には対応していない。

また最終的に画像を出力する部分はライブラリ(image クレート)を使用しておりスコープ外。それと、実際のブラウザウィンドウができるわけではない。

やってみた感想

レンダリングエンジンのしくみについては以前 Google

を読んでいたので、全体的な流れについては理解していたが、このときの記憶をおさらいする感じで読めたのでよかった。
また、特に後半の Style tree から Layout tree を構築して最終的にピクセル単位の情報(どこの座標は何色で描画するか)にするまでの処理はこちらの方が詳しく書かれており、勉強になった。

あと、この記事は昨年の秋頃に読もうとして Rust 全然わからなくてすぐに挫折したんだけど、今だと書いてあることは読めるぐらいにはなっていて数ヶ月前よりは成長を実感できた。
相変わらず所有権まわりというか、どういうときに参照のままで計算してよくてどういうときにムーブしたらいいのか、とか、ここで参照外しする必要あるの?とかはよくわかってない。

ソースコードはここに置いてある。

github.com

元のコードと比べて、2018 edition で一部変更が必要だったところ、image クレートの最新バージョンでうまく動かないところなどは修正してある。
またテストが全くなかったので Part 4 ぐらいまではテストを追加しており、参考になるかもしれない。

学び

レンダリングの流れ

記事中の画像を引用する。

大きな流れは

  • 最初に HTML と CSS をそれぞれパースし DOM と CSS の Rule 群を構築する
  • DOM と Rules から Style tree(DOM にスタイル情報が付与されたような木構造)を構築する
  • Style tree から Layout tree(要素がブロック要素かインライン要素か...という情報と、コンテンツの矩形領域の大きさ)を構築する
  • Layout tree から描画コマンドのリストを作成し、描画

となっている。

Part2: HTML のパース

特に言うことなし。HTML 文字列をパースして DOM を構築する。
記事中のパーサーは文字列をいきなり最終的な DOM に変換しており Lexer(文字列を Token に変換するやつ)は登場しなかった。

Part3: CSS のパース

DOM と異なり、こちらは木構造ではなくただのリストになっている(型で書くと Vec<Rule>)。
記事中のパーサーは simple selector のみサポートしており、複数のセレクターを >+ などの結合子でつなげたものはサポートしていない。
ref. 結合子 - ウェブ開発を学ぶ | MDN

Part4: Style tree

Style tree は DOM の各要素にスタイル情報を付与したようなもの。
コードではスタイル情報は

/// Map from CSS property names to values.
type PropertyMap = HashMap<String, Value>;

/// A node with associated style data.
#[derive(Debug, PartialEq)]
pub struct StyledNode<'a> {
    pub node: &'a Node, // pointer to a DOM node
    pub specified_values: PropertyMap,
    pub children: Vec<StyledNode<'a>>,
}

というようにハッシュマップで持たせていた。

また、この specified_values を構築する処理は

/// Apply styles to a single element, returning the specified values.
fn specified_values(elem: &ElementData, stylesheet: &Stylesheet) -> PropertyMap {
    let mut values = HashMap::new();
    let mut rules = matching_rules(elem, stylesheet);

    // Go through the rules from lowest to highest specificity.
    rules.sort_by(|&(a, _), &(b, _)| a.cmp(&b));
    for (_, rule) in rules {
        for declaration in &rule.declarations {
            values.insert(declaration.name.clone(), declaration.value.clone());
        }
    }
    values
}

という関数でやってるんだけど、複数のルールが該当したときに詳細度が高いものが勝つように一度詳細度の低い順に並び替えてから順次ハッシュマップに insert、ということをやっていた(declaration.name が重複してるものは後の方で上書きされる)。

Part5, 6: Layout tree

Style tree から Layout tree を計算する。Layout tree は

struct LayoutBox<'a> {
    dimensions: Dimensions,
    box_type: BoxType<'a>,
    children: Vec<LayoutBox<'a>>,
}
  • box_type: 要素がブロック要素かインライン要素か(または none)
  • dimensions.content: コンテンツの x, y 座標と、縦横の大きさ
  • dimensions.padding, border, margin: padding, border, margin のそれぞれの上下左右幅

といった情報を持った木構造

DevTools でよく見るこのボックスをイメージするとわかりやすい。

ここに、矩形左上の点の x, y 座標が追加されているイメージ。

ここの座標計算が一番複雑で、かつ元々このあたりの知識がなく理解が難しかったポイント。

  • ブロック要素は縦方向に並び、幅は親 (container) の幅いっぱいになる
  • インライン要素は横方向に並び、親の幅を超えそうになったら折り返す

という基本的なことに加え、ブロック要素とインライン要素が混在していた場合、インライン要素群のまわりに Anonymous Box と呼ばれるブロック要素が形成されるらしい。

(画像は記事より引用)

また、要素の幅に関しては親に依存するが、高さはその要素が包含する子要素の高さに依存する。
そのため、子要素の幅・高さを計算する際に親要素の高さに子の分を足し合わせるといった処理が必要になる。
それは layout_block_children() でやっている。

Part 7: Painting

"Rasterization"(日本語だと "ラスタライズ"?)と呼ばれる描画処理。
Layout tree から直接描画するのではなく、一旦 "円を描け" "テキスト文字を描け" のような描画に関するコマンド( DisplayCommand )のリストを作り、最後にそのリストの通りに順番に描画する。

なぜ一旦コマンドに変換するかというと、いくつかの理由がある。

  • 後続のコマンドで完全にカバーされるコマンドを検索し、取り除いて無駄な描画を排除できる
  • 一部の要素しか変更されていないことを知っている場合、コマンドのリストを変更したり再利用できる
  • 同じコマンドリストから異なるアウトプット(たとえばスクリーン用にピクセル、プリンタ用にベクタ)を生成できる

("Building the Display List" の項を参照)