New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@popeindustries/lit-html-server

Package Overview
Dependencies
Maintainers
1
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@popeindustries/lit-html-server - npm Package Compare versions

Comparing version 0.14.4 to 1.0.0-rc.1

src/default-template-processor.js

59

directives/cache.js

@@ -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;

@@ -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'",

@@ -55,9 +55,9 @@ [![NPM Version](https://img.shields.io/npm/v/@popeindustries/lit-html-server.svg?style=flat)](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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc