HTML-импорт — include для веба: часть 2

в 9:36, , рубрики: html, html5, import, w3c, web-разработка, Веб-разработка

Перевод статьи «HTML Imports #include for the web», Eric Bidelman.

Ссылка на первую часть перевода.

Предоставление веб-компонентов

HTML-импорт упрощает загрузку и повторное использование кода. В частности, это хороший способ распространения веб-компонентов. Это касается как простых HTML <template>, так и полноценных кастомных элементов с теневым DOM [1, 2, 3]. Когда эти технологии работают вместе, импорт становится инструментом для подключения веб-компонентов.

Подключение шаблонов

HTML-шаблоны это хороший пример того, где может пригодиться импорт. Тэг <template> позволяет выносить определенные секции разметки для дальнейшего их использования. Шаблоны полезны тем, что их содержимое и скрипты не будут отображаться и выполняться до их добавления на страницу при помощи JS кода.

import.html

<template>
  <h1>Hello World!</h1>
  <!-- Img is not requested until the <template> goes live. -->
  <img src="">
  <script>alert("Executed when the template is activated.");</script>
</template>

index.html

<head>
  <link rel="import" href="import.html">
</head>
<body>
  <div id="container"></div>
  <script>
    var link = document.querySelector('link[rel="import"]');

    // Clone the <template> in the import.
    var template = link.import.querySelector('template');
    var clone = document.importNode(template.content, true);

    document.querySelector('#container').appendChild(clone);
  </script>
</body>

Определение пользовательских элементов

Пользовательские элементы это еще одна технология веб-компонентов, которая отлично сочетается с HTML-импортом. Как мы знаем в импорте могут выполняться скрипты, таким образом мы можем объявлять и регистрировать пользовательские элементы, позволяя использовать их в разметке главного документа. Назовем это авторегистрацией.

elements.html

<script>
  // Define and register <say-hi>.
  var proto = Object.create(HTMLElement.prototype);

  proto.createdCallback = function() {
    this.innerHTML = 'Hello, <b>' +
                     (this.getAttribute('name') || '?') + '</b>';
  };

  document.registerElement('say-hi', {prototype: proto});

  // Define and register <shadow-element> that uses Shadow DOM.
  var proto2 = Object.create(HTMLElement.prototype);

  proto2.createdCallback = function() {
    var root = this.createShadowRoot();
    root.innerHTML = "<style>::content > *{color: red}</style>" +
                     "I'm a " + this.localName +
                     " using Shadow DOM!<content></content>";
  };
  document.registerElement('shadow-element', {prototype: proto2});
</script>

Данный импорт определяет (и регистрирует) два элемента: <say-hi> и <shadow-element>. Мы можем использовать их прямо в разметке импортирующего документа без каких-либо дополнительных предусловий.

index.html

<head>
  <link rel="import" href="elements.html">
