TL; DR; Мы внесли некоторые улучшения в процесс сборки и развертывания нашего веб-приложения, используя некоторые новые функции Webpack 3 и сосредоточившись на создании согласованных и долгосрочных кэшируемых сборок.

Веб-клиент Туро - это приложение на основе React / Redux, которое мы переносим из нашего устаревшего веб-приложения, написанного на JSP + jQuery. Наше приложение React отображается в минимальном контейнере внутри устаревшего приложения, что упрощает решение некоторых проблем при миграции, таких как аутентификация. Наша первая страница React была развернута весной 2016 года и имела простой процесс сборки-развертывания.

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

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

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

Старый процесс сборки и развертывания

Сборка React

База кода webapp в основном состоит из четырех частей:

  • JS файлы, содержащие логику приложения.
  • CSS файлы.
  • JSON файлы с переводами.
  • Изображения (файлы PNG, JPG и SVG).

Все эти файлы собираются, компилируются и минимизируются с помощью Webpack, который де-факто является одним из сборщиков модулей для Интернета.

Этот процесс компиляции выполняется Team City каждый раз, когда мы развертываем веб-приложение.

В процессе сборки создаются комплект и папка ssets.

Папка bundle будет содержать файл CSS и набор файлов JS:

  • Файл common.css, содержащий все правила стилей для веб-приложения. Он также содержит представление в базе 64 определенных изображений, которые слишком малы для того, чтобы их можно было использовать в качестве отдельного файла (поскольку для этого потребуется дополнительный HTTP-вызов всего на несколько байтов). Этот файл не сворачивается.
  • Файл chunk. ‹Id› .js, содержащий код маршрута в приложении. Мы настроили Webpack для создания разных файлов для каждого маршрута. Таким образом, мы сохраняем баланс между количеством файлов, которые пользователь должен загрузить при первой загрузке для запуска приложения, и количеством неиспользуемого кода в этих файлах. Мы рассматриваем каждый маршрут как независимое и изолированное микро-веб-приложение.
  • Файл common.js, который является точкой входа в приложение. Он содержит набор общих библиотек, таких как react или redux, и часть кода, который используется разными маршрутами.

Файлы перевода в конечном итоге встроены в код JS маршрута, к которому они принадлежат, или в общий файл, если они являются общими языковыми стандартами.

Папка assets содержит все изображения, которые приложение собирается использовать (те, которые не встроены в файл CSS). Эти изображения оптимизированы для производства и в большинстве случаев объединяются в спрайты для уменьшения количества HTTP-вызовов. Имя этих файлов будет содержать хэш их содержимого, позволяя браузерам кэшировать их, и если создается новая версия изображения, ее хеш будет изменяться.

Как только сборка будет создана, она будет загружена в корзину S3. В зависимости от среды (производственные или тестовые серверы) будет использоваться другой сегмент. Структура этого ведра следующая:

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

Для каждой сборки будет одна папка, которая будет содержать файлы JS и CSS, упомянутые ранее. Имя этой папки содержит идентификатор сборки из Team City и хэш фиксации, который представляет эта сборка.

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

Эти корзины S3 содержат код каждой сборки, созданной team city с момента первого развертывания приложения React. В производственной корзине используются только текущие развернутые сборки и сборка, созданная в процессе развертывания перед ее повышением.

Вот как выглядит сборка:

Крупный план общих модулей, модулей проверки и листинга, где мы можем увидеть повторение некоторых узловых модулей и некоторых компонентов пользовательского интерфейса:

Размер сборки:

Если мы посмотрим на эти изображения, мы можем заметить несколько вещей:

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

Загрузка из нашего устаревшего приложения

Теперь у нас есть новая сборка в S3. Но это мало что делает. На сегодняшний день приложение React загружается из нашего внутреннего Java-приложения на странице JSP. Это означает, что в устаревшем приложении должен быть какой-то код, который знает, где найти последнюю версию веб-приложения.

Для этого есть еще один актив, который процесс сборки Team City загрузит в S3. После продвижения последней сборки файл version.json будет добавлен в корень корзины S3. Этот файл содержит имя папки, связанной с последней действующей сборкой React.

Всякий раз, когда загружается страница React, первое, что делает JS-код в JSP-контейнере, - это загружает этот JSON-файл, чтобы создать правильный URL-адрес для загрузки остальных файлов webapp, то есть common.js и common.css. Загрузка фрагментов выполняется внутри Webpack, поэтому нам не нужно беспокоиться об этом вручную.

