@popeindustries/lit-html-server
Advanced tools
Comparing version 0.14.4 to 1.0.0-rc.1
@@ -1,48 +0,29 @@ | ||
(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 = global.litHtmlServer || {}, global.litHtmlServer.directives = global.litHtmlServer.directives || {}, global.litHtmlServer.directives.cache = {}))); | ||
}(this, function (exports) { 'use strict'; | ||
'use strict'; | ||
/** | ||
* Determine if 'obj' is a directive function | ||
* @param {*} obj | ||
* @returns {boolean} | ||
*/ | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
/** | ||
* Define new directive for 'fn' | ||
* @param {function} fn | ||
* @returns {function} | ||
*/ | ||
function directive(fn) { | ||
return function directive(...args) { | ||
const result = fn(...args); | ||
var index_js = require('../index.js'); | ||
if (typeof result !== 'function') { | ||
throw Error('directives are factory functions and must return a function when called'); | ||
} | ||
/** | ||
* @typedef NodePart { import('../parts.js').NodePart } | ||
*/ | ||
result.isDirective = true; | ||
return result; | ||
}; | ||
} | ||
const cache = index_js.directive(cacheDirective); | ||
/** | ||
* Enables fast switching between multiple templates by caching previous results. | ||
* Not possible/desireable to cache between requests, so this is a no-op. | ||
* @param {*} value | ||
* @returns {function} | ||
*/ | ||
const cache = directive((value) => (part) => { | ||
if (part.isAttribute) { | ||
/** | ||
* Enables fast switching between multiple templates by caching previous results. | ||
* Not possible/desireable to cache between requests, so this is a no-op. | ||
* | ||
* @param { any } value | ||
* @returns { (part: NodePart) => void } | ||
*/ | ||
function cacheDirective(value) { | ||
return function(part) { | ||
if (part.constructor.name === 'AttributePart') { | ||
throw Error('The `cache` directive must be only be used in text nodes'); | ||
} | ||
part.setValue(value); | ||
}); | ||
}; | ||
} | ||
exports.cache = cache; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
})); | ||
exports.cache = cache; |
@@ -1,40 +0,19 @@ | ||
(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 = global.litHtmlServer || {}, global.litHtmlServer.directives = global.litHtmlServer.directives || {}, global.litHtmlServer.directives['class-map'] = {}))); | ||
}(this, function (exports) { 'use strict'; | ||
'use strict'; | ||
/** | ||
* Determine if 'obj' is a directive function | ||
* @param {*} obj | ||
* @returns {boolean} | ||
*/ | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
/** | ||
* Define new directive for 'fn' | ||
* @param {function} fn | ||
* @returns {function} | ||
*/ | ||
function directive(fn) { | ||
return function directive(...args) { | ||
const result = fn(...args); | ||
var index_js = require('../index.js'); | ||
if (typeof result !== 'function') { | ||
throw Error('directives are factory functions and must return a function when called'); | ||
} | ||
const classMap = index_js.directive(classMapDirective); | ||
result.isDirective = true; | ||
return result; | ||
}; | ||
} | ||
/** | ||
* Apply CSS classes. | ||
* Only applies to 'class' attribute. | ||
* 'classInfo' keys are added as class names if values are truthy | ||
* @param {object} classInfo | ||
* @returns {function} | ||
*/ | ||
const classMap = directive((classInfo) => (part) => { | ||
if (!part.isAttribute || part.attributeName !== 'class') { | ||
/** | ||
* Applies CSS classes, where'classInfo' keys are added as class names if values are truthy. | ||
* Only applies to 'class' attribute. | ||
* | ||
* @param { object } classInfo | ||
* @returns { (part: AttributePart) => void } | ||
*/ | ||
function classMapDirective(classInfo) { | ||
return function(part) { | ||
if (part.constructor.name !== 'AttributePart' || part.name !== 'class') { | ||
throw Error('The `classMap` directive must be used in the `class` attribute'); | ||
@@ -52,8 +31,5 @@ } | ||
part.setValue(value); | ||
}); | ||
}; | ||
} | ||
exports.classMap = classMap; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
})); | ||
exports.classMap = classMap; |
@@ -1,47 +0,24 @@ | ||
(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 = global.litHtmlServer || {}, global.litHtmlServer.directives = global.litHtmlServer.directives || {}, global.litHtmlServer.directives.guard = {}))); | ||
}(this, function (exports) { 'use strict'; | ||
'use strict'; | ||
/** | ||
* Determine if 'obj' is a directive function | ||
* @param {*} obj | ||
* @returns {boolean} | ||
*/ | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
/** | ||
* Define new directive for 'fn' | ||
* @param {function} fn | ||
* @returns {function} | ||
*/ | ||
function directive(fn) { | ||
return function directive(...args) { | ||
const result = fn(...args); | ||
var index_js = require('../index.js'); | ||
if (typeof result !== 'function') { | ||
throw Error('directives are factory functions and must return a function when called'); | ||
} | ||
const guard = index_js.directive(guardDirective); | ||
result.isDirective = true; | ||
return result; | ||
}; | ||
} | ||
/** | ||
* Guard against re-render. | ||
* Not possible to compare against previous render in a server context, | ||
* so this is a no-op. | ||
* @param {*} value | ||
* @param {function} fn | ||
* @returns {function} | ||
*/ | ||
const guard = directive((value, fn) => (part) => { | ||
/** | ||
* Guard against re-render. | ||
* Not possible to compare against previous render in a server context, | ||
* so this is a no-op. | ||
* | ||
* @param { any } value | ||
* @param { () => any } fn | ||
* @returns { (part: NodePart) => void } | ||
*/ | ||
function guardDirective(value, fn) { | ||
return function(part) { | ||
part.setValue(fn()); | ||
}); | ||
}; | ||
} | ||
exports.guard = guard; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
})); | ||
exports.guard = guard; |
@@ -1,50 +0,25 @@ | ||
(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 = global.litHtmlServer || {}, global.litHtmlServer.directives = global.litHtmlServer.directives || {}, global.litHtmlServer.directives['if-defined'] = {}))); | ||
}(this, function (exports) { 'use strict'; | ||
'use strict'; | ||
/** | ||
* Determine if 'obj' is a directive function | ||
* @param {*} obj | ||
* @returns {boolean} | ||
*/ | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
/** | ||
* Define new directive for 'fn' | ||
* @param {function} fn | ||
* @returns {function} | ||
*/ | ||
function directive(fn) { | ||
return function directive(...args) { | ||
const result = fn(...args); | ||
var index_js = require('../index.js'); | ||
if (typeof result !== 'function') { | ||
throw Error('directives are factory functions and must return a function when called'); | ||
} | ||
const ifDefined = index_js.directive(ifDefinedDirective); | ||
result.isDirective = true; | ||
return result; | ||
}; | ||
} | ||
/** | ||
* Sets the attribute if 'value' is defined, | ||
* removes the attribute if undefined. | ||
* @param {*} value | ||
* @returns {function} | ||
*/ | ||
const ifDefined = directive((value) => (part) => { | ||
if (value === undefined && part.isAttribute) { | ||
// Should import from '../string.js' but Rollup can't tree shake he.encode | ||
part.setValue('{__null__}'); | ||
return; | ||
/** | ||
* Sets the attribute if 'value' is defined, | ||
* removes the attribute if undefined. | ||
* | ||
* @param { any } value | ||
* @returns { (part: AttributePart) => void } | ||
*/ | ||
function ifDefinedDirective(value) { | ||
return function(part) { | ||
if (value === undefined && part.constructor.name === 'AttributePart') { | ||
return part.setValue(index_js.nothingString); | ||
} | ||
part.setValue(value); | ||
}); | ||
}; | ||
} | ||
exports.ifDefined = ifDefined; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
})); | ||
exports.ifDefined = ifDefined; |
@@ -1,54 +0,29 @@ | ||
(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 = global.litHtmlServer || {}, global.litHtmlServer.directives = global.litHtmlServer.directives || {}, global.litHtmlServer.directives.repeat = {}))); | ||
}(this, function (exports) { 'use strict'; | ||
'use strict'; | ||
/** | ||
* Determine if 'obj' is a directive function | ||
* @param {*} obj | ||
* @returns {boolean} | ||
*/ | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
/** | ||
* Define new directive for 'fn' | ||
* @param {function} fn | ||
* @returns {function} | ||
*/ | ||
function directive(fn) { | ||
return function directive(...args) { | ||
const result = fn(...args); | ||
var index_js = require('../index.js'); | ||
if (typeof result !== 'function') { | ||
throw Error('directives are factory functions and must return a function when called'); | ||
} | ||
const repeat = index_js.directive(repeatDirective); | ||
result.isDirective = true; | ||
return result; | ||
}; | ||
/** | ||
* Loop through 'items' and call 'template'. | ||
* No concept of efficient re-ordering possible in server context, | ||
* so this is a simple no-op map operation. | ||
* | ||
* @param { Array<any> } items | ||
* @param { function } keyFnOrTemplate | ||
* @param { (item: any, index: number) => TemplateResult } [template] | ||
* @returns { (part: Part) => void } | ||
*/ | ||
function repeatDirective(items, keyFnOrTemplate, template) { | ||
if (template === undefined) { | ||
template = keyFnOrTemplate; | ||
} | ||
/** | ||
* Loop through 'items' and call 'template'. | ||
* No concept of efficient re-ordering possible in server context, | ||
* so this is a simple no-op map operation. | ||
* @param {Array} items | ||
* @param {function} [keyFnOrTemplate] | ||
* @param {function) => any} template | ||
* @returns {function} | ||
*/ | ||
const repeat = directive((items, keyFnOrTemplate, template) => { | ||
if (template === undefined) { | ||
template = keyFnOrTemplate; | ||
} | ||
return function(part) { | ||
part.setValue(items.map((item, index) => template(item, index))); | ||
}; | ||
} | ||
return (part) => { | ||
part.setValue(items.map((item, index) => template(item, index))); | ||
}; | ||
}); | ||
exports.repeat = repeat; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
})); | ||
exports.repeat = repeat; |
@@ -1,40 +0,19 @@ | ||
(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 = global.litHtmlServer || {}, global.litHtmlServer.directives = global.litHtmlServer.directives || {}, global.litHtmlServer.directives['style-map'] = {}))); | ||
}(this, function (exports) { 'use strict'; | ||
'use strict'; | ||
/** | ||
* Determine if 'obj' is a directive function | ||
* @param {*} obj | ||
* @returns {boolean} | ||
*/ | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
/** | ||
* Define new directive for 'fn' | ||
* @param {function} fn | ||
* @returns {function} | ||
*/ | ||
function directive(fn) { | ||
return function directive(...args) { | ||
const result = fn(...args); | ||
var index_js = require('../index.js'); | ||
if (typeof result !== 'function') { | ||
throw Error('directives are factory functions and must return a function when called'); | ||
} | ||
const styleMap = index_js.directive(styleMapDirective); | ||
result.isDirective = true; | ||
return result; | ||
}; | ||
} | ||
/** | ||
* Apply CSS properties. | ||
* Only applies to 'style' attribute. | ||
* 'styleInfo' keys and values are added as CSS properties | ||
* @param {object} styleInfo | ||
* @returns {function} | ||
*/ | ||
const styleMap = directive((styleInfo) => (part) => { | ||
if (!part.isAttribute || part.attributeName !== 'style') { | ||
/** | ||
* Apply CSS properties, where 'styleInfo' keys and values are added as CSS properties. | ||
* Only applies to 'style' attribute. | ||
* | ||
* @param { object } styleInfo | ||
* @returns { (part: AttributePart) => void } | ||
*/ | ||
function styleMapDirective(styleInfo) { | ||
return function(part) { | ||
if (part.constructor.name !== 'AttributePart' || part.name !== 'style') { | ||
throw Error('The `styleMap` directive must be used in the `style` attribute'); | ||
@@ -50,8 +29,5 @@ } | ||
part.setValue(value); | ||
}); | ||
}; | ||
} | ||
exports.styleMap = styleMap; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
})); | ||
exports.styleMap = styleMap; |
@@ -1,48 +0,24 @@ | ||
(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 = global.litHtmlServer || {}, global.litHtmlServer.directives = global.litHtmlServer.directives || {}, global.litHtmlServer.directives['unsafe-html'] = {}))); | ||
}(this, function (exports) { 'use strict'; | ||
'use strict'; | ||
/** | ||
* Determine if 'obj' is a directive function | ||
* @param {*} obj | ||
* @returns {boolean} | ||
*/ | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
/** | ||
* Define new directive for 'fn' | ||
* @param {function} fn | ||
* @returns {function} | ||
*/ | ||
function directive(fn) { | ||
return function directive(...args) { | ||
const result = fn(...args); | ||
var index_js = require('../index.js'); | ||
if (typeof result !== 'function') { | ||
throw Error('directives are factory functions and must return a function when called'); | ||
} | ||
const unsafeHTML = index_js.directive(unsafeHTMLDirective); | ||
result.isDirective = true; | ||
return result; | ||
}; | ||
} | ||
/** | ||
* Render unescaped HTML | ||
* @param {string} value | ||
* @returns {function} | ||
*/ | ||
const unsafeHTML = directive((value) => (part) => { | ||
if (part.isAttribute) { | ||
/** | ||
* Render "value" without HTML escaping | ||
* | ||
* @param { string } value | ||
* @returns { (part: NodePart) => void } | ||
*/ | ||
function unsafeHTMLDirective(value) { | ||
return function(part) { | ||
if (part.constructor.name !== 'NodePart') { | ||
throw Error('unsafeHTML can only be used in text bindings'); | ||
} | ||
// Should import from '../string.js' but Rollup can't tree shake he.encode | ||
part.setValue(`<!-- no escape -->${value}`); | ||
}); | ||
part.setValue(`${index_js.unsafeStringPrefix}${value}`); | ||
}; | ||
} | ||
exports.unsafeHTML = unsafeHTML; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
})); | ||
exports.unsafeHTML = unsafeHTML; |
@@ -1,46 +0,32 @@ | ||
(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 = global.litHtmlServer || {}, global.litHtmlServer.directives = global.litHtmlServer.directives || {}, global.litHtmlServer.directives.until = {}))); | ||
}(this, function (exports) { 'use strict'; | ||
'use strict'; | ||
/** | ||
* Determine if 'obj' is a directive function | ||
* @param {*} obj | ||
* @returns {boolean} | ||
*/ | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
/** | ||
* Define new directive for 'fn' | ||
* @param {function} fn | ||
* @returns {function} | ||
*/ | ||
function directive(fn) { | ||
return function directive(...args) { | ||
const result = fn(...args); | ||
var index_js = require('../index.js'); | ||
if (typeof result !== 'function') { | ||
throw Error('directives are factory functions and must return a function when called'); | ||
} | ||
/** | ||
* Determine if "value" is a primitive | ||
* | ||
* @param { any } value | ||
* @returns { boolean } | ||
*/ | ||
function isPrimitive(value) { | ||
const type = typeof value; | ||
result.isDirective = true; | ||
return result; | ||
}; | ||
} | ||
return value === null || !(type === 'object' || type === 'function'); | ||
} | ||
function isPrimitive(value) { | ||
const type = typeof value; | ||
const until = index_js.directive(untilDirective); | ||
return value === null || !(type === 'object' || type === 'function'); | ||
} | ||
/** | ||
* Renders one of a series of values, including Promises, in priority order. | ||
* Not possible to render more than once in a server context, so primitive | ||
* sync values are prioritised over async, unless there are no more pending | ||
* values, in which case the last value is always rendered regardless. | ||
* @param {...} args | ||
* @returns {function} | ||
*/ | ||
const until = directive((...args) => (part) => { | ||
/** | ||
* Renders one of a series of values, including Promises, in priority order. | ||
* Not possible to render more than once in a server context, so primitive | ||
* sync values are prioritised over async, unless there are no more pending | ||
* values, in which case the last value is always rendered regardless. | ||
* | ||
* @param { ...: Array<any> } args | ||
* @returns { (AttributePart|NodePart) => void } | ||
*/ | ||
function untilDirective(...args) { | ||
return function(part) { | ||
for (let i = 0, n = args.length; i < n; i++) { | ||
@@ -53,11 +39,7 @@ const value = args[i]; | ||
part.setValue(value); | ||
return; | ||
} | ||
} | ||
}); | ||
}; | ||
} | ||
exports.until = until; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
})); | ||
exports.until = until; |
1427
index.js
@@ -5,39 +5,42 @@ 'use strict'; | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var stream = require('stream'); | ||
var stream__default = _interopDefault(stream); | ||
var fs = _interopDefault(require('fs')); | ||
/** | ||
* Determine if 'obj' is a directive function | ||
* @param {*} obj | ||
* @returns {boolean} | ||
* @typedef { Array<string|Promise<any>> } TemplateResult - an array of template strings (or Promises) and their resolved values | ||
* @property { boolean } isTemplateResult | ||
*/ | ||
function isDirective(obj) { | ||
return typeof obj === 'function' && obj.isDirective; | ||
/** | ||
* @typedef TemplateResultProcessor { import('./default-template-result-processor.js').TemplateResultProcessor } | ||
*/ | ||
/** | ||
* Determine if 'obj' is a template result | ||
* | ||
* @param { any } obj | ||
* @returns { boolean } | ||
*/ | ||
function isTemplateResult(obj) { | ||
return Array.isArray(obj) && obj.isTemplateResult; | ||
} | ||
/** | ||
* Define new directive for 'fn' | ||
* @param {function} fn | ||
* @returns {function} | ||
* Reduce a Template's strings and values to an array of resolved strings (or Promises) | ||
* | ||
* @param { Template } template | ||
* @param { Array<any> } values | ||
* @param { TemplateResultProcessor } processor | ||
* @returns { TemplateResult } | ||
*/ | ||
function directive(fn) { | ||
return function directive(...args) { | ||
const result = fn(...args); | ||
function TemplateResult(template, values, processor) { | ||
const result = processor.processTemplate(template, values); | ||
if (typeof result !== 'function') { | ||
throw Error('directives are factory functions and must return a function when called'); | ||
} | ||
result.isDirective = true; | ||
return result; | ||
}; | ||
result.isTemplateResult = true; | ||
return result; | ||
} | ||
function isStream(stream$$1) { | ||
return stream$$1 != null && stream$$1.pipe != null; | ||
} | ||
/** | ||
* Determine if "promise" is a Promise instance | ||
* | ||
* @param { Promise<any> } promise | ||
* @returns { boolean } | ||
*/ | ||
function isPromise(promise) { | ||
@@ -47,2 +50,8 @@ return promise != null && promise.then != null; | ||
/** | ||
* Determine if "iterator" is an synchronous iterator | ||
* | ||
* @param { IterableIterator } iterator | ||
* @returns { boolean } | ||
*/ | ||
function isSyncIterator(iterator) { | ||
@@ -57,2 +66,14 @@ return ( | ||
/** | ||
* Determine if "value" is a primitive | ||
* | ||
* @param { any } value | ||
* @returns { boolean } | ||
*/ | ||
function isPrimitive(value) { | ||
const type = typeof value; | ||
return value === null || !(type === 'object' || type === 'function'); | ||
} | ||
// https://github.com/facebook/react/packages/react-dom/src/server/escapeTextForBrowser.js | ||
@@ -157,3 +178,3 @@ | ||
* | ||
* @param {*} text Text value to escape. | ||
* @param {any} text Text value to escape. | ||
* @return {string} An escaped string. | ||
@@ -171,852 +192,828 @@ */ | ||
const ASYNC_PLACEHOLDER = '{__async__}'; | ||
const HEADER = '<!-- lit-html-server -->'; | ||
const NO_ESCAPE = '<!-- no escape -->'; | ||
const NULL_ATTRIBUTE = '{__null__}'; | ||
const RE_ENCODED = /&#x/; | ||
/** | ||
* @typedef Part { import('./parts.js').Part } | ||
*/ | ||
/** | ||
* Add header to 'str' | ||
* @param {string} str | ||
* @returns {string} | ||
* Determine if "obj" is a directive function | ||
* | ||
* @param { any } obj | ||
* @returns { boolean } | ||
*/ | ||
function addHeader(str) { | ||
if (hasHeader(str)) { | ||
return str; | ||
} | ||
return `${HEADER}${str}`; | ||
function isDirective(obj) { | ||
return typeof obj === 'function' && obj.isDirective; | ||
} | ||
/** | ||
* Return an array of 'length' filled with empty strings | ||
* This is compatible with engines that don't support Array.prototype.fill() | ||
* @param {number} length | ||
* @returns {*[]} | ||
* 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 emptyArray(length) { | ||
const array = []; | ||
function directive(fn) { | ||
return function directive(...args) { | ||
const result = fn(...args); | ||
for (let i = 0; i < length; i++) { | ||
array[i] = ''; | ||
} | ||
if (typeof result !== 'function') { | ||
throw Error('directives are factory functions and must return a function when called'); | ||
} | ||
return array; | ||
result.isDirective = true; | ||
return result; | ||
}; | ||
} | ||
/** | ||
* Determine if 'str' has header | ||
* @param {string} str | ||
* @returns {boolean} | ||
* A value for strings that signals a Part to clear its content | ||
*/ | ||
function hasHeader(str) { | ||
return typeof str === 'string' && str.indexOf(HEADER) === 0; | ||
} | ||
const nothingString = '__nothing-lit-html-server-string__'; | ||
/** | ||
* Remove header from 'str' | ||
* @param {string} str | ||
* @returns {string} | ||
* A prefix value for strings that should not be escaped | ||
*/ | ||
function removeHeader(str) { | ||
if (!hasHeader(str)) { | ||
return str; | ||
} | ||
return str.slice(24); | ||
} | ||
const unsafeStringPrefix = '__unsafe-lit-html-server-string__'; | ||
/** | ||
* Remove header from 'str' if present, | ||
* or encode 'str' for html | ||
* @param {string} str | ||
* @returns {string} | ||
* Base class interface for Node/Attribute parts | ||
*/ | ||
function sanitize(str) { | ||
if (typeof str !== 'string') { | ||
return str; | ||
class Part { | ||
/** | ||
* Constructor | ||
*/ | ||
constructor() { | ||
this._value; | ||
} | ||
if (hasHeader(str)) { | ||
return str.slice(24); | ||
/** | ||
* 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; | ||
} | ||
if (str.indexOf(NO_ESCAPE) === 0) { | ||
return str.slice(18); | ||
/** | ||
* Retrieve resolved string from passed "value" | ||
* | ||
* @param { any } value | ||
* @returns { any } | ||
*/ | ||
getValue(value) { | ||
return value; | ||
} | ||
// Prevent multiple encodes | ||
return RE_ENCODED.test(str) ? str : escapeTextForBrowser(str); | ||
/** | ||
* No-op | ||
*/ | ||
commit() {} | ||
} | ||
/** | ||
* Asynchronous tagged template processor for HTML templates created with `htmlTemplate`. | ||
* Returns a Readable stream. | ||
* Based on https://github.com/almost/stream-template | ||
* @param {[string]} strings | ||
* @param {*} values | ||
* @returns {Readable} | ||
* A dynamic template part for text nodes | ||
*/ | ||
function asyncHtmlTemplate(strings, ...values) { | ||
return new AsyncHtmlTemplate(strings, values); | ||
class NodePart extends Part { | ||
/** | ||
* Retrieve resolved value given passed "value" | ||
* | ||
* @param { any } value | ||
* @returns { any } | ||
*/ | ||
getValue(value) { | ||
return resolveValue(value, this, true); | ||
} | ||
} | ||
class AsyncHtmlTemplate extends stream.Readable { | ||
/** | ||
* A dynamic template part for attributes. | ||
* Unlike text nodes, attributes may contain multiple strings and parts. | ||
*/ | ||
class AttributePart extends Part { | ||
/** | ||
* Constructor | ||
* @param {[string]} strings | ||
* @param {*} values | ||
* | ||
* @param { string } name | ||
* @param { Array<string> } strings | ||
*/ | ||
constructor(strings, values) { | ||
super({ autoDestroy: true }); | ||
constructor(name, strings) { | ||
super(); | ||
this.name = name; | ||
this.strings = strings; | ||
this.values = values; | ||
this.done = false; | ||
this.awaitingPromise = false; | ||
this.nestedStream = null; | ||
this.queue = [strings[0]]; | ||
this.stringBuffer = []; | ||
this.wantsData = false; | ||
for (let i = 0, n = values.length; i < n; i++) { | ||
const value = values[i]; | ||
// If is stream or Promise, handle downstream errors | ||
if (isStream(value) || isPromise(value)) { | ||
this.handleDownstreamErrors(value); | ||
} | ||
this.queue.push(value, strings[i + 1]); | ||
} | ||
this.length = strings.length - 1; | ||
} | ||
/** | ||
* Handle errors from interpolated stream or Promise values | ||
* @param {Readable|Promise} streamOrPromise | ||
* Retrieve resolved string 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 { string|Promise<string> } | ||
*/ | ||
handleDownstreamErrors(streamOrPromise) { | ||
if (isStream(streamOrPromise)) { | ||
stream.finished(streamOrPromise, (err) => { | ||
if (err) { | ||
this.destroy(err); | ||
} | ||
}); | ||
} else { | ||
streamOrPromise.catch((err) => { | ||
this.destroy(err); | ||
}); | ||
} | ||
} | ||
getValue(values) { | ||
const strings = this.strings; | ||
const endIndex = strings.length - 1; | ||
const result = [`${this.name}="`]; | ||
let pending; | ||
/** | ||
* Extend super.read() | ||
*/ | ||
_read() { | ||
this.wantsData = true; | ||
this.processQueue(); | ||
} | ||
for (let i = 0; i < endIndex; i++) { | ||
const string = strings[i]; | ||
let value = resolveValue(values[i], this, false); | ||
/** | ||
* Process all pending queue items | ||
*/ | ||
processQueue() { | ||
if (this.done || this.awaitingPromise) { | ||
return; | ||
} | ||
result.push(string); | ||
// Read data from nested stream | ||
if (this.nestedStream) { | ||
const chunk = this.nestedStream.read(); | ||
if (chunk != null) { | ||
if (this.push(chunk) === false) { | ||
this.wantsData = false; | ||
// Bail if 'nothing' | ||
if (value === nothingString) { | ||
return ''; | ||
} else if (isPromise(value)) { | ||
if (pending === undefined) { | ||
pending = []; | ||
} | ||
} | ||
return; | ||
} | ||
while (!this.done) { | ||
// Finished | ||
if (this.queue.length === 0) { | ||
// Write all buffered data, but delay closing if backpressure | ||
if (this.drainStringBuffer() === false) { | ||
return; | ||
} | ||
// Close stream | ||
this.push(null); | ||
return; | ||
} | ||
const index = result.push(value) - 1; | ||
let item = this.queue.shift(); | ||
const type = typeof item; | ||
// TODO: add iterator support | ||
// TODO: add directive support? | ||
// Handle primitives | ||
if (item == null || type === 'string' || type === 'number' || type === 'boolean') { | ||
// Ignore undefined | ||
if (item === undefined) { | ||
item = ''; | ||
} | ||
// Remove any headers added by htmlTemplate parent and/or template nesting | ||
this.stringBuffer.push(Buffer.from(`${removeHeader(item)}`, 'utf8')); | ||
} else if (Array.isArray(item) || isSyncIterator(item)) { | ||
// Escape before adding to queue | ||
this.queue = Array.from(item, sanitize).concat(this.queue); | ||
pending.push( | ||
value.then((value) => { | ||
result[index] = value; | ||
}) | ||
); | ||
} else if (Array.isArray(value)) { | ||
result.push(value.join('')); | ||
} else { | ||
// Write all buffered data, but abort if backpressure | ||
if (this.drainStringBuffer() === false) { | ||
this.queue.unshift(item); | ||
return; | ||
} | ||
result.push(value); | ||
} | ||
} | ||
if (isStream(item)) { | ||
this.nestedStream = new stream.PassThrough(); | ||
item.pipe(this.nestedStream); | ||
this.nestedStream.once('end', () => { | ||
this.nestedStream = null; | ||
if (this.wantsData) { | ||
this.processQueue(); | ||
} | ||
}); | ||
this.nestedStream.on('readable', () => { | ||
if (this.wantsData) { | ||
this.processQueue(); | ||
} | ||
}); | ||
} else if (isPromise(item)) { | ||
this.awaitingPromise = true; | ||
item | ||
.then((result) => { | ||
this.awaitingPromise = false; | ||
// Escape before adding to queue | ||
this.queue.unshift(sanitize(result)); | ||
this.processQueue(); | ||
}) | ||
.catch(() => { | ||
// Catch here to prevent unhandledPromiseRejection | ||
}); | ||
} else { | ||
this.emit('error', Error(`uknown interpolation value: ${item}`)); | ||
} | ||
result.push(`${strings[endIndex]}"`); | ||
// Loop will be manually started again above, so abort | ||
return; | ||
} | ||
if (pending !== undefined) { | ||
// Flatten in case array returned from Promise | ||
return Promise.all(pending).then(() => | ||
result.reduce((result, value) => result.concat(value), []).join('') | ||
); | ||
} | ||
return result.join(''); | ||
} | ||
} | ||
/** | ||
* A dynamic template part for boolean attributes. | ||
* Boolean attributes are prefixed with "?" | ||
*/ | ||
class BooleanAttributePart extends AttributePart { | ||
/** | ||
* Write all buffered strings. | ||
* Returns `false` if write triggered backpressure, otherwise `true` | ||
* @returns {boolean} | ||
* Constructor | ||
* | ||
* @param { string } name | ||
* @param { Array<string> } strings | ||
* @throws error when multiple expressions | ||
*/ | ||
drainStringBuffer() { | ||
if (this.stringBuffer.length > 0) { | ||
const toWrite = Buffer.concat(this.stringBuffer); | ||
constructor(name, strings) { | ||
super(name, strings); | ||
this.stringBuffer.length = 0; | ||
if (this.push(toWrite) === false) { | ||
return false; | ||
} | ||
if (strings.length !== 2 || strings[0] !== '' || strings[1] !== '') { | ||
throw Error('Boolean attributes can only contain a single expression'); | ||
} | ||
return true; | ||
} | ||
/** | ||
* Extend super.destroy() | ||
* @param {Error} [err] | ||
* Retrieve resolved string from passed "values". | ||
* | ||
* @param { Array<any> } values | ||
* @returns { string|Promise<string> } | ||
*/ | ||
_destroy(err) { | ||
if (this.done) { | ||
return; | ||
} | ||
getValue(values) { | ||
let value = values[0]; | ||
// Destroy all nested streams | ||
for (let i = 0, n = this.values.length; i < n; i++) { | ||
const value = this.values[i]; | ||
if (isStream(value)) { | ||
value.destroy(); | ||
} | ||
if (isDirective(value)) { | ||
value = getDirectiveValue(value, this); | ||
} | ||
if (err) { | ||
this.emit('error', err); | ||
if (isPromise(value)) { | ||
return value.then((value) => (value ? this.name : '')); | ||
} | ||
this.emit('close'); | ||
this.done = true; | ||
this.strings = null; | ||
this.values = null; | ||
this.nestedStream = null; | ||
this.queue = null; | ||
this.stringBuffer = null; | ||
this.removeAllListeners(); | ||
return value ? this.name : ''; | ||
} | ||
} | ||
// Returns a wrapper function that returns a wrapped callback | ||
// The wrapper function should do some stuff, and return a | ||
// presumably different callback function. | ||
// This makes sure that own properties are retained, so that | ||
// decorations and such are not lost along the way. | ||
var wrappy_1 = wrappy; | ||
function wrappy (fn, cb) { | ||
if (fn && cb) return wrappy(fn)(cb) | ||
/** | ||
* A dynamic template part for property attributes. | ||
* Property attributes are prefixed with "." | ||
*/ | ||
class PropertyAttributePart extends AttributePart { | ||
/** | ||
* Retrieve resolved string from passed "values". | ||
* Properties have no server-side representation, | ||
* so always returns an empty string. | ||
* | ||
* @param { Array<any> } values | ||
* @returns { string } | ||
*/ | ||
getValue(/* values */) { | ||
return ''; | ||
} | ||
} | ||
if (typeof fn !== 'function') | ||
throw new TypeError('need wrapper function') | ||
/** | ||
* A dynamic template part for event attributes. | ||
* Event attributes are prefixed with "@" | ||
*/ | ||
class EventAttributePart extends AttributePart { | ||
/** | ||
* Retrieve resolved string 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 ''; | ||
} | ||
} | ||
Object.keys(fn).forEach(function (k) { | ||
wrapper[k] = fn[k]; | ||
}); | ||
/** | ||
* Resolve "value" to string if possible | ||
* | ||
* @param { any } value | ||
* @param { Part } part | ||
* @param { boolean } ignoreNothingAndUndefined | ||
* @returns { any } | ||
*/ | ||
function resolveValue(value, part, ignoreNothingAndUndefined = true) { | ||
if (isDirective(value)) { | ||
value = getDirectiveValue(value, part); | ||
} | ||
return wrapper | ||
if (ignoreNothingAndUndefined && (value === nothingString || value === undefined)) { | ||
return ''; | ||
} | ||
function wrapper() { | ||
var args = new Array(arguments.length); | ||
for (var i = 0; i < args.length; i++) { | ||
args[i] = arguments[i]; | ||
// Pass-through template result | ||
if (isTemplateResult(value)) { | ||
return value; | ||
} else if (isPrimitive(value)) { | ||
const string = typeof value !== 'string' ? String(value) : value; | ||
// Escape if not prefixed with unsafeStringPrefix, otherwise strip prefix | ||
return string.indexOf(unsafeStringPrefix) === 0 ? string.slice(33) : escapeTextForBrowser(string); | ||
} else if (isPromise(value)) { | ||
return value.then((value) => resolveValue(value, part, ignoreNothingAndUndefined)); | ||
} else if (isSyncIterator(value)) { | ||
if (!Array.isArray(value)) { | ||
value = Array.from(value); | ||
} | ||
var ret = fn.apply(this, args); | ||
var cb = args[args.length-1]; | ||
if (typeof ret === 'function' && ret !== cb) { | ||
Object.keys(cb).forEach(function (k) { | ||
ret[k] = cb[k]; | ||
}); | ||
} | ||
return ret | ||
return value.reduce((values, value) => { | ||
value = resolveValue(value, part, ignoreNothingAndUndefined); | ||
// Allow nested template results to also be flattened by not checking isTemplateResult | ||
if (Array.isArray(value)) { | ||
return values.concat(value); | ||
} | ||
values.push(value); | ||
return values; | ||
}, []); | ||
} else { | ||
return value; | ||
} | ||
} | ||
var once_1 = wrappy_1(once); | ||
var strict = wrappy_1(onceStrict); | ||
once.proto = once(function () { | ||
Object.defineProperty(Function.prototype, 'once', { | ||
value: function () { | ||
return once(this) | ||
}, | ||
configurable: true | ||
}); | ||
Object.defineProperty(Function.prototype, 'onceStrict', { | ||
value: function () { | ||
return onceStrict(this) | ||
}, | ||
configurable: true | ||
}); | ||
}); | ||
function once (fn) { | ||
var f = function () { | ||
if (f.called) return f.value | ||
f.called = true; | ||
return f.value = fn.apply(this, arguments) | ||
}; | ||
f.called = false; | ||
return f | ||
/** | ||
* Retrieve value from "directive" | ||
* | ||
* @param { function } directive | ||
* @param { Part } part | ||
* @returns { any } | ||
*/ | ||
function getDirectiveValue(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; | ||
return value; | ||
} | ||
function onceStrict (fn) { | ||
var f = function () { | ||
if (f.called) | ||
throw new Error(f.onceError) | ||
f.called = true; | ||
return f.value = fn.apply(this, arguments) | ||
}; | ||
var name = fn.name || 'Function wrapped with `once`'; | ||
f.onceError = name + " shouldn't be called more than once"; | ||
f.called = false; | ||
return f | ||
} | ||
once_1.strict = strict; | ||
/** | ||
* @typedef TemplateProcessor | ||
* @property { (name: string, strings: Array<string>) => AttributePart } handleAttributeExpressions | ||
* @property { () => NodePart } handleTextExpression | ||
*/ | ||
var noop = function() {}; | ||
/** | ||
* Class representing the default Template processor. | ||
* Exposes factory functions for generating Part instances to use for | ||
* resolving a template's dynamic values. | ||
*/ | ||
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]; | ||
var isRequest = function(stream$$1) { | ||
return stream$$1.setHeader && typeof stream$$1.abort === 'function'; | ||
}; | ||
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); | ||
} | ||
var isChildProcess = function(stream$$1) { | ||
return stream$$1.stdio && Array.isArray(stream$$1.stdio) && stream$$1.stdio.length === 3 | ||
}; | ||
return new AttributePart(name, strings); | ||
} | ||
var eos = function(stream$$1, opts, callback) { | ||
if (typeof opts === 'function') return eos(stream$$1, null, opts); | ||
if (!opts) opts = {}; | ||
/** | ||
* Create part instance for dynamic text values | ||
* | ||
* @returns { NodePart } | ||
*/ | ||
handleTextExpression() { | ||
return new NodePart(); | ||
} | ||
} | ||
callback = once_1(callback || noop); | ||
/** | ||
* @typedef TemplateResultProcessor | ||
* @property { (template: Template, values: Array<any>) => TemplateResult } processTemplate | ||
*/ | ||
var ws = stream$$1._writableState; | ||
var rs = stream$$1._readableState; | ||
var readable = opts.readable || (opts.readable !== false && stream$$1.readable); | ||
var writable = opts.writable || (opts.writable !== false && stream$$1.writable); | ||
/** | ||
* Class representing the default template result processor. | ||
* @implements TemplateResultProcessor | ||
*/ | ||
class DefaultTemplateResultProcessor { | ||
/** | ||
* Create template result array from "template" instance and dynamic "values". | ||
* The returned array contains Template "strings" concatenated with known string "values", | ||
* and any Promises that will eventually resolve asynchronous string "values". | ||
* A synchronous template tree will reduce to an array containing a single string. | ||
* An asynchronous template tree will reduce to an array of strings and Promises. | ||
* | ||
* @param { Template } template | ||
* @param { Array<any> } values | ||
* @returns { TemplateResult } | ||
*/ | ||
processTemplate(template, values) { | ||
const { strings, parts } = template; | ||
const endIndex = strings.length - 1; | ||
const result = []; | ||
let buffer = ''; | ||
var onlegacyfinish = function() { | ||
if (!stream$$1.writable) onfinish(); | ||
}; | ||
for (let i = 0; i < endIndex; i++) { | ||
const string = strings[i]; | ||
const part = parts[i]; | ||
let value = values[i]; | ||
var onfinish = function() { | ||
writable = false; | ||
if (!readable) callback.call(stream$$1); | ||
}; | ||
buffer += string; | ||
var onend = function() { | ||
readable = false; | ||
if (!writable) callback.call(stream$$1); | ||
}; | ||
if (part instanceof AttributePart) { | ||
// AttributeParts can have multiple values, so slice based on length | ||
// (strings in-between values are already stored in the instance) | ||
if (part.length > 1) { | ||
value = part.getValue(values.slice(i, i + part.length)); | ||
i += part.length - 1; | ||
} else { | ||
value = part.getValue([value]); | ||
} | ||
} else { | ||
value = part.getValue(value); | ||
} | ||
var onexit = function(exitCode) { | ||
callback.call(stream$$1, exitCode ? new Error('exited with error code: ' + exitCode) : null); | ||
}; | ||
buffer = reduce(buffer, result, value); | ||
} | ||
var onerror = function(err) { | ||
callback.call(stream$$1, err); | ||
}; | ||
buffer += strings[endIndex]; | ||
result.push(buffer); | ||
var onclose = function() { | ||
if (readable && !(rs && rs.ended)) return callback.call(stream$$1, new Error('premature close')); | ||
if (writable && !(ws && ws.ended)) return callback.call(stream$$1, new Error('premature close')); | ||
}; | ||
return result; | ||
} | ||
} | ||
var onrequest = function() { | ||
stream$$1.req.on('finish', onfinish); | ||
}; | ||
/** | ||
* Commit value to string "buffer" | ||
* | ||
* @param { string } buffer | ||
* @param { TemplateResult } result | ||
* @param { any } value | ||
* @returns { string } | ||
*/ | ||
function reduce(buffer, result, value) { | ||
if (typeof value === 'string') { | ||
buffer += value; | ||
return buffer; | ||
} else if (Array.isArray(value)) { | ||
return value.reduce((buffer, value) => reduce(buffer, result, value), buffer); | ||
} else if (isPromise(value)) { | ||
result.push(buffer, value); | ||
return ''; | ||
} | ||
} | ||
if (isRequest(stream$$1)) { | ||
stream$$1.on('complete', onfinish); | ||
stream$$1.on('abort', onclose); | ||
if (stream$$1.req) onrequest(); | ||
else stream$$1.on('request', onrequest); | ||
} else if (writable && !ws) { // legacy streams | ||
stream$$1.on('end', onlegacyfinish); | ||
stream$$1.on('close', onlegacyfinish); | ||
} | ||
/** | ||
* @typedef TemplateResult { import('./template-result.js).TemplateResult } | ||
*/ | ||
if (isChildProcess(stream$$1)) stream$$1.on('exit', onexit); | ||
/** | ||
* Buffer strings from "result" and store them on "accumulator" | ||
* | ||
* @param { TemplateResult } result | ||
* @param { object } [accumulator] | ||
* @returns { Promise<string> } | ||
*/ | ||
async function bufferResult( | ||
result, | ||
accumulator = { | ||
buffer: '', | ||
bufferChunk(chunk) { | ||
this.buffer += chunk; | ||
} | ||
} | ||
) { | ||
let stack = result.slice(); | ||
let chunk; | ||
stream$$1.on('end', onend); | ||
stream$$1.on('finish', onfinish); | ||
if (opts.error !== false) stream$$1.on('error', onerror); | ||
stream$$1.on('close', onclose); | ||
accumulator.buffer = ''; | ||
return function() { | ||
stream$$1.removeListener('complete', onfinish); | ||
stream$$1.removeListener('abort', onclose); | ||
stream$$1.removeListener('request', onrequest); | ||
if (stream$$1.req) stream$$1.req.removeListener('finish', onfinish); | ||
stream$$1.removeListener('end', onlegacyfinish); | ||
stream$$1.removeListener('close', onlegacyfinish); | ||
stream$$1.removeListener('finish', onfinish); | ||
stream$$1.removeListener('exit', onexit); | ||
stream$$1.removeListener('end', onend); | ||
stream$$1.removeListener('error', onerror); | ||
stream$$1.removeListener('close', onclose); | ||
}; | ||
}; | ||
while ((chunk = stack.shift()) !== undefined) { | ||
if (typeof chunk === 'string') { | ||
accumulator.bufferChunk(chunk); | ||
} else if (Array.isArray(chunk)) { | ||
stack = chunk.concat(stack); | ||
} else if (isPromise(chunk)) { | ||
stack.unshift(await chunk); | ||
} else { | ||
throw Error('unknown value type', chunk); | ||
} | ||
} | ||
var endOfStream = eos; | ||
return accumulator.buffer; | ||
} | ||
// we only need fs to get the ReadStream and WriteStream prototypes | ||
/** | ||
* @typedef TemplateResult { import('./template-result.js).TemplateResult } | ||
*/ | ||
var noop$1 = function () {}; | ||
var ancient = /^v?\.0/.test(process.version); | ||
/** | ||
* A class for rendering a template result to a string resolving Promise | ||
*/ | ||
class PromiseTemplateRenderer { | ||
/** | ||
* Constructor | ||
* | ||
* @param { TemplateResult } result | ||
* @returns { Promise<string> } | ||
*/ | ||
constructor(result) { | ||
return bufferResult(result); | ||
} | ||
} | ||
var isFn = function (fn) { | ||
return typeof fn === 'function' | ||
}; | ||
/** | ||
* @typedef TemplateResult { import('./template-result.js).TemplateResult } | ||
*/ | ||
var isFS = function (stream$$1) { | ||
if (!ancient) return false // newer node version do not need to care about fs is a special way | ||
if (!fs) return false // browser | ||
return (stream$$1 instanceof (fs.ReadStream || noop$1) || stream$$1 instanceof (fs.WriteStream || noop$1)) && isFn(stream$$1.close) | ||
}; | ||
/** | ||
* A custom Readable stream class that renders a TemplateResult | ||
*/ | ||
class StreamTemplateRenderer extends stream.Readable { | ||
/** | ||
* Constructor | ||
* | ||
* @param { TemplateResult } result | ||
* @param { object } [options] Readable options | ||
* @see https://nodejs.org/api/stream.html#stream_new_stream_readable_options | ||
* @returns { Readable } | ||
*/ | ||
constructor(result, options) { | ||
super({ autoDestroy: true, ...options }); | ||
var isRequest$1 = function (stream$$1) { | ||
return stream$$1.setHeader && isFn(stream$$1.abort) | ||
}; | ||
this.canPushData = true; | ||
this.done = false; | ||
this.buffer = ''; | ||
this.index = 0; | ||
var destroyer = function (stream$$1, reading, writing, callback) { | ||
callback = once_1(callback); | ||
bufferResult(result, this) | ||
.then(() => { | ||
this.done = true; | ||
this._drainBuffer(); | ||
}) | ||
.catch((err) => { | ||
this.destroy(err); | ||
}); | ||
} | ||
var closed = false; | ||
stream$$1.on('close', function () { | ||
closed = true; | ||
}); | ||
/** | ||
* Push "chunk" onto buffer | ||
* (Called by "bufferResult" utility) | ||
* | ||
* @param { string } chunk | ||
*/ | ||
bufferChunk(chunk) { | ||
this.buffer += chunk; | ||
this._drainBuffer(); | ||
} | ||
endOfStream(stream$$1, {readable: reading, writable: writing}, function (err) { | ||
if (err) return callback(err) | ||
closed = true; | ||
callback(); | ||
}); | ||
var destroyed = false; | ||
return function (err) { | ||
if (closed) return | ||
if (destroyed) return | ||
destroyed = true; | ||
if (isFS(stream$$1)) return stream$$1.close(noop$1) // use close for fs streams to avoid fd leaks | ||
if (isRequest$1(stream$$1)) return stream$$1.abort() // request.destroy just do .end - .abort is what we want | ||
if (isFn(stream$$1.destroy)) return stream$$1.destroy() | ||
callback(err || new Error('stream was destroyed')); | ||
/** | ||
* Extend Readable.read() | ||
*/ | ||
_read() { | ||
this.canPushData = true; | ||
this._drainBuffer(); | ||
} | ||
}; | ||
var call = function (fn) { | ||
fn(); | ||
}; | ||
/** | ||
* Write all buffered content to stream. | ||
* Returns "false" if write triggered backpressure, otherwise "true". | ||
* | ||
* @returns { boolean } | ||
*/ | ||
_drainBuffer() { | ||
if (!this.canPushData) { | ||
return false; | ||
} | ||
var pipe = function (from, to) { | ||
return from.pipe(to) | ||
}; | ||
const bufferLength = this.buffer.length; | ||
var pump = function () { | ||
var streams = Array.prototype.slice.call(arguments); | ||
var callback = isFn(streams[streams.length - 1] || noop$1) && streams.pop() || noop$1; | ||
if (this.index < bufferLength) { | ||
// Strictly speaking we shouldn't compare character length with byte length, but close enough | ||
const length = Math.min(bufferLength - this.index, this.readableHighWaterMark); | ||
const chunk = this.buffer.slice(this.index, this.index + length); | ||
if (Array.isArray(streams[0])) streams = streams[0]; | ||
if (streams.length < 2) throw new Error('pump requires two streams per minimum') | ||
this.canPushData = this.push(chunk, 'utf8'); | ||
this.index += length; | ||
} else if (this.done) { | ||
this.push(null); | ||
} | ||
var error; | ||
var destroys = streams.map(function (stream$$1, i) { | ||
var reading = i < streams.length - 1; | ||
var writing = i > 0; | ||
return destroyer(stream$$1, reading, writing, function (err) { | ||
if (!error) error = err; | ||
if (err) destroys.forEach(call); | ||
if (reading) return | ||
destroys.forEach(call); | ||
callback(error); | ||
}) | ||
}); | ||
return this.canPushData; | ||
} | ||
return streams.reduce(pipe) | ||
}; | ||
/** | ||
* Extend Readalbe.destroy() | ||
* | ||
* @param { Error } [err] | ||
*/ | ||
_destroy(err) { | ||
if (this.done) { | ||
return; | ||
} | ||
var pump_1 = pump; | ||
if (err) { | ||
this.emit('error', err); | ||
} | ||
this.emit('close'); | ||
const {PassThrough} = stream__default; | ||
var bufferStream = options => { | ||
options = Object.assign({}, options); | ||
const {array} = options; | ||
let {encoding} = options; | ||
const buffer = encoding === 'buffer'; | ||
let objectMode = false; | ||
if (array) { | ||
objectMode = !(encoding || buffer); | ||
} else { | ||
encoding = encoding || 'utf8'; | ||
} | ||
if (buffer) { | ||
encoding = null; | ||
} | ||
let len = 0; | ||
const ret = []; | ||
const stream$$1 = new PassThrough({objectMode}); | ||
if (encoding) { | ||
stream$$1.setEncoding(encoding); | ||
} | ||
stream$$1.on('data', chunk => { | ||
ret.push(chunk); | ||
if (objectMode) { | ||
len = ret.length; | ||
} else { | ||
len += chunk.length; | ||
} | ||
}); | ||
stream$$1.getBufferedValue = () => { | ||
if (array) { | ||
return ret; | ||
} | ||
return buffer ? Buffer.concat(ret, len) : ret.join(''); | ||
}; | ||
stream$$1.getBufferedLength = () => len; | ||
return stream$$1; | ||
}; | ||
class MaxBufferError extends Error { | ||
constructor() { | ||
super('maxBuffer exceeded'); | ||
this.name = 'MaxBufferError'; | ||
} | ||
this.canPushData = false; | ||
this.done = true; | ||
this.buffer = ''; | ||
this.index = 0; | ||
this.removeAllListeners(); | ||
} | ||
} | ||
function getStream(inputStream, options) { | ||
if (!inputStream) { | ||
return Promise.reject(new Error('Expected a stream')); | ||
} | ||
/** | ||
* @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"'`<>=]*|"[^"]*|'[^']*))$/; | ||
options = Object.assign({maxBuffer: Infinity}, options); | ||
/** | ||
* @typedef TemplateProcessor { import('./default-template-processor.js').TemplateProcessor } | ||
*/ | ||
const {maxBuffer} = options; | ||
const RE_QUOTE = /"[^"]*|'[^']*$/; | ||
let stream$$1; | ||
return new Promise((resolve, reject) => { | ||
const rejectPromise = error => { | ||
if (error) { // A null check | ||
error.bufferedData = stream$$1.getBufferedValue(); | ||
} | ||
reject(error); | ||
}; | ||
stream$$1 = pump_1(inputStream, bufferStream(options), error => { | ||
if (error) { | ||
rejectPromise(error); | ||
return; | ||
} | ||
resolve(); | ||
}); | ||
stream$$1.on('data', () => { | ||
if (stream$$1.getBufferedLength() > maxBuffer) { | ||
rejectPromise(new MaxBufferError()); | ||
} | ||
}); | ||
}).then(() => stream$$1.getBufferedValue()); | ||
} | ||
var getStream_1 = getStream; | ||
var buffer = (stream$$1, options) => getStream(stream$$1, Object.assign({}, options, {encoding: 'buffer'})); | ||
var array = (stream$$1, options) => getStream(stream$$1, Object.assign({}, options, {array: true})); | ||
var MaxBufferError_1 = MaxBufferError; | ||
getStream_1.buffer = buffer; | ||
getStream_1.array = array; | ||
getStream_1.MaxBufferError = MaxBufferError_1; | ||
class Part { | ||
constructor(isAttribute, attributeName) { | ||
this.isAttribute = isAttribute; | ||
this.attributeName = attributeName; | ||
this._value; | ||
} | ||
setValue(value) { | ||
this._value = value; | ||
} | ||
commit() { | ||
// no-op | ||
} | ||
} | ||
// https://github.com/Polymer/lit-html/blob/be84d43f446f22fdd4d44201155b60cf35318912/src/lib/template.ts#L244 | ||
// eslint-disable-next-line no-control-regex | ||
const RE_ATTRIBUTE_NAME = /[ \x09\x0a\x0c\x0d]([^\0-\x1F\x7F-\x9F \x09\x0a\x0c\x0d"'>=/]+)[ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*)$/; | ||
const WARN_ASYNC = | ||
'warning: although lit-html-server allows interpreting Promise values, lit-html does not. Consider using the "until" directive instead.'; | ||
let warnedOnAsync = false; | ||
/** | ||
* Factory for producing a tagged template processor for HTML templates, | ||
* with support for special lit-html syntax. | ||
* @param {function} asyncHtmlTemplate | ||
* A cacheable Template that stores the "strings" and "parts" associated with a | ||
* tagged template literal invoked with "html`...`". | ||
*/ | ||
function htmlTemplateFactory(asyncTemplate) { | ||
class Template$1 { | ||
/** | ||
* Returns string for synchronous value types, | ||
* or a Readable stream or Promise (depending on passed 'asyncTemplate') if asynchronous. | ||
* @param {[string]} strings | ||
* @param {*} values | ||
* @returns {string|Readable|Promise} | ||
* Create Template instance | ||
* | ||
* @param { Array<string> } strings | ||
* @param { TemplateProcessor } processor | ||
*/ | ||
return function htmlTemplate(strings, ...values) { | ||
// Prefix with header so we can distinguish between nested template results | ||
// and primitive values that should be escaped (via sanitize()). | ||
let html = addHeader(strings[0]); | ||
let asyncValues; | ||
constructor(strings, processor) { | ||
this.strings = []; | ||
this.parts = []; | ||
for (let i = 0, n = values.length; i < n; i++) { | ||
// In attribute mode if an element tag is currently open | ||
const isAttributeValue = isTagOpen(html); | ||
let string = strings[i + 1]; | ||
let value = values[i]; | ||
let warnOnAsync = true; | ||
let attributeName; | ||
let attributeNameMatch; | ||
this._prepare(strings, processor); | ||
} | ||
if (isDirective(value)) { | ||
attributeNameMatch = RE_ATTRIBUTE_NAME.exec(html); | ||
attributeName = | ||
attributeNameMatch !== null && attributeNameMatch[1] !== undefined | ||
? attributeNameMatch[1] | ||
: undefined; | ||
/** | ||
* 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]; | ||
const part = new Part(isAttributeValue, attributeName); | ||
for (let i = 0; i < endIndex; i++) { | ||
let string = nextString; | ||
nextString = strings[i + 1]; | ||
const tagState = getTagState(string); | ||
let skip = 0; | ||
let part; | ||
value(part); | ||
value = part._value; | ||
// Disable warning if Promise returned by directive | ||
if (isPromise(value)) { | ||
warnOnAsync = false; | ||
} | ||
// Open/close tag found at end of string | ||
if (tagState !== -1) { | ||
attributeMode = tagState === 1; | ||
} | ||
// Treat Array/iterator as nested template in order to correctly handle all types | ||
if (Array.isArray(value) || isSyncIterator(value)) { | ||
value = Array.from(value); | ||
value = htmlTemplate(emptyArray(value.length + 1), ...value); | ||
} | ||
if (attributeMode) { | ||
const matchName = lastAttributeNameRegex.exec(string); | ||
// Async if Promise or Readable stream | ||
// TODO: add async iterator support | ||
const isAsyncValue = isPromise(value) || isStream(value); | ||
if (matchName) { | ||
let [, prefix, name, suffix] = matchName; | ||
if (isAsyncValue) { | ||
// Unable to support async attribute interpolation because | ||
// strings can't be modified after they have been sent to the client | ||
if (isAttributeValue) { | ||
throw Error( | ||
`only synchronous values are valid when interpolating element attribute values: ${html.slice( | ||
Math.min(html.length, 10) * -1 | ||
)}\${[${typeof value}]}${string.slice(0, Math.min(string.length, 10))}` | ||
); | ||
} | ||
if (!warnedOnAsync && warnOnAsync && isPromise(value)) { | ||
console.warn(WARN_ASYNC); | ||
warnedOnAsync = true; | ||
} | ||
// Since attributes are conditional, remove "name" and "suffix" from static string | ||
string = string.slice(0, matchName.index + prefix.length); | ||
asyncValues = asyncValues || []; | ||
asyncValues.push(value); | ||
value = ASYNC_PLACEHOLDER; | ||
} else if (isAttributeValue) { | ||
attributeNameMatch = attributeNameMatch || RE_ATTRIBUTE_NAME.exec(html); | ||
const matchQuote = RE_QUOTE.exec(suffix); | ||
if (attributeNameMatch !== null && attributeNameMatch[1] !== undefined) { | ||
attributeName = attributeNameMatch[1]; | ||
const specialAttributeFlag = attributeName.charAt(0); | ||
const hasLeadingQuote = isQuoteChar( | ||
html.charAt(attributeNameMatch.index + attributeName.length + 2) | ||
); | ||
// 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 = [suffix.slice(matchQuote.index + 1)]; | ||
let open = true; | ||
skip = 0; | ||
let attributeString; | ||
// Handle special attribute bindings or NULL_ATTRIBUTE value | ||
if ( | ||
value === NULL_ATTRIBUTE || | ||
specialAttributeFlag === '?' || | ||
specialAttributeFlag === '.' || | ||
specialAttributeFlag === '@' | ||
) { | ||
// Remove name, '=', and quote if it is present | ||
html = html.slice(0, attributeNameMatch.index + 1); | ||
// Keep attribute name if boolean and truthy | ||
if (specialAttributeFlag === '?' && value) { | ||
html += attributeName.slice(1); | ||
// 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(attributeString); | ||
skip++; | ||
} else { | ||
attributeStrings.push(attributeString.slice(0, closingQuoteIndex)); | ||
nextString = attributeString.slice(closingQuoteIndex + 1); | ||
i += skip; | ||
open = false; | ||
} | ||
} | ||
value = ''; | ||
// Remove trailing quote | ||
if (isQuoteChar(string.charAt(0))) { | ||
string = string.slice(1); | ||
} | ||
part = processor.handleAttributeExpressions(name, attributeStrings); | ||
} else { | ||
// Remove header from nested template or escape if not | ||
value = sanitize(value); | ||
// Add missing quotes | ||
if (!hasLeadingQuote) { | ||
value = `"${value}"`; | ||
} | ||
part = processor.handleAttributeExpressions(name, ['', '']); | ||
} | ||
} | ||
} else { | ||
// Ignore if undefined, | ||
// otherwise remove header from nested template or escape if not | ||
value = value === undefined ? '' : sanitize(value); | ||
part = processor.handleTextExpression(); | ||
} | ||
html += value; | ||
html += string; | ||
this.strings.push(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(''); | ||
this.parts.push(null); | ||
skip = 0; | ||
} | ||
} | ||
// Return stream if at least one async value | ||
if (asyncValues && asyncValues.length > 0) { | ||
return asyncTemplate(html.split(ASYNC_PLACEHOLDER), ...asyncValues); | ||
} | ||
return html; | ||
}; | ||
this.strings.push(nextString); | ||
} | ||
} | ||
/** | ||
* Determine if last element tag in 'str' is open | ||
* @param {string} str | ||
* @returns {boolean} | ||
* 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 isTagOpen(str) { | ||
const close = str.lastIndexOf('>'); | ||
function getTagState(string) { | ||
for (let i = string.length - 1; i >= 0; i--) { | ||
const char = string[i]; | ||
return str.indexOf('<', close + 1) > -1; | ||
if (char === '>') { | ||
return 0; | ||
} else if (char === '<') { | ||
return 1; | ||
} | ||
} | ||
return -1; | ||
} | ||
/** | ||
* Determine if 'char' is quote character | ||
* @param {string} char | ||
* @returns {boolean} | ||
* @typedef Readable { import('stream').Readable } | ||
* @typedef TemplateResult { import('./template-result.js).TemplateResult } | ||
*/ | ||
function isQuoteChar(char) { | ||
return char === "'" || char === '"'; | ||
} | ||
const htmlTemplate = htmlTemplateFactory(asyncHtmlTemplate); | ||
const defaultTemplateProcessor = new DefaultTemplateProcessor(); | ||
const defaultTemplateResultProcessor = new DefaultTemplateResultProcessor(); | ||
const templateCache = new Map(); | ||
/** | ||
* Render lit-html style HTML 'template' as Readable stream | ||
* @param {string|Readable} template | ||
* @returns {Readable} | ||
* 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 render(template) { | ||
// Force to stream | ||
if (typeof template === 'string') { | ||
// Strip header | ||
const str = removeHeader(template); | ||
let reads = 0; | ||
function html(strings, ...values) { | ||
let template = templateCache.get(strings); | ||
template = new stream.Readable({ | ||
read() { | ||
if (!reads++) { | ||
template.push(str, 'utf8'); | ||
} else { | ||
template.push(null); | ||
} | ||
} | ||
}); | ||
if (template === undefined) { | ||
template = new Template$1(strings, defaultTemplateProcessor); | ||
templateCache.set(strings, template); | ||
} | ||
return template; | ||
return TemplateResult(template, values, defaultTemplateResultProcessor); | ||
} | ||
/** | ||
* Render lit-html style HTML 'template' as string (via Promise) | ||
* @param {string|Readable} template | ||
* @returns {Promise<string>} | ||
* Render a template result to a Readable stream | ||
* | ||
* @param { TemplateResult } result - a template result returned from call to "html`...`" | ||
* @param { object } [options] - Readable stream options | ||
* @see https://nodejs.org/api/stream.html#stream_new_stream_readable_options | ||
* @returns { Readable } | ||
*/ | ||
function renderToString(template) { | ||
if (typeof template === 'string') { | ||
return Promise.resolve(removeHeader(template)); | ||
} | ||
return getStream_1(template); | ||
function renderToStream(result, options) { | ||
return new StreamTemplateRenderer(result, options); | ||
} | ||
exports.html = htmlTemplate; | ||
exports.svg = htmlTemplate; | ||
exports.render = render; | ||
/** | ||
* Render a template result to a string resolving Promise | ||
* | ||
* @param { TemplateResult } result | ||
* @returns { Promise<string> } | ||
*/ | ||
function renderToString(result) { | ||
return new PromiseTemplateRenderer(result); | ||
} | ||
exports.defaultTemplateProcessor = defaultTemplateProcessor; | ||
exports.defaultTemplateResultProcessor = defaultTemplateResultProcessor; | ||
exports.html = html; | ||
exports.svg = html; | ||
exports.isTemplateResult = isTemplateResult; | ||
exports.renderToStream = renderToStream; | ||
exports.renderToString = renderToString; | ||
exports.templateCache = templateCache; | ||
exports.AttributePart = AttributePart; | ||
exports.NodePart = NodePart; | ||
exports.nothingString = nothingString; | ||
exports.unsafeStringPrefix = unsafeStringPrefix; | ||
exports.directive = directive; |
{ | ||
"name": "@popeindustries/lit-html-server", | ||
"version": "0.14.4", | ||
"version": "1.0.0-rc.1", | ||
"description": "Render lit-html templates on the server", | ||
@@ -14,3 +14,3 @@ "keywords": [ | ||
"main": "index.js", | ||
"browser": "browser.js", | ||
"module": "src/index.js", | ||
"repository": "https://github.com/popeindustries/lit-html-server.git", | ||
@@ -24,6 +24,6 @@ "author": "Alexander Pope <alex@pope-industries.com>", | ||
"chai": "^4.2.0", | ||
"eslint": "^5.12.0", | ||
"eslint-config-prettier": "^3.3.0", | ||
"eslint": "^5.12.1", | ||
"eslint-config-prettier": "^3.4.0", | ||
"eslint-plugin-prettier": "^3.0.1", | ||
"esm": "^3.0.84", | ||
"esm": "^3.1.0", | ||
"get-stream": "^4.1.0", | ||
@@ -34,5 +34,4 @@ "husky": "^1.3.1", | ||
"mocha": "^5.2.0", | ||
"normalize-html-whitespace": "^0.2.0", | ||
"prettier": "^1.15.3", | ||
"rollup": "^1.1.0", | ||
"prettier": "^1.16.0", | ||
"rollup": "^1.1.1", | ||
"rollup-plugin-commonjs": "^9.2.0", | ||
@@ -45,3 +44,3 @@ "rollup-plugin-node-resolve": "^4.0.0" | ||
"scripts": { | ||
"build": "rollup --config rollup.config.js", | ||
"build": "rm -rf ./index.js ./directives/*.js && rollup --config rollup.config.js", | ||
"format": "prettier --write './{directives,lib,test}/**/*.{js,json}'", | ||
@@ -48,0 +47,0 @@ "lint": "eslint './{directives,lib,test}/**/*.js'", |
125
README.md
@@ -55,9 +55,9 @@ [](https://npmjs.org/package/@popeindustries/lit-html-server) | ||
```js | ||
const { render } = require('@popeindustries/lit-html-server'); | ||
const { renderToStream } = require('@popeindustries/lit-html-server'); | ||
// Returns a Node.js Readable stream which can be piped to `response` | ||
render(layout({ title: 'Home', api: '/api/home' })); | ||
renderToStream(layout({ title: 'Home', api: '/api/home' })); | ||
``` | ||
## Node API | ||
## API | ||
@@ -80,7 +80,7 @@ ### `html` | ||
html` | ||
<div> ${unsafeHTML('<span>dangerous!</span>')} </div> | ||
<div>${unsafeHTML('<span>dangerous!</span>')}</div> | ||
`; | ||
``` | ||
### `render(template: string|Readable): Readable` | ||
### `renderToStream(TemplateResult): Readable` | ||
@@ -97,3 +97,3 @@ Returns the result of the template tagged by `html` as a Node.js `Readable` stream of markup. | ||
### `renderToString(template: string|Readable): Promise<string>` | ||
### `renderToString(TemplateResult): Promise<string>` | ||
@@ -110,18 +110,2 @@ Returns the result of the template tagged by `html` as a Promise which resolves to a string of markup. | ||
## Browser API | ||
**lit-html-server** can also be used in a browser context: | ||
```js | ||
const { html, renderToString } = require('@popeindustries/lit-html-server/browser.js'); | ||
``` | ||
### `html` | ||
The tag function to apply to HTML template literals (also aliased as `svg`). See [above](#html). | ||
### `renderToString(template: string|Promise): Promise<string>` | ||
Returns the result of the template tagged by `html` as a Promise which resolves to a string of markup. See [above](#rendertostringtemplate-stringreadable-promisestring). | ||
## Writing templates | ||
@@ -207,3 +191,4 @@ | ||
const page = html` | ||
${header} <p>This is some text</p> | ||
${header} | ||
<p>This is some text</p> | ||
`; | ||
@@ -218,10 +203,8 @@ ``` | ||
<ul> | ||
${ | ||
items.map( | ||
(item) => | ||
html` | ||
<li>${item}</li> | ||
` | ||
) | ||
} | ||
${items.map( | ||
(item) => | ||
html` | ||
<li>${item}</li> | ||
` | ||
)} | ||
</ul> | ||
@@ -243,3 +226,3 @@ `; | ||
> note that **lit-html** no longer supports Promise values, and **lit-html-server** will therefore log a warning. Use the `until` directive instead. | ||
> Note that **lit-html** no longer supports Promise values. Though **lit-html-server** does, it's recommended to use the `until` directive instead. | ||
@@ -250,3 +233,3 @@ ### Directives | ||
- `guard(value, fn)`: no-op since re-rendering does not apply (renders result of `fn`) | ||
- `guard(value, fn)`: no-op since re-rendering does not apply (renders result of `fn`): | ||
@@ -257,12 +240,10 @@ ```js | ||
<div> | ||
${ | ||
guard(items, () => | ||
items.map( | ||
(item) => | ||
html` | ||
${item} | ||
` | ||
) | ||
${guard(items, () => | ||
items.map( | ||
(item) => | ||
html` | ||
${item} | ||
` | ||
) | ||
} | ||
)} | ||
</div> | ||
@@ -272,3 +253,3 @@ `; | ||
- `ifDefined(value)`: sets the attribute if the value is defined and removes the attribute if the value is undefined | ||
- `ifDefined(value)`: sets the attribute if the value is defined and removes the attribute if the value is undefined: | ||
@@ -288,12 +269,10 @@ ```js | ||
<ul> | ||
${ | ||
repeat( | ||
items, | ||
(i) => i.id, | ||
(i, index) => | ||
html` | ||
<li>${index}: ${i.name}</li> | ||
` | ||
) | ||
} | ||
${repeat( | ||
items, | ||
(i) => i.id, | ||
(i, index) => | ||
html` | ||
<li>${index}: ${i.name}</li> | ||
` | ||
)} | ||
</ul> | ||
@@ -303,3 +282,3 @@ `; | ||
- `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 | ||
- `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: | ||
@@ -310,10 +289,8 @@ ```js | ||
<p> | ||
${ | ||
until( | ||
fetch('content.json').then((r) => r.json()), | ||
html` | ||
<span>Loading...</span> | ||
` | ||
) | ||
} | ||
${until( | ||
fetch('content.json').then((r) => r.json()), | ||
html` | ||
<span>Loading...</span> | ||
` | ||
)} | ||
</p> | ||
@@ -325,12 +302,10 @@ `; | ||
<p> | ||
${ | ||
until( | ||
fetch('content.json').then((r) => r.json()), | ||
isBrowser | ||
? html` | ||
<span>Loading...</span> | ||
` | ||
: undefined | ||
) | ||
} | ||
${until( | ||
fetch('content.json').then((r) => r.json()), | ||
isBrowser | ||
? html` | ||
<span>Loading...</span> | ||
` | ||
: undefined | ||
)} | ||
</p> | ||
@@ -341,3 +316,3 @@ `; | ||
- `classMap(classInfo)`: applies css classes to the `class` attribute. 'classInfo' keys are added as class names if values are truthy | ||
- `classMap(classInfo)`: applies css classes to the `class` attribute. 'classInfo' keys are added as class names if values are truthy: | ||
@@ -351,3 +326,3 @@ ```js | ||
- `styleMap(styleInfo)`: applies css properties to the `style` attribute. 'styleInfo' keys and values are added as style properties | ||
- `styleMap(styleInfo)`: applies css properties to the `style` attribute. 'styleInfo' keys and values are added as style properties: | ||
@@ -361,3 +336,3 @@ ```js | ||
- `cache(value)`: Enables fast switching between multiple templates by caching previous results. Since it's generally not desireable to cache between requests, this is a no-op | ||
- `cache(value)`: Enables fast switching between multiple templates by caching previous results. Since it's generally not desireable to cache between requests, this is a no-op: | ||
@@ -364,0 +339,0 @@ ```js |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
75760
16
32
2128
1
338
1