Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@lit-labs/ssr

Package Overview
Dependencies
Maintainers
11
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lit-labs/ssr - npm Package Compare versions

Comparing version 3.1.9 to 3.2.0

lib/server-template.d.ts

1

index.d.ts

@@ -8,2 +8,3 @@ /**

export { ElementRenderer } from './lib/element-renderer.js';
export * from './lib/server-template.js';
//# sourceMappingURL=index.d.ts.map

@@ -8,2 +8,3 @@ /**

export { ElementRenderer } from './lib/element-renderer.js';
export * from './lib/server-template.js';
//# sourceMappingURL=index.js.map

@@ -23,2 +23,11 @@ /**

private _currentIterator?;
/**
* `_waiting` flag is used to prevent multiple concurrent reads.
*
* RenderResultReadable handles async RenderResult's, and must await them.
* While awaiting a result, it's possible for `_read` to be called again.
* Without this flag, a new read is initiated and the order of the data in the
* stream becomes inconsistent.
*/
private _waiting;
constructor(result: RenderResult);

@@ -25,0 +34,0 @@ _read(_size: number): Promise<void>;

18

lib/render-result-readable.js

@@ -13,2 +13,11 @@ /**

super();
/**
* `_waiting` flag is used to prevent multiple concurrent reads.
*
* RenderResultReadable handles async RenderResult's, and must await them.
* While awaiting a result, it's possible for `_read` to be called again.
* Without this flag, a new read is initiated and the order of the data in the
* stream becomes inconsistent.
*/
this._waiting = false;
this._result = result;

@@ -18,2 +27,5 @@ this._iterators = [this._result[Symbol.iterator]()];

async _read(_size) {
if (this._waiting) {
return;
}
// This implementation reads values from the RenderResult and pushes them

@@ -34,6 +46,2 @@ // into the base class's Readable implementation. It tries to be as

// does not want any more values.
// - `this._read()` should not be called by the underlying Readable until
// after this.push() has returned false, so we can wait on a Promise
// and call _read() when it resolves to continue without a race condition.
// (We try to verify this with the this._waiting field)
// - `this.push(null)` ends the stream

@@ -65,3 +73,5 @@ //

this._iterators.push(this._currentIterator);
this._waiting = true;
this._currentIterator = (await value)[Symbol.iterator]();
this._waiting = false;
}

@@ -68,0 +78,0 @@ }

@@ -39,3 +39,3 @@ /// <reference lib="dom" />

}
export declare function renderValue(value: unknown, renderInfo: RenderInfo): RenderResult;
export declare function renderValue(value: unknown, renderInfo: RenderInfo, hydratable?: boolean): RenderResult;
//# sourceMappingURL=render-value.d.ts.map
/// <reference lib="dom" />
import { nothing, noChange } from 'lit';
import { PartType } from 'lit/directive.js';
import { isPrimitive, isTemplateResult, getDirectiveClass, TemplateResultType, } from 'lit/directive-helpers.js';
import { isPrimitive, isTemplateResult, getDirectiveClass, TemplateResultType, isCompiledTemplateResult, } from 'lit/directive-helpers.js';
import { _$LH } from 'lit-html/private-ssr-support.js';

@@ -11,5 +11,6 @@ const { getTemplateHtml, marker, markerMatch, boundAttributeSuffix, overrideDirectiveResolve, setDirectiveClass, getAttributePartCommittedValue, resolveDirective, AttributePart, PropertyPart, BooleanAttributePart, EventPart, connectedDisconnectable, isIterable, } = _$LH;

import { parseFragment } from 'parse5';
import { isElementNode, isCommentNode, traverse } from '@parse5/tools';
import { isElementNode, isCommentNode, traverse, isTextNode, isTemplateNode, } from '@parse5/tools';
import { isRenderLightDirective } from '@lit-labs/ssr-client/directives/render-light.js';
import { reflectedAttributeName } from './reflected-attributes.js';
import { isHydratable } from './server-template.js';
const patchedDirectiveCache = new WeakMap();

