Управление активами JS и CSS в Rails 7

В Ruby on Rails 7 процессы управления ресурсами были изменены с использования Webpacker на использование конвейера ресурсов с картами импорта по умолчанию в качестве способа упростить использование инструментов на основе JavaScript и менеджеров пакетов, таких как Webpack (или другие сборщики), Yarn или npm.

Эта статья направлена ​​на изучение карт импорта и пользовательских настроек связывания на высоком уровне, включая краткий обзор Webpacker, чтобы его можно было сравнить с другими подходами, краткий пример использования карт импорта и более сложный пример пользовательского связывания с использованием esbuild с TypeScript. и PostCSS.

Надеюсь, эта статья может быть использована в качестве отправной точки для тех, кто использует инструменты JavaScript для объединения ресурсов, но мало знает о том, как это работает в настоящее время в контексте приложения Rails.

Вебпакер

Этот подход к управлению активами был представлен в Rails 6 и, по сути, является реализацией Webpack, специально настроенного для использования с Rails. Это краткий обзор Webpacker, чтобы мы могли провести сравнение с более поздними подходами к объединению активов.

В Webpacker config/webpacker.yml используется в качестве интерфейса для определения конфигурации Webpack приложения, а папка config/webpack используется для хранения файлов, чтобы указать обработку активов в различных средах (разработка, производство) или адаптировать его для использования определенных библиотек JavaScript, которые могут потребовать дополнительной настройки. .

Он также будет включать package.json, который стал широко использоваться в любом приложении, использующем модули Node.

Чтобы установить зависимости, необходимо запустить yarn install, но при запуске rails server приложение Rails будет запущено и запущена задача наблюдения Webpack, чтобы активы были правильно объединены.

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

Что я имею в виду под уровнем абстракции здесь, так это то, что не было бы необходимости настраивать Webpack, и он просто работал бы из коробки, но аспекты конфигурации скрыты за кулисами, и их изменение требуется для изменения webpacker.yml, а не конфигурации Webpack напрямую. . У Rails была логика, чтобы склеить все это вместе за кулисами.

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

Импорт карт

Импорт карт — это шаблон, поставляемый с приложением Rails 7 по умолчанию. Он использует функцию, при которой модули JavaScript, которые обычно устанавливаются с помощью менеджера пакетов, такого как Yarn или npm, и в большинстве случаев транспилируются и объединяются в файл .js, могут быть импортированы непосредственно в браузер и использоваться в вашем приложении без дополнительный этап сборки.

Ключевые аспекты подхода к импорту карт

  • Это более тесно связано с Rails, поскольку создатель поощряет разработчиков использовать и поставлять приложение Rails по умолчанию.
  • Может упростить вашу цепочку инструментов, поскольку для использования библиотек JavaScript не требуются npm или упаковщики.
  • Требуется меньше настроек, для начала достаточно запустить новый rails new myapp.
  • Он не включает опцию, если вы предпочитаете объединять свои собственные стили. Например, используя SASS или Postcss, хотя ничто не мешает вам использовать гибридный подход и добавить шаг сборки самостоятельно.
  • Меньший контроль над объединением ваших активов, поэтому, если вам требуется более сложная обработка JavaScript и CSS, например, использование партиалов Postcss или использование собственного способа транспиляции JavaScript, это может быть не лучшим выбором.

Использование Import Maps на веб-сайте (включая приложение Rails) приведет к тому, что исходный код будет выглядеть примерно так:

<script type="importmap" data-turbo-track="reload">{
  "imports": {
    "application": "/assets/application.js", // A local JS file.
		"another-js-library": "/assets/another-js-library.js, // Another local JS file.
		"local-time": "<https://ga.jspm.io/npm:[email protected]/app/assets/javascripts/local-time.js>" // A library being imported via a CDN.
  }
}</script>

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

Затем модули импортируются после тега скрипта importmap путем рендеринга нескольких дополнительных тегов module (иногда может быть по одному на модуль). В этом случае библиотеки в теге сценария importmaps используются в application.js, поэтому требуется только один тег module, и это должно работать в большинстве случаев:

<script type="module">import "application"</script>

Rails сгенерирует эти теги для вас, когда <%= javascript_importmap_tags %> будет добавлен в макет, обычно application.html.erb, и определит, какие модули необходимо включить.

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

Что такое прокладка?

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

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

Использование карт импорта в Rails

Чтобы импортировать пакет, который обычно доступен в npm, выполните следующую команду в терминале. В этом случае он установит local-time:

./bin/importmap pin local-time

Это добавит новую строку в config/importmap.rb, чтобы использовать пакет. Этот файл в основном используется Rails для генерации тега скрипта Import Maps, который помещается в окончательный вывод HTML:

pin "local-time", to: "<https://ga.jspm.io/npm:[email protected]/app/assets/javascripts/local-time.js>"

Если вы хотите загрузить пакет, чтобы сохранить его в своем приложении, использование флага --download переместит файл модуля в vendor/javascript/local-time.js, а также изменит оператор pin, чтобы отразить изменение:

pin "local-time" # @2.1.0

Затем модуль можно использовать в app/javascript/application.js, как обычный импорт:

import LocalTime from "local-time"

В некоторых случаях вы можете захотеть использовать модуль, над которым вы работали, и который не размещен на npm. Для этого добавьте файл в assets/javascript, в данном случае я назвал его home.js:

console.log("Hello Home!")

Затем его можно импортировать в application.js:

// Configure your import map in config/importmap.rb. Read more: <https://github.com/rails/importmap-rails>
import "@hotwired/turbo-rails"
import "controllers"
import "trix"
import "@rails/actiontext"
// Importing the home.js script here!
import "./home"
import LocalTime from "local-time"
LocalTime.start()

Так и должно быть, код внутри home.js должен работать без необходимости закрепления в importmap.rb.

Файл importmap.rb используется для определения того, какие модули будут в следующем теге:

<script type="importmap" data-turbo-track="reload">{
  "imports": {
    "application": "/assets/application.js", // A local JS file.
		"another-js-library": "/assets/another-js-library.js, // Another local JS file.
		"local-time": "<https://ga.jspm.io/npm:[email protected]/app/assets/javascripts/local-time.js>" // A library being imported via a CDN.
  }
}</script>

Он также будет отображать любые другие необходимые теги для работы Import Maps. Каждый тег указывает на модуль, используемый этим приложением, в частности, поэтому ваш вывод может отличаться от этого фрагмента:

<link rel="modulepreload" href="/assets/application-97114f95015a6fb5e0cb87c109b1397e96ba9a9d1e7422725a491c2034ce6580.js">
<link rel="modulepreload" href="/assets/turbo.min-305f0d205866ac9fc3667580728220ae0c3b499e5f15df7c4daaeee4d03b5ac1.js">
<link rel="modulepreload" href="/assets/stimulus.min-900648768bd96f3faeba359cf33c1bd01ca424ca4d2d05f36a5d8345112ae93c.js">
<link rel="modulepreload" href="/assets/stimulus-loading-685d40a0b68f785d3cdbab1c0f3575320497462e335c4a63b8de40a355d883c0.js">
<script src="/assets/es-module-shims.min-6982885c6ce151b17d1d2841985042ce58e1b94af5dc14ab8268b3d02e7de3d6.js" async="async" data-turbo-track="reload"></script>

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

Индивидуальная комплектация

Использование вашей собственной системы связывания, такой как Webpack, Rollup, esbuild или другой, также возможно в тех случаях, когда вам требуется более надежная настройка. Возможно, вы хотели бы использовать TypeScript или реализовать собственную конфигурацию React, Svelte или Vue. Вам может понадобиться установка с Sass или Postcss. Вы можете просто захотеть иметь больше контроля над тем, как устанавливаются зависимости и где они заканчиваются. Если вам требуется более сложная настройка, это может быть правильным подходом.

Ключевые аспекты индивидуального подхода к комплектованию

  • Выбор и конфигурация упаковщика полностью зависят от вас. Это может быть либо положительным изменением, поскольку вы получаете больше контроля, либо может означать, что требуется дополнительный шаг при настройке конвейера и ряд дополнительных файлов конфигурации.
  • Команда Rails предоставила гем jsbundling-rails, который упрощает настройку вашего приложения с помощью esbuild, Webpack или Rollup вместе с cssbundling-rails, который эквивалентен управлению сборкой CSS. В данном случае используется пряжа.
  • Этот подход требует запуска yarn build --watch вместе с серверным процессом Rails, но использование ./bin/dev запустит оба процесса одновременно.

В новых приложениях Rails 7 упаковщик и препроцессор CSS можно указать с помощью следующей команды:

rails new myapp -j esbuild -c postcss

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

После использования этой команды объект scripts с задачами build и build:css все еще необходимо определить и настроить в package.json. Пример того, как эти задачи могут выглядеть с использованием ранее выбранного сборщика и препроцессора:

// previous file contents...
"scripts": {
    "build": "esbuild ./app/javascript/*.* --outfile=./app/assets/builds/application.js --bundle",
    "build:css": "postcss ./app/assets/stylesheets/application.postcss.css -o ./app/assets/builds/application.css"
},
// file continues...

Использование этого подхода по-прежнему связывает его с конфигурацией Rails, которая предполагает несколько вещей:

  • Окончательный вывод JS и CSS необходимо скопировать в app/assets/builds. Это означает, что ваши окончательные транспилированные .js и обработанные .css файлы будут обслуживаться отсюда.
  • Rails использует <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> и <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %> для поиска связанных application.js и application.css в каталоге builds и ожидает, что они существуют.

