Привет всем

У меня отношения на расстоянии, а это значит, что каждые несколько недель я летаю в Англию. Каждый раз, когда я нахожусь в этом самолете, я думаю о том, как было бы хорошо прочитать какие-нибудь посты на Reddit. Что я мог сделать, так это найти приложение Reddit, которое позволяет кэшировать сообщения для офлайн (я уверен, что оно есть), или я мог бы воспользоваться возможностью написать что-нибудь сам и использовать некоторые из последних и лучшие технологии и веб-стандарты, и повеселитесь!

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

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

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

Итак, давайте сразу перейдем к установке нескольких зависимостей:

npm i @babel/core babel-loader @babel/preset-env @babel/preset-react webpack webpack-cli react react-dom redux react-redux html-webpack-plugin are-you-tired-yet html-loader webpack-dev-server

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

Мы будем использовать:

  • LitElement
    В этом проекте мы будем использовать LitElement в качестве нашей компонентной модели. Он прост в использовании, легкий, почти металлический и использует веб-компоненты.
  • @ vaadin / router
    Маршрутизатор Vaadin - это действительно небольшой (‹7kb) маршрутизатор, у которого есть * потрясающий * опыт разработки, и я не могу рекомендовать его достаточно.
  • @ pika / web
    Пика поможет нам собрать наши модули вместе для упрощения разработки.
  • es-dev-server
    Простой dev-сервер для современных рабочих процессов веб-разработки, сделанный нами в open-wc. Хотя подойдет любой http-сервер; не стесняйтесь приносить свои собственные.

Вот и все. Мы также будем использовать несколько стандартов браузеров, а именно: es-модули, веб-компоненты, import-maps, kv-storage и service-worker.

Итак, давайте продолжим и установим наши зависимости:

npm i -S lit-element @vaadin/router
npm i -D @pika/web es-dev-server

Мы также добавим в наш файл package.json ловушку postinstall, которая будет запускать для нас Pika:

“scripts”: {
    “start”: “es-dev-server”,
    “postinstall”: “pika-web”
}

🐭 Пика

Pika - это проект Фреда К. Шотта, цель которого - привнести ностальгическую простоту 2014 года в веб-разработку 2019 года. Фред придумывает множество замечательных вещей, например, он создал pika.dev, который позволяет вам легко искать современные пакеты JavaScript в npm. Он также недавно выступил с докладом Переосмысление реестра на DinosaurJS 2019, который я настоятельно рекомендую вам посмотреть.

@ Pika / web делает еще один шаг вперед. Если мы запустим `pika-web`, он установит наши зависимости как отдельные файлы javascript в новый каталог` web_modules / `. Если ваша зависимость экспортирует точку входа ES module в своем манифесте `package.json`, Pika поддерживает ее. Если у вас есть какие-либо транзитивные зависимости, Pika создаст отдельные фрагменты для любого общего кода между вашими зависимостями. Легкий лимонный сок.

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

└─ web_modules/
    ├─ lit-element.js
    └─ @vaadin
    └─ router.js

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

📥 Импортировать карты

Хорошо! Теперь мы разобрались с зависимостями, приступим к работе. Мы создадим index.html, который будет выглядеть примерно так:

<html>
    <! — head etc →
    <body>
        <reddit-pwa-app></reddit-pwa-app>
        <script src=”./src/reddit-pwa-app.js” type=”module”></script>
    </body>
</html>

И `reddit-pwa-app.js`:

import { LitElement, html } from ‘lit-element’;
class RedditPwaApp extends LitElement {
 
 // …
 
   render() {
      return html`
         <h1>Hello world!</h1>
      `;
   }
}
customElements.define(‘reddit-pwa-app’, RedditPwaApp);

У нас отличное начало. Давайте посмотрим, как это выглядит на данный момент в браузере, так что давайте запустим наш сервер, откроем браузер и ... Что это? Ошибка?

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

import { LitElement, html } from ‘lit-element’; // ← bare module specifier
import { Router } from ‘@vaadin/router’; // ← bare module specifier