@@ -126,2 +127,3 @@ /**

TemplateResultType.HTML);
const hydratable = isHydratable(result);
/**

@@ -264,2 +266,10 @@ * The html string is parsed into a parse5 AST with source code information

const [, prefix, caseSensitiveName] = /([.?@])?(.*)/.exec(name);
if (!hydratable) {
if (prefix === '.') {
throw new Error(`Server-only templates can't bind to properties. Bind to attributes instead, as they can be serialized when the template is rendered and sent to the browser.`);
}
else if (prefix === '@') {
throw new Error(`Server-only templates can't bind to events. There's no way to serialize an event listener when generating HTML and sending it to the browser.`);
}
}
ops.push({

@@ -282,2 +292,6 @@ type: 'attribute-part',

else {
if (!hydratable) {
throw new Error(`Server-only templates don't support element parts, as their API does not currently give them any way to render anything on the server. Found in template:
${displayTemplateResult(result)}`);
}
ops.push({

@@ -315,2 +329,44 @@ type: 'element-part',

}
else if (!hydratable &&
/^(title|textarea|script|style)$/.test(node.tagName)) {
const dangerous = isJavaScriptScriptTag(node);
// Marker comments in a rawtext element will be parsed as text,
// so we need to look at the text value of childnodes to try to
// find them and render child-part opcodes.
for (const child of node.childNodes) {
if (!isTextNode(child)) {
throw new Error(`Internal error: Unexpected child node inside raw text node, a ${node.tagName} should only contain text nodes, but found a ${node.nodeName} (tagname: ${node.tagName})`);
}
const text = child.value;
const textStart = child.sourceCodeLocation.startOffset;
flushTo(textStart);
const markerRegex = new RegExp(marker.replace(/\$/g, '\\$'), 'g');
for (const mark of text.matchAll(markerRegex)) {
flushTo(textStart + mark.index);
if (dangerous) {
throw new Error(`Found binding inside an executable <script> tag in a server-only template. For security reasons, this is not supported, as it could allow an attacker to execute arbitrary JavaScript. If you do need to create a script element with dynamic contents, you can use the unsafeHTML directive to make one, as that way the code is clearly marked as unsafe and needing careful handling. The template with the dangerous binding is:
${displayTemplateResult(result)}`);
}
if (node.tagName === 'style') {
throw new Error(`Found binding inside a <style> tag in a server-only template. For security reasons, this is not supported, as it could allow an attacker to exfiltrate information from the page. If you do need to create a style element with dynamic contents, you can use the unsafeHTML directive to make one, as that way the code is clearly marked as unsafe and needing careful handling. The template with the dangerous binding is:
${displayTemplateResult(result)}`);
}
ops.push({
type: 'child-part',
index: nodeIndex,
useCustomElementInstance: false,
});
skipTo(textStart + mark.index + mark[0].length);
}
flushTo(textStart + text.length);
}
}
else if (!hydratable && isTemplateNode(node)) {
// Server-only templates look inside of <template> nodes, because
// we can afford the complexity and cost, and there's way more
// benefit to be gained from it
traverse(node.content, this, node);
}
nodeIndex++;

@@ -332,3 +388,3 @@ }

};
export function* renderValue(value, renderInfo) {
export function* renderValue(value, renderInfo, hydratable = true) {
patchIfDirective(value);

@@ -351,7 +407,14 @@ if (isRenderLightDirective(value)) {

if (value != null && isTemplateResult(value)) {
yield `<!--lit-part ${digestForTemplateResult(value)}-->`;
if (hydratable) {
yield `<!--lit-part ${digestForTemplateResult(value)}-->`;
}
yield* renderTemplateResult(value, renderInfo);
if (hydratable) {
yield `<!--/lit-part-->`;
}
}
else {
yield `<!--lit-part-->`;
if (hydratable) {
yield `<!--lit-part-->`;
}
if (value === undefined ||

@@ -366,3 +429,3 @@ value === null ||

for (const item of value) {
yield* renderValue(item, renderInfo);
yield* renderValue(item, renderInfo, hydratable);
}

@@ -373,4 +436,6 @@ }

}
if (hydratable) {
yield `<!--/lit-part-->`;
}
}
yield `<!--/lit-part-->`;
}

@@ -392,2 +457,3 @@ function* renderTemplateResult(result, renderInfo) {

// previous span of HTML.
const hydratable = isHydratable(result);
const ops = getTemplateOpcodes(result);

@@ -403,3 +469,15 @@ /* The next value in result.values to render */

const value = result.values[partIndex++];
yield* renderValue(value, renderInfo);
let isValueHydratable = hydratable;
if (isTemplateResult(value)) {
isValueHydratable = isHydratable(value);
if (!isValueHydratable && hydratable) {
throw new Error(`A server-only template can't be rendered inside an ordinary, hydratable template. A server-only template can only be rendered at the top level, or within other server-only templates. The outer template was:
${displayTemplateResult(result)}
And the inner template was:
${displayTemplateResult(value)}
`);
}
}
yield* renderValue(value, renderInfo, isValueHydratable);
break;

@@ -488,3 +566,5 @@ }

renderInfo.customElementHostStack.length > 0) {
yield `<!--lit-node ${op.nodeIndex}-->`;
if (hydratable) {
yield `<!--lit-node ${op.nodeIndex}-->`;
}
}

@@ -531,3 +611,3 @@ break;

${result.strings.join('${...}')}
${displayTemplateResult(result)}

