Asset Pipeline

Это руководство раскрывает файлопровод (asset pipeline).

Обратившись к этому руководству, вы узнаете:

  • Что такое файлопровод, и зачем он нужен.
  • Как должным образом организовывать ассеты своего приложения.
  • Преимущества файлопровода.
  • Как добавить препроцессор к файлопроводу.
  • Как упаковывать ассеты в гем.

1. Что такое файлопровод (Asset Pipeline)?

Файлопровод представляет фреймворк для соединения и минимизации или сжатия ассетов JavaScript и CSS. Он также добавляет возможность писать эти ассеты на других языках и препроцессорах, таких как CoffeeScript, Sass и ERB. Это позволяет комбинировать ассеты вашего приложения с ассетами других гемов. Например, jquery-rails содержит копию jquery.js и включает особенности AJAX в Rails.

Файлопровод реализован в геме sprockets-rails и включен по умолчанию. Можно отключить файлопровод при создании нового приложения, передав опцию --skip-sprockets.

rails new appname --skip-sprockets

Rails автоматически добавляет гемы sass-rails, coffee-rails и uglifier в ваш Gemfile, которые используются Sprockets для компрессии ассетов:

gem 'sass-rails'
gem 'uglifier'
gem 'coffee-rails'

Использование опции --skip-sprockets предотвратит Rails от их добавления в Gemfile, поэтому, если вы позже решите включить файлопровод, будет необходимо добавить эти гемы в Gemfile. Также, создание приложения с опцией --skip-sprockets сгенерирует немного иной файл config/application.rb, с закомментированным выражением требования sprockets railtie. Необходимо раскомментировать эту строчку, чтобы в дальнейшем включить файлопровод:

# require "sprockets/railtie"

Чтобы установить методы компрессии ассетов, установите соответствующие конфигурационные опции в production.rb - config.assets.css_compressor для CSS и config.assets.js_compressor для JavaScript:

config.assets.css_compressor = :yui
config.assets.js_compressor = :uglifier

Гем sass-rails автоматически используется для сжатия CSS, если он подключен в Gemfile, опцию config.assets.css_compressor устанавливать не нужно.

1.1. Основные особенности

Первой особенностью файлопровода является соединение ассетов, что может уменьшить количество запросов, необходимых браузеру для отображения страницы. Браузеры ограничены в количестве запросов, которые они могут выполнить параллельно, поэтому меньшее количество запросов может означать более быструю загрузку вашего приложения.

Sprockets соединяет все JavaScript файлы в один главный файл .js и все CSS файлы в один главный файл .css. Как будет сказано далее в этом руководстве, можно настроить эту стратегию, сгруппировав файлы любым способом. В production, Rails вставляет метку SHA256 в каждое имя файла, таким образом файл кэшируется браузером. Кэш можно сделать недействительным, изменив эту метку, что происходит автоматически каждый раз, когда изменяется содержимое файла.

Второй особенностью файлопровода является минимизация или сжатие ассетов. Для файлов CSS это выполняется путем удаления пробелов и комментариев. Для JavaScript могут быть применены более сложные процессы. Можно выбирать из набора встроенных опций или определить свои.

Третьей особенностью файлопровода является то, что он позволяет писать эти ассеты на языке более высокого уровня с дальнейшей прекомпиляцией до фактического ассета. Поддерживаемые языки по умолчанию включают Sass для CSS, CoffeeScript для JavaScript и ERB для обоих.

1.2. Что за метки и зачем они нужны?

Метки (fingerprinting) - это техника, реализующая зависимость имени файла от его содержимого. При изменении содержимого файла, имя файла также изменяется. Для статичного или нечасто обновляемого содержимого это предоставляет легкий способ сказать, идентичны ли две версии файла, даже если они на разных серверах, или имеют различную дату размещения.

Когда имя файла уникально и основано на его содержимом, заголовками HTTP можно установить повсеместное кэширование (в CDN, у провайдера, в сетевом оборудовании или браузере), чтобы у них была собственная копия содержимого. Когда содержимое изменяется, метка тоже изменится. Это приведет к тому, что удаленные клиенты запросят новую копию содержимого. Эта техника известна как cache busting.

Техникой, используемой Sprockets для меток, является вставка хэша содержимого в имя, обычно в конце. Например, файл CSS global.css:

global-908e25f4bf641868d8683022a5b62f54.css

Это стратегия, принятая файлопроводом Rails.

Прежней стратегией Rails было добавление строки запроса, основанной на дате, к каждому ассету, присоединенному с помощью встроенного хелпера. В исходнике сгенерированный код выглядел так:

/stylesheets/global.css?1309495796

У стратегии, основанной на строке запроса, имелось несколько недостатков:

  • Не все кэши надежно кэшировали содержимое, когда имя файла отличалось только параметрами строки запроса

    Steve Souders рекомендует, "...избегать строки запросов для кэшируемых ресурсов". Он обнаружил, что в этом случае 5-20% запросов не будут закэшированы. В частности, строки запроса совсем не работают с некоторыми сетями доставки контента (CDN) для инвалидации кэша.

  • Имя файла может быть разным на разных узлах в мультисерверных окружениях.

    По умолчанию, строка запроса в Rails 2.x основывается на времени изменения файлов. Когда ассеты размещаются в кластер, нет никакой гарантии, что временная метка будет одной и той же, в результате будут использованы различные значения в зависимости от того, какой сервер будет обрабатывать запрос.

  • Слишком много прекращенного кэша

    При размещении статичных ассетов с каждым новым релизом кода, mtime (время последнего изменения) всех этих файлов изменялось, принуждая всех удаленных клиентов получать их снова, даже если содержимое этих ассетов не менялось.

Метки исправляют эти проблемы, избегая строки запроса и обеспечивая то, что имя файла основывается на его содержимом.

По умолчанию метки включены для сред development и production. Их можно включить или отключить в конфигурации с помощью опции config.assets.digest.

Более подробно:

2. Как использовать файлопровод (Asset Pipeline)

В прежних версиях Rails, все ассеты были расположены в субдиректориях public, таких как images, javascripts и stylesheets. Сейчас, с файлопроводом, предпочтительным местом размещения для этих ассетов стала директория app/assets. Файлы в этой директории отдаются промежуточной программой Sprockets.

Ассеты все еще могут быть размещены в public. Любой ассет в public будет отдан как статичный файл приложением или веб-сервером, когда config.public_file_server.enabled установлена true. Следует использовать app/assets для файлов, которые должны пройти некоторую предварительную обработку перед тем, как будут отданы.

По умолчанию в production Rails прекомпилирует эти файлы в public/assets. Прекомпилированные копии затем отдаются веб-сервером как статичные ассеты. Файлы в app/assets никогда не отдаются напрямую в production.

2.1. Ассеты конкретного контроллера

При генерации скаффолда или контроллера, Rails также генерирует файл JavaScript (или файл CoffeeScript, если гем coffee-rails имеется в Gemfile) и файл CSS (или файл SCSS, если sass-rails имеется в Gemfile) для этого контроллера. Дополнительно при генерации скаффолда, Rails генерирует файл scaffolds.css (или scaffolds.scss, если sass-rails находится в Gemfile.)

Например, если генерируете ProjectsController, Rails также добавит новый файл app/assets/javascripts/projects.coffee и еще один app/assets/stylesheets/projects.scss. По умолчанию эти файлы будут готовы к немедленному использованию вашим приложением, с помощью директивы require_tree. Смотрите Файлы манифеста и директивы о подробностях require_tree.

Опционально можно включить таблицы стилей и файлы JavaScript конкретного контроллера только для их соответствующих контроллеров, используя следующее:

<%= javascript_include_tag params[:controller] %> или <%= stylesheet_link_tag params[:controller] %>.

При этом убедитесь, что не используете директиву require_tree, так как она приведет к тому, что ассеты будут включены более одного раза.

При использовании прекомпиляции ассетов, необходимо убедиться, что ассеты контроллера будут прекомпилированы при варианте загрузки их на основе страницы. По умолчанию файлы .coffee и .scss не будут прекомпилированы отдельно. Смотрите Прекомпиляция ассетов о подробностях работы прекомпиляции.

Вам необходим runtime, поддерживаемый ExecJS, чтобы использовать CoffeeScript. Если используете macOS или Windows, у вас уже имеется JavaScript runtime, установленный в операционной системе. Обратитесь к документации по ExecJS, чтобы узнать обо всех поддерживаемых JavaScript runtime-ах.

Отключить генерацию ассетов при генерации контроллера можно, добавив следующее в конфигурацию config/application.rb:

  config.generators do |g|
    g.assets false
  end

2.2. Организация ассетов

Ассеты файлопровода могут быть размещены в приложении в одном из этих трех мест: app/assets, lib/assets или vendor/assets.

app/assets предназначено для ассетов, принадлежащих приложению, таких как изображения, файлы JavaScript или таблицы стилей, изготовленные специально для приложения.

lib/assets предназначено для кода ваших собственных библиотек, которые не вписываются в сферу применения приложения или эти библиотеки используются в нескольких приложениях.

vendor/assets предназначено для ассетов, принадлежащих сторонним субъектам, таких как код плагинов JavaScript и фреймворки CSS. Имейте в виду, что код третьей стороны со ссылками на другие файлы, также обрабатывающиеся файлопроводом (изображения, таблицы стилей и так далее), должен быть переписан с помощью хелперов, таких как asset_path.

Если вы обновляетесь с Rails 3, примите во внимание, что ассеты в lib/assets или vendor/assets доступны для включения в манифестах приложения, но больше не являются частью массива прекомпиляции. Что делать в этом случае, смотрите Прекомпиляция ассетов.

2.2.1. Пути поиска

Когда к файлу обращаются из манифеста или хелпера, Sprockets ищет в трех дефолтных местах размещения ассетов для этого файла.

Дефолтные места следующие: директории images, javascripts и stylesheets в папке app/assets, но эти поддиректории не особенные - поиск будет вестись по любому пути в assets/*.

Для примера, на эти файлы:

app/assets/javascripts/home.js
lib/assets/javascripts/moovinator.js
vendor/assets/javascripts/slider.js
vendor/assets/somepackage/phonebox.js

можно сослаться в манифесте таким образом:

//= require home
//= require moovinator
//= require slider
//= require phonebox

Ассеты в поддиректориях также доступны.

app/assets/javascripts/sub/something.js

доступен как:

//= require sub/something

Можно просмотреть путь поиска, проинспектировав Rails.application.config.assets.paths в консоли Rails.

Помимо стандартных путей assets/* в файлопровод могут быть добавлены дополнительные (полностью ограниченные) пути в config/initializers/assets.rb. Например:

Rails.application.config.assets.paths << Rails.root.join("lib", "videoplayer", "flash")

Пути обходятся в том порядке, в котором они выводятся в пути поиска. По умолчанию это означает, что имеют преимущество файлы в app/assets, они перекроют соответствующие пути в lib и vendor.

Важно заметить, что если хотите сослаться на что-то еще, в прекомпилированный массив должен быть добавлен манифест, или оно не будет доступно в среде production.

2.2.2. Использование индексных файлов

Sprockets использует файлы с именем index (с соответствующим расширением) для специальных целей.

Например, если имеется библиотека jQuery с множеством модулей, хранящаяся в lib/assets/javascripts/library_name, файл lib/assets/javascripts/library_name/index.js служит манифестом для всех файлов в этой библиотеке. Этот файл может включать список всех требуемых файлов в нужном порядке, или просто директиву require_tree.

Библиотека в целом может быть доступна из манифеста приложения следующим образом:

//= require library_name

Это упрощает поддержку и сохраняет чистоту, позволяя коду быть сгруппированным перед включением где-нибудь еще.

2.3. Кодирование ссылок на ассеты

Sprockets не добавляет какие-либо новые методы для доступа к вашим ассетам - используйте знакомые методы javascript_include_tag и stylesheet_link_tag.

<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application" %>

При использовании гема turbolinks, который включен по умолчанию в Rails, включите опцию 'data-turbolinks-track', которая вызывает проверку turbolinks, что ассет был обновлен, таким образом загружая его на страницу:

<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => "reload" %>
<%= javascript_include_tag "application", "data-turbolinks-track" => "reload" %>

В обычных вьюхах можно получить доступ к изображениям в директории app/assets/images следующим образом:

<%= image_tag "rails.png" %>

При условии, что файлопровод включен в вашем приложении (и не отключен в контексте текущей среды), этот файл будет отдан с помощью Sprockets. Если файл существует в public/assets/rails.png, он будет отдан веб-сервером.

Кроме того, запрос файла с хэшем SHA256, такого как public/assets/rails-f90d8a84c707a8dc923fca1ca1895ae8ed0a09237f6992015fef1e11be77c023.png будет обработан тем же образом. Как генерируются эти хэши будет раскрыто позже в этом руководстве в разделе В production.

Sprockets также будет смотреть среди путей, определенных в config.assets.paths, включающих стандартные пути приложения и любые пути, добавленные engine-ами Rails.

Изображения также могут быть организованы в субдиректории и могут быть доступны с помощью указания имени директории в теге:

<%= image_tag "icons/rails.png" %>

Если вы прекомпилируете ассеты (смотрите раздел В production далее), связывание с несуществующим ассетом вызовет исключение на вызывающей странице. Это также справедливо и для связывания с пустой строкой. Поэтому будьте осторожны при использовании image_tag и других хелперов с данными, предоставленными пользователями.

2.3.1. CSS и ERB

Файлопровод автоматически вычисляет ERB. Это означает, что, если добавить расширение erb к ассету CSS (например, application.css.erb), будут доступны хелперы, такие как asset_path, в правилах вашего CSS:

.class { background-image: url(<%= asset_path 'image.png' %>) }

Этот фрагмент кода записывает путь к определенному указанному ассету. Этот пример имеет смысл если имеется изображение в одном из путей загрузки ассетов, такое как app/assets/images/image.png, на которое тут будет ссылка. Если это изображение уже имеется в public/assets как файл с меткой, то будет ссылка на него.

Если хотите использовать data URI - метод встраивания данных изображения непосредственно в файл CSS - используйте хелпер asset_data_uri.

#logo { background: url(<%= asset_data_uri 'logo.png' %>) }

Этот фрагмент кода вставит правильно отформатированный URI в код CSS.

Отметьте, что закрывающий тег не может быть стиля -%>.

2.3.2. CSS и Sass

При использовании файлопровода пути к ассетам должны быть переписаны. sass-rails предоставляет хелперы -url и -path (через дефис в Sass, через подчеркивание в Ruby) для следующих классов ассета: изображение, шрифт, видео, аудио, JavaScript и таблица стилей.

  • image-url("rails.png") возвращает url(/assets/rails.png)
  • image-path("rails.png") возвращает "/assets/rails.png".

Также может быть использована более общая форма:

  • asset-url("rails.png") возвращает url(/assets/rails.png)
  • asset-path("rails.png") возвращает "/assets/rails.png"
2.3.3. JavaScript/CoffeeScript и ERB

Если добавить расширение erb к ассету JavaScript, сделав его чем-то вроде application.js.erb, можно использовать хелпер asset_path в коде вашего JavaScript:

$('#logo').attr({ src: "<%= asset_path('logo.png') %>" });

Этот фрагмент кода записывает путь к определенному указанному ассету.

Подобным образом можно использовать хелпер asset_path в файлах CoffeeScript с расширением erb (т.е. application.coffee.erb):

$('#logo').attr src: "<%= asset_path('logo.png') %>"

2.4. Файлы манифеста и директивы

Sprockets использует файлы манифеста для определения, какие ассеты включать и отдавать. Эти файлы манифеста содержат директивы - инструкции, говорящие Sprockets, какие файлы требуются для создания отдельного файла CSS или JavaScript. С помощью этих директив Sprockets загружает указанные файлы, при необходимости их обрабатывает, соединяет в отдельный файл и затем сжимает их (основываясь на значении Rails.application.config.assets.js_compressor). При отдаче одного файла, а не нескольких, время загрузки страницы значительно уменьшается, поскольку браузер делает меньше запросов. Компрессия также уменьшает размер файла, что позволяет браузеру быстрее его скачать.

К примеру, новое приложение Rails включает дефолтный файл app/assets/javascripts/application.js, содержащий следующие строчки:

// ...
//= require rails-ujs
//= require turbolinks
//= require_tree .

В файлах JavaScript директивы начинаются с //=. В вышеприведенном примере файл использует директивы require и require_tree. Директива require используется, чтобы указать Sprockets на требуемые файлы. Здесь затребованы файлы rails-ujs.js и turbolinks.js, которые доступны где-то по пути поиска для Sprockets. Не нужно явно указывать расширение. Sprockets предполагает, что вы требуете файл .js, когда выполняется из файла .js.

Директива require_tree говорит Sprockets рекурсивно включить все файлы JavaScript в указанной директории в результирующий файл. Эти пути должны быть определены только относительно файла манифеста. Также можно использовать директиву require_directory, включающая все файлы JavaScript только в определенной директории, без рекурсии.

Директивы обрабатываются сверху вниз, но порядок, в котором файлы включаются с помощью require_tree не определен. Не следует полагаться на какой-то определенный порядок при ее использовании. Если хотите убедиться, что какой-то определенный JavaScript закончится до некоторого другого в объединенном файле, затребуйте нужный файл раньше в манифесте. Отметьте, что семейство директив require предотвращает повторное включения файлов в результирующий файл.

Rails также создает дефолтный файл app/assets/stylesheets/application.css, содержащий эти строчки:

/* ...
*= require_self
*= require_tree .
*/

Rails создает оба app/assets/javascripts/application.js и app/assets/stylesheets/application.css независимо от того, была ли выбрана опция --skip-sprockets при создании нового приложения Rails. Это для того, чтобы было легко добавить файлопровод в будущем, если захотите.

Директивы, работающие в файлах JavaScript, также работают в таблицах стилей (хотя, очевидно, включая таблицы стилей вместо JavaScript). В манифесте CSS директива require_tree работает так же, как и для JavaScript, включающая все таблицы стилей из текущей директории.

В этом примере использована require_self. Это помещает CSS, содержащийся в файле (если есть) в месте расположения вызова require_self.

Если хотите использовать несколько файлов Sass, как правило следует использовать правило Sass @import вместо директив Sprockets. При использовании директив Sprockets все файлы существуют в своей собственной области видимости, что делает переменные или примеси (mixins) доступными только в определяющем их документе.

Можно подключить несколько файлов с помощью @import "*", и добавить все дерево с помощью @import "**/*", что эквивалентно require_tree. Обратитесь к документации sass-rails за подробностями и важными предостережениями.

Можно иметь сколько угодно манифестов. Для примера, манифесты admin.css и admin.js могут содержать файлы JS и CSS, используемые для административного раздела приложения.

Применяются те же оговорки о порядке следования, что сделаны выше. В частности, можно определить отдельные файлы и порядок, в котором они будут компилироваться. Например, можно соединить три файла CSS вместе следующим образом:

/* ...
*= require reset
*= require layout
*= require chrome
*/

2.5. Предварительная обработка

Расширение, использованное для ассета, определяет, какая будет применена предварительная обработка. Когда генерируется скаффолд или контроллер с помощью дефолтного набора гемов Rails, сгенерируются файл CoffeeScript и файл SCSS вместо обычных файлов JavaScript и CSS. В использованном ранее примере был контроллер с именем "projects", который сгенерировал файлы app/assets/javascripts/projects.coffee и app/assets/stylesheets/projects.scss.

В режиме development или если отключен файлопровод, когда запрашиваются эти файлы, они обрабатываются процессорами, предоставленными гемами coffee-script и sass, а затем отдаются браузеру как JavaScript и CSS соответственно. Когда файлопровод включен, эти файлы обрабатываются и помещаются в директорию public/assets для раздачи или приложением Rails, или веб-сервером.

Может быть запрошен дополнительный уровень обработки, если добавить другое расширение, каждое расширение обрабатывается в порядке справа налево. Их следует использовать в том порядке, в котором должна быть применена обработка. Например, таблица стилей с именем app/assets/stylesheets/projects.scss.erb сначала обрабатывается как ERB, затем SCSS и, наконец, отдается как CSS. То же самое применимо к файлу JavaScript - app/assets/javascripts/projects.coffee.erb обрабатывается как ERB, затем CoffeeScript и отдается как JavaScript.

Помните, что порядок этих препроцессоров важен. Например, если вы вызовете свой файл JavaScript app/assets/javascripts/projects.erb.coffee, то он будет сначала обработан интерпретатором CoffeeScript, который не понимает ERB, и, следовательно, возникнут проблемы.

3. В development

В режиме development ассеты отдаются как отдельные файлы в порядке, в котором они определены в файле манифеста.

Этот манифест app/assets/javascripts/application.js:

//= require core
//= require projects
//= require tickets

сгенерирует этот HTML:

<script src="/assets/core.js?body=1"></script>
<script src="/assets/projects.js?body=1"></script>
<script src="/assets/tickets.js?body=1"></script>

Параметр body требуется Sprockets.

3.1. Вызов ошибки, если ассет не найден

Если используется sprockets-rails >= 3.2.0, можно настроить, что произойдет, когда выполнен поиск ассета, и ничего не было найдено. Если выключить "asset fallback", тогда будет вызвана ошибка, когда ассет не может быть найден.

config.assets.unknown_asset_fallback = false

Если "asset fallback" включен, тогда, когда ассет не может быть найден, вместо этого будет выведен путь, а не вызвана ошибка. Поведение "asset fallback" включено по умолчанию.

3.2. Отключение меток

Можно отключить метки, добавив в config/environments/development.rb:

config.assets.digest = false

Когда эта опция true, для URL ассета будет генерироваться метка.

3.3. Отключение отладки

Можно отключить режим отладки, обновив config/environments/development.rb, вставив:

config.assets.debug = false

Когда режим отладки отключен, Sprockets соединяет все файлы и запускает необходимые препроцессоры. С отключенным режимом отладки вышеуказанный манифест сгенерирует:

<script src="/assets/application.js"></script>

Ассеты компилируются и кэшируются при первом запросе после запуска сервера. Sprockets устанавливает HTTP-заголовок контроля кэша must-revalidate для уменьшения нагрузки на последующие запросы - на них браузер получает отклик 304 (Not Modified).

Если какой-либо из файлов в манифесте изменился между запросами, сервер возвращает новый скомпилированный файл.

Режим отладки также может быть включен в методе хелпера Rails:

<%= stylesheet_link_tag "application", debug: true %>
<%= javascript_include_tag "application", debug: true %>

Опция :debug излишняя, если режим отладки всегда включен.

Также можно включить сжатие в режиме development в качестве проверки на нормальность и отключать его по требованию, когда необходимо для отладки.

4. В production

В среде production Sprockets использует схему меток, описанную ранее. По умолчанию Rails полагает, что ассеты прекомпилированы и будут отданы как статичные ассеты вашим веб-сервером.

В течение фазы прекомпиляции из содержимого компилированных файлов генерируется SHA256 и вставляется в имена файлов, когда они записываются на диск. Эти имена меток используются хелперами Rails вместо имени манифеста.

Например, это:

<%= javascript_include_tag "application" %>
<%= stylesheet_link_tag "application" %>

сгенерирует что-то наподобие этого:

<script src="/assets/application-908e25f4bf641868d8683022a5b62f54.js"></script>
<link href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" media="screen"
rel="stylesheet" />

с Asset Pipeline опции :cache и :concat больше не используются, удалите эти опции из javascript_include_tag и stylesheet_link_tag.

Режим меток контролируется с помощью инициализационной опции config.assets.digest (которая по умолчанию true).

В нормальных обстоятельствах опция config.assets.digest по умолчанию не должна изменяться. Если нет дайджеста в именах файлов и установлены заголовки с вечным кэшированием, удаленные клиенты никогда не узнают, когда перезапросить файлы при изменении их содержимого.

4.1. Прекомпиляция ассетов

В Rails имеется встроенная задача для компиляции на диск манифестов ассетов и других файлов в файлопроводе.

Скомпилированные ассеты записываются в адрес, указанный в config.assets.prefix. По умолчанию это директория /assets.

Эту задачу можно вызвать на сервере во время деплоя, чтобы создать скомпилированные версии ассетов непосредственно на сервере. Смотрите следующий раздел, чтобы узнать о том, как скомпилировать локально.

Задача такая:

$ RAILS_ENV=production bin/rails assets:precompile

Capistrano (версии 2.15.1 и выше) включает рецепт для управления этим при деплое. Добавьте следующую строчку в Capfile:

load 'deploy/assets'

Это свяжет папку, указанную в config.assets.prefix с shared/assets. Если вы уже используете эту общую папку, вам следует написать собственную задачу для деплоя.

Важно то, что эта папка является общей между деплоями, так что удаленно кэшированные страницы, ссылающиеся на старые скомпилированные ассеты, все еще будут работать, пока не истечет срок кэширования.

По умолчанию компилирующиеся файлы включают application.js, application.css и все не-JS/CSS файлы (это автоматически включает все ассеты изображений) из папок app/assets, включая гемы:

[ Proc.new { |filename, path| path =~ /app\/assets/ && !%w(.js .css).include?(File.extname(filename)) },
/application.(css|js)$/ ]

Условие отбора (и другие части прекомпиляционного массива; смотрите выше) применяется к итоговым скомпилированным именам файлов. Это означает, что все, что компилируется в JS/CSS, исключается, так же, как и файлы с чистым JS/CSS; например, файлы .coffee и .scss не включаются автоматически, так как они компилируются в JS/CSS.

Если у вас имеются для включения другие манифесты или отдельные таблицы стилей или файлы JavaScript, их можно добавить в массив precompile в config/initializers/assets.rb:

Rails.application.config.assets.precompile += %w( admin.js admin.css )

Всегда определяйте ожидаемое имя скомпилированного файла, оканчивающееся на .js или .css, даже если хотите добавить в массив прекомпиляции файлы Sass или CoffeeScript.

Задача также генерирует .sprockets-manifest-md5hash.json (где md5hash - это хэш MD5), который содержит список всех ваших ассетов и соответствующие им метки. Это используется методами хелпера Rails, чтобы избежать направления запроса в Sprockets. Обычный файл манифеста выглядит так:

{"files":{"application-aee4be71f1288037ae78b997df388332edfd246471b533dcedaa8f9fe156442b.js":{"logical_path":"application.js","mtime":"2016-12-23T20:12:03-05:00","size":412383,
"digest":"aee4be71f1288037ae78b997df388332edfd246471b533dcedaa8f9fe156442b","integrity":"sha256-ruS+cfEogDeueLmX3ziDMu39JGRxtTPc7aqPn+FWRCs="},
"application-86a292b5070793c37e2c0e5f39f73bb387644eaeada7f96e6fc040a028b16c18.css":{"logical_path":"application.css","mtime":"2016-12-23T19:12:20-05:00","size":2994,
"digest":"86a292b5070793c37e2c0e5f39f73bb387644eaeada7f96e6fc040a028b16c18","integrity":"sha256-hqKStQcHk8N+LA5fOfc7s4dkTq6tp/lub8BAoCixbBg="},
"favicon-8d2387b8d4d32cecd93fa3900df0e9ff89d01aacd84f50e780c17c9f6b3d0eda.ico":{"logical_path":"favicon.ico","mtime":"2016-12-23T20:11:00-05:00","size":8629,
"digest":"8d2387b8d4d32cecd93fa3900df0e9ff89d01aacd84f50e780c17c9f6b3d0eda","integrity":"sha256-jSOHuNTTLOzZP6OQDfDp/4nQGqzYT1DngMF8n2s9Dto="},
"my_image-f4028156fd7eca03584d5f2fc0470df1e0dbc7369eaae638b2ff033f988ec493.png":{"logical_path":"my_image.png","mtime":"2016-12-23T20:10:54-05:00","size":23414,
"digest":"f4028156fd7eca03584d5f2fc0470df1e0dbc7369eaae638b2ff033f988ec493","integrity":"sha256-9AKBVv1+ygNYTV8vwEcN8eDbxzaequY4sv8DP5iOxJM="}},
"assets":{"application.js":"application-aee4be71f1288037ae78b997df388332edfd246471b533dcedaa8f9fe156442b.js",
"application.css":"application-86a292b5070793c37e2c0e5f39f73bb387644eaeada7f96e6fc040a028b16c18.css",
"favicon.ico":"favicon-8d2387b8d4d32cecd93fa3900df0e9ff89d01aacd84f50e780c17c9f6b3d0eda.ico",
"my_image.png":"my_image-f4028156fd7eca03584d5f2fc0470df1e0dbc7369eaae638b2ff033f988ec493.png"}}

Размещение манифеста по умолчанию - корень папки, определенной в config.assets.prefix (по умолчанию '/assets').

Если в production отсутствуют прекомпилированные файлы, вы получите исключение Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError, указывающее имя отсутствующего файла(-ов).

4.1.1. Вечный заголовок Expires

Прекомпилированные ассеты существуют в файловой системе и отдаются непосредственно веб-сервером. По умолчанию у них нет заголовков вечного кэширования, таким образом, чтобы получить преимущество от меток, необходимо обновить конфигурацию вашего сервера, чтобы добавить эти заголовки.

Для Apache:

# Директивы Expires* требуют, чтобы модуль Apache `mod_expires` был включен.
<Location /assets/>
  # Не рекомендуется использование ETag, когда присутствует Last-Modified
  Header unset ETag
  FileETag None
  # RFC предписывает кэшировать только на 1 год
  ExpiresActive On
  ExpiresDefault "access plus 1 year"
