Denoの現状と展望
Denoが登場したのは2018年の6月ころですが、2019年の1年間でDenoは大きく進歩しました この記事ではこの1年間でDenoがどのように進歩したか、また2020年はどうなるのかを語ります
継続的なリリース
この記事の執筆時(2019/12/4)時点ではDenoの最新リリースはv0.25.0です Denoは2018/12 ~ 2019/12の1年間で計45のバージョンがリリースされました(v0.2.5 ~)
現在はほぼ一週間に一度のリリースサイクルでリリースされています
Denoはこの1年アクティブに開発され、常に改善され続けています
v0.4に上がった頃から、Weekly Releaseは常にマイナーアップに切り替わり、これはv1に至るまで続く予定です
ネットワーク関連の安定/TLSのサポート
今年の1月から3月にかけてDenoのネットワーク機能、特にDeno.listenの機能が不安定でした
現在は解消され相当に安定しています
また、v0.19.0からDeno.dialTLS、v0.22.0からDeno.listenTLSが実装され、TLS接続がクライアント、サーバーともにサポートされました
自作のHTTPモジュールであるServestもすでにTLSに対応済みで、HTTPSのサーバーを立てることが可能です TCPベースのHTTPSのKeep-Aliveコネクションエージェントも作成することができます
ビルドシステムがgnからcargoに完全移行
これも地味なのですが大きいニュースでした
v0.18.0からDenoのビルドシステムがそれまで使っていたGNからRustのデフォルトビルドシステムであるCargoに移行されました
Gnが何かというとGoogleしか使いこなせないNinjaという汎用ビルドシステムのファイルを生成するビルドシステムです
これはとにかく闇が深すぎて誰もビルドシステムに手を付けられなくなっていました
現在はGn由来ののビルドスクリプトは消え、cargo build, cargo testなどのコマンド一発でDenoをビルドできるようになりました
Deno開発を始める上ではこれは大きな改善で、闇の深いsetup.pyのようなファイルが軒並み消えてくれました
Denoのコアに興味のある人はgit cloneした後Cargoでビルドしてみるといいでしょう。最新のrustupが必要です。 メッセージシリアライザがFlatBuffersからJSON+SharedArrayBufferに移行
DenoはfsやnetなどのOS由来の機能をTypeScriptから使うためにRust側にOp(Operation)と呼ばれるマイクロメッセージを送信し、その結果をPromiseで受け取ることで実装されています
Denoの最初期にはメッセージのシリアライザはProtocolBuffersが使われていましたが、そのうちにFlatBuffersに移行されました
しかしFlatBuffers自体のシリアライズ処理がボトルネックになり得るのではないかという議論が立ち上がり、傍からはよくわからないままに独自形式のものに移行しました
この仕組みはdeno_coreと呼ばれており、Opごとに以下の2つのどちらかが使われています
SharedArrayBuffer
op_read, op_writeなどインテンシティの高いOpで使われる
Rust側で静的に確保した数百バイト程度のメモリをV8のSharedArrayBufferにわりあて、Queueとして使う
主な利点は、シリアライズ処理が発生しないので、理論上はオーバーヘッドがゼロという点
欠点としては、生のメモリを使うのでバグる危険性がある…ていうか初期はバグってた。ことと、サイズが固定されているのでQueueから溢れたメッセージは後述のJSON形式で送られるので、Opの並列性が高まると意味がなくなる点。
とはいえ、通常時は相当に効率的にOpが処理されています
JSON
FlatBuffersで定義していたメッセージをJSONに移行しただけです
個人的にはこの移行にパフォーマンス上でどんな意味があったのかよく分かっていません
FlatBuffersのシリアライズがJSONより遅いというのはにわかには信じがたいのですが…
開発はしやすくなりました。多分
deno_stdが本体にマージ
これはビッグニュースでしょう
deno_stdはもともと「net」というリポジトリとして去年の12月に誕生し、いつの間にかdeno_stdという標準ライブラリ候補になっていきました v0.21.0からdeno_stdは本体のリポジトリにマージされ、リリースサイクルが共有されました
現在はコアもstdも同じリポジトリで管理されるようになりました
deno_stdはそのほぼ全てがRyan以外のコントリビューターのコードで出来ており、この一年の最も大きな進歩といえるでしょう
RustのことはよくわからないけどTypeScriptは好き、という人はstdからコントリビュートを始めることをおすすめします
僕も基本的にはstdに多くコントリビュートをしています
webサイトが新生
それまでWebサイトは静的なHTMLで管理されていましたが、今年10月ころにReact + Netlifyというモダンなスタックに移行されました これも突然のことだったので真意はよくわかりませんが、サイトは速くなったようです
このサイト自体はもともとモジュールのレジストリとしても機能しているのですが、
このバージョンからコードにドキュメントの自動生成機能が付きました
内部的にはTypeScript Compiler APIを使ってJSDoc形式のコメントを頑張って解析してReactに表示しているようです
ちょこちょこパースに失敗してるコードもあるけどね…
JSXがサポートされた
俺!俺!俺がやったからコレ!
v0.20.0から、DenoはJSX/TSXファイルを実行できるようになりました
TypeScriptにはもともとJSXをコンパイルする機能があるのですが、Denoでは有効にする手段が有りませんでした
個人的にはサーバーサイドJSXの有用性は知っていたのでずっと使いたいと思っていました
でもイシューを探しても誰もJSXに言及していなかったので、結局自分でイシューを立てて自分で実装することになりました
hashrockさんのこの記事はちょいバズりだった模様です
海外の人からも多くのいいねなどをもらったので個人的には今年一番の成果かなと思ってます
DenoでJSXを使うためには(とりあえず)Reactを使う必要あるのですが、React自体がJSで書かれていることもあってDenoで使うにちょっと癖があるのが実情です
CIがGithub Actionsに移行
以前はTravisCIとAppVeyorを使っていましたが、移行後からCIの実行時間が明らかに短くなったようです
内容というより、プラットフォームごとの並列実行が効率的になったからですね
wasmサポート
DenoではWebAssembly(wasm)ファイルをimportして実行することができます
code:ts
import { fib } from "./fib.wasm";
console.log(fib(20));
これ、ちょっと凄いことなんじゃないでしょうか
GoやRustをはじめとした別の言語で生成したwasmコードをDenoではこんなに簡単に使うことができます
※ 現在wasmのimport自体にprivilegeは必要ありませんが、これは実質的にDenoのサンドボックスを破壊するので、個人的にはwasmのimportには専用のprivilege --allow-wasmが必要なんじゃないかと思っています
サブコマンドの追加
denoのCLIにいくつかの便利なサブコマンドが追加されました
deno fmt
denoのコードを自動整形してくれます。めちゃ便利。
内部的にはPrettierを利用しています
deno bundle
denoのコードをバンドリングしてくれます。
URL importなどもすべて解決して一つのJSファイルにしてくれるので、ブラウザでも実行できるようになります
Denoオブジェクトに依存しているコードは落ちてしまいますが
deno test
go testに影響を受けたテストランナーが実装されました
実行したディレクトにある*_test.tsファイルをすべてimportしたjsを作成し、stdのrunTestsを実行するようです
deno install
リモートのdenoファイルをCLIとしてインストールするコマンドです
例えば自作のモジュールマネージャであるdinkはこうすることでインストールできます code:ts
これでdinkファイルが~/.deno/bin/dinkに作成されます
コレはもともと/deno-ja/syumai.iconさんが第一回Deno会で作っていたdenogetというツールで、形を変えてstdに入りました
さらに細分化したpriviledge
--allow-read, --allow-write, --allow-netにホワイトリストの設定ができるようになりました
--allow-read=/deno/src/info.json, --allow-write=/tmpのような形で特定のファイル、ディレクトリのみにたいするアクセスを許可することが出来るようになりました
--allow-netはlisten/dial両方のネットワークアクセスに対してホワイトリストを設定できます
サーバーを起動する場合は--allow-net=0.0.0.0:8000など
fetchなどで外部サイトにアクセスする場合は--allow-net=deno.land,127.0.0.1:8080などのようにhostname形式で指定できます
Lockfile
v0.23.0から依存モジュールのintegrity check機能が入ったらしいです
code:ts
deno fetch main.ts --lock=lock.json --lock-write
これで何かしらが起こるらしいのですが、全然使い方がわかりません
package-lock.jsonやyarn.lockのようなものを目指しているらしいのですが…
ネイティブプラグインがサポート間近
これは以前から議論されていた、.soや.dylibなどの動的リンクファイルをDenoから実行時に読み込めるようにしようという機能です
グラフィック系やDBドライバなどはこの機能を使わないと実現できないことが沢山あるので、これが実現するとDenoはNode.jsと同様既存のC++/Cのコードの資産を利用できるようになります
今はEmscripten経由のwasmを使うという手段もありますが、これらがどう差別化されていくのか興味深いです
気になるプラグインの作り方ですが、基本的にはRust専用になるようです
C++などのコードはRustのFFIを利用して使うのかな?
まだはっきりわからないのですが、/deno-ja/kt3k.iconさんによると、
まずRustでこのような登録処理と、ハンドラーの設定を行います
code:plugin.rs
// ハンドラーの登録処理
fn init(context: &mut dyn PluginInitContext) {
context.register_op("test_io_sync", Box::new(op_test_io_sync));
}
// ハンドラー本体
pub fn op_test_io_sync(data: &u8, zero_copy: Option<PinnedBuf>) -> CoreOp { if let Some(buf) = zero_copy {
let data_str = std::str::from_utf8(&data..).unwrap(); let buf_str = std::str::from_utf8(&buf..).unwrap(); println!(
"Hello from native bindings. data: {} | zero_copy: {}",
data_str, buf_str
);
}
let result = b"test";
let result_box: Buf = Box::new(*result);
Op::Sync(result_box)
}
これをrustcでビルドしlinuxなら.so、maxなら.dylib形式の動的リンクファイルにします
これらをTypeScript側から開き、Rust側に登録したハンドラーと同じ名前のハンドラーの.dispatchに関数を呼び出すことで返り値を受け取るようです
code:plugin.ts
const plugin = Deno.openPlugin(ビルドしたpluginのパス);
const test_io_sync = plugin.ops.test_io_sync;
const response = test_io_sync.dispatch(
);
これらは同期的関数、Promiseベースの非同期関数の両方の呼び出しがサポートされているようで、実際なんでも出来るようになる気がしています
Node polyfillが実装開始
現在、既存のNode.js(CommonJS)のコードをDeno上で実行可能にするためのPolyfillの作業が始まっています
基本方針としては、DenoにrequireとNode.jsの標準ライブラリ(fs, child_process)をpolyfillし、それらに依存したコードをDenoで実行可能にするというもののようです
当然Node.jsのfsやchild_processの中身はNode.jsなので、その中身の実装をAPIはそのままでDenoで再実装することになると思われます
これはNode.jsのコードをそのままでランタイムをDenoへ乗り換えやすくするための施策だと思われます
ちなみに僕も最近そのまったく逆の、Node.jsにDenoをpolyfillする実験を進めています
DenoのAPIは、基本的にDenoグローバル変数にまとめられているので、互換性で言えばDenoのほうがNode.jsに優しく作られています。これはもともとDenoがブラウザとの互換性を目指すという方針を理由とするもので、Denoオブジェクトに加えfetchやURLなどのいくつかのWHATWG由来のAPIを別途polyfillすれば、DenoのコードがNode.jsで動くことも検証できています
このように、今DenoとNode.js、そしてブラウザJSの互換性を高める作業が進められています
これは後発であるDenoが、Node.jsと共存する上でやはり必須の施策であると言えます
Node.jsはすでに過去最大規模のプログラミングランタイムになった現状が有り、いくらオリジナルの作者が「作り直す!」と言い出してもすべてをひっくり返すことは現実的ではありませんし、それを目的とはしていません
しかし一方でDenoが解決しようとしているNode.jsの設計上の問題点のツケは何らかの形で改善しないといけない
起動したらやりたい放題のセキュリティ設計
複雑怪奇でECMA標準から取り残されたrequireの挙動
捨てられない標準ライブラリのコールバック地獄
近年はfs.promiseやutil.promisifyなどで多少マシになってきているとはいえ…
ESMに完全に舵を切れなかった結果生まれた.mjsという悲しき拡張子
そういう中でDenoがランタイムとしてほぼNode.jsと同じ機能を提供する上で優位性を示すには、Node.jsからの移行コストが低いことが大前提になると思います
現状、ブラウザ用ライブラリの多くはDenoで動くので、Node.jsとDenoの互換性が高まって日によってランタイムを変えるくらいカジュアルにDenoとNodeを切り替えられるようになるといいなと思っています
僕がDenoのpolyfillを作り始めたのは、仕事などでNode.jsプロジェクトにDenoを入れるのは流石になぁ…と思ったことがきっかけで、まずはAPIだけでもDenoを使ってみるというきっかけを増やしたかったからです
DenoのAPIデザインはNode.jsよりも間違いなく優れており、ちょっとしたスクリプトを書くのにとても使いやすくなっています
hr.icon
現状と展望
このように多岐にわたる進歩がDenoにはありました
では実際、Denoがプロダクションレディになるのは一体いつなのか?
それはぶっちゃけ使う人次第だと思っています
Node.jsはv0.0.1から初の正式メジャーリリースであるv4.0.0が出るまで実に6年の歳月を要しました
v1が出るまでProduction Readyでないという理屈が成立するのであれば、Node.jsは2015年までそうでなかったと言えます
Denoがいつv1に到達するのか?それはまだはっきり分かっていません
Ryanは春頃今年の7月に出したいと言っていました(夏頃は12月に出したいと言っていました)
僕はどちらも「いや無理だろ…」と思っていたので、12月になった今でもまだv1に到達できないと考えています
v1に必要な機能
Denoではv1到達に必要な機能、というのがまとまっています
これによると現状は80~85%程度と言えそうです
個人的には、ここに書いてある機能はWebアプリケーションを作ったりする上で「必須の機能」だとは思いません
バッチ処理のスクリプトを書いたりWebサイトを立ち上げるくらいの用途であれば、DenoはすでにProduction Readyです
2020年は……
二人目の踊り手を待つにも書いたのですが、とにかくここから先は踊りだす人が増えないことにはDenoは流行りません 流行るのを待っていては流行らない!
どんな小さなことでもいいので、2020年はぜひDenoを使い始めてみてください
日本語情報は /deno-ja にまとまっていますし、ここ からSlackにjoinすることもできます 2020年はDenoの年だ!!!