javascript / coffeescriptプロジェクトの自動ビルド

少なくとも大規模なjavascriptプロジェクトを開発する場合、すぐにすべてのコードを単一のファイルに書くことができないことに気付きます。 その後、コードは複数のファイルとディレクトリに分散され、これらすべてのファイルを1つの大きな本番ファイルに簡単に結合できるように、簡単なスクリプトが記述されます。 しばらくすると、ファイル間の依存関係を監視することが難しくなり、開発されたメカニズム全体が松葉杖に似ていることに気づき始めます。 そして、この問題に対してどのような解決策が存在するのかを見るといいというインスピレーションがあります。



プロジェクトアセンブリ制御システムには、次の要件が課されています。

  1. coffescriptからjavascriptへのコンパイル。 coffeescriptファイルにエラーが含まれている場合、ファイル名とエラーメッセージがコンソールに表示されます。
  2. プロジェクトを1つのjavascriptファイルにアセンブルするには、依存関係に基づく必要があります。
  3. アプリケーション全体を複数の形式で単一のファイルにアセンブルする機能(コメント付き、最小化)。 さらに、アプリケーション自体は複数のモジュールで構成されている場合があります。
  4. テストファイルをアセンブルし、コンソールで実行します(はい、Web用に開発していますが、マウスに触れず、お気に入りのvimからクロールしません)。
  5. もちろん、これはすべて使いやすいはずです。


この記事では、テストの問題には対処しませんが、rakeおよびRake :: Pipeline( git )を使用して、プロジェクトのjavascript / coffescriptアセンブリ制御システムのバリアント(およびプロジェクト構造自体)を検討します。



Rake :: Pipelineはファイル処理システムです。 彼女は、指定されたテンプレートに従ってディレクトリからファイルを読み取り、指定されたルールに従ってファイルを変更し、結果を書き込むことができます。



ご想像のとおり、Rake :: Pipelineはrakeを使用しているため、動作させるにはrubyが必要です。 通常、すべてのパイプライン設定はAssetfileファイルに保存されます。 このファイルはルビースクリプトです。 たとえば、次の形式を使用できます。

#  Assetfile #        input "app/assets/javascripts" #         output "public/javascripts" #         , #  input.          #*.js,      "app/assets/javascripts" match "*.js" do #ConcatFilter - ,      . #    *.js    app/assets/javascripts #   application.js,      #public/javascripts. filter Rake::Pipeline::ConcatFilter, "application.js" end
      
      







たとえば、「アプリケーション」というプロジェクトを考えてみましょう。 このプロジェクトは、「file1.coffee」、「file2.coffee」、「file3.coffee」の3つのcoffeescriptファイルで構成されます。 したがって、次のディレクトリ構造を取得します。



-アプリケーション

--src

---file1.coffee

---file2.coffee

---file3.coffee



次の依存関係があるとします。

2番目は1番目と3番目に依存

3番目は1番目に依存

したがって、アセンブルされたバージョンでは、ファイルは次の順序で配置する必要があります。

便宜上、メインファイル「main.coffee」を作成します。 プロジェクトで使用されるファイルのリストが含まれます。 これで、ファイルの入力を開始できます。



 # main.coffee (     ): require("file1") require("file2") require("file3") # file1.coffee (    ): # ...  ... file1 = true #   # file2.coffee (  1-  3-): require("file1") require("file3") # ...  ... file2 = true #   # file3.coffee (  1-): require("file1") # ...  ... file3 = true #  
      
      







この場合、require( "file1")は擬似関数です。 より正確には、これはテンプレートであり、最初のファイルが動作する必要があるという事実へのポインターです。 require( "file1")の代わりに以下を書く必要があるように設定できます:

  ! , , file1.   , .
      
      





つまり、ファイル接続構文は任意の方法で作成できます。 たとえば、コメントに依存関係を指定できます。 これにより、CSSファイルの処理などにパイプラインを使用できます。



