@popeindustries/lit-html-server
Advanced tools
Comparing version 1.1.1 to 1.2.0
@@ -1,7 +0,3 @@ | ||
'use strict'; | ||
import { directive, isNodePart } from '../index.js'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var index_js = require('../index.js'); | ||
/** | ||
@@ -11,3 +7,3 @@ * @typedef NodePart { import('../parts.js').NodePart } | ||
const asyncAppend = index_js.directive(asyncAppendDirective); | ||
const asyncAppend = directive(asyncAppendDirective); | ||
@@ -22,3 +18,3 @@ /** | ||
return function(part) { | ||
if (!index_js.isNodePart(part)) { | ||
if (!isNodePart(part)) { | ||
throw Error('The `asyncAppend` directive can only be used in text nodes'); | ||
@@ -30,2 +26,2 @@ } | ||
exports.asyncAppend = asyncAppend; | ||
export { asyncAppend }; |
@@ -1,7 +0,3 @@ | ||
'use strict'; | ||
import { directive, isNodePart } from '../index.js'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var index_js = require('../index.js'); | ||
/** | ||
@@ -11,3 +7,3 @@ * @typedef NodePart { import('../parts.js').NodePart } | ||
const cache = index_js.directive(cacheDirective); | ||
const cache = directive(cacheDirective); | ||
@@ -23,3 +19,3 @@ /** | ||
return function(part) { | ||
if (!index_js.isNodePart(part)) { | ||
if (!isNodePart(part)) { | ||
throw Error('The `cache` directive can only be used in text nodes'); | ||
@@ -31,2 +27,2 @@ } | ||
exports.cache = cache; | ||
export { cache }; |
@@ -1,9 +0,5 @@ | ||
'use strict'; | ||
import { directive, isAttributePart } from '../index.js'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
const classMap = directive(classMapDirective); | ||
var index_js = require('../index.js'); | ||
const classMap = index_js.directive(classMapDirective); | ||
/** | ||
@@ -18,3 +14,3 @@ * Applies CSS classes, where'classInfo' keys are added as class names if values are truthy. | ||
return function(part) { | ||
if (!index_js.isAttributePart(part) || part.name !== 'class') { | ||
if (!isAttributePart(part) || part.name !== 'class') { | ||
throw Error('The `classMap` directive can only be used in the `class` attribute'); | ||
@@ -35,2 +31,2 @@ } | ||
exports.classMap = classMap; | ||
export { classMap }; |
@@ -1,9 +0,5 @@ | ||
'use strict'; | ||
import { directive } from '../index.js'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
const guard = directive(guardDirective); | ||
var index_js = require('../index.js'); | ||
const guard = index_js.directive(guardDirective); | ||
/** | ||
@@ -24,2 +20,2 @@ * Guard against re-render. | ||
exports.guard = guard; | ||
export { guard }; |
@@ -1,9 +0,5 @@ | ||
'use strict'; | ||
import { directive, isAttributePart, nothingString } from '../index.js'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
const ifDefined = directive(ifDefinedDirective); | ||
var index_js = require('../index.js'); | ||
const ifDefined = index_js.directive(ifDefinedDirective); | ||
/** | ||
@@ -18,4 +14,4 @@ * Sets the attribute if 'value' is defined, | ||
return function(part) { | ||
if (value === undefined && index_js.isAttributePart(part)) { | ||
return part.setValue(index_js.nothingString); | ||
if (value === undefined && isAttributePart(part)) { | ||
return part.setValue(nothingString); | ||
} | ||
@@ -26,2 +22,2 @@ part.setValue(value); | ||
exports.ifDefined = ifDefined; | ||
export { ifDefined }; |
@@ -1,9 +0,5 @@ | ||
'use strict'; | ||
import { directive } from '../index.js'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
const repeat = directive(repeatDirective); | ||
var index_js = require('../index.js'); | ||
const repeat = index_js.directive(repeatDirective); | ||
/** | ||
@@ -29,2 +25,2 @@ * Loop through 'items' and call 'template'. | ||
exports.repeat = repeat; | ||
export { repeat }; |
@@ -1,9 +0,5 @@ | ||
'use strict'; | ||
import { directive, isAttributePart } from '../index.js'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
const styleMap = directive(styleMapDirective); | ||
var index_js = require('../index.js'); | ||
const styleMap = index_js.directive(styleMapDirective); | ||
/** | ||
@@ -18,3 +14,3 @@ * Apply CSS properties, where 'styleInfo' keys and values are added as CSS properties. | ||
return function(part) { | ||
if (!index_js.isAttributePart(part) || part.name !== 'style') { | ||
if (!isAttributePart(part) || part.name !== 'style') { | ||
throw Error('The `styleMap` directive can only be used in the `style` attribute'); | ||
@@ -33,2 +29,2 @@ } | ||
exports.styleMap = styleMap; | ||
export { styleMap }; |
@@ -1,9 +0,5 @@ | ||
'use strict'; | ||
import { directive, isNodePart, unsafePrefixString } from '../index.js'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
const unsafeHTML = directive(unsafeHTMLDirective); | ||
var index_js = require('../index.js'); | ||
const unsafeHTML = index_js.directive(unsafeHTMLDirective); | ||
/** | ||
@@ -17,9 +13,9 @@ * Render "value" without HTML escaping | ||
return function(part) { | ||
if (!index_js.isNodePart(part)) { | ||
if (!isNodePart(part)) { | ||
throw Error('The `unsafeHTML` directive can only be used in text nodes'); | ||
} | ||
part.setValue(`${index_js.unsafePrefixString}${value}`); | ||
part.setValue(`${unsafePrefixString}${value}`); | ||
}; | ||
} | ||
exports.unsafeHTML = unsafeHTML; | ||
export { unsafeHTML }; |
@@ -1,7 +0,3 @@ | ||
'use strict'; | ||
import { directive } from '../index.js'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var index_js = require('../index.js'); | ||
/** | ||
@@ -19,3 +15,3 @@ * Determine if "value" is a primitive | ||
const until = index_js.directive(untilDirective); | ||
const until = directive(untilDirective); | ||
@@ -45,2 +41,2 @@ /** | ||
exports.until = until; | ||
export { until }; |
2216
browser/index.js
@@ -1,612 +0,561 @@ | ||
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
typeof define === 'function' && define.amd ? define(['exports'], factory) : | ||
(global = global || self, factory(global.litHtmlServer = {})); | ||
}(this, function (exports) { 'use strict'; | ||
/* global window */ | ||
/* eslint no-unused-vars: 0 */ | ||
const Buffer = | ||
(typeof window !== 'undefined' && window.Buffer) || | ||
class Buffer { | ||
/** | ||
* Determine if 'buffer' is a buffer | ||
* | ||
* @param { any } buffer | ||
* @returns { boolean } | ||
*/ | ||
static isBuffer(buffer) { | ||
return buffer != null && typeof buffer === 'object' && buffer.string !== undefined; | ||
} | ||
/* global window */ | ||
/* eslint no-unused-vars: 0 */ | ||
const Buffer = | ||
(typeof window !== 'undefined' && window.Buffer) || | ||
class Buffer { | ||
/** | ||
* Determine if 'buffer' is a buffer | ||
* | ||
* @param { any } buffer | ||
* @returns { boolean } | ||
*/ | ||
static isBuffer(buffer) { | ||
return buffer != null && typeof buffer === 'object' && buffer.string !== undefined; | ||
/** | ||
* Create buffer from 'string' | ||
* | ||
* @param { string } string | ||
* @returns { string } | ||
*/ | ||
static from(string) { | ||
string = typeof string === 'string' ? string : String(string); | ||
return new Buffer(string); | ||
} | ||
/** | ||
* Join 'buffers' into a single string | ||
* | ||
* @param { Array<any> } buffers | ||
* @param { number } [length] | ||
* @returns { string } | ||
*/ | ||
static concat(buffers, length) { | ||
let string = ''; | ||
for (let i = 0, n = buffers.length; i < n; i++) { | ||
const buffer = buffers[i]; | ||
string += typeof buffer === 'string' ? buffer : String(buffer); | ||
} | ||
/** | ||
* Create buffer from 'string' | ||
* | ||
* @param { string } string | ||
* @returns { string } | ||
*/ | ||
static from(string) { | ||
string = typeof string === 'string' ? string : String(string); | ||
return new Buffer(string); | ||
if (length !== undefined && string.length > length) { | ||
string = string.slice(0, length); | ||
} | ||
/** | ||
* Join 'buffers' into a single string | ||
* | ||
* @param { Array<any> } buffers | ||
* @param { number } [length] | ||
* @returns { string } | ||
*/ | ||
static concat(buffers, length) { | ||
let string = ''; | ||
return new Buffer(string); | ||
} | ||
for (let i = 0, n = buffers.length; i < n; i++) { | ||
const buffer = buffers[i]; | ||
/** | ||
* Construct Buffer instance | ||
* | ||
* @param { string } string | ||
*/ | ||
constructor(string) { | ||
this.string = string; | ||
} | ||
string += typeof buffer === 'string' ? buffer : String(buffer); | ||
} | ||
/** | ||
* Stringify | ||
* | ||
* @returns { string } | ||
*/ | ||
toString() { | ||
return this.string; | ||
} | ||
}; | ||
if (length !== undefined && string.length > length) { | ||
string = string.slice(0, length); | ||
} | ||
return new Buffer(string); | ||
} | ||
/** | ||
* Determine if "promise" is a Promise instance | ||
* | ||
* @param { Promise<any> } promise | ||
* @returns { boolean } | ||
*/ | ||
function isPromise(promise) { | ||
return promise != null && promise.then != null; | ||
} | ||
/** | ||
* Construct Buffer instance | ||
* | ||
* @param { string } string | ||
*/ | ||
constructor(string) { | ||
this.string = string; | ||
} | ||
/** | ||
* Determine if "iterator" is an synchronous iterator | ||
* | ||
* @param { IterableIterator } iterator | ||
* @returns { boolean } | ||
*/ | ||
function isSyncIterator(iterator) { | ||
return ( | ||
iterator != null && | ||
// Ignore strings (which are also iterable) | ||
typeof iterator !== 'string' && | ||
typeof iterator[Symbol.iterator] === 'function' | ||
); | ||
} | ||
/** | ||
* Stringify | ||
* | ||
* @returns { string } | ||
*/ | ||
toString() { | ||
return this.string; | ||
} | ||
}; | ||
/** | ||
* Determine if "iterator" is an asynchronous iterator | ||
* | ||
* @param { AsyncIterableIterator } iterator | ||
* @returns { boolean } | ||
*/ | ||
function isAsyncIterator(iterator) { | ||
return iterator != null && typeof iterator[Symbol.asyncIterator] === 'function'; | ||
} | ||
/** | ||
* Determine if "result" is an iterator result object | ||
* | ||
* @param { object } result | ||
* @returns { boolean } | ||
*/ | ||
function isIteratorResult(result) { | ||
return typeof result === 'object' && 'value' in result && 'done' in result; | ||
} | ||
/** | ||
* Determine if "promise" is a Promise instance | ||
* | ||
* @param { Promise<any> } promise | ||
* @returns { boolean } | ||
*/ | ||
function isPromise(promise) { | ||
return promise != null && promise.then != null; | ||
} | ||
/** | ||
* Determine if "value" is a primitive | ||
* | ||
* @param { any } value | ||
* @returns { boolean } | ||
*/ | ||
function isPrimitive(value) { | ||
const type = typeof value; | ||
/** | ||
* Determine if "iterator" is an synchronous iterator | ||
* | ||
* @param { IterableIterator } iterator | ||
* @returns { boolean } | ||
*/ | ||
function isSyncIterator(iterator) { | ||
return ( | ||
iterator != null && | ||
// Ignore strings (which are also iterable) | ||
typeof iterator !== 'string' && | ||
typeof iterator[Symbol.iterator] === 'function' | ||
); | ||
} | ||
return value === null || !(type === 'object' || type === 'function'); | ||
} | ||
/** | ||
* Determine if "iterator" is an asynchronous iterator | ||
* | ||
* @param { AsyncIterableIterator } iterator | ||
* @returns { boolean } | ||
*/ | ||
function isAsyncIterator(iterator) { | ||
return iterator != null && typeof iterator[Symbol.asyncIterator] === 'function'; | ||
} | ||
/** | ||
* An empty string Buffer | ||
*/ | ||
const emptyStringBuffer = Buffer.from(''); | ||
/** | ||
* Determine if "result" is an iterator result object | ||
* | ||
* @param { object } result | ||
* @returns { boolean } | ||
*/ | ||
function isIteratorResult(result) { | ||
return typeof result === 'object' && 'value' in result && 'done' in result; | ||
} | ||
/** | ||
* A value for strings that signals a Part to clear its content | ||
*/ | ||
const nothingString = '__nothing-lit-html-server-string__'; | ||
/** | ||
* Determine if "value" is a primitive | ||
* | ||
* @param { any } value | ||
* @returns { boolean } | ||
*/ | ||
function isPrimitive(value) { | ||
const type = typeof value; | ||
/** | ||
* A prefix value for strings that should not be escaped | ||
*/ | ||
const unsafePrefixString = '__unsafe-lit-html-server-string__'; | ||
return value === null || !(type === 'object' || type === 'function'); | ||
} | ||
// https://github.com/facebook/react/packages/react-dom/src/server/escapeTextForBrowser.js | ||
/** | ||
* An empty string Buffer | ||
*/ | ||
const emptyStringBuffer = Buffer.from(''); | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* Based on the escape-html library, which is used under the MIT License below: | ||
* | ||
* Copyright (c) 2012-2013 TJ Holowaychuk | ||
* Copyright (c) 2015 Andreas Lubbe | ||
* Copyright (c) 2015 Tiancheng "Timothy" Gu | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining | ||
* a copy of this software and associated documentation files (the | ||
* 'Software'), to deal in the Software without restriction, including | ||
* without limitation the rights to use, copy, modify, merge, publish, | ||
* distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to | ||
* the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
*/ | ||
/** | ||
* A value for strings that signals a Part to clear its content | ||
*/ | ||
const nothingString = '__nothing-lit-html-server-string__'; | ||
// code copied and modified from escape-html | ||
/** | ||
* Module variables. | ||
* @private | ||
*/ | ||
/** | ||
* A prefix value for strings that should not be escaped | ||
*/ | ||
const unsafePrefixString = '__unsafe-lit-html-server-string__'; | ||
const matchHtmlRegExp = /["'&<>]/; | ||
// https://github.com/facebook/react/packages/react-dom/src/server/escapeTextForBrowser.js | ||
/** | ||
* Escapes special characters and HTML entities in a given html string. | ||
* | ||
* @param {string} string HTML string to escape for later insertion | ||
* @return {string} | ||
* @public | ||
*/ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* Based on the escape-html library, which is used under the MIT License below: | ||
* | ||
* Copyright (c) 2012-2013 TJ Holowaychuk | ||
* Copyright (c) 2015 Andreas Lubbe | ||
* Copyright (c) 2015 Tiancheng "Timothy" Gu | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining | ||
* a copy of this software and associated documentation files (the | ||
* 'Software'), to deal in the Software without restriction, including | ||
* without limitation the rights to use, copy, modify, merge, publish, | ||
* distribute, sublicense, and/or sell copies of the Software, and to | ||
* permit persons to whom the Software is furnished to do so, subject to | ||
* the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be | ||
* included in all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | ||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
*/ | ||
function escapeHtml(string) { | ||
const str = '' + string; | ||
const match = matchHtmlRegExp.exec(str); | ||
// code copied and modified from escape-html | ||
/** | ||
* Module variables. | ||
* @private | ||
*/ | ||
if (!match) { | ||
return str; | ||
} | ||
const matchHtmlRegExp = /["'&<>]/; | ||
let escape; | ||
let html = ''; | ||
let index; | ||
let lastIndex = 0; | ||
/** | ||
* Escapes special characters and HTML entities in a given html string. | ||
* | ||
* @param {string} string HTML string to escape for later insertion | ||
* @return {string} | ||
* @public | ||
*/ | ||
for (index = match.index; index < str.length; index++) { | ||
switch (str.charCodeAt(index)) { | ||
case 34: // " | ||
escape = '"'; | ||
break; | ||
case 38: // & | ||
escape = '&'; | ||
break; | ||
case 39: // ' | ||
escape = '''; // modified from escape-html; used to be ''' | ||
break; | ||
case 60: // < | ||
escape = '<'; | ||
break; | ||
case 62: // > | ||
escape = '>'; | ||
break; | ||
default: | ||
continue; | ||
} | ||
function escapeHtml(string) { | ||
const str = '' + string; | ||
const match = matchHtmlRegExp.exec(str); | ||
if (!match) { | ||
return str; | ||
if (lastIndex !== index) { | ||
html += str.substring(lastIndex, index); | ||
} | ||
let escape; | ||
let html = ''; | ||
let index; | ||
let lastIndex = 0; | ||
lastIndex = index + 1; | ||
html += escape; | ||
} | ||
for (index = match.index; index < str.length; index++) { | ||
switch (str.charCodeAt(index)) { | ||
case 34: // " | ||
escape = '"'; | ||
break; | ||
case 38: // & | ||
escape = '&'; | ||
break; | ||
case 39: // ' | ||
escape = '''; // modified from escape-html; used to be ''' | ||
break; | ||
case 60: // < | ||
escape = '<'; | ||
break; | ||
case 62: // > | ||
escape = '>'; | ||
break; | ||
default: | ||
continue; | ||
} | ||
return lastIndex !== index ? html + str.substring(lastIndex, index) : html; | ||
} | ||
// end code copied and modified from escape-html | ||
if (lastIndex !== index) { | ||
html += str.substring(lastIndex, index); | ||
} | ||
/** | ||
* Escapes text to prevent scripting attacks. | ||
* | ||
* @param {any} text Text value to escape. | ||
* @return {string} An escaped string. | ||
*/ | ||
function escapeTextForBrowser(text) { | ||
if (typeof text === 'boolean' || typeof text === 'number') { | ||
// this shortcircuit helps perf for types that we know will never have | ||
// special characters, especially given that this function is used often | ||
// for numeric dom ids. | ||
return '' + text; | ||
} | ||
return escapeHtml(text); | ||
} | ||
lastIndex = index + 1; | ||
html += escape; | ||
} | ||
/** | ||
* @typedef Part { import('./parts.js').Part } | ||
*/ | ||
return lastIndex !== index ? html + str.substring(lastIndex, index) : html; | ||
} | ||
// end code copied and modified from escape-html | ||
/** | ||
* Determine if "obj" is a directive function | ||
* | ||
* @param { any } obj | ||
* @returns { boolean } | ||
*/ | ||
function isDirective(obj) { | ||
return typeof obj === 'function' && obj.isDirective; | ||
} | ||
/** | ||
* Escapes text to prevent scripting attacks. | ||
* | ||
* @param {any} text Text value to escape. | ||
* @return {string} An escaped string. | ||
*/ | ||
function escapeTextForBrowser(text) { | ||
if (typeof text === 'boolean' || typeof text === 'number') { | ||
// this shortcircuit helps perf for types that we know will never have | ||
// special characters, especially given that this function is used often | ||
// for numeric dom ids. | ||
return '' + text; | ||
/** | ||
* Define new directive for "fn". | ||
* The passed function should be a factory function, | ||
* and must return a function that will eventually be called with a Part instance | ||
* | ||
* @param { (...args) => (part: Part) => void } fn | ||
* @returns { (...args) => (part: Part) => void } | ||
*/ | ||
function directive(fn) { | ||
return function directive(...args) { | ||
const result = fn(...args); | ||
if (typeof result !== 'function') { | ||
throw Error('directives are factory functions and must return a function when called'); | ||
} | ||
return escapeHtml(text); | ||
} | ||
/** | ||
* @typedef Part { import('./parts.js').Part } | ||
*/ | ||
result.isDirective = true; | ||
return result; | ||
}; | ||
} | ||
/** | ||
* Determine if "part" is a NodePart | ||
* | ||
* @param { Part } part | ||
* @returns { boolean } | ||
*/ | ||
function isNodePart(part) { | ||
return part instanceof NodePart || typeof part.name === 'undefined'; | ||
} | ||
/** | ||
* Determine if "part" is an AttributePart | ||
* | ||
* @param { Part } part | ||
* @returns { boolean } | ||
*/ | ||
function isAttributePart(part) { | ||
return part instanceof AttributePart || typeof part.name !== 'undefined'; | ||
} | ||
/** | ||
* Base class interface for Node/Attribute parts | ||
*/ | ||
class Part { | ||
/** | ||
* Determine if "obj" is a directive function | ||
* | ||
* @param { any } obj | ||
* @returns { boolean } | ||
* Constructor | ||
*/ | ||
function isDirective(obj) { | ||
return typeof obj === 'function' && obj.isDirective; | ||
constructor() { | ||
this._value; | ||
} | ||
/** | ||
* Define new directive for "fn". | ||
* The passed function should be a factory function, | ||
* and must return a function that will eventually be called with a Part instance | ||
* Store the current value. | ||
* Used by directives to temporarily transfer value | ||
* (value will be deleted after reading). | ||
* | ||
* @param { (...args) => (part: Part) => void } fn | ||
* @returns { (...args) => (part: Part) => void } | ||
* @param { any } value | ||
*/ | ||
function directive(fn) { | ||
return function directive(...args) { | ||
const result = fn(...args); | ||
if (typeof result !== 'function') { | ||
throw Error('directives are factory functions and must return a function when called'); | ||
} | ||
result.isDirective = true; | ||
return result; | ||
}; | ||
setValue(value) { | ||
this._value = value; | ||
} | ||
/** | ||
* Determine if "part" is a NodePart | ||
* Retrieve resolved string from passed "value" | ||
* | ||
* @param { Part } part | ||
* @returns { boolean } | ||
* @param { any } value | ||
* @returns { any } | ||
*/ | ||
function isNodePart(part) { | ||
return part instanceof NodePart || typeof part.name === 'undefined'; | ||
getValue(value) { | ||
return value; | ||
} | ||
/** | ||
* Determine if "part" is an AttributePart | ||
* | ||
* @param { Part } part | ||
* @returns { boolean } | ||
* No-op | ||
*/ | ||
function isAttributePart(part) { | ||
return part instanceof AttributePart || typeof part.name !== 'undefined'; | ||
} | ||
commit() {} | ||
} | ||
/** | ||
* A dynamic template part for text nodes | ||
*/ | ||
class NodePart extends Part { | ||
/** | ||
* Base class interface for Node/Attribute parts | ||
* Retrieve resolved value given passed "value" | ||
* | ||
* @param { any } value | ||
* @returns { any } | ||
*/ | ||
class Part { | ||
/** | ||
* Constructor | ||
*/ | ||
constructor() { | ||
this._value; | ||
} | ||
/** | ||
* Store the current value. | ||
* Used by directives to temporarily transfer value | ||
* (value will be deleted after reading). | ||
* | ||
* @param { any } value | ||
*/ | ||
setValue(value) { | ||
this._value = value; | ||
} | ||
/** | ||
* Retrieve resolved string from passed "value" | ||
* | ||
* @param { any } value | ||
* @returns { any } | ||
*/ | ||
getValue(value) { | ||
return value; | ||
} | ||
/** | ||
* No-op | ||
*/ | ||
commit() {} | ||
getValue(value) { | ||
return resolveNodeValue(value, this); | ||
} | ||
} | ||
/** | ||
* A dynamic template part for attributes. | ||
* Unlike text nodes, attributes may contain multiple strings and parts. | ||
*/ | ||
class AttributePart extends Part { | ||
/** | ||
* A dynamic template part for text nodes | ||
* Constructor | ||
* | ||
* @param { string } name | ||
* @param { Array<Buffer> } strings | ||
*/ | ||
class NodePart extends Part { | ||
/** | ||
* Retrieve resolved value given passed "value" | ||
* | ||
* @param { any } value | ||
* @returns { any } | ||
*/ | ||
getValue(value) { | ||
return resolveNodeValue(value, this); | ||
} | ||
constructor(name, strings) { | ||
super(); | ||
this.name = name; | ||
this.strings = strings; | ||
this.length = strings.length - 1; | ||
this.prefix = Buffer.from(`${this.name}="`); | ||
this.suffix = Buffer.from(`${this.strings[this.length]}"`); | ||
} | ||
/** | ||
* A dynamic template part for attributes. | ||
* Unlike text nodes, attributes may contain multiple strings and parts. | ||
* Retrieve resolved string Buffer from passed "values". | ||
* Resolves to a single string, or Promise for a single string, | ||
* even when responsible for multiple values. | ||
* | ||
* @param { Array<any> } values | ||
* @returns { Buffer|Promise<Buffer> } | ||
*/ | ||
class AttributePart extends Part { | ||
/** | ||
* Constructor | ||
* | ||
* @param { string } name | ||
* @param { Array<Buffer> } strings | ||
*/ | ||
constructor(name, strings) { | ||
super(); | ||
this.name = name; | ||
this.strings = strings; | ||
this.length = strings.length - 1; | ||
this.prefix = Buffer.from(`${this.name}="`); | ||
this.suffix = Buffer.from(`${this.strings[this.length]}"`); | ||
} | ||
getValue(values) { | ||
let chunks = [this.prefix]; | ||
let chunkLength = this.prefix.length; | ||
let pendingChunks; | ||
/** | ||
* Retrieve resolved string Buffer from passed "values". | ||
* Resolves to a single string, or Promise for a single string, | ||
* even when responsible for multiple values. | ||
* | ||
* @param { Array<any> } values | ||
* @returns { Buffer|Promise<Buffer> } | ||
*/ | ||
getValue(values) { | ||
let chunks = [this.prefix]; | ||
let chunkLength = this.prefix.length; | ||
let pendingChunks; | ||
for (let i = 0; i < this.length; i++) { | ||
const string = this.strings[i]; | ||
let value = resolveAttributeValue(values[i], this); | ||
for (let i = 0; i < this.length; i++) { | ||
const string = this.strings[i]; | ||
let value = resolveAttributeValue(values[i], this); | ||
// Bail if 'nothing' | ||
if (value === nothingString) { | ||
return emptyStringBuffer; | ||
} | ||
// Bail if 'nothing' | ||
if (value === nothingString) { | ||
return emptyStringBuffer; | ||
chunks.push(string); | ||
chunkLength += string.length; | ||
if (Buffer.isBuffer(value)) { | ||
chunks.push(value); | ||
chunkLength += value.length; | ||
} else if (isPromise(value)) { | ||
// Lazy init for uncommon scenario | ||
if (pendingChunks === undefined) { | ||
pendingChunks = []; | ||
} | ||
chunks.push(string); | ||
chunkLength += string.length; | ||
const index = chunks.push(value) - 1; | ||
if (Buffer.isBuffer(value)) { | ||
chunks.push(value); | ||
chunkLength += value.length; | ||
} else if (isPromise(value)) { | ||
// Lazy init for uncommon scenario | ||
if (pendingChunks === undefined) { | ||
pendingChunks = []; | ||
} | ||
const index = chunks.push(value) - 1; | ||
pendingChunks.push( | ||
value.then((value) => { | ||
chunks[index] = value; | ||
chunkLength += value.length; | ||
}) | ||
); | ||
} else if (Array.isArray(value)) { | ||
for (const chunk of value) { | ||
chunks.push(chunk); | ||
chunkLength += chunk.length; | ||
} | ||
pendingChunks.push( | ||
value.then((value) => { | ||
chunks[index] = value; | ||
chunkLength += value.length; | ||
}) | ||
); | ||
} else if (Array.isArray(value)) { | ||
for (const chunk of value) { | ||
chunks.push(chunk); | ||
chunkLength += chunk.length; | ||
} | ||
} | ||
} | ||
chunks.push(this.suffix); | ||
chunkLength += this.suffix.length; | ||
if (pendingChunks !== undefined) { | ||
return Promise.all(pendingChunks).then(() => Buffer.concat(chunks, chunkLength)); | ||
} | ||
return Buffer.concat(chunks, chunkLength); | ||
chunks.push(this.suffix); | ||
chunkLength += this.suffix.length; | ||
if (pendingChunks !== undefined) { | ||
return Promise.all(pendingChunks).then(() => Buffer.concat(chunks, chunkLength)); | ||
} | ||
return Buffer.concat(chunks, chunkLength); | ||
} | ||
} | ||
/** | ||
* A dynamic template part for boolean attributes. | ||
* Boolean attributes are prefixed with "?" | ||
*/ | ||
class BooleanAttributePart extends AttributePart { | ||
/** | ||
* A dynamic template part for boolean attributes. | ||
* Boolean attributes are prefixed with "?" | ||
* Constructor | ||
* | ||
* @param { string } name | ||
* @param { Array<Buffer> } strings | ||
* @throws error when multiple expressions | ||
*/ | ||
class BooleanAttributePart extends AttributePart { | ||
/** | ||
* Constructor | ||
* | ||
* @param { string } name | ||
* @param { Array<Buffer> } strings | ||
* @throws error when multiple expressions | ||
*/ | ||
constructor(name, strings) { | ||
super(name, strings); | ||
constructor(name, strings) { | ||
super(name, strings); | ||
this.name = Buffer.from(this.name); | ||
this.name = Buffer.from(this.name); | ||
if ( | ||
strings.length !== 2 || | ||
!strings[0] === emptyStringBuffer || | ||
!strings[1] === emptyStringBuffer | ||
) { | ||
throw Error('Boolean attributes can only contain a single expression'); | ||
} | ||
if ( | ||
strings.length !== 2 || | ||
!strings[0] === emptyStringBuffer || | ||
!strings[1] === emptyStringBuffer | ||
) { | ||
throw Error('Boolean attributes can only contain a single expression'); | ||
} | ||
} | ||
/** | ||
* Retrieve resolved string Buffer from passed "values". | ||
* | ||
* @param { Array<any> } values | ||
* @returns { Buffer|Promise<Buffer> } | ||
*/ | ||
getValue(values) { | ||
let value = values[0]; | ||
/** | ||
* Retrieve resolved string Buffer from passed "values". | ||
* | ||
* @param { Array<any> } values | ||
* @returns { Buffer|Promise<Buffer> } | ||
*/ | ||
getValue(values) { | ||
let value = values[0]; | ||
if (isDirective(value)) { | ||
value = resolveDirectiveValue(value, this); | ||
} | ||
if (isDirective(value)) { | ||
value = resolveDirectiveValue(value, this); | ||
} | ||
if (isPromise(value)) { | ||
return value.then((value) => (value ? this.name : emptyStringBuffer)); | ||
} | ||
return value ? this.name : emptyStringBuffer; | ||
if (isPromise(value)) { | ||
return value.then((value) => (value ? this.name : emptyStringBuffer)); | ||
} | ||
} | ||
/** | ||
* A dynamic template part for property attributes. | ||
* Property attributes are prefixed with "." | ||
*/ | ||
class PropertyAttributePart extends AttributePart { | ||
/** | ||
* Retrieve resolved string Buffer from passed "values". | ||
* Properties have no server-side representation, | ||
* so always returns an empty string. | ||
* | ||
* @param { Array<any> } values | ||
* @returns { string } | ||
*/ | ||
getValue(/* values */) { | ||
return emptyStringBuffer; | ||
} | ||
return value ? this.name : emptyStringBuffer; | ||
} | ||
} | ||
/** | ||
* A dynamic template part for property attributes. | ||
* Property attributes are prefixed with "." | ||
*/ | ||
class PropertyAttributePart extends AttributePart { | ||
/** | ||
* A dynamic template part for event attributes. | ||
* Event attributes are prefixed with "@" | ||
* Retrieve resolved string Buffer from passed "values". | ||
* Properties have no server-side representation, | ||
* so always returns an empty string. | ||
* | ||
* @param { Array<any> } values | ||
* @returns { string } | ||
*/ | ||
class EventAttributePart extends AttributePart { | ||
/** | ||
* Retrieve resolved string Buffer from passed "values". | ||
* Event bindings have no server-side representation, | ||
* so always returns an empty string. | ||
* | ||
* @param { Array<any> } values | ||
* @returns { string } | ||
*/ | ||
getValue(/* values */) { | ||
return emptyStringBuffer; | ||
} | ||
getValue(/* values */) { | ||
return emptyStringBuffer; | ||
} | ||
} | ||
/** | ||
* A dynamic template part for event attributes. | ||
* Event attributes are prefixed with "@" | ||
*/ | ||
class EventAttributePart extends AttributePart { | ||
/** | ||
* Resolve "value" to string if possible | ||
* Retrieve resolved string Buffer from passed "values". | ||
* Event bindings have no server-side representation, | ||
* so always returns an empty string. | ||
* | ||
* @param { any } value | ||
* @param { AttributePart } part | ||
* @returns { any } | ||
* @param { Array<any> } values | ||
* @returns { string } | ||
*/ | ||
function resolveAttributeValue(value, part) { | ||
if (isDirective(value)) { | ||
value = resolveDirectiveValue(value, part); | ||
} | ||
getValue(/* values */) { | ||
return emptyStringBuffer; | ||
} | ||
} | ||
if (value === nothingString) { | ||
return value; | ||
} | ||
/** | ||
* Resolve "value" to string if possible | ||
* | ||
* @param { any } value | ||
* @param { AttributePart } part | ||
* @returns { any } | ||
*/ | ||
function resolveAttributeValue(value, part) { | ||
if (isDirective(value)) { | ||
value = resolveDirectiveValue(value, part); | ||
} | ||
if (isTemplateResult(value)) { | ||
value = value.read(); | ||
} | ||
if (value === nothingString) { | ||
return value; | ||
} | ||
if (isPrimitive(value)) { | ||
const string = typeof value !== 'string' ? String(value) : value; | ||
// Escape if not prefixed with unsafePrefixString, otherwise strip prefix | ||
return Buffer.from( | ||
string.indexOf(unsafePrefixString) === 0 ? string.slice(33) : escapeTextForBrowser(string) | ||
); | ||
} else if (Buffer.isBuffer(value)) { | ||
return value; | ||
} else if (isPromise(value)) { | ||
return value.then((value) => resolveAttributeValue(value, part)); | ||
} else if (isSyncIterator(value)) { | ||
if (!Array.isArray(value)) { | ||
value = Array.from(value); | ||
} | ||
return Buffer.concat( | ||
value.reduce((values, value) => { | ||
value = resolveAttributeValue(value, part); | ||
// Flatten | ||
if (Array.isArray(value)) { | ||
return values.concat(value); | ||
} | ||
values.push(value); | ||
return values; | ||
}, []) | ||
); | ||
} else { | ||
throw Error('unknown AttributPart value', value); | ||
} | ||
if (isTemplateResult(value)) { | ||
value = value.read(); | ||
} | ||
/** | ||
* Resolve "value" to string Buffer if possible | ||
* | ||
* @param { any } value | ||
* @param { NodePart } part | ||
* @returns { any } | ||
*/ | ||
function resolveNodeValue(value, part) { | ||
if (isDirective(value)) { | ||
value = resolveDirectiveValue(value, part); | ||
if (isPrimitive(value)) { | ||
const string = typeof value !== 'string' ? String(value) : value; | ||
// Escape if not prefixed with unsafePrefixString, otherwise strip prefix | ||
return Buffer.from( | ||
string.indexOf(unsafePrefixString) === 0 ? string.slice(33) : escapeTextForBrowser(string) | ||
); | ||
} else if (Buffer.isBuffer(value)) { | ||
return value; | ||
} else if (isPromise(value)) { | ||
return value.then((value) => resolveAttributeValue(value, part)); | ||
} else if (isSyncIterator(value)) { | ||
if (!Array.isArray(value)) { | ||
value = Array.from(value); | ||
} | ||
if (value === nothingString || value === undefined) { | ||
return emptyStringBuffer; | ||
} | ||
if (isPrimitive(value)) { | ||
const string = typeof value !== 'string' ? String(value) : value; | ||
// Escape if not prefixed with unsafePrefixString, otherwise strip prefix | ||
return Buffer.from( | ||
string.indexOf(unsafePrefixString) === 0 ? string.slice(33) : escapeTextForBrowser(string) | ||
); | ||
} else if (isTemplateResult(value) || Buffer.isBuffer(value)) { | ||
return value; | ||
} else if (isPromise(value)) { | ||
return value.then((value) => resolveNodeValue(value, part)); | ||
} else if (isSyncIterator(value)) { | ||
if (!Array.isArray(value)) { | ||
value = Array.from(value); | ||
} | ||
return value.reduce((values, value) => { | ||
value = resolveNodeValue(value, part); | ||
return Buffer.concat( | ||
value.reduce((values, value) => { | ||
value = resolveAttributeValue(value, part); | ||
// Flatten | ||
@@ -618,717 +567,814 @@ if (Array.isArray(value)) { | ||
return values; | ||
}, []); | ||
} else if (isAsyncIterator(value)) { | ||
return resolveAsyncIteratorValue(value, part); | ||
} else { | ||
throw Error('unknown NodePart value', value); | ||
} | ||
}, []) | ||
); | ||
} else { | ||
throw Error('unknown AttributPart value', value); | ||
} | ||
} | ||
/** | ||
* Resolve values of async "iterator" | ||
* | ||
* @param { AsyncIterator } iterator | ||
* @param { NodePart } part | ||
* @returns { AsyncGenerator } | ||
*/ | ||
async function* resolveAsyncIteratorValue(iterator, part) { | ||
for await (const value of iterator) { | ||
yield resolveNodeValue(value, part); | ||
} | ||
/** | ||
* Resolve "value" to string Buffer if possible | ||
* | ||
* @param { any } value | ||
* @param { NodePart } part | ||
* @returns { any } | ||
*/ | ||
function resolveNodeValue(value, part) { | ||
if (isDirective(value)) { | ||
value = resolveDirectiveValue(value, part); | ||
} | ||
/** | ||
* Resolve value of "directive" | ||
* | ||
* @param { function } directive | ||
* @param { Part } part | ||
* @returns { any } | ||
*/ | ||
function resolveDirectiveValue(directive$$1, part) { | ||
// Directives are synchronous, so it's safe to read and delete value | ||
directive$$1(part); | ||
const value = part._value; | ||
part._value = undefined; | ||
if (value === nothingString || value === undefined) { | ||
return emptyStringBuffer; | ||
} | ||
if (isPrimitive(value)) { | ||
const string = typeof value !== 'string' ? String(value) : value; | ||
// Escape if not prefixed with unsafePrefixString, otherwise strip prefix | ||
return Buffer.from( | ||
string.indexOf(unsafePrefixString) === 0 ? string.slice(33) : escapeTextForBrowser(string) | ||
); | ||
} else if (isTemplateResult(value) || Buffer.isBuffer(value)) { | ||
return value; | ||
} else if (isPromise(value)) { | ||
return value.then((value) => resolveNodeValue(value, part)); | ||
} else if (isSyncIterator(value)) { | ||
if (!Array.isArray(value)) { | ||
value = Array.from(value); | ||
} | ||
return value.reduce((values, value) => { | ||
value = resolveNodeValue(value, part); | ||
// Flatten | ||
if (Array.isArray(value)) { | ||
return values.concat(value); | ||
} | ||
values.push(value); | ||
return values; | ||
}, []); | ||
} else if (isAsyncIterator(value)) { | ||
return resolveAsyncIteratorValue(value, part); | ||
} else { | ||
throw Error('unknown NodePart value', value); | ||
} | ||
} | ||
const pool = []; | ||
let id = 0; | ||
/** | ||
* Resolve values of async "iterator" | ||
* | ||
* @param { AsyncIterator } iterator | ||
* @param { NodePart } part | ||
* @returns { AsyncGenerator } | ||
*/ | ||
async function* resolveAsyncIteratorValue(iterator, part) { | ||
for await (const value of iterator) { | ||
yield resolveNodeValue(value, part); | ||
} | ||
} | ||
/** | ||
* Determine whether "result" is a TemplateResult | ||
* | ||
* @param { TemplateResult } result | ||
* @returns { boolean } | ||
*/ | ||
function isTemplateResult(result) { | ||
return result instanceof TemplateResult; | ||
/** | ||
* Resolve value of "directive" | ||
* | ||
* @param { function } directive | ||
* @param { Part } part | ||
* @returns { any } | ||
*/ | ||
function resolveDirectiveValue(directive, part) { | ||
// Directives are synchronous, so it's safe to read and delete value | ||
directive(part); | ||
const value = part._value; | ||
part._value = undefined; | ||
return value; | ||
} | ||
const pool = []; | ||
let id = 0; | ||
/** | ||
* Determine whether "result" is a TemplateResult | ||
* | ||
* @param { TemplateResult } result | ||
* @returns { boolean } | ||
*/ | ||
function isTemplateResult(result) { | ||
return result instanceof TemplateResult; | ||
} | ||
/** | ||
* Retrieve TemplateResult instance. | ||
* Uses an object pool to recycle instances. | ||
* | ||
* @param { Template } template | ||
* @param { Array<any> } values | ||
* @returns { TemplateResult } | ||
*/ | ||
function templateResult(template, values) { | ||
let instance = pool.pop(); | ||
if (instance) { | ||
instance.template = template; | ||
instance.values = values; | ||
} else { | ||
instance = new TemplateResult(template, values); | ||
} | ||
return instance; | ||
} | ||
/** | ||
* A class for consuming the combined static and dynamic parts of a lit-html Template. | ||
* TemplateResults | ||
*/ | ||
class TemplateResult { | ||
/** | ||
* Retrieve TemplateResult instance. | ||
* Uses an object pool to recycle instances. | ||
* Constructor | ||
* | ||
* @param { Template } template | ||
* @param { Array<any> } values | ||
* @returns { TemplateResult } | ||
*/ | ||
function templateResult(template, values) { | ||
let instance = pool.pop(); | ||
if (instance) { | ||
instance.template = template; | ||
instance.values = values; | ||
} else { | ||
instance = new TemplateResult(template, values); | ||
} | ||
return instance; | ||
constructor(template, values) { | ||
this.template = template; | ||
this.values = values; | ||
this.id = id++; | ||
this.index = 0; | ||
} | ||
/** | ||
* A class for consuming the combined static and dynamic parts of a lit-html Template. | ||
* TemplateResults | ||
* Consume template result content. | ||
* *Note* that instances may only be read once, | ||
* and will be destroyed upon completion. | ||
* | ||
* @param { boolean } deep - recursively resolve nested TemplateResults | ||
* @returns { any } | ||
*/ | ||
class TemplateResult { | ||
/** | ||
* Constructor | ||
* | ||
* @param { Template } template | ||
* @param { Array<any> } values | ||
*/ | ||
constructor(template, values) { | ||
this.template = template; | ||
this.values = values; | ||
this.id = id++; | ||
this.index = 0; | ||
} | ||
read(deep) { | ||
let buffer = emptyStringBuffer; | ||
let chunk, chunks; | ||
/** | ||
* Consume template result content. | ||
* *Note* that instances may only be read once, | ||
* and will be destroyed upon completion. | ||
* | ||
* @param { boolean } deep - recursively resolve nested TemplateResults | ||
* @returns { any } | ||
*/ | ||
read(deep) { | ||
let buffer = emptyStringBuffer; | ||
let chunk, chunks; | ||
while ((chunk = this.readChunk()) !== null) { | ||
if (Buffer.isBuffer(chunk)) { | ||
buffer = Buffer.concat([buffer, chunk], buffer.length + chunk.length); | ||
} else { | ||
if (chunks === undefined) { | ||
chunks = []; | ||
} | ||
buffer = reduce(buffer, chunks, chunk, deep); | ||
while ((chunk = this.readChunk()) !== null) { | ||
if (Buffer.isBuffer(chunk)) { | ||
buffer = Buffer.concat([buffer, chunk], buffer.length + chunk.length); | ||
} else { | ||
if (chunks === undefined) { | ||
chunks = []; | ||
} | ||
buffer = reduce(buffer, chunks, chunk, deep); | ||
} | ||
} | ||
if (chunks !== undefined) { | ||
chunks.push(buffer); | ||
return chunks.length > 1 ? chunks : chunks[0]; | ||
} | ||
return buffer; | ||
if (chunks !== undefined) { | ||
chunks.push(buffer); | ||
return chunks.length > 1 ? chunks : chunks[0]; | ||
} | ||
/** | ||
* Consume template result content one chunk at a time. | ||
* *Note* that instances may only be read once, | ||
* and will be destroyed when the last chunk is read. | ||
* | ||
* @returns { any } | ||
*/ | ||
readChunk() { | ||
const isString = this.index % 2 === 0; | ||
const index = (this.index / 2) | 0; | ||
return buffer; | ||
} | ||
// Finished | ||
if (!isString && index >= this.template.strings.length - 1) { | ||
this.destroy(); | ||
return null; | ||
} | ||
/** | ||
* Consume template result content one chunk at a time. | ||
* *Note* that instances may only be read once, | ||
* and will be destroyed when the last chunk is read. | ||
* | ||
* @returns { any } | ||
*/ | ||
readChunk() { | ||
const isString = this.index % 2 === 0; | ||
const index = (this.index / 2) | 0; | ||
this.index++; | ||
// Finished | ||
if (!isString && index >= this.template.strings.length - 1) { | ||
this.destroy(); | ||
return null; | ||
} | ||
if (isString) { | ||
return this.template.strings[index]; | ||
} | ||
this.index++; | ||
const part = this.template.parts[index]; | ||
let value; | ||
if (isString) { | ||
return this.template.strings[index]; | ||
} | ||
if (part instanceof AttributePart) { | ||
// AttributeParts can have multiple values, so slice based on length | ||
// (strings in-between values are already handled the instance) | ||
if (part.length > 1) { | ||
value = part.getValue(this.values.slice(index, index + part.length)); | ||
this.index += part.length; | ||
} else { | ||
value = part.getValue([this.values[index]]); | ||
} | ||
const part = this.template.parts[index]; | ||
let value; | ||
if (part instanceof AttributePart) { | ||
// AttributeParts can have multiple values, so slice based on length | ||
// (strings in-between values are already handled the instance) | ||
if (part.length > 1) { | ||
value = part.getValue(this.values.slice(index, index + part.length)); | ||
this.index += part.length; | ||
} else { | ||
value = part.getValue(this.values[index]); | ||
value = part.getValue([this.values[index]]); | ||
} | ||
return value; | ||
} else { | ||
value = part.getValue(this.values[index]); | ||
} | ||
/** | ||
* Destroy the instance, | ||
* returning it to the object pool | ||
* | ||
* @param { boolean } permanent - permanently destroy instance and it's children | ||
* @returns { void } | ||
*/ | ||
destroy(permanent) { | ||
if (this.values !== undefined) { | ||
if (permanent) { | ||
for (const value of this.values) { | ||
if (isTemplateResult(value)) { | ||
value.destroy(permanent); | ||
} | ||
return value; | ||
} | ||
/** | ||
* Destroy the instance, | ||
* returning it to the object pool | ||
* | ||
* @param { boolean } permanent - permanently destroy instance and it's children | ||
* @returns { void } | ||
*/ | ||
destroy(permanent) { | ||
if (this.values !== undefined) { | ||
if (permanent) { | ||
for (const value of this.values) { | ||
if (isTemplateResult(value)) { | ||
value.destroy(permanent); | ||
} | ||
} | ||
this.values.length = 0; | ||
} | ||
this.values = undefined; | ||
this.template = undefined; | ||
this.index = 0; | ||
if (!permanent) { | ||
pool.push(this); | ||
} | ||
this.values.length = 0; | ||
} | ||
this.values = undefined; | ||
this.template = undefined; | ||
this.index = 0; | ||
if (!permanent) { | ||
pool.push(this); | ||
} | ||
} | ||
} | ||
/** | ||
* Commit "chunk" to string "buffer". | ||
* Returns new "buffer" value. | ||
* | ||
* @param { Buffer } buffer | ||
* @param { Array<any> } chunks | ||
* @param { any } chunk | ||
* @param { boolean } [deep] | ||
* @returns { Buffer } | ||
*/ | ||
function reduce(buffer, chunks, chunk, deep = false) { | ||
if (Buffer.isBuffer(chunk)) { | ||
return Buffer.concat([buffer, chunk], buffer.length + chunk.length); | ||
} else if (isTemplateResult(chunk)) { | ||
if (deep) { | ||
return reduce(buffer, chunks, chunk.read(deep), deep); | ||
} else { | ||
chunks.push(buffer, chunk); | ||
return emptyStringBuffer; | ||
} | ||
} else if (Array.isArray(chunk)) { | ||
return chunk.reduce((buffer, chunk) => reduce(buffer, chunks, chunk), buffer); | ||
} else if (isPromise(chunk) || isAsyncIterator(chunk)) { | ||
/** | ||
* Commit "chunk" to string "buffer". | ||
* Returns new "buffer" value. | ||
* | ||
* @param { Buffer } buffer | ||
* @param { Array<any> } chunks | ||
* @param { any } chunk | ||
* @param { boolean } [deep] | ||
* @returns { Buffer } | ||
*/ | ||
function reduce(buffer, chunks, chunk, deep = false) { | ||
if (Buffer.isBuffer(chunk)) { | ||
return Buffer.concat([buffer, chunk], buffer.length + chunk.length); | ||
} else if (isTemplateResult(chunk)) { | ||
if (deep) { | ||
return reduce(buffer, chunks, chunk.read(deep), deep); | ||
} else { | ||
chunks.push(buffer, chunk); | ||
return emptyStringBuffer; | ||
} | ||
} else if (Array.isArray(chunk)) { | ||
return chunk.reduce((buffer, chunk) => reduce(buffer, chunks, chunk), buffer); | ||
} else if (isPromise(chunk) || isAsyncIterator(chunk)) { | ||
chunks.push(buffer, chunk); | ||
return emptyStringBuffer; | ||
} | ||
} | ||
/** | ||
* @typedef TemplateProcessor | ||
* @property { (name: string, strings: Array<string>) => AttributePart } handleAttributeExpressions | ||
* @property { () => NodePart } handleTextExpression | ||
*/ | ||
/** | ||
* @typedef TemplateProcessor | ||
* @property { (name: string, strings: Array<string>) => AttributePart } handleAttributeExpressions | ||
* @property { () => NodePart } handleTextExpression | ||
*/ | ||
/** | ||
* Class representing the default Template processor. | ||
* Exposes factory functions for generating Part instances to use for | ||
* resolving a template's dynamic values. | ||
*/ | ||
class DefaultTemplateProcessor { | ||
/** | ||
* Class representing the default Template processor. | ||
* Exposes factory functions for generating Part instances to use for | ||
* resolving a template's dynamic values. | ||
* Create part instance for dynamic attribute values | ||
* | ||
* @param { string } name | ||
* @param { Array<string> } strings | ||
* @returns { AttributePart } | ||
*/ | ||
class DefaultTemplateProcessor { | ||
/** | ||
* Create part instance for dynamic attribute values | ||
* | ||
* @param { string } name | ||
* @param { Array<string> } strings | ||
* @returns { AttributePart } | ||
*/ | ||
handleAttributeExpressions(name, strings = []) { | ||
const prefix = name[0]; | ||
handleAttributeExpressions(name, strings = []) { | ||
const prefix = name[0]; | ||
if (prefix === '.') { | ||
return new PropertyAttributePart(name.slice(1), strings); | ||
} else if (prefix === '@') { | ||
return new EventAttributePart(name.slice(1), strings); | ||
} else if (prefix === '?') { | ||
return new BooleanAttributePart(name.slice(1), strings); | ||
} | ||
return new AttributePart(name, strings); | ||
if (prefix === '.') { | ||
return new PropertyAttributePart(name.slice(1), strings); | ||
} else if (prefix === '@') { | ||
return new EventAttributePart(name.slice(1), strings); | ||
} else if (prefix === '?') { | ||
return new BooleanAttributePart(name.slice(1), strings); | ||
} | ||
/** | ||
* Create part instance for dynamic text values | ||
* | ||
* @returns { NodePart } | ||
*/ | ||
handleTextExpression() { | ||
return new NodePart(); | ||
} | ||
return new AttributePart(name, strings); | ||
} | ||
/** | ||
* @typedef TemplateResultProcessor | ||
* @property { (renderer: TemplateResultRenderer, stack: Array<any>, [highWaterMark: number]) => function } getProcessor | ||
* Create part instance for dynamic text values | ||
* | ||
* @returns { NodePart } | ||
*/ | ||
handleTextExpression() { | ||
return new NodePart(); | ||
} | ||
} | ||
/* eslint no-constant-condition:0 */ | ||
/** | ||
* Class for the default TemplateResult processor | ||
* used by Promise/Stream TemplateRenderers. | ||
* | ||
* @implements TemplateResultProcessor | ||
*/ | ||
class DefaultTemplateResultProcessor { | ||
/** | ||
* Class for the default TemplateResult processor | ||
* used by Promise/Stream TemplateRenderers. | ||
* Process "stack" and push chunks to "renderer" | ||
* | ||
* @implements TemplateResultProcessor | ||
* @param { TemplateResultRenderer } renderer | ||
* @param { Array<any> } stack | ||
* @param { number } [highWaterMark] - byte length to buffer before pushing data | ||
* @returns { () => void } | ||
*/ | ||
class DefaultTemplateResultProcessor { | ||
/** | ||
* Process "stack" and push chunks to "renderer" | ||
* | ||
* @param { TemplateResultRenderer } renderer | ||
* @param { Array<any> } stack | ||
* @param { number } [highWaterMark] - byte length to buffer before pushing data | ||
* @returns { () => void } | ||
*/ | ||
getProcessor(renderer, stack, highWaterMark = 0) { | ||
const buffer = []; | ||
let bufferLength = 0; | ||
let paused = false; | ||
getProcessor(renderer, stack, highWaterMark = 0) { | ||
const buffer = []; | ||
let bufferLength = 0; | ||
let processing = false; | ||
function flushBuffer() { | ||
if (buffer.length > 0) { | ||
const keepPushing = renderer.push(Buffer.concat(buffer, bufferLength)); | ||
function flushBuffer() { | ||
if (buffer.length > 0) { | ||
const keepPushing = renderer.push(Buffer.concat(buffer, bufferLength)); | ||
bufferLength = buffer.length = 0; | ||
return keepPushing; | ||
} | ||
bufferLength = buffer.length = 0; | ||
return keepPushing; | ||
} | ||
} | ||
return function process() { | ||
while (!paused) { | ||
let chunk = stack[0]; | ||
let breakLoop = false; | ||
let popStack = true; | ||
return function process() { | ||
if (processing) { | ||
return; | ||
} | ||
if (chunk === undefined) { | ||
flushBuffer(); | ||
return renderer.push(null); | ||
} | ||
while (true) { | ||
processing = true; | ||
let chunk = stack[0]; | ||
let breakLoop = false; | ||
let popStack = true; | ||
if (isTemplateResult(chunk)) { | ||
popStack = false; | ||
chunk = getTemplateResultChunk(chunk, stack); | ||
} | ||
// Done | ||
if (chunk === undefined) { | ||
flushBuffer(); | ||
return renderer.push(null); | ||
} | ||
// Skip if finished reading TemplateResult (null) | ||
if (chunk !== null) { | ||
if (Buffer.isBuffer(chunk)) { | ||
buffer.push(chunk); | ||
bufferLength += chunk.length; | ||
// Flush buffered data if over highWaterMark | ||
if (bufferLength > highWaterMark) { | ||
// Pause if backpressure triggered | ||
breakLoop = !flushBuffer(); | ||
} | ||
} else if (isPromise(chunk)) { | ||
// Flush buffered data before waiting for Promise | ||
flushBuffer(); | ||
breakLoop = true; | ||
paused = true; | ||
// Add pending Promise for value to stack | ||
stack.unshift(chunk); | ||
chunk | ||
.then((chunk) => { | ||
paused = false; | ||
// Handle IteratorResults from AsyncIterator | ||
if (isIteratorResult(chunk)) { | ||
if (chunk.done) { | ||
// Clear resolved Promise | ||
stack.shift(); | ||
// Clear AsyncIterator | ||
stack.shift(); | ||
} else { | ||
// Replace resolved Promise with IteratorResult value | ||
stack[0] = chunk.value; | ||
} | ||
if (isTemplateResult(chunk)) { | ||
popStack = false; | ||
chunk = getTemplateResultChunk(chunk, stack); | ||
} | ||
// Skip if finished reading TemplateResult (null) | ||
if (chunk !== null) { | ||
if (Buffer.isBuffer(chunk)) { | ||
buffer.push(chunk); | ||
bufferLength += chunk.length; | ||
// Flush buffered data if over highWaterMark | ||
if (bufferLength > highWaterMark) { | ||
// Break if backpressure triggered | ||
breakLoop = !flushBuffer(); | ||
processing = !breakLoop; | ||
} | ||
} else if (isPromise(chunk)) { | ||
// Flush buffered data before waiting for Promise | ||
flushBuffer(); | ||
// "processing" is still true, so prevented from restarting until Promise resolved | ||
breakLoop = true; | ||
// Add pending Promise for value to stack | ||
stack.unshift(chunk); | ||
chunk | ||
.then((chunk) => { | ||
// Handle IteratorResults from AsyncIterator | ||
if (isIteratorResult(chunk)) { | ||
if (chunk.done) { | ||
// Clear resolved Promise | ||
stack.shift(); | ||
// Clear AsyncIterator | ||
stack.shift(); | ||
} else { | ||
// Replace resolved Promise with value | ||
stack[0] = chunk; | ||
// Replace resolved Promise with IteratorResult value | ||
stack[0] = chunk.value; | ||
} | ||
process(); | ||
}) | ||
.catch((err) => { | ||
destroy(stack); | ||
renderer.destroy(err); | ||
}); | ||
} else if (Array.isArray(chunk)) { | ||
// First remove existing Array if at top of stack (not added by pending TemplateResult) | ||
if (stack[0] === chunk) { | ||
popStack = false; | ||
stack.shift(); | ||
} | ||
stack.unshift(...chunk); | ||
} else if (isAsyncIterator(chunk)) { | ||
} else { | ||
// Replace resolved Promise with value | ||
stack[0] = chunk; | ||
} | ||
processing = false; | ||
process(); | ||
}) | ||
.catch((err) => { | ||
destroy(stack); | ||
renderer.destroy(err); | ||
}); | ||
} else if (Array.isArray(chunk)) { | ||
// First remove existing Array if at top of stack (not added by pending TemplateResult) | ||
if (stack[0] === chunk) { | ||
popStack = false; | ||
// Add AsyncIterator to stack (will be cleared when done iterating) | ||
if (stack[0] !== chunk) { | ||
stack.unshift(chunk); | ||
} | ||
// Add pending Promise for IteratorResult to stack | ||
stack.unshift(chunk[Symbol.asyncIterator]().next()); | ||
} else { | ||
destroy(stack); | ||
return renderer.destroy(Error(`unknown chunk type: ${chunk}`)); | ||
stack.shift(); | ||
} | ||
stack.unshift(...chunk); | ||
} else if (isAsyncIterator(chunk)) { | ||
popStack = false; | ||
// Add AsyncIterator back to stack (will be cleared when done iterating) | ||
if (stack[0] !== chunk) { | ||
stack.unshift(chunk); | ||
} | ||
// Add pending Promise for IteratorResult to stack | ||
stack.unshift(chunk[Symbol.asyncIterator]().next()); | ||
} else { | ||
destroy(stack); | ||
return renderer.destroy(Error(`unknown chunk type: ${chunk}`)); | ||
} | ||
} | ||
if (popStack) { | ||
stack.shift(); | ||
} | ||
if (popStack) { | ||
stack.shift(); | ||
} | ||
if (breakLoop) { | ||
break; | ||
} | ||
if (breakLoop) { | ||
break; | ||
} | ||
}; | ||
} | ||
} | ||
}; | ||
} | ||
} | ||
/** | ||
* Permanently destroy all remaining TemplateResults in "stack". | ||
* (Triggered on error) | ||
* | ||
* @param { Array<any> } stack | ||
*/ | ||
function destroy(stack) { | ||
if (stack.length > 0) { | ||
for (const chunk of stack) { | ||
if (isTemplateResult(chunk)) { | ||
chunk.destroy(true); | ||
} | ||
/** | ||
* Permanently destroy all remaining TemplateResults in "stack". | ||
* (Triggered on error) | ||
* | ||
* @param { Array<any> } stack | ||
*/ | ||
function destroy(stack) { | ||
if (stack.length > 0) { | ||
for (const chunk of stack) { | ||
if (isTemplateResult(chunk)) { | ||
chunk.destroy(true); | ||
} | ||
} | ||
stack.length = 0; | ||
} | ||
stack.length = 0; | ||
} | ||
/** | ||
* Retrieve next chunk from "result". | ||
* Adds nested TemplateResults to the stack if necessary. | ||
* | ||
* @param { TemplateResult } result | ||
* @param { Array<any> } stack | ||
*/ | ||
function getTemplateResultChunk(result, stack) { | ||
let chunk = result.readChunk(); | ||
// Skip empty strings | ||
if (Buffer.isBuffer(chunk) && chunk.length === 0) { | ||
chunk = result.readChunk(); | ||
} | ||
// Finished reading, dispose | ||
if (chunk === null) { | ||
stack.shift(); | ||
} else if (isTemplateResult(chunk)) { | ||
// Add to top of stack | ||
stack.unshift(chunk); | ||
chunk = getTemplateResultChunk(chunk, stack); | ||
} | ||
return chunk; | ||
} | ||
/** | ||
* @typedef TemplateResult { import('./template-result.js).TemplateResult } | ||
* @typedef TemplateResultProcessor { import('./default-template-result-processor.js).TemplateResultProcessor } | ||
* @typedef TemplateResultRenderer { import('./default-template-result-renderer.js).TemplateResultRenderer } | ||
*/ | ||
/** | ||
* A class for rendering a template result to a string resolving Promise | ||
*/ | ||
class PromiseTemplateRenderer { | ||
/** | ||
* Retrieve next chunk from "result". | ||
* Adds nested TemplateResults to the stack if necessary. | ||
* Constructor | ||
* | ||
* @param { TemplateResult } result | ||
* @param { Array<any> } stack | ||
* @param { TemplateResultProcessor } processor | ||
* @param { boolean } [asBuffer] | ||
* @returns { Promise<string> } | ||
*/ | ||
function getTemplateResultChunk(result, stack) { | ||
let chunk = result.readChunk(); | ||
constructor(result, processor, asBuffer = false) { | ||
return new Promise((resolve, reject) => { | ||
let stack = [result]; | ||
let buffer = []; | ||
let bufferLength = 0; | ||
// Skip empty strings | ||
if (Buffer.isBuffer(chunk) && chunk.length === 0) { | ||
chunk = result.readChunk(); | ||
} | ||
processor.getProcessor( | ||
{ | ||
push(chunk) { | ||
if (chunk === null) { | ||
buffer = Buffer.concat(buffer, bufferLength); | ||
resolve(asBuffer ? buffer : buffer.toString()); | ||
} else { | ||
buffer.push(chunk); | ||
bufferLength += chunk.length; | ||
} | ||
return true; | ||
}, | ||
destroy(err) { | ||
buffer.length = stack.length = bufferLength = 0; | ||
buffer = undefined; | ||
stack = undefined; | ||
reject(err); | ||
} | ||
}, | ||
stack | ||
)(); | ||
}); | ||
} | ||
} | ||
// Finished reading, dispose | ||
if (chunk === null) { | ||
stack.shift(); | ||
} else if (isTemplateResult(chunk)) { | ||
// Add to top of stack | ||
stack.unshift(chunk); | ||
chunk = getTemplateResultChunk(chunk, stack); | ||
} | ||
/* global ReadableStream */ | ||
return chunk; | ||
} | ||
/** | ||
* A custom Readable stream class for rendering a template result to a stream | ||
* | ||
* @implements TemplateResultRenderer | ||
*/ | ||
class StreamTemplateRenderer { | ||
/** | ||
* @typedef TemplateResult { import('./template-result.js).TemplateResult } | ||
* Constructor | ||
* | ||
* @param { TemplateResult } result - a template result returned from call to "html`...`" | ||
* @param { TemplateResultProcessor } processor | ||
* @returns { ReadableStream } | ||
*/ | ||
/** | ||
* @typedef TemplateResultProcessor { import('./default-template-result-processor.js).TemplateResultProcessor } | ||
*/ | ||
/** | ||
* @typedef TemplateResultRenderer { import('./default-template-result-renderer.js).TemplateResultRenderer } | ||
*/ | ||
constructor(result, processor) { | ||
if (typeof ReadableStream === 'undefined') { | ||
throw Error('ReadableStream not supported on this platform'); | ||
} | ||
if (typeof TextEncoder === 'undefined') { | ||
throw Error('TextEncoder not supported on this platform'); | ||
} | ||
/** | ||
* A class for rendering a template result to a string resolving Promise | ||
*/ | ||
class PromiseTemplateRenderer { | ||
/** | ||
* Constructor | ||
* | ||
* @param { TemplateResult } result | ||
* @param { TemplateResultProcessor } processor | ||
* @param { boolean } [asBuffer] | ||
* @returns { Promise<string> } | ||
*/ | ||
constructor(result, processor, asBuffer = false) { | ||
return new Promise((resolve, reject) => { | ||
return new ReadableStream({ | ||
process: null, | ||
start(controller) { | ||
const encoder = new TextEncoder(); | ||
const underlyingSource = this; | ||
let stack = [result]; | ||
let buffer = []; | ||
let bufferLength = 0; | ||
processor.getProcessor( | ||
this.process = processor.getProcessor( | ||
{ | ||
push(chunk) { | ||
if (chunk === null) { | ||
buffer = Buffer.concat(buffer, bufferLength); | ||
resolve(asBuffer ? buffer : buffer.toString()); | ||
} else { | ||
buffer.push(chunk); | ||
bufferLength += chunk.length; | ||
controller.close(); | ||
return false; | ||
} | ||
return true; | ||
controller.enqueue(encoder.encode(chunk.toString())); | ||
// Pause processing (return "false") if stream is full | ||
return controller.desiredSize > 0; | ||
}, | ||
destroy(err) { | ||
buffer.length = stack.length = bufferLength = 0; | ||
buffer = undefined; | ||
controller.error(err); | ||
underlyingSource.process = undefined; | ||
stack = undefined; | ||
reject(err); | ||
} | ||
}, | ||
stack | ||
)(); | ||
}); | ||
} | ||
stack, | ||
16384 | ||
); | ||
}, | ||
pull() { | ||
this.process(); | ||
} | ||
}); | ||
} | ||
} | ||
/** | ||
* @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 | ||
*/ | ||
/** | ||
* An expression marker with embedded unique key to avoid collision with | ||
* possible text in templates. | ||
*/ | ||
const marker = `{{lit-${String(Math.random()).slice(2)}}}`; | ||
/** | ||
* This regex extracts the attribute name preceding an attribute-position | ||
* expression. It does this by matching the syntax allowed for attributes | ||
* against the string literal directly preceding the expression, assuming that | ||
* the expression is in an attribute-value position. | ||
* | ||
* See attributes in the HTML spec: | ||
* https://www.w3.org/TR/html5/syntax.html#attributes-0 | ||
* | ||
* "\0-\x1F\x7F-\x9F" are Unicode control characters | ||
* | ||
* " \x09\x0a\x0c\x0d" are HTML space characters: | ||
* https://www.w3.org/TR/html5/infrastructure.html#space-character | ||
* | ||
* So an attribute is: | ||
* * The name: any character except a control character, space character, ('), | ||
* ("), ">", "=", or "/" | ||
* * Followed by zero or more space characters | ||
* * Followed by "=" | ||
* * Followed by zero or more space characters | ||
* * Followed by: | ||
* * Any character except space, ('), ("), "<", ">", "=", (`), or | ||
* * (") then any non-("), or | ||
* * (') then any non-(') | ||
*/ | ||
const lastAttributeNameRegex = /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F \x09\x0a\x0c\x0d"'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/; | ||
/** | ||
* @typedef TemplateProcessor { import('./default-template-processor.js').TemplateProcessor } | ||
*/ | ||
const RE_QUOTE = /"[^"]*|'[^']*$/; | ||
/** | ||
* A cacheable Template that stores the "strings" and "parts" associated with a | ||
* tagged template literal invoked with "html`...`". | ||
*/ | ||
class Template { | ||
/** | ||
* @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 | ||
*/ | ||
/** | ||
* An expression marker with embedded unique key to avoid collision with | ||
* possible text in templates. | ||
*/ | ||
const marker = `{{lit-${String(Math.random()).slice(2)}}}`; | ||
/** | ||
* This regex extracts the attribute name preceding an attribute-position | ||
* expression. It does this by matching the syntax allowed for attributes | ||
* against the string literal directly preceding the expression, assuming that | ||
* the expression is in an attribute-value position. | ||
* Create Template instance | ||
* | ||
* See attributes in the HTML spec: | ||
* https://www.w3.org/TR/html5/syntax.html#attributes-0 | ||
* | ||
* "\0-\x1F\x7F-\x9F" are Unicode control characters | ||
* | ||
* " \x09\x0a\x0c\x0d" are HTML space characters: | ||
* https://www.w3.org/TR/html5/infrastructure.html#space-character | ||
* | ||
* So an attribute is: | ||
* * The name: any character except a control character, space character, ('), | ||
* ("), ">", "=", or "/" | ||
* * Followed by zero or more space characters | ||
* * Followed by "=" | ||
* * Followed by zero or more space characters | ||
* * Followed by: | ||
* * Any character except space, ('), ("), "<", ">", "=", (`), or | ||
* * (") then any non-("), or | ||
* * (') then any non-(') | ||
* @param { Array<string> } strings | ||
* @param { TemplateProcessor } processor | ||
*/ | ||
const lastAttributeNameRegex = /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F \x09\x0a\x0c\x0d"'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/; | ||
constructor(strings, processor) { | ||
this.strings = []; | ||
this.parts = []; | ||
/** | ||
* @typedef TemplateProcessor { import('./default-template-processor.js').TemplateProcessor } | ||
*/ | ||
this._prepare(strings, processor); | ||
} | ||
const RE_QUOTE = /"[^"]*|'[^']*$/; | ||
/** | ||
* A cacheable Template that stores the "strings" and "parts" associated with a | ||
* tagged template literal invoked with "html`...`". | ||
* Prepare the template's static strings, | ||
* and create Part instances for the dynamic values, | ||
* based on lit-html syntax. | ||
* | ||
* @param { Array<string> } strings | ||
* @param { TemplateProcessor } processor | ||
*/ | ||
class Template$1 { | ||
/** | ||
* Create Template instance | ||
* | ||
* @param { Array<string> } strings | ||
* @param { TemplateProcessor } processor | ||
*/ | ||
constructor(strings, processor) { | ||
this.strings = []; | ||
this.parts = []; | ||
_prepare(strings, processor) { | ||
const endIndex = strings.length - 1; | ||
let attributeMode = false; | ||
let nextString = strings[0]; | ||
this._prepare(strings, processor); | ||
} | ||
for (let i = 0; i < endIndex; i++) { | ||
let string = nextString; | ||
nextString = strings[i + 1]; | ||
const tagState = getTagState(string); | ||
let skip = 0; | ||
let part; | ||
/** | ||
* Prepare the template's static strings, | ||
* and create Part instances for the dynamic values, | ||
* based on lit-html syntax. | ||
* | ||
* @param { Array<string> } strings | ||
* @param { TemplateProcessor } processor | ||
*/ | ||
_prepare(strings, processor) { | ||
const endIndex = strings.length - 1; | ||
let attributeMode = false; | ||
let nextString = strings[0]; | ||
// Open/close tag found at end of string | ||
if (tagState !== -1) { | ||
attributeMode = tagState === 1; | ||
} | ||
for (let i = 0; i < endIndex; i++) { | ||
let string = nextString; | ||
nextString = strings[i + 1]; | ||
const tagState = getTagState(string); | ||
let skip = 0; | ||
let part; | ||
if (attributeMode) { | ||
const matchName = lastAttributeNameRegex.exec(string); | ||
// Open/close tag found at end of string | ||
if (tagState !== -1) { | ||
attributeMode = tagState === 1; | ||
} | ||
if (matchName) { | ||
let [, prefix, name, suffix] = matchName; | ||
if (attributeMode) { | ||
const matchName = lastAttributeNameRegex.exec(string); | ||
// Since attributes are conditional, remove "name" and "suffix" from static string | ||
string = string.slice(0, matchName.index + prefix.length); | ||
if (matchName) { | ||
let [, prefix, name, suffix] = matchName; | ||
const matchQuote = RE_QUOTE.exec(suffix); | ||
// Since attributes are conditional, remove "name" and "suffix" from static string | ||
string = string.slice(0, matchName.index + prefix.length); | ||
// If attribute is quoted, handle potential multiple values | ||
if (matchQuote) { | ||
const quoteCharacter = matchQuote[0].charAt(0); | ||
// Store any text between quote character and value | ||
const attributeStrings = [Buffer.from(suffix.slice(matchQuote.index + 1))]; | ||
let open = true; | ||
skip = 0; | ||
let attributeString; | ||
const matchQuote = RE_QUOTE.exec(suffix); | ||
// Scan ahead and gather all strings for this attribute | ||
while (open) { | ||
attributeString = strings[i + skip + 1]; | ||
const closingQuoteIndex = attributeString.indexOf(quoteCharacter); | ||
// If attribute is quoted, handle potential multiple values | ||
if (matchQuote) { | ||
const quoteCharacter = matchQuote[0].charAt(0); | ||
// Store any text between quote character and value | ||
const attributeStrings = [Buffer.from(suffix.slice(matchQuote.index + 1))]; | ||
let open = true; | ||
skip = 0; | ||
let attributeString; | ||
// Scan ahead and gather all strings for this attribute | ||
while (open) { | ||
attributeString = strings[i + skip + 1]; | ||
const closingQuoteIndex = attributeString.indexOf(quoteCharacter); | ||
if (closingQuoteIndex === -1) { | ||
attributeStrings.push(Buffer.from(attributeString)); | ||
skip++; | ||
} else { | ||
attributeStrings.push(Buffer.from(attributeString.slice(0, closingQuoteIndex))); | ||
nextString = attributeString.slice(closingQuoteIndex + 1); | ||
i += skip; | ||
open = false; | ||
} | ||
if (closingQuoteIndex === -1) { | ||
attributeStrings.push(Buffer.from(attributeString)); | ||
skip++; | ||
} else { | ||
attributeStrings.push(Buffer.from(attributeString.slice(0, closingQuoteIndex))); | ||
nextString = attributeString.slice(closingQuoteIndex + 1); | ||
i += skip; | ||
open = false; | ||
} | ||
} | ||
part = processor.handleAttributeExpressions(name, attributeStrings); | ||
} else { | ||
part = processor.handleAttributeExpressions(name, [ | ||
emptyStringBuffer, | ||
emptyStringBuffer | ||
]); | ||
} | ||
part = processor.handleAttributeExpressions(name, attributeStrings); | ||
} else { | ||
part = processor.handleAttributeExpressions(name, [ | ||
emptyStringBuffer, | ||
emptyStringBuffer | ||
]); | ||
} | ||
} else { | ||
part = processor.handleTextExpression(); | ||
} | ||
} else { | ||
part = processor.handleTextExpression(); | ||
} | ||
this.strings.push(Buffer.from(string)); | ||
this.parts.push(part); | ||
// Add placehholders for strings/parts that wil be skipped due to multple values in a single AttributePart | ||
if (skip > 0) { | ||
this.strings.push(null); | ||
this.parts.push(null); | ||
skip = 0; | ||
} | ||
this.strings.push(Buffer.from(string)); | ||
this.parts.push(part); | ||
// Add placehholders for strings/parts that wil be skipped due to multple values in a single AttributePart | ||
if (skip > 0) { | ||
this.strings.push(null); | ||
this.parts.push(null); | ||
skip = 0; | ||
} | ||
} | ||
this.strings.push(Buffer.from(nextString)); | ||
} | ||
this.strings.push(Buffer.from(nextString)); | ||
} | ||
} | ||
/** | ||
* Determine if 'string' terminates with an opened or closed tag. | ||
* | ||
* Iterating through all characters has at worst a time complexity of O(n), | ||
* and is better than the alternative (using "indexOf/lastIndexOf") which is potentially O(2n). | ||
* | ||
* @param { string } string | ||
* @returns { number } - returns "-1" if no tag, "0" if closed tag, or "1" if open tag | ||
*/ | ||
function getTagState(string) { | ||
for (let i = string.length - 1; i >= 0; i--) { | ||
const char = string[i]; | ||
/** | ||
* Determine if 'string' terminates with an opened or closed tag. | ||
* | ||
* Iterating through all characters has at worst a time complexity of O(n), | ||
* and is better than the alternative (using "indexOf/lastIndexOf") which is potentially O(2n). | ||
* | ||
* @param { string } string | ||
* @returns { number } - returns "-1" if no tag, "0" if closed tag, or "1" if open tag | ||
*/ | ||
function getTagState(string) { | ||
for (let i = string.length - 1; i >= 0; i--) { | ||
const char = string[i]; | ||
if (char === '>') { | ||
return 0; | ||
} else if (char === '<') { | ||
return 1; | ||
} | ||
if (char === '>') { | ||
return 0; | ||
} else if (char === '<') { | ||
return 1; | ||
} | ||
return -1; | ||
} | ||
/** | ||
* @typedef Readable { import('stream').Readable } | ||
* @typedef TemplateResult { import('./template-result.js).TemplateResult } | ||
*/ | ||
return -1; | ||
} | ||
const defaultTemplateProcessor = new DefaultTemplateProcessor(); | ||
const defaultTemplateResultProcessor = new DefaultTemplateResultProcessor(); | ||
const templateCache = new Map(); | ||
/** | ||
* @typedef TemplateResult { import('./template-result.js).TemplateResult } | ||
*/ | ||
/** | ||
* Interprets a template literal as an HTML template that can be | ||
* rendered as a Readable stream or String | ||
* | ||
* @param { Array<string> } strings | ||
* @param { ...any } values | ||
* @returns { TemplateResult } | ||
*/ | ||
function html(strings, ...values) { | ||
let template = templateCache.get(strings); | ||
const defaultTemplateProcessor = new DefaultTemplateProcessor(); | ||
const defaultTemplateResultProcessor = new DefaultTemplateResultProcessor(); | ||
const templateCache = new Map(); | ||
if (template === undefined) { | ||
template = new Template$1(strings, defaultTemplateProcessor); | ||
templateCache.set(strings, template); | ||
} | ||
/** | ||
* Interprets a template literal as an HTML template that can be | ||
* rendered as a Readable stream or String | ||
* | ||
* @param { Array<string> } strings | ||
* @param { ...any } values | ||
* @returns { TemplateResult } | ||
*/ | ||
function html(strings, ...values) { | ||
let template = templateCache.get(strings); | ||
return templateResult(template, values); | ||
if (template === undefined) { | ||
template = new Template(strings, defaultTemplateProcessor); | ||
templateCache.set(strings, template); | ||
} | ||
/** | ||
* Render a template result to a string resolving Promise. | ||
* *Note* that TemplateResults are single use, and can only be rendered once. | ||
* | ||
* @param { TemplateResult } result - a template result returned from call to "html`...`" | ||
* @returns { Promise<string> } | ||
*/ | ||
function renderToString(result) { | ||
return new PromiseTemplateRenderer(result, defaultTemplateResultProcessor, false); | ||
} | ||
return templateResult(template, values); | ||
} | ||
// TODO: renderToStream using browser ReadableStream | ||
/** | ||
* Render a template result to a Readable stream | ||
* *Note* that TemplateResults are single use, and can only be rendered once. | ||
* | ||
* @param { TemplateResult } result - a template result returned from call to "html`...`" | ||
* @returns { Readable } | ||
*/ | ||
function renderToStream(result) { | ||
return new StreamTemplateRenderer(result, defaultTemplateResultProcessor); | ||
} | ||
exports.defaultTemplateProcessor = defaultTemplateProcessor; | ||
exports.defaultTemplateResultProcessor = defaultTemplateResultProcessor; | ||
exports.html = html; | ||
exports.svg = html; | ||
exports.isTemplateResult = isTemplateResult; | ||
exports.renderToString = renderToString; | ||
exports.templateCache = templateCache; | ||
exports.isAttributePart = isAttributePart; | ||
exports.isNodePart = isNodePart; | ||
exports.nothingString = nothingString; | ||
exports.unsafePrefixString = unsafePrefixString; | ||
exports.directive = directive; | ||
/** | ||
* Render a template result to a string resolving Promise. | ||
* *Note* that TemplateResults are single use, and can only be rendered once. | ||
* | ||
* @param { TemplateResult } result - a template result returned from call to "html`...`" | ||
* @returns { Promise<string> } | ||
*/ | ||
function renderToString(result) { | ||
return new PromiseTemplateRenderer(result, defaultTemplateResultProcessor, false); | ||
} | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
})); | ||
export { defaultTemplateProcessor, defaultTemplateResultProcessor, directive, html, isAttributePart, isNodePart, isTemplateResult, nothingString, renderToStream, renderToString, html as svg, templateCache, unsafePrefixString }; |
54
index.js
@@ -577,5 +577,5 @@ 'use strict'; | ||
*/ | ||
function resolveDirectiveValue(directive$$1, part) { | ||
function resolveDirectiveValue(directive, part) { | ||
// Directives are synchronous, so it's safe to read and delete value | ||
directive$$1(part); | ||
directive(part); | ||
const value = part._value; | ||
@@ -809,6 +809,3 @@ part._value = undefined; | ||
/** | ||
* @typedef TemplateResultProcessor | ||
* @property { (renderer: TemplateResultRenderer, stack: Array<any>, [highWaterMark: number]) => function } getProcessor | ||
*/ | ||
/* eslint no-constant-condition:0 */ | ||
@@ -833,3 +830,3 @@ /** | ||
let bufferLength = 0; | ||
let paused = false; | ||
let processing = false; | ||
@@ -846,3 +843,8 @@ function flushBuffer() { | ||
return function process() { | ||
while (!paused) { | ||
if (processing) { | ||
return; | ||
} | ||
while (true) { | ||
processing = true; | ||
let chunk = stack[0]; | ||
@@ -852,2 +854,3 @@ let breakLoop = false; | ||
// Done | ||
if (chunk === undefined) { | ||
@@ -870,4 +873,5 @@ flushBuffer(); | ||
if (bufferLength > highWaterMark) { | ||
// Pause if backpressure triggered | ||
// Break if backpressure triggered | ||
breakLoop = !flushBuffer(); | ||
processing = !breakLoop; | ||
} | ||
@@ -877,4 +881,4 @@ } else if (isPromise(chunk)) { | ||
flushBuffer(); | ||
// "processing" is still true, so prevented from restarting until Promise resolved | ||
breakLoop = true; | ||
paused = true; | ||
// Add pending Promise for value to stack | ||
@@ -884,3 +888,2 @@ stack.unshift(chunk); | ||
.then((chunk) => { | ||
paused = false; | ||
// Handle IteratorResults from AsyncIterator | ||
@@ -901,2 +904,3 @@ if (isIteratorResult(chunk)) { | ||
} | ||
processing = false; | ||
process(); | ||
@@ -917,3 +921,3 @@ }) | ||
popStack = false; | ||
// Add AsyncIterator to stack (will be cleared when done iterating) | ||
// Add AsyncIterator back to stack (will be cleared when done iterating) | ||
if (stack[0] !== chunk) { | ||
@@ -988,7 +992,3 @@ stack.unshift(chunk); | ||
* @typedef TemplateResult { import('./template-result.js).TemplateResult } | ||
*/ | ||
/** | ||
* @typedef TemplateResultProcessor { import('./default-template-result-processor.js).TemplateResultProcessor } | ||
*/ | ||
/** | ||
* @typedef TemplateResultRenderer { import('./default-template-result-renderer.js).TemplateResultRenderer } | ||
@@ -1042,2 +1042,4 @@ */ | ||
* @typedef TemplateResult { import('./template-result.js).TemplateResult } | ||
* @typedef TemplateResultProcessor { import('./default-template-result-processor.js).TemplateResultProcessor } | ||
* @typedef TemplateResultRenderer { import('./default-template-result-renderer.js).TemplateResultRenderer } | ||
*/ | ||
@@ -1061,3 +1063,4 @@ | ||
this.process = processor.getProcessor(this, [result], 16384); | ||
this.stack = [result]; | ||
this.process = processor.getProcessor(this, this.stack, 16384); | ||
} | ||
@@ -1083,3 +1086,4 @@ | ||
this.process = null; | ||
this.process = undefined; | ||
this.stack = undefined; | ||
this.removeAllListeners(); | ||
@@ -1144,3 +1148,3 @@ } | ||
*/ | ||
class Template$1 { | ||
class Template { | ||
/** | ||
@@ -1290,3 +1294,3 @@ * Create Template instance | ||
if (template === undefined) { | ||
template = new Template$1(strings, defaultTemplateProcessor); | ||
template = new Template(strings, defaultTemplateProcessor); | ||
templateCache.set(strings, template); | ||
@@ -1333,13 +1337,13 @@ } | ||
exports.defaultTemplateResultProcessor = defaultTemplateResultProcessor; | ||
exports.directive = directive; | ||
exports.html = html; | ||
exports.svg = html; | ||
exports.isAttributePart = isAttributePart; | ||
exports.isNodePart = isNodePart; | ||
exports.isTemplateResult = isTemplateResult; | ||
exports.nothingString = nothingString; | ||
exports.renderToBuffer = renderToBuffer; | ||
exports.renderToStream = renderToStream; | ||
exports.renderToString = renderToString; | ||
exports.svg = html; | ||
exports.templateCache = templateCache; | ||
exports.isAttributePart = isAttributePart; | ||
exports.isNodePart = isNodePart; | ||
exports.nothingString = nothingString; | ||
exports.unsafePrefixString = unsafePrefixString; | ||
exports.directive = directive; |
{ | ||
"name": "@popeindustries/lit-html-server", | ||
"version": "1.1.1", | ||
"version": "1.2.0", | ||
"description": "Render lit-html templates on the server", | ||
@@ -15,3 +15,3 @@ "keywords": [ | ||
"module": "index.mjs", | ||
"browser": "browser/index.mjs", | ||
"browser": "browser/index.js", | ||
"repository": "https://github.com/popeindustries/lit-html-server.git", | ||
@@ -22,18 +22,20 @@ "author": "Alexander Pope <alex@pope-industries.com>", | ||
"devDependencies": { | ||
"autocannon": "^3.2.0", | ||
"autocannon": "^3.2.1", | ||
"babel-eslint": "^10.0.1", | ||
"chai": "^4.2.0", | ||
"eslint": "^5.14.1", | ||
"eslint-config-prettier": "^4.0.0", | ||
"eslint": "^5.16.0", | ||
"eslint-config-prettier": "^4.2.0", | ||
"eslint-plugin-prettier": "^3.0.1", | ||
"esm": "^3.2.5", | ||
"get-stream": "^4.1.0", | ||
"husky": "^1.3.1", | ||
"lint-staged": "^8.1.4", | ||
"esm": "^3.2.22", | ||
"get-stream": "^5.1.0", | ||
"husky": "^2.1.0", | ||
"lint-staged": "^8.1.5", | ||
"lit-html": "^1.0.0", | ||
"mocha": "^6.0.1", | ||
"prettier": "^1.16.4", | ||
"rollup": "^1.2.2", | ||
"rollup-plugin-commonjs": "^9.2.0", | ||
"rollup-plugin-node-resolve": "^4.0.1" | ||
"mocha": "^6.1.4", | ||
"prettier": "^1.17.0", | ||
"rollup": "^1.10.1", | ||
"rollup-plugin-commonjs": "^9.3.4", | ||
"rollup-plugin-node-resolve": "^4.2.3", | ||
"text-encoding": "^0.7.0", | ||
"web-streams-polyfill": "^2.0.3" | ||
}, | ||
@@ -40,0 +42,0 @@ "engines": { |
103
README.md
@@ -6,5 +6,5 @@ [](https://npmjs.org/package/@popeindustries/lit-html-server) | ||
Render [**lit-html**](https://polymer.github.io/lit-html/) templates on the server as Node.js streams. Supports all **lit-html** types, special attribute expressions, and many of the standard directives. | ||
Render [**lit-html**](https://polymer.github.io/lit-html/) templates on the server as strings or streams (and in the browser too!). Supports all **lit-html** types, special attribute expressions, and many of the standard directives. | ||
> Although based on **lit-html** semantics, **lit-html-server** is a great general purpose HTML template streaming library. Tagged template literals are a native JavaScript feature, and the HTML rendered is 100% standard markup, with no special syntax or client-side runtime required! | ||
> Although based on **lit-html** semantics, **lit-html-server** is a great general purpose HTML template streaming library. Tagged template literals are a native JavaScript feature, and the HTML rendered is 100% standard markup, with no special syntax or runtime required! | ||
@@ -48,3 +48,3 @@ ## Usage | ||
<x-widget ?enabled="${data.hasWidget}"></x-widget> | ||
<p class="${classMap({ 'negative': data.invertedText }">${data.text}</p> | ||
<p class="${classMap({ negative: data.invertedText })}">${data.text}</p> | ||
`; | ||
@@ -70,9 +70,11 @@ } | ||
## API | ||
## API (Node.js) | ||
### `html` | ||
The tag function to apply to HTML template literals (also aliased as `svg`) | ||
The tag function to apply to HTML template literals (also aliased as `svg`): | ||
```js | ||
const { html } = require('@popeindustries/lit-html-server'); | ||
const name = 'Bob'; | ||
@@ -87,5 +89,7 @@ html` | ||
```js | ||
const { html } = require('@popeindustries/lit-html-server'); | ||
const { unsafeHTML } = require('@popeindustries/lit-html-server/directives/unsafe-html.js'); | ||
html` | ||
<div>${unsafeHTML('<span>dangerous!</span>')}</div> | ||
<div>${unsafeHTML('<span>danger!</span>')}</div> | ||
`; | ||
@@ -96,5 +100,8 @@ ``` | ||
Returns the result of the template tagged by `html` as a Node.js `Readable` stream of markup. | ||
Returns the result of the template tagged by `html` as a Node.js `Readable` stream of markup: | ||
```js | ||
const { html, renderToStream } = require('@popeindustries/lit-html-server'); | ||
const name = 'Bob'; | ||
renderToStream( | ||
@@ -109,5 +116,8 @@ html` | ||
Returns the result of the template tagged by `html` as a Promise which resolves to a string of markup. | ||
Returns the result of the template tagged by `html` as a Promise which resolves to a string of markup: | ||
```js | ||
const { html, renderToString } = require('@popeindustries/lit-html-server'); | ||
const name = 'Bob'; | ||
const markup = await renderToString( | ||
@@ -123,5 +133,8 @@ html` | ||
Returns the result of the template tagged by `html` as a Promise which resolves to a Buffer of markup. | ||
Returns the result of the template tagged by `html` as a Promise which resolves to a Buffer of markup: | ||
```js | ||
const { html, renderToBuffer } = require('@popeindustries/lit-html-server'); | ||
const name = 'Bob'; | ||
const markup = await renderToBuffer( | ||
@@ -135,2 +148,60 @@ html` | ||
## API (Browser) | ||
**lit-html-server** may also be used in the browser to render strings of markup, or in a Service Worker script to render streams of markup. | ||
### `html` | ||
The tag function to apply to HTML template literals (also aliased as `svg`): | ||
```js | ||
import { html } from '@popeindustries/lit-html-server'; | ||
const name = 'Bob'; | ||
html` | ||
<h1>Hello ${name}!</h1> | ||
`; | ||
``` | ||
### `renderToStream(TemplateResult): ReadableStream` | ||
Returns the result of the template tagged by `html` as a `ReadableStream` stream of markup. This may be used in a Service Worker script to stream an html response to the browser: | ||
```js | ||
import { html, renderToStream } from '@popeindustries/lit-html-server'); | ||
self.addEventListener('fetch', (event) => { | ||
const name = 'Bob'; | ||
const stream = renderToStream( | ||
html` | ||
<h1>Hello ${name}!</h1> | ||
` | ||
); | ||
const response = new Response(stream, { | ||
headers: { | ||
'content-type': 'text/html' | ||
} | ||
}); | ||
event.respondWith(response); | ||
}); | ||
``` | ||
> _NOTE: a bundler is required to package modules for use in a Service Worker_ | ||
### `renderToString(TemplateResult): Promise<string>` | ||
Returns the result of the template tagged by `html` as a Promise which resolves to a string of markup: | ||
```js | ||
import { html, renderToString } from '@popeindustries/lit-html-server'; | ||
const name = 'Bob'; | ||
const markup = await renderToString( | ||
html` | ||
<h1>Hello ${name}!</h1> | ||
` | ||
); | ||
document.body.innerHtml = markup; | ||
``` | ||
## Writing templates | ||
@@ -253,4 +324,6 @@ | ||
Most of the built-in **lit-html** [directives](https://polymer.github.io/lit-html/guide/writing-templates.html#directives) are also included for compatibility when using templates on the server and client (even though some directives are no-ops in a server context): | ||
Most of the built-in **lit-html** [directives](https://polymer.github.io/lit-html/guide/writing-templates.html#directives) are also included for compatibility when using templates on the server and client (even though some directives are no-ops in a server rendered context): | ||
> _NOTE: directives for use in the browser are imported from `@popeindustries/lit-html-server/browser/directives`_ | ||
- `asyncAppend(value)`: Renders the items of an AsyncIterable, appending new values after previous values: | ||
@@ -260,2 +333,3 @@ | ||
const asyncAppend = require('@popeindustries/lit-html-server/directives/async-append.js'); | ||
html` | ||
@@ -272,2 +346,3 @@ <ul> | ||
const cache = require('@popeindustries/lit-html-server/directives/cache.js'); | ||
cache( | ||
@@ -288,2 +363,3 @@ loggedIn | ||
const classMap = require('@popeindustries/lit-html-server/directives/class-map.js'); | ||
html` | ||
@@ -298,2 +374,3 @@ <div class="${classMap({ red: true })}"></div> | ||
const guard = require('@popeindustries/lit-html-server/directives/guard.js'); | ||
html` | ||
@@ -317,2 +394,3 @@ <div> | ||
const ifDefined = require('@popeindustries/lit-html-server/directives/if-defined.js'); | ||
html` | ||
@@ -327,2 +405,3 @@ <div class="${ifDefined(className)}"></div> | ||
const repeat = require('@popeindustries/lit-html-server/directives/repeat.js'); | ||
html` | ||
@@ -346,2 +425,3 @@ <ul> | ||
const styleMap = require('@popeindustries/lit-html-server/directives/style-map.js'); | ||
html` | ||
@@ -352,6 +432,7 @@ <div style="${styleMap({ color: 'red' })}"></div> | ||
- `until(...args)`: renders one of a series of values, including Promises, in priority order. Since it's not possible to render more than once in a server context, primitive sync values are prioritised over async Promises, unless there are no more pending values, in which case the last value is rendered regardless of type: | ||
- `until(...args)`: renders one of a series of values, including Promises, in priority order. Since it's not possible to render more than once in a server context, primitive synchronous values are prioritised over asynchronous Promises. If no synchronous values are passed, the last value is rendered regardless of type: | ||
```js | ||
const until = require('@popeindustries/lit-html-server/directives/until.js'); | ||
html` | ||
@@ -358,0 +439,0 @@ <p> |
Sorry, the diff of this file is not supported yet
451
140549
18
33
4197