web-dev-qa-db-ja.com

クロージャーコンパイラーとNode.jsをうまく再生する

Node.jsとclosure-compiler(略してCC)を一緒に使用したプロジェクトはありますか?

CCの公式の推奨事項は、アプリケーションのすべてのコードをまとめてコンパイルすることですが、require("./MyLib.js")を含む単純なnode.jsコードをコンパイルすると、その行は出力に直接配置されますが、作成されません。その文脈での任意の意味。

いくつかのオプションがあります。

  1. アプリケーション全体を単一のファイルとしてコーディングします。これはそれを回避することで問題を解決しますが、メンテナンスには不向きです。
  2. 実行前にすべてのファイルが連結されると想定します。この場合も、問題は回避されますが、コンパイルされていないデバッグモードの実装が難しくなります。
  3. CCにnode.jsのrequire()関数を「理解」させたいのですが、コンパイラ自体を編集せずにそれを行うことはおそらくできないでしょう。
27
bukzor

まだリリースしていないプロジェクトでClosureCompilerをNodeで使用しています。少しのツールが必要ですが、多くのエラーをキャッチするのに役立ち、編集もかなり短いです-再起動-テストサイクル。

まず、Closureコンパイラ、ライブラリ、およびテンプレートを一緒に使用するために、 plovr (これは私が作成および保守したプロジェクトです)を使用します。 Nodeコードをクロージャーライブラリのスタイルで記述しているので、各ファイルは独自のクラスまたはユーティリティのコレクション(_goog.array_など)を定義します。

次のステップは、使用したいNode関数のexternファイルの束を作成することです。これらのいくつかを次の場所で公開しました:

https://github.com/bolinfest/node-google-closure-latitude-experiment/tree/master/externs/node/v0.4.8

最終的には、文書化する機能がたくさんあるので、これはもっとコミュニティ主導のものになるはずだと思います。 (一部のNode関数には、最後の引数ではなくオプションの中間引数があり、型の注釈が複雑になるため、これも煩わしいです。)作業を行うことができる可能性があるため、この動きを自分で開始していません。クロージャーコンパイラーを使用して、これをより厄介なものにします(以下を参照)。

Node名前空間httpのexternsファイルを作成したとします。私のシステムでは、httpが必要なときはいつでもそれを含めることにしました。 :

_var http = require('http');
_

そのrequire()呼び出しをコードに含めていませんが。代わりに、クロージャーコンパイラの_output-wrapper_機能を使用して、ファイルの先頭にすべてのrequire()を追加します。これは、plovrで宣言すると、現在のプロジェクトでは次のようになります。

_"output-wrapper": [
  // Because the server code depends on goog.net.Cookies, which references the
  // global variable "document" when instantiating goog.net.cookies, we must
  // supply a dummy global object for document.
  "var document = {};\n",

  "var bee = require('beeline');\n",
  "var crypto = require('crypto');\n",
  "var fs = require('fs');\n",
  "var http = require('http');\n",
  "var https = require('https');\n",
  "var mongodb = require('mongodb');\n",
  "var nodePath = require('path');\n",
  "var nodeUrl = require('url');\n",
  "var querystring = require('querystring');\n",
  "var SocketIo = require('socket.io');\n",
  "%output%"
],
_

このように、私のライブラリコードはノードのrequire()を呼び出すことはありませんが、コンパイラはそれらをexternとして認識するため、コードでのhttpなどの使用を許容します。それらは真の外部ではないので、私が説明したようにそれらを前に付ける必要があります。

最終的に、これについて話し合った後 ディスカッションリストで 、より良い解決策は、次のような名前空間の新しい型注釈を付けることだと思います。

_goog.scope(function() {

    /** @type {~NodeHttpNamesapce} */
    var http = require('http');

    // Use http throughout.

});
_

このシナリオでは、externsファイルはNodeHttpNamespaceを定義し、ClosureCompilerがexternsファイルを使用してプロパティをタイプチェックできるようにします。ここでの違いは、httpの型はこの特別な名前空間型になるため、require()の戻り値に任意の名前を付けることができることです。 (_$_の「jQuery名前空間」を特定することも同様の問題です。)このアプローチにより、Node名前空間のローカル変数に一貫して名前を付ける必要がなくなり、 plovr構成のその巨大な_output-wrapper_。

しかし、それは余談でした...上記のように設定すると、次のようなシェルスクリプトが作成されます。

  1. Plovrを使用して、すべてをRAWモードでビルドします。
  2. Plovrによって生成されたファイルに対してnodeを実行します。