私たちの場合、2番目のファイルは1番目と3番目のファイルに依存するため、main.coffeeファイルでは、require( "file2")という1行のみを書き込むことができます。 残りのファイルは自動的に接続するはずです。



構造を把握しましたが、これをすべて収集する必要があります。 これを行うには、プロジェクトのルートに次の内容のGemfileを作成します。

 #  Gemfile source "http://rubygems.org" gem "rake-pipeline", :git => "https://github.com/livingsocial/rake-pipeline.git" gem "rake-pipeline-web-filters", :git => "https://github.com/wycats/rake-pipeline-web-filters.git" gem "uglifier", :git => "https://github.com/lautis/uglifier.git" group :development do gem "rack" gem "github_downloads" gem "coffee-script" end
      
      





ここで、rake-pipeline-web-filtersは補助ライブラリです。特に、coffeスクリプトを処理するためのクラスが含まれています。 uglifierは、javascriptを最小化するライブラリです。



次に、Rakefileを作成します。

 #  Rakefile abort "Please use Ruby 1.9 to build application!" if RUBY_VERSION !~ /^1\.9/ require "bundler/setup" def pipeline require 'rake-pipeline' Rake::Pipeline::Project.new("Assetfile") end task :dist do puts "build application" pipeline.invoke puts "done" end task :default => :dist
      
      







ここでRake :: Pipeline :: Project.new(「Assetfile」)-新しいオブジェクトが作成されます。「Assetfile」は、まだ設定されていないビルド設定を持つファイルですが、今から作成します。



すぐに、コンパイル済みファイルのルートディレクトリを登録できます。 このパスは「ターゲット」になります。

 #  Assetfile output "target"
      
      







プロジェクトの組み立ては2段階で行います。 最初に、すべてのcoffescriptファイルをjavascriptにコンパイルしてから、プロジェクト自体をコンパイルします。



JavaScriptでのコンパイル



「target / src」ディレクトリにコンパイルします。 さらに、各ファイル.coffeには独自のファイル.jsがあります(つまり、この段階ではファイルをマージしません)。 これを行うには、「Assetfile」に次の行を追加します



 #  Assetfile #      "src" input "src" do #    *.coffee (   "src") match "**/*.coffee" do require "rake-pipeline-web-filters" #       javascript filter Rake::Pipeline::Web::Filters::CoffeeScriptFilter do |filename| # ,       ( ) #  js . #         "src"  "target" #       '.coffee'  '.js' File.join("src/", filename.gsub('.coffee', '.js')) end end end
      
      







rakeコマンドを実行すると、javascriptでコンパイルされたプロジェクトバージョンが「target / src / lib」ディレクトリに作成されます。 この場合、ファイルの1つをコンパイルできない場合、エラーメッセージが表示されます。



javascriptプロジェクトをビルドする



今回は、「src / lib」ディレクトリからすでにコンパイルされたjsファイルを読み取ります。

 #  Assetfile #   ,      name="application" #     "target/src" input "target/src" do #  main.js  match "main.js" do #   NeuterFilter. #      ,  #       . neuter( # ,   . :additional_dependencies => proc { |input| #       ,   main Dir.glob(File.join(File.dirname(input.fullpath),'**','*.js')) }, #     :path_transform => proc { |path, input| #    require("file1")   #   .    . #  require("file1")   require("file1.js") "#{path}.js" }, # ,         js- :closure_wrap => false ) do |filename| "#{name}.js" end end end
      
      







rakeコマンドを実行すると、ファイル「application.js」がディレクトリ「src」に次の内容で表示されます。

 #  application.js (function() { var file1; file1 = true; }).call(this); (function() { var file3; file3 = true; }).call(this); (function() { require("file2"); }).call(this);
      
      







しかし、ちょっと待ってください! ここで行っている行は何ですか

 require("file2");
      
      





? 結局、彼女は姿を消さなければならなかった。 これは、明らかに「中性」フィルターエラーです。 このフィルターのソースコード( code )を見てみましょう。 ここの行に興味があります:



 #  neuter_filter.rb regexp = @config[:require_regexp] || %r{^\s*require\(['"]([^'"]*)['"]\);?\s*}
      
      





