AMD、CommonJS、および ES Harmony を使用してモジュラー JavaScript を書く

Tweet

Modularity The Importance Of Decoupling Your Application

アプリケーションがモジュラーであるというとき、一般的には、モジュール内に格納されている高度に切り離され異なる機能部分のセットからなることを指します。 おそらくご存知のように、疎結合は、可能な限り依存関係を排除することにより、アプリケーションの保守性を容易にします。

しかし、いくつかの伝統的なプログラミング言語とは異なり、現在の JavaScript (ECMA-262) は、開発者にコードのモジュールをクリーンで組織的な方法でインポートする手段を提供しません。 これは、より組織化された JavaScript アプリケーションの必要性が明らかになった近年まで、あまり考慮する必要のなかった仕様に関する懸念事項の 1 つです。 これらの多くでは、モジュール スクリプトは、名前空間が単一のグローバル オブジェクトによって記述され、DOM 内でつなぎ合わされています。

これらの問題に対するネイティブなソリューションは ES Harmony で登場しますが、良いニュースは、モジュール式 JavaScript を書くことがかつてないほど簡単になり、今日から実行できるようになったことです。 AMD、CommonJS、および JavaScript の次期バージョンである Harmony への提案です。

Prelude A Note On Script Loaders

AMD と CommonJS モジュールについては、部屋の中の象、スクリプト ローダーについて話さないと難しいのですが、ここでは、スクリプト ローダーについて説明します。 現時点では、スクリプト ローダーは目標への手段であり、その目標は、今日のアプリケーションで使用できるモジュール化された JavaScript です – このためには、互換性のあるスクリプト ローダーの使用は残念ながら必要です。 この記事を最大限に活用するために、一般的なスクリプト ローダーがどのように動作するかについて基本的な理解を深め、モジュール形式の説明が文脈上意味をなすようにすることをお勧めします。 これらのツールに関する完全なチュートリアルはこの記事の範囲外ですが、John Hann の curl.js に関する投稿と James Burke の RequireJS API ドキュメントを読むことをお勧めします。

生産の観点から、これらのモジュールを扱う場合、最適化ツール (RequireJS optimizer など) を使用してスクリプトを連結して展開することが推奨されます。 興味深いことに、Almond AMD シムを使用すると、RequireJS は展開されたサイトでロールバックする必要がなく、スクリプト ローダーと考えられるものは、開発以外の場所に簡単に移動させることができます。 AMD (Asynchronous Module Definition) フォーマットの全体的な目標は、開発者が今日使用できるモジュラー JavaScript のためのソリューションを提供することです。 これは、XHR+eval を使用した Dojo の実際の経験から生まれ、このフォーマットの支持者は、過去のソリューションの弱点に苦しむ将来のソリューションを避けたいと考えていました。 これは、非同期であることや、コードとモジュール ID の間にありがちな緊密な結合を取り除く本質的に非常に柔軟であることなど、多くの明確な利点を有しています。 多くの開発者がこれを使用しており、ES Harmonyで提案されているモジュールシステムへの信頼性の高い足がかりとして考えることができます。

AMD は CommonJS リストでモジュール形式のドラフト仕様として始まりましたが、完全な合意に達することができなかったので、この形式のさらなる開発は amdjs グループに移りました。 CommonJS AMD 形式という用語は、時折野生の中で見られますが、CJS リストのすべての参加者がそれを追求することを望まなかったので、単に AMD または非同期モジュール サポートとして参照することが最善です。

Getting Started With Modules

ここで知っておくべき 2 つの重要な概念は、モジュール定義を容易にする define メソッドと依存関係のロードを処理する require メソッドの考え方です。 define は、次のシグネチャを使用して、提案に基づいて名前付きまたは名前なしのモジュールを定義するために使用されます:

インライン コメントでわかるように、module_id はオプション引数で、通常、非 AMD 連結ツールを使用している場合にのみ必要です (他にも役に立つエッジ ケースがあるかもしれません)。 この引数を省略した場合、モジュールを anonymous と呼びます。

