この記事の執筆時点では、JavaScriptには公式のモジュラーシステムはなく、誰もができる限りモジュールをエミュレートしていました。
モジュールまたは同様の構造は、成人向けプログラミング言語の不可欠な部分です。 他に方法はありません モジュールを使用すると、スコープを制限したり、アプリケーションの一部を再利用したり、アプリケーションをより構造化したり、リソースをノイズから分離したり、一般的にコードをより見やすくしたりできます。
JavaScriptには独自の雰囲気があります-言語には公式のモジュールはありません。さらに、すべてのファイルはリモートにあり、1つのアプリケーションストリームです。 ダウンロードに関するいくつかの奇妙な問題を常に解決し、ダウンロード時間を短縮するためにモジュールを1つのファイルに巧妙にパックする必要があります。 二重の標準と戦い、異なる形式のモジュールを適応させる必要がある場合があります。
実際、彼らは「DOMをパッチ」するだけでなく、巨大なプロジェクトをJavaScriptで行うことができるとは考えていなかったため、モジュールについては考えていませんでした。 はい、将来については考えませんでした。 そして、突然、未来が来ました! すべてがすでにそこにあるようで、JavaScriptのモジュールは、控えめに言っても遅いです。 したがって、開発者はある種のモジュラーエミュレータをスピンして発明する必要があります。
多くの人が優れた記事「Addy Osmani Writing Modular JavaScript With AMD、CommonJS&ES Harmony 」を読んだことがあると思います。これは彼の著書「JavaScriptデザインパターンの学習 」の章の1つになり、 : 「古い」モジュールについての詳細2010。
これらの記事を翻訳したり、それらを寄せ集めたりするつもりはありません。 私の記事では、モジュラーパスについて説明します。 「古い」モジュールから「新しい」モジュールへの移行方法と、現在使用しているものとその理由について。
この記事は3つのパートで構成されています:モジュールパスウェイ、モジュールガッツの一致部分、およびモジュールの一般的なタイプ
tl; dr
モジュールパス
ステージ1:モジュールなし
JavaScriptコードがほとんどなかった当時、私はモジュールを完全に省きました。 それから私はそれらを必要としませんでした。 モジュラーシステムを導入すると、50行のコードが150行になります。また、モジュールなしでDOMにすばやくパッチを適用できました。 私は名前空間で完全に管理し、アセンブリを使用しませんでした。そして、ミニファイアーはそのとき開発されませんでした。
モジュール
MyNs.MyModule = function () {}; MyNs.MyModule.prototype = { // ... };
組立
<script src="myNs.js"/> <script src="myNs/myModule.js"/>
catを使用してファイルを収集し始めたとき、私のアプリケーションの進捗はさらに半歩進みました。
$ cat js/*.js > build.js
ステージ2:前処理
進行は止まっておらず、私の50行のコードは徐々に1500になり、サードパーティのライブラリとそのプラグインを使い始めました。 そして、私が書いたアプリケーションは、Rich Internet Applicationと呼ぶこともできます。 モジュールへの分割とそれらの部分的な分離は、当時の私の問題を解決しました。 アセンブリには、プリプロセッサを使用し始めました。 多くのモジュールがあり、依存関係がありました。依存関係を自分の手で解決したくなかったので、前処理が不可欠でした。 私は名前空間を使用しましたが、それらには大騒ぎがありました:
if (typeof MyNamespace === 'undefined') { var MyNamespace = {}; }
余分な落書き:
new MyNamespace.MyConstructor(MyNamespace.MY_CONST); // vs new MyConstructor(MY_CONST);
そして当時のミニフィケーターはこのコードをうまく圧縮していませんでした:
new a.MyConstructor(a.MY_CONST); // vs new a(b);
完全な分離を適用し始め、名前空間を破棄してスコープに置き換えたとき、私のモジュールは少し前進しました。 そして彼はこれらのモジュールを使い始めました:
include('deps/dep1.js'); var MyModule = (function () { var MyModule = function () {}; MyModule.prototype = { // ... }; return MyModule; })();
そして、そのようなアセンブリ
(function () { include('myModule.js'); }());
そして同じ前処理
$ includify builds/build.js index.js
各モジュールにはローカルスコープがあり、アセンブリ全体が別のIEFEでラップされます。 これにより、モジュールを相互に保護し、アプリケーション全体をグローバルから保護できます。
ステージ3:AMD
ある日、Redditを読んでいるときに、AMDとRequireJSに関する記事に出会いました。
小さな余談。 実際、AMDのアイデアはYUI Modulesとよくドピレンから借りたものです。 モジュールを使用して宣言するために、余分な文字を記述する必要がなくなったため、構成も簡単になりました。
だった
YUI().use('dep1', function (Y) { Y.dep1.sayHello(); });
になっています
require(['dep1'], function (dep1) { dep1.sayHello(); });
AMDのことを知り、私はこの時まですべてが間違っていたことに気付きました。
require()
と
define()
を
require()
と
define()
関数
require()
2つだけで、私の問題はすべて解決されました! モジュールは依存関係自体をロードし始め、正常なエクスポートとインポートが現れました。 モジュールは3つの部分(インポート、エクスポート、モジュール本体)に分割され、簡単に理解できました。 また、彼が必要とし、エクスポートするリソースを見つけることも容易になりました。 コードは構造化され、よりクリーンになりました!
モジュール
define('myModule', ['dep1', 'dep2'], function (dep1, dep2) { var MyModule = function () {}; MyModule.prototype = { // ... }; return MyModule; });
組立
$ node r.js index.js bundle.js
しかし、それほど単純ではありません...
ステージ4:AMDへの失望
上に示したのは、理想的なモジュールと完璧なアセンブリです。 これは実際のプロジェクトでは発生しません。 また、モジュールには多くの依存関係があります。 その後、次のようになります。
require(['deps/dep1', 'deps/dep2', 'deps/dep3', 'deps/dep4', 'deps/dep5', 'deps/dep6', 'deps/dep7'], function ( dep1, dep2, dep3, dep4, dep5, dep6, dep7) { return function () { return dep1 + dep2; }; });
このモジュールを使用できますが、大騒ぎします。 この問題を克服するために、Simplified CommonJSでそのようなモジュールを作り直すことができます。 この場合でも、
define()
ラッパーを作成して、正直なCommonJSモジュールを作成し、
r.js
を使用してそれらをビルドする必要はあり
r.js
define(function (require, module, exports) { var dep1 = require('dep1'), dep2 = require('dep2'), dep3 = require('dep3'), dep4 = require('dep4'), dep5 = require('dep5'), dep6 = require('dep6'), dep7 = require('dep7'); return function () { return dep1 + dep2; }; });
RequireJSのSimplified CommonJS形式は「ネイティブではなく」、開発者が作成する必要がありました。 このようなモジュールの作成を開始すると、RequireJSは正規者によるこのモジュールの依存関係の検索を開始します。
そして、おそらく何かが見つかりません:
require("myModule//"); require("my module"); require("my" + "Module"); var require = r; r("myModule");
このコードは有効ですが、単一のモジュールはありません。 もちろん、この例は抽象的であり、いくつかの名前は不自然ですが、テンプレートやいくつかの工場など、モジュール名の動的な構築のケースによく出くわしました。
もちろん、RequireJSにはこれに対する解決策があります-そのような各モジュールをconfigに登録します:
({ "paths": { "myModule": "modules/myModule.js" } })
また、そのようなモジュール(テンプレート)が多数あり、毎回構成に新しいモジュールを書きたくないため、構成を動的に生成するなど、あらゆる種類の魔法でコードが成長し始めます。 また、「動的モジュール」を使用しないことは、使用可能な機能を使用すると愚かです。
私は正直なCommonJSモジュールを書き始め、開発中でもr.jsを使用してアセンブリを使用しました。 AMDが拒否されたことで、Node.jsでこれらのモジュールを魔法なく使用できるようになりました。 私はこのツールが基本的に私に合っていることを理解し始めましたが、松葉杖と余分な研磨が必要です。
RequireJSが提供してくれた動的ローディングモジュールオプションは必要ありませんでした。 開発と本番で最も類似したコードがあることを確認したかったので、開発中のモジュールの非同期読み込みは私には適さなかったため、1つのファイルにモジュールを収集しました。
プロジェクトの一部は起動時に読み込まれ(1リクエスト)、残りの部分はオンデマンドで読み込まれました。 そして、それらは小さなリクエストの束ではなく、1つの大きなリクエスト(1mファイルの複数のモジュールのアセンブリ)でロードされました。 これにより、時間とトラフィックの両方が節約され、ネットワークエラーのリスクが軽減されました。
また、いくつかのアセンブリを作成する必要がある場合もあります。 たとえば、テスト環境向けのロシア語ロケールのアプリケーションや、企業ネットワーク向けの英語版IE用に最適化されたアプリケーションなどです。 または、広告が無効になっているウクライナ向けiPad用に最適化されたアプリケーション。 無秩序が君臨し、コピー&ペースト...
RequireJsの哲学では、
require()
があらゆるリソースを生産するための普遍的なプラントであるという事実は好きではありませんでした。
require()
は、プラグインと既にロードされたモジュールの抽象化を行います。何らかの理由でプラグインが接続されなかった場合、何らかの形でプラグインを明示的にロードせず、それを使用してリソースをロードします。
require(['async!i18n/data', 'pewpew.js', 'text!templates/index.html'], fucntion (data, pewpew, template) { });
リソースが単調であるか、リソースがあまり多くないプロジェクトでは、これは約
ステージ5:モジュール検索
もうそんなふうに生きていけないことに気づいた...
1つのモジュールはCommonJSである必要があります
Node.jsとJS @ DOMの両方で同じモジュールを実行する必要がある非常に頻繁なケース。 ほとんどの場合、これらは外部環境(ファイルシステム/ DOM)またはそれから抽象化された部分に関連しないいくつかのモジュールです:テンプレート(最も一般的な部分)、時間を扱う機能、フォーマット機能、ローカリゼーション、バリデーター...
AMDを作成し、何かを再利用する必要がある場合、2つの方法があります。AMDをCJSに書き換えるか、node-requireを使用します。 多くの場合、何も変更する必要がないため、2番目のオプションを選択します。 しかし。 次に、Node.jsの既存のモジュールロードシステムに対する奇妙な抽象化である、モジュール式の混乱が現れます。 Node.jsのAMDモジュールが本当に好きではありませんでした。
CJSは、Node.jsとの互換性に加えて、
define()
ラッパーと、関数本体をフォーマットする追加のインデントを奪われています。 その要件とエクスポートは、
define()
wayよりも視覚的でES6モジュールに近いです。 自分で比較してください:
ES6モジュール
import "dep1" as dep1; import "dep2" as dep2; export var name = function () { return dep1 + dep2; };
CommonJS /モジュール
var dep1 = require("dep1"), dep2 = require("dep2"); exports.name = function () { return dep1 + dep2; };
AMD
require(['dep1', 'dep2'], function (dep1, dep2) { return { name: function () { return dep1 + dep2; } }; });
そして、AMDに戻る必要が生じたとしても、まったく問題はありません。r.jsがCJSモジュールをラップするように、設定に1行だけ登録する必要があります。
2モジュールビルダー
今日、CoffeeScriptを書かなくても、すべてがうまくいき、何らかの形でスクリプトをチェック、収集、圧縮します。
CJSモジュールを適応させるには、コレクターが私のためにできるラッパーが必要です。 コレクターは、すべてのモジュールが存在するかどうか、モジュールの名前を間違えたかどうか、すべてのプラグインを宣言したかどうかも確認できます。
アセンブリの結果として、作業に必要なモジュールとスクリプトの両方を含む1つのファイルを取得したいと思います。
アプリケーションを「マイスクリプト」と「マイニングではない」「キャッシュのために」(ブートローダーコードとコードを別々に接続する)に分割することは、ほとんど単一ページのWebアプリケーションを記述し、今日キャッシュを洗い流すことができるため、意味がありませんでした分。 また、オールインワンアセンブリにより、アップグレード中に「モジュールローダー」との互換性の問題を取り除くことができます。
3柔軟な構成システム:依存関係、継承、ミックスイン
すでに書いたように、私のアプリケーションには、さまざまなデバイス、ブラウザー、環境、およびロケール用のアセンブリがたくさんあります。 不要なコピーと貼り付けや落書きをせずに控えめな構成システムを取得したかったのです。
たとえば、
prod
configがあります;
dev
configはそれから継承され、いくつかのモジュールを置き換えます。
prod+en
、
dev+ru
混在させることができる構成
ru
と
en
もあります。 現在、「共通」コピーペースト(
prod-ru
、
prod-en
、
dev-ru
、
dev-en
)の代わりに、4つの「ドライ」構成ファイル(
prod
、
dev
、
ru
、
en
)しかありません。
4 CLI
これは、作業の半分を行うロボットへのインターフェースです。 それが非常に過負荷であるか、
-- ----
を
-- ----
必要がある場合、これは
Makefile
の外観に負担をかけ始め、このロボットを起動するのに多くの時間の無駄を伴うため、時間を節約できます。
頻繁に繰り返されるアクションは、可能な限り簡素化する必要があります。 サブコマンドと同じ引数名であるデフォルト値を使用する必要があります。 一般的に、開発者が最小限のことを覚えて書くようにします。
比較する
$ tool make -f path/to/build_name.js -o path/to/build.js
そして
$ tool make build_name
そして今、もう一度オートコンプリートなしでコンソールでこの長いコマンドを書き出すと、このツールを嫌い始めます。 1番目のオプションはおそらく2番目のオプションよりも明示的であることは明らかですが、実際にはグラフマニアックツールのように見えます。
ステージ6:browserify
browserifyは、ブラウザでNode.jsモジュールを実行できるツールです。
browserify main.js > bundle.js
を参照するだけで機能します。
しばらくの間browserifyを使用した後、Node.js環境をブラウザーで動作するように適応させるという真のユースケースに気付きました。 browserifyはその目的には最適ですが、Webアプリケーションが作成される現実には適していません。 適応されていないサードパーティのモジュールがある場合、アプリケーションの大部分の動的なロードがある場合。 すべてが機能するように、コンソールでよく考えなければなりませんでした。
ステージ7:LMD
私は本当にやりたくありませんでしたが、 LMD-私の人生を楽にするツールに取り組み始めなければなりませんでした。 既存のツールを自分の目標に合わせてカスタマイズできなくなりました。
その結果、プロジェクトのスクリプト部分を組み立てるツールが開発されました。
LMDの基礎となるいくつかの機能を次に示します。
1設定からのアセンブリ
構成の存在は避けられないので、それを基にして構築してみませんか?! lmdの動作は、モジュールとプラグインの両方が含まれる構成と、結果のファイルのエクスポートパスによって完全に決定されます。 構成を継承し、他の構成と混合できます。
これは設定がどのように見えるかです
{ "name": "My Config", "root": "../js", "output": "../build.lmd.js", "modules": { "main": "index.js" }, "optimize": true, "ie": false, "promise": true }
100個のモジュールがある場合-各モジュールをconfigに登録する必要はありません! 同じタイプのモジュールに対して「書き換えルール」を登録するだけで十分です。
{ "modules": { "main": "index.js", "<%= file %>Template": "templates/*.html" } }
極端な場合、CJSモジュールの形式で構成を記述し、すべてをその場で生成できます。
2抽象FS:ファイルシステムへのバインドの欠如
一方ではFSへのバインドは自然であり、HTTPサーバーはファイルシステムを一意に反映できます。 しかし、ブラウザーにはファイルシステムがなく、HTTPサーバーはリソースを提供し、コードはこのURLのこのテキストがモジュールであることを既に理解していることを覚えておく価値があります。 リソースは、CDNに任意の名前で移動して配置できます。
抽象ファイルシステムの導入により、モジュールの抽象化が可能になります。 たとえば、locale.ru.jsonとlocale.en.jsonの両方が同じインターフェイスを持っているという事実のために非表示にできるロケールモジュールがあります。あるファイルを別のファイルに透過的に変更できます。
モジュールには好きな名前を付けて、相対パスを考えずに接続できます。 多くのモジュールがあり、このモジュールの下に隠されているファイルを忘れた場合は、
lmd info
を使用するだけです。
$ lmd info build_name | grep module_name info: module_name ✘ plain ✘ ✘ ✘ info: module_name <- /Users/azproduction/project/lib/module_name.js
3オーバーロードされていないrequire()およびプラグイン
私はrequireがファクトリーであることが好きではなかったので、その動作はわずかに書き直されました。 次に、単に
require()
が抽象ファイルシステムからモジュールをロードし、それ以外は何もロードしません。
require.*()
プラグインを使用して
*
独自の処理を行います。 たとえば、
require.js()
、
$.loadScript
似たJavaScriptファイル
require.js()
ロードします。
プラグインは、構成に明示的に記述する必要がありますが、LMDは、「正しいコード」を記述する場合にプラグインを有効にすることを忘れないでください。
たとえば、このコードでは、LMDは3つのプラグイン、css、parallel、promiseを忘れないようにします。
require.css(['/pewpew.css', '/ololo.css']).then(function () { });
しかし、このコードでは、jsプラグインのみ
var js = require.js; js('http://site.com/file.js').then(function () { });
継承とミックス設定を使用して、プラグインを有効または無効にできます。
4適応モジュール
プロジェクトには、モジュールを呼び出すのが難しいファイルがいくつかありますが、他のモジュールと同様に使用する必要があります。 LMDは、ビルド時に任意のファイルを簡単に適合させ、そこからCJSモジュールを作成できます。 さらに、テキストファイル(テンプレート)とJSONファイルを使用するために、プラグイン(RequireJSのプラグインテキストを参照)またはアダプターを登録する必要はありません。 同じRequireJS LMDとは異なり、これらのファイルは正直なモジュールに変換され、shimで適合しません。
現在、LMDには多数のプラグインとそれらの使用例、およびアセンブリ用の組み込み分析システムがあります。 そしてもちろん、LMDは私の生活を楽にします。 LMDの詳細については、私の記事の範囲を超えています。 次回は、LMDのサンプルプロジェクトに関する記事を書きます。
未来は?
はい、もちろん、これらはES6モジュールです。 それらの形式は、他の言語のモジュールの多くの形式と類似しており、JavaScriptの初心者の期待に応えます。 これらには、インポート、エクスポート、モジュールラッパー(複数のファイルを連結する必要がある場合)に必要なすべてのモジュール属性があります。 CJSとAMDで完全に放送されます。 ただし、現在ドラフトに含まれている形式では、実際のプロジェクトで使用することは困難です。
インポートは静的です。 モジュールビルダーを使用して、アプリケーションの起動を高速化する必要があります。 外部モジュールのインポートはブロックされます:
<script> import {get, Deferred} from "http://yandex.st/jquery/3.0/jquery.min.js"; get('/').then(console.log.bind(console)); </script>
ほぼ同じです
<script src="http://yandex.st/jquery/3.0/jquery.min.js"> <script> var get = $.get, Deferred = $.Deferred; get('/').then(console.log.bind(console)); </script>
次に、
<script async/>
を使用してロックを解除できます
モジュールは動的にロードされますが、現在は完全ではありません。
Loader.load('http://json.org/modules/json2.js', function(JSON) { alert(JSON.stringify([0, {a: true}])); });
モジュールローダーがいくつかのモジュールのアセンブリをロードできることを願っています。 それで十分でしょう。
標準は現在活発に議論されており、今日お見せしたことは明日はそうではないかもしれません(しかし、そうではないでしょう)。 現在、インポート/エクスポートのモジュールと構文は、他の言語で見るのに慣れているものと似ています。 多くの開発者がJavaScriptを使用しており、AMDのようなワイルドハックを見るのが痛いため、これは良いことです。 今日、ECMAScript開発の方向の1つは、言語を他の言語からの翻訳のための一種のアセンブラーに変えることを目的としています。 そして、モジュールはこの方向の不可欠な部分です。
結論
今日、JavaScriptには確立されたモジュラーシステムがないと言えますが、モジュラーエミュレーターしかありませんが、ES6モジュール構文を使用してCJSとAMDでモジュールをコンパイルする機会があります。 JavaScriptには独自の雰囲気があり、多くの使い慣れたインポートの使用を許可しない多くの制限(ネットワークブレーキ、トラフィック、遅延)があります。 アセンブリと非同期ロードの問題は、一般的なモジュラーエミュレーターで何らかの形で解決されますが、ES6開発者がどのように解決するかは問題です。
マテリエル
あなたが私のモジュラーパスを習得していれば、私の小さなモジュラー分類に興味があると思います。
既存のJavaScript「モジュール」とそのインフラストラクチャを機能別に分類しました。 分類では、多くの機能が考慮されます。 モジュールの分類を見てから、モジュールシステムを分離します。
- 依存関係の解決
- 手動制御
- 依存関係は構成に書き込まれます
- 依存関係はモジュール自体に書き込まれます
- 依存関係はモジュールと構成に登録されます
- 依存関係アクセス
- 任意
- ダイナミック
- 宣言的
- モジュールからエクスポート
- 混oticとした輸出
- 強力なマネージドエクスポート
- 厳密な名前の「自己エクスポート」
- カスタム名前付きエクスポート
- 正直なインポート/エクスポート
- サイドモジュール
- 組み立てなし
- ファイルの連結をマスクする
- 前処理
- 静的依存性分析
- 構成アセンブリ
- モジュールの初期化と解釈
- 起動時に初期化および解釈されます
- 起動時に初期化され、オンデマンドで解釈されます
- リクエストに応じて初期化および解釈されます
- 外部依存関係をダウンロードする
- 非管理モジュールローダー
- マネージドモジュールローダー
- モジュールの分離
- モジュールは分離されていません
- 分離されたモジュール
- モジュールは完全に分離されています
依存関係の解決
アセンブリツールまたは開発者が、このモジュールの通常の操作のために接続/初期化する必要がある依存関係を決定する方法。 依存関係は、順番に依存関係を持つこともできます。
依存関係の解決。 手動制御
開発者の肩の依存関係管理。 開発者は、接続する必要がある依存関係を分析的に理解します。
<script src="deps/dep1.js"/> <script src="deps/dep2.js"/> <script src="moduleName.js"/>
そしてそれに応じて
main.js
var moduleName = function () { return dep1 + dep2; };
使用するサードパーティのライブラリはありません
多くのモジュールがなく、それらがすべて独自のものである場合、それは問題ありません
多くのモジュールがある場合、そのようなコードはサポートできません。
複数のファイル=複数のサーバー要求
「クイックフィル」に適しています。
依存関係の解決。 依存関係は構成に書き込まれます
依存関係は外部構成に登録され、継承できます。 この設定を使用して、一部のアセンブリツールはこのモジュールの依存関係をダウンロード/接続します。 構成は、特定のモジュールとプロジェクト全体の両方に対して記述できます。
この構成はLMDで使用されます
{ "modules": { "main": "moduleName.js" "<%= file %>": "deps/*.js" } }
そしてそれに応じて
main.js
var dep1 = require('dep1'), dep2 = require('dep2'); module.exports function () { return dep1 + dep2; };
モジュールはファイルシステムに関連付けられていません(任意のファイルに任意の名前を付けることができます)
モジュールの名前を変更せずに、その内容を変更できます
このような設定を記述する必要があり、
追加のツール/ライブラリが必要です。
依存関係の解決。依存関係はモジュール自体に書き込まれます
依存関係、ファイルへのパス、および実行時にそれらがどのように呼び出されるかは、ファイル自体で宣言されます。モジュールは実際に作業に必要なリソースを決定し、ローダーはそれらを提供します。依存関係と依存関係の依存関係が読み込まれるまで、モジュールは作業を開始しません。
このメソッドはAMD(RequireJS)を使用します
require(['deps/dep1', 'deps/dep2'], function (dep1, dep2) { return function () { return dep1 + dep2; }; });
1つのモジュールに多くの依存関係がある場合、この構文は通常、CommonJS定義に低下するか、あらゆる種類の歪みを使用します。
倒錯
require(['deps/dep1', 'deps/dep2', 'deps/dep3', 'deps/dep4', 'deps/dep5', 'deps/dep6', 'deps/dep7'], function ( dep1, dep2, dep3, dep4, dep5, dep6, dep7) { return function () { return dep1 + dep2; }; });
CommonJS定義の劣化
define(function (require, module, exports) { var dep1 = require('dep1'), dep2 = require('dep2'), dep3 = require('dep3'), dep4 = require('dep4'), dep5 = require('dep5'), dep6 = require('dep6'), dep7 = require('dep7'); return function () { return dep1 + dep2; }; });
この低下を使用する場合、RequireJSは依存関係を定期的に検索します。これは95%の信頼できる方法です。正直な方法(ASTまたは巧妙な処理)は、リソース(コードサイズと処理時間)を過度に消費しますが、すべてのニーズをカバーするわけでもありません。
たとえば、定義方法がわからない古いモジュールを適応させるため、またはある種の「正直なモジュール」が動的に初期化される場合など、同じ方法で構成を記述する必要がある場合
require('templates/' + type)
があります。動的な初期化はまれであり、主にテンプレートの動的なロードに使用されますが、可能です。
ほとんどすべての依存関係のファイルに記述
非同期ロードしないのconfigs
書き込み設定する必要はありませんが
ただし、とにかく設定を書かなければならない場合があります。
追加のツール/ライブラリが必要です
依存関係の解決。依存関係はモジュールと構成に登録されます
依存関係は、ファイル自体と特別な構成で登録されます。
configは、依存関係を解決するためにパッケージマネージャーによって使用されます。たとえば、npmおよび
package.json
{ "dependencies": { "express": "3.x", "colors": "*" } }
それに応じて
main.js
// var express = require('express'); // var dep1 = require('./deps/dep1'), dep2 = require('./deps/dep2'); module.exports function () { return dep1 + dep2; };
. . , , .
package.json
.
require('pewpew.js')
,
/ , browserify
, .
依存関係へのアクセス。任意
すべてのモジュールは、グローバルスコープまたは名前空間にオープンにあります。各モジュールは、制限なしに、アプリケーションのあらゆる部分にどこからでもアクセスできます。
var dep1 = 1; var dep2 = 2; alert(dep1 + dep2);
モジュールは多くはありません、彼らは大きなされていない場合、それは大丈夫だ
、モジュールの多くは、このコードが維持できない場合には
(グローバル変数または名前空間の名前を探すために)が、目はモジュールの依存関係を決定することはできません
依存関係へのアクセス。ダイナミック
モジュールへのアクセスは、「ローダー」を介してのみ取得できます。
require()
または、モジュールの依存関係を宣言することにより取得
define()
できます。この機能はグローバルにアクセスすることもできます。
var dep1 = require('./deps/dep1'), dep2 = require('./deps/dep2'); alert(dep1 + dep2);
したがって、
define()
require(['./deps/dep1', './deps/dep2'], function (dep1, dep2) { alert(dep1 + dep2); });
簡単に応じて、見つける/理解するための
依存関係へのアクセスが緩和され、あなたが怠惰など、ランタイムベースを計算するためのモジュールを初期化することができます
静的に全体の依存関係グラフのほとんどを定義することが可能である
コードに冗長ビットを、それが保守のために良いの手数料だ
追加のライブラリ
依存関係へのアクセス。宣言的
モジュールはコードを記述するときに宣言され、動的にはロードされません。静的コードアナライザーは、アプリケーションの動作に必要なモジュールのセットを明確に理解できます。ほとんどすべてのインポート構造がこのように機能します。
import * from "dep1"; import * from "dep2";
また、この方法では、依存関係へのアクセスの属性を割り当てることができ、静的AMDが定義されます()
define('module', ['./deps/dep1', './deps/dep2'], function (dep1, dep2) { });
静的インポートにより、コレクターは依存関係を構築でき、ES6モジュールトランスレーターはES3互換でコードをリメイクできます。
静的解析が可能(完全または部分的)で
利用可能な放送ES6モジュール
めったに適用ピュア
モジュールからエクスポート
ほとんどの場合、モジュールは他のモジュールが使用できるリソースを提供します。これには、データ、ユーティリティ(日付、数値、i18nなどの形式)を使用できます。モジュールからエクスポートすると、そのようなリソースを提供しているというモジュールの表示方法が決まります。
エクスポート。混oticとした輸出
モジュールは、何でも、どこでも、いつでもエクスポートします
var a = 10, b = ''; for (var i = 0; i < a; i++) { b += i; } var dep1 = b;
地獄と悪夢のグローバルな範囲を詰まらせる、いずれにしても、これは原則としてサポートされていません
エクスポート。強力なマネージドエクスポート
IIFEを追加して以前のメソッドを少し変更すると、このメソッドが得られます。モジュールは、どこにあるのか、何を呼び出すのかを事前に知っています。
var dep1 = (function () { var a = 10, b = ''; for (var i = 0; i < a; i++) { b += i; } return b; })();
またはわずかに異なるオプション
(function () { var a = 10, b = ''; for (var i = 0; i < a; i++) { b += i; } exports.dep1 = b; })(exports);
またはAMDという名前
define('dep1', [], function () { var a = 10, b = ''; for (var i = 0; i < a; i++) { b += i; } return b; });
その
ようなモジュールをビルドして使用するのに特別なツールは必要ありません(AMDを除く)。
必要なものだけがエクスポートされます。
モジュールは、エクスポート先とその名前を知っています。
エクスポート。厳密な名前の「自己エクスポート」
このメソッドは、「モジュールを登録する」という特別な機能に基づいて
ready()
います。モジュールは、準備ができたら呼び出す必要があります。モジュールの名前とそれが提供するリソースという2つの引数を取ります。
(function () { var a = 10, b = ''; for (var i = 0; i < a; i++) { b += i; } ready('dep1', b); })();
機能のモジュールの依存関係をロードするためには使用されている
load()
、と同様
require()
load('dep1', 'dep2', function (dep1, dep2) { ready('dep3', function () { return dep1 + dep2; }); });
load('dep3', do.stuff);
モジュールは非同期にエクスポートされ、エクスポートを遅らせることができます。
モジュールはどこにあるかわかりません。
モジュール自体はエクスポートされます(モジュールはそれを使用するモジュールに従属します)。
モジュールはその名前を知っており、動的に変更する
ことができ
ます。
エクスポート。カスタム名前付きエクスポート
モジュールは、その名前もそれがどこにあるかを知りません。モジュールのコンシューマー自身が、コンシューマーのコンテキストで呼び出されるモジュールを決定します。
これはCommonJSモジュールです
var a = 10, b = ''; for (var i = 0; i < a; i++) { b += i; } module.exports = b;
または匿名のAMD
define([], function () { var a = 10, b = ''; for (var i = 0; i < a; i++) { b += i; } return b; });
モジュールのエクスポート中に任意の名前を使用できます。
var dep1 = require('deps/dep1');
彼はそれを使用するときに呼び出されますどのようにあるか、どこモジュールが知らない
あなたはモジュールの名前を変更する場合にのみ、ファイルの名前を変更する必要があり
、組み立ておよび使用のためのライブラリの必要性
エクスポート。正直なインポート/エクスポート
モジュールを宣言するこのメソッドは、2つおきのプログラミング言語を使用します。ECMAScript 6モジュール仕様はかなり前に登場したので、遅かれ早かれこのような構文はJavaScriptで提供されるでしょう。
モジュールを宣言します。
module "deps" { var a = 10, b = ''; for (var i = 0; i < a; i++) { b += i; } export var dep1 = b; export var dep2 = b + 1; }
バインディングなしでモジュールを宣言することもできます
module {}
。
デフォルトの名前を使用して、より少なく書くことができます
import * from "deps"; console.log(dep1);
名前の競合は、一種の「名前空間」を使用して回避できます
import "crypto" as ns; console.log(ns.dep1);
モジュールの一部をエクスポートできます
import {dep1} from "deps"; console.log(dep1);
使い慣れた視覚的な多くの言語からの馴染みのあるインポート
これはECMAScript 6
です。たとえば、TypeScriptのモジュールを使用するなど、ES6モジュールをES3互換コードに変換する必要があります。
サイドモジュール
今日、モジュールを含め、ほぼすべてが進行中です。CoffeeScriptとAMDを使用しない場合でも、いずれにせよプロジェクトを組み立てます:ファイルを連結し、圧縮します。
組み立てなし
HTMLのすべて
<script src="deps/dep1.js"/> <script src="deps/dep2.js"/> <script src="moduleName.js"/>
それは単にある
ため、リクエスト数の増加を遅くするために、アプリケーションが停止し、開始によってサポートされるモジュールの数を増やすことにより、
HTMLエンティティと宣言モジュールミキシング
新しいビルドを-新しい.htmlを
モジュールの組み立て。ファイルの連結をマスクする
集める
$ cat **/*.js > build.js
使用する
<script src="build.js"/>
簡単です。
アップロードするファイルは1つだけです。
各タイプのアセンブリについて、新しいスクリプトを作成する必要があります。
ファイルは、異なるOSおよびFSで任意の順序でアセンブルできます。
モジュールの組み立て。前処理
方法は、ファイル内の特別な「タグ」を検索することです-
include('path/name.js')
または
// include path/name.js
同様の
include('deps/dep1.js'); include('deps/dep2.js'); var moduleName = function () { return dep1 + dep2; };
これらはすべて、この形式の特別なユーティリティによって展開されます。
/* start of deps/dep1.js */ var dep1 = 1; /* end of deps/dep1.js */ /* start of deps/dep2.js */ var dep2 = 2; /* end of deps/dep2.js */ var moduleName = function () { return dep1 + dep2; };
したがって、ネストされたモジュールには依然依存関係があり、再帰的にロードされます。
あるのみ1ファイル
なし「の継承のconfigs」を作ることはできません
アセンブリの各タイプのためには、すべてのリストに新しいファイルを作成する必要がある
include
ならば、プリプロセッサ愚かなことの可能なコードの重複や他のアーティファクト
かのモジュールにコードの誤用の可能な注入
の整合性へのモジュールのリード線へのコードの注入モジュールでは、
"use strict"
名前の競合やその他の問題が発生します。
これが典型的な例です
(function () { "use strict"; var i = 3; include('dep1'); // return dep1 + i; })();
そして彼の中毒
var i = 4, dep = 01234;
結果を理解していると思う;-)
モジュールの組み立て。静的依存性分析
依存関係検索によるモジュールコンテンツの静的分析。このメソッドは、r.js(RequireJSモジュールのコレクター)およびbrowserify(CommonJSモジュールのアダプターおよびブラウザーのNode.jsインフラストラクチャ)を使用します。ASTパーサーを使用して、define / require呼び出しを探し、依存関係を見つけ、includeとは異なり、これらの依存関係をモジュールの外側に置きます。
たとえば、そのようなモジュール
require(['dep1', 'dep2'], function (dep1, dep2) { return function () { return dep1 + dep2; }; });
r.jsを介してドライブする場合、このフォームでここでやり直されます。
define('dep1', [], function () { return 1; }); define('dep2', [], function () { return 2; }); require(['dep1', 'dep2'], function (dep1, dep2) { return function () { return dep1 + dep2; }; });
同様にbrowserifyに動作しますが、収集し、より複雑なフォーマット
のみ1ファイルに行く
すべての依存関係は、モジュールに登録されている
アセンブリの種類ごとに新しいファイルを作成したり、シンボリックリンクでマジックを行うことが必要である
プリプロセッサ(動的モジュール名によって構築)いくつかの依存関係を見つけることができない
前の段落を修正するにはこれらのモジュールを有効にするために設定を書く必要があります
サイドモジュール。構成アセンブリ
ここではすべてが明らかです。構成では、必要なモジュール。コレクターはそれらをアセンブリに含め、依存関係を見つけます。その後、アナライザーによってアセンブリの結果が静的にチェックされます。アナライザーは、何かの追加または削除を勧めます。
このメソッドはLMDを使用します。
{ "root": "../js", "modules": { "main": "main.js", "dep1": "deps/dep1.js", "dep2": "deps/dep2.js" } }
もちろん、このオプションは興味深いものですが、なぜモジュールと構成に同じことを2回書くのですか?!
これは簡単に説明できます。 LMDはファイルシステムを認識せず、実際には構成は抽象ファイルシステムです。これにより、相対パスを考慮せずに、モジュールの転送/名前変更中に、プロジェクト全体でパスを実行または変更しないでください。抽象FSを使用すると、ローカリゼーション、環境設定の置換、およびその他の最適化のための安価な依存性注入を取得できます。また、モジュールが動的に接続され、静的アナライザーがそれらを物理的に見つけることができないため、構成でモジュールに関する記録を作成する必要があります。毎回configにモジュールを登録することは一歩後退することは明らかです。したがって、LMDでは、glob-ingを使用してディレクトリ全体をサブディレクトリに接続することが可能です。書き換えルールの一種。
この構成は、前の構成と同じです。
{ "root": "../js", "modules": { "<%= file %>": "**/*.js" } }
どのファイルが必要かを判断し、テンプレートを作成して、このLMDモジュールでの表示方法を指定します。名前を決定するために、LMDはlodashのテンプレートエンジンを使用するため、よりトリッキーな構造を記述できます。
{ "root": "../js", "modules": { "<%= file %><%= dir[0][0].toUpperCase() %><%= dir[0].slice(1, -1) %>": "{controllers,models,views}/*.js" } }
この方法の結果は以下のとおりです。
視覚-すべてのプロジェクトツリーを1つのファイルに記述することができます
安全に-除外パーサエラー
抽象ファイルシステムが
設定書き込む必要がある
必要性コレクタを
モジュールの初期化と解釈
これは、大量のコードが実行されたときにアプリケーションの起動時の遅延を減らすことができる重要なポイントです。コードがページに到達すると、初期化中にコードが初期化され(関数を作成し、何らかの名前で登録されます)、コードが解析され、検証されて、さらなる解釈と可能なJITコンパイルのためにASTに渡されます。関数が呼び出されると、そのコードが解釈されます。
関数は初期化または解釈されません。JavaScript文字列のみが初期化されます。
'function a() {return Math.PI;}';
関数が初期化されます。
function a() { return Math.PI; }
関数は初期化され、解釈されます。
function a() { return Math.PI; } a();
各関数の宣言とその呼び出しには、特にモバイルでは時間がかかるため、この時間を短縮するとよいでしょう。
起動時に初期化および解釈されます
モジュールはそのまま提供され、プログラムの開始時に実行されます。たとえ今彼がいなくても ご覧のとおり、モジュールには作業の速度を低下させる可能性のあるサイクルがいくつかあります。
var dep1 = (function () { var a = 10, b = ''; for (var i = 0; i < a; i++) { b += i; } return b; })();
追加のツールを使用する必要はありません。
コードが大きくない場合、初期化時間は重要ではありません。
コードの量が増えると、スタートアップレイテンシが表示され始めます。
起動時に初期化され、オンデマンドで解釈されます
AMDとNode.jsのモジュールの両方で使用されている非常に人気のある方法
define('dep1', [], function () { var a = 10, b = ''; for (var i = 0; i < a; i++) { b += i; } return b; });
このモジュールは起動時に初期化されます。しかし、彼の体は要求に応じて実行され、結果は
return b;
キャッシュされ、次回呼び出されたときに解釈はパスしません。
モジュールの非常に強力なビューに変更する必要はありませ
大量のコードが時にスタートアップ待ち時間が大幅に短縮されていない
追加のライブラリが必要
リクエストに応じて初期化および解釈されます
コードの初期化を遅らせることができる、以前のメソッドの小さな変更。主にモバイルデバイスでのコードダウンロードの最適化に使用されます。このような最適化は、RequireJSおよびLMDで実行できます。
LMDアセンブリピース(構成ではない)
{ 'dep1': '(function(){var a=10,b="";for(var i=0;i<a;i++){b+=i;}return b;})' }
一部のモジュールにモジュールリソースが必要な場合
dep1
、LMDはこのコードを解釈して初期化します。
このようなもの:
var resources = new Function('return ' + modules['dep1'])()(require, module, exports);
コードの初期化時間は、
new Function
正直な初期化よりも少し遅くなる場合がありますが、この最適化を賢明に使用すれば、最初から時間を稼ぐことができます。
new Function
とは異なり
eval()
、生成されたコードはJITコンパイラーによって最適化できます。
この操作は開発者に対して透過的であり、
追加のライブラリが
必要です。
外部依存関係をダウンロードする
すでに述べたように、JavaScript @ DOMには独自の雰囲気があるため、モジュールをロードする通常の方法はここでは機能しません。モジュールはリモートにあり、それらの同期ロードは現実的ではありません。デスクトップアプリケーションで「光の速度で」ライブラリを同期的にリンクできる場合、JavaScript @ DOMでは、EventLoopのブロックにより、これはほとんど現実的ではありません。
一度にすべてをロードすることはできないので、何かを発明して苦しむ必要があります:)
非管理モジュールローダー
非実行モジュールとは、追加の処理を必要としないコードを意味します。たとえば、このようなローダー
jQuery.getScript(file)
は次のようなものです。
var script = document.createElement('script'); script.src = file; script.onload = done; document.head.appendChild(script);
複数のモジュールを同時にロードする場合、それらはロード順に実行されます。そのため、モジュールをリストされている順序で実行する必要があります。たとえば、LAB.jsライブラリは、XHRを使用して同時にスクリプトコードを読み込み、そのコードを順番に実行します。XHRは、その制限を導入します。
$LAB .script("framework.js").wait() .script("plugin.framework.js");
YepNopeやscript.jsなどのその他のローダーは、ほぼ同じことを実行しています。
安価なソリューション
XHRの一部に制限があるか、追加の記述があります。
マネージドモジュールローダー
成人のモジュラーシステムには独自のローダーが付属しており、モジュールとその依存関係をロードできます。例えば、それが機能し
require()
と
define()
RequireJSのを。RequireJS
の関数
require()
は、必要な依存関係と依存関係の依存関係を読み込み、指定された順序でこれらのモジュールのコードを実行します。
require(['dep1', 'dep2'], function (dep1, dep2) { console.log(dep1 + dep2); });
たとえば、LMDには、バンドルのようなものがあります-いくつかのモジュールが1つのファイルにまとめられています。このバンドルをロードすると、すべてのモジュールがすべてのモジュールで利用可能になります。
_e4fg43a({ 'dep1': function () { return 1; }, 'dep2': 2, 'string': 'Hello, <%= name %>!' });
require.bundle('name').then(function () { // do stuff });
モジュールのロードと初期化の両方の管理;
開発者にとってほとんど透過的なダウンロード;
追加のツールと設定が必要
モジュールの分離
モジュールのセキュリティまたは分離は、作業を中断する開発者ではなく開発者に必要です。モジュールのプロパティへの直接かつ無秩序なアクセスは、不適切に使用されると「コードを台無しにする」可能性があります。一方、グローバルスコープにJavaScriptの痕跡がない場合、コードの研究者が何かを理解して「ブレイク」することはより困難になりますが、ここでは時間の問題です。
モジュールは分離されていません
モジュールまたはその一部はグローバルにアクセス可能で、開発者はどこからでも利用できます。
var dep1 = (function () { var a = 10, b = ''; for (var i = 0; i < a; i++) { b += i; } return b; })();
繰り返しますが、これは簡単です。
ツールは不要です。
名前空間について考える必要
があります。モジュールの分業はありません。彼は仕事をし、依存関係の受け取りを管理します。
分離されたモジュール
モジュールはグローバルにアクセスできませんが、名前-を知ることで取得できます
require('pewpew')
。私が言ったように、非表示はモジュール式システムの目標ではなく、結果です。AMDは、あなたが何らかの形でモジュールへのアクセスを得ることが可能な2つの機能がある-これを
require()
して
define()
。リソースを取得するには、モジュールのコード名を知るだけで十分です。
define('dep3', ['dep1', 'dep2'], function (dep1, dep2) { return function () { return dep1 + dep2; }; });
モジュールは他のモジュールから分離されているため、誤って何かを損なうことはありません。
別のモジュールへのアクセスは明示的に宣言されてい
ます。
モジュールは完全に分離されています
このようなモジュールの目的は、外部からモジュールに到達できないようにすることです。私は多くの人がすでにそのような「モジュール」を見たことがあると思う、例えば:
$(function () { var dep1 = (function () { var a = 10, b = ''; for (var i = 0; i < a; i++) { b += i; } return b; })(); $('button').click(function () { console.log(dep1); }); });
実際、これは完全に隔離されたモジュールであり、外部から内部に到達することは不可能です。しかし、これは1つのモジュールの例です。そのような各モジュールが「クロージャ」にラップされている場合、それらは相互作用できません。複数のモジュールを分離するには、それらを共通のスコープに配置するか、スコープ内のいくつかの共通リソースをスローします。これらのリソースを使用すると、このようなモジュールは互いに通信できます。
IEFEでそのようなモジュールをラップするだけで十分です。
(function () { /* start of deps/dep1.js */ var dep1 = 1; /* start of deps/dep2.js */ var dep2 = 2; var moduleName = function () { return dep1 + dep2; }; })();
このビルド方法では、たとえばjQueryを使用します。
LMDとbrowserifyもモジュールを環境から完全に分離しますが、オールインワンアセンブリとは異なり、それらのモジュールは互いに、またアセンブリの「制御部分」から分離されています。
彼らはこのように回っています:
(function (main, modules) { function lmd_require() {} // ... main(lmd_require); }) (function (require) { var dep1 = require('dep1'); // ... }, { dep1: function (r,m,e) {} });
単純なケースでは、完全な絶縁を簡単に達成できます。
他のケースでは、追加のツールが必要です
人気のあるJavaScriptモジュールエミュレーターの比較表
AMD、YUI | ES6 | CJS / LMD | IEFE | |
---|---|---|---|---|
依存関係の解決 | モジュール+ config | モジュール内 | 構成で | マニュアル |
依存関係アクセス | ダイナミック | 宣言的 | ダイナミック | 任意 |
輸出する | 任意の名前で | 正直なインポート/エクスポート | 任意の名前で | 混oticとした/制御不能 |
サイドモジュール | 静的解析 | 不要/連結 | 構成アセンブリ | 連結 |
モジュールの解釈 | リクエストに応じて | ネイティブソリューション | リクエストに応じて | 開始時 |
モジュールの分離 | 孤立した | 孤立した | 完全に分離 | 孤立していない |
一般的なモジュール形式
そして最後に、今日存在するJavaScriptモジュレーターに関する背景情報をいくつか紹介します。
モジュールなし
var moduleName = function () { return dep1 + dep2; };
名前空間
var MyNs.moduleName = function () { return MyNs.dep1 + MyNs.dep2; };
IIFEリターン
var moduleName = (function (dep1, dep2) { return function () { return dep1 + dep2; }; }(dep1, dep2));
IIFEの輸出
(function (exports, dep1, dep2) { exports.moduleName = function () { return dep1 + dep2; }; }(window, dep1, dep2));
AMD
YUIモジュールは、AMDと意味的に類似しています。私はそれらを実証しません。
define(["dep1", "dep2"], function (dep1, dep2) { return function () { return dep1 + dep2; }; });
CommonJSのAMDラッパー
define(function (require, module, exports) { var dep1 = require('dep1'), dep2 = require('dep2'); module.exports = function () { return dep1 + dep2; }; });
Commonjs
var dep1 = require('dep1'), dep2 = require('dep2'); module.exports = function () { return dep1 + dep2; };
UMD
現在、サポートが必要なモジュールには少なくとも3つの形式があることがわかります。プロジェクトを書いて、何でも書くことができるなら、それは一つのことです。すべての形式をサポートすることが望ましいオープンソースプロジェクトは別の問題です。これらのモジュールはすべて、本質的に同じことを行う単なるラッパーです-リソースを取得し、リソースを提供します。少し前まで、UMD:Universal Module Definitionプロジェクトが登場しました。これは、すべての形式のユニバーサルラッパーを「標準化」しました。
(function (root, factory) { if (typeof exports === 'object') { // 1: CommonJS factory(exports, require('dep1'), require('dep2')); } else if (typeof define === 'function' && define.amd) { // 2: AMD ( ) define(['exports', 'dep1', 'dep2'], factory); } else { // 3: factory(window, root.dep1, root.dep2); } })(this, function (exports, dep1, dep2) { // exports.moduleName = function () { return dep1 + dep2; }; });
開発でこれを使用するのは一種の奇妙なことですが、エクスポートでは最も多く使用されます。
読む
- JavaScriptモジュールパターン:詳細
- YUIモジュールの作成
- AMD、CommonJS、ES Harmonyを使用したモジュラーJavaScriptの記述
- Why AMD?
- AMD is Not the Answer
- Why not AMD?
- Proposal ES6 Modules
- Playing with ECMAScript.Harmony Modules using Traceur
- Author In ES6, Transpile To ES5 As A Build-step: A Workflow For Grunt
, , .