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/application.rb. Например:

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 helpers (через дефис в 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 jquery
//= require jquery_ujs
//= require_tree .

В файлах JavaScript директивы начинаются с //=. В вышеприведенном примере файл использует директивы require и require_tree. Директива require используется, чтобы указать Sprockets на требуемые файлы. Здесь затребованы файлы jquery.js и jquery_ujs.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. Проверка ошибок во время выполнения

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

config.assets.raise_runtime_errors = false

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

3.2. Raise an Error When an Asset is Not Found

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

config.assets.unknown_asset_fallback = false

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

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

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

config.assets.digest = false

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

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

Можно отключить режим отладки, обновив 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 'therubyracer'
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:

# Разворачивать строки, загружающие ресурсы the lines which load the assets
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'