匿名モジュールを扱う場合、モジュールの ID の考え方は DRY であり、ファイル名とコードの重複を避けるのは些細なことです。 コードはより移植性が高いため、コード自体を変更したり ID を変更したりする必要なく、他の場所 (またはファイルシステム周辺) に簡単に移動することができます。 module_id は、単純なパッケージやパッケージで使用されていない場合のフォルダーパスと同等です。 また、r.js などの CommonJS 環境で動作する AMD オプティマイザを使用するだけで、複数の環境で同じコードを実行することができます。

define シグニチャに戻ると、dependencies 引数は定義するモジュールによって必要とされる依存関係の配列を表し、第3引数 (「定義関数」) はモジュールをインスタンス化するために実行される関数となります。 ベアボーンモジュールは、次のように定義することができます。

Understanding AMD: define()

require 一方、トップレベルの JavaScript ファイルまたはモジュール内でコードをロードするために、動的に依存関係を取得したい場合に一般的に使用されます。 使用例は次のとおりです:

Understanding AMD: require()

動的にロードされる依存関係

Understanding AMD: plugins

以下は、AMD 互換プラグインの定義の例です:

Note: しかし、css! が含まれていますが、このアプローチには、CSS が完全にロードされたときに確立できないなど、いくつかの注意点があることを覚えておくことが重要です。 ビルドのアプローチによっては、最適化されたファイルに CSS が依存関係として含まれることになるため、そのような場合に CSS をロードされた依存関係として使用するのは注意が必要です。js

Loading AMD Modules Using curl.js

Modules With Deferred Dependencies

