12/08/2018, 10:59

Rails Assets PipeLine - part 2

Ở phần 1 chúng ta đã làm rõ một số khái niệm liên quan tới precompile assets. Tiếp theo chúng ta sẽ cùng nhau làm rõ về quá trình xử lý precompile assets. Quá trình xử lý compile assets khi thực hiện rake assets:precompile như sau: Bước 1: Gọi tới task precompile đã được build trong file ...

Ở phần 1 chúng ta đã làm rõ một số khái niệm liên quan tới precompile assets. Tiếp theo chúng ta sẽ cùng nhau làm rõ về quá trình xử lý precompile assets.

Quá trình xử lý compile assets khi thực hiện rake assets:precompile như sau:

Bước 1: Gọi tới task precompile đã được build trong file task.rb của sprockets-rails. Nội dung của rake task:

desc "Compile all the assets named in config.assets.precompile"
task :precompile => :environment do
  with_logger do
    manifest.compile(assets)
  end
end

manifest là một instance của Sprockets::Manifest có nhiệm vụ lưu lại quá trình compile asset vào một thư mục compile định sẵn. Nó sẽ lưu những thông tin cơ bản asset phục vụ cho việc tìm kiếm nhanh mà không cần phải compile lại lần nữa. File này là một file json data, vì thế nên việc tìm kiếm sẽ rất nhanh. Nội dung file sẽ có dạng sau:

{
  "files":{
    "full_calendar-94f2588c650c3dbc9d62f7cd6ef25167a4407e9d6ddb091fce946dd186255637.js":{
      "logical_path":"full_calendar.js",
      "mtime":"2015-07-22T21:32:24+07:00",
      "size":1102019,
      "digest":"94f2588c650c3dbc9d62f7cd6ef25167a4407e9d6ddb091fce946dd186255637",
      "integrity":"sha256-lPJYjGUMPbydYvfNbvJRZ6RAfp1t2wkfzpRt0YYlVjc="
    },
    "select2-d6b5d8d83dbc18fb8d77c8761d331cd9e5123c9684950bab0406e98a24ac5ae8.png":{
      "logical_path":"select2.png",
      "mtime":"2015-05-23T23:53:35+07:00",
      "size":613,
      "digest":"d6b5d8d83dbc18fb8d77c8761d331cd9e5123c9684950bab0406e98a24ac5ae8",
      "integrity":"sha256-1rXY2D28GPuNd8h2HTMc2eUSPJaElQurBAbpiiSsWug="
    },
    ...
  }
}

assets là một array chứa thông tin những asset sẽ được compile. Chúng ta có thể kiểm tra thông tin assets bằng cách sử dụng command Rails.application.config.assets.precompile

 => [#<Proc:0x007fdd0149f7d8@/Users/justin/.rvm/gems/ruby-2.1.5/gems/sprockets-rails-2.3.1/lib/sprockets/railtie.rb:60 (lambda)>, /(?:/||A)application.(css|js)$/, "schedules.js,", "full_calendar.js", /bootstrap/glyphicons-halflings-regular.(?:eot|svg|ttf|woff)$/, "select2.png", "select2-spinner.gif", "select2x2.png", "rails_admin/rails_admin.js", "rails_admin/rails_admin.css", "rails_admin/jquery.colorpicker.js", "rails_admin/jquery.colorpicker.css"]

Bước 2 Thực hiện compile bởi function compile trong class Sprockets::Manifest. Sẽ có nhiều phần xử lý con ở trong bước này.

def compile(*args)
  unless environment
    raise Error, "manifest requires environment for compilation"
  end

  filenames = []

  find(*args) do |asset|
    files[asset.digest_path] = {
      'logical_path' => asset.logical_path,
      'mtime'        => Time.now.iso8601,
      'size'         => asset.bytesize,
      'digest'       => asset.hexdigest,

      # Deprecated: Remove beta integrity attribute in next release.
      # Callers should DigestUtils.hexdigest_integrity_uri to compute the
      # digest themselves.
      'integrity'    => DigestUtils.hexdigest_integrity_uri(asset.hexdigest)
    }
    assets[asset.logical_path] = asset.digest_path

    target = File.join(dir, asset.digest_path)

    if File.exist?(target)
      logger.debug "Skipping #{target}, already exists"
    else
      logger.info "Writing #{target}"
      asset.write_to target
    end

    filenames << asset.filename
  end
  save

  filenames
end

Instance Sprockets::Manifest sẽ gọi function initialize. Hàm này có chức năng là tìm kiếm file json data mà chúng ta đã đề cập ở trên, nếu có sẽ trả về dữ liệu bên trong sau khi được json_decode đồng thời cũng assign giá trị cho instance variables. Các bạn có thể xem nội dung hàm này tại đây

Như chúng ta thấy, function này sẽ gọi tới find(*args), là một instance method của Sprockets::Manifest, hàm này sử dụng instance của Sprockets::CachedEnvironment để tìm kiếm asset thông qua logical path và sau đó trả về Enumerator của Assets. Cụ thể:

  • Xử lý tham số truyền vào để phân tách thành các paths là danh sách logical path
  • Tìm kiếm asset dựa vào các logical path bởi function environment.find_all_linked_assets(path) và trả về các Enumerator object tương ứng.

Ví dụ một asset được trả về với logical path là: full_calendar.js được convert sang array như sau:

  [#<Sprockets::Asset:3fe46495f71c "file:///Users/justin/workspaces/rails/crb/app/assets/javascripts/full_calendar.js?type=application/javascript&id=4d9313759f41141eac4cfd865662234123da09642b044adfbd3a55a3fcc0055e">]

Đây là một Sprockets::Asset object, và nó được build trong quá trình thực hiện function load(uri) trong module loader. Có 2 trường hợp xảy ra:

  • Load từ trong cache ra nếu asset đã tồn tại. Đây cũng là lý do tại sao khi chúng ta compile lại một assets đã compile trước đó mà không có thay đổi thì gì chạy rất nhanh.
  • Gọi load_asset_by_uri(uri, filename, params). Đây là quá trình sử dụng các processors để nén, build asset và lưu vào cache.
def load(uri)
  filename, params = parse_asset_uri(uri)
  if params.key?(:id)
    unless asset = cache.get("asset-uri:#{VERSION}:#{uri}", true)
      id = params.delete(:id)
      uri_without_id = build_asset_uri(filename, params)
      asset = load_asset_by_uri(uri_without_id, filename, params)
      if asset[:id] != id
        @logger.warn "Sprockets load error: Tried to find #{uri}, but latest was id #{asset[:id]}"
      end
    end
  else
    asset = fetch_asset_from_dependency_cache(uri, filename) do |paths|
      if paths
        digest = digest(resolve_dependencies(paths))
        if id_uri = cache.get("asset-uri-digest:#{VERSION}:#{uri}:#{digest}", true)
          cache.get("asset-uri:#{VERSION}:#{id_uri}", true)
        end
      else
        load_asset_by_uri(uri, filename, params)
      end
    end
  end
  Asset.new(self, asset)
end

Bước 3 Ghi asset vào thư mục mount point. Toàn bộ asset sẽ được lưu vào mount point khi gọi tới function write_to như bên dưới.

def write_to(filename)
  FileUtils.mkdir_p File.dirname(filename)

  PathUtils.atomic_write(filename) do |f|
    f.write source
  end

  nil
end

Như vậy mình đã trình bày xong quá trình xử lý bên trong khi thực hiện precompile assets.

0