import { foo } from ‘./bar.js’; // ← not bare!
import { html } from ‘https://unpkg.com/lit-html'; // ← not bare!

Естественно, мы могли бы использовать для этого некоторые инструменты, такие как webpack, или накопительный пакет, или сервер разработки, который переписывает простые спецификаторы модуля на что-то значимое для браузеров, чтобы мы могли загружать наши импортированные файлы. Но это означает, что мы должны использовать набор инструментов, погрузиться в конфигурацию, и мы стараемся оставаться здесь минимальными. Мы просто хотим писать код! Чтобы решить эту проблему, мы собираемся взглянуть на импорт карт.

Карты импорта - это новое предложение, которое позволяет вам контролировать поведение импорта JavaScript. Используя карту импорта, мы можем контролировать, какие URL-адреса выбираются операторами JavaScript `import` и выражениями` import () `, и позволяет повторно использовать это сопоставление в контекстах, не связанных с импортом. Это здорово по нескольким причинам:

  • Позволяет нашим описателям модулей работать
  • Обеспечивает разрешение отката, так что `import $ from« jquery »;` может сначала попытаться перейти к CDN, но вернуться к локальной версии, если сервер CDN не работает
  • Включает полифиллинг или другой контроль над встроенными модулями (подробнее об этом позже, подождите!)
  • Решает проблему вложенных зависимостей (прочтите этот блог!)

Звучит довольно мило, не так ли? Карты импорта в настоящее время доступны в Chrome 75+, за флагом, и, имея в виду эти знания, давайте перейдем к нашему index.html и добавим карту импорта в наш ‹head›:

<head>
   <script type="importmap">
      {
        "imports": {
          "@vaadin/router": "/web_modules/@vaadin/router.js",
          "lit-element": "/web_modules/lit-element.js"
        }
      }
   </script>
</head>

Если мы вернемся в браузер и обновим страницу, ошибок больше не будет, и мы увидим на экране надпись «‹h1› Hello world! ‹/H1›«.

Импорт карт - это невероятно интересный новый стандарт, на который обязательно стоит обратить внимание. Если вы хотите поэкспериментировать с ними и сгенерировать свою собственную карту импорта на основе файла `yarn.lock`, вы можете попробовать наш пакет open-wc import-maps-generate и поиграться. Я очень рад видеть, что люди будут развивать в сочетании с импортными картами.

📡 Сервисный работник

Хорошо, мы немного забегаем вперед по времени. Наши зависимости работают, у нас настроен маршрутизатор, и мы выполнили несколько вызовов API, чтобы получить данные из Reddit и отобразить их на нашем экране. Рассмотрение всего кода немного выходит за рамки этого сообщения, но помните, что вы можете найти весь код в репозитории github, если хотите прочитать детали реализации.

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

Сервис-воркеры - это своего рода JavaScript-воркер, который работает в фоновом режиме. Вы можете визуализировать это как сидящее между веб-страницей и сетью. Всякий раз, когда ваша веб-страница делает запрос, он сначала проходит через сервис-воркер. Это означает, что мы можем перехватить запрос и что-то с ним делать! Например; мы можем позволить запросу пройти в сеть, чтобы получить ответ, и кэшировать его, когда он вернется, чтобы мы могли использовать эти кэшированные данные позже, когда мы можем быть в автономном режиме. Мы также можем использовать сервис-воркер для предварительного кэширования наших ресурсов. Это означает, что мы можем предварительно кэшировать любые критически важные ресурсы, которые могут понадобиться нашему приложению для работы в автономном режиме. Если у нас нет сетевого подключения, мы можем просто вернуться к кэшированным активам и при этом иметь работающее (хотя и автономное) приложение.

Если вы хотите узнать больше о прогрессивных веб-приложениях и сервис-воркерах, я очень рекомендую вам прочитать Поваренную книгу оффлайн Джейка Арчибальда. А также этот видеоурок из серии от Jad Joubran.