Проблемы с развертыванием

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

Несмотря на все это, у этого процесса развертывания есть некоторые проблемы, которые становятся более важными теперь, когда у нас есть несколько общедоступных страниц в React. Это важно, потому что, как вы увидите, большинство проблем связано с долгосрочным кешированием:

  • Последняя сборка React всегда будет указывать на другой URL-адрес после каждого развертывания, что делает невозможным кэширование каких-либо JS-файлов браузерами.
  • Имена файлов для файлов фрагментов не имеют согласованного наименования и просто используют автоматически сгенерированный идентификатор. Если создается новый фрагмент, ничто не гарантирует, что chunk.2.js, представляющий страницу оформления заказа, по-прежнему будет фрагментом 2 в следующей сборке.
  • Имена файлов всегда одинаковы. Мы не используем хеши в именах файлов, что означает, что два исходных файла с одинаковым именем и разным содержимым будут давать одно и то же имя выходного файла, что снова делает невозможным кэширование этих данных браузерами.
  • common.js содержит как сторонние библиотеки, так и часть нашего исходного кода. Маловероятно, что сторонние библиотеки со временем изменятся, за исключением обновлений версий. Даже в этом случае мы заставляем каждого пользователя загружать тяжелые библиотеки, такие как react или moment, каждый раз, когда мы развертываем, даже если эти библиотеки вообще не менялись.
  • Сборки несовместимы. Одно изменение в файле локали приведет к изменению всех файлов JS в сборке (подробнее об этом в следующих разделах).
  • Приложение React и унаследованные приложения тесно связаны. Сценарий, который заботится о загрузке веб-приложения, имеет имена файлов, которые необходимо загрузить, жестко запрограммированные, что означает, что если мы хотим изменить сборку React, мы должны внести изменения в страницы JSP.
  • То, как мы выполняем разделение кода, не позволяет проверить определение маршрута. Этот фрагмент кода использует проприетарный API Webpack, чтобы указать, когда создавать новый фрагмент. Наши модульные тесты не используют Webpack (и он должен оставаться таким), и поэтому мы не можем протестировать эту часть приложения (у нас были некоторые ошибки в этих файлах, и мы смогли отловить их только в производственной среде).
  • Несмотря на то, что процесс отката был бы довольно простым (просто обновите файл version.json, чтобы он указывал на предыдущую сборку), у нас его еще нет.

Новый процесс сборки и развертывания

Из-за всех вышеупомянутых проблем пришло время переосмыслить процесс сборки и развертывания веб-приложений React.

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

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

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

До версии 3 Webpack это можно было сделать только через собственный API Webpack, основанный на обратных вызовах (require.ensure). Однако с последней версией библиотеки они переключились на предложение динамического импорта ECMAScript.

Этот новый способ указания динамического импорта стандартизирован и больше не является проприетарным API, но его поведение немного изменилось, поэтому мы решили не обновлять новый синтаксис при обновлении до Webpack 3.

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

Вот как будет выглядеть образец кода маршрута с использованием предыдущей версии в нашем проекте React:

export default store => ({  
  load(cb) {
    require.ensure(
      [],
      require => {
        const {checkout, paymentMethodPlugin} = require('./CheckoutReducer');
        const {CHECKOUT_PAYMENT_FORM} = require('./CheckoutForms');
        injectAsyncReducer(store, 'checkout', checkout);
        injectAsyncFormPluginReducer(store, CHECKOUT_PAYMENT_FORM.NAME, paymentMethodPlugin);
        const CheckoutView = require('./CheckoutView').default;
        cb(CheckoutView);
      },
      'checkout'
    );
  },
  path: '/checkout',
  private: true,
  routes: [onboardingRoute(store)],
});

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

Внутри этого обратного вызова мы также вынуждены использовать require вместо import, как мы загружаем модули в остальной части нашей базы кода.

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

При использовании динамического импорта мы можем просто указать одну зависимость, поэтому мы перемещаем весь код обратного вызова в файл load.js:

import {injectAsyncReducer, injectAsyncFormPluginReducer} from '../store';  
import {checkout, paymentMethodPlugin} from './CheckoutReducer';  
import {CHECKOUT_PAYMENT_FORM} from './CheckoutForms';  
import CheckoutView from './CheckoutView';

