Новая спецификация «HTML Imports» описывает, как вставить один документ в другой при помощи HTML-тега <link rel="import">
.
Зачем?
Мы ведь и так можем вставлять документ в документ, при помощи <iframe>
, зачем нужен ещё какой-то импорт? Что не так с iframe
?
…С iframe
всё так. Однако, по своему смыслу iframe
– это отдельный документ.
- Для
iframe
создаётся полностью своё окружение, у него свой объектwindow
и свои переменные. - Если
iframe
загружен с другого домена, то взаимодействие с ним возможно только черезpostMessage
.
Это хорошо, когда нужно действительно в одной странице отобразить содержимое другой.
А что, если нужно встроить другой документ как естественную часть текущего? С единым скриптовым пространством, едиными стилями, но при этом – другой документ.
Например, это нужно для подгрузки внешних частей документа (веб-компонент) снаружи. И желательно не иметь проблем с разными доменами: если уж мы действительно хотим подключить HTML с одного домена в страницу на другом – мы должны иметь возможность это сделать без «плясок с бубном».
Иначе говоря, <link rel="import">
– это аналог <script>
, но для подключения полноценных документов, с шаблонами, библиотеками, веб-компонентами и т.п. Всё станет понятнее, когда мы посмотрим детали.
Пример вставки
Синтаксис:
<link rel="import" href="http://site.com/document.html">
- В отличие от
<iframe>
тег<link rel="import">
может быть в любом месте документа, даже в<head>
. - При вставке через
<iframe>
документ показывается внутри фрейма. В случае с<link rel="import">
это не так, по умолчанию документ вообще не показывается.
HTML, загруженный через <link rel="import">
имеет отдельный DOM документа, но скрипты в нём выполняются в общем контексте страницы.
Файл, загруженный через <link rel="import">
, обрабатывается, выполняются скрипты, строится DOM документа, но не показывается, а записывается в свойство link.import
.
Мы сами решаем, где и когда его вставить.
В примере ниже <link rel="import" href="timer.html">
подключает документ timer.html
и, после его загрузки, вызывает функцию show
. Эта функция через link.import.querySelector('time')
выбирает интересующую часть подгружённого документа и вставляет её в текущий:
<!DOCTYPE HTML>
<html>
<body>
<script>
function show() {
var time = link.import.querySelector('time')
document.body.appendChild(time);
};
</script>
<link rel="import" id="link" onload="show()" href="timer.html">
</body>
</html>
В файле timer.html
находится элемент и скрипт, который его «оживляет»:
<!DOCTYPE HTML>
<html>
<body>
<time id="timer">0</time>
<script>
var localDocument = document.currentScript.ownerDocument;
var timer = localDocument.getElementById('timer');
var timerId = setInterval(function() {
timer.innerHTML++;
}, 1000);
</script>
</body>
</html>
<!DOCTYPE HTML>
<html>
<body>
<script>
function show() {
var time = link.import.querySelector('time')
document.body.appendChild(time);
};
</script>
<link rel="import" id="link" onload="show()" href="timer.html">
</body>
</html>
<!DOCTYPE HTML>
<html>
<body>
<time id="timer">0</time>
<script>
var localDocument = document.currentScript.ownerDocument;
var timer = localDocument.getElementById('timer');
var timerId = setInterval(function() {
timer.innerHTML++;
}, 1000);
</script>
</body>
</html>
Важные детали:
- После загрузки все скрипты в подключённом
timer.html
выполняются в контексте основной страницы, так чтоtimer
и другие переменные станут глобальными переменными страницы. - Переменная
document
– это документ основной страницы. Для доступа к импортированному, то есть текущему документу изнутриtimer.html
его можно получить какdocument.currentScript.ownerDocument
. - Таймер в загруженном документе начинает работать сразу, новый документ оживает сразу после загрузки, хотя до переноса узлов в основной документ этого может быть и не видно.
В примере выше содержимым импорта управлял основной документ, но timer.html
мог бы и показать сам себя вызовом document.body.appendChild(timer)
или вызвать функцию с внешнего документа, так как у них единая область видимости. Тогда не понадобился бы никакой onload
.
Ещё пример вставки, на этот раз документ только подключает <link>
, а таймер вставляет себя сам:
<!DOCTYPE HTML>
<html>
<head>
<link rel="import" id="link" href="timer.html">
</head>
<body></body>
</html>
<!DOCTYPE HTML>
<html>
<head>
<style>
#timer {
color: red;
}
</style>
</head>
<body>
<p id="timer">0</p>
<script>
var localDocument = document.currentScript.ownerDocument;
var timer = localDocument.getElementById('timer');
var timerId = setInterval(function() {
timer.innerHTML++;
}, 1000);
document.body.appendChild(timer);
</script>
</body>
</html>
Обратим внимание – стили импорта попадают в контекст страницы. В примере выше импорт добавил и стиль для #timer
и сам элемент.
Веб-компоненты
Импорт задуман как часть платформы Web Components.
Предполагается, что главный документ может импортировать файлы-определения, в которых будут все необходимые HTML, JS и CSS для элементов, а затем использовать их.
Пример:
<link rel="import" href="ui-tabs.html">
<link rel="import" href="ui-dialog.html">
<ui-tabs>...</ui-tabs>
<ui-dialog>...</ui-dialog>
В следующей главе мы разберём расширенный пример на эту тему.
Повторное использование
Повторный импорт с тем же URL использует уже существующий документ.
Если файл libs.html
импортирован два раза, то CSS и скрипты из него подключатся и выполнятся ровно один раз.
Это можно использовать, чтобы не подгружать одинаковые зависимости много раз. И сама страница и её импорты, и их подимпорты, и так далее, могут подключать libs.html
без опасения лишний раз перезагрузить и выполнить скрипты.
Например:
-
Главный файл
index.html
подключает документы:<link rel="import" href="ui-tabs.html"> <link rel="import" href="ui-dialog.html"> ...
-
ui-tabs.html
подключаетlibs.html
:<link rel="import" href="libs.html"> ...template и код для табов...
-
ui-dialog.html
также используетlibs.html
:<link rel="import" href="libs.html"> ...template и код для диалогов...
Файл libs.html
при этом будет подключён только один раз. Это позволяет не бояться лишнего дублирования библиотек, используемых при описании множества компонент.
Итого
Тег <link rel="import">
позволяет подключить любой документ к странице, причём:
- Скриптовое пространство и стили со страницей будут общие.
- Документ DOM – отдельный, он доступен как
link.import
снаружи, а из внутреннего скрипта – черезdocument.currentScript.ownerDocument
. Можно без проблем переносить элементы из главного документа в импорт и наоборот. - Импорты могут содержать другие импорты.
- Если какой-то URL импортируется повторно – подключается уже готовый документ, без повторного выполнения скриптов в нём. Это позволяет избежать дублирования при использовании одной библиотеки во множестве мест.