Inclure pour le Web
Pourquoi importer ?
Réfléchissez à la façon dont vous chargez différents types de ressources sur le Web. Pour JavaScript, nous avons <script src>
. En ce qui concerne le CSS, <link rel="stylesheet">
est probablement votre outil de référence. Pour les images, il s'agit de <img>
. La vidéo contient <video>
. Audio, <audio>
… Allez-y ! La plupart des contenus Web se chargent de manière simple et déclarative. Ce n'est pas le cas pour le code HTML. Voici les options qui s'offrent à vous :
<iframe>
: solution éprouvée, mais lourde. Le contenu d'un iframe réside entièrement dans un contexte distinct de celui de votre page. Bien que cette fonctionnalité soit généralement très utile, elle crée des défis supplémentaires (il est difficile de réduire la taille du cadre à son contenu, extrêmement frustrant de l'utiliser dans/hors d'un script et presque impossible de le styliser).- AJAX – J'adore
xhr.responseType="document"
, mais vous indiquez avoir besoin de JavaScript pour charger le code HTML ? Il semble y avoir un problème. - CrazyHacksTM : intégré dans des chaînes, masqué en tant que commentaires (par exemple,
<script type="text/html">
). Beurk !
Vous voyez l'ironie ? Le contenu le plus basique du Web, HTML, demande le plus d'efforts. Heureusement, les composants Web sont là pour nous remettre sur les rails.
Premiers pas
Les importations HTML, qui font partie de la caste Web Components, permettent d'inclure des documents HTML dans d'autres documents HTML. Vous n'êtes pas limité au balisage. Une importation peut également inclure du CSS, du JavaScript ou tout autre élément qu'un fichier .html
peut contenir. En d'autres termes, les importations constituent un excellent outil pour charger les fichiers HTML/CSS/JS associés.
Principes de base
Incluez une importation sur votre page en déclarant un <link rel="import">
:
<head>
<link rel="import" href="/path/to/imports/stuff.html">
</head>
L'URL d'une importation est appelée emplacement d'importation. Pour charger du contenu à partir d'un autre domaine, l'emplacement d'importation doit être compatible avec CORS:
<!-- Resources on other origins must be CORS-enabled. -->
<link rel="import" href="http://example.com/elements.html">
Détection et compatibilité des fonctionnalités
Pour détecter la compatibilité, vérifiez si .import
existe sur l'élément <link>
:
function supportsImports() {
return 'import' in document.createElement('link');
}
if (supportsImports()) {
// Good to go!
} else {
// Use other libraries/require systems to load files.
}
La compatibilité avec les navigateurs en est encore à ses débuts. Chrome 31 est le premier navigateur à avoir vu une implémentation, mais les autres fournisseurs de navigateurs attendent de voir comment fonctionnent les modules ES. Toutefois, pour les autres navigateurs, le polyfill Webcomponents.js fonctionne très bien jusqu'à ce qu'il soit largement accepté.
Regrouper des ressources
Les importations fournissent une convention permettant de regrouper du code HTML/CSS/JS (même d'autres importations HTML) dans un seul livrable. Il s'agit d'une fonctionnalité intrinsèque, mais puissante. Si vous créez un thème, une bibliothèque ou si vous souhaitez simplement segmenter votre application en blocs logiques, il est judicieux de fournir aux utilisateurs une seule URL. Vous pouvez même fournir une application entière via une importation. Réfléchissez-y une seconde.
Bootstrap en est un exemple concret. Bootstrap se compose de fichiers individuels (bootstrap.css, bootstrap.js, polices), nécessite JQuery pour ses plug-ins et fournit des exemples de balisage. Les développeurs apprécient la flexibilité à la carte. Elle leur permet d'adhérer aux parties du framework qu'ils souhaitent utiliser. Ceci étant dit, j'imagine que votre JoeDeveloperTM habituel téléchargera facilement Bootstrap.
Les importations ont beaucoup de sens pour un élément tel que Bootstrap. Je vous présente l'avenir du chargement de Bootstrap:
<head>
<link rel="import" href="bootstrap.html">
</head>
Les utilisateurs chargent simplement un lien d'importation HTML. Ils n'ont pas besoin de s'occuper de la dispersion des fichiers. Au lieu de cela, l'intégralité de Bootstrap est gérée et encapsulée dans une importation, bootstrap.html:
<link rel="stylesheet" href="bootstrap.css">
<link rel="stylesheet" href="fonts.css">
<script src="jquery.js"></script>
<script src="bootstrap.js"></script>
<script src="bootstrap-tooltip.js"></script>
<script src="bootstrap-dropdown.js"></script>
...
<!-- scaffolding markup -->
<template>
...
</template>
Laissez-moi réfléchir. C'est passionnant.
Événements de chargement/d'erreur
L'élément <link>
déclenche un événement load
lorsqu'une importation est chargée avec succès et onerror
lorsque la tentative échoue (par exemple, si la ressource renvoie une erreur 404).
Les importations tentent de se charger immédiatement. Un moyen simple d'éviter les maux de tête consiste à utiliser les attributs onload
/onerror
:
<script>
function handleLoad(e) {
console.log('Loaded import: ' + e.target.href);
}
function handleError(e) {
console.log('Error loading import: ' + e.target.href);
}
</script>
<link rel="import" href="file.html"
onload="handleLoad(event)" onerror="handleError(event)">
Si vous créez l'importation de manière dynamique:
var link = document.createElement('link');
link.rel = 'import';
// link.setAttribute('async', ''); // make it async!
link.href = 'file.html';
link.onload = function(e) {...};
link.onerror = function(e) {...};
document.head.appendChild(link);
Utiliser le contenu
Inclure une importation sur une page ne signifie pas "placer le contenu de ce fichier ici". Cela signifie "analyseur, extrayez ce document pour pouvoir l'utiliser". Pour utiliser réellement le contenu, vous devez passer à l'action et écrire un script.
Un moment critique de aha!
est de réaliser qu'une importation n'est qu'un document. En fait, le contenu d'une importation est appelé document d'importation. Vous êtes capable de manipuler le fonctionnement d'une importation à l'aide des API DOM standards.
link.import
Pour accéder au contenu d'une importation, utilisez la propriété .import
de l'élément de lien :
var content = document.querySelector('link[rel="import"]').import;
link.import
est null
dans les conditions suivantes :
- Le navigateur ne prend pas en charge les importations HTML.
<link>
ne contient pasrel="import"
.<link>
n'a pas été ajouté au DOM.- Le
<link>
a été supprimé du DOM. - La ressource n'est pas compatible avec CORS.
Exemple complet
Disons que warnings.html
contient :
<div class="warning">
<style>
h3 {
color: red !important;
}
</style>
<h3>Warning!
<p>This page is under construction
</div>
<div class="outdated">
<h3>Heads up!
<p>This content may be out of date
</div>
Les importateurs peuvent récupérer une partie spécifique de ce document et la cloner sur leur page :
<head>
<link rel="import" href="warnings.html">
</head>
<body>
...
<script>
var link = document.querySelector('link[rel="import"]');
var content = link.import;
// Grab DOM from warning.html's document.
var el = content.querySelector('.warning');
document.body.appendChild(el.cloneNode(true));
</script>
</body>
Écrire des scripts dans les importations
Les importations ne figurent pas dans le document principal. Ils en sont satellites. Toutefois, votre importation peut toujours agir sur la page principale, même si le document principal est le plus important. Une importation peut accéder à son propre DOM et/ou au DOM de la page qui l'importe:
Exemple : import.html qui ajoute l'une de ses feuilles de style à la page principale
<link rel="stylesheet" href="http://www.example.com/styles.css">
<link rel="stylesheet" href="http://www.example.com/styles2.css">
<style>
/* Note: <style> in an import apply to the main
document by default. That is, style tags don't need to be
explicitly added to the main document. */
#somecontainer {
color: blue;
}
</style>
...
<script>
// importDoc references this import's document
var importDoc = document.currentScript.ownerDocument;
// mainDoc references the main document (the page that's importing us)
var mainDoc = document;
// Grab the first stylesheet from this import, clone it,
// and append it to the importing document.
var styles = importDoc.querySelector('link[rel="stylesheet"]');
mainDoc.head.appendChild(styles.cloneNode(true));
</script>
Remarquez ce qui se passe ici. Le script de l'importation fait référence au document importé (document.currentScript.ownerDocument
) et ajoute une partie de ce document à la page d'importation (mainDoc.head.appendChild(...)
). Assez complexe, si vous voulez mon avis.
Règles de JavaScript dans une importation :
- Le script de l'importation est exécuté dans le contexte de la fenêtre contenant l'
document
d'importation.window.document
fait donc référence au document de la page principale. Cela a deux corollaires utiles :- définies dans une importation se retrouvent sur
window
. - Vous n'avez pas besoin de faire quoi que ce soit de compliqué, comme ajouter les blocs
<script>
de l'importation à la page principale. Le script est à nouveau exécuté.
- définies dans une importation se retrouvent sur
- Les importations ne bloquent pas l'analyse de la page principale. Toutefois, les scripts qui y sont inclus sont traités dans l'ordre. Cela signifie que vous obtenez un comportement de type report tout en conservant l'ordre correct du script. Vous trouverez plus d'informations à ce sujet ci-dessous.
Diffuser des composants Web
La conception des importations HTML se prête particulièrement bien au chargement de contenu réutilisable sur le Web. En particulier, il s'agit d'un moyen idéal de distribuer des composants Web. Tout, des éléments HTML <template>
de base aux éléments personnalisés complets avec le DOM ombragé [1, 2, 3]. Lorsque ces technologies sont utilisées en tandem, les importations deviennent un #include
pour les composants Web.
Inclure des modèles
L'élément "Modèle HTML" est particulièrement adapté aux importations HTML. <template>
est idéal pour créer des sections de balisage que l'application d'importation peut utiliser à sa guise. Encapsuler du contenu dans une <template>
vous offre également l'avantage de le rendre inerte jusqu'à son utilisation. Autrement dit, les scripts ne s'exécutent pas tant que le modèle n'est pas ajouté au DOM. Magnifique !
import.html
<template>
<h1>Hello World!</h1>
<!-- Img is not requested until the <template> goes live. -->
<img src="world.png">
<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>
Enregistrer des éléments personnalisés
Les éléments personnalisés sont une autre technologie de composants Web qui fonctionne très bien avec les importations HTML. Les importations peuvent exécuter du script. Pourquoi ne pas définir et enregistrer vos éléments personnalisés pour que les utilisateurs n'aient pas à le faire ? Appelez-le..." enregistrement automatique".
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});
</script>
<template id="t">
<style>
::content > * {
color: red;
}
</style>
<span>I'm a shadow-element using Shadow DOM!</span>
<content></content>
</template>
<script>
(function() {
var importDoc = document.currentScript.ownerDocument; // importee
// Define and register <shadow-element>
// that uses Shadow DOM and a template.
var proto2 = Object.create(HTMLElement.prototype);
proto2.createdCallback = function() {
// get template in import
var template = importDoc.querySelector('#t');
// import template into
var clone = document.importNode(template.content, true);
var root = this.createShadowRoot();
root.appendChild(clone);
};
document.registerElement('shadow-element', {prototype: proto2});
})();
</script>
Cette importation définit (et enregistre) deux éléments, <say-hi>
et <shadow-element>
. Le premier montre un élément personnalisé de base qui s'enregistre dans l'importation. Le deuxième exemple montre comment implémenter un élément personnalisé qui crée un Shadow DOM à partir d'un <template>
, puis s'enregistre.
L'avantage d'enregistrer des éléments personnalisés dans une importation HTML est que l'importateur déclare simplement votre élément sur sa page. Aucun câblage n'est requis.
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>
À mon avis, ce workflow seul fait des importations HTML un moyen idéal de partager des composants Web.
Gérer les dépendances et les sous-importations
Sous-importations
Il peut être utile qu'une importation inclue une autre. Par exemple, si vous souhaitez réutiliser ou étendre un autre composant, utilisez une importation pour charger l'autre ou les autres éléments.
Vous trouverez ci-dessous un exemple concret tiré de Polymer. Il s'agit d'un nouveau composant d'onglet (<paper-tabs>
) qui réutilise un composant de mise en page et un composant de sélecteur. Les dépendances sont gérées à l'aide d'importations HTML.
Paper-tabs.html (simplifié):
<link rel="import" href="iron-selector.html">
<link rel="import" href="classes/iron-flex-layout.html">
<dom-module id="paper-tabs">
<template>
<style>...</style>
<iron-selector class="layout horizonta center">
<content select="*"></content>
</iron-selector>
</template>
<script>...</script>
</dom-module>
Les développeurs d'applications peuvent importer ce nouvel élément en utilisant:
<link rel="import" href="paper-tabs.html">
<paper-tabs></paper-tabs>
Si un nouveau <iron-selector2>
plus génial est disponible à l'avenir, vous pourrez remplacer <iron-selector>
et l'utiliser immédiatement. Vous ne risquez pas de perturber vos utilisateurs grâce aux importations et aux composants Web.
Gestion des dépendances
Nous savons tous que charger JQuery plusieurs fois par page entraîne des erreurs. Cela ne va-t-il pas poser un problème énorme pour les composants Web lorsque plusieurs composants utilisent la même bibliothèque ? Pas si vous utilisez des importations HTML. Ils peuvent être utilisés pour gérer les dépendances.
En encapsulant des bibliothèques dans une importation HTML, vous supprimez automatiquement les doublons de ressources. Le document n'est analysé qu'une seule fois. Les scripts ne sont exécutés qu'une seule fois. Par exemple, supposons que vous définissiez une importation, jquery.html, qui charge une copie de JQuery.
jquery.html
<script src="http://cdn.com/jquery.js"></script>
Cette importation peut être réutilisée dans les importations ultérieures comme suit :
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>
Même la page principale peut inclure jquery.html si elle a besoin de la bibliothèque :
<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>
Bien que jquery.html soit inclus dans de nombreux arbres d'importation différents, son document n'est extrait et traité qu'une seule fois par le navigateur. L'examen du panneau "Network" le confirme :
Considérations sur les performances
Les importations HTML sont vraiment géniales, mais comme pour toute nouvelle technologie Web, vous devez les utiliser judicieusement. Les bonnes pratiques en matière de développement Web restent valables. Voici quelques points à prendre en compte.
Concaténer des importations
La réduction des requêtes réseau est toujours importante. Si vous avez de nombreux liens d'importation de premier niveau, envisagez de les regrouper en une seule ressource et d'importer ce fichier.
Vulcanize est un outil de compilation npm de l'équipe Polymer qui aplatit de manière récursive un ensemble d'importations HTML dans un seul fichier. Vous pouvez considérer cette étape comme une étape de compilation de concaténation pour les composants Web.
Les importations utilisent la mise en cache du navigateur
Beaucoup de gens oublient que la pile réseau du navigateur a été affinée au fil des ans. Les importations (et les sous-importations) exploitent également cette logique. L'importation http://cdn.com/bootstrap.html
peut comporter des sous-ressources, mais elles seront mises en cache.
Le contenu n'est utile que lorsque vous l'ajoutez
Considérez le contenu comme inerte jusqu'à ce que vous appeliez ses services. Prenons une feuille de style normale créée dynamiquement :
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';
Le navigateur ne demande pas styles.css tant que link
n'a pas été ajouté au DOM :
document.head.appendChild(link); // browser requests styles.css
Un autre exemple est le balisage créé de manière dynamique :
var h2 = document.createElement('h2');
h2.textContent = 'Booyah!';
Le h2
n'a relativement aucun sens tant que vous ne l'ajoutez pas au DOM.
Le même concept s'applique au document d'importation. Sauf si vous ajoutez son contenu au DOM, il s'agit d'une opération sans effet. En fait, la seule chose qui "s'exécute" directement dans le document d'importation est <script>
. Consultez la section Écrire des scripts dans les importations.
Optimiser le chargement asynchrone
Importations du blocage du rendu
Les importations bloquent l'affichage de la page principale. Cela ressemble à ce que fait <link rel="stylesheet">
. La raison pour laquelle le navigateur bloque le rendu des feuilles de style est de minimiser le FOUC. Les importations se comportent de manière similaire, car elles peuvent contenir des feuilles de style.
Pour être complètement asynchrone et ne pas bloquer l'analyseur ni le rendu, utilisez l'attribut async
:
<link rel="import" href="/path/to/import_that_takes_5secs.html" async>
async
n'est pas le paramètre par défaut pour les importations HTML, car il nécessite plus de travail de la part des développeurs. Par défaut, les importations HTML contenant des définitions d'éléments personnalisés sont chargées et mises à niveau dans l'ordre. Dans un monde complètement asynchrone, les développeurs devraient gérer eux-mêmes cette danse et les délais de mise à niveau.
Vous pouvez également créer une importation asynchrone de manière dynamique :
var l = document.createElement('link');
l.rel = 'import';
l.href = 'elements.html';
l.setAttribute('async', '');
l.onload = function(e) { ... };
Les importations ne bloquent pas l'analyse
Les importations ne bloquent pas l'analyse de la page principale. Les scripts dans les importations sont traités dans l'ordre, mais ne bloquent pas la page d'importation. Cela signifie que vous obtenez un comportement semblable à celui de la mise en attente tout en conservant l'ordre correct des scripts. L'un des avantages de placer vos importations dans <head>
est que l'analyseur peut commencer à travailler sur le contenu dès que possible. Toutefois, il est important de se rappeler que <script>
dans le document principal continue de bloquer la page. Le premier <script>
après une importation bloque le rendu de la page. En effet, une importation peut contenir un script qui doit être exécuté avant le script dans la page principale.
<head>
<link rel="import" href="/path/to/import_that_takes_5secs.html">
<script>console.log('I block page rendering');</script>
</head>
Selon la structure de votre application et votre cas d'utilisation, vous pouvez optimiser le comportement asynchrone de plusieurs façons. Les techniques ci-dessous permettent de limiter le blocage du rendu de la page principale.
Scénario 1 (recommandé): vous n'avez pas de script en <head>
ni intégré en <body>
Je vous recommande de ne pas placer <script>
immédiatement après vos importations. Déplacez les scripts le plus tard possible dans le jeu. Mais vous suivez déjà cette bonne pratique, N'EST-CE PAS ? ;)
Exemple :
<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>
Tout est en bas.
Scénario 1.5 : l'importation s'ajoute automatiquement
Une autre option consiste à laisser l'importation ajouter son propre contenu. Si l'auteur de l'importation établit un contrat que le développeur de l'application doit suivre, l'importation peut s'ajouter à une zone de la page principale :
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>
Scénario 2 : vous avez un script dans <head>
ou intégré dans <body>
Si l'importation prend beaucoup de temps à se charger, le premier <script>
qui la suit sur la page empêche l'affichage de la page. Par exemple, Google Analytics recommande de placer le code de suivi dans le <head>
. Si vous ne pouvez pas éviter de placer <script>
dans le <head>
, l'ajout dynamique de l'importation empêchera le blocage de la page :
<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>
Vous pouvez également ajouter l'importation vers la fin de <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>
Éléments à retenir
Le type MIME d'une importation est
text/html
.Les ressources d'autres origines doivent être compatibles avec le protocole CORS.
Les importations à partir de la même URL sont récupérées et analysées une seule fois. Cela signifie que le script d'une importation n'est exécuté que la première fois qu'il est vu.
Les scripts d'une importation sont traités dans l'ordre, mais ne bloquent pas l'analyse du document principal.
Un lien d'importation ne signifie pas "#inclure le contenu ici". Cela signifie "Analyseur, allez chercher ce document pour que je puisse l'utiliser plus tard". Les scripts s'exécutent au moment de l'importation, mais les feuilles de style, le balisage et d'autres ressources doivent être ajoutés explicitement à la page principale. Notez que les
<style>
n'ont pas besoin d'être ajoutés explicitement. Il s'agit d'une différence majeure entre les importations HTML et<iframe>
, qui indique que vous pouvez charger et afficher ce contenu ici.
Conclusion
Les importations HTML permettent de regrouper les fichiers HTML/CSS/JS en une seule ressource. Bien qu'utile en soi, cette idée devient extrêmement puissante dans le monde des composants Web. Les développeurs peuvent créer des composants réutilisables que d'autres peuvent consommer et intégrer à leur propre application, le tout via <link rel="import">
.
Les importations HTML sont un concept simple, mais elles permettent de nombreux cas d'utilisation intéressants pour la plate-forme.
Cas d'utilisation
- Distribuez les éléments HTML/CSS/JS associés en un seul lot. Théoriquement, vous pouvez importer une application Web entière dans une autre.
- Organisation du code : segmentez les concepts de manière logique dans différents fichiers, ce qui favorise la modularité et la réutilisation**.
- Fournissez une ou plusieurs définitions d'élément personnalisé. Une importation peut être utilisée pour register et inclure ces éléments dans une application. Cette pratique applique de bons modèles logiciels, en séparant l'interface/la définition de l'élément de la façon dont il est utilisé.
- Gérer les dépendances : les ressources sont automatiquement dédupliquées.
- Scripts fragmentés : avant les importations, le fichier d'une bibliothèque JS de grande taille était entièrement analysé pour que son exécution démarre, ce qui était lent. Avec les importations, la bibliothèque peut commencer à fonctionner dès que le fragment A est analysé. Latence réduite !
// TODO: DevSite - Code sample removed as it used inline event handlers
Parallélise l'analyse HTML : il s'agit de la première fois que le navigateur est en mesure d'exécuter plusieurs analyseurs HTML en parallèle.
Permet de basculer entre les modes débogage et non débogage dans une application, simplement en modifiant la cible d'importation elle-même. Votre application n'a pas besoin de savoir si la cible d'importation est une ressource groupée/compilée ou un arbre d'importation.