Пусть всем кажется,
что всё грузится быстро

Виктор Русакович, GP Solutions,

21 июня 2019г
  1. Куда?
  2. Сколько?
  3. Когда?
  4. Как?
  5. А подешевле?
  6. Почём?

2004

2008

  1. Куда?
  2. Сколько?
  3. Когда?
  4. Как?
  5. А подешевле?
  6. Почём?
  7. Дорого! Скидка только для Выходных? 

2012

  1. <html>...</html>
  2. <script>...</script>
  3. <style>...</style>
  4. fetch('api/openSession')
  5. fetch('api/timetable')
  6. fetch('api/cabins')
  7. fetch('api/price')

Все компоненты готовы?

  • Да - убираем спиннер
  • Нет - показываем спиннер

Можно лучше! 

  • Компоненту хватает данных для отрисовки?
    • Показываем его
  • Компонентом можно пользоваться?
    • Разблокируем его

Секрет №1

  • 👎  Блокировать весь интерфейс
  • 👌   Блокировать только недоступные компоненты

Пабло Пикассо, 8 лет,

"Жёлтый пикадор",

первая отрисовка

Команда разработчиков,

~25 лет

"Покупка билетов",

тоже первая отрисовка

Секрет №2

  • 👌   Если вы примерно знаете, что будет показано - используйте skeleton

Типы skeleton

SVG skeleton

SVG skeleton

SVG skeleton

SVG skeleton

Используем SVG

<img src="image.svg" />

body.isLoading { 
  background-image: url(image.svg); 
}

169ms ждали,

1.24ms скачивали

Секрет №3

  • 😍 Грузите скелетоны вместе с HTML
  • Загрузка скелетонов как отдельных ресурсов уменьшает их пользу

Не тратимся на запросы

  • GIF/SVG - base64
  • SVG - inline
  • HTML/CSS - уже хорошо

Не тратимся на запросы

<img src="data:image/gif;base64,
R0lGODlhFwAYAAgAACH/C05FVFNDQVBFMi4wAwEAAAAh+
QQEAwAAACwAAAAAFwAYAKAAAAAAAAAIKAABCBxIsKDBgw
gTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyAxBgQAIfk
EABQAAAAsAAAAABcAGACg/9oAAAAACCgAAQgcSLCgwYMI
EypcyLChw4cQI0qcSLGixYsYM2rcyLGjx48gMQYEADs="/>

260 байт / 170 байт  = 130%

Не тратимся на запросы

<body>
<svg width="380" height="165" xmlns="http://www.w3.org/2000/svg">
 <g>
  <rect fill="#fff" stroke-width="1.5" x="14" y="14" width="124" height="65" stroke="#000"/>
  <rect fill="#fff" stroke-width="1.5" x="160" y="14" width="200" height="66" stroke="#000"/>
  <rect fill="#fff" stroke-width="1.5" x="10" y="96" width="350" height="62" stroke="#000"/>
 </g>
</svg>
</body>

Не тратимся на запросы

// WEBPACK config
  module: {
    rules: [
      { 
        test: /\.jpe?g$|\.gif$|\.png$/, 
        loader: 'url-loader', 
        options: { limit: 30000, name: '[path][name].[ext]' } 
      },

Не тратимся на запросы

Не тратимся на запросы

Не тратимся на запросы

<body>
  <style>
    .box-small { width: 100px; height: 100px; border: 1px solid black; }
  </style>
  <div class="box-small"></div>
  <div class="box-medium"></div>
  <div class="box-large"></div>
</body>

2017

Поддержка скелетонов

Network - throttling

Network - throttling - Add custom profile

Переключаем скелетон через код

Переключаем скелетон через код

Секрет №4

  • 🙏Не игнорируйте устаревшие скелетоны
  • Настройте окружение для поддержки скелетонов

2018

Разработчик

Пользователи

Избавляемся от fetch('openSession')

Избавляемся от

fetch('timetable') и 

fetch('cabins')

Избавляемся от fetch()

// index.html
var preload = {
  session: 'fasdfas-32432-fdsf',
  timetable: [
    { date: '2019-09-01', price: 77 },
    { date: '2019-09-02', price: 12 }
  ],
  cabins: { 
    '2019-09-01': { 'A': 77, 'B': 99 }
  ]
}

Избавляемся от fetch()

// rest.js
function getTimetable(date) {
  if (window.preload.timetable) {
    return Promise.resolve(window.preload.timetable)
  }

  return axios.get('/api/timetable?date=' + date)
}

Секрет №5

  • 🙈 Предскажите обязательные запросы и внедрите их в index.html

Собственная статистика от Google

var t0 = performance.now()
await verySlowAppCode()
var t1 = performance.now()
ga('send', 'timing', 'App loaded', 'load', t1 - t0);

Собственная статистика от Google

Собственная статистика от себя

  1. Собираем данные
  2. Ждём момента
  3. Отправляем

Собственная статистика от себя

      var currentTimestamp = new Date().getTime()
      var track = {
        browserVersion:            $.browser.version,
        session:                   Env.sessionGUID,
        version:                   Env.Version,
        mobileDevice:              isMobileDevice(),
        withVoucher:               !!options.voucherId
      }

      if ('performance' in window && 'timing' in window.performance) {
        var timing = window.performance.timing
        var networkLatency = timing.requestStart - timing.navigationStart
        var appLatency = currentTimestamp - timing.requestStart

        track.networkLatency = networkLatency
        track.appLoadTimeByBrowser = appLatency
      }

      sendTrack('api/stat/iki3', track)

Собственная статистика от себя

Pubsub.waitAll('component:timetable:ready', 'component:cabin:ready')
      .then(signalApplicationReady)
      .then(analytics.trackReadinessTimeToBackEnd(options))

Собственная статистика от   

select 
   browser_name, mean(app_load_time) 
   from "page_load_time_iki3" 
   where $timeFilter 
   and app_load_time < 20000 
   group by time($interval), browser_name 
order asc

Собственная статистика от себя

Все секреты (вместо вывода)

  • меньше запросов - лучше 
  • не давайте пользователю скучать - используйте скелетоны
  • собирайте статистику

Спасибо!

Виктор Русакович,

компания GP Solutions, Минск

Слайды - http://bit.ly/devconf-secret

Bonus

//booking page
document.getElementsByTagName('head')[0]
  .appendChild(getPrefetchLink('generated/booking.js', 'script'))

function getPrefetchLink(href, as) {
  var link = document.createElement('link')
  link.rel = 'prefetch'
  link.as = as
  link.href = 'https://booking.tallink.com/' + href + '?v=3.84.1'

  return link
}

<link rel="preload" href="generated/booking.js" as="script"/>