RAWモードを使用すると、すべてのファイルが大きく連結されます(ただし、SoyテンプレートやCoffeeScriptをJavaScriptに変換することもできます)。確かに、行番号が意味をなさないため、これはデバッグを面倒にしますが、これまでのところ十分に機能しています。クロージャーコンパイラーによって実行されるすべてのチェックは、それだけの価値があります。

50
bolinfest

クロージャーコンパイラのsvn HEADは AMDのサポート

6
Jauco

以前のアプローチをより単純なアプローチに置き換えました。

新しいアプローチ

  • Nodeモジュールの場合のみ、require()は自分のアプリコードを呼び出す必要はありません
  • 実行またはコンパイルする前に、サーバーコードを単一のファイルに連結する必要があります
  • 連結とコンパイルは 単純なうなり声のスクリプト を使用して行われます。

面白いことに、require()呼び出しにexternを追加する必要さえありませんでした。 Google Closureコンパイラは、それを自動的に理解します。私は 私が使用するnodejsモジュールのexternを追加する必要がありました。

古いアプローチ

OPの要求に応じて、Google ClosureCompilerを使用してnode.jsコードをコンパイルする方法について詳しく説明します。

私はbolinfestが問題を解決した方法に触発され、私の解決策は同じ原理を使用しています。違いは、モジュールのインライン化を含むすべてを実行する1つのnode.jsスクリプトを作成したことです(bolinfestのソリューションではGCCがそれを処理します)。これにより、自動化が進みますが、壊れやすくなります。

サーバーコードをコンパイルするために実行するすべてのステップにコードコメントを追加しました。このコミットを参照してください: https://github.com/blaise-io/xssnake/commit/da52219567b3941f13b8d94e36f743b0cbef44a

要約する:

  1. メインモジュールである、実行するときにNode)に渡すJSファイルから始めます。
    私の場合、このファイルは start.js です。
  2. このファイルでは、正規表現を使用して、割り当て部分を含むすべてのrequire()呼び出しを検出します。
    start.jsでは、これは1つのrequire呼び出しに一致します:var Server = require('./lib/server.js');
  3. ファイル名に基づいてファイルが存在するパスを取得し、その内容を文字列としてフェッチし、内容内のmodule.exports割り当てを削除します。
  4. 次に、手順2のrequire呼び出しを手順3の内容に置き換えます。コアnode.jsモジュールでない限り、後で保存するコアモジュールのリストに追加します。
  5. ステップ3にはおそらくより多くのrequire()呼び出しが含まれるため、すべてのrequire()呼び出しがなくなり、すべてのコードを含む1つの巨大な文字列が残るまで、ステップ3と4を再帰的に繰り返します。
  6. すべての再帰が完了したら、REST APIを使用してコードをコンパイルします。
    オフラインコンパイラを使用することもできます。
    すべてのコアnode.jsモジュールにexternがあります。 このツールはexternの生成に役立ちます
  7. 削除されたcore.jsモジュールのrequire呼び出しをコンパイル済みコードにプリプリメントします。

コンパイル済みのコード。
すべてのrequire呼び出しが削除されます。私のコードはすべてフラット化されています。
http://Pastebin.com/eC2rVMiN

コンパイル後のコード。
Node.jsコアのrequire呼び出しが手動で追加されました。
http://Pastebin.com/uB8CaejN


このようにすべきではない理由:

  1. require呼び出しを検出し、module.exportsをインライン化および削除するために、正規表現(パーサーまたはトークナイザーではない)を使用します。これは、すべての構文バリエーションを網羅しているわけではないため、脆弱です。
  2. インライン化すると、すべてのモジュールコードがグローバル名前空間に追加されます。これは、すべてのファイルに独自の名前空間があるNode.jsの原則に反しており、同じグローバル変数を持つ2つの異なるモジュールがある場合にエラーが発生します。
  3. V8はインライン化やデッドコードの削除などの多くのコード最適化も実行するため、コードの速度はそれほど向上しません。

なぜあなたがすべきか:

  1. 一貫性のあるコードがある場合に機能するためです。
  2. 詳細な警告を有効にすると、サーバーコードのエラーが検出されます。
3
Blaise

Node.jsのクロージャーライブラリを60秒で。

サポートされています。チェックしてください https://code.google.com/p/closure-library/wiki/NodeJS

2