ご覧のとおり、必要なファイルの名前でテキストを識別するための独自のルールがパラメーターで指定されていない場合、デフォルトで正規表現が使用されます

 %r{^\s*require\(['"]([^'"]*)['"]\);?\s*}
      
      





残念ながら、そのような正規表現が最初の要求のみを処理し、2番目の要求は気付かない理由を理解できませんでした。 あなたが問題が何であるかを説明するならば、私は非常に感謝するでしょう。 この問題を次のように解決しました。

 #  Assetfile input "target/src" do match "main.js" do neuter( .... :closure_wrap => false, :require_regexp => %r{^\s*require\(['"]([^'"]*)['"]\);?\s*$} ...
      
      





表示される「$」記号に注意してください。 つまり、正規表現を行末に限定しました。 その後、コンパイルされたファイルは次のようになります。

 #  application.js (function() { var file1; file1 = true; }).call(this); (function() { var file3; file3 = true; }).call(this); (function() { var file2; file2 = true; }).call(this); (function() { }).call(this);
      
      







シック(ファイルの順序に注意してください)。 このすべてを別の大きなjavascript関数でラップしたい場合(理由はわかりませんが、知らない)、次のことができます。 独自のフィルターを作成します。



 #  Assetfile class ClosureFilter < Rake::Pipeline::Filter def generate_output(inputs, output) inputs.each do |input| # output.write "(function() {\n#{input.read}\n})()" end end end
      
      





そして、このフィルターは、中性フィルターを適用した後に指定されたままになります

 #  Assetfile input "target/src" do match "main.js" do neuter( ............. ) do |filename| "#{name}.js" end filter ClosureFilter end end
      
      







今、すべてが大丈夫です。 最小化されたバージョンのアプリケーションを作成することだけが残っています。 これを行うには、5行のみを記述します。

 #  Assetfile input "target" do match "#{name}.js" do # uglify -    uglify{ "#{name}.min.js" } end end
      
      





これで、コンパイル中に、「application.js」に加えて、「application.min.js」というファイルがコンテンツとともに作成されます。

 (function(){(function(){var e;e=!0}).call(this),function(){var e;e=!0}.call(this),function(){var e;e=!0}.call(this),function(){}.call(this)})();
      
      







Assetfileの最終バージョン

 #  Assetfile require "json" require "rake-pipeline-web-filters" name="application" output "target" input "src" do match "**/*.coffee" do filter Rake::Pipeline::Web::Filters::CoffeeScriptFilter do |filename| File.join("src/", filename.gsub('.coffee', '.js')) end end end class ClosureFilter < Rake::Pipeline::Filter def generate_output(inputs, output) inputs.each do |input| output.write "(function() {\n#{input.read}\n})()" end end end input "target/src" do match "main.js" do neuter( :additional_dependencies => proc { |input| Dir.glob(File.join(File.dirname(input.fullpath),'**','*.js')) }, :path_transform => proc { |path, input| "#{path}.js" }, :closure_wrap => false, :require_regexp => %r{^\s*require\(['"]([^'"]*)['"]\);?\s*$} ) do |filename| "#{name}.js" end filter ClosureFilter end end input "target" do match "#{name}.js" do uglify{ "#{name}.min.js" } end end # vim: filetype=ruby
      
      







プロジェクトの構造にネストされたディレクトリが含まれる場合があることに注意してください。 サブディレクトリからファイルを接続する必要がある場合は、指定する必要があります

 require("dir_name/file_name")
      
      





また、独自のフィルタを作成することもできます。たとえば、ライセンステキスト、バージョン番号、最後のコミットの日付と時刻、アセンブリ時の都市の温度と湿度などに置き換えます。



興味深い場合は、次の記事で、phantom.js(コンソールからのテスト)を使用してjavascriptテストを整理し、ビルド段階でテンプレートファイルを接続する方法を示します。



All Articles