Why Is AMD A Better Choice For Writing Modular JavaScript?

  • How to approach flexible modulesの定義を明確に提案している。
  • 私たちの多くが依存している現在のグローバル名前空間および <script> タグのソリューションよりもかなりクリーンです。
  • モジュール定義はカプセル化されており、グローバル名前空間の汚染を避けるのに役立つ。
  • いくつかの代替ソリューション (例: CommonJS、これはまもなく見ていきます) よりもうまく機能する。 クロスドメイン、ローカル、またはデバッグの問題がなく、使用するサーバー側ツールへの依存がない。 ほとんどの AMD ローダーは、ビルド プロセスなしでブラウザでのモジュールのロードをサポートします。
  • 1 つのファイルに複数のモジュールを含めるための「トランスポート」アプローチを提供します。 CommonJS のような他のアプローチは、トランスポート フォーマットについてまだ合意していません。
  • これが必要な場合、スクリプトを遅延ロードすることが可能です。 上記のように、モジュールの依存性を配列で最初の引数として定義し、依存性が読み込まれた時点でモジュールを実行するコールバック (ファクトリー) を提供します。g:
    define(, function( Tooltip ){ //Our dijit tooltip is now available for local use new Tooltip(...);});

    今、Dojo 非同期ローダー、RequireJS またはあなたが使い慣れた標準 dojo.require() モジュール ローダーによって消費できるモジュールの匿名の性質に注意してください。 モジュールを参照する AMD が推奨する方法は、マッチする引数のセットと依存関係リストでそれらを宣言しますが、これは Dojo 1.6 ビルドシステムではサポートされていません – それは本当に AMD に準拠したローダーでのみ機能します。g:

    define(, function( cookie, Tooltip ){ var cookieValue = cookie("cookieName"); new Tree(...); });

    これは、モジュールがもはや毎回完全な名前空間を直接参照する必要がないため、 ネストされた名前空間よりも多くの利点があります – 必要なのは依存関係の ‘dojo/cookie’ パスだけで、いったん引数にエイリアスが付けば、その変数で参照することが可能です。 これは、アプリケーションで ‘dojo.’ を繰り返しタイプアウトする必要性を取り除きます。

    注意: Dojo 1.6 はユーザーベースの AMD モジュール (および非同期ロード) を公式にサポートしていませんが、多くの異なるスクリプトローダーを使用して Dojo でこれを動作させることが可能です。 現在、すべての Dojo コアおよび Dijit モジュールは AMD 構文に変換され、改善された全体的な AMD サポートはおそらく 1.7 と 2.0 の間に着地するでしょう。

    注意すべき最後の混乱は、Dojo ビルド システムを使い続けたい場合、または古いモジュールをこの新しい AMD スタイルに移行したい場合は、以下のより詳細なバージョンによって移行が簡単に行えるようになることです。 dojo と dijit も依存関係として参照されることに注意してください。

    AMD モジュール デザイン パターン (Dojo)

    デザイン パターンの利点に関する私の以前の投稿のいずれかに従っていれば、共通の開発問題に対するソリューションを構造化して取り組む方法を改善する上で非常に有効であることをご存知だと思います。 ジョン・ハンは最近、シングルトン、デコレーター、メディエーターなどをカバーする AMD モジュールのデザイン パターンについて、素晴らしいプレゼンテーションを行いました。 もし機会があれば、彼のスライドをチェックすることを強くお勧めします。

    Decorator パターンのいくつかのサンプルを以下に示します。

    Adapter パターン

    AMD Modules With jQuery

    The Basics

    Dojo とは異なり、jQuery には本当に 1 つのファイルだけが付属しますが、ライブラリのプラグイン ベースという性質を考慮して、それを使用する AMD モジュールを定義するのにいかに簡単であるかが、以下により実証されました。

    しかし、この例では何かが欠けており、それは登録の概念です。

    Registering jQuery As an Async-compatible Module

    jQuery 1.7 で採用された主要機能の1つは、jQuery を非同期モジュールとして登録するためのサポートでした。 jQuery の人気の結果、AMD ローダーは、理想的には複数の異なるバージョンを同時にロードしたくないので、同じページにロードされるライブラリの複数のバージョンを考慮する必要があります。 ローダーは、この問題を特に考慮するか、または、サード パーティ製スクリプトとそのライブラリに既知の問題があることをユーザーに指示するかのいずれかを選択できます。

    これが機能する方法は、採用されているスクリプト ローダーが、プロパティ define.amd.jQuery が true に等しいことを指定することにより、複数の jQuery バージョンをサポートしていることを示すことです。 より具体的な実装の詳細に興味がある方は、AMD の define() メソッドを使用する可能性がある他のファイルと連結されるリスクがあるため、名前付きモジュールとして jQuery を登録しますが、匿名の AMD モジュール定義を理解する適切な連結スクリプトは使用されません。

    名前付き AMD は、ほとんどのユースケースで堅牢かつ安全であるという安全ブランケットを提供します。

    Smarter jQuery Plugins

    JQuery プラグインが Universal Module Definition (UMD) のパターンを使用してどのように書かれるかといういくつかのアイデアと例をここで最近議論しました。 UMD は、クライアントとサーバーの両方で動作し、現在利用可能なすべての一般的なスクリプト ローダーで動作するモジュールを定義します。 これはまだ新しい分野で、多くの概念がまだ確定していませんが、以下の AMD && CommonJS のセクション タイトルのコード サンプルを自由に見て、私たちがより良くできることがあると感じたら教えてください。

    どのスクリプト ローダー & フレームワークが AMD をサポートしていますか?

    In-browser:
    Server-side:
    • RequireJS http://requirejs.org
    • PINF http://github.com/pinf/loader-js

    AMD まとめ

    以上は AMD モジュールがどれほど有用であるかについての非常につまらない例でしたが、それらがどのようにして働くかを知る基礎を提供できればと思っています。

    現在、多くの目に見える大規模なアプリケーションや企業が、アーキテクチャの一部として AMD モジュールを使用していることに興味を持たれるかもしれません。 IBM や BBC iPlayer など、このフォーマットが企業レベルの開発者によっていかに真剣に検討されているかを浮き彫りにしています。

    CommonJS A Module Format Optimized For The Server

    CommonJS はボランティアのワーキング グループで、JavaScript API を設計、試作、標準化することを目的としています。 今日まで、彼らはモジュールとパッケージの両方の標準を批准しようとしてきました。 CommonJS のモジュール提案は、サーバサイドでモジュールを宣言するためのシンプルな API を指定し、AMD とは異なり、io、ファイルシステム、約束事など、より広い関心事をカバーしようと試みています。

    はじめに

    構造の観点から、CJS モジュールは、依存するコードで利用可能になる特定のオブジェクトをエクスポートする、再利用可能な JavaScript のピースです。

    高レベルでは、モジュールは基本的に 2 つの主要な部分を含みます。それは、モジュールが他のモジュールに利用可能にしたいオブジェクトを含む exports という自由変数と、モジュールが他のモジュールのエクスポートをインポートするために使用できる require 関数です。

    Understanding CJS: require() and exports

    Basic consumption of exports

    AMD-equivalent Of the First CJS Example

    Consuming Multiple Dependencies

    app.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.js
    bar.js
    exports.name = 'bar';
    foo.js
    require('./bar');exports.helloWorld = function(){ return 'Hello World!!''}

    どのローダー & Frameworks が CJS をサポートしているか?

    In-browser:
    • curl.js http://github.com/unscriptable/curl
    • SproutCore 1.1 http://sproutcore.com
    • PINF http://github.com/pinf/loader-js
    • (and more)
    Server-side:

    Is CJS Suitable For the Browser?

    CommonJS がサーバーサイドの開発に適していると感じている開発者もいます。これは、Harmony 以前の時代において、今後どのフォーマットが事実上の標準として使われるべきか、使われるかということについて、現在意見が分かれている理由の 1 つでもあります。 CJS に対するいくつかの議論には、多くの CommonJS API が、JavaScript でブラウザレベルで単純に実装できないようなサーバー指向の機能を扱うという注釈があります – たとえば、io、system、js はその機能の性質上、実装不可能と見なされます。 クライアントとサーバーの両方でアプリケーションを持つモジュールには、検証、変換、およびテンプレート化エンジンが含まれます。 一部の開発者は、サーバーサイドの環境でも使えるモジュールはCJSを選び、そうでない場合はAMDを使うという方法で、どちらの形式を使うかを選択しているようです。

    AMD モジュールはプラグインを使用することができ、コンストラクターや関数などのより詳細なものを定義できるので、これは理にかなっています。 CJS モジュールはオブジェクトしか定義できないので、オブジェクトからコンストラクターを取得しようとすると、作業が面倒になります。

    この記事の範囲外ですが、AMD と CJS について議論するとき、異なるタイプの ‘require’ メソッドが言及されていることに気づいたかもしれません。

    同様の命名規則に対する懸念はもちろん混乱で、コミュニティは現在、グローバルな require 関数のメリットについて意見が分かれています。 John Hann のここでの提案は、グローバルと内側の require の違いについてユーザーに知らせるという目的をおそらく達成できないであろう ‘require’ と呼ぶよりも、グローバル ローダー メソッドを何か別の名前 (たとえば、ライブラリの名前) に変更する方がより理にかなっているかもしれない、ということです。 curl.js のようなローダーが require ではなく curl() を使用するのは、このような理由からです。

    AMD && CommonJS Competing, but Equally Valid Standards

    この記事は CJS より AMD を使うことを強調していますが、実際には両方の形式が有効で用途があることは事実です。

    AMD は開発に対してブラウザ ファーストのアプローチを採用し、非同期動作と簡略化された後方互換性を選択しますが、ファイル I/O の概念はありません。 オブジェクト、関数、コンストラクター、文字列、JSON、その他多くのタイプのモジュールをサポートし、ブラウザでネイティブに実行されます。 9258>

    一方、CommonJS はサーバーファーストのアプローチをとり、同期的な動作を仮定し、John Hann が言うようにグローバルな荷物はなく、(サーバー上で)将来に対応しようとします。 このことは、CJSがラップされていないモジュールをサポートしているので、AMDが強制するdefine()ラッパーから解放され、ES.next/Harmonyの仕様にもう少し近く感じることができることを意味します。

    さらに別のモジュール形式という考えは難しいかもしれませんが、ハイブリッド AMD/CJS および Univeral AMD/CJS モジュールに関する作業のいくつかのサンプルに興味を持たれるかもしれません。

    Basic AMD Hybrid Format (John Hann)

    AMD/CommonJS Universal Module Definition (Variation 2, UMDjs)

    Extensible UMD Plugins With (Variation by me and Thomas Davis)。

    core.js
    myExtension.js
    ;(function ( name, definition ) { var theModule = definition(), hasDefine = typeof define === 'function', hasExports = typeof module !== 'undefined' && module.exports; if ( hasDefine ) { // AMD Module define(theModule); } else if ( hasExports ) { // Node.js Module module.exports = theModule; } else { // Assign to common namespaces or simply the global object (window) // account for for flat-file/global module extensions var obj = null; var namespaces = name.split("."); var scope = (this.jQuery || this.ender || this.$ || this); for (var i = 0; i 
    app.js
    $(function(){ // the plugin 'core' is exposed under a core namespace in // this example which we first cache var core = $.core; // use then use some of the built-in core functionality to // highlight all divs in the page yellow core.highlightAll(); // access the plugins (extensions) loaded into the 'plugin' // namespace of our core module: // Set the first div in the page to have a green background. core.plugin.setGreen("div:first"); // Here we're making use of the core's 'highlight' method // under the hood from a plugin loaded in after it // Set the last div to the 'errorColor' property defined in // our core module/plugin. If you review the code further down // you'll see how easy it is to consume properties and methods // between the core and other plugins core.plugin.setRed('div:last');});

    ES Harmony Modules Of The Future

    TC39, ECMAScript とその将来の繰り返しの文法とセマンティクスを定義する責任を負う標準団体は、多くの非常に賢い開発者によって構成されています。 これらの開発者の一部 (Alex Russell など) は、過去数年間、大規模な開発における JavaScript の使用の進化に目を光らせており、よりモジュール化された JS を記述するためのより良い言語機能の必要性を痛感しています。 このセクションでは、ES.next のモジュールの構文のコード サンプルをいくつか紹介し、今後何が起こるかを体験していただきます。

    注意:Harmony はまだ提案段階ですが、Google の Traceur コンパイラーにより、モジュラー JavaScript を書くためのネイティブ サポートに対応した ES.next の機能をすでに(一部)試してみることができます。 Traceurを1分以内に使いこなすには、このスタートガイドを読んでください。 また、このプロジェクトについてもっと学びたい場合は、JSConf プレゼンテーションを見る価値があります。

    Modules With Imports And Exports

    AMD と CJS モジュールに関するセクションを読み終えたら、モジュールの依存関係 (imports) とモジュールのエクスポート (or, public API/variables we allow other modules to consume) という概念に馴染んでいることでしょう。 ES.nextでは、これらの概念は、importキーワードを使用して依存性を指定することで、より簡潔な方法で提案されています。

    • import 宣言は、モジュールのエクスポートをローカル変数としてバインドし、名前の衝突/衝突を避けるために名前を変更することができます。 興味深いことに、モジュールは子モジュールをエクスポートすることができますが、他の場所で定義されているモジュールをエクスポートすることはできません。

    Modules Loaded From Remote Sources

    モジュール提案は、外部の場所からモジュールをロードするのを簡単にするリモートベースのモジュール (サードパーティ API ラッパーなど) にも適用できます。

    Module Loader API

    提案されているモジュール ローダーは、高度に制御されたコンテキストでモジュールを読み込むための動的 API を記述しています。 ローダーでサポートされているシグネチャには、モジュールをロードするための load( url, moduleInstance, error) createModule( object, globalModuleReferences)、およびその他があります。 最初に定義したモジュールを動的にロードする別の例を示します。

    Loader.load('http://addyosmani.com/factory/cakes.js', function(cakeFactory){ cakeFactory.oven.makeCupcake('chocolate'); });

    CommonJS-like Modules For The Server

    サーバー指向の開発者のために、ES.next で提案されているモジュール システムは、ブラウザでモジュールを見ることだけに制限されるわけではないことに注意してください。 下記はその例で、サーバーで使用するために提案されたCJSのようなモジュールを見ることができます。

    // io/File.jsexport function open(path) { ... };export function close(hnd) { ... };
    module lexer from 'compiler/LexicalHandler';module stdlib from '@std'; //... scan(cmdline) ...

    Classes With Constructors, Getters & Setters

    クラスの概念は、純粋主義者と常に論争の的になってきました。これまで、私たちは JavaScript のプロトタイプ的性質に立ち戻るか、同じプロトタイプ的動作に脱線した形でクラス定義を使用できる枠組みや抽象化を使用するかのいずれかで対処してきました。

    Harmony では、クラスはコンストラクターや(最終的に)真のプライバシーの感覚とともに、言語の一部として提供されます。 以下の例では、クラスがどのように構成されているかを理解するために、いくつかのインライン コメントを含めましたが、ここで「関数」という単語がないことにもお気づきでしょう。 これは誤字脱字ではありません。 TC39 は、あらゆるものに対する function キーワードの乱用を減らすために意識的に努力しており、これによってコードの書き方を単純化できるようになればと願っています。

コメントを残す

メールアドレスが公開されることはありません。