7
1

More than 5 years have passed since last update.

BinaryASTでJavaScriptのロードを高速化

Last updated at Posted at 2019-05-23

TL; DR

JavaScriptファイルをプリコンパイルしておける機能。
ただし、まだ実験段階なので2019年5月時点で実用は出来ません。
サポートしているのはFirefox Nightly版のみ。

BinaryASTとは

BinaryASTは、JavaScriptの初回ロードを高速化します。
初回ロードの高速化というと、ダウンロード速度の方をイメージしてしまいますが、
ダウンロード後(あるいはキャッシュ読み込み後)のブラウザでのパース作業を高速化します。

ASTについて

ブラウザで実行する通常のJavaScriptコードの場合、ソースはコードの構文構造を記述する抽象構文木(Abstract Syntax Tree, AST)と呼ばれる中間表現に解析されます。
この表現はバイトコードまたはネイティブのマシンコードにコンパイルして実行することができます。

生のJavaScript
const hoge = 3 + 4;
AST表現されたJavaScript
{
    "type": "Program",
    "body": [
        {
            "type": "VariableDeclaration",
            "declarations": [
                {
                    "type": "VariableDeclarator",
                    "id": {
                        "type": "Identifier",
                        "name": "hoge"
                    },
                    "init": {
                        "type": "BinaryExpression",
                        "operator": "+",
                        "left": {
                            "type": "Literal",
                            "value": 3,
                            "raw": "3"
                        },
                        "right": {
                            "type": "Literal",
                            "value": 4,
                            "raw": "4"
                        }
                    }
                }
            ],
            "kind": "const"
        }
    ],
    "sourceType": "script"
}

JavaScriptコードからASTへの変換は以下のページで試せます。
http://esprima.org/demo/parse.html

高速化の仕組み

BinaryASTは、元のJavaScriptコードとそれに関連付けられたデータ構造をバイナリ形式で表現します。
これによりクライアントでの上記パース作業を簡略化しようという仕組みです。
モバイル端末などのローエンドなクライアントほど効果が出ます。

  • 適用前
    image.png

  • 適用後
    image.png

pre-encode

エンコーダーはRustで実装されているようです。試しにエンコードしてみます。
お試しなのでサクッとDockerで実行します。

Rustインストール
docker run --rm -it ubuntu
apt-get update
apt-get upgrade
apt-get install curl
curl https://sh.rustup.rs -sSf | sh
1を選択
1) Proceed with installation (default)
cargoにパスを通す
source ~/.cargo/env
nodejsとnpmをインストール
apt-get install nodejs npm
binastを取ってくる
apt-get install git
git clone https://github.com/binast/binjs-ref
エンコーダーとデコーダー
cd binjs-ref
cargo run --bin binjs_encode -- --help
cargo run --bin binjs_decode -- --help

ここまでで環境準備は整いました。
エンコードを試してみます。

hoge.js
const hoge = 3 + 4;
console.log(hoge);
エンコード実行
cargo run --bin binjs_encode -- -i hoge.js -o output
cd output
ll
total 28
drwxr-xr-x  2 root root 4096 May 19 04:33 ./
drwxr-xr-x 15 root root 4096 May 19 04:33 ../
-rw-r--r--  1 root root  379 May 19 04:33 hoge.binjs
-rw-r--r--  1 root root   10 May 19 04:33 hoge.grammar
-rw-r--r--  1 root root   39 May 19 04:33 hoge.js
-rw-r--r--  1 root root   10 May 19 04:33 hoge.strings
-rw-r--r--  1 root root   10 May 19 04:33 hoge.tree

binjsファイルが出来ていますね。