export default function load(store) {  
  injectAsyncReducer(store, 'checkout', checkout);
  injectAsyncFormPluginReducer(store, CHECKOUT_PAYMENT_FORM.NAME, paymentMethodPlugin);
  return CheckoutView;
}

Код очень похож, но теперь у нас есть то преимущество, что его можно протестировать.

Теперь файл маршрута становится очень простым:

import onboardingRoute from './onboarding';

export default {  
  loader: () => import(/* webpackChunkName: "checkout" */ './load'),
  path: '/checkout',
  private: true,
  routes: [onboardingRoute],
};

Функционал остался прежним, но теперь у нас есть несколько очень важных преимуществ:

  • Мы можем протестировать код инициализации маршрута.
  • Мы можем вызвать import (‘/ checkout / load.js’) из любой другой части приложения, что дает нам возможность интеллектуально упреждающе выбирать контент, сокращая время загрузки для конечных пользователей.

Анализ дерева зависимостей Webpack

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

Webpack можно настроить для создания файла JSON с метаданными о процессе сборки. Есть несколько инструментов визуализации, которые могут помочь в навигации и понимании этого файла (webpack-bundle-analyzer - один из моих любимых).

Вот так выглядела наша предыдущая сборка:

Мы можем наблюдать несколько вещей:

  • Файл common.js, который является точкой входа в наше приложение, ОГРОМНЫЙ, и большинство вещей, которые он содержит, являются сторонними библиотеками, которые не сильно меняются.
  • Компоненты внутри src / ui распределены по всем блокам.
  • Несмотря на то, что у нас есть разделение кода, есть некоторый код доступности, который добавляется в файл common.js.

Определенно есть возможности для улучшения. Итак, приступим!

Помещение сборки в одну папку

Что любопытно в нашем предыдущем процессе развертывания, так это то, что ресурсы каждой сборки хранятся только в одном месте, а код JS каждый раз будет находиться в другой папке. Было бы лучше, если бы каждая сборка генерировала папку со всем необходимым ей содержимым. Если ресурсы не меняются, их имя и хеш также не изменятся, и пока вы гарантируете, что последняя версия вашего кода находится под тем же URL-адресом (например, s3-bucket / response / latest ), то на кеширование ресурсов это не повлияет.

Когда мы компилируем веб-приложение, создаются следующие папки:

  • dist, корневая папка сборки.
  • dist / assets, содержит ресурсы сборки.
  • dist / bundle, содержит файлы сборки JS.

Вместо этого мы могли бы переместить те же файлы JS, добавив только в dist, и мы могли бы просто скопировать эту папку в S3 в одно место, вместо того, чтобы копировать две папки в два разных места.

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

name: `${isPROD ? '../' : ''}assets/[hash].[ext]`,

Кроме того, имя, которое мы даем точке входа в приложение, кажется не совсем правильным. Это должно быть что-то вроде main.js или index.js. Кажется, что файл common.js указывает на какую-то библиотеку, а не на точку входа в приложение.

Я верю в принцип наименьшего удивления и что вещи должны называться в честь того, что они на самом деле делают. Чтение common.js может сбивать с толку.

Фрагмент поставщика

Как мы видели ранее, точка входа в приложение не просто содержала точку входа и магистраль, необходимую для запуска нашего веб-приложения. Также он содержал множество сторонних библиотек. Бывает, что эти библиотеки используются во всем приложении, и бывает, что эти библиотеки почти никогда не меняются. Имеет смысл объединить все эти библиотеки (за исключением некоторых библиотек, таких как D3 или Google Maps, которые используются в очень определенных точках приложения) в другой и кэшируемый файл. Так рождается vendor.js.

Извлечение пакета пользовательского интерфейса

Еще одна вещь, которую мы обнаружили после анализа сборки, - это то, что компоненты внутри src / ui распределены по всем блокам. Эта папка содержит нашу библиотеку React UI, то есть набор компонентов, поверх которых мы создаем приложение. На практике его можно рассматривать как внешнюю библиотеку, и именно это я и сделал: сказал Webpack поместить все файлы в эту папку в файл ui.js в окончательной сборке.

Назови все вещи

На данный момент у нас есть:

  • main.js, файл с минимумом для запуска веб-приложения.
  • vendor.js, файл с наиболее важными сторонними библиотеками.
  • ui.js, файл с нашей библиотекой компонентов.
  • chunk.id.js, файл для каждого разбиения кода, которое мы делаем.

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