Итак, давайте продолжим и реализуем сервис-воркер. В наш index.html мы добавим следующий фрагмент:

<script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker.register('./sw.js').then(() => {
          console.log('ServiceWorker registered!');
        }, (err) => {
          console.log('ServiceWorker registration failed: ', err);
        });
      });
    }
</script>

Мы также добавим файл sw.js в корень нашего проекта. Итак, мы собираемся предварительно кэшировать ресурсы нашего приложения, и именно здесь Pika действительно упростила нам жизнь. Если вы посмотрите на обработчик установки в файле сервис-воркера:

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHENAME).then((cache) => {
      return cache.addAll([
        '/',
        './web_modules/lit-element.js',
        './web_modules/@vaadin/router.js',
        './src/reddit-pwa-app.js',
        './src/reddit-pwa-comment.js',
        './src/reddit-pwa-search.js',
        './src/reddit-pwa-subreddit.js',
        './src/reddit-pwa-thread.js',
        './src/utils.js',
      ]);
    })
  );
});

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

📴 Переход в автономный режим

Верно. Теперь, когда мы кэшировали наши ресурсы для работы в автономном режиме, было бы отлично, если бы мы действительно могли сохранить несколько сообщений, которые мы можем читать в автономном режиме. Есть много путей, которые ведут в Рим, но, поскольку мы живем немного на грани, мы собираемся пойти с: Kv-хранилищем!

📦 Встроенные модули

Здесь есть о чем поговорить. Kv-хранилище - это встроенный модуль. Встроенные модули очень похожи на обычные модули JavaScript, за исключением того, что они поставляются вместе с браузером. Стоит отметить, что хотя встроенные модули поставляются вместе с браузером, они не отображаются в глобальной области видимости и имеют пространство имен с помощью `std:` (да, действительно.). У этого есть несколько преимуществ: они не будут добавлять никаких накладных расходов на запуск нового контекста среды выполнения JavaScript (например, новой вкладки, рабочего или служебного работника), и они не будут потреблять память или процессор, если они фактически не импортированы. , а также избегать конфликтов имен с существующим кодом.

Еще одно интересное, хотя и несколько спорное предложение в качестве встроенного модуля - это элементы std-toast и std-switch.

🗃 Кв-накопитель

Хорошо, давайте поговорим о кв-хранилище. Kv-хранилище (или
ключевое значение хранилище) довольно похоже на localStorage, за исключением нескольких основных отличий, и расположено поверх IndexedDB. .

Мотивация для kv-storage заключается в том, что localStorage является синхронным, что может привести к снижению производительности и проблемам с синхронизацией. Он также ограничен исключительно строковыми парами ключ / значение. Альтернатива, IndexedDb,… сложна в использовании. Причина, по которой его так сложно использовать, заключается в том, что он предшествует обещаниям, и это приводит к, ну, довольно плохому опыту разработчика. Не смешно. Однако Kv-хранилище - это очень весело, асинхронно, и просто в использовании! Рассмотрим следующий пример:

import { storage, /* StorageArea */ } from "std:kv-storage";
(async () => {
  await storage.set("mycat", "Tom");
  console.log(await storage.get("mycat")); // Tom
})();

Обратите внимание, как мы импортируем из `std: kv-storage`? Этот спецификатор импорта также является пустым, но в данном случае это нормально, потому что он фактически поставляется с браузером.

Довольно аккуратно. Мы можем идеально использовать это для добавления кнопки «Сохранить для автономного режима» и просто сохранить данные JSON для потока Reddit и получить их, когда они нам понадобятся.

`reddit-pwa-thread.js: 52`:

const savedPosts = new StorageArea("saved-posts");
// ...
async saveForOffline() {
  await savedPosts.set(this.location.params.id, this.thread); // id of the post + thread as json
  this.isPostSaved = true;
}

Итак, теперь, если мы нажмем кнопку «Сохранить для офлайн» и перейдем на вкладку «Приложение» инструментов разработчика, мы увидим «kv-storage: saved-posts», в котором хранятся данные JSON для этого поста:

А если мы вернемся на страницу поиска, у нас будет список сохраненных сообщений с только что сохраненным сообщением:

🔮 Полифиллинг

Превосходно. Однако здесь мы столкнемся с другой проблемой. Жить на грани - весело, но и опасно. Проблема, с которой мы сталкиваемся, заключается в том, что на момент написания kv-хранилище реализовано только в Chrome за флагом. Это явно не здорово. К счастью, доступен полифилл, и в то же время мы можем продемонстрировать еще одну действительно полезную функцию import-maps; полифиллинг!

Перво-наперво, давайте установим kv-storage-polyfill:

`npm i -S kv-storage-polyfill`

Обратите внимание, что наш хук `postinstall` снова запустит для нас Pika.

И давайте добавим следующее к нашей карте импорта в наш `index.html`:

<script type="importmap">
  {
    "imports": {
      "@vaadin/router": "/web_modules/@vaadin/router.js",
      "lit-element": "/web_modules/lit-element.js",
      "/web_modules/kv-storage-polyfill.js": [
        "std:kv-storage",
        "/web_modules/kv-storage-polyfill.js"
      ]
    }
  }
</script>

Итак, здесь происходит следующее: всякий раз, когда запрашивается или импортируется `/ web_modules / kv-storage-polyfill.js`, браузер первым пытается проверить, доступен ли` std: kv-storage`; однако, если это не удается, вместо этого загружается `/ web_modules / kv-storage-polyfill.js`.

Итак, в коде, если мы импортируем:

import { StorageArea } from '/web_modules/kv-storage-polyfill.js';

Вот что произойдет:

"/web_modules/kv-storage-polyfill.js": [     // when I'm requested
  "std:kv-storage",                          // try me first!
  "/web_modules/kv-storage-polyfill.js"      // or fallback to me
]

🎉 Заключение

И теперь у нас должен быть простой работающий PWA с минимальными зависимостями. В этом проекте есть несколько нюансов, на которые мы можем пожаловаться, и все они, вероятно, будут справедливыми. Например; мы, наверное, могли бы обойтись без Pika, но это действительно облегчает нам жизнь. Вы могли бы привести тот же аргумент о добавлении простой конфигурации Webpack, но вы упустили суть. Дело здесь в том, чтобы сделать забавное приложение, используя при этом некоторые из последних функций, отбросить несколько модных словечек и иметь низкий барьер для входа. Как сказал бы Фред Шотт: «В 2019 году вам следует использовать упаковщик, потому что вы хотите, а не потому, что вам нужно».

Однако, если вас интересуют придирки, вы можете прочитать Великую дискуссию об использовании Webpack против Pika и без сборки, и вы получите отличные идеи от Шона Ларкинна из основной команды Webpack, а также от Фред К. Шотт , создатель Pika.

Надеюсь, вам понравился этот пост в блоге, и я надеюсь, что вы что-то узнали или открыли для себя новых интересных людей, за которыми можно подписаться. Прямо сейчас в этом пространстве происходит много интересных событий, и я надеюсь, что вы так же взволнованы ими, как и я. Если у вас есть какие-либо вопросы, комментарии, отзывы или придирки, не стесняйтесь обращаться ко мне в твиттере по адресу @passle_ или @openwc и не забудьте проверить open-wc.org 😉.

Почетные упоминания

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

Для начала: Гай Бедфорд, который написал es-module-shims, который, ну, склеивает модули es и импортирует карты. Что, если вы спросите меня, является довольно удивительным подвигом и позволяет мне фактически использовать некоторые из этих новых технологий, которые еще не реализованы во всех браузерах.

А если вас интересует нечто подобное, вам обязательно стоит послушать выступление Люка Джексона Не создавайте это приложение! Никакого веб-пакета, никаких забот 🤓🤙, как сказал бы Люк.

Я также хотел бы поблагодарить Бенни Пауэрса и Ларса ден Баккера за их полезные комментарии и отзывы.