hoge.binjs
BINJS[GRAMMAR]identity;(IdentifierExpression0LiteralNumericExpression(AssertedDeclaredName2AssertedScriptGlobalScope BinaryExpression"BindingIdentifier CallExpression&ExpressionStatement
    Script,StaticMemberExpression&VariableDeclaration$VariableDeclarator[STRINGS]identity;P
                                                                                          hogeconsolelog+
constconst lexical[TREE]identity;Z                                                                                                                                                  @@

デコーダーも試してみましょう。

デコード実行
cargo run --bin binjs_decode -- ./output/hoge.binjs decode_hoge.js

スペースや改行は消されましたが、戻っていますね。
行末のセミコロンも消されているので、minifyされているんでしょうか。

decode_hoge.js
const hoge=3+4;console.log(hoge)

ついでにダンプも実行してみます。

ダンプ実行
cargo run --bin binjs_dump -- ./output/hoge.binjs

ダンプ結果
stdout
Attempting to decode as multipart.

    0 : 10                      # Script {
      :                         # .scope
    1 : 06                      # AssertedScriptGlobalScope {
      :                         # .declaredNames
    2 : 02                      # list (length=1) [
    3 : 04                      # AssertedDeclaredName {
      :                         # .name
    4 : 00                      # string="hoge"
      :                         # .kind
    5 : 0a                      # string="const lexical"
      :                         # .isCaptured
    6 : 00                      # bool=false
      :                         # }
      :                         # ]
      :                         # .hasDirectEval
    7 : 00                      # bool=false
      :                         # }
      :                         # .directives
    8 : 00                      # list (length=0) []
      :                         # .statements
    9 : 04                      # list (length=2) [
   10 : 14                      # VariableDeclaration {
      :                         # .kind
   11 : 08                      # string="const"
      :                         # .declarators
   12 : 02                      # list (length=1) [
   13 : 16                      # VariableDeclarator {
      :                         # .binding
   14 : 0a                      # BindingIdentifier {
      :                         # .name
   15 : 00                      # string="hoge"
      :                         # }
      :                         # .init
   16 : 08                      # BinaryExpression {
      :                         # .operator
   17 : 06                      # string="+"
      :                         # .left
   18 : 02                      # LiteralNumericExpression {
      :                         # .value
   19 : 00 00 00 00 00 00 08 40 # float=3
      :                         # }
      :                         # .right
   27 : 02                      # LiteralNumericExpression {
      :                         # .value
   28 : 00 00 00 00 00 00 10 40 # float=4
      :                         # }
      :                         # }
      :                         # }
      :                         # ]
      :                         # }
   36 : 0e                      # ExpressionStatement {
      :                         # .expression
   37 : 0c                      # CallExpression {
      :                         # .callee
   38 : 12                      # StaticMemberExpression {
      :                         # .object
   39 : 00                      # IdentifierExpression {
      :                         # .name
   40 : 02                      # string="console"
      :                         # }
      :                         # .property
   41 : 04                      # string="log"
      :                         # }
      :                         # .arguments
   42 : 02                      # list (length=1) [
   43 : 00                      # IdentifierExpression {
      :                         # .name
   44 : 00                      # string="hoge"
      :                         # }
      :                         # ]
      :                         # }
      :                         # }
      :                         # ]
      :                         # }

サーバーへの配置

binast-cf-worker を見る感じ、リクエストヘッダーのAcceptapplication/javascript-binastが含まれていれば、Content-Typeapplication/javascript-binastを指定した.binjsファイルを返せば良さそうです。

上記のサンプルでは、クライアント判定NGの場合は通常の.jsファイルを返すようにしていますが、今回はお試しなので、.binjsファイルなら無理やりapplication/javascript-binastにしてみます。

また、httpsでないとbinASTは有効にならないようですので、ローカルでテストするときは注意。
とりあえずオレオレ証明書で回避しておきます。

docker run --rm --name binjstest -v ~/output:/usr/share/nginx/html:ro -d -p 8080:80 nginx
docker exec -it binjstest bash

apt-get update
apt-get install openssl
openssl genrsa 2048 > server.key
openssl req -new -key server.key > server.csr
openssl x509 -days 3650 -req -signkey server.key < server.csr > server.crt
mkdir /etc/nginx/ssl
mv server* /etc/nginx/ssl

apt-get install vim
vim /etc/nginx/conf.d/default.conf

vim /etc/nginx/mime.types

nginx -s reload
/etc/nginx/conf.d/default.conf
server {
    listen 80 ssl;
    server_name localhost;

    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_certificate /etc/nginx/ssl/server.crt;
         :
/etc/nginx/mime.types
types {
    text/html                                        html htm shtml;
    text/css                                         css;
    text/xml                                         xml;
    image/gif                                        gif;
    image/jpeg                                       jpeg jpg;
    application/javascript                           js;
    application/javascript-binast                    binjs;
         :
~/output/hoge.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <script type="text/javascript" src="hoge.binjs"></script>
  </head>
  <body>
  </body>
</html>

クライアント準備

現在BinaryASTをサポートしているのはFirefox Nightly版のみとのことで、インストールします。

about:configにて以下を設定します。
dom.script_loader.binast_encoding.domain.restrictfalse
dom.script_loader.binast_encoding.enabledtrue
image.png

hoge.htmlをブラウザで表示

以下のエラー。えぇ、プレビュー版はconstは使えないのかよ!

SyntaxError: BinAST Parsing Error: Const is not supported in this preview release     hoge.binjs

constをvarに変えて再度エンコード。デプロイして再確認。
やっとできました。ちゃんとBinary表現になっていますね。結果も正しく動いています。
image.png

image.png

image.png

まとめ

情報が少ない中、何とかここまでたどり着けました。
今回は短いソースなので、ファイルサイズも逆に大きくなってしまいましたが、次回は大きなファイルでどれだけ変わるかやってみようと思います。(そこが重要)

ちなみに、公式デモサイトである下記は、正しくmime-typeを返していない為binjsになっていないようです。(2019/05/23)
https://serve-binjs.that-test.site/

image.png

image.png

参考元: https://blog.cloudflare.com/binary-ast/

2019/06/07 追記

公式デモサイトが修正されたようで、正しくbinjsで取得されるようになりました。

image.png

image.png

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