javascript

Современная Web-платформа: как расслабиться и получать удовольствие? Практическое руководство, часть

  • среда, 25 октября 2017 г. в 03:13:51
https://habrahabr.ru/post/340530/
  • Разработка веб-сайтов
  • Браузеры
  • JavaScript


Всем привет!


Помните эту статью? Раньше мы могли быстро собрать статичную HTML-страничку в каком-нибудь FrontPage и сайт был готов. С этим мог справится любой студент. В более сложном случае, мы писали пару строк на PHP и получали уже целый портал, собранный из разных элементов шаблона на сервере. Затем мы захотели, чтобы наш сайт как-то выделялся на общем фоне и умел чуть-чуть больше. Трон занял его-величество jQuery. Теперь же, мы оказались погребены под завалами фреймворков и библиотек, инструментов сборки, менеджеров зависимостей, препроцессоров и постпроцессоров, особых форматов, языков и стилей программирования, чтобы иметь возможность стряпать простые лэндинги. Все стало слишком сложно. Спикеры на конференциях стали соревноваться в изощренности того, каким еще образом можно сломать нам мозг. Как мы докатились до жизни такой? Чем «раньше» так сильно отличается от «сейчас»? Что нас ждет «потом»? Есть ли в современной веб-разработке некий дзен-стайл, блюдя который, можно, как в старые добрые времена, собрать себе уютный сайтик «на коленке» за пару вечеров, без ковыряния в документации десятка хипстерских технологий-однодневок? Насколько доступны нам простые решения в серьезной промышленной разработке? Куда движется веб-платформа? Предлагаю разобраться.

Для того, чтобы поэкспериментировать с практической частью, вам понадобится любой удобный редактор кода (например Visual Studio Code) и актуальная версия браузера Chrome. Для начала этого будет вполне достаточно. Впоследствии (я планирую целый цикл публикаций на эту тему), все неминуемо усложнится, но мы будем стараться оставаться «в рамках» — это наша цель.

Предпосылки и решение


Когда я делал свои первые сайты (в конце 90-х — начале 2000-х), первое, что мне показалось странным и ужасно неудобным в обычном HTML — невозможность описать «заголовок», «подвал» и «главное меню» сайта в одном месте для всех страниц сразу. Я мог вставить одну картинку или один скрипт во многих местах, но не банальный кусок разметки. Также, я не мог описать общий макет страницы, без необходимости повторять его в каждом отдельном HTML-файле. Я думаю, многих эти-же причины подтолкнули к первым экспериментам с серверными технологиями. Но для всего серверного нужен соответствующий сервер, а это новый уровень усложнения задачи, казавшейся сперва такой простой. Так или иначе, эта проблема решалась множество раз и множеством способов. Мы пытались использовать iframe, пытались динамически управлять видимостью фрагментов, содержащихся на одной странице; как только не издевались над собой и здравым смыслом. В итоге, мы пришли к современному набору мета-платформ типа React или Vue.js, которые, среди прочего, позволяют нам создать структуру модулей, отражающую структуру того, что мы видим на экране. Но и сама веб-платформа не стоит на месте и, о чудо, теперь у нас есть нативная возможность создавать больше чем просто многократно используемые куски разметки: теперь мы можем создавать свои собственные настоящие HTML-теги! Причем, каждый такой тег может быть как примитивным контейнером, содержащим только необходимое оформление (или быть интерактивным UI-элементом), так и макетом всей страницы. Он даже может содержать в себе целое сложное приложение с кучей, необходимой вам, клиент-серверной логики. Да, я говорю о новом стандарте Custom Elements (Living Standard). И он действительно многое меняет.

Пробуем на вкус


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

Файл index.html:

<html>
  <head>
    <script src="elements/my-layout.js"></script>
    <script src="elements/my-header.js"></script>
    <script src="elements/my-footer.js"></script>
    <script src="elements/my-menu.js"></script>
  </head>
  <body>
    <my-layout>
      <my-header slot="header">Hi, I am your header.</my-header>
      <div slot="content">Hello World! This is content.</div>
      <my-menu slot="menu"></my-menu>
      <my-footer slot="footer">And I am your footer.</my-footer>
    </my-layout>
  </body>
</html>

Обратите внимание на именование кастомных тегов: по стандарту оно обязательно должно содержать дефис (один или более). Также, вы, наверное, заметили атрибут «slot» — о нем немного позже.

Теперь перейдем к файлу, описывающему основной макет страницы elements/my-layout.js:

class MyLayout extends HTMLElement {
  constructor() {
    // Сперва вызываем конструктор суперкласса HTMLElement:
    super();
    // Cоздаем ShadowDOM элемента - его локальную,
    // закрытую от внешнего мира область разметки, но доступную для JS:
    this.attachShadow({
      mode: 'open',
    });
    // присваеваем нашему элементу свой шаблон:
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
        }
        .header {
          position: fixed;
          top: 0;
          left: 0;
          right: 0;
          height: 40px;
          line-height: 40px;
          padding-left: 20px;
          padding-right: 20px;
          background-color: #000;
          color: #fff;
        }
        .menu {
          position: fixed;
          top: 0;
          bottom: 0;
          right: 0;
          width: 240px;
          padding: 20px;
          background-color: #eee;
          box-shadow: 0 0 8px rgba(0, 0, 0, 0.4);
          z-index: 1000;
        }
        .content {
          padding-top: 60px;
          padding-bottom: 60px;
        }
        .footer {
          position: fixed;
          left: 0;
          right: 0;
          bottom: 0;
          height: 40px;
          line-height: 40px;
          padding-left: 20px;
          padding-right: 20px;
          background-color: #eee;
          border-top: 2px solid #000;
        }
      </style>
      <div class="header"><slot name="header"></slot></div>
      <div class="content"><slot></slot></div>
      <div class="menu"><slot name="menu"></slot></div>
      <div class="footer"><slot name="footer"></slot></div>
    `;
  }
}
// Регистируем созданный элемент:
window.customElements.define('my-layout', MyLayout);
// Теперь браузер знает о существовании нового тега <my-layout>.

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

В части шаблона, где находится сама разметка, мы снова встречаем слово «slot» — это специальный тег, который работает в сочетании с ShadowDOM — он определяет позиции в разметке для частей контента нашего элемента. Соответствие определяется тем самым атрибутом «slot», который я упомянул ранее. Если у тега «slot» нет атрибута «name» — в него попадет контент «по умолчанию», который, в свою очередь, не имеет атрибута «slot». Это очень простой, но очень мощный нативный инструмент шаблонизации на «клиент-сайде».

Хозяйке на заметку
VS Code — пока не умеет автоматически подсвечивать HTML-синтаксис внутри ES6-строк. Для работы с большими участками разметки внутри JS-файлов, вы можете временно переключать синтаксис открытого файла в режим HTML (нижний правый угол окна): вам станет доступна подсветка, подсказки и автодополнение в HTML и CSS.

Создадим остальные элементы. Файл elements/my-menu.js:
class MyMenu extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({
      mode: 'open',
    });
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
        }
        a {
          display: flex;
          align-items: center;
          justify-content: center;
          background-color: #fff;
          cursor: pointer;
          height: 40px;
          text-decoration: none;
          color: currentColor;
          font-size: 1.2em;
          margin-bottom: 4px;
          border: 1px solid #fff;
        }
        a:hover {
          border: 1px solid currentColor;
        }
      </style>
      <a href="pages/page1.html">Page 1</a>
      <a href="pages/page2.html">Page 2</a>
      <a href="pages/page3.html">Page 3</a>
    `;
  }
}
window.customElements.define('my-menu', MyMenu);

Файл elements/my-header.js:

class MyHeader extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({
      mode: 'open',
    });
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
        }
        span {
          font-size: 0.8em;
          opacity: 0.6;
        }
      </style>
      <span>Text inside ShadowDOM.  </span>
      <slot></slot>
    `;
  }
}
window.customElements.define('my-header', MyHeader);

И последний файл нашего нано-проекта elements/my-footer.js:

class MyFooter extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({
      mode: 'open',
    });
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
        }
        ::slotted(*) {
          display: inline-block;
        }
        span {
          color: #f00;
          font-size: 0.8em;
        }
      </style>
      <slot></slot>
      <span> © 2018, Vasya Pupkin</span>
    `;
  }
}
window.customElements.define('my-footer', MyFooter);

Та-дам! Можно открывать наш index.html в браузере. Внимательный читатель заметил, что стили для тега «span» были напрямую определены сразу в двух местах (для разных элементов), однако они не повлияли друг-на-друга и отобразились правильно.

Что мы увидели?


Мы увидели пример настоящей модульной разработки без подключения каких-либо внешних библиотек, без настройки окружения, без ожидания сборки проекта, даже без необходимости запускать локальный сервер. В инструментах разработчика браузера мы видим непосредственно свой JS-код и свою собственную разметку, а не результат работы скриптов, создающих за нас DOM. Контент нашего главного HTML-файла находится в нем-же и сразу доступен, опять-же, без какого-либо предварительного рендера. Мы можем повторно использовать наши кастомные теги в этом-же проекте или в любых других. Мы не загрузили ничего лишнего и отобразили страницу практически мгновенно. Объем дополнительного JS-кода, который нам понадобился для этого — микроскопический. При этом, каждый наш новый элемент — это такой-же полноправный DOM-элемент как и любой стандартный div. Для него доступны те-же самые стандартные атрибуты, свойства, события и методы типа addEventListener, appendChild, remove и т. д. Это то, чего мы так долго хотели? По моему, да.

Дальше — больше


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

  • Жизненный цикл Custom Element в DOM
  • Тег template
  • Оптимизация производительности, динамическое обновление
  • Custom Elements и ООП
  • HTML-атрибуты, работа с общими данными
  • ShadowDOM и CSS: инкапсуляция, селекторы, нативные CSS-переменные, нативные CSS-выражения
  • SVG и Custom Elements — практические советы
  • Организация и структура кода: модули, точки подключения, динамическая загрузка, http/2 Server Push
  • Новый подход к прототипированию, созданию, тестированию и внедрению дизайна
  • Нестандартные стандарты: браузеры, полифиллы, ShadyDOM, HTML-imports
  • Существующая экосистема: библиотеки и готовые компоненты, реальные проекты
  • Custom Elements и серверный рендеринг
  • Custom Elements и SEO
  • PWA

Спасибо за внимание!