@@ -571,3 +651,66 @@ This could be because you're attempting to render an expression in an invalid location. See

}
/**
* Returns a debug string suitable for an error message describing a
* TemplateResult.
*/
function displayTemplateResult(result) {
if (isCompiledTemplateResult(result)) {
return result._$litType$.h.join('${...}');
}
return result.strings.join('${...}');
}
const getLast = (a) => a[a.length - 1];
/**
* Returns true if the given node is a <script> node that the browser will
* automatically execute if it's rendered on server-side, outside of a
* <template> tag.
*/
function isJavaScriptScriptTag(node) {
function isScriptTag(node) {
return /script/i.test(node.tagName);
}
if (!isScriptTag(node)) {
return false;
}
let safeTypeSeen = false;
for (const attr of node.attrs) {
if (attr.name !== 'type') {
continue;
}
switch (attr.value) {
// see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#textjavascript
case null:
case undefined:
case '':
case 'module':
case 'text/javascript':
case 'application/javascript':
case 'application/ecmascript':
case 'application/x-ecmascript':
case 'application/x-javascript':
case 'text/ecmascript':
case 'text/javascript1.0':
case 'text/javascript1.1':
case 'text/javascript1.2':
case 'text/javascript1.3':
case 'text/javascript1.4':
case 'text/javascript1.5':
case 'text/jscript':
case 'text/livescript':
case 'text/x-ecmascript':
case 'text/x-javascript':
// If we see a dangerous type, we can stop looking
return true;
default:
safeTypeSeen = true;
}
}
// So, remember that attributes can be repeated. If we saw a dangerous type,
// then we would have returned early. However, if there's no type, then
// that's dangerous.
// It's only if all types seen were safe, and we saw at least one type, that
// we can return false.
const willExecute = !safeTypeSeen;
return willExecute;
}
//# sourceMappingURL=render-value.js.map

@@ -8,2 +8,4 @@ /**

import { renderValue } from './render-value.js';
import { isTemplateResult } from 'lit-html/directive-helpers.js';
import { isHydratable } from './server-template.js';
/**

@@ -29,4 +31,8 @@ * Renders a lit-html template (or any renderable lit-html value) to a string

renderInfo = { ...defaultRenderInfo, ...renderInfo };
yield* renderValue(value, renderInfo);
let hydratable = true;
if (isTemplateResult(value)) {
hydratable = isHydratable(value);
}
yield* renderValue(value, renderInfo, hydratable);
}
//# sourceMappingURL=render.js.map
{
"name": "@lit-labs/ssr",
"type": "module",
"version": "3.1.9",
"version": "3.2.0",
"publishConfig": {

@@ -21,3 +21,4 @@ "access": "public"

"test:integration:unshimmed:prod": "wireit",
"test:unit": "wireit"
"test:unit": "wireit",
"test:types": "wireit"
},

@@ -74,3 +75,4 @@ "wireit": {

"test:unit",
"test:integration"
"test:integration",
"test:types"
]

@@ -177,2 +179,18 @@ },

"output": []
},
"test:types": {
"command": "tsc --pretty --noEmit",
"dependencies": [
"../ssr-client:build:ts:types",
"../ssr-dom-shim:build:ts",
"../../lit:build:ts:types",
"../../lit-html:build:ts:types",
"../../lit-element:build:ts:types"
],
"files": [
"src/**/*.ts",
"custom_typings/**/*.ts",
"tsconfig.json"
],
"output": []
}

@@ -218,5 +236,5 @@ },

"enhanced-resolve": "^5.10.0",
"lit": "^2.7.0 || ^3.0.0",
"lit": "^3.1.0",
"lit-element": "^3.3.0 || ^4.0.0",
"lit-html": "^2.7.0 || ^3.0.0",
"lit-html": "^3.1.0",
"node-fetch": "^3.2.8",

@@ -223,0 +241,0 @@ "parse5": "^7.1.1"

@@ -15,3 +15,3 @@ # @lit-labs/ssr

`LitElement` definitions they may use) into the node global scope and render
them to a stream (or string) using the `render(value: unknown): Iterable<string>` function provided by the `render-lit-html.js` module. When
them to a stream (or string) using the `render(value: unknown, renderInfo?: Partial<RenderInfo>): RenderResult` function provided by `@lit-labs/ssr`. When
running in Node, Lit automatically depends on Node-compatible implementations of

@@ -21,6 +21,15 @@ a minimal set of DOM APIs provided by the `@lit-labs/ssr-dom-shim` package,

