Includi per il web
Perché le importazioni?
Pensa a come carichi diversi tipi di risorse sul web. Per JS, abbiamo <script src>
. Per il CSS, la scelta migliore è probabilmente <link rel="stylesheet">
. Per le immagini, è <img>
. Il video contiene <video>
. Audio, <audio>
… vai al punto. La maggior parte dei contenuti del web ha un modo semplice e dichiarativo per caricarsi. Non è così per HTML. Ecco le opzioni a tua disposizione:
<iframe>
: affidabile, ma pesante. I contenuti di un iframe si trovano interamente in un contesto separato rispetto alla tua pagina. Sebbene si tratti per lo più di una funzionalità eccezionale, crea ulteriori sfide (ridurre le dimensioni del riquadro ai suoi contenuti è difficile, estremamente frustrante per gli script e quasi impossibile per lo stile).- AJAX: Adoro
xhr.responseType="document"
, ma mi stai dicendo che ho bisogno di JS per caricare HTML? Non mi sembra giusto. - CrazyHacks™: incorporati nelle stringhe, nascosti come commenti (ad es.
<script type="text/html">
).
Capisci l'ironia? I contenuti più di base del web, ovvero l'HTML, richiedono il maggiore impegno. Fortunatamente, i componenti web sono qui per aiutarci a tornare in pista.
Per iniziare
Importazioni HTML, che fanno parte del cast di componenti web, è un modo per includere documenti HTML in altri documenti HTML. Non sei limitato al markup. Un'importazione può includere anche CSS, JavaScript o qualsiasi altro elemento che può essere contenuto in un file .html
. In altre parole, le importazioni sono un fantastico strumento per caricare HTML/CSS/JS correlati.
Nozioni di base
Includi un'importazione nella tua pagina dichiarando un <link rel="import">
:
<head>
<link rel="import" href="/path/to/imports/stuff.html">
</head>
L'URL di un'importazione è chiamato posizione di importazione. Per caricare contenuti da un altro dominio, la posizione di importazione deve essere abilitata per CORS:
<!-- Resources on other origins must be CORS-enabled. -->
<link rel="import" href="http://example.com/elements.html">
Rilevamento e supporto delle funzionalità
Per rilevare il supporto, controlla se .import
esiste nell'elemento <link>
:
function supportsImports() {
return 'import' in document.createElement('link');
}
if (supportsImports()) {
// Good to go!
} else {
// Use other libraries/require systems to load files.
}
Il supporto dei browser è ancora agli inizi. Chrome 31 è stato il primo browser a implementare questa funzionalità, ma altri fornitori di browser stanno aspettando di vedere come si comportano gli ES Modules. Tuttavia, per altri browser il polyfill webcomponents.js funziona perfettamente finché le funzionalità non saranno supportate a livello generale.
Raggruppamento di risorse
Le importazioni forniscono una convenzione per raggruppare HTML/CSS/JS (anche altre importazioni HTML) in un unico deliverable. Si tratta di una funzionalità intrinseca, ma molto potente. Se stai creando un tema, una libreria o vuoi semplicemente segmentare la tua app in blocchi logici, offrire agli utenti un unico URL è un'opzione interessante. Puoi persino pubblicare un'intera app tramite un'importazione. Pensaci un attimo.
Un esempio reale è Bootstrap. Bootstrap è costituito da singoli file (bootstrap.css, bootstrap.js, font), richiede JQuery per i suoi plug-in e fornisce esempi di markup. Gli sviluppatori apprezzano la flessibilità à la carte. Consente loro di adottare le parti del framework che loro vogliono utilizzare. Detto questo, scommetto che il tuo tipico sviluppatore medio™ sceglie la strada più facile e scarica tutto Bootstrap.
Le importazioni sono molto utili per qualcosa come Bootstrap. Ecco il futuro del caricamento di Bootstrap:
<head>
<link rel="import" href="bootstrap.html">
</head>
Gli utenti devono semplicemente caricare un link di importazione HTML. Non devono preoccuparsi di gestire una serie di file. L'intera libreria Bootstrap viene invece gestita e racchiusa in un'importazione, 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>
Lascia perdere. È entusiasmante.
Eventi di caricamento/errore
L'elemento <link>
attiva un evento load
quando un'importazione viene caricata correttamente
e onerror
quando il tentativo non va a buon fine (ad es. se la risorsa restituisce un codice 404).
Le importazioni vengono caricate immediatamente. Un modo semplice per evitare problemi è utilizzare gli attributi 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)">
In alternativa, se crei l'importazione in modo dinamico:
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);
Utilizzo dei contenuti
Includere un'importazione in una pagina non significa "inserire i contenuti del file qui". Significa "parser, vai a recuperare questo documento in modo che io possa utilizzarlo". Per utilizzare effettivamente i contenuti, devi intervenire e scrivere lo script.
Un momento aha!
critico è capire che un'importazione è solo un documento. Infatti, i contenuti di un'importazione sono chiamati documento di importazione. Puoi manipolare i componenti interni di un'importazione utilizzando le API DOM standard.
link.import
Per accedere ai contenuti di un'importazione, utilizza la proprietà .import
dell'elemento link:
var content = document.querySelector('link[rel="import"]').import;
link.import
è null
alle seguenti condizioni:
- Il browser non supporta le importazioni HTML.
<link>
non harel="import"
.<link>
non è stato aggiunto al DOM.- L'elemento
<link>
è stato rimosso dal DOM. - La risorsa non è abilitata per CORS.
Esempio completo
Supponiamo che warnings.html
contenga:
<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>
Gli importatori possono acquisire una parte specifica di questo documento e clonarla nella loro pagina:
<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>
Scripting nelle importazioni
Le importazioni non sono nel documento principale. Sono satelliti di questo. Tuttavia, l'importazione può comunque agire sulla pagina principale anche se il documento principale è preminente. Un'importazione può accedere al proprio DOM e/o al DOM della pagina che la sta importando:
Esempio: import.html che aggiunge uno dei suoi fogli di stile alla pagina 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>
Noti cosa succede qui? Lo script all'interno dell'importazione fa riferimento al documento importato (document.currentScript.ownerDocument
) e aggiunge parte di questo documento alla pagina di importazione (mainDoc.head.appendChild(...)
). Se mi chiedi, è piuttosto complicato.
Regole di JavaScript in un'importazione:
- Lo script nell'importazione viene eseguito nel contesto della finestra contenente
document
in fase di importazione. Pertanto,window.document
si riferisce al documento della pagina principale. Questo ha due corollari utili:- Le funzioni definite in un'importazione vengono inserite in
window
. - Non devi fare nulla di complicato, ad esempio aggiungere i blocchi
<script>
dell'importazione alla pagina principale. Anche in questo caso, lo script viene eseguito.
- Le funzioni definite in un'importazione vengono inserite in
- Le importazioni non bloccano l'analisi della pagina principale. Tuttavia, gli script al loro interno vengono elaborati in ordine. Ciò significa che ottieni un comportamento simile a quello di defer mantenendo l'ordine corretto degli script. Scopri di più di seguito.
Pubblicazione di componenti web
Il design di HTML Import si presta bene al caricamento di contenuti riutilizzabili sul web. In particolare, è un modo ideale per distribuire i componenti web. Tutto, dagli elementi HTML <template>
di base agli elementi personalizzati completi con Shadow DOM [1, 2, 3]. Quando queste tecnologie vengono utilizzate in tandem, le importazioni diventano un #include
per i componenti web.
Inclusione di modelli
L'elemento Modello HTML è ideale per le importazioni HTML. <template>
è ideale per creare la struttura di sezioni di markup da utilizzare a piacere dall'app di importazione. L'inserimento dei contenuti in un <template>
offre anche il vantaggio aggiuntivo di renderli inattivi finché non vengono utilizzati. In altre parole, gli script non vengono eseguiti finché il modello non viene aggiunto al DOM. Super!
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>
Registrazione di elementi personalizzati
Custom Elements è un'altra tecnologia Web Components che funziona benissimo con HTML Imports. Le importazioni possono eseguire script, quindi perché non definire e registrare gli elementi personalizzati in modo che gli utenti non debbano farlo? Chiamalo…"registrazione automatica".
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>
Questa importazione definisce (e registra) due elementi, <say-hi>
e <shadow-element>
. Il primo mostra un elemento personalizzato di base che si registra all'interno dell'importazione. Il secondo esempio mostra come implementare un elemento personalizzato che crea shadow DOM da un <template>
e poi si registra.
Il vantaggio principale della registrazione di elementi personalizzati all'interno di un'importazione HTML è che l'importatore dichiara semplicemente l'elemento nella propria pagina. Non è necessario alcun cablaggio.
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>
A mio avviso, questo flusso di lavoro da solo rende HTML Imports un modo ideale per condividere componenti web.
Gestione di dipendenze e importazioni secondarie
Importazioni secondarie
Può essere utile che un'importazione includa un'altra. Ad esempio, se vuoi riutilizzare o estendere un altro componente, utilizza un'importazione per caricare gli altri elementi.
Di seguito è riportato un esempio reale di Polymer. Si tratta di un nuovo componente della scheda (<paper-tabs>
) che riutilizza un componente di layout e un componente di selettore. Le dipendenze vengono gestite utilizzando le importazioni HTML.
paper-tabs.html (semplificato):
<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>
Gli sviluppatori di app possono importare questo nuovo elemento utilizzando:
<link rel="import" href="paper-tabs.html">
<paper-tabs></paper-tabs>
Quando in futuro verrà lanciato un nuovo <iron-selector2>
ancora più fantastico, potrai sostituire <iron-selector>
e iniziare a utilizzarlo immediatamente. Grazie alle importazioni e ai componenti web, non causerai interruzioni per gli utenti.
Gestione delle dipendenze
Sappiamo tutti che il caricamento di JQuery più di una volta per pagina causa errori. Non sarà un enorme problema per i componenti web quando più componenti utilizzano la stessa libreria? Non se utilizziamo le importazioni HTML. Possono essere utilizzati per gestire le dipendenze.
Se inserisci le librerie in un'importazione HTML, elimini automaticamente le risorse duplicate. Il documento viene analizzato una sola volta. Gli script vengono eseguiti una sola volta. Ad esempio, supponiamo di definire un'importazione, jquery.html, che carica una copia di JQuery.
jquery.html
<script src="http://cdn.com/jquery.js"></script>
Questa importazione può essere riutilizzata nelle importazioni successive nel seguente modo:
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>
Anche la pagina principale stessa può includere jquery.html se ha bisogno della libreria:
<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>
Sebbene jquery.html sia incluso in molti alberi di importazione diversi, il documento viene recuperato ed elaborato una sola volta dal browser. L'esame del riquadro della rete lo dimostra:

Considerazioni sulle prestazioni
Le importazioni HTML sono fantastiche, ma come per qualsiasi nuova tecnologia web, devi usarle con saggezza. Le best practice per lo sviluppo web rimangono valide. Di seguito sono riportati alcuni aspetti da tenere presente.
Importazioni concatenate
Ridurre le richieste di rete è sempre importante. Se hai molti link di importazione di primo livello, ti consigliamo di combinarli in un'unica risorsa e di importare il file.
Vulcanize è uno strumento di compilazione npm del team di Polymer che appiattisce in modo ricorsivo un insieme di importazioni HTML in un unico file. Puoi considerarlo un passaggio di compilazione di concatenazione per i componenti web.
Le importazioni sfruttano la memorizzazione nella cache del browser
Molte persone dimenticano che lo stack di rete del browser è stato ottimizzato nel corso degli anni. Anche le importazioni (e le sottoimportazioni) sfruttano questa logica. L'importazione di http://cdn.com/bootstrap.html
potrebbe avere risorse secondarie, ma verranno memorizzate nella cache.
I contenuti sono utili solo se li aggiungi
Pensa ai contenuti come inerti finché non richiedi i relativi servizi. Prendi un normale foglio di stile creato dinamicamente:
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';
Il browser non richiederà styles.css finché link
non viene aggiunto al DOM:
document.head.appendChild(link); // browser requests styles.css
Un altro esempio è il markup creato dinamicamente:
var h2 = document.createElement('h2');
h2.textContent = 'Booyah!';
h2
non ha molto significato finché non lo aggiungi al DOM.
Lo stesso vale per il documento di importazione. A meno che non aggiunga i relativi contenuti al DOM, non viene eseguita alcuna operazione. Infatti, l'unica cosa che viene "eseguita" direttamente nel documento di importazione è <script>
. Consulta la sezione relativa agli script nelle importazioni.
Ottimizzazione per il caricamento asincrono
Le importazioni bloccano il rendering
Le importazioni bloccano il rendering della pagina principale. Questo è simile a ciò che fa <link rel="stylesheet">
. Il motivo per cui il browser blocca il rendering degli stili è ridurre al minimo il FOUC. Le importazioni si comportano in modo simile perché possono contenere stili.
Per essere completamente asincrona e non bloccare il parser o il rendering, utilizza l'attributo async
:
<link rel="import" href="/path/to/import_that_takes_5secs.html" async>
Il motivo per cui async
non è il valore predefinito per le importazioni HTML è che richiede agli sviluppatori di lavorare di più. Se l'opzione è impostata su Sincrona per impostazione predefinita, il caricamento e l'upgrade delle importazioni HTML contenenti definizioni di elementi personalizzati sono garantiti in ordine. In un mondo completamente asincrono, gli sviluppatori dovrebbero gestire autonomamente la danza e i tempi di upgrade.
Puoi anche creare un'importazione asincrona in modo dinamico:
var l = document.createElement('link');
l.rel = 'import';
l.href = 'elements.html';
l.setAttribute('async', '');
l.onload = function(e) { ... };
Le importazioni non bloccano l'analisi
Le importazioni non bloccano l'analisi della pagina principale. Gli script all'interno delle importazioni vengono elaborati in ordine, ma non bloccano la pagina di importazione. Ciò significa che ottieni un comportamento simile a quello di defer mantenendo l'ordine corretto degli script. Uno dei vantaggi di inserire le importazioni in <head>
è che consente all'analizzatore sintattico di iniziare a lavorare sui contenuti il prima possibile. Detto questo, è fondamentale ricordare che <script>
nel documento principale continua a bloccare la pagina. Il primo <script>
dopo un'importazione bloccherà il rendering della pagina. Questo perché un'importazione può contenere uno script che deve essere eseguito prima dello script nella pagina principale.
<head>
<link rel="import" href="/path/to/import_that_takes_5secs.html">
<script>console.log('I block page rendering');</script>
</head>
A seconda della struttura e del caso d'uso dell'app, esistono diversi modi per ottimizzare il comportamento asincrono. Le tecniche riportate di seguito riducono il blocco del rendering della pagina principale.
Scenario 1 (opzione preferita): non hai script in <head>
o incorporato in <body>
Il mio consiglio per il posizionamento di <script>
è di evitare di farlo subito dopo le importazioni. Sposta gli script il più tardi possibile nel gioco… ma stai già seguendo questa best practice, vero? ;)
Ecco un esempio:
<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>
Tutto è in basso.
Scenario 1.5: l'importazione si aggiunge da sola
Un'altra opzione è consentire all'importazione di aggiungere i propri contenuti. Se l'autore dell'importazione stabilisce un contratto da seguire per lo sviluppatore dell'app, l'importazione può aggiungersi a un'area della pagina 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>
Scenario 2: hai uno script in <head>
o incorporato in <body>
Se un'importazione richiede molto tempo per il caricamento, il primo <script>
che segue nella pagina ne bloccherà il rendering. Google Analytics, ad esempio, consiglia di inserire il codice di monitoraggio in <head>
. Se non puoi evitare di inserire <script>
in <head>
, l'aggiunta dinamica dell'importazione impedirà il blocco della pagina:
<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>
In alternativa, aggiungi l'importazione verso la fine del file <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>
Fattori da tenere in considerazione
Il tipo MIME di un'importazione è
text/html
.Le risorse di altre origini devono essere abilitate per CORS.
Le importazioni dallo stesso URL vengono recuperate e analizzate una sola volta. Ciò significa che lo script in un'importazione viene eseguito solo la prima volta che viene visualizzata l'importazione.
Gli script in un'importazione vengono elaborati in ordine, ma non bloccano l'analisi del documento principale.
Un link di importazione non significa "#include i contenuti qui". Significa "parser, vai a recuperare questo documento in modo che io possa utilizzarlo in un secondo momento". Sebbene gli script vengano eseguiti al momento dell'importazione, gli stili, il markup e altre risorse devono essere aggiunti esplicitamente alla pagina principale. Tieni presente che
<style>
non deve essere aggiunto esplicitamente. Questa è una differenza fondamentale tra le importazioni HTML e<iframe>
, che indica "carica e visualizza qui questi contenuti".
Conclusione
Le importazioni HTML consentono di raggruppare HTML/CSS/JS come una singola risorsa. Sebbene sia utile da sola, questa idea diventa estremamente potente nel mondo dei componenti web. Gli sviluppatori possono creare componenti riutilizzabili per altri utenti e importarli nella propria app, il tutto tramite <link rel="import">
.
Le importazioni HTML sono un concetto semplice, ma consentono una serie di casi d'uso interessanti per la piattaforma.
Casi d'uso
- Distribuisci i file HTML/CSS/JS correlati come un unico bundle. In teoria, potresti importare un'intera app web in un'altra.
- Organizzazione del codice: suddividi i concetti in modo logico in file diversi, incoraggiando la modularità e la riutilizzabilità**.
- Pubblica una o più definizioni di Elemento personalizzato. Un'importazione può essere utilizzata per register gli elementi e includerli in un'app. Questa è una buona pratica per i pattern di software, in quanto mantiene separata l'interfaccia/definizione dell'elemento dal modo in cui viene utilizzato.
- Gestisci dipendenze: le risorse vengono deduplicate automaticamente.
- Script in blocchi: prima delle importazioni, il file di una libreria JS di grandi dimensioni veniva analizzato completamente per poter iniziare a funzionare, il che era lento. Con le importazioni, la libreria può iniziare a funzionare non appena viene analizzato il chunk A. Meno latenza.
// TODO: DevSite - Code sample removed as it used inline event handlers
Esegue in parallelo l'analisi HTML: è la prima volta che il browser è in grado di eseguire due (o più) parser HTML in parallelo.
Consente di passare dalla modalità di debug a quella non di debug in un'app semplicemente modificando il target di importazione stesso. L'app non deve sapere se la destinazione dell'importazione è una risorsa in bundle/compilata o una struttura ad albero di importazione.