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 si carica tramite un modo semplice e dichiarativo. Ciò non avviene nel caso del formato 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. Anche se questa è per lo più una caratteristica eccezionale, crea ulteriori sfide (la riduzione delle dimensioni del fotogramma per i contenuti è complicata, estremamente frustrante da inserire/rimuovere, quasi impossibile da applicare).- 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, ciò rende le importazioni uno strumento fantastico per caricare i relativi file HTML/CSS/JS.
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 i contenuti da un altro dominio, il percorso di importazione deve essere abilitato 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 caricano semplicemente un link HTML Import. Non devono preoccuparsi di gestire una serie di file. Al contrario, Bootstrap viene gestito nella sua interezza e racchiusa in un file di 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
L'inclusione di un'importazione in una pagina non significa che devi inserire qui i contenuti di quel file. 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. In realtà, i contenuti di un'importazione sono chiamati documenti di importazione. Puoi manipolare i componenti 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 satellitari. Tuttavia, l'importazione può essere eseguita nella pagina principale anche se quest'ultimo ha la priorità. 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 quel 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 difficile come 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 delle importazioni HTML si presta perfettamente al caricamento di contenuti riutilizzabili sul web. In particolare, è un modo ideale per distribuire i componenti web. Qualsiasi cosa, dai <template>
HTML 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 si adatta perfettamente alle 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 i tuoi 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>
Ritengo che questo flusso di lavoro da solo rende le importazioni HTML un modo ideale per condividere i componenti web.
Gestione di dipendenze e importazioni secondarie
Importazioni secondarie
Può essere utile includere un'importazione in 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 tratto da 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>
In futuro, quando sarà disponibile un nuovo <iron-selector2>
più fantastico, potrai sostituire <iron-selector>
e iniziare a utilizzarlo immediatamente. Grazie alle importazioni e ai componenti web, non avrai interruzioni per i tuoi 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 panel della rete dimostra che:
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 disponi di molti link di importazione di primo livello, valuta la possibilità di combinarli in un'unica risorsa e di importare il file.
Vulcanize è uno strumento di creazione npm del team di Polymer che riunisce in modo ricorsivo un insieme di importazioni HTML in un unico file. È una sorta di passaggio di build di concatenazione per i componenti web.
Le importazioni sfruttano la memorizzazione nella cache del browser
Molte persone dimenticano che lo stack di networking del browser è stato perfezionato nel corso degli anni. Anche le importazioni (e le sottoimportazioni) sfruttano questa logica. L'importazione http://cdn.com/bootstrap.html
potrebbe avere risorse secondarie, ma queste verranno memorizzate nella cache.
I contenuti sono utili solo se li aggiungi
Pensa ai contenuti come inerti finché non richiedi i relativi servizi. Utilizza un foglio di stile normale creato dinamicamente:
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';
Il browser non richiederà stili.css finché link
non verrà 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
è relativamente privo di 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 fogli di stile.
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 svolgere più lavoro. Se l'opzione è impostata su Sincrona per impostazione predefinita, il caricamento e l'upgrade delle importazioni HTML che contengono 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. Un vantaggio dell'inserimento delle importazioni in <head>
è che consente all'analizzatore sintattico di iniziare a lavorare ai contenuti il prima possibile. Detto questo, è fondamentale ricordare che <script>
nel documento principale ancora 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à adottando la best practice, NON TE!? ;)
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 è fare in modo che l'importazione aggiunga 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. Ad esempio, Google Analytics 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 provenienti da 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 singola risorsa. Sebbene sia utile di per sé, questa idea diventa estremamente potente nel mondo dei componenti web. Gli sviluppatori possono creare componenti riutilizzabili da utilizzare e importare 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 registrare 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 chunk: prima delle importazioni, una libreria JS di grandi dimensioni doveva analizzare interamente il file per poter essere eseguito, operazione lenta. Con le importazioni, la libreria può iniziare a funzionare non appena il blocco A viene analizzato. 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.