</Location>

Для NGINX:

location ~ ^/assets/ {
  expires 1y;
  add_header Cache-Control public;

  add_header ETag "";
}

4.2. Локальная прекомпиляция

Имеется несколько причин того, что может возникнуть желание компилировать ассеты локально. Вот некоторые из них:

  • У вас нет права записи в файловую систему production.
  • Вы размещаетесь более чем на одном сервере и хотите избежать дублирования работы.
  • Вы часто производите деплои, не включающие изменения ассетов.

Локальная компиляция позволяет зафиксировать скомпилированные файлы в управлении версиями и деплоить как обычно.

Однако есть три оговорки:

  • Вы не должны запускать задачу деплоя с помощью Capistrano, которая прекомпилирует ассеты.
  • Вы должны убедиться, что в вашей системе разработки присутствуют все необходимые компрессоры или минифайеры.
  • Вы должны изменить следующую конфигурационную настройку приложения:

В config/environments/development.rb поместите следующую строчку:

config.assets.prefix = "/dev-assets"

Изменение prefix позволяет Sprockets использовать другой URL для обслуживания ассетов в режиме development и передавать все запросы в Sprockets. Префикс остался установленным /assets в режиме production. Без этого изменения приложение будет обслуживаться прекомпилированными ассетами из /assets в development, и вы не увидите какие-либо локальные изменения, пока снова не скомпилируете ассеты.

На практике это позволит прекомпилировать локально, держать эти файлы в рабочей ветке и при необходимости фиксировать в системе контроля версий. Режим development будет работать так, как от от него ожидается.

4.3. Компиляция в реальном времени

В некоторых обстоятельствах, возможно, необходимо использовать компиляцию в реальном времени. В этом режиме все запросы для ассетов в файлопроводе обрабатываются непосредственно Sprockets.

Чтобы включить эту опцию, установите:

config.assets.compile = true

При первом запросе ассеты компилируются и кэшируются так, как описывалось в разделе про development, и имена манифеста, использованного в хелперах, изменяется путем включения хэша SHA256.

Sprockets также устанавливает HTTP-заголовок Cache-Control как max-age=31536000. Это сигнализирует всем кэшам между вашим сервером и браузером клиента, что это содержимое (отданный файл) может быть закэшировано на 1 год. В результате уменьшается количество запросов для этого ассета на ваш сервер; есть хороший шанс, что ассет будет в локальном кэше браузера или в каком-либо промежуточном кэше.

Этот режим использует больше памяти, имеет худшее быстродействие, чем по-умолчанию, и не рекомендуется.

Если приложение размещается в системе без существующего JavaScript runtimes, возможно понадобится добавить в Gemfile следующий фрагмент кода:

group :production do
  gem 'mini_racer'
end

4.4. CDN

CDN расшифровывается как Content Delivery Network, она в основном предназначена для кэширования ассетов по всему миру, поэтому, когда браузер запрашивает ассет, кэшированная копия будет географически ближайшая к этому браузеру. Если отдавать ассеты непосредственно от сервера Rails в production, лучшей практикой будет использовать CDN перед приложением.

Обычным образцом использования CDN является установка вашего приложения в production как "origin" сервер. Это означает, что когда браузер запрашивает ассет из CDN, и кэш отсутствует, он возьмет файл с вашего сервера на лету и кэширует его. Например, если вы запустили приложение Rails на example.com, и у вас настроен CDN на mycdnsubdomain.fictional-cdn.com, то, когда делается запрос к mycdnsubdomain.fictional-cdn.com/assets/smile.png, CDN единожды запросит ваш сервер на example.com/assets/smile.png и кэширует запрос. Следующий запрос к CDN, пришедший по тому же самому URL, получит кэшированную копию. Когда CDN может отдать ассет напрямую, запрос никогда не затронет сервер Rails. Так как ассеты из CDN географически ближе к браузеру, запрос быстрее, и, так как серверу не нужно тратить время на раздачу ассетов, он может сфокусироваться на как можно быстром обслуживании кода приложения.

4.4.1. Настройка CDN на раздачу статических ассетов

Для настройки CDN вам нужно, чтобы ваше приложение было запущенно в production в интернете на публично доступном URL, например example.com. Далее необходимо зарегистрироваться на сервисе CDN облачного провайдера. После этого необходимо настроить "origin" для CDN, указав ваш сайт example.com, по документации провайдера по настройке origin-сервера.

Подготовленный CDN даст определенный поддомен для вашего приложения, такой как mycdnsubdomain.fictional-cdn.com (отметьте, что fictional-cdn.com это не существующий провайдер CDN в настоящее время). Теперь, когда есть настроенный сервер CDN, необходимо сообщить браузерам использовать ваш CDN для того, чтобы брать ассеты оттуда, а не от сервера Rails. Это можно осуществить, настроив Rails, установив ваш CDN в качестве хоста ассетов, вместо использования относительного пути. Для настройки хоста ассетов в Rails, необходимо установить config.action_controller.asset_host в config/environments/production.rb:

config.action_controller.asset_host = 'mycdnsubdomain.fictional-cdn.com'

Необходимо предоставить только "host", это поддомен и корневой домен, не нужно указывать протокол или "scheme", такие как http:// или https://. Когда запрашивается страница, протокол в сгенерированной ссылке на ассет будет соответствовать тому, какой доступ к странице.

Это значение также можно настроить с помощью переменной среды, чтобы упростить запуск staging-копий вашего сайта:

config.action_controller.asset_host = ENV['CDN_HOST']

Чтобы это работало, вам необходимо установить на сервере CDN_HOST значение mycdnsubdomain.fictional-cdn.com.

После того, как вы настроили свой сервер и ваш CDN, когда вы отдаете страницу, содержащую ассет:

<%= asset_path('smile.png') %>

Вместо того, чтобы вернуть путь, такой как /assets/smile.png (метки опущены для читаемости), сгенерированный URL будет содержать полный путь к вашему CDN.