</head>
<body>
  <say-hi name="Eric"></say-hi>
  <shadow-element>
    <div>( I'm in the light dom )</div>
  </shadow-element>
</body>

Как мне кажется, такой подход превращает HTML-импорт в идеальный способ предоставления веб-компонентов.

Работа с зависимостями и вложенным импортом

Эй, ты, я вижу тебе понравился импорт, так что я вложил импорт внутрь импорта1.

Вложенный импорт

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

Снизу представлен реальный пример из Полимера. Это компонент tab (<polymer-ui-tabs>) использующий компоненты layout и selector. Эти зависимости загружаются при помощи HTML-импорта.

polymer-ui-tabs.html

<link rel="import" href="polymer-selector.html">
<link rel="import" href="polymer-flex-layout.html">

<polymer-element name="polymer-ui-tabs" extends="polymer-selector" ...>
  <template>
    <link rel="stylesheet" href="polymer-ui-tabs.css">
    <polymer-flex-layout></polymer-flex-layout>
    <shadow></shadow>
  </template>
</polymer-element>

Вот так разработчики могут использовать этот элемент:

<link rel="import" href="polymer-ui-tabs.html">
<polymer-ui-tabs></polymer-ui-tabs>

Когда выйдет новая версия селектора, например <polymer-selector2>, вы сможете подменить <polymer-selector> и сразу же начать пользоваться новой версией. Благодаря импорту и веб-компонентам, это обновление не заденет код использующий компонент polymer-ui-tabs.

Управление зависимостями

Как вы знаете, если дважды подключить JQuery, это приведет к ошибкам. Не будет ли это проблемой, если несколько компонентов используют одну библиотеку? Нет, если мы будем использовать HTML-импорт! Использование импорта само по себе разрешает проблему управления зависимостями.

Оборачивая используемые библиотеки в импорт, вы автоматически избегаете их повторной загрузки. Импортируемый документ парсится только один раз. Скрипты в нем тоже выполняются только раз. Для примера вы можете импортировать jquery.html, который загружает сам JQuery.

jquery.html

<script src="http://cdn.com/jquery.js"></script>

Данный импорт может быть использован в других импортируемых компонентах:

import2.html

<link rel="import" href="jquery.html">
<div>Hello, I'm import 2</div>

ajax-element.html

<link rel="import" href="jquery.html">
<link rel="import" href="import2.html">

<script>
  var proto = Object.create(HTMLElement.prototype);

  proto.makeRequest = function(url, done) {
    return $.ajax(url).done(function() {
      done();
    });
  };

  document.registerElement('ajax-element', {prototype: proto});
</script>

Если понадобится, главный документ также может использовать jquery.html:

<head>
  <link rel="import" href="jquery.html">
  <link rel="import" href="ajax-element.html">
</head>
<body>

...

<script>
  $(document).ready(function() {
    var el = document.createElement('ajax-element');
    el.makeRequest('http://example.com');
  });
</script>
</body><source lang="html">

Несмотря на то, что jquery.html подключается в нескольких разных документах, его загрузка и исполнение происходит только один раз. Это можно увидеть заглянув на панель network:

image

Соображения производительности

HTML-импорт — замечательная технология, но как и с любой новой веб-технологией, вы должны с умом подойти к её использованию. Лучшие практики веб-разработки никто не отменял. Вот несколько вещей, которые вы должны помнить, работая с импортом.

Объединение импорта

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

Vulcanize, инструмент от создателей Полимера, используемый для построения проектов из npm. Он производит рекурсивную сборку всех импортируемых документов в единственный главный файл, это, по сути, объединение всех веб-компонентов в один файл. Данный инструмент написали создатели Полимера.

Браузерное кеширование импорта

HTML-импорт также хорошо справляется с логикой кеширования. Импорт http://cdn.com/bootstrap.html содержит вложенные ресурсы, но они так же кешируются.

Содержимое используется, только когда оно добавлено в DOM

Содержимое никак не вызывается, до тех пор пока оно не использовано в скриптах. Для примера у нас есть динамически созданная таблица стилей:

var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';

Браузер не запросит styles.css, до тех пор пока элемент link не будет добавлен в DOM:

document.head.appendChild(link); // browser requests styles.css

Другой пример, это динамически созданная разметка:

var h2 = document.createElement('h2');
h2.textContent = 'Booyah!';

Элемент h2 ничего не изменит, пока вы не добавите его в DOM.

Эта же идея остается истинной и для импортируемых документов. Пока вы не добавите их в DOM, они ничего не делают. Но на самом деле, скрипты являются исключением, они исполняются, сразу же при загрузке импорта. Смотрите скрипты в импорте.

Оптимизация для асинхронной загрузки

Импорт не блокирует парсинг главного документа. Скрипты внутри импорта выполняются по очереди, но не блокируют импортирующую страницу. Это значит, что происходит отложенное выполнение скриптов. Преимущество подключения импорта в элементе <head> заключается в том, что это не заблокирует работу с содержимым документов. Имея это ввиду, важно все же помнить, что скрипты в главном документе все равно блокируют загрузку страницы <script>:

<head>
  <link rel="import" href="/path/to/import_that_takes_5secs.html">
  <script>console.log('I block page rendering');</script>
</head>

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

Сценарий #1 (предпочтительный): у вас нет скриптов в элементе <head> или по ходу тела документа

Я рекомендую использовать скрипты как можно ниже, чтобы предотвратить немедленную загрузку вашего импорта. Но вы ведь и так следуете этой практике, НЕ ТАК ЛИ!? ;)

вот пример:

<head>
  <link rel="import" href="/path/to/import.html">
  <link rel="import" href="/path/to/import2.html">
  <!-- avoid including script -->
</head>
<body>
  <!-- avoid including script -->

  <div id="container"></div>

  <!-- avoid including script -->
  ...

  <script>
    // Other scripts n' stuff.

    // Bring in the import content.
    var link = document.querySelector('link[rel="import"]');
    var post = link.import.querySelector('#blog-post');

    var container = document.querySelector('#container');
    container.appendChild(post.cloneNode(true));
  </script>
</body>

Все скрипты расположены снизу.

Сценарий 1.5: импорт добавляется самостоятельно<body>

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

import.html:

<div id="blog-post">...</div>
<script>
  var me = document.currentScript.ownerDocument;
  var post = me.querySelector('#blog-post');

  var container = document.querySelector('#container');
  container.appendChild(post.cloneNode(true));
</script>

index.html

<head>
  <link rel="import" href="/path/to/import.html">
</head>
<body>
  <!-- no need for script. the import takes care of things -->
</body>

Сценарий #2: у вас есть скрипты в элементе <head> или по ходу тела документа

Если у вас есть большой импорт, на загрузку которого нужно много времени, следующий за ним скрипт остановит рендеринг страницы. Google Analytics например, рекомендует добавлять отслеживающий код в элементе <head>. Если вы не можете избежать размешения скриптов в элементе <head>, динамическое добавление тега импорта предотвратит блокировку:

<head>
  <script>
    function addImportLink(url) {
      var link = document.createElement('link');
      link.rel = 'import';
      link.href = url;
      link.onload = function(e) {
        var post = this.import.querySelector('#blog-post');

        var container = document.querySelector('#container');
        container.appendChild(post.cloneNode(true));
      };
      document.head.appendChild(link);
    }

    addImportLink('/path/to/import.html'); // Import is added early :)
  </script>
  <script>
    // other scripts
  </script>
</head>
<body>
   <div id="container"></div>
   ...
</body>

Как вариант, вы можете добавить весь импорт в конце <body>.

<head>
  <script>
    // other scripts
  </script>
</head>
<body>
  <div id="container"></div>
  ...

  <script>
    function addImportLink(url) { ... }

    addImportLink('/path/to/import.html'); // Import is added very late :(
  </script>
</body>

Примечание: Самый последний вариант — наименее предпочтителен. Парсер не начинает работать с содержимым страницы до самого окончания страницы.

Что стоит запомнить

  • mime-тип импорта, это text/html
  • Ресурсы с других доменов должны разрешать междоменное разделение ресурсов(CORS)
  • Импорт с одним URL запрашивается и выполняется только один раз. Это также значит, что скрипты внутри импорта выполняются только один раз.
  • Скрипты в импорте выполняются по порядку но не останавливают парсинг главного документа.
  • Элемент импорта не говорит браузеру "#добавь содержимое прямо сюда". Он говорит «пойди и возьми этот документ, чтобы я в дальнейшем мог его использовать». Скрипты в импорте выполняются автоматически, а разметка, стили и другие ресурсы должны быть явно добавлены на главную страницу. Примечание: элементы <style> также добавляются автоматически. Это все составляет огромную разницу между HTML-импортом и <iframe>, который говорит браузеру «загрузи и отобрази контент прямо здесь».

Заключение

HTML-импорт позволяет предоставлять набор HTML/CSS/JS кода, как единый ресурс. Это полезно, как само по себе, так и в контексте веб-компонентов. Разработчики могут создавать повторно используемые компоненты, для их потребления другими разработчиками при помощи <link rel="import">.

HTML-импорт это простая идея, но она предоставляет нам несколько интересных полезностей.

Полезности

  • Распространение наборов взаимосвязанного HTML/CSS/JS кода в одной связке. Теоретически, вы можете импортировать целые приложения друг в друга.
  • Организация кода — разделение на кода отдельные компоненты, улучшает модульность и приводит к повторному использованию кода.
  • Предоставление пользовательских элементов. Импорт может использоваться для подключения и регистрации пользовательских элементов на странице. Это, по сути, отделение интерфейса от его использования, что является хорошей практикой при разработке приложений.
  • Управление зависимостями — автоматическое предотвращение повторной загрузки ресурсов.
  • Разбитые скрипты — до появления импорта JS библиотеки могли начинать выполнение только после того, как весь документ был обработан, что конечно же плохо для скорости загрузки. С импортом библиотеки смогут работать, по ходу обработки необходимых кода. Это уменьшает временные задержки!
    <link rel="import" href="chunks.html">:

    <script>/* script chunk A goes here */</script>
    <script>/* script chunk B goes here */</script>
    <script>/* script chunk C goes here */</script>
    ...
    

  • Распаралеливание обработки HTML-разметки — впервые браузер может запускать несколько HTML-парсеров одновременно.
  • Переключение между режимами отладки и продакшна простым изменением ссылки на импорт верхнего уровня. Это не зависит от того, является ли главный импорт скомпилированным ресурсом или древом импорта.


1. В оригинале предложение звучит так «Yo dawg. I hear you like imports, so I included an import in your import.», это отсылка к известному выражению репера Xzibit

Автор: jojo97

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js