Чтобы узнать, что такое Flipkart Lite, прочитайте нашу предыдущую статью об истории создания Progressive Web App: A New Way to Experience Mobile

Технология, лежащая в основе приложения

Ну с чего мне вообще начать? Ниже приведен список большинства технологий, лежащих в основе Flipkart Lite, НЕ в определенном порядке. Большое спасибо всем авторам и разработчикам этих инструментов, библиотек, фреймворков, спецификаций и т. Д.

Внешний интерфейс:

Инструменты - Сборка и обслуживание:

Веб-платформа:

И еще кое-что, что мне, вероятно, не хватает.

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

Монолит

Архитектурное решение началось с выбора между «Один большой монолит» и «Разделить приложение на несколько проектов». В наших предыдущих проектах у нас был один большой монолитный репозиторий, и многим разработчикам не понравился опыт получения кода для освоения. Проблема, когда все разработчики вносят свой вклад в один репозиторий, заключается в том, чтобы помочь им быстро запустить свой код в производство. Новые функции появляются каждый день, и развертывание продолжает замедляться. Каждая команда хочет как можно скорее довести свою функциональность до совершенства и эффективно внедрить ее в производство. Квинтэссенция требования к каждому человеку, отправляющему свой код в свою ветку, заключается в том, что он должен быть рассмотрен и объединен в течение следующего часа, перенесен в предварительную среду, подобную среде, в течение следующих 5 минут и, в конечном итоге, произведен в тот же день. . Но как с первого дня создать такое сложное приложение с нуля.

Мы начали с простой системы и добавили сложности

Отдельные проблемы

Цель заключалась в том, чтобы иметь следующие свойства DX (Developer Experience),

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

Поэтому мы планировали разделить наше приложение на разные проекты, где каждое приложение представляет поток продуктов, например: Home-Browse-Product или приложение предварительной оплаты, приложение Checkout, приложение Accounts и т. Д., Но при этом не жертвуя некоторыми общими вещами. что они могут поделиться друг с другом. Это было похоже на ограничение использования в технологии. Но все согласились использовать React, Phrontend и webpack :). Так стало легко добавлять простые сценарии в каждый проект и автоматизировать циклы сборки и развертывания.

Из этих различных приложений одно из них является основным - приложение Home-Browse-Product. Поскольку пользователь входит в Flipkart Lite через одну из страниц в этом приложении, назовем его «основным» приложением. В этой статье я буду говорить только об основном приложении, так как другие приложения используют подмножество инструментов и настроек так же, как и в «основном» приложении.

Главная-Обзор-Продукт

«Основное» приложение обслуживает 5 разных сущностей. Поскольку мы используем webpack, каждая из сущностей просто стала конфигурацией webpack.

  • vendors.config.js: реагировать, реагировать-маршрутизатор, phrontend и т. д.
  • client.config.js: client.js → routes.js → корневой компонент
  • server.config.js: server.js → routes.js → корневой компонент
  • hbs.config.js: hbs-loader → пакет hbs → ‹shell› .hbs
  • sw.config.js: sw.js + сборка реквизитов от клиента и поставщика.

vendors.config.js

Это создает связку из React, response-router, phrontend и еще нескольких утилит.

{
  entry: {
    react: "react",
    "react-dom": "react-dom",
    "react-router": "react-router",
    phrontend: "phrontend",
    ...
  },
  output: {
    library: "[name]",
    libraryTarget: "this",
    ...
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({ name: "react" }),
    new CombineChunksPlugin({ prelude: "react" })
  ]
}

Это примерно конфигурация для поставщиков js. Мы пробовали разные подходы, и у нас это сработало. Итак, концепция состоит в том, чтобы

  • указать разные точки входа
  • используйте CommonsChunkPlugin, чтобы переместить среду выполнения в этот кусок
  • используйте CombineChunksPlugin (я загрузил исходный код здесь), чтобы объединить все фрагменты в один файл с фрагментом времени выполнения вверху

В результате мы получаем один-единственный файл - vendors. ‹Hash› .js,, который затем синхронизируем с нашими серверами CDN.

Список поставщиков передается по конвейеру сборки и другим приложениям, и каждое из них (включая основное) выводит поставщиков на внешний рынок, чтобы использовать их из vendors.bundle.js,

{
  externals: vendors
}

client.config.js

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

server.config.js

Зачем нам вообще нужен server.config.js? Зачем нам что-то связывать с сервером?

