私が遭遇したすべてのアプローチの主な問題は、フロントエンドコードの構造、デプロイおよびバックエンドコードのロジック(主にテンプレート)、およびセマンティクスの欠如の間の密接な関係です。 さらに、 フロントエンドコードという用語は、 .js 、 .css 、およびブラウザに提供されるその他のファイルまたはリソースのすべての組み合わせを意味します。 通常、これらのファイルはフロントエンド開発者によって処理されます(うんざりです!)。
最初に、いくつかの実際の例を示します(さまざまなフレームワークと言語がどこでも使用されているため、疑似コードで、実際のコードは私たちを混乱させるだけです)、使用されるアプローチに関連する欠点と問題を検討し、最後にこの問題の私のビジョンを説明します。
最初の例
1つの大きなプロジェクト( Zend Frameworkに基づく)では、静的ファイルはおよそ次の方法で接続されました。
// someWidget.tpl // PROJECT_STATIC_VERSION — ( ) ViewHelpers.appendStylesheet("css/some-path/sub-path/some-widget.css?" + PROJECT_STATIC_VERSION); ViewHelpers.appendJsFile ("js/some-path/sub-path/some-widget.js?" + PROJECT_STATIC_VERSION); // Layout: <div class="some-widget"> <!-- a lot of cool html --> </div>
メソッドViewHelpers.appendStylesheetおよびViewHelpers.appendJsFileは、最終ページの対応するタグで転送されたファイルの接続を保証すると想定します。 PROJECT_STATIC_VERSION行は 、URLにキーを追加するために使用され、更新すると、ブラウザーはこのファイルの新しいバージョンを強制的にダウンロードします。
これに加えて、ファイルはテンプレートの外部、たとえばコントローラーコードや要素デコレーターコード( Zend_Form_Decorator )で接続されていることがよくありました。 テンプレートに含まれるjsコードがExtJSに依存している場合、 ExtJS jsフレームワークファイルの接続が特に頻繁に発生しました 。 残念ながら、95%の場合、これはフォームのコピーと貼り付けによって行われました。
ViewHelpers.appendJsFile("js/libs/ext-js/ext-js.js?" + PROJECT_STATIC_VERSION); ViewHelpers.appendCss ("css/libs/ext-js/ext-js.css?" + PROJECT_STATIC_VERSION); ViewHelpers.appendJsFile("js/libs/ext-js/locale-ru.js?" + PROJECT_STATIC_VERSION);
したがって、このアプローチの欠点(もちろん、ほとんどの場合):
- バックエンドコードとテンプレートコードは、フロントエンドコードの構造を知っています。 フロントエンドコードの変更(追加、移動、マージ、分割)を行うと、バックエンドコードを変更する必要が生じます。 いくつかのファイルがかなりの数のテンプレートで接続されている場合(多くの場合、接続されている場合)、かなり長くて苦痛なプロセスになります。 つまり フロントエンド開発者は基本的にバックエンドの仲間に依存しています。 氷ではないもの! (上記の例では、プロジェクト上でフロントエンド/バックエンドの開発者が分離されていないため、このコンポーネントまたはそのコンポーネントを実装する人がバックエンドとフロントエンドの両方のコードを作成しました。)
- 静的リソースを接続するための単一のポイントはありません。 なぜなら ファイルはさまざまな場所から接続できます:コントローラコード、要素デコレータコード、ビューヘルパーコード、テンプレートコード自体、特定のコンポーネントに必要なファイルを簡単に判断することはできなくなりました。
- 明示的な依存関係の欠如。 ファイルのリストは単純に接続されているため、ファイル間の関係を識別することはできませんでした。 たとえば、他の誰かに基づいて新しいコンポーネントを作成する開発者は、リソース接続の一部をコピーして(ある種の孤立した部分を担当していると考えている)、JavaScriptが機能しない理由を解明できます。 しかし、実際には、ファイル/lib/some-cool-plugin.jsを含めるのを忘れていました。
- 展開ロジックをテンプレートに追加します。 これは最も間違った動きだと思います。 (次の例では、さらに悲しくなります!)。 リソースURLへの静的バージョンの連結は、この環境またはその環境の展開手法の1つであり、テンプレートおよびフロントエンドコードのロジックとは関係ありません。 さらに、これは、このキーを追加するのを忘れる(面倒です!)か、このキーを変更するのを忘れる(これはよく起こります)ミスを犯すもう1つの機会です。
- 複製。 接続コード( ViewHelpers.blaBlaBla() )と、異なるテンプレートの同じファイルの両方。 一般的に、 DRY 。
- セマンティクスの欠如。 上記のリソースのリストにはほとんど何も書かれていません。 依存関係を分離したり、これらのリソースの性質を判断したり、このコードがまだ使用されている場所を理解したりすることはできません。
- ありふれたタイプミスの機会。 長いファイルのパスと名前は、しばしばタイプミスになりやすいです。 彼は、指定されたファイルが接続されていないと判断するために、古いヘッドに余分な時間を費やすことがよくありました(404 Not Found)。 もちろん、特定のファイルの存在をチェックするコードを書くこともできますが、これは常に可能ではありませんでした。 多くの場合、 nginx 'aと混在するルーティング。 とにかく、誰もこれをしていませんでした。
symfonyプロジェクトの2番目の例
// SomeConroller.SomeAction If (Config.Env == "Production") { includeCss("styles/feature.min.css"); includeJs("js/feature.min.js"); } Else If(Config.Env == "Dev") { includeCss("styles/feature/global.css"); includeCss("styles/feature/sub-feature.css"); includeJs("js/classes/Core.js"); includeJs("js/classes/Event.js"); includeJs("js/classes/CoolPlugin.js"); includeJs("js/classes/Feature.js"); includeJs("js/classes/FeatureSubFeature.js"); } Layout: <div class="feature"> <!-- feature's code is here --> </div>
さらに、プロジェクトレベルでは、フォームのjsおよびcssコードをマージおよび縮小するために必要なファイルを記述するための構成がありました。
styles/feature.min.css: styles/feature/main.css styles/feature/sub-feature.css js/feature.min.js: js/classes/Core.js js/classes/Event.js js/classes/CoolPlugin.js js/classes/Feature.js js/classes/FeatureSubFeature.js
この例には、以前の例のすべての欠点がありますが、より恐ろしい形式になっています。
- テンプレートの展開ロジック。 そのため、フロントエンドプログラマは、アプリケーションをデプロイできるすべての(2つまでの)環境について完全に理解しています。 さらに、これにより、本番環境でのみテストできるファイルをマージおよび縮小するための構成を維持する責任が追加されます。 ステージやテストなど、新しい環境が追加されるとどうなるか想像するのは怖いです。 実際、静的なものは接続しません( if-else-if )。 これは、静的リソースを接続する最も悪夢のようなオプションだと思います。
- 複製の束。 フロントエンドコードの構造を変更すると、悪夢に変わります。 構成を変更する必要があります。すべての場所を異なる環境に含めます。
- 依存関係はありません。 一部のコンポーネントが共通のコードを使用し始めた瞬間、私はシャーマンをしなければなりませんでした。 最小化されたバージョンはすべて2つの部分に分割され(Devのリストは変更されませんでした)、構成が長くなり、何よりも最悪になりました。
同時に、クラッシュしていない部分がまだ使用されている場所からもエラーが発生し、一部のコードは異なる場所で2回機能していました。 簡単にするため、例を考えてみましょう。ブロックAは1.jsと2.jsを使用します。 ブロックBは2.jsおよび3.jsファイルを使用します。 これらのブロックの両方を接続することはできなくなりました。 2.jsファイルは2回処理されます。
挑戦する
その結果、これらのアプローチやその他のアプローチの欠点を分析した後、静的リソースを接続するためのシステムに関する多くの要件を収集しました。
- リソースを接続する1つの場所
- 構造の独立性とフロントエンドコードの変更の容易さ
- テンプレートにデプロイロジックはありません
- 簡単な依存関係管理、重複の最小化
- タイプミスの場合の明示的なエラーメッセージ
- セマンティクスの存在
解決策
- リソースを接続する単一の場所。 静的リソースを接続できるプロジェクト内の場所を厳密に決定する必要があります。 適切な場所はテンプレートだけだと思います。 なんで? 原則として、1つまたは別のマークアップブロックは、対応するスタイルとJavaスクリプトに関連付けられます。 この接続を同じテンプレートで定義することは論理的です。 最後に、テンプレートコード外のファイルの接続を禁止することを提案します。
- セマンティクスの存在。 ファイルやリソースのリストを操作するよりも、特定のエンティティを操作する方が簡単です。 したがって、接続の単位は、テンプレートの外部で定義されたブロックの名前になります。 この名前は、構成や物理的な場所ではなく、接続の本質を反映する必要があります。 名前の例:lib / jquery、lib / twitter-bootstrap、reset、blog-module / main、blog-module / photos、plugin / cool-oneなど
- 依存関係の説明と重複の最小化。 なぜなら ブロックの名前を参照します。これらのブロックを説明する場所が必要です。 読みやすい設定形式(YAMLなど)を使用して、いわゆる「静的リソースマップ」を記述することをお勧めします。
reset: - fw/css/reset.css lib/underscore: - libs/underscore/underscore.js options: - useCdn lib/jquery: - libs/jquery/jquery-1.7.2.min.js options: - useCdn lib/twitter-bootstrap: - libs/bootstrap/css/bootstrap.css - libs/bootstrap/js/bootstrap.js - css/bootstrap-override.css depends: - lib/jquery framework/core: - fw/js/Tiks.js - fw/js/Classes.js - fw/js/EventsManager.js - fw/js/Core.js - fw/js/CorePublic.js - fw/js/ModulesManager.js - fw/js/Module.js - fw/js/ModuleSandbox.js depends: - lib/underscore - lib/jquery options: - merge module/blog: - js/modules/blog.js - css/modules/blog.css depends: - framework/core - lib/twitter-bootstrap
これで、ブログテンプレートで接続する必要があります。
StaticInclude("module/blog")
すべての依存関係は、正しい順序で自分自身を引き上げます。 重複は1回のみ接続します(例: lib / jquery )。
- 展開ロジックはありません。 静的リソースのデプロイ方法は、バックエンドアプリケーション/フレームワークコードによって決定する必要があります。 そこで、任意の戦略(合併、縮小、CDNからの復帰など)を適用できます。 これを管理するために、構成形式を拡張できます。
- 1つのテンプレート-1つの「含む」。 テンプレートが静的リソースを接続する必要がある場合は、一目でわかる名前でこれを実行することをお勧めします。 「library + my file」や「general_module + modification」などの接続のブロックを開始するのを怠らないでください。 1つのテンプレートで2つ以上の接続を使用する場合、テンプレート自体に依存関係の説明を追加して、最初の例の問題に戻ります。
- フロントエンドコードの独立性と変更の容易さ 。 ブロック、分割、移動などに新しいファイルを簡単に追加できるようになりました。 ただし、テンプレートに変更を加える必要はありません。
- タイプミスの場合のエラー。 はい ブロックがテンプレートで接続されているか、ブロックが依存関係として静的リソースマップで定義されていない別のブロックを使用している場合、明示的なエラーメッセージが表示されます。 そのため、特定の接続が正しいことを常に確認できます。
有用な慣行
- 「マップ」全体を1つのファイルに保存する必要はありません。 ブロックが多数ある場合、マップをlibs.yaml、framework、yaml、my-module.yaml、my-component.yamlなどのエンティティに分割するのが理にかなっています。
- 「マップ」設定形式を展開します。 .lessファイルなどのさまざまな機能を追加し、生成されたリソース(たとえば、JSモジュール記述子、JSONP経由のローカリゼーションファイル)をマップ機能にロードします。 とても便利です。
結論として、私はこのアプローチを個人的なプロジェクトでうまく使用し、現在のプロジェクトに徐々に導入していると言いたいと思います。
それを読んだすべての人に感謝します。 コメントや提案があれば嬉しいです!