http://mycdnsubdomain.fictional-cdn.com/assets/smile.png

Если на CDN имеется копия smile.png, она будет отдана браузеру, и ваш сервер даже не узнает, что она была запрошена. Если на CDN нет копии, он попытается найти ее на "origin" example.com/assets/smile.png, а затем сохранить ее для дальнейшего использования.

Если хотите отдавать только некоторые ассеты из CDN, можно использовать опцию :host в хелпере ассета, переопределяющую значение, установленное в config.action_controller.asset_host.

<%= asset_path 'image.png', host: 'mycdnsubdomain.fictional-cdn.com' %>

4.4.2. Настройка поведения кэширования CDN

CDN работает, кэшируя содержимое. Если в CDN имеется устаревшее или плохое содержимое, то это скорее навредит, чем поможет вашему приложению. Целью этого раздела является описание основных особенностей кэширования многих CDN, поведение вашего определенного провайдера может немного отличаться.

4.4.2.1. Кэширование запросов CDN

Хотя CDN описывается как кэширующий файлы ассетов, на самом деле он кэширует целые запросы. Они включают тело ассета, а также его заголовки. Наиболее важным является Cache-Control, который сообщает CDN (и браузерам), как кэшировать содержимое. Это означает, что если кто-то запрашивает несуществующий ассет /assets/i-dont-exist.png, и ваше приложение Rails возвращает 404, тогда ваш CDN скорее всего закэширует страницу 404, если присутствует валидный заголовок Cache-Control.

4.4.2.2. Отладка заголовков CDN

Одним из способов проверить, что заголовки кэшируются правильно на CDN, является использование curl. Вы можете запросить заголовки от сервера и от CDN, чтобы сверить, что они одинаковые:

$ curl -I http://www.example/assets/application-
d0e099e021c95eb0de3615fd1d8c4d83.css
HTTP/1.1 200 OK
Server: Cowboy
Date: Sun, 24 Aug 2014 20:27:50 GMT
Connection: keep-alive
Last-Modified: Thu, 08 May 2014 01:24:14 GMT
Content-Type: text/css
Cache-Control: public, max-age=2592000
Content-Length: 126560
Via: 1.1 vegur

Против копии на CDN.

$ curl -I http://mycdnsubdomain.fictional-cdn.com/application-
d0e099e021c95eb0de3615fd1d8c4d83.css
HTTP/1.1 200 OK Server: Cowboy Last-
Modified: Thu, 08 May 2014 01:24:14 GMT Content-Type: text/css
Cache-Control:
public, max-age=2592000
Via: 1.1 vegur
Content-Length: 126560
Accept-Ranges:
bytes
Date: Sun, 24 Aug 2014 20:28:45 GMT
Via: 1.1 varnish
Age: 885814
Connection: keep-alive
X-Served-By: cache-dfw1828-DFW
X-Cache: HIT
X-Cache-Hits:
68
X-Timer: S1408912125.211638212,VS0,VE0

Проверьте документацию вашего CDN, чтобы найти подробности о том, что такое X-Cache или любые другие добавленные ими заголовки.

4.4.2.3. CDN и заголовок Cache-Control

Заголовок контроля кэша - это спецификация W3C, описывающая, как может быть закэширован запрос. Когда не используется CDN, браузер использует эту информацию для кэширования содержимого. Это очень полезно для неизменяемых ассетов, так как браузеру не нужно повторно скачивать CSS или JavaScript сайта при каждом запросе. Как правило, мы хотим, чтобы наш сервер Rails сообщил нашему CDN (и браузеру), что ассет "public", что означает, что любой кэш может сохранять запрос. Также, мы в основном хотим установить max-age, который означает, как долго кэш будет хранить объект до недействительности кэша. Значение max-age устанавливается в секундах с максимально возможным значением 31536000, равным одному году. Это можно сделать в вашем приложении Rails, установив

config.public_file_server.headers = {
  'Cache-Control' => 'public, max-age=31536000'
}

Теперь, когда ваше приложение отдает ассет в production, CDN сохранит ассет на один год. Так как большинство CDN также кэшируют заголовки запроса, этот Cache-Control будет передан всем браузерам, обращающимся к этому ассету, и браузер будет знать, что он может хранить этот ассет очень долго без необходимости повторного запроса.

4.4.2.4. CDN и недействительность кэша, основанного на URL

Большинство CDN кэшируют содержимое ассета, основываясь на полном URL. Это означает, что запрос к

http://mycdnsubdomain.fictional-cdn.com/assets/smile-123.png

Будет полностью по-другому закэширован, чем

http://mycdnsubdomain.fictional-cdn.com/assets/smile.png

Если хотите установить длительный max-age в вашем Cache-Control (и делаете так), то убедитесь, что, когда вы изменяете ассеты, ваш кэш прекращается. Например, при изменении рожицы смайлика в изображении с желтого на синий, вы хотите, чтобы все посетители вашего сайта получили новую синюю рожицу. При использовании CDN с настройкой файлопровода Rails config.assets.digest, установленной true по умолчанию, каждый ассет будет иметь другое имя, если он изменится. Таким образом, вам даже не нужно вручную прекращать любые элементы в вашем кэше. Используя иную технику для уникального имени ассета, ваши пользователи также получат самый свежий ассет.

5. Настройка файлопровода

5.1. Сжатие CSS

Одним из вариантов для сжатия CSS является YUI. YUI CSS compressor предоставляет минификацию.

Следующая строчка включает сжатие YUI и требует гем yui-compressor.

config.assets.css_compressor = :yui

Имеется другой вариант для сжатия CSS, если у вас установлен гем sass-rails

config.assets.css_compressor = :sass

5.2. Сжатие JavaScript

Возможные варианты для сжатия JavaScript это :closure, :uglifier and :yui. Они требуют использование гемов closure-compiler, uglifier или yui-compressor соответственно.

Gemfile по умолчанию включает uglifier. Этот гем оборачивает UglifierJS (написанный для NodeJS) в Ruby. Он сжимает ваш код, убирая пробелы и комментарии, сокращая имена локальных переменных и выполняя иные микро-оптимизации, наподобие замены ваших выражений if и else на тернарные операторы там, где возможно.