Однако есть еще одна проблема с именем, которое мы даем каждому чанку. Они непоследовательны. Webpack присваивает им идентификатор, который мы не можем контролировать и который может меняться от одной сборки к другой, и это совсем нехорошо. Мы хотим, чтобы все было согласовано. Представьте, что страница оформления заказа получает идентификатор 3 в нашей сборке, но теперь мы добавляем новую точку разделения кода (также называемую новым маршрутом страницы), и внезапно проверка получает идентификатор 4. Хотя мы не изменили код оформления заказа на все, теперь, после развертывания новой версии, пользователь, у которого был кэшированный код оформления заказа, не может его использовать, потому что теперь у него другой идентификатор.

Мы должны использовать имена вместо идентификаторов. Похоже, что единственное преимущество идентификаторов в том, что мы "запутываем" нашу файловую структуру, но на самом деле показываем chunk.4.js вместо chunk.checkout. js и пожертвовать долгосрочным кешированием кажется действительно большим компромиссом. Более того, этот фрагмент будет загружен, когда пользователь перейдет по URL-адресу / checkout, поэтому мы не раскрываем ничего нового и получаем огромный выигрыш с точки зрения эффективности.

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

Кроме того, похоже, что у параметра recordsPath также есть некоторые известные проблемы; (Здесь и здесь).

Webpack проявляет

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

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

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

Есть три вещи, которые мы должны убрать из нашей сборки, чтобы иметь настоящее долгосрочное кеширование:

  • Манифест Webpack. Это очень крошечный фрагмент кода, который запускает Webpack и зависит от каждой сборки. Он должен быть независимым от нашего поставщика и нашей точки входа (которая не сильно меняет)
new webpack.optimize.CommonsChunkPlugin({  
  name: 'manifest',
  minChunks: Infinity,
}),

Этот файл необходимо загрузить до main.js, чтобы все работало нормально.

  • Поскольку мы используем разделение кода, это означает, что внутри нашей сборки у нас будет некоторый код, который ссылается на имена файла, который будет загружаться при каждом посещении нового маршрута. До сих пор имена файлов фрагментов были согласованы, однако теперь у них есть хеши, что означает, что если мы внесем изменения в фрагмент, наш файл main.js тоже изменится. Чтобы исправить это, мы создаем файл JSON с сопоставлением имени файла и его хешированного имени. Таким образом, main.js всегда ссылается на одни и те же имена и использует этот манифест фрагмента для ссылки на настоящий файл. Поэтому перед загрузкой любого файла JS мы должны загрузить этот манифест в глобальную переменную.

Это webpack-chunk-manifest.json

{
  "vendor": "chunk.vendor.ddd5c4c8c6199efc58ab.js",
  "main": "chunk.main.61ff6dfece4ed049bb3b.js",
  "checkout": "chunk.checkout.9bbb45b23d1ef386cf88.js",
  "dashboard": "chunk.dashboard.4072144f4c31886f1f5c.js",
  "earnings": "chunk.earnings.f6d305555a1d252f1a83.js",
  "error": "chunk.error.8a045c2dfef43e0b7548.js",
  "host": "chunk.host.41ce2b2537f03ab133e4.js",
  ...
  "yourCar": "chunk.yourCar.8c7fe298f8289871ba3e.js",
  "yourCarDetails": "chunk.yourCarDetails.2329c8d4930cfe55e21f.js"
}

Это инициализация глобальной переменной:

window.webpackManifest = webpackManifest;
  • В нашем предыдущем процессе сборки с нехешированными файлами ссылка на точку входа React со страницы JSP была довольно простой. Мы просто указываем на common.js и common.css, и он работает каждый раз. Этот процесс во многом связывает React и унаследованное приложение: мы не можем изменить структуру приложения React, не изменив страницу JSP. Теперь, когда нам нужно загрузить несколько файлов JS для запуска React и учитывая, что имена этих файлов будут меняться с каждой сборкой (хеши), текущий подход больше не работает. Решением этой проблемы является создание манифеста актива для каждой сборки, в котором указывается имя и порядок каждого файла, который необходимо загрузить, чтобы веб-приложение могло работать. Таким образом, унаследованное приложение менее связано с React, поскольку оно зависит только от файла манифеста и его структуры, но, поскольку этот файл создается из веб-приложения, мы можем изменить его так, как мы хотим.

Вот как выглядит webpack-asset-manifest.json:

{
  "js": [
    "manifest.e0d6aa4d32e9a707a2ba.js",
    "vendor.ddd5c4c8c6199efc58ab.js",
    "main.61ff6dfece4ed049bb3b.js"
  ],
  "css": [
    "styles.2584da067bb2877cbc3030511e75ab8a.css"
  ]
}

Теперь у нас есть все необходимое для долгосрочного кэширования.

Минимизация CSS

Во время этого процесса я понял, что файл webapp common.css не сворачивается, поэтому добавление строки кода в наш файл конфигурации сэкономило нам пару КБ:

if (isProd) {  
  plugins.push(
    new OptimizeCssAssetsPlugin({
      cssProcessorOptions: {discardComments: {removeAll: true}},
    })
  );
}

Загрузка из устаревшего приложения

Теперь у нас больше нет файлов common.js и common.css, на которые мы всегда можем ссылаться со страницы JSP. Вместо этого у нас есть два файла манифеста, которые содержат всю информацию, необходимую для загрузки React. Нам пришлось обновить код JSP, который загружает веб-приложение, чтобы оно считывало эти два файла и загружало оттуда все зависимости:

function load(base) {  
  window.webpackPublicPath = base;
  loadJsonFile(base, 'webpack-chunk-manifest.json', function(webpackManifest) {
    loadJsonFile(base, 'webpack-asset-manifest.json', function(webpackAssets) {
      window.webpackManifest = webpackManifest;
      webpackAssets.css.forEach(loadStyleSheet(base));
      loadJSAssets(base, webpackAssets.js);
    });
  });
}

load(getClientBase(VERSION, CLIENT_URL));

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

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

Новый процесс развертывания

Все изменения, описанные в этом посте, несовместимы с предыдущим процессом развертывания в Team City, и, как я упоминал ранее, он не позволяет нам иметь долгосрочное кеширование, поскольку каждая новая сборка указывает на другой URL-адрес (кроме для активов).

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

Из-за этих двух фактов мы также обновили наш процесс развертывания.

Наш предыдущий процесс развертывания состоял из двух рабочих мест в Team City:

  • Сборка и развертывание. Это задание будет проверять последнюю версию веб-приложения из Github, компилировать ее и загружать в папку S3 bucket (имя этой папки - это идентификатор сборки + хеш фиксации). Это также поместит изображения в общую папку assets в корзине. Будет создан файл version.json с именем этой папки. Этот файл отображается как артефакт Team City.
  • Обновите версию. Это задание загрузит файл version.json в корень корзины S3. Этот файл сообщает устаревшему приложению, где находится последняя сборка веб-приложения.

Вакансии Team City в новом процессе развертывания выглядят так:

  • Сборка и развертывание. Эта работа очень похожа на старую, с той разницей, что она загружает всю сборку в ту же папку в корзине S3 (нет различия между изображениями и кодом JS). Сборки будут сохранены в папке / builds. В то же время эта сборка загрузит файлы манифеста Webpack в папку / manifest / next. Он также загрузит в эту папку файл version.json, чтобы мы знали, что представляет собой этот код git commit и Team City build.
  • Продвигать. Это задание заменяет задание Обновить версию. Файлы для каждой сборки React всегда будут в папке / builds корзины S3. Поскольку каждый файл имеет уникальное имя (хеш), конфликтов не будет. Это задание берет содержимое папки manifest / current и помещает его в папку manifest / previous. После этого содержимое папки manifest / next будет помещено в папку manifest / current.
  • Возвращаться. Если после продвижения что-то не работает корректно, мы всегда можем вернуться к ранее развернутой версии. Это задание берет содержимое папки manifest / previous и помещает его в папку manifest / current. Папка manifest / current - это то место, где устаревшее приложение будет пытаться загрузить манифесты веб-приложений по умолчанию.

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

Заключение

В этом сообщении блога описан мыслительный процесс, лежащий в основе реализации нового процесса сборки и развертывания веб-приложений:

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

Вот так выглядит финальная сборка:

И это крупный план, на котором мы видим, что между оформлением заказа и потоком листинга меньше повторений, поскольку теперь у нас есть поставщик и модуль пользовательского интерфейса:

Размер сборки практически не изменился, но размер модулей перераспределился:

Ресурсы

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