@webcomponents/html-imports
Version:
909 lines (851 loc) • 31.2 kB
JavaScript
/**
* @license
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
((scope) => {
/********************* base setup *********************/
const link = document.createElement('link');
const useNative = Boolean('import' in link);
const emptyNodeList = link.querySelectorAll('*');
// Polyfill `currentScript` for browsers without it.
let currentScript = null;
if ('currentScript' in document === false) {
Object.defineProperty(document, 'currentScript', {
get() {
return (
currentScript ||
// NOTE: only works when called in synchronously executing code.
// readyState should check if `loading` but IE10 is
// interactive when scripts run so we cheat. This is not needed by
// html-imports polyfill but helps generally polyfill `currentScript`.
(document.readyState !== 'complete'
? document.scripts[document.scripts.length - 1]
: null)
);
},
configurable: true,
});
}
/**
* @param {Array|NodeList|NamedNodeMap} list
* @param {!Function} callback
* @param {boolean=} inverseOrder
*/
const forEach = (list, callback, inverseOrder) => {
const length = list ? list.length : 0;
const increment = inverseOrder ? -1 : 1;
let i = inverseOrder ? length - 1 : 0;
for (; i < length && i >= 0; i = i + increment) {
callback(list[i], i);
}
};
/**
* @param {!Node} node
* @param {!string} selector
* @return {!NodeList<!Element>}
*/
const QSA = (node, selector) => {
// IE 11 throws a SyntaxError if a node with no children is queried with
// a selector containing the `:not([type])` syntax.
if (!node.childNodes.length) {
return emptyNodeList;
}
return node.querySelectorAll(selector);
};
/**
* @param {!DocumentFragment} fragment
*/
const replaceScripts = (fragment) => {
forEach(QSA(fragment, 'template'), (template) => {
forEach(QSA(template.content, scriptsSelector), (script) => {
const clone =
/** @type {!HTMLScriptElement} */
(document.createElement('script'));
forEach(script.attributes, (attr) =>
clone.setAttribute(attr.name, attr.value)
);
clone.textContent = script.textContent;
script.parentNode.replaceChild(clone, script);
});
replaceScripts(template.content);
});
};
/********************* path fixup *********************/
const CSS_URL_REGEXP = /(url\()([^)]*)(\))/g;
const CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g;
const STYLESHEET_REGEXP = /(<link[^>]*)(rel=['|"]?stylesheet['|"]?[^>]*>)/g;
// path fixup: style elements in imports must be made relative to the main
// document. We fixup url's in url() and @import.
const Path = {
fixUrls(element, base) {
if (element.href) {
element.setAttribute(
'href',
Path.resolveUrl(element.getAttribute('href'), base)
);
}
if (element.src) {
element.setAttribute(
'src',
Path.resolveUrl(element.getAttribute('src'), base)
);
}
if (element.localName === 'style') {
const r = Path.replaceUrls(element.textContent, base, CSS_URL_REGEXP);
element.textContent = Path.replaceUrls(r, base, CSS_IMPORT_REGEXP);
}
},
replaceUrls(text, linkUrl, regexp) {
return text.replace(regexp, (m, pre, url, post) => {
let urlPath = url.replace(/["']/g, '');
if (linkUrl) {
urlPath = Path.resolveUrl(urlPath, linkUrl);
}
return pre + "'" + urlPath + "'" + post;
});
},
resolveUrl(url, base) {
// Lazy feature detection.
if (Path.__workingURL === undefined) {
Path.__workingURL = false;
try {
const u = new URL('b', 'http://a');
u.pathname = 'c%20d';
Path.__workingURL = u.href === 'http://a/c%20d';
} catch (e) {} // eslint-disable-line no-empty
}
if (Path.__workingURL) {
return new URL(url, base).href;
}
// Fallback to creating an anchor into a disconnected document.
let doc = Path.__tempDoc;
if (!doc) {
doc = document.implementation.createHTMLDocument('temp');
Path.__tempDoc = doc;
doc.__base = doc.createElement('base');
doc.head.appendChild(doc.__base);
doc.__anchor = doc.createElement('a');
}
doc.__base.href = base;
doc.__anchor.href = url;
return doc.__anchor.href || url;
},
};
/********************* Xhr processor *********************/
const Xhr = {
async: true,
/**
* @param {!string} url
* @param {!function(!string, string=)} success
* @param {!function(!string)} fail
*/
load(url, success, fail) {
if (!url) {
fail('error: href must be specified');
} else if (url.match(/^data:/)) {
// Handle Data URI Scheme
const pieces = url.split(',');
const header = pieces[0];
let resource = pieces[1];
if (header.indexOf(';base64') > -1) {
resource = atob(resource);
} else {
resource = decodeURIComponent(resource);
}
success(resource);
} else {
const request = new XMLHttpRequest();
request.open('GET', url, Xhr.async);
request.onload = () => {
// Servers redirecting an import can add a Location header to help us
// polyfill correctly. Handle relative and full paths.
// Prefer responseURL which already resolves redirects
// https://xhr.spec.whatwg.org/#the-responseurl-attribute
let redirectedUrl =
(request.responseURL || request.getResponseHeader('Location')) ??
undefined;
if (redirectedUrl && redirectedUrl.indexOf('/') === 0) {
// In IE location.origin might not work
// https://connect.microsoft.com/IE/feedback/details/1763802/location-origin-is-undefined-in-ie-11-on-windows-10-but-works-on-windows-7
const origin =
location.origin || location.protocol + '//' + location.host;
redirectedUrl = origin + redirectedUrl;
}
const resource = /** @type {string} */ (request.response ||
request.responseText);
if (
request.status === 304 ||
request.status === 0 ||
(request.status >= 200 && request.status < 300)
) {
success(resource, redirectedUrl);
} else {
fail(resource);
}
};
request.send();
}
},
};
/********************* importer *********************/
const isIE = /Trident/.test(navigator.userAgent);
const isEdge = /Edge\/\d./i.test(navigator.userAgent);
const importSelector = 'link[rel=import]';
// Used to disable loading of resources.
const importDisableType = 'import-disable';
const disabledLinkSelector = `link[rel=stylesheet][href][type=${importDisableType}]`;
const scriptsSelector =
`script:not([type]),script[type="application/javascript"],` +
`script[type="text/javascript"],script[type="module"]`;
const importDependenciesSelector =
`${importSelector},${disabledLinkSelector},` +
`style:not([type]),link[rel=stylesheet][href]:not([type]),` +
scriptsSelector;
const importDependencyAttr = 'import-dependency';
const rootImportSelector = `${importSelector}:not([${importDependencyAttr}])`;
const pendingScriptsSelector = `script[${importDependencyAttr}]`;
const pendingStylesSelector =
`style[${importDependencyAttr}],` +
`link[rel=stylesheet][${importDependencyAttr}]`;
/**
* Importer will:
* - load any linked import documents (with deduping)
* - whenever an import is loaded, prompt the parser to try to parse
* - observe imported documents for new elements (these are handled via the
* dynamic importer)
*/
class Importer {
constructor() {
this.documents = {};
// Used to keep track of pending loads, so that flattening and firing of
// events can be done when all resources are ready.
this.inflight = 0;
this.dynamicImportsMO = new MutationObserver((m) =>
this.handleMutations(m)
);
// Observe changes on <head>.
this.dynamicImportsMO.observe(document.head, {
childList: true,
subtree: true,
});
// 1. Load imports contents
// 2. Assign them to first import links on the document
// 3. Wait for import styles & scripts to be done loading/running
// 4. Fire load/error events
this.loadImports(document);
}
/**
* @param {!(HTMLDocument|DocumentFragment|Element)} doc
*/
loadImports(doc) {
const links =
/** @type {!NodeList<!HTMLLinkElement>} */
(QSA(doc, importSelector));
forEach(links, (link) => this.loadImport(link));
}
/**
* @param {!HTMLLinkElement} link
*/
loadImport(link) {
const url = link.href;
// This resource is already being handled by another import.
if (this.documents[url] !== undefined) {
// If import is already loaded, we can safely associate it to the link
// and fire the load/error event.
const imp = this.documents[url];
if (imp && imp['__loaded']) {
link['__import'] = imp;
this.fireEventIfNeeded(link);
}
return;
}
this.inflight++;
// Mark it as pending to notify others this url is being loaded.
this.documents[url] = 'pending';
Xhr.load(
url,
(resource, redirectedUrl) => {
const doc = this.makeDocument(resource, redirectedUrl || url);
this.documents[url] = doc;
this.inflight--;
// Load subtree.
this.loadImports(doc);
this.processImportsIfLoadingDone();
},
() => {
// If load fails, handle error.
this.documents[url] = null;
this.inflight--;
this.processImportsIfLoadingDone();
}
);
}
/**
* Creates a new document containing resource and normalizes urls accordingly.
* @param {string=} resource
* @param {string=} url
* @return {!DocumentFragment}
*/
makeDocument(resource, url) {
if (!resource) {
return document.createDocumentFragment();
}
if (isIE || isEdge) {
// <link rel=stylesheet> should be appended to <head>. Not doing so
// in IE/Edge breaks the cascading order. We disable the loading by
// setting the type before setting innerHTML to avoid loading
// resources twice.
resource = resource.replace(STYLESHEET_REGEXP, (match, p1, p2) => {
if (match.indexOf('type=') === -1) {
return `${p1} type=${importDisableType} ${p2}`;
}
return match;
});
}
let content;
const template =
/** @type {!HTMLTemplateElement} */
(document.createElement('template'));
template.innerHTML = resource;
if (template.content) {
content = template.content;
// Clone scripts inside templates since they won't execute when the
// hosting template is cloned.
replaceScripts(content);
} else {
// <template> not supported, create fragment and move content into it.
content = document.createDocumentFragment();
while (template.firstChild) {
content.appendChild(template.firstChild);
}
}
// Support <base> in imported docs. Resolve url and remove its href.
const baseEl = content.querySelector('base');
if (baseEl) {
url = Path.resolveUrl(baseEl.getAttribute('href'), url);
baseEl.removeAttribute('href');
}
const n$ =
/** @type {!NodeList<!(HTMLLinkElement|HTMLScriptElement|HTMLStyleElement)>} */
(QSA(content, importDependenciesSelector));
// For source map hints.
let inlineScriptIndex = 0;
forEach(n$, (n) => {
whenElementLoaded(n);
Path.fixUrls(n, url);
if (n.localName === 'style' && this.styleNeedsCloning(n)) {
const clone = this.cloneStyle(n);
whenElementLoaded(clone);
n.parentNode.replaceChild(clone, n);
n = clone;
}
// Mark for easier selectors.
n.setAttribute(importDependencyAttr, '');
// Generate source map hints for inline scripts.
if (n.localName === 'script' && !n.src && n.textContent) {
if (n.type === 'module') {
throw new Error(
'Inline module scripts are not supported in HTML Imports.'
);
}
const num = inlineScriptIndex ? `-${inlineScriptIndex}` : '';
const content = n.textContent + `\n//# sourceURL=${url}${num}.js\n`;
// We use the src attribute so it triggers load/error events, and it's
// easier to capture errors (e.g. parsing) like this.
n.setAttribute(
'src',
'data:text/javascript;charset=utf-8,' + encodeURIComponent(content)
);
n.textContent = '';
inlineScriptIndex++;
}
});
return content;
}
/**
* @param {!HTMLStyleElement} style
* @return {boolean}
*/
styleNeedsCloning(style) {
return isEdge && style.textContent.indexOf('@import') > -1;
}
/**
* In Edge, styles with `@import` will not load if the text content is
* modified, or the style is moved.
*
* We must clone the style, but not with `cloneNode()` as that carries the
* implicit "loaded" state somehow.
* @param {!HTMLStyleElement} style
* @return {!HTMLStyleElement}
*/
cloneStyle(style) {
const clone = /** @type {!HTMLStyleElement} */ (style.ownerDocument.createElement(
'style'
));
clone.textContent = style.textContent;
forEach(style.attributes, (attr) =>
clone.setAttribute(attr.name, attr.value)
);
return clone;
}
/**
* Waits for loaded imports to finish loading scripts and styles, then fires
* the load/error events.
*/
processImportsIfLoadingDone() {
// Wait until all resources are ready, then load import resources.
if (this.inflight) {
return;
}
// Stop observing, flatten & load resource, then restart observing <head>.
this.dynamicImportsMO.disconnect();
this.flatten(document);
// We wait for styles to load, and at the same time we execute the scripts,
// then fire the load/error events for imports to have faster whenReady
// callback execution.
// NOTE: This is different for native behavior where scripts would be
// executed after the styles before them are loaded.
// To achieve that, we could select pending styles and scripts in the
// document and execute them sequentially in their dom order.
let scriptsOk = false,
stylesOk = false;
const onLoadingDone = () => {
if (stylesOk && scriptsOk) {
// Catch any imports that might have been added while we
// weren't looking, wait for them as well.
this.loadImports(document);
if (this.inflight) {
return;
}
// Restart observing.
this.dynamicImportsMO.observe(document.head, {
childList: true,
subtree: true,
});
this.fireEvents();
}
};
this.waitForStyles(() => {
stylesOk = true;
onLoadingDone();
});
this.runScripts(() => {
scriptsOk = true;
onLoadingDone();
});
}
/**
* @param {!HTMLDocument} doc
*/
flatten(doc) {
const n$ =
/** @type {!NodeList<!HTMLLinkElement>} */
(QSA(doc, importSelector));
forEach(n$, (n) => {
const imp = this.documents[n.href];
n['__import'] = /** @type {!Document} */ (imp);
if (imp && imp.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
// We set the .import to be the link itself, and update its readyState.
// Other links with the same href will point to this link.
this.documents[n.href] = n;
n.readyState = 'loading';
n['__import'] = n;
this.flatten(imp);
n.appendChild(imp);
}
});
}
/**
* Replaces all the imported scripts with a clone in order to execute them.
* Updates the `currentScript`.
* @param {!function()} callback
*/
runScripts(callback) {
const s$ = QSA(document, pendingScriptsSelector);
const l = s$.length;
const cloneScript = (i) => {
if (i < l) {
// The pending scripts have been generated through innerHTML and
// browsers won't execute them for security reasons. We cannot use
// s.cloneNode(true) either, the only way to run the script is manually
// creating a new element and copying its attributes.
const s = s$[i];
const clone =
/** @type {!HTMLScriptElement} */
(document.createElement('script'));
// Remove import-dependency attribute to avoid double cloning.
s.removeAttribute(importDependencyAttr);
forEach(s.attributes, (attr) =>
clone.setAttribute(attr.name, attr.value)
);
// Update currentScript and replace original with clone script.
currentScript = clone;
s.parentNode.replaceChild(clone, s);
whenElementLoaded(clone, () => {
currentScript = null;
cloneScript(i + 1);
});
} else {
callback();
}
};
cloneScript(0);
}
/**
* Waits for all the imported stylesheets/styles to be loaded.
* @param {!function()} callback
*/
waitForStyles(callback) {
const s$ =
/** @type {!NodeList<!(HTMLLinkElement|HTMLStyleElement)>} */
(QSA(document, pendingStylesSelector));
let pending = s$.length;
if (!pending) {
callback();
return;
}
// <link rel=stylesheet> should be appended to <head>. Not doing so
// in IE/Edge breaks the cascading order
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10472273/
// If there is one <link rel=stylesheet> imported, we must move all imported
// links and styles to <head>.
const needsMove =
(isIE || isEdge) && !!document.querySelector(disabledLinkSelector);
forEach(s$, (s) => {
// Listen for load/error events, remove selector once is done loading.
if (
needsMove &&
this.styleNeedsCloning(s) &&
s.ownerDocument.defaultView !== window.top
) {
// If a style is moved in Edge with an `@import` and there's a <link> in the page,
// and the style is in an iframe, the style won't fire a load event.
s['__loaded'] = true;
}
whenElementLoaded(s, () => {
s.removeAttribute(importDependencyAttr);
if (--pending === 0) {
callback();
}
});
// Check if was already moved to head, to handle the case where the element
// has already been moved but it is still loading.
if (needsMove && s.parentNode !== document.head) {
// Replace the element we're about to move with a placeholder.
const placeholder = document.createElement(s.localName);
// Add reference of the moved element.
placeholder['__appliedElement'] = s;
// Disable this from appearing in document.styleSheets.
placeholder.setAttribute('type', 'import-placeholder');
// Append placeholder next to the sibling, and move original to <head>.
s.parentNode.insertBefore(placeholder, s.nextSibling);
let newSibling = importForElement(s);
while (newSibling && importForElement(newSibling)) {
newSibling = importForElement(newSibling);
}
if (newSibling.parentNode !== document.head) {
newSibling = null;
}
document.head.insertBefore(s, newSibling);
// Enable the loading of <link rel=stylesheet>.
s.removeAttribute('type');
}
});
}
/**
* Fires load/error events for imports in the right order .
*/
fireEvents() {
const n$ =
/** @type {!NodeList<!HTMLLinkElement>} */
(QSA(document, importSelector));
// Inverse order to have events firing bottom-up.
forEach(n$, (n) => this.fireEventIfNeeded(n), true);
}
/**
* Fires load/error event for the import if this wasn't done already.
* @param {!HTMLLinkElement} link
*/
fireEventIfNeeded(link) {
// Don't fire twice same event.
if (!link['__loaded']) {
link['__loaded'] = true;
// Update link's import readyState.
link.import && (link.import.readyState = 'complete');
const eventType = link.import ? 'load' : 'error';
link.dispatchEvent(
newCustomEvent(eventType, {
bubbles: false,
cancelable: false,
detail: undefined,
})
);
}
}
/**
* @param {Array<MutationRecord>} mutations
*/
handleMutations(mutations) {
forEach(mutations, (m) =>
forEach(m.addedNodes, (elem) => {
if (elem && elem.nodeType === Node.ELEMENT_NODE) {
// NOTE: added scripts are not updating currentScript in IE.
if (isImportLink(elem)) {
this.loadImport(/** @type {!HTMLLinkElement} */ (elem));
} else {
this.loadImports(/** @type {!Element} */ (elem));
}
}
})
);
}
}
/**
* @param {!Node} node
* @return {boolean}
*/
const isImportLink = (node) => {
return (
node.nodeType === Node.ELEMENT_NODE &&
node.localName === 'link' &&
/** @type {!HTMLLinkElement} */ (node).rel === 'import'
);
};
/**
* Waits for an element to finish loading. If already done loading, it will
* mark the element accordingly.
* @param {!(HTMLLinkElement|HTMLScriptElement|HTMLStyleElement)} element
* @param {function()=} callback
*/
const whenElementLoaded = (element, callback) => {
if (element['__loaded']) {
callback && callback();
} else if (
(element.localName === 'script' && !element.src) ||
(element.localName === 'style' && !element.firstChild) ||
(element.localName === 'style' &&
element.namespaceURI === 'http://www.w3.org/2000/svg')
) {
// Inline scripts,empty styles, and styles in <svg> don't trigger
// load/error events, consider them already loaded.
element['__loaded'] = true;
callback && callback();
} else {
const onLoadingDone = (event) => {
element.removeEventListener(event.type, onLoadingDone);
element['__loaded'] = true;
callback && callback();
};
element.addEventListener('load', onLoadingDone);
// NOTE: We listen only for load events in IE/Edge, because in IE/Edge
// <style> with @import will fire error events for each failing @import,
// and finally will trigger the load event when all @import are
// finished (even if all fail).
if (element.localName !== 'style' || (!isIE && !isEdge)) {
element.addEventListener('error', onLoadingDone);
}
}
};
/**
* Calls the callback when all imports in the document at call time
* (or at least document ready) have loaded. Callback is called synchronously
* if imports are already done loading.
* @param {function()=} callback
*/
const whenReady = (callback) => {
// 1. ensure the document is in a ready state (has dom), then
// 2. watch for loading of imports and call callback when done
whenDocumentReady(() => whenImportsReady(() => callback && callback()));
};
/**
* Invokes the callback when document is in ready state. Callback is called
* synchronously if document is already done loading.
* @param {!function()} callback
*/
const whenDocumentReady = (callback) => {
const stateChanged = () => {
// NOTE: Firefox can hit readystate interactive without document.body existing.
// This is anti-spec, but we handle it here anyways by waiting for next change.
if (document.readyState !== 'loading' && !!document.body) {
document.removeEventListener('readystatechange', stateChanged);
callback();
}
};
document.addEventListener('readystatechange', stateChanged);
stateChanged();
};
/**
* Invokes the callback after all imports are loaded. Callback is called
* synchronously if imports are already done loading.
* @param {!function()} callback
*/
const whenImportsReady = (callback) => {
let imports =
/** @type {!NodeList<!HTMLLinkElement>} */
(QSA(document, rootImportSelector));
let pending = imports.length;
if (!pending) {
callback();
return;
}
forEach(imports, (imp) =>
whenElementLoaded(imp, () => {
if (--pending === 0) {
callback();
}
})
);
};
/**
* Returns the import document containing the element.
* @param {!Node} element
* @return {HTMLLinkElement|Document|undefined}
*/
const importForElement = (element) => {
if (useNative) {
// Return only if not in the main doc!
return element.ownerDocument !== document ? element.ownerDocument : null;
}
let doc = element['__importDoc'];
if (!doc && element.parentNode) {
doc = /** @type {!Element} */ (element.parentNode);
if (typeof doc.closest === 'function') {
// Element.closest returns the element itself if it matches the selector,
// so we search the closest import starting from the parent.
doc = doc.closest(importSelector);
} else {
// Walk up the parent tree until we find an import.
// eslint-disable-next-line no-empty
while (!isImportLink(doc) && (doc = doc.parentNode)) {}
}
element['__importDoc'] = doc;
}
return doc;
};
let importer = null;
/**
* Ensures imports contained in the element are imported.
* Use this to handle dynamic imports attached to body.
* @param {!(HTMLDocument|Element)} doc
*/
const loadImports = (doc) => {
if (importer) {
importer.loadImports(doc);
}
};
const newCustomEvent = (type, params) => {
if (typeof window.CustomEvent === 'function') {
return new CustomEvent(type, params);
}
const event = /** @type {!CustomEvent} */ (document.createEvent(
'CustomEvent'
));
event.initCustomEvent(
type,
Boolean(params.bubbles),
Boolean(params.cancelable),
params.detail
);
return event;
};
if (useNative) {
// Check for imports that might already be done loading by the time this
// script is actually executed. Native imports are blocking, so the ones
// available in the document by this time should already have failed
// or have .import defined.
const imps =
/** @type {!NodeList<!HTMLLinkElement>} */
(QSA(document, importSelector));
forEach(imps, (imp) => {
if (!imp.import || imp.import.readyState !== 'loading') {
imp['__loaded'] = true;
}
});
// Listen for load/error events to capture dynamically added scripts.
/**
* @type {!function(!Event)}
*/
const onLoadingDone = (event) => {
const elem = /** @type {!Element} */ (event.target);
if (isImportLink(elem)) {
elem['__loaded'] = true;
}
};
document.addEventListener('load', onLoadingDone, true /* useCapture */);
document.addEventListener('error', onLoadingDone, true /* useCapture */);
} else {
// Override baseURI so that imported elements' baseURI can be used seemlessly
// on native or polyfilled html-imports.
// NOTE: a <link rel=import> will have `link.baseURI === link.href`, as the link
// itself is used as the `import` document.
/** @type {Object|undefined} */
const native_baseURI = Object.getOwnPropertyDescriptor(
Node.prototype,
'baseURI'
);
// NOTE: if not configurable (e.g. safari9), set it on the Element prototype.
const klass =
!native_baseURI || native_baseURI.configurable ? Node : Element;
Object.defineProperty(klass.prototype, 'baseURI', {
get() {
const ownerDoc = /** @type {HTMLLinkElement} */ (isImportLink(this)
? this
: importForElement(this));
if (ownerDoc) {
return ownerDoc.href;
}
// Use native baseURI if possible.
if (native_baseURI && native_baseURI.get) {
return native_baseURI.get.call(this);
}
// Polyfill it if not available.
const base = /** @type {HTMLBaseElement} */ (document.querySelector(
'base'
));
return (base || window.location).href;
},
configurable: true,
enumerable: true,
});
// Define 'import' read-only property.
Object.defineProperty(HTMLLinkElement.prototype, 'import', {
get() {
return /** @type {HTMLLinkElement} */ (this)['__import'] || null;
},
configurable: true,
enumerable: true,
});
whenDocumentReady(() => {
importer = new Importer();
});
}
/**
Add support for the `HTMLImportsLoaded` event and the `HTMLImports.whenReady`
method. This api is necessary because unlike the native implementation,
script elements do not force imports to resolve. Instead, users should wrap
code in either an `HTMLImportsLoaded` handler or after load time in an
`HTMLImports.whenReady(callback)` call.
NOTE: This module also supports these apis under the native implementation.
Therefore, if this file is loaded, the same code can be used under both
the polyfill and native implementation.
*/
whenReady(() =>
document.dispatchEvent(
newCustomEvent('HTMLImportsLoaded', {
cancelable: true,
bubbles: true,
detail: undefined,
})
)
);
// exports
scope.useNative = useNative;
scope.whenReady = whenReady;
scope.importForElement = importForElement;
scope.loadImports = loadImports;
})((window.HTMLImports = window.HTMLImports || {}));