Как кэшировать js библиотеку при импорте
Это руководство раскрывает файлопровод (asset pipeline).
Обратившись к этому руководству, вы узнаете:
- Что такое файлопровод, и зачем он нужен.
- Как должным образом организовывать ассеты своего приложения.
- Преимущества файлопровода.
- Как добавить препроцессор к файлопроводу.
- Как упаковывать ассеты в гем.
1. Что такое файлопровод (Asset Pipeline)?
Файлопровод представляет фреймворк для обработки доставки ассетов JavaScript и CSS. Это выполняется с использованием технологий, таких как HTTP/2, и техник, таких как конкатенация и минификация. Наконец, это позволяет приложению автоматически соединяться с ассетами других гемов.
Файлопровод реализован в гемах importmap-rails, sprockets и sprockets-rails и включен по умолчанию. Можно отключить файлопровод при создании нового приложения, передав опцию —skip-asset-pipeline .
$ rails new appname --skip-asset-pipeline
Это руководство фокусируется на файлопроводе по умолчанию с использованием только sprockets для обработки CSS и importmap-rails для JavaScript. Главное ограничение у них в том, что нет поддержки для транспиляции, поэтому нельзя использовать такие вещи как Babel , Typescript , Sass , React JSX format или TailwindCSS . Мы рекомендуем прочитать раздел про альтернативные библиотеки, если вам необходима транспиляция для JavaScript/CSS.
2. Основные особенности
Первой особенностью файлопровода является вставка метки SHA256 в каждое имя файла, чтобы этот файл кэшировался браузером и CDN. Эта метка автоматически обновляется при изменении содержимого файла, что инвалидирует кэш.
Второй особенностью файлопровода является использование карт импорта при раздаче файлов JavaScript. Это позволяет создавать современное приложение с помощью библиотек Javascript, сделанных для модулей ES (ESM), без необходимости транспиляции и сборки. В свою очередь, это устраняет необходимость Webpack, yarn, node или любой другой части инструментария JavaScript.
Третьей особенностью файлопровода является соединение всех CSS файлов в один главный файл .css , который затем минифицируется или сжимается. Как будет сказано далее в этом руководстве, можно настроить эту стратегию, сгруппировав файлы любым способом. В production, Rails вставляет метку SHA256 в каждое имя файла, таким образом файл кэшируется браузером. Кэш можно сделать недействительным, изменив эту метку, что происходит автоматически каждый раз, когда изменяется содержимое файла.
Четвертой особенностью файлопровода является то, что он позволяет писать эти ассеты на языке более высокого уровня для CSS.
2.1. Что за метки и зачем они нужны?
Метки (fingerprinting) — это техника, реализующая зависимость имени файла от его содержимого. При изменении содержимого файла, имя файла также изменяется. Для статичного или нечасто обновляемого содержимого это предоставляет легкий способ сказать, идентичны ли две версии файла, даже если они на разных серверах, или имеют различную дату размещения.
Когда имя файла уникально и основано на его содержимом, заголовками HTTP можно установить повсеместное кэширование (в CDN, у провайдера, в сетевом оборудовании или браузере), чтобы у них была собственная копия содержимого. Когда содержимое изменяется, метка тоже изменится. Это приведет к тому, что удаленные клиенты запросят новую копию содержимого. Эта техника известна как cache busting.
Техникой, используемой Sprockets для меток, является вставка хэша содержимого в имя, обычно в конце. Например, файл CSS global.css :
global-908e25f4bf641868d8683022a5b62f54.css
Это стратегия, принятая файлопроводом Rails.
Метки включены по умолчанию для сред development и production. Их также можно включить или отключить в конфигурации с помощью опции config.assets.digest .
2.2. Что такое карты импорта (Import Maps), и зачем они мне?
Карты импорта позволяют импортировать модули JavaScript с помощью логичных имен, связанных с версионированными/мечеными файлами, – прямо из браузера. Таким образом, можно создавать современное приложение с помощью библиотек Javascript, сделанных для модулей ES (ESM), без необходимости транспиляции и сборки.
С таким подходом можно поставлять множество маленьких файлов JavaScript вместо одного большого файла JavaScript. Это благодаря HTTP/2, который больше не несет ухудшения материального быстродействия во время первоначальной доставки, а фактически даже предлагает значительные преимущества в долгосрочной перспективе за счет лучшей динамики кэширования.
3. Как использовать карты импорта в качестве файлопровода Javascript
Карты импорта являются обработчиком Javascript по умолчанию, логика генерации карт импорта обрабатывается гемом importmap-rails .
Карты импорта используются только для файлов Javascript, и не могут быть использованы для доставки CSS. Чтобы узнать о CSS, обратитесь к разделу по Sprockets.
Детальные инструкции по использованию находятся на домашней странице гема, но важно понимать основы importmap-rails .
3.1. Как они работают
Карты импорта по сути это строковая замена для того, что называется «bare module specifiers». Они позволяют стандартизировать имена модулей импорта JavaScript.
Возьмем, к примеру, такое определение импорта, оно не будет работать без карты импорта:
import React from "react"
Вы бы могли определить его так, чтобы оно заработало:
import React from "https://ga.jspm.io/npm:react@17.0.2/index.js"
Тут вступает карта импорта, мы определяем, что имя react прикреплено к адресу https://ga.jspm.io/npm:react@17.0.2/index.js . С такой информацией браузер принимает упрощенное определение import React from «react» . Можно думать о карте импорта как о псевдониме для адреса исходника библиотеки.
3.2. Использование
С помощью importmap-rails создается файл конфигурации importmap, привязывая путь библиотеки к имени:
# config/importmap.rb pin "application" pin "react", to: "https://ga.jspm.io/npm:react@17.0.2/index.js"
Все сконфигурированные карты импорта должны быть прикреплены к элементу приложения, добавив . javascript_importmap_tags рендерит ряд скриптов в элемент head :
- JSON со всеми сконфигурированными картами импорта:
type="importmap"> "imports": "application": "/assets/application-39f16dc3f3. js" "react": "https://ga.jspm.io/npm:react@17.0.2/index.js" > >
- Es-module-shims , действующий как полифил, обеспечивающий поддержку для import maps в старых браузерах:
src="/assets/es-module-shims.min" async="async" data-turbo-track="reload">
- Точку входа для загрузки JavaScript из app/javascript/application.js :
type="module">import "application"
3.3. Использование пакетов npm из JavaScript CDN
Можно использовать команду ./bin/importmap , добавленную как часть importmap-rails , чтобы привязать, отвязать или обновить пакеты npm в карте импорта. Этот исполняемый файл использует JSPM.org .
Он работает так:
./bin/importmap pin react react-dom Pinning "react" to https://ga.jspm.io/npm:react@17.0.2/index.js Pinning "react-dom" to https://ga.jspm.io/npm:react-dom@17.0.2/index.js Pinning "object-assign" to https://ga.jspm.io/npm:object-assign@4.1.1/index.js Pinning "scheduler" to https://ga.jspm.io/npm:scheduler@0.20.2/index.js ./bin/importmap json "imports": "application": "/assets/application-37f365cbecf1fa2810a8303f4b6571676fa1f9c56c248528bc14ddb857531b95.js", "react": "https://ga.jspm.io/npm:react@17.0.2/index.js", "react-dom": "https://ga.jspm.io/npm:react-dom@17.0.2/index.js", "object-assign": "https://ga.jspm.io/npm:object-assign@4.1.1/index.js", "scheduler": "https://ga.jspm.io/npm:scheduler@0.20.2/index.js" > >
Как видите, два пакета react и react-dom разрешаются в четыре зависимости, при jspm по умолчанию.
Теперь их можно использовать в вашей точке входа application.js , как и любой другой модуль:
import React from "react" import ReactDOM from "react-dom"
Также можно назначить указанную версию для привязки:
./bin/importmap pin react@17.0.1 Pinning "react" to https://ga.jspm.io/npm:react@17.0.1/index.js Pinning "object-assign" to https://ga.jspm.io/npm:object-assign@4.1.1/index.js
Или даже убирать привязки:
./bin/importmap unpin react Unpinning "react" Unpinning "object-assign"
Можно контролировать среду пакета для пакетов с раздельными сборками для «production» (по умолчанию) и «development»:
./bin/importmap pin react --env development Pinning "react" to https://ga.jspm.io/npm:react@17.0.2/dev.index.js Pinning "object-assign" to https://ga.jspm.io/npm:object-assign@4.1.1/index.js
Также можно подобрать альтернативу, поддерживаемую провайдером CDN, при привязке, например unpkg или jsdelivr (по умолчанию jspm ):
./bin/importmap pin react --from jsdelivr Pinning "react" to https://cdn.jsdelivr.net/npm/react@17.0.2/index.js
Однако помните, что если вы переключили привязку от одного провайдера на другой, возможно будет необходимость очистить зависимости, добавленные первым провайдером, но не используемые вторым провайдером.
Запустите ./bin/importmap , чтобы увидеть все параметры.
Отметьте, что эта команда — просто удобная обертка для разрешения логических имен пакетов к CDN URL. Можно просто найти CDN URL самостоятельно и привязать их. Например, если нужен Skypack для React, можно просто добавить следующее в config/importmap.rb :
pin "react", to: "https://cdn.skypack.dev/react"
3.4. Предварительная загрузка привязанных модулей
Чтобы избежать эффекта водопада, когда браузеру нужно загружать файлы один за другим, до того, как он доберется до самого глубоко вложенного импорта, importmap-rails поддерживает ссылки modulepreload. Привязанные модули могут быть предварительно загружены, если добавить preload: true к привязке.
Хорошей идей является предварительная загрузка библиотек или фреймворков, используемых во всем приложении, так как это сообщит браузеру скачать их поскорее.
# config/importmap.rb pin "@github/hotkey", to: "https://ga.jspm.io/npm:@github/hotkey@1.4.4/dist/index.js", preload: true pin "md5", to: "https://cdn.jsdelivr.net/npm/md5@2.3.0/md5.js" # app/views/layouts/application.html.erb %= javascript_importmap_tags %> # включит следующую ссылку перед настройкой importmap: "modulepreload" href="https://ga.jspm.io/npm:@github/hotkey@1.4.4/dist/index.js"> .
Обратитесь к репозиторию importmap-rails за актуальной документацией.
4. Как использовать Sprockets
Наивным подходом выложить ассеты вашего приложение в веб будет хранить их в поддиректориях папки public , таких как images и stylesheets . Делать это вручную может быть сложно, так как большинство современных приложений требует обработки ассетов определенным образом, например, сжатие и добавление меток к ассетам.
Sprockets разработан, чтобы автоматически предварительно обрабатывать ваши ассеты, хранимые в сконфигурированных директориях, и после обработки выкладывает их в папку public/assets с метками, сжатием, генерации карт исходников и другими конфигурированными особенностями.
Ассеты все еще можно размещать в иерархии public . Любой ассет в public будет роздан как статичный файл приложением или веб-сервером, когда config.public_file_server.enabled установлена true. Можно определить директивы manifest.js для файлов, которые должны подвергаться некоторой предварительной обработке перед тем, как раздаваться.
В production Rails по умолчанию прекомпилирует эти файлы в public/assets . Прекомпилированные копии затем раздаются как статичные ассеты веб сервером. Файлы в app/assets никогда не раздаются напрямую в production.
4.1. Файлы манифеста и директивы
При компиляции ассетов с помощью Sprockets, ему нужно решить, какие цели верхнего уровня компилировать, обычно application.css и изображения. Цели верхнего уровня определяются в файле Sprockets manifest.js , по умолчанию он выглядит так:
//= link_tree ../images //= link_directory ../stylesheets .css //= link_tree ../../javascript .js //= link_tree ../../../vendor/javascript .js
Он содержит директивы — инструкции, сообщающие Sprockets, какие файлы требуются, чтобы создать отдельный файл CSS или JavaScript.
Он означает, что надо включить содержимое всех файлов, найденных в директории ./app/assets/images , а также любых поддиректориях, а также любой файл, распознанный как JS, непосредственно в ./app/javascript или ./vendor/javascript .
Он загрузит любой CSS из директории ./app/assets/stylesheets (не включая поддиректории). Допустим, что у нас есть файлы application.css и marketing.css в папке ./app/assets/stylesheets , он позволит загрузить эти таблицы стилей с помощью или из вью.
Возможно, вы заметили, что наши файлы JavaScript не загружаются из директории assets по умолчанию, то потому, что ./app/javascript по умолчанию точка входа для гема importmap-rails , и папка vendor это место, где будут храниться скачанные пакеты JS.
В manifest.js можно также указать директиву link , чтобы загрузить определенный файл вместо целой директории. Директива link требует предоставления явного расширения файла.
Sprockets загружает указанные файлы, при необходимости обрабатывает их, соединяет в единый файл, а затем сжимает (на основании значения config.assets.css_compressor или config.assets.js_compressor ). Сжатие уменьшает размер файла, позволяя браузеру скачивать файлы быстрее.
You can also opt to include controller-specific stylesheets files only in their respective controllers using the following:
stylesheet_link_tag params[:controller] %>
4.2. Ассеты конкретного контроллера
При генерации скаффолда или контроллера, Rails также генерирует файл CSS для этого контроллера. Дополнительно при генерации скаффолда, Rails генерирует файл scaffolds.css .
Например, если генерируете ProjectsController , Rails также добавит новый файл app/assets/stylesheets/projects.css . По умолчанию эти файлы будут готовы к немедленному использованию вашим приложением, с помощью директивы link_directory в файле manifest.js .
Опционально можно включить таблицы стилей конкретного контроллера только для их соответствующих контроллеров, используя следующее:
stylesheet_link_tag params[:controller] %>
When doing this, ensure you are not using the require_tree directive in your application.css , as that could result in your controller-specific assets being included more than once.
4.3. Asset Organization
Pipeline assets can be placed inside an application in one of three locations: app/assets , lib/assets or vendor/assets .
- app/assets is for assets that are owned by the application, such as custom images or stylesheets.
При этом убедитесь, что не используете директиву require_tree в application.css , так как она приведет к тому, что ассеты контроллера будут включены более одного раза.
4.4. Организация ассетов
Ассеты файлопровода могут быть размещены в приложении в одном из этих трех мест расположений: app/assets , lib/assets или vendor/assets .
- app/assets предназначено для ассетов, принадлежащих приложению, таких как изображения или таблицы стилей, изготовленные специально для приложения.
- app/javascript для кода JavaScript
- vendor/[assets|javascript] предназначено для ассетов, принадлежащих сторонним субъектам, таких как фреймворки CSS или библиотеки JavaScript. Имейте в виду, что код третьей стороны со ссылками на другие файлы, также обрабатывающиеся файлопроводом (изображения, таблицы стилей и так далее), должен быть переписан с помощью хелперов, таких как asset_path .
В файле manifest.js можно настроить другие расположения, обратитесь к Файлы манифеста и директивы.
4.4.1. Пути поиска
Когда к файлу обращаются из манифеста или хелпера, Sprockets ищет во всех местах, указанных в manifest.js для него. Путь поиска можно увидеть, просмотрев Rails.application.config.assets.paths в консоли Rails.
4.4.2. Использование индексных файлов как прокси для папок
Sprockets использует файлы с именем index (с соответствующим расширением) для специальных целей.
Например, если имеется библиотека CSS с множеством модулей, хранящаяся в lib/assets/stylesheets/library_namee , файл lib/assets/stylesheets/library_name/index.css служит манифестом для всех файлов в этой библиотеке. Этот файл может включать список всех требуемых файлов в нужном порядке, или просто директиву require_tree .
Это чем-то похоже на способ, которым файл public/library_name/index.html можно достичь запросом к /library_name . Это означает, что нельзя напрямую использовать индексный файл.
Библиотека в целом может быть доступна из файлов .css следующим образом:
/* . *= require library_name */
Это упрощает поддержку и сохраняет чистоту, позволяя коду быть сгруппированным перед включением где-нибудь еще.
4.5. Кодирование ссылок на ассеты
Sprockets не добавляет какие-либо новые методы для доступа к вашим ассетам — используйте знакомый метод stylesheet_link_tag .
stylesheet_link_tag "application", media: "all" %>
При использовании гема turbo-rails , который включен по умолчанию в Rails, включите опцию ‘data-turbo-track’, которая вызывает проверку Turbo, что ассет был обновлен, таким образом загружая его на страницу:
stylesheet_link_tag "application", "data-turbo-track": "reload" %>
В обычных вью можно получить доступ к изображениям в директории app/assets/images следующим образом:
image_tag "rails.png" %>
При условии, что файлопровод включен в вашем приложении (и не отключен в контексте текущей среды), этот файл будет отдан с помощью Sprockets. Если файл существует в public/assets/rails.png , он будет отдан веб-сервером.
Кроме того, запрос файла с хэшем SHA256, такого как public/assets/rails-f90d8a84c707a8dc923fca1ca1895ae8ed0a09237f6992015fef1e11be77c023.png будет обработан тем же образом. Как генерируются эти хэши будет раскрыто позже в этом руководстве в разделе В production.
Изображения также могут быть организованы в поддиректории и могут быть доступны с помощью указания имени директории в теге:
image_tag "icons/rails.png" %>
Если вы прекомпилируете ассеты (смотрите раздел В production далее), связывание с несуществующим ассетом вызовет исключение на вызывающей странице. Это также справедливо и для связывания с пустой строкой. Поэтому будьте осторожны при использовании image_tag и других хелперов с данными, предоставленными пользователями.
4.5.1. CSS и ERB
Файлопровод автоматически вычисляет ERB. Это означает, что, если добавить расширение erb к ассету CSS (например, application.css.erb ), будут доступны хелперы, такие как asset_path , в правилах вашего CSS:
.class background-image: url() >
Этот фрагмент кода записывает путь к определенному указанному ассету. Этот пример имеет смысл, если имеется изображение в одном из путей загрузки ассетов, такое как app/assets/images/image.png , на которое тут будет ссылка. Если это изображение уже имеется в public/assets как файл с меткой, то будет ссылка на него.
Если хотите использовать data URI — метод встраивания данных изображения непосредственно в файл CSS — используйте хелпер asset_data_uri .
#logo background: url() >
Этот фрагмент кода вставит правильно отформатированный URI в код CSS.
Отметьте, что закрывающий тег не может быть в стиле -%> .
4.6. Вызов ошибки, если ассет не найден
Если используется sprockets-rails >= 3.2.0, можно настроить, что произойдет, когда выполнен поиск ассета, и ничего не было найдено. Если выключить «asset fallback», тогда будет вызвана ошибка, когда ассет не может быть найден.
config.assets.unknown_asset_fallback = false
Если «asset fallback» включен, тогда, когда ассет не может быть найден, вместо этого будет выведен путь, а не вызвана ошибка. Поведение «asset fallback» выключено по умолчанию.
4.7. Включение дайджестов
Можно отключить дайджесты, добавив в config/environments/development.rb :
config.assets.digest = false
Когда эта опция true, для URL ассета будет генерироваться дайджест.
4.8. Включение карт исходников
Можно включить карты исходников, добавив в config/environments/development.rb :
config.assets.debug = true
Когда включена отладка, Sprockets сгенерирует карту исходников для каждого ассета. Это позволит вам отлаживать каждый файл по отдельности в средствах разработчика вашего браузера.
Ассеты компилируются и кэшируются при первом запросе после запуска сервера. Sprockets устанавливает HTTP-заголовок контроля кэша must-revalidate для уменьшения нагрузки на последующие запросы — на них браузер получает отклик 304 (Not Modified).
Если какой-либо из файлов в манифесте меняется между запросами, сервер возвращает новый скомпилированный файл.
5. В production
В среде production Sprockets использует схему меток, описанную ранее. По умолчанию Rails полагает, что ассеты прекомпилированы и будут отданы как статичные ассеты вашим веб-сервером.
В течение фазы прекомпиляции из содержимого компилированных файлов генерируется SHA256 и вставляется в имена файлов, когда они записываются на диск. Эти имена меток используются хелперами Rails вместо имени манифеста.
stylesheet_link_tag "application" %>
сгенерирует что-то наподобие этого:
href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" rel="stylesheet" />
Режим меток контролируется с помощью инициализационной опции config.assets.digest (которая по умолчанию true ).
В нормальных обстоятельствах опция config.assets.digest по умолчанию не должна изменяться. Если нет дайджеста в именах файлов и установлены заголовки с вечным кэшированием, удаленные клиенты никогда не узнают, когда перезапросить файлы при изменении их содержимого.
5.1. Прекомпиляция ассетов
В Rails имеется встроенная команда для компиляции на диск манифестов ассетов и других файлов в файлопроводе.
Скомпилированные ассеты записываются в место расположения, указанное в config.assets.prefix . По умолчанию это директория /assets .
Эту команду можно вызвать на сервере во время деплоя, чтобы создать скомпилированные версии ассетов непосредственно на сервере. Смотрите следующий раздел, чтобы узнать о том, как скомпилировать локально.
$ RAILS_ENV=production rails assets:precompile
Это свяжет папку, указанную в config.assets.prefix с shared/assets . Если вы уже используете эту общую папку, вам следует написать собственную команду для деплоя.
Важно то, что эта папка является общей между деплоями, так что удаленно кэшированные страницы, ссылающиеся на старые скомпилированные ассеты, все еще будут работать, пока не истечет срок кэширования.
Всегда определяйте ожидаемое имя скомпилированного файла, оканчивающееся на .js или .css .
Команда также генерирует .sprockets-manifest-randomhex.json (где randomhex — это 16-байтовая случайная шестнадцатеричная строка), который содержит список всех ваших ассетов и соответствующие им метки. Это используется методами хелпера Rails, чтобы избежать направления запроса в Sprockets. Обычный файл манифеста выглядит так:
"files":"application-.js":"logical_path":"application.js","mtime":"2016-12-23T20:12:03-05:00","size":412383, "digest":"","integrity":"sha256-">>, "assets":"application.js":"application-.js">>
В реальном приложении будет больше файлов и ассетов, перечисленных в манифесте, также будут сгенерированы и .
Место расположения манифеста по умолчанию — корень папки, определенной в config.assets.prefix (по умолчанию ‘/assets’).
Если в production отсутствуют прекомпилированные файлы, вы получите исключение Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError , указывающее имя отсутствующего файла(-ов).
5.1.1. Вечный заголовок Expires
Прекомпилированные ассеты существуют в файловой системе и отдаются непосредственно веб-сервером. По умолчанию у них нет заголовков вечного кэширования, таким образом, чтобы получить преимущество от меток, необходимо обновить конфигурацию вашего сервера, чтобы добавить эти заголовки.
# Директивы Expires* требуют, чтобы модуль Apache `mod_expires` был включен. Location /assets/> # Не рекомендуется использование ETag, когда присутствует Last-Modified Header unset ETag FileETag None # RFC предписывает кэшировать только на 1 год ExpiresActive On ExpiresDefault "access plus 1 year" Location>
location ~ ^/assets/ expires 1y; add_header Cache-Control public; add_header ETag ""; >
5.2. Локальная прекомпиляция
Иногда вы не хотите или не можете компилировать ассеты на сервере production. Например, у вас ограниченное право записи в файловую систему production, или вы планируете часто деплоить без внесения каких-либо изменений в ваши ассеты.
В таких случаях можно прекомпилировать ассеты локально — то есть добавить окончательный набор скомпилированных, готовых к production ассетов в репозиторий исходного кода перед отправлением на production. Таким образом, их не нужно прекомпилировать отдельно на сервере production при каждом деплое.
Как указано выше, это можно сделать с помощью
$ RAILS_ENV=production rails assets:precompile
Есть следующие оговорки:
-
Если доступны прекомпилированные ассеты, они будут отданы, даже если они больше не соответствуют оригинальным (не компилированным) ассетам, даже на сервере development. Чтобы убедиться, что сервер development всегда компилирует ассеты на лету (и, таким образом, всегда отражает последнее состояние кода), среда development должна быть настроена содержать прекомпилированные ассеты в другом месте, чем содержит production. В противном случае, любые ассеты, прекомпилированные для использования в production, будут ломать запросы к ним в development (например, последующие сделанные изменения в ассетах не будут отражены в браузере). Это можно сделать, добавив следующую строчку в config/environments/development.rb :
config.assets.prefix = "/dev-assets"
Также можно установить ENV[«SECRET_KEY_BASE_DUMMY»] , чтобы запустить использование случайно сгенерированного secret_key_base , хранящегося во временном файле. Это полезно при прекомпиляции ассетов для production как части шага сборки, которому тогда не нужен доступ к секретам production.
$ SECRET_KEY_BASE_DUMMY=1 bundle exec rails assets:precompile
5.3. Компиляция в реальном времени
В некоторых обстоятельствах, возможно, необходимо использовать компиляцию в реальном времени. В этом режиме все запросы для ассетов в файлопроводе обрабатываются непосредственно Sprockets.
Чтобы включить эту опцию, установите:
config.assets.compile = true
При первом запросе ассеты компилируются и кэшируются так, как описывается в разделе про Хранилище кэша ассетов, и имена манифеста, использованного в хелперах, изменяется путем включения хэша SHA256.
Sprockets также устанавливает HTTP-заголовок Cache-Control как max-age=31536000 . Это сигнализирует всем кэшам между вашим сервером и браузером клиента, что это содержимое (отданный файл) может быть закэшировано на 1 год. В результате уменьшается количество запросов для этого ассета на ваш сервер; есть хороший шанс, что ассет будет в локальном кэше браузера или в каком-либо промежуточном кэше.
Этот режим использует больше памяти, имеет худшее быстродействие, чем по умолчанию, и не рекомендуется.
5.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 географически ближе к браузеру, запрос быстрее, и, так как серверу не нужно тратить время на раздачу ассетов, он может сфокусироваться на как можно быстром обслуживании кода приложения.
5.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.asset_host в config/environments/production.rb :
config.asset_host = 'mycdnsubdomain.fictional-cdn.com'
Необходимо предоставить только «host», это поддомен и корневой домен, не нужно указывать протокол или «scheme», такие как http:// или https:// . Когда запрашивается страница, протокол в сгенерированной ссылке на ассет будет соответствовать тому, какой доступ к странице.
Это значение также можно настроить с помощью переменной среды, чтобы упростить запуск staging-копий вашего сайта:
config.asset_host = ENV['CDN_HOST']
Чтобы это работало, вам необходимо установить на сервере CDN_HOST значение mycdnsubdomain.fictional-cdn.com .
После того, как вы настроили свой сервер и ваш CDN, пути ассета из хелперов такие как:
asset_path('smile.png') %>
Будут отрендерены полные пути к 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' %>
5.4.2. Настройка поведения кэширования CDN
CDN работает, кэшируя содержимое. Если в CDN имеется устаревшее или плохое содержимое, то это скорее навредит, чем поможет вашему приложению. Целью этого раздела является описание основных особенностей кэширования многих CDN. Поведение вашего определенного провайдера может немного отличаться.
5.4.2.1. Кэширование запросов CDN
Хотя CDN описывается как кэширующий файлы ассетов, фактически он кэширует целые запросы. Они включают тело ассета, а также его заголовки. Наиболее важным является Cache-Control , который сообщает CDN (и браузерам), как кэшировать содержимое. Это означает, что если кто-то запрашивает несуществующий ассет, наподобие /assets/i-dont-exist.png , и ваше приложение Rails возвращает 404, тогда ваш CDN скорее всего закэширует страницу 404, если присутствует валидный заголовок Cache-Control .
5.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 или любые другие добавленные ими заголовки.
5.4.2.3. CDN и заголовок Cache-Control
Заголовок Cache-Control описывает, как может быть закэширован запрос. Когда не используется 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 будет передан всем браузерам, обращающимся к этому ассету. Браузер тогда будет знать, что он может хранить этот ассет очень долго без необходимости повторного запроса.
5.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 по умолчанию, каждый ассет будет иметь другое имя, если он изменится. Таким образом, вам даже не нужно вручную прекращать любые элементы в вашем кэше. Используя иную технику для уникального имени ассета, ваши пользователи также получат самый свежий ассет.
6. Настройка файлопровода
6.1. Сжатие CSS
Одним из вариантов для сжатия CSS является YUI. YUI CSS compressor предоставляет минификацию.
Следующая строчка включает сжатие YUI и требует гем yui-compressor .
config.assets.css_compressor = :yui
6.2. Сжатие JavaScript
Возможные варианты для сжатия JavaScript это :terser , :closure и :yui . Они требуют использование гемов terser , closure-compiler или yui-compressor соответственно.
Возьмем, к примеру, гем terser . Этот гем оборачивает Terser (написанный для Node.js) в Ruby. Он сжимает ваш код, убирая пробелы и комментарии, сокращая имена локальных переменных и выполняя иные микро-оптимизации, наподобие замены ваших выражений if и else на тернарные операторы там, где возможно.
Следующая строчка вызывает terser для сжатия JavaScript.
config.assets.js_compressor = :terser
Необходим runtime, поддерживаемый ExecJS, чтобы использовать terser . Если используете macOS или Windows, у вас уже имеется JavaScript runtime, установленный в операционной системе.
Компрессия JavaScript также будет работать для ваших файлов JavaScript, когда вы загружаете свои ассеты через гемы importmap-rails или jsbundling-rails .
6.3. Сжатие ассетов
По умолчанию будет сгенерирована сжатая версия скомпилированных ассетов, вместе с несжатой версией ассетов. Сжатые ассеты помогают уменьшить передачу данных через канал связи. Это можно настроить, установив флажок gzip .
config.assets.gzip = false # отключает генерацию сжатых ассетов
Обратитесь к документации своего веб-сервера за инструкцией, как раздавать сжатые ассеты.
6.4. Использование собственного компрессора
Настройки конфигурации компрессора для CSS и JavaScript также могут принимать любой объект. Этот объект должен иметь метод compress , принимающий строку как единственный аргумент, и он должен возвращать строку.
class Transformer def compress(string) do_something_returning_a_string(string) end end
Чтобы включить это, передайте новый объект в конфигурационную опцию в application.rb :
config.assets.css_compressor = Transformer.new
6.5. Изменение пути assets
Публичный путь, используемый Sprockets по умолчанию, это /assets .
Он может быть заменен на что-то другое:
config.assets.prefix = "/some_other_path"
Это удобная опция, если вы обновляете старый проект, не использующий файлопровод и уже использующий этот путь, или вы хотите использовать этот путь для нового ресурса.
6.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 ).
За дальнейшими подробностями обращайтесь к документации своих веб-серверов:
7. Хранилище кэша ассетов
По умолчанию 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
8. Добавление ассетов в ваши гемы
Ассеты также могут идти от внешних источников в виде гемов.
Хорошим примером этого является гем jquery-rails . Этот гем содержит класс engine, унаследованный от Rails::Engine . Сделав так, Rails становится проинформированным, что директории для этого гема могут содержать ассеты, и директории app/assets , lib/assets и vendor/assets этого engine добавляются в путь поиска Sprockets.
9. Создание препроцессора в вашей библиотеке или геме
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
10. Альтернативные библиотеки
С годами существовало несколько подходов по умолчанию для обработки ассетов. Веб развивался и мы видели все больше и больше тяжеловесных Javascript приложений. В доктрине Rails мы полагаем, что надо выбирать Омакасэ, поэтому мы сфокусировались на настройке по умолчанию: Sprockets с картами импорта.
Мы в курсе, что нет универсального решения, подходящего каждому доступному фреймворку/расширению JavaScript и CSS. В экосистеме Rails есть другие библиотеки сборки, которые должны помочь вам в ситуациях, когда настройки по умолчанию не достаточно.
10.1. jsbundling-rails
jsbundling-rails это зависимая от Node.js альтернатива способу importmap-rails сборки JavaScript с помощью esbuild, rollup.js или Webpack.
Гем предоставляет процесс yarn build —watch для автоматической генерации вывода в development. Для production, он автоматически прицепляет задачу javascript:build к задаче assets:precompile , чтобы обеспечить, что все ваши зависимости от пакетов были установлены, и JavaScript был создан для всех точек входа.
Когда использовать вместо importmap-rails ? Если ваш код JavaScript зависит от транспиляции, то есть если вы используете Babel, TypeScript или формат React JSX , то jsbundling-rails правильный способ.
10.2. Webpacker/Shakapacker
Webpacker был препроцессором JavaScript по умолчанию и сборщиком для Rails 5 и 6. Теперь он не разрабатывается. Существует преемник, называющийся shakapacker , но он не поддерживается командой или проектом Rails.
В отличие от других библиотек в этом списке, webpacker / shakapacker полностью не зависят от Sprockets и могут обрабатывать файлы как JavaScript, так и CSS.
Прочитайте документ по сравнению с Webpacker, чтобы понять разницу между jsbundling-rails и webpacker / shakapacker .
10.3. cssbundling-rails
cssbundling-rails позволяет сборку и обработку ваших CSS с помощью Tailwind CSS, Bootstrap, Bulma, PostCSS или Dart Sass, а затем доставляет CSS посредством файлопровода.
Он работает схожим образом с jsbundling-rails , поэтому добавьте зависимость от Node.js в ваше приложение с помощью процесса yarn build:css —watch , чтобы заново сгенерировать ваши таблицы стилей в development и прицепляется к задаче assets:precompile в production.
Какое отличие от Sprockets? Sprockets сам по себе не способен транспилировать Sass в CSS, требуется Node.js, чтобы сгенерировать файлы .css из файлов .sass . Как только сгенерированы файлы .css , тогда Sprockets сможет доставить их вашим клиентам.
cssbundling-rails полагается на Node для обработки CSS. Гемы dartsass-rails и tailwindcss-rails это самостоятельные версии Tailwind CSS и Dart Sass, что означает отсутствие зависимости от Node. Если вы используете importmap-rails для обработки Javascript и dartsass-rails или tailwindcss-rails для CSS, можно полностью избежать зависимости от Node, что приведет к менее сложному решению.
10.4. dartsass-rails
Если хотите использовать Sass в приложении, dartsass-rails пришел на замену для устаревшего гема sassc-rails . dartsass-rails использует реализацию Dart Sass вместо устаревшего в 2020 году LibSass , используемого в sassc-rails .
В отличие от sassc-rails , новый гем напрямую не интегрируется со Sprockets . Обратитесь к домашней странице гема за инструкциями по установке/миграции.
Популярный гем sassc-rails не поддерживается с 2019 года.
10.5. tailwindcss-rails
tailwindcss-rails это гем-обертка для самостоятельной выполняемой версии фреймворка Tailwind CSS v3. Используется для новых приложений, когда предоставлена —css tailwind для команды rails new . Предоставляет процесс watch , чтобы автоматически сгенерировать вывод Tailwind в development. Для production он прицепляется к задаче assets:precompile .
Лицензия CC BY-SA 4.0 «Rails», «Ruby on Rails» и логотип Rails — торговые марки DHH
Обзор техник кэширования в React
Получение данных в React — это один процесс, а вот их хранение и кэширование — совсем другой. Возможности кажутся безграничными, а отличия зачастую настолько тонкие, что выбрать правильную технику становится не так уж и просто.
В статье мы рассмотрим различные техники, обращая внимание на все их нюансы и неуловимые различия. Что выбрать: useMemo или мемоизацию, хранение данных с помощью useState или контекста? Изучив материал, вы сможете принимать осознанные решения относительно кэширования данных и будете владеть подробной информацией по данной теме. А еще вас ждет много GIF-анимации.
Данные
Перед тем, как углубиться в код, быстро просмотрим данные, которые нам предстоит получать в большинстве компонентов. Файл, выступающий в роли API, выглядит следующим образом:
export default function handler(req, res) setTimeout(
() =>
res.status(200).json( randomNumber: Math.round(Math.random() * 10000),
people: [
< name: "John Doe" >,
< name: "Olive Yew" >,
< name: "Allie Grater" >,
],
>),
750
);
>
Этот код выполняется при осуществлении запроса к пути проекта /api/people . Как видно, мы возвращаем объект с двумя свойствами:
- randomNumber : произвольное число в диапазоне 0–10000.
- people : статический массив с тремя вымышленными именами.
Свойство randomNumber поможет наглядно продемонстрировать, происходит ли отображение кэшированных данных во фронтенде. По мере углубления в тему вы поймете, о чем идет речь.
Обратите внимание, что мы имитируем небольшую сетевую задержку, используя setTimeout .
Компонент People
Отображая данные из API, мы передаем их в компонент с именем PeopleRenderer . Выглядит это следующим образом:
Имея в виду эту вводную информацию, рассмотрим первую технику.
useEffect
Внутри компонентов для получения данных можно задействовать хук useEffect , после чего сохранять эти данные локально (внутри компонента) с помощью useState :
import < useEffect, useState >from "react";
import PeopleRenderer from "../PeopleRenderer";
export default function PeopleUseEffect() const [json, setJson] = useState(null);
useEffect(() => fetch("/api/people")
.then((response) => response.json())
.then((data) => setJson(data));
>, []);
return />;
>
При передаче пустого массива в качестве второго параметра (строка 11) хук useEffect будет выполнен после встраивания компонента в DOM. При новом отображении компонента повторно он выполняться уже не будет. Назовем этот хук “одноразовым”.
В связи с применением useEffect таким способом следует упомянуть о том, что при наличии нескольких экземпляров компонента в DOM все они будут получать данные по отдельности (при встраивании).
В этой технике нет ничего плохого. Более того, иногда она приходится как нельзя кстати. Но в ряде случаев требуется единожды получить данные и повторно их использовать во всех других экземплярах посредством кэширования. С этой целью можно обратиться к другим техникам.
Мемоизация
Мемоизация — это мудреный термин для обозначения очень простой техники. Суть ее в том, что вы создаете функцию, которая при каждом вызове сохраняет результаты в кэше, прежде чем их вернуть.
При первом вызове такой мемоизованной функции результаты вычисляются (или получаются — все зависит от выполняемых операций внутри тела функции). Прежде чем вернуть результаты, вы сохраняете их в кэше в ключе, который создается с входными параметрами:
const MyMemoizedFunction = (age) => if(cache.hasKey(age)) return cache.get(age);
>
const value = `You are $ years old!`;
cache.set(age, value);
return value;
>
Написание такого шаблонного кода вскоре может стать трудоемким процессом, поэтому такие известные библиотеки, как Lodash и Underscore, предоставляют вспомогательные функции, упрощая создание мемоизованных:
import memoize from "lodash.memoize";
const MyFunction = (age) => return `You are $ years old!`;
>
const MyMemoizedFunction = memoize(MyFunction);
Мемоизация для получения данных
Данная техника подходит для получения данных. Мы создаем функцию getData , возвращающую обещание Promise , разрешение которого происходит по окончании fetch-запроса. Мы сохраняем этот Promise :
import memoize from "lodash.memoize";
const getData = () =>
new Promise((resolve) => fetch("http://localhost:3000/api/people")
.then((response) => response.json())
.then((data) => resolve(data));
>);
export default memoize(getData);
Обратите внимание, что в данном примере мы не проводим обработку ошибок. Эта тема заслуживает отдельной статьи, особенно в связи с мемоизацией (поскольку отклоненное обещание Promise тоже бы сохранилось, что чревато проблемами).
Теперь заменим хук useEffect на другой, который выглядит следующим образом:
import < useEffect, useState >from "react";
import getData from "./getData";
import PeopleRenderer from "../PeopleRenderer";
export default function PeopleMemoize() const [json, setJson] = useState(null);
useEffect(() => getData().then((data) => setJson(data));
>, []);
return />;
>
Поскольку результат getData сохраняется, то при встраивании все компоненты получат одинаковые данные:
Стоит также отметить, что при открытии страницы memoize.tsx (до добавления первого экземпляра компонента) данные уже предварительно получаются. Дело в том, что мы определили функцию getData в отдельном расположенном в верхней части страницы файле, при загрузке которого создается Promise .
Можно аннулировать (очистить) кэш мемоизованной функции, присвоив ее свойству cache нового Cache .
getData.cache = new memoize.Cache();
В качестве альтернативного варианта можно очистить существующий кэш (это экземпляр Map ).
getData.cache.clear();
Однако данная функциональность характерна лишь для Lodash. Другие же библиотеки требуют иных решений. Ниже представлен наглядный пример очистки кэша в действии.
React Context
React Context — еще один известный и подробно изученный инструмент (в отношении которого однако часто происходит недопонимание). В связи с этим в очередной раз напоминаю, что он не заменяет такие инструменты, как Redux, поскольку не является средством управления состоянием.
Итак, что такое Context? Это механизм для внедрения данных в дерево компонентов. Если у вас есть некие данные, то их можно сохранить, к примеру, с помощью хука useState , внутри компонента, находящегося на более высоком уровне иерархии. Затем с помощью Provider Context внедрить эти данные в дерево, что позволит считывать (использовать) их в любом нижестоящем компоненте.
Для большей ясности приведем пример. Сначала создаем новый контекст.
import < createContext >from "react";
export const PeopleContext = createContext(null);
После этого обертываем компонент, отображающий компоненты People , в Provider Context.
export default function Context() const [json, setJson] = useState(null);
useEffect(() => fetch("/api/people")
.then((response) => response.json())
.then((data) => setJson(data));
>, []);
return (
>>
.
);
>
В 12 строке у нас есть возможность отобразить любой элемент. В определенный момент, опускаясь вниз по дереву, отобразим компонент(ы) People .
import < useContext >from "react";
import PeopleRenderer from "../PeopleRenderer";
import < PeopleContext >from "./context";
export default function PeopleWithContext() const < json >= useContext(PeopleContext);
return />;
>
Можно применить значение из Provider с помощью хука useContext , получая следующий результат.
Обратите внимание на одно важное здесь отличие! На последнем этапе анимации мы нажимаем кнопку “Set new seed”. При этом повторно получаются данные, хранящиеся в Provider Context. По завершении этого (спустя 750 мс) полученные данные становятся новым значением Provider , и компоненты People отображаются еще раз. Как видите, все они пользуются одними и теми же общими данными.
Эта техника значительно отличается от ранее рассмотренного примера мемоизации, в котором каждый компонент хранил собственную копию мемоизованных данных с помощью useState . В этом же случае, задействуя контекст, они не хранят копии, а оперируют только ссылками на один и тот же объект. Вот почему все компоненты обновляются одинаковыми данными при обновлении значения в Provider .
useMemo
В последнем, но не маловажном разделе, проведем беглый обзор useMemo . От предыдущих техник этот хук отличается тем, что он является лишь формой кэширования на локальном уровне: внутри одного экземпляра компонента. useMemo не предназначен для совместного использования данных несколькими компонентами — по крайней мере, не без обходных решений в виде пробрасывания (prop-drilling) пропсов или внедрения зависимостей (например, React Context).
useMemo — это инструмент оптимизации. Он позволяет избежать пересчета значения при каждом повторном отображении компонента. Нет лучшего объяснения, чем сама документация, но рассмотрим пример.
export default function Memo() const getRnd = () => Math.round(Math.random() * 10000);
const [age, setAge] = useState(24);
const [randomNumber, setRandomNumber] = useState(getRnd());
const pow = useMemo(() => Math.pow(age, 2) + getRnd(), [age]);
return (
.
);
>
- getRnd (строка 2): функция, возвращающая случайное число в диапазоне 0–10000.
- age (строка 4): с помощью useState сохраняет число, обозначающее возраст.
- randomNumber (строка 5): сохраняет случайное число посредством useState .
И наконец, в строке 7 применяется useMemo . Мы запоминаем результат вызова функции и сохраняем его в переменной pow . Функция возвращает значение суммы age , возведенной во вторую степень, и случайного числа. Поскольку она зависит от переменной age , мы передаем эту переменную в аргумент зависимости вызова useMemo .
Функция выполняется только в случае изменения значения age . Если компонент отображается повторно, а значение age не изменилось, useMemo просто вернет мемоизованный результат.
В этом примере вычисление pow не представляет большой сложности, но не трудно представить себе все преимущества данного подхода в случае с более громоздкой функцией и при необходимости частого повторного отображения компонента.
Две последние анимации показывают, что происходит. Сначала мы обновляем randomNumber , не затрагивая значение age , вследствие чего наблюдаем useMemo в действии (значение pow не меняется при повторном отображении компонента). При каждом нажатии на кнопку компонент заново отображается.
Однако изменение значения age повлечет за собой изменение pow , поскольку вызов useMemo зависит от значения age .
Заключение
Для кэширования данных в JavaScript существуют разные техники и вспомогательные средства. В данной статье мы прошлись по самым верхам, тем не менее надеюсь, что полученные знания помогут вам в дальнейшем продвижении в мире разработки.
С полным вариантом кода, представленного в статье, можно ознакомиться в репозитории на GitLab.
Спасибо за внимание!
- 3 способа улучшить управление состоянием в React
- Как сделать приложение с дополненной реальностью, используя React Native
- Управляйте приложением React с помощью голоса
Как работают нативные JavaScript imports


Lead Front-end Developer в One Source, Преподаватель Компьютерной школы Hillel.
- 1. Навіщо потрібні import-и ?
- 2. Как работают import-ы
- 3. Как V8 JavaScript Engine делает импорты?
- 4. Как использовать import?
- 5. О динамическом импорте
- 6. Интересное
- 7. Вывод
Стандарт EcmaScript 2015 (ES6) ввел новый инструмент для работы с модулями JavaScript, известный как import. Этот инструмент позволяет легко импортировать функции, классы, переменные и объекты из других файлов, создавая модульную структуру вашего кода.
В этой статье мы рассмотрим, как работают нативные JavaScript import-ы. Важно понимать, что в этом материале мы не затрагиваем тему того, как работают импорты в инструментах сборки, например, webpack.
Зачем нужны import-ы?
Модульный код помогает разделить программу на логические части (модули), что облегчает организацию, поддержку и расширение кода.
Он способствует повторному использованию кода, изолирует переменные и функции других модулей, упрощает тестирование и развитие программы и облегчает сотрудничество разработчиков. Также модульный код использует ключевое слово import для импорта функций, классов и переменных из других модулей и поддерживает использование сторонних библиотек для улучшения функциональности.
Как работают import-ы
Импорты в JavaScript определяются спецификацией EcmaScript (ES6 и более поздних версий). Под капотом механизм импорта включает в себя несколько ключевых аспектов:
Размещение файлов
Когда вы производите импорт в JavaScript, вы указываете путь к файлу, который нужно импортировать. Этот путь может быть абсолютным или относительным, и браузер (или среда выполнения, если это Node.js) выполняет запрос на сервер для загрузки этого файла.
HTTP-запрос
Браузер (или среда выполнения) делает HTTP-запрос на сервер для загрузки файла, указанного в импорте. Это может быть GET-запросом или другими HTTP-методами.
Загрузка файла
Сервер обрабатывает запрос и посылает содержимое файла на клиентскую сторону. Файл загружается в память.
Выполнение кода
После загрузки файла содержимое файла обрабатывается и выполняется в среде выполнения (браузер или Node.js). Если файл содержит импорты, импортируемые модули также загружаются и выполняются.
Область видимости
Импортируемые переменные или функции становятся доступными в области видимости импортируемого файла.
// module.js export function greet(name) < console.log(`Hello, $!`); > // main.js import < greet >from './module.js'; greet('Alice');
Во время выполнения main.js браузер (или Node.js) выполнит HTTP-запрос в файл module.js, загрузит его содержимое, выполнит функцию greet и выведет «Hello, Alice!».
Как V8 JavaScript Engine делает импорты?
V8 — это виртуальная машина JavaScript, которая используется в браузерах и Node.js для выполнения JavaScript-кода. V8 взаимодействует с импортами в JavaScript через стандартные механизмы ECMAScript (ES6 и более поздних версий).
Рассмотрим, как V8 обрабатывает импорты:
Анализ и парсинг:
При анализе и парсинге импортируемых файлов JavaScript-движок (в нашем случае, V8) разбирает входящий текст кода и идентифицирует ключевые слова import и export. Он также распознает импортируемые и экспортируемые имена. Парсер понимает синтаксис языка и определяет структуру модуля.
Распознавание путей:
Во время парсинга V8 распознает пути к импортируемым файлам. Он определяет, являются ли эти пути абсолютными (начинаются с `/`) или относительными (начинаются с `./` или `../`). Внутренняя логика определяет, какой файл нужно скачать.
Загрузка файлов:
V8 инициирует загрузку файлов, указанных в импортах.
В веб-браузерах это может включать выполнение HTTP-запроса на сервер с помощью API, такого как `fetch`, для получения содержимого файла. В среде Node.js это может включать в себя обращение к файловой системе для чтения файла с диска.
Кэширование импортов:
Для оптимизации производительности V8 может кэшировать импортируемые файлы во избежание повторной загрузки. Если этот файл импортируется несколько раз в разных частях кода, он может быть взят из кэша, что уменьшает нагрузку на сеть или файловую систему.
Выполнение кода:
После загрузки V8 обрабатывает содержимое импортированных файлов и выполняет код JavaScript.
Это включает выполнение всех импортируемых модулей и установку связей между экспортируемыми и импортируемыми объектами. Выполнение кода включает обработку функций, переменных, классов и любого другого кода, содержащегося в импортированных файлах.
Построение зависимостей:
V8 следит за зависимостями между модулями и обеспечивает, что модули всегда загружаются в правильном порядке. Это помогает избежать циклических зависимостей и обеспечить корректное исполнение кода.
Этот механизм позволяет разработчикам создавать структуру проекта, состоящую из разных модулей, и использовать их в соответствии с потребностями.
События и обработка ошибок:
V8 отслеживает события загрузки и выполнения модулей и обрабатывает ошибки, если они возникают в ходе этих операций.
Это может включать в себя обработку ошибок отсутствия файла, ошибок синтаксиса или других видов ошибок. Обработка ошибок помогает идентифицировать и устранить проблемы с импортами.
Область видимости:
После загрузки и выполнения модуля импортируемые объекты (функции, переменные, классы) становятся доступными в области видимости модуля, который делает импорт.
Вы можете использовать их в коде, импортирующем модуль. Механизм области видимости помогает изолировать переменные и функции одного модуля от других, уменьшая возможные конфликты и облегчая управление кодом.
Эти механизмы взаимодействуют, чтобы позволить создавать структурированный и модульный код JavaScript.
Как использовать import?
Импорты в JavaScript (JS) и TypeScript (TS) — это способ включить функциональность из других модулей (файлов) в ваш код. Это помогает организовать проект и поддерживать чистоту кода, разделяя его на меньшие, самостоятельные части.
У JavaScript и TypeScript существует несколько способов импорта. Рассмотрим основные с простейшими примерами на языке JavaScript.
Default Imports (импорт по умолчанию):
В файле module.js мы можем иметь функцию по умолчанию, которую мы хотим импортировать.
// module.js export default function greet(name) < console.log(`Hello, $!`); >
В другом файле мы можем импортировать эту функцию так:
// main.js import greet from './module.js'; greet('Alice');
Named Imports (импорт по имени):
// module.js export function greet(name) < console.log(`Hello, $!`); > export function farewell(name) < console.log(`Goodbye, $!`); >
В другом файле мы импортируем их так:
// main.js import < greet, farewell >from './module.js'; greet('Charlie'); farewell('David');
Aliasing (импорт с псевдонимами):
Вы можете создать псевдонимы для импортируемых модулей или объектов, чтобы облегчить их использование.
// module.js export function greet(name) < console.log(`Hello, $!`); > // main.js import < greet as sayHello >from './module.js'; sayHello('Grace');
Это главные примеры импортов в JS. Если вам нужно импортировать объекты или другие вещи, синтаксис остается схожим, иногда другие конструкции могут использоваться для разных ситуаций.
О динамическом импорте
Динамический импорт — это механизм JavaScript, позволяющий загружать модули не в момент компиляции, а во время выполнения программы. Он придает большую гибкость и позволяет оптимизировать загрузку модулей, особенно в больших приложениях.
Синтаксис
Для того чтобы использовать динамический импорт, вы используете ключевое слово import() вместо обычного import.
Эта функция принимает строку с помощью модуля, который вы хотите загрузить. Динамический импорт возвращает объект Promise.
const dynamicImport = import('./myModule.js');
Использование Promise:
Объект, возвращаемый из import(), является обещанием (Promise). Вы можете использовать метод .then() или ключевое слово await для решения этого обещания и выполнения кода, когда модуль загружен.
Пример использования .then():
dynamicImport .then((module) => < // Використання завантаженого модуля >) .catch((error) => < // Обробка помилки завантаження >);
Пример использования await:
try < const module = await dynamicImport; // Використання завантаженого модуля >catch (error) < // Обробка помилки завантаження >
Ленивая загрузка
Динамический импорт стремится загрузить модуль только тогда, когда он фактически нужен в коде. Это позволяет уменьшить время загрузки страницы, поскольку только тот код загружается, который требуется для текущей страницы или взаимодействия.
В зависимости от переменных
Динамический импорт позволяет загружать модули на основе значений переменных или результатов функций. Например, можно сконструировать путь к модулю на основе пользовательского ввода или на других условиях.
const moduleName = 'myModule'; const dynamicImport = import(`./$.js`);
Интересное
Импорт в форме export* from …
Вы можете импортировать все экспорты из другого модуля с помощью export*.
Это позволяет импортировать все функции и переменные из другого модуля в текущий модуль.
export * from './myModule.js';
Внешний импорт файлов
В некоторых случаях, кроме загрузки модулей JavaScript, можно импортировать другие типы файлов, такие как изображения или стили CSS.
Это позволяет включать содержание файлов непосредственно в свой код.
import myImage from './myImage.png';
Модульные импорты в HTML
Вы можете использовать импорты модулей непосредственно в HTML-файлах, чтобы загружать и выполнять JavaScript-код при загрузке страницы.
Динамические ключи для импорта
Вы можете генерировать импорты с динамически созданными ключами, например, на основе переменных значений. Это позволяет загружать модули с помощью переменных или результатов функций.
const moduleName = 'myModule'; import(`./$.js`) .then((module) => < // Використання завантаженого модуля >);
Вывод
Использование импортов в JavaScript имеет множество преимуществ:
Импорты позволяют разделить ваш код на небольшие, самодостаточные модули, что облегчает разработку, понимание и поддержку кода.
- Разделение функциональности:
Вы можете импортировать только те части кода, которые вам нужны, вместо загрузки всего. Это уменьшает объем загруженного кода и улучшает производительность.
- Сохранность ресурсов:
Импорты позволяют загружать ресурсы, такие как изображения или стили, только если они нужны, что облегчает оптимизацию загрузки страницы.
- Зависимость и поддержка:
Импорты помогают управлять зависимостями вашего проекта и облегчают обновление модулей.
- Ленивая загрузка:
Некоторые импорты могут быть загружены «линейно», то есть только тогда, когда они фактически нужны, уменьшая время загрузки страницы.
- Удобство и читабельность:
Импорты делают код более понятным, так как они указывают, откуда загружены символы или функции.
Все эти преимущества делают импорты важным инструментом для разработки как небольших, так и крупных приложений JavaScript.
Рекомендуем курс по теме
JavaScript Basiс basic

Lead Front-end Developer в One Source, Преподаватель Компьютерной школы Hillel.
Методы оптимизации JavaScript для более быстрой загрузки веб-сайта. Подробное руководство

Освойте оптимизацию JavaScript для повышения производительности веб-сайта: минимизируйте размеры файлов, уменьшите количество запросов, используйте кэширование и асинхронную загрузку, а также используйте лучшие практики для более быстрой загрузки и улучшения UX.
В современном быстро меняющемся цифровом мире производительность веб-сайта играет решающую роль в определении успеха любого онлайн-бизнеса. Быстрый, отзывчивый и удобный веб-сайт не только привлекает и удерживает посетителей, но и способствует повышению рейтинга в поисковых системах, повышению коэффициента конверсии и улучшению пользовательского опыта (UX). Как инженер-программист или веб-разработчик, важно расставить приоритеты в методах оптимизации производительности в своих проектах. В этой статье рассмотрим различные методы оптимизации кода JavaScript, включая минимизацию размеров файлов, сокращение сетевых запросов, использование кэширования и асинхронной загрузки, а также использование лучших практик для обеспечения более быстрой загрузки и улучшения UX.
Минимизация размеров файлов
Одним из важнейших факторов, влияющих на время загрузки веб-сайта, является размер файлов, предоставляемых пользователю. Загрузка больших файлов занимает больше времени и может привести к медленной загрузке вашего веб-сайта, что приведет к неоптимальному взаимодействию с пользователем. Файлы JavaScript не являются исключением, и оптимизация их размеров является фундаментальным шагом в повышении производительности вашего сайта. Минификация — это процесс удаления ненужных символов (таких как пробелы, комментарии и разрывы строк) и сокращения имен переменных в коде JavaScript без ущерба для его функциональности. Это приводит к значительно меньшему размеру файла, что, в свою очередь, приводит к более быстрой загрузке и повышению производительности.
Пример кода JavaScript: до и после минификации
Давайте посмотрим на простой пример, чтобы понять влияние минификации на размер файла: Перед минификацией:
// Функция вычисления суммы двух чисел function addNumbers(num1, num2) < return num1 + num2; >// Вызов функции для вычисления суммы 3 и 5 const sum = addNumbers(3, 5); // Вывод результата в консоль console.log("The sum is:", sum);function addNumbers(n,e)const sum=addNumbers(3,5);console.log("The sum is:",sum);Как вы можете видеть, уменьшенная версия кода значительно меньше, с удаленными ненужными символами и сокращенными именами переменных. Это приводит к меньшему размеру файла и более быстрой загрузке, не влияя на функциональность кода.
Сжатие файлов
Сжатие — это еще один метод, используемый для уменьшения размера файлов, что приводит к сокращению времени загрузки веб-сайта. Он работает, применяя алгоритмы для сжатия данных в файлах, делая их меньше без потери их функциональности. Когда браузер запрашивает сжатый файл, он распаковывается на лету, что позволяет правильно отображать и выполнять содержимое. Существует два широко используемых алгоритма сжатия файлов JavaScript: Gzip и Brotli. Gzip долгое время был стандартом де-факто, но Brotli, новый алгоритм сжатия, разработанный Google, становится все более популярным благодаря превосходной степени сжатия и скорости.
Методы сжатия Gzip и Brotli
Gzip: Gzip — это широко распространенный алгоритм сжатия, который может значительно уменьшить размер файлов JavaScript. Gzip использует алгоритм Deflate, который сочетает в себе кодирование LZ77 и Huffman для эффективного сжатия данных. Brotli: Brotli — это более новый алгоритм сжатия, разработанный Google, обеспечивающий лучшую степень сжатия, чем Gzip. Brotli использует комбинацию LZ77, кодирования Хаффмана и новой техники моделирования контекста для достижения более высоких степеней сжатия. В большинстве случаев Brotli превосходит Gzip как по степени сжатия, так и по скорости, что делает его привлекательным вариантом для современных веб-приложений.
Конфигурация на стороне сервера для сжатия
- Apache: Включите модуль mod_deflate для сжатия Gzip или mod_brotli для сжатия Brotli и настройте соответствующие параметры в файле .htaccess или конфигурации виртуального хоста.
- Nginx: Используйте директивы gzip или brotli в конфигурационном файле Nginx, чтобы включить сжатие и указать настройки.
- Node.js: Для серверов на базе Node.js вы можете использовать промежуточное программное обеспечение (middleware), такое как compression для Gzip или shrink-ray-current для Brotli в сочетании с Express или аналогичным веб-фреймворком.
Бандлинг для сокращения сетевых запросов
Сокращение количества сетевых запросов имеет решающее значение для повышения производительности веб-сайта, поскольку каждый запрос увеличивает задержку и потребляет пропускную способность.
Что такое бандлинг?
Бандлинг — это процесс объединения нескольких файлов JavaScript в один файл. Это уменьшает количество HTTP-запросов, которые необходимо сделать браузеру, тем самым ускоряя процесс загрузки. Объединение может значительно повысить производительность веб-сайта, особенно для веб-сайтов с большим количеством файлов JavaScript меньшего размера.
Инструменты для бандлинга
- Webpack: Webpack — это мощный и гибкий сборщик модулей, который не только объединяет файлы JavaScript, но и обрабатывает другие ресурсы, такие как css и изображения. Он имеет надежную экосистему плагинов, позволяющую расширять его функциональность по мере необходимости.
- Rollup: Rollup — еще один популярный сборщик модулей JavaScript, ориентированный на простоту и производительность. Он особенно хорошо подходит для объединения библиотек и может выводить несколько форматов, включая модули CommonJS, AMD и ES.
Пример кода JavaScript: объединение нескольких файлов
Чтобы продемонстрировать процесс объединения, предположим, что у вас есть три отдельных файла JavaScript:
// main.js import < greet >from './greeting.js'; import < calculate >from './math.js'; console.log(greet('John')); console.log(calculate(5, 3));// greeting.js export function greet(name) < return `Hello, $!`; >// math.js export function calculate(x, y)Используя инструмент бандлинга, такой как Webpack или Rollup, вы можете объединить эти файлы в один связанный файл. Выходные данные могут выглядеть примерно так:
(function () < 'use strict'; function greet(name) < return `Hello, $!`; > function calculate(x, y) < return x * y; >console.log(greet('John')); console.log(calculate(5, 3)); >)();Как видите, результирующий файл содержит весь необходимый код из исходных файлов в одном автономном блоке, что сокращает количество сетевых запросов, необходимых для загрузки скриптов. Минимизируя количество запросов, вы можете сократить время, необходимое браузеру для загрузки и обработки необходимых ресурсов, что приведет к более быстрой загрузке и более отзывчивому взаимодействию с пользователем.
Использование спрайтов для изображений и иконок
Использование спрайтов изображений — еще один метод сокращения сетевых запросов и повышения производительности веб-сайта. Спрайты — это, по сути, один файл изображения, содержащий несколько изображений меньшего размера, таких как значки или элементы пользовательского интерфейса.
Что такое спрайт изображения
Спрайт изображения — это большое изображение, содержащее несколько изображений меньшего размера, расположенных в виде сетки. В коде CSS или JavaScript вы можете ссылаться на отдельные изображения в спрайте, указывая их положение и размеры. Этот метод позволяет загружать множество изображений с помощью всего одного HTTP-запроса, уменьшая задержку и сокращая время загрузки.
Создание спрайтов изображений
- Инструменты генератора спрайтов: Онлайн-инструменты, такие как SpritePad или Stitches, позволяют загружать несколько изображений и автоматически генерировать спрайт вместе с соответствующим кодом CSS.
- Программное обеспечение для редактирования изображений: такие программы, как Adobe Photoshop или GIMP, можно использовать для ручного создания спрайтов, упорядочивая меньшие изображения в новом файле и экспортируя результат в виде одного изображения.
Пример кода CSS: использование спрайтов изображений
Предположим, что есть изображение спрайта с именем icons.png , содержащее несколько значков. Вы можете использовать следующий CSS код для отображения отдельных значков в качестве фоновых изображений для разных элементов:
.icon < width: 32px; height: 32px; background-image: url('icons.png'); >.icon-search < background-position: 0 0; >.icon-settings < background-position: -32px 0; >.icon-userКаждый класс значков определяет положение соответствующей иконки внутри спрайта, что позволяет отобразить нужное изображение без дополнительных HTTP-запросов. Объединяя эти меньшие изображения в один файл, браузеру нужно запросить только одно изображение, уменьшая количество HTTP-запросов.
Ленивая загрузка ресурсов
Ленивая загрузка — это метод, который откладывает загрузку некритических ресурсов до тех пор, пока они действительно не понадобятся. Это означает, что вместо того, чтобы загружать все ресурсы заранее, вы загружаете только те, которые необходимы для немедленного просмотра, в то время как остальные извлекаются по мере их актуальности. Отложенная загрузка может значительно улучшить начальное время загрузки и воспринимаемую производительность веб-сайта, особенно при работе с большими ресурсами, такими как изображения или длинные скрипты.
Пример кода JavaScript: реализация отложенной загрузки
Чтобы проиллюстрировать отложенную загрузку, давайте воспользуемся примером загрузки изображений только тогда, когда они становятся видимыми в окне просмотра. Это можно сделать с помощью API IntersectionObserver . Вот простая реализация: Во-первых, добавьте data-src к элементам изображения, содержащий фактический источник изображения:
Затем создайте скрипт, который настраивает IntersectionObserver для загрузки изображений по мере их поступления в окно просмотра (viewport):
document.addEventListener('DOMContentLoaded', function () < const lazyImages = [].slice.call(document.querySelectorAll('.lazy-load')); if ('IntersectionObserver' in window) < const lazyImageObserver = new IntersectionObserver(function (entries, observer) < entries.forEach(function (entry) < if (entry.isIntersecting) < const lazyImage = entry.target; lazyImage.src = lazyImage.dataset.src; lazyImage.classList.remove('lazy-load'); lazyImageObserver.unobserve(lazyImage); >>); >); lazyImages.forEach(function (lazyImage) < lazyImageObserver.observe(lazyImage); >); > >);В этом примере IntersectionObserver следит за тем, чтобы изображения .lazy-load попадали в окно просмотра. При обнаружении изображения атрибуту src присваивается атрибут data-src , что приводит к фактической загрузке изображения. После загрузки образа класс .lazy-load удаляется, и наблюдение за изображением останавливается ( .unobserve() ). Используя эту простую технику отложенной загрузки, вы можете гарантировать, что загружаются только те изображения, которые в данный момент находятся в поле зрения, уменьшая количество сетевых запросов и улучшая начальное время загрузки вашего веб-сайта.
Использование кэширования
Производительность веб-сайта является решающим фактором в обеспечении отличного пользовательского опыта. Одним из важных методов повышения производительности является кэширование, которое позволяет браузерам хранить копии ресурсов вашего веб-сайта, таких как изображения, таблицы стилей и скрипты. Это снижает потребность в повторных загрузках и ускоряет время загрузки. В этом разделе мы рассмотрим концепцию кэширования и то, как вы можете использовать его для повышения производительности вашего сайта.
Кэширование браузера
Кэширование браузера — это механизм, который позволяет веб-браузерам локально хранить копии файлов веб-сайта. Когда пользователь повторно посещает ваш сайт, браузер может загружать эти ресурсы из кэша вместо того, чтобы загружать их снова, что приводит к более быстрой загрузке и снижению нагрузки на сервер. Настроив сервер на доставку соответствующих заголовков кэширования, можно контролировать, какие ресурсы кэшируются и на какой срок.
Заголовки Cache-Control и ETag
Двумя важными заголовками для управления кэшированием браузера являются Cache-Control и ETag . Заголовок Cache-Control позволяет задать директивы кэша, такие как максимальный срок службы ресурса в кэше или необходимость его повторной проверки. Например, можно использовать Cache-Control: public, max-age=3600 , чтобы указать, что ресурс может кэшироваться в течение одного часа. Заголовок ETag предоставляет уникальный идентификатор (обычно хэш) для определенной версии ресурса. Когда браузер запрашивает ресурс, он отправляет значение ETag , которое находится в его кэше. Если значение ETag сервера совпадает со значением, отправленным браузером, сервер отвечает статусом 304 Not Modified , и браузер использует кэшированную версию. Этот механизм помогает гарантировать, что браузер всегда имеет самую последнюю версию ресурса.
Настройка кэширования на стороне сервера
Чтобы включить кэширование браузера, необходимо настроить сервер на доставку соответствующих заголовков для ресурсов. Этот процесс зависит от программного обеспечения сервера. Например, на сервере Apache вы можете использовать файл .htaccess для установки заголовков кэширования:
Header set Cache-Control "public, max-age=86400" Эта конфигурация задает заголовок Cache-Control для файлов CSS, JS, JPG и PNG, что позволяет кэшировать их в течение 24 часов. Используя кэширование браузера, вы можете значительно сократить объем данных, которые необходимо извлекать при повторном посещении пользователем вашего сайта, ускоряя время загрузки и улучшая общий пользовательский опыт.
Использование асинхронной загрузки
По мере усложнения веб-сайтов управление загрузкой файлов JavaScript становится все более важным для производительности. По умолчанию браузеры загружают скрипты синхронно, блокируя процесс отрисовки до тех пор, пока скрипт не будет полностью загружен и выполнен. Асинхронная загрузка позволяет загружать скрипты параллельно с другими ресурсами, предотвращая их блокировку рендеринга и сокращая общее время загрузки. В этом разделе мы обсудим, как использовать асинхронную загрузку файлов JavaScript для повышения производительности вашего веб-сайта.
Асинхронная загрузка файлов JavaScript
Асинхронная загрузка позволяет браузерам загружать и выполнять файлы JavaScript, не блокируя рендеринг остальной части страницы. Такой подход не только ускоряет первоначальный рендеринг вашего веб-сайта, но и снижает риск того, что медленный или не отвечающий скрипт вызовет задержки. С помощью атрибутов async и defer можно управлять загрузкой и выполнением файлов JavaScript.
Использование атрибутов Async и Defer
- async : атрибут async указывает браузеру загрузить сценарий, не блокируя рендеринг. Как только скрипт будет загружен, браузер приостановит рендеринг для его выполнения. Это полезно для скриптов, которые не полагаются на другие скрипты или полную загрузку модели DOM.
- defer : атрибут defer предписывает браузеру загрузить скрипт, не блокируя рендеринг, но откладывает выполнение до тех пор, пока модель DOM не будет полностью проанализирована. Это полезно для скриптов, зависящих от модели DOM или других скриптов.
Пример кода JavaScript: использование async и defer
Рассмотрим пример использования атрибутов async и defer в HTML-файле:
В этом примере main.js загружается с атрибутом defer , гарантируя, что он не будет блокировать рендеринг и будет выполнен после полного анализа DOM. Между тем, analytics.js загружается с атрибутом async , что позволяет ему загружаться и выполняться независимо от остальной части страницы. Используя асинхронную загрузку файлов JavaScript, вы можете свести к минимуму ресурсы, блокирующие рендеринг, и повысить производительность и удобство работы вашего веб-сайта.
Использование лучших практик для более быстрой загрузки и улучшения UX
Оптимизация веб-сайта — это непрерывный процесс, и для максимальной производительности важно идти в ногу с последними передовыми методами.
Разделение кода
- Webpack: Этот популярный сборщик предлагает встроенную поддержку разделения кода. Используя функцию динамического импорта import() , вы можете загружать модули JavaScript по запросу, сокращая время первоначальной загрузки.
- React.lazy: Если вы используете React, функция React.lazy позволяет загружать компоненты лениво по мере необходимости, что еще больше оптимизирует ваше приложение.
Пример кода JavaScript: реализация разделения кода
Ниже приведен пример разделения кода с помощью Webpack и React:
import React, < lazy, Suspense >from 'react'; // Ленивая загрузка компонента с React.lazy const MyComponent = lazy(() => import('./MyComponent')); function App() < return (>>Loading.

