Edited at

BinaryASTでJavaScriptのロードを高速化


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コードとそれに関連付けられたデータ構造をバイナリ形式で表現します。

これによりクライアントでの上記パース作業を簡略化しようという仕組みです。

モバイル端末などのローエンドなクライアントほど効果が出ます。


  • 適用前



  • 適用後




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


hoge.htmlをブラウザで表示

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

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

constをvarに変えて再度エンコード。デプロイして再確認。

やっとできました。ちゃんとBinary表現になっていますね。結果も正しく動いています。


まとめ

情報が少ない中、何とかここまでたどり着けました。

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

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

https://serve-binjs.that-test.site/

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


2019/06/07 追記

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