Первая значимая краска в PWA на базе Ionic 3 - какие проблемы имеет этот подход? - PullRequest
0 голосов
/ 24 августа 2018

Итак, у меня есть PWA на базе Ionic 3, и довольно скоро я понял, что при 2G / 3G / неоптимальном соединении первая загрузка довольно плохая: ~ 10 секунд для первой значимой краски на основе теста аудита маяка PWA в Chrome Dev Tools.

Я использовал ленивую загрузку для всех страниц и все обычные оптимизации (сборка продукта и т. Д.) В соответствии с лучшими практиками и т. Д. И сузил его до 7 с,который все еще не идеален.

Чтобы опускаться ниже, я оценил рендеринг на стороне сервера, но по состоянию на ionic 3 он не доступен; (и мне пришлось экспериментировать с другими вещами.

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

ЗдесьВот как веб-приложение Ionic 3 загружает свои основные ресурсы javascript в index.html по умолчанию:

<body>

  <!-- Ionic's root component and where the app will load -->
  <ion-app></ion-app>

  <!-- The polyfills js is generated during the build process -->
  <script src="build/polyfills.js"></script>

  <!-- The vendor js is generated during the build process
       It contains all of the dependencies in node_modules -->
  <script src="build/vendor.js"></script>

  <!-- The main bundle js is generated during the build process -->
  <script src="build/main.js"></script>

</body> 

, а вот как загружается CSS:

<head>

  ...
  <link href="build/main.css" rel="stylesheet">

</head>

Ключевой момент, который я здесь заметил, былчто майn.js - это своего рода выражение для немедленного вызова функции, в момент его загрузки - его выполнение немедленно приводит к начальной загрузке Ionic App и т. д.

Таким образом, рассмотрение начальной (первой загрузки) страницы для нового пользователя всегдаОблегченная «вводная» страница (простая страница с логотипом, небольшим текстом и кнопкой подтверждения (в будущем это может стать лицензионным соглашением с конечным пользователем). Я решил создать такую ​​страницу внутри index.html, как статический html + стиль + некоторые jsи откладывать выполнение main.js до тех пор, пока пользователь не нажмет кнопку подтверждения, но в то же время я хотел, чтобы main.js и main.css загружались как можно скорее.Поэтому я придумал следующий код для index.html (см. Ниже).Основная идея:

  • функция обнаружения возможности предварительной загрузки тега ссылки
  • , если да - добавьте эти элементы в DOM и начните загружать одновременно main.js & css, а js-файлы поставщика и полифила должныбыть загруженным с флагом отсрочки и при успешной загрузке те индикатор выполнения увеличения (верхняя часть экрана).
  • если нет возможности предварительной загрузки - вещи загружаются, как обычно, с кнопкой подтверждения, открывающей доступ к приложению по-прежнему

Источник для индекса:

<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
  <meta charset="UTF-8">
  <title>Ionic App</title>
  <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <meta name="format-detection" content="telephone=no">
  <meta name="msapplication-tap-highlight" content="no">

  <link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
  <link rel="manifest" href="manifest.json">
  <meta name="theme-color" content="#4e8ef7">

  <!-- add to homescreen for ios -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">

  <!-- cordova.js required for cordova apps (remove if not needed) -->
  <script src="cordova.js"></script>

  <!-- un-comment this code to enable service worker
  <script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('service-worker.js')
        .then(() => console.log('service worker installed'))
        .catch(err => console.error('Error', err));
    }
  </script>-->
  <div id="linksContainer"></div>
</head>
<body>
  <!-- Ionic's root component and where the app will load -->
  <ion-app>
  </ion-app>
  <!-- preload magic here -->
  <div id="introScreenContainer">
    <progress id="loadingProgressBar" value="0" max="100"></progress>
    <p class="introTitle">Welcome to my App</p>
    <div id="introTextContainer">
      <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
    </div>
    <button id="swearButton" onclick="loadApp()">i solemnly swear that I am up to no good!</button>
    <style>
      html, body {
        margin: 0;
        padding: 0;
      }
      #loadingProgressBar {
        position: absolute;
        top: 0px;
        width: 100%;
        display: block;
      }
      #introTextContainer {
        display: flex;
        align-items: center;
        height: 100%;
        margin-top: 20%;
      }
      .introTitle {
        text-align: center;
      }
      #swearButton {
        position: absolute;
        bottom: 4px;
        background-color: #488aff;
        width: 100%;
        border: none;
        border-radius: 4px;
        color: white;
        padding: 12px 32px;
        text-align: center;
        font-size: 16px;
        visibility: hidden;
      }
    </style>
    <script>
      // cache dom elements:
      var linksContainer = document.getElementById("linksContainer");
      var introScreenContainer = document.getElementById('introScreenContainer');
      var loadingProgressBar = document.getElementById('loadingProgressBar');
      var swearButton = document.getElementById("swearButton");
      // loading counter counts scripts that has to be loaded before Ionic App can start:
      var loadingCounter = 0;
      // this gets called after each core script loads:
      function bumpCounter() {
        loadingCounter++;
        if (loadingCounter === 1) {
          loadingProgressBar.setAttribute("value", 25);
        }
        if (loadingCounter === 2) {
          loadingProgressBar.setAttribute("value", 60);
        }
        if (loadingCounter === 3) {
          loadingProgressBar.setAttribute("value", 85);
        }
        if (loadingCounter === 4) {
          loadingProgressBar.setAttribute("value", 100);
          swearButton.style.visibility = "visible";
          console.log("we can proceed to load");
        }
      }
      function loadApp() {
        var mainScript = document.createElement('script')
        mainScript.src = "build/main.js";
        document.body.appendChild(mainScript);
        introScreenContainer.remove();
        cssLink.rel = "stylesheet";
      }
      // feature detect preload capability with link tag:
      var supportsPreload = (function () { try { return document.createElement("link").relList.supports('preload'); } catch (e) { return false; } }());
      // if preload via link tag is supported we add tags and start downloading of the js and css resources:
      if (supportsPreload) {
        var mainLink = document.createElement('link');
        mainLink.rel = "preload";
        mainLink.href = "build/main.js";
        mainLink.as = "script";
        mainLink.onload = bumpCounter();
        linksContainer.appendChild(mainLink);
        var cssLink = document.createElement('link');
        cssLink.rel = "preload";
        cssLink.href = "build/main.css";
        cssLink.as = "style";
        cssLink.onload = bumpCounter();
        linksContainer.appendChild(cssLink);
      } else {
        // if not supported, then we just proceed as is:
        var cssLink = document.createElement('link');
        cssLink.rel = "stylesheet";
        cssLink.href = "build/main.css";
        cssLink.as = "style";
        cssLink.onload = bumpCounter();
        linksContainer.appendChild(cssLink);
        bumpCounter();
      }
    </script>
  </div>
  <!-- The polyfills js is generated during the build process -->
  <script defer src="build/polyfills.js" onload="bumpCounter()"></script>
  <script defer src="build/vendor.js" onload="bumpCounter()"></script>
  <!-- my own polyjouices / dependencies -->
  <noscript>Javascript needs to be enabled to use this app!</noscript>
</body>
</html>

Small Update:

  • , поэтому мне удалось набрать 100/100/100 в тесте Lighthouse с этим длядовольно большое приложение
  • Я сделал несколько изменений в коде, чтобы сделать его красивее.

enter image description here

...