Помимо этого, кажется, что файлы JavaScript и файлы CSS могут быть объединены гибким образом. Однако использование метода stylesheet_link_tag для добавления тегов link в заголовок документа по-прежнему требует, чтобы связанные файлы находились в папке builds:

<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag "style", "data-turbo-track": "reload" %>
<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>

В приведенном выше примере тег ссылки, указывающий на app/assets/builds/style.css, также будет включен в отображаемый HTML.

Как Rails определяет, что папка builds должна быть там, где должны храниться скомпилированные активы? Это определяется кодовыми базами jsbundling-rails и cssbundling-rails в их внутренней конфигурации по умолчанию.

Как насчет создания модуля JavaScript?

Точно так же, как ожидается, что связанный файл CSS будет в /builds при использовании stylesheet_link_tag, то же самое ожидается для связанного файла JS при использовании javascript_include_tag.

По умолчанию, используя этот индивидуальный подход к связыванию, Rails использует app/javascript/application.js в качестве точки входа для компиляции файлов, и вы можете разделить свои скрипты в этой папке и импортировать их, а также любые модули, установленные через Yarn, вот как выглядит файл:

// Entry point for the build script in your package.json
import "@hotwired/turbo-rails"
import "./controllers"

Создание нового модуля в app/javascript/external.mjs показывает, как Rails улавливает изменения, когда файл импортируется в application.js, и что расширение .mjs можно использовать без проблем:

export const external_message = "External module loaded";
export function result() {
  return 3 + 3;
}

А как насчет TypeScript?

TypeScript можно добавить за несколько шагов, ознакомьтесь с постом Ноэля Раппина о том, как настроить и запустить TypeScript.

Вот разбивка примера установки, основанного на предыдущих шагах, начните с установки typescript, tsc-watch и пакета конфигурации. Я использовал @tsconfig/recommended:

yarn add --dev typescript tsc-watch @tsconfig/recommended

Затем мы хотим запустить средство проверки TypeScript до того, как esbuild перенесет код, поэтому команда watch:ts была добавлена ​​вместе с командой failure:ts для запуска при сбое объекта сценариев package.json:

"scripts": {
    "build": "esbuild ./app/javascript/application.ts --outfile=./app/assets/builds/application.js --bundle",
    "build:css": "postcss ./app/assets/stylesheets/application.postcss.css -o ./app/assets/builds/application.css",
    "failure:ts": "rm ./app/assets/builds/application.js && rm ./app/assets/builds/application.js.map",
    "watch:ts": "tsc-watch --noClear -p ./tsconfig.json --onSuccess \\"yarn build\\" --onFailure \\"yarn failure:ts\\""
},

Для этого требуется tsconfig.json, это может быть сложно настроить, если вы не делаете это часто, поэтому вот конфигурация, которую я использовал:

{
  "extends": "@tsconfig/recommended/tsconfig.json",
  "compilerOptions": {
    "target": "ES2015",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "downlevelIteration": true
  },
  "$schema": "<https://json.schemastore.org/tsconfig>",
  "display": "Recommended",
  "include": [
    "./app/javascript/**/*.ts"
  ],
  "exclude": [
    "./node_modules"
  ]
}

Далее необходимо переименовать входной файл с app/javascript/application.js на application.ts, чтобы его обнаружила программа проверки TypeScript.

Наконец, содержимое [Procfile.dev](<http://Procfile.dev>) необходимо отредактировать, чтобы запустить команду просмотра TS вместо команды сборки. Мы запускаем команду esbuild через ts-watch и поэтому она не должна быть в Procfile:

web: bin/rails server -p 2077
js: yarn watch:ts
css: yarn build:css --watch

Запуск ./bin/dev в терминале запустит задачи и отследит изменения, а также запустит проверки TypeScript для любых файлов .ts в каталоге ./app/javascript.

Заключение

С Rails 7 фреймворк теперь поставляется с подходом Import Maps по умолчанию, но он оставляет варианты для более сложных настроек с настраиваемой комплектацией, которые в некотором смысле все еще должны быть выполнены «путем Rails». Это заметно, например, когда для скриптов и предварительно обработанных стилей назначаются точки входа по умолчанию. Это помогает разработчикам, которые хотят немного больше контролировать свои пакеты, и это кажется шагом в правильном направлении.

Как сказано в Руководстве по началу работы с Rails:

Если вы изучите «Путь Rails», вы, вероятно, обнаружите огромный рост производительности. Если вы продолжаете привносить старые привычки из других языков в свою разработку для Rails и пытаетесь использовать шаблоны, которые вы узнали где-то еще, у вас может быть менее счастливый опыт.

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

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