lit-html
Version:
HTML template literals in JavaScript
143 lines • 6.41 kB
JavaScript
/**
* @license
* Copyright (c) 2017 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
*/
import { removeNodes } from './dom.js';
import { insertNodeIntoTemplate, removeNodesFromTemplate } from './modify-template.js';
import { templateInstances } from './render.js';
import { templateCaches } from './template-factory.js';
import { TemplateInstance } from './template-instance.js';
import { Template } from './template.js';
export { html, svg, TemplateResult } from '../lit-html.js';
// Get a key to lookup in `templateCaches`.
const getTemplateCacheKey = (type, scopeName) => `${type}--${scopeName}`;
let compatibleShadyCSSVersion = true;
if (typeof window.ShadyCSS === 'undefined') {
compatibleShadyCSSVersion = false;
}
else if (typeof window.ShadyCSS.prepareTemplateDom === 'undefined') {
console.warn(`Incompatible ShadyCSS version detected.` +
`Please update to at least @webcomponents/webcomponentsjs@2.0.2 and` +
`@webcomponents/shadycss@1.3.1.`);
compatibleShadyCSSVersion = false;
}
/**
* Template factory which scopes template DOM using ShadyCSS.
* @param scopeName {string}
*/
const shadyTemplateFactory = (scopeName) => (result) => {
const cacheKey = getTemplateCacheKey(result.type, scopeName);
let templateCache = templateCaches.get(cacheKey);
if (templateCache === undefined) {
templateCache = new Map();
templateCaches.set(cacheKey, templateCache);
}
let template = templateCache.get(result.strings);
if (template === undefined) {
const element = result.getTemplateElement();
if (compatibleShadyCSSVersion) {
window.ShadyCSS.prepareTemplateDom(element, scopeName);
}
template = new Template(result, element);
templateCache.set(result.strings, template);
}
return template;
};
const TEMPLATE_TYPES = ['html', 'svg'];
/**
* Removes all style elements from Templates for the given scopeName.
*/
function removeStylesFromLitTemplates(scopeName) {
TEMPLATE_TYPES.forEach((type) => {
const templates = templateCaches.get(getTemplateCacheKey(type, scopeName));
if (templates !== undefined) {
templates.forEach((template) => {
const { element: { content } } = template;
// IE 11 doesn't support the iterable param Set constructor
const styles = new Set();
Array.from(content.querySelectorAll('style')).forEach((s) => {
styles.add(s);
});
removeNodesFromTemplate(template, styles);
});
}
});
}
const shadyRenderSet = new Set();
/**
* For the given scope name, ensures that ShadyCSS style scoping is performed.
* This is done just once per scope name so the fragment and template cannot
* be modified.
* (1) extracts styles from the rendered fragment and hands them to ShadyCSS
* to be scoped and appended to the document
* (2) removes style elements from all lit-html Templates for this scope name.
*
* Note, <style> elements can only be placed into templates for the
* initial rendering of the scope. If <style> elements are included in templates
* dynamically rendered to the scope (after the first scope render), they will
* not be scoped and the <style> will be left in the template and rendered
* output.
*/
const ensureStylesScoped = (fragment, template, scopeName) => {
// only scope element template once per scope name
if (!shadyRenderSet.has(scopeName)) {
shadyRenderSet.add(scopeName);
const styleTemplate = document.createElement('template');
Array.from(fragment.querySelectorAll('style')).forEach((s) => {
styleTemplate.content.appendChild(s);
});
window.ShadyCSS.prepareTemplateStyles(styleTemplate, scopeName);
// Fix templates: note the expectation here is that the given `fragment`
// has been generated from the given `template` which contains
// the set of templates rendered into this scope.
// It is only from this set of initial templates from which styles
// will be scoped and removed.
removeStylesFromLitTemplates(scopeName);
// ApplyShim case
if (window.ShadyCSS.nativeShadow) {
const style = styleTemplate.content.querySelector('style');
if (style !== null) {
// Insert style into rendered fragment
fragment.insertBefore(style, fragment.firstChild);
// Insert into lit-template (for subsequent renders)
insertNodeIntoTemplate(template, style.cloneNode(true), template.element.content.firstChild);
}
}
}
};
// NOTE: We're copying code from lit-html's `render` method here.
// We're doing this explicitly because the API for rendering templates is likely
// to change in the near term.
export function render(result, container, scopeName) {
const templateFactory = shadyTemplateFactory(scopeName);
const template = templateFactory(result);
let instance = templateInstances.get(container);
// Repeat render, just call update()
if (instance !== undefined && instance.template === template &&
instance.processor === result.processor) {
instance.update(result.values);
return;
}
// First render, create a new TemplateInstance and append it
instance = new TemplateInstance(template, result.processor, templateFactory);
templateInstances.set(container, instance);
const fragment = instance._clone();
instance.update(result.values);
// If there's a shadow host, do ShadyCSS scoping...
if (container instanceof ShadowRoot && compatibleShadyCSSVersion) {
ensureStylesScoped(fragment, template, scopeName);
window.ShadyCSS.styleElement(container.host);
}
removeNodes(container, container.firstChild);
container.appendChild(fragment);
}
//# sourceMappingURL=shady-render.js.map