Следующая строчка вызывает uglifier для сжатия JavaScript.

config.assets.js_compressor = :uglifier

Необходим runtime, поддерживаемый ExecJS, чтобы использовать uglifier. Если используете macOS или Windows, у вас уже имеется JavaScript runtime, установленный в операционной системе.

5.3. Обслуживание сжатой версии ассетов

По умолчанию будет сгенерирована сжатая версия скомпилированных ассетов, вместе с несжатой версией ассетов. Сжатые ассеты помогают уменьшить передачу данных через канал связи. Это можно настроить, установив флажок gzip.

config.assets.gzip = false # отключает генерацию сжатых ассетов

5.4. Использование собственного компрессора

Настройки конфигурации компрессора для CSS и JavaScript также могут принимать любой объект. Этот объект должен иметь метод compress, принимающий строку как единственный аргумент, и он должен возвращать строку.

class Transformer
  def compress(string)
    do_something_returning_a_string(string)
  end
end

Чтобы его включить, передайте new объект в настройку конфигурации в application.rb:

config.assets.css_compressor = Transformer.new

5.5. Изменение пути assets

Публичный путь, используемый Sprockets по умолчанию, это /assets.

Он может быть заменен на что-то другое:

config.assets.prefix = "/some_other_path"

Это удобная опция, если вы обновляете старый проект, не использующий файлопровод и уже использующий этот путь, или вы хотите использовать этот путь для нового ресурса.

5.6. Заголовки X-Sendfile

Заголовок X-Sendfile — это указание веб-серверу игнорировать отклик от приложения, и вместо этого отдать определенный файл с диска. Эта опция отключена по умолчанию, но может быть включена, если ее поддерживает сервер. Когда опция включена, обязанность по отдаче файла передается веб-серверу, который справляется с ней быстрее. Обратитесь к send_file, чтобы узнать, как использовать эту особенность.

Apache и NGINX поддерживают эту опцию. Она включается в config/environments/production.rb.

# config.action_dispatch.x_sendfile_header = "X-Sendfile" # для Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # для NGINX

Если вы обновляете свое существующее приложение и намереваетесь использовать эту опцию, убедитесь, что скопировали эту опцию только в production.rb и в любую другую среду, которую вы определили, как имеющую поведение production (не в application.rb).

За дальнейшими подробностями обращайтесь к документации своих веб-серверов:

6. Хранилище кэша ассетов

По умолчанию Sprockets кэширует ассеты в tmp/cache/assets в development и production. Это может быть изменено следующим образом:

config.assets.configure do |env|
  env.cache = ActiveSupport::Cache.lookup_store(:memory_store,
                                                { size: 32.megabytes })
end

Чтобы отключить хранилище кэша ассетов:

config.assets.configure do |env|
  env.cache = ActiveSupport::Cache.lookup_store(:null_store)
end

7. Добавление ассетов в ваши гемы

Ассеты также могут идти от внешних источников в виде гемов.

Хорошим примером этого является гем jquery-rails, поставляющийся вместе с Rails как гем стандартной JavaScript библиотеки. Этот гем содержит класс engine, унаследованный от Rails::Engine. Сделав так, Rails становится проинформированным, что директории для этого гема могут содержать ассеты, и директории app/assets, lib/assets и vendor/assets этого engine добавляются в путь поиска Sprockets.

8. Создание препроцессора в вашей библиотеке или геме

Sprockets использует Процессоры, Трансформеры, Компрессоры и Экспортеры для расширения функциональности Sprockets. Обратитесь к Расширение Sprockets, чтобы узнать больше об этом. Здесь мы зарегистрировали препроцессор, чтобы добавить комментарий в конец text/css (.css) файлов.

module AddComment
  def self.call(input)
    { data: input[:data] + "/* Hello From my sprockets extension */" }
  end
end

Теперь, когда у вас есть модуль, который изменяет входные данные, самое время зарегистрировать его как препроцессор для вашего mime типа.

Sprockets.register_preprocessor 'text/css', AddComment

9. Обновление со старых версий Rails

Имеется несколько проблем при обновлении c Rails 3.0 или Rails 2.x. Первая — это перемещение файлов из public/ в новые места размещения. Смотрите Организация ассетов ранее в руководстве для правильного размещения файлов разных типов.

Следующей является необходимость избегать дублирования файлов JavaScript. Так как jQuery является библиотекой JavaScript по умолчанию, начиная с Rails 3.1 и далее, не нужно копировать jquery.js в app/assets, он будет включен автоматически.

Третья это обновление файлов различных сред с правильными значениями по умолчанию.

В application.rb:

# Версия ассетов, измените ее, если хотите, чтобы срок существующих ассетов истек
config.assets.version = '1.0'

# Измените путь, откуда отдаются ассеты для config.assets.prefix = "/assets"

В development.rb:

# Разворачивать строчки, загружающие ассеты
config.assets.debug = true

И в production.rb:

# Выбрать используемый компрессор (если имеется)
config.assets.js_compressor = :uglifier
# config.assets.css_compressor = :yui

# Не обращаться к файлопроводу, если отсутствует прекомпилированный ассет
config.assets.compile = false

# Генерировать дайджесты для URL ассетов.
config.assets.digest = true

# Прекомпилировать дополнительные ассеты (application.js, application.css и все
# не-JS/CSS уже добавлены)
# config.assets.precompile `= %w( admin.js admin.css )

Rails 4 и выше более не устанавливает конфигурационные значения по умолчанию для Sprockets в test.rb, поэтому теперь test.rb требует конфигурацию Sprockets. Старыми значениями по умолчанию в тестовом окружении являются: config.assets.compile = true, config.assets.compress = false, config.assets.debug = false и config.assets.digest = false.

Следующее также должно быть добавлено в Gemfile:

gem 'sass-rails',   "~> 3.2.3"
gem 'coffee-rails', "~> 3.2.1"
gem 'uglifier'