#### Rendering to a stream
Web servers should prefer rendering to a stream, as they have a lower memory
footprint and allow sending data in chunks as they are being processed. For this
case use `RenderResultReadable`, which is a Node `Readable` stream
implementation that provides values from `RenderResult`. This can be piped
into a `Writable` stream, or passed to web server frameworks like [Koa](https://koajs.com/).
```js
// Example: server.js:
import {render} from '@lit-labs/ssr/lib/render-lit-html.js';
import {render} from '@lit-labs/ssr';
import {RenderResultReadable} from '@lit-labs/ssr/lib/render-result-readable.js';
import {myTemplate} from './my-template.js';

@@ -31,5 +40,27 @@

const ssrResult = render(myTemplate(data));
context.body = Readable.from(ssrResult);
// Assume `context` is a Koa.Context.
context.body = new RenderResultReadable(ssrResult);
```
#### Rendering to a string
To render to a string, you can use the `collectResult` or `collectResultSync` helper functions.
```js
import {render} from '@lit-labs/ssr';
import {
collectResult,
collectResultSync,
} from '@lit-labs/ssr/lib/render-result.js';
import {html} from 'lit';
const myServerTemplate = (name) => html`<p>Hello ${name}</p>`;
const ssrResult = render(myServerTemplate('SSR with Lit!'));
// Will throw if a Promise is encountered
console.log(collectResultSync(ssrResult));
// Awaits promises
console.log(await collectResult(ssrResult));
```
### Rendering in a separate VM context

@@ -51,3 +82,4 @@

import {render} from '@lit-labs/ssr/lib/render-lit-html.js';
import {render} from '@lit-labs/ssr';
import {RenderResultReadable} from '@lit-labs/ssr/lib/render-result-readable.js';
import {myTemplate} from './my-template.js';

@@ -74,3 +106,4 @@ export const renderTemplate = (someData) => {

context.body = Readable.from(ssrResult);
// Assume `context` is a Koa.Context, or other API that accepts a Readable.
context.body = new RenderResultReadable(ssrResult);
```

@@ -108,3 +141,4 @@

```js
import {render} from '@lit-labs/ssr/lib/render-lit-html.js';
import {html} from 'lit';
import {render} from '@lit-labs/ssr';
import './app-components.js';

@@ -151,2 +185,67 @@

## Server-only templates
`@lit-labs/ssr` also exports an `html` template function, similar to the normal Lit `html` function, only it's used for server-only templates. These templates can be used for rendering full documents, including the `<!DOCTYPE html>`, and rendering into elements that Lit normally cannot, like `<title>`, `<textarea>`, `<template>`, and safe `<script>` tags like `<script type="text/json">`. They are also slightly more efficient than normal Lit templates, because the generated HTML doesn't need to include markers for updating.
Server-only templates can be composed, and combined, and they support almost all features that normal Lit templates do, with the exception of features that don't have a pure HTML representation, like event handlers or property bindings.
Server-only templates can only be rendered on the server, they can't be rendered on the client. However if you render a normal Lit template inside a server-only template, then it can be hydrated and updated. Likewise, if you place a custom element inside a server-only template, it can be hydrated and update like normal.
Here's an example that shows how to use a server-only template to render a full document, and then lazily hydrate both a custom element and a template:
```js
import {render, html} from '@lit-labs/ssr';
import {RenderResultReadable} from '@lit-labs/ssr/lib/render-result-readable.js';
import './app-shell.js';
import {getContent} from './content-template.js';
const pageInfo = {
/* ... */
};
const ssrResult = render(html`
<!DOCTYPE html>
<html>
<head><title>MyApp ${pageInfo.title}</head>
<body>
<app-shell>
<!-- getContent is hydratable, as it returns a normal Lit template -->
<div id="content">${getContent(pageInfo.description)}</div>
</app-shell>
<script type="module">
// Hydrate template-shadowroots eagerly after rendering (for browsers without
// native declarative shadow roots)
import {
hasNativeDeclarativeShadowRoots,
hydrateShadowRoots,
} from './node_modules/@webcomponents/template-shadowroot/template-shadowroot.js';
import {hydrate} from '@lit-labs/ssr-client';
import {getContent} from './content-template.js';
if (!hasNativeDeclarativeShadowRoots()) {
hydrateShadowRoots(document.body);
}
// Load and hydrate app-shell lazily
import('./app-shell.js');
// Hydrate content template. This <script type=module> will run after
// the page has loaded, so we can count on page-id being present.
const pageInfo = JSON.parse(document.getElementById('page-info').textContent);
hydrate(getContent(pageInfo.description), document.querySelector('#content'));
// #content element can now be efficiently updated
</script>
<!-- Pass data to client. -->
<script type="text/json" id="page-info">
${JSON.stringify(pageInfo)}
</script>
</body>
</html>
`);
// ...
context.body = new RenderResultReadable(ssrResult);
```
## Notes and limitations

@@ -153,0 +252,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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