Мы активно используем webpack и полагаемся на некоторые соглашения - мы добавляем собственные пути к разрешению модуля, импортируем файлы css, используем код ES6, и node не может понять большую часть этого изначально. Кроме того, мы не хотим связывать каждую зависимость в один файл и запускать его с помощью node. Webpack предоставляет способ экстернализации этих зависимостей, оставляя требования для этих внешних факторов нетронутыми, и это один из способов сделать это,

var nodeModules = {};
fs.readdirSync(‘node_modules’).filter(function(m) {
  return [‘.bin’].indexOf(m) === -1;
}).forEach(function(m) {
  nodeModules[m] = ‘commonjs2 ‘ + m;
});

и в конфигурации webpack,

{
  output: {
    libraryTarget: "commonjs2",
    ...
  },
  externals: nodeModules,
  target: "node"
}

sw.config.js

Sw.config.js объединяет sw-toolbox, наш код сервис-воркера и версии сборки из vendors.config.js и client.config.js. Когда выпускается новая версия приложения, поскольку мы используем [хеши] в именах файлов, URL-адрес ресурса изменяется, и он отображается как новый sw.bundle.js. Поскольку теперь у нас есть байтовое различие в sw.bundle.js, новый сервисный работник подключается к клиенту и обновляет приложение, и это будет работать со следующего обновления.

hbs.config.js

Перед отправкой разметки пользователю мы используем два уровня шаблонов. Первый отображается во время сборки hbshbs. Второй отображается во время выполнения hbshtml. Прежде чем продолжить, я хотел бы поговорить о оболочках HTML-страниц и о том, как мы создали их с помощью react и response-router.

Оболочки HTML-страниц

Подробные сведения о том, какие оболочки HTML-страниц или оболочки приложений приведены здесь - https://developers.google.com/web/updates/2015/11/app-shell. Вот как выглядит одна из наших оболочек страницы -

На левом изображении показано состояние «до данных» - оболочка, а на правом - состояние «после данных» - оболочка + содержимое. Одна из основных вещей, которые это помогает нам в достижении, - это:

Воспринимаемый ›Фактический

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

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

Попался? может быть.

Мы кое-что пробовали, и я поделюсь тем, что сработало для нас.

componentDidMount

Это крючок жизненного цикла, предоставляемый React, который работает ТОЛЬКО на клиенте. Таким образом, на сервере вызывается метод рендеринга для компонента, но не вызывается componentDidMount. Итак, мы помещаем в него все наши API, вызывающие действия Flux, и тщательно конструируем наши методы рендеринга для всех наших компонентов верхнего уровня, чтобы после рендеринга он выдавал оболочку вместо того, чтобы бросать пустой контейнер.

Параметризованные маршруты

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

/:slug/p/:itemid
/(.*)/pr
/search
/accounts/(.*)

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

Hackity Hack Взломанная версия

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

function convertParams(p) {
  return PathUtils.injectParams(p, {
    splat: 'splat',
    slug: 'slug',
    itemId: 'itemId'
  });
}

Примечание. Мы использовали response-router 0.13. В более поздних версиях API могли быть изменены. Но что-то похожее будет использовать response-router.

И теперь мы получаем эту таблицу маршрутов.

Route Defined     Route To RenderPageShell
/:slug/p/:itemid  → /slug/p/itemId     → product
/(.*)/pr          → /splat/pr          → browse
/search           → /search            → search
/accounts/(.*)    → /accounts/splat    → accounts

Это просто работает, потому что оболочка, которую мы генерируем, НЕ содержит никакого содержимого. То же самое для всех похожих страниц (например, страницы продукта).

Двухуровневое создание шаблонов

Мы вернулись к hbs.config.js. Итак, у нас есть один-единственный файл hbs - index.js со следующим содержанием.

// this is where the build time renderedApp shell goes in
// React.renderToString output
<div id="root">{{content}}</div>
// and some stuff for second level 
// notice the escape <script nonce="\{{nonce}}"> <script src="\{{vendorjs}}"></script> <script src="\{{appjs}}"></script>

Отрисовка во время сборки: для каждого типа маршрута мы генерируем оболочку с $ content, вставленным в index.hbs и получите hbs для этого конкретного маршрута, например - product.hbs

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

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

Конец

И со всем этим и множеством ошибок, которые я упустил, мы успешно запустили Flipkart Lite в производство. Ой, погоди! Это началось не как рассказ. Так или иначе. Спасибо, что дочитали до этого места.

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

- Boopati Rajaa, команда Flipkart Web