This commit is contained in:
2025-06-16 13:37:14 +02:00
parent ac273655e6
commit a8b82208f7
5100 changed files with 737524 additions and 2 deletions

21
node_modules/preact-render-to-string/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Jason Miller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

149
node_modules/preact-render-to-string/README.md generated vendored Normal file
View File

@@ -0,0 +1,149 @@
# preact-render-to-string
[![NPM](http://img.shields.io/npm/v/preact-render-to-string.svg)](https://www.npmjs.com/package/preact-render-to-string)
[![Build status](https://github.com/preactjs/preact-render-to-string/actions/workflows/ci.yml/badge.svg)](https://github.com/preactjs/preact-render-to-string/actions/workflows/ci.yml)
Render JSX and [Preact](https://github.com/preactjs/preact) components to an HTML string.
Works in Node & the browser, making it useful for universal/isomorphic rendering.
\>\> **[Cute Fox-Related Demo](http://codepen.io/developit/pen/dYZqjE?editors=001)** _(@ CodePen)_ <<
---
### Render JSX/VDOM to HTML
```js
import { render } from 'preact-render-to-string';
import { h } from 'preact';
/** @jsx h */
let vdom = <div class="foo">content</div>;
let html = render(vdom);
console.log(html);
// <div class="foo">content</div>
```
### Render Preact Components to HTML
```js
import { render } from 'preact-render-to-string';
import { h, Component } from 'preact';
/** @jsx h */
// Classical components work
class Fox extends Component {
render({ name }) {
return <span class="fox">{name}</span>;
}
}
// ... and so do pure functional components:
const Box = ({ type, children }) => (
<div class={`box box-${type}`}>{children}</div>
);
let html = render(
<Box type="open">
<Fox name="Finn" />
</Box>
);
console.log(html);
// <div class="box box-open"><span class="fox">Finn</span></div>
```
---
### Render JSX / Preact / Whatever via Express!
```js
import express from 'express';
import { h } from 'preact';
import { render } from 'preact-render-to-string';
/** @jsx h */
// silly example component:
const Fox = ({ name }) => (
<div class="fox">
<h5>{name}</h5>
<p>This page is all about {name}.</p>
</div>
);
// basic HTTP server via express:
const app = express();
app.listen(8080);
// on each request, render and return a component:
app.get('/:fox', (req, res) => {
let html = render(<Fox name={req.params.fox} />);
// send it back wrapped up as an HTML5 document:
res.send(`<!DOCTYPE html><html><body>${html}</body></html>`);
});
```
### Error Boundaries
Rendering errors can be caught by Preact via `getDerivedStateFromErrors` or `componentDidCatch`. To enable that feature in `preact-render-to-string` set `errorBoundaries = true`
```js
import { options } from 'preact';
// Enable error boundaries in `preact-render-to-string`
options.errorBoundaries = true;
```
---
### `Suspense` & `lazy` components with [`preact/compat`](https://www.npmjs.com/package/preact)
```bash
npm install preact preact-render-to-string
```
```jsx
export default () => {
return <h1>Home page</h1>;
};
```
```jsx
import { Suspense, lazy } from 'preact/compat';
// Creation of the lazy component
const HomePage = lazy(() => import('./pages/home'));
const Main = () => {
return (
<Suspense fallback={<p>Loading</p>}>
<HomePage />
</Suspense>
);
};
```
```jsx
import { renderToStringAsync } from 'preact-render-to-string';
import { Main } from './main';
const main = async () => {
// Rendering of lazy components
const html = await renderToStringAsync(<Main />);
console.log(html);
// <h1>Home page</h1>
};
// Execution & error handling
main().catch((error) => {
console.error(error);
});
```
---
### License
[MIT](http://choosealicense.com/licenses/mit/)

File diff suppressed because one or more lines are too long

17
node_modules/preact-render-to-string/dist/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,17 @@
import { VNode } from 'preact';
export default function renderToString<P = {}>(
vnode: VNode<P>,
context?: any
): string;
export function render<P = {}>(vnode: VNode<P>, context?: any): string;
export function renderToString<P = {}>(vnode: VNode<P>, context?: any): string;
export function renderToStringAsync<P = {}>(
vnode: VNode<P>,
context?: any
): string | Promise<string>;
export function renderToStaticMarkup<P = {}>(
vnode: VNode<P>,
context?: any
): string;

6
node_modules/preact-render-to-string/dist/index.js generated vendored Normal file
View File

@@ -0,0 +1,6 @@
const mod = require('./commonjs');
mod.default.renderToStringAsync = mod.renderToStringAsync;
mod.default.renderToStaticMarkup = mod.default;
mod.default.renderToString = mod.default;
mod.default.render = mod.default;
module.exports = mod.default;

File diff suppressed because one or more lines are too long

2
node_modules/preact-render-to-string/dist/index.mjs generated vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,35 @@
import { ComponentChildren, ComponentChild, VNode } from 'preact';
interface Suspended {
id: string;
promise: Promise<any>;
context: any;
isSvgMode: boolean;
selectValue: any;
vnode: VNode;
parent: VNode | null;
}
interface RendererErrorHandler {
(
this: RendererState,
error: any,
vnode: VNode<{ fallback: any }>,
renderChild: (child: ComponentChildren, parent: ComponentChild) => string
): string | undefined;
}
interface RendererState {
start: number;
suspended: Suspended[];
abortSignal?: AbortSignal | undefined;
onWrite: (str: string) => void;
onError?: RendererErrorHandler;
}
interface RenderToChunksOptions {
context?: any;
onError?: (error: any) => void;
onWrite: (str: string) => void;
abortSignal?: AbortSignal;
}

24
node_modules/preact-render-to-string/dist/jsx.d.ts generated vendored Normal file
View File

@@ -0,0 +1,24 @@
import { VNode } from 'preact';
interface Options {
jsx?: boolean;
xml?: boolean;
pretty?: boolean | string;
shallow?: boolean;
functions?: boolean;
functionNames?: boolean;
skipFalseAttributes?: boolean;
}
export default function renderToStringPretty(
vnode: VNode,
context?: any,
options?: Options
): string;
export function render(vnode: VNode, context?: any, options?: Options): string;
export function shallowRender(
vnode: VNode,
context?: any,
options?: Options
): string;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
const entry = require('./commonjs');
entry.default.render = entry.render;
entry.default.shallowRender = entry.shallowRender;
module.exports = entry.default;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,19 @@
import { VNode } from 'preact';
import { WritableStream } from 'node:stream';
interface RenderToPipeableStreamOptions {
onShellReady?: () => void;
onAllReady?: () => void;
onError?: (error: any) => void;
}
interface PipeableStream {
abort: (reason?: unknown) => void;
pipe: (writable: WritableStream) => void;
}
export function renderToPipeableStream<P = {}>(
vnode: VNode<P>,
options: RenderToPipeableStreamOptions,
context?: any
): PipeableStream;

10
node_modules/preact-render-to-string/dist/stream.d.ts generated vendored Normal file
View File

@@ -0,0 +1,10 @@
import { VNode } from 'preact';
interface RenderStream extends ReadableStream<Uint8Array> {
allReady: Promise<void>;
}
export function renderToReadableStream<P = {}>(
vnode: VNode<P>,
context?: any
): RenderStream;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,818 @@
var node_stream = require('node:stream');
var preact = require('preact');
const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
const NAMESPACE_REPLACE_REGEX = /^(xlink|xmlns|xml)([A-Z])/;
const HTML_LOWER_CASE = /^(?:accessK|auto[A-Z]|cell|ch|col|cont|cross|dateT|encT|form[A-Z]|frame|hrefL|inputM|maxL|minL|noV|playsI|popoverT|readO|rowS|src[A-Z]|tabI|useM|item[A-Z])/;
const SVG_CAMEL_CASE = /^ac|^ali|arabic|basel|cap|clipPath$|clipRule$|color|dominant|enable|fill|flood|font|glyph[^R]|horiz|image|letter|lighting|marker[^WUH]|overline|panose|pointe|paint|rendering|shape|stop|strikethrough|stroke|text[^L]|transform|underline|unicode|units|^v[^i]|^w|^xH/; // Boolean DOM properties that translate to enumerated ('true'/'false') attributes
const HTML_ENUMERATED = new Set(['draggable', 'spellcheck']); // DOM properties that should NOT have "px" added when numeric
const ENCODED_ENTITIES = /["&<]/;
/** @param {string} str */
function encodeEntities(str) {
// Skip all work for strings with no entities needing encoding:
if (str.length === 0 || ENCODED_ENTITIES.test(str) === false) return str;
let last = 0,
i = 0,
out = '',
ch = ''; // Seek forward in str until the next entity char:
for (; i < str.length; i++) {
switch (str.charCodeAt(i)) {
case 34:
ch = '&quot;';
break;
case 38:
ch = '&amp;';
break;
case 60:
ch = '&lt;';
break;
default:
continue;
} // Append skipped/buffered characters and the encoded entity:
if (i !== last) out = out + str.slice(last, i);
out = out + ch; // Start the next seek/buffer after the entity's offset:
last = i + 1;
}
if (i !== last) out = out + str.slice(last, i);
return out;
}
const JS_TO_CSS = {};
const IS_NON_DIMENSIONAL = new Set(['animation-iteration-count', 'border-image-outset', 'border-image-slice', 'border-image-width', 'box-flex', 'box-flex-group', 'box-ordinal-group', 'column-count', 'fill-opacity', 'flex', 'flex-grow', 'flex-negative', 'flex-order', 'flex-positive', 'flex-shrink', 'flood-opacity', 'font-weight', 'grid-column', 'grid-row', 'line-clamp', 'line-height', 'opacity', 'order', 'orphans', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'tab-size', 'widows', 'z-index', 'zoom']);
const CSS_REGEX = /[A-Z]/g; // Convert an Object style to a CSSText string
function styleObjToCss(s) {
let str = '';
for (let prop in s) {
let val = s[prop];
if (val != null && val !== '') {
const name = prop[0] == '-' ? prop : JS_TO_CSS[prop] || (JS_TO_CSS[prop] = prop.replace(CSS_REGEX, '-$&').toLowerCase());
let suffix = ';';
if (typeof val === 'number' && // Exclude custom-attributes
!name.startsWith('--') && !IS_NON_DIMENSIONAL.has(name)) {
suffix = 'px;';
}
str = str + name + ':' + val + suffix;
}
}
return str || undefined;
}
function markAsDirty() {
this.__d = true;
}
function createComponent(vnode, context) {
return {
__v: vnode,
context,
props: vnode.props,
// silently drop state updates
setState: markAsDirty,
forceUpdate: markAsDirty,
__d: true,
// hooks
__h: new Array(0)
};
} // Necessary for createContext api. Setting this property will pass
/**
* @template T
*/
class Deferred {
constructor() {
// eslint-disable-next-line lines-around-comment
/** @type {Promise<T>} */
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
// Options hooks
const DIFF = '__b';
const RENDER = '__r';
const DIFFED = 'diffed';
const COMMIT = '__c';
const SKIP_EFFECTS = '__s';
const CATCH_ERROR = '__e'; // VNode properties
const COMPONENT = '__c';
const CHILDREN = '__k';
const PARENT = '__';
const VNODE = '__v';
const DIRTY = '__d';
const NEXT_STATE = '__s';
const CHILD_DID_SUSPEND = '__c';
const EMPTY_OBJ = {};
const EMPTY_ARR = [];
const isArray = Array.isArray;
const assign = Object.assign;
const EMPTY_STR = ''; // Global state for the current render pass
let beforeDiff, afterDiff, renderHook, ummountHook;
/**
* Render Preact JSX + Components to an HTML string.
* @param {VNode} vnode JSX Element / VNode to render
* @param {Object} [context={}] Initial root context object
* @param {RendererState} [_rendererState] for internal use
* @returns {string} serialized HTML
*/
function renderToString(vnode, context, _rendererState) {
// Performance optimization: `renderToString` is synchronous and we
// therefore don't execute any effects. To do that we pass an empty
// array to `options._commit` (`__c`). But we can go one step further
// and avoid a lot of dirty checks and allocations by setting
// `options._skipEffects` (`__s`) too.
const previousSkipEffects = preact.options[SKIP_EFFECTS];
preact.options[SKIP_EFFECTS] = true; // store options hooks once before each synchronous render call
beforeDiff = preact.options[DIFF];
afterDiff = preact.options[DIFFED];
renderHook = preact.options[RENDER];
ummountHook = preact.options.unmount;
const parent = preact.h(preact.Fragment, null);
parent[CHILDREN] = [vnode];
try {
const rendered = _renderToString(vnode, context || EMPTY_OBJ, false, undefined, parent, false, _rendererState);
if (isArray(rendered)) {
return rendered.join(EMPTY_STR);
}
return rendered;
} catch (e) {
if (e.then) {
throw new Error('Use "renderToStringAsync" for suspenseful rendering.');
}
throw e;
} finally {
// options._commit, we don't schedule any effects in this library right now,
// so we can pass an empty queue to this hook.
if (preact.options[COMMIT]) preact.options[COMMIT](vnode, EMPTY_ARR);
preact.options[SKIP_EFFECTS] = previousSkipEffects;
EMPTY_ARR.length = 0;
}
}
/**
* @param {VNode} vnode
* @param {Record<string, unknown>} context
*/
function renderClassComponent(vnode, context) {
let type =
/** @type {import("preact").ComponentClass<typeof vnode.props>} */
vnode.type;
let isMounting = true;
let c;
if (vnode[COMPONENT]) {
isMounting = false;
c = vnode[COMPONENT];
c.state = c[NEXT_STATE];
} else {
c = new type(vnode.props, context);
}
vnode[COMPONENT] = c;
c[VNODE] = vnode;
c.props = vnode.props;
c.context = context; // turn off stateful re-rendering:
c[DIRTY] = true;
if (c.state == null) c.state = EMPTY_OBJ;
if (c[NEXT_STATE] == null) {
c[NEXT_STATE] = c.state;
}
if (type.getDerivedStateFromProps) {
c.state = assign({}, c.state, type.getDerivedStateFromProps(c.props, c.state));
} else if (isMounting && c.componentWillMount) {
c.componentWillMount(); // If the user called setState in cWM we need to flush pending,
// state updates. This is the same behaviour in React.
c.state = c[NEXT_STATE] !== c.state ? c[NEXT_STATE] : c.state;
} else if (!isMounting && c.componentWillUpdate) {
c.componentWillUpdate();
}
if (renderHook) renderHook(vnode);
return c.render(c.props, c.state, context);
}
/**
* Recursively render VNodes to HTML.
* @param {VNode|any} vnode
* @param {any} context
* @param {boolean} isSvgMode
* @param {any} selectValue
* @param {VNode} parent
* @param {boolean} asyncMode
* @param {RendererState | undefined} [renderer]
* @returns {string | Promise<string> | (string | Promise<string>)[]}
*/
function _renderToString(vnode, context, isSvgMode, selectValue, parent, asyncMode, renderer) {
// Ignore non-rendered VNodes/values
if (vnode == null || vnode === true || vnode === false || vnode === EMPTY_STR) {
return EMPTY_STR;
}
let vnodeType = typeof vnode; // Text VNodes: escape as HTML
if (vnodeType != 'object') {
if (vnodeType == 'function') return EMPTY_STR;
return vnodeType == 'string' ? encodeEntities(vnode) : vnode + EMPTY_STR;
} // Recurse into children / Arrays
if (isArray(vnode)) {
let rendered = EMPTY_STR,
renderArray;
parent[CHILDREN] = vnode;
const vnodeLength = vnode.length;
for (let i = 0; i < vnodeLength; i++) {
let child = vnode[i];
if (child == null || typeof child == 'boolean') continue;
const childRender = _renderToString(child, context, isSvgMode, selectValue, parent, asyncMode, renderer);
if (typeof childRender == 'string') {
rendered = rendered + childRender;
} else {
if (!renderArray) {
renderArray = new Array(vnodeLength);
}
if (rendered) renderArray.push(rendered);
rendered = EMPTY_STR;
if (isArray(childRender)) {
renderArray.push(...childRender);
} else {
renderArray.push(childRender);
}
}
}
if (renderArray) {
if (rendered) renderArray.push(rendered);
return renderArray;
}
return rendered;
} // VNodes have {constructor:undefined} to prevent JSON injection:
if (vnode.constructor !== undefined) return EMPTY_STR;
vnode[PARENT] = parent;
if (beforeDiff) beforeDiff(vnode);
let type = vnode.type,
props = vnode.props; // Invoke rendering on Components
if (typeof type == 'function') {
let cctx = context,
contextType,
rendered,
component;
if (type === preact.Fragment) {
// Serialized precompiled JSX.
if ('tpl' in props) {
let out = EMPTY_STR;
for (let i = 0; i < props.tpl.length; i++) {
out = out + props.tpl[i];
if (props.exprs && i < props.exprs.length) {
const value = props.exprs[i];
if (value == null) continue; // Check if we're dealing with a vnode or an array of nodes
if (typeof value == 'object' && (value.constructor === undefined || isArray(value))) {
out = out + _renderToString(value, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
} else {
// Values are pre-escaped by the JSX transform
out = out + value;
}
}
}
return out;
} else if ('UNSTABLE_comment' in props) {
// Fragments are the least used components of core that's why
// branching here for comments has the least effect on perf.
return '<!--' + encodeEntities(props.UNSTABLE_comment) + '-->';
}
rendered = props.children;
} else {
contextType = type.contextType;
if (contextType != null) {
let provider = context[contextType.__c];
cctx = provider ? provider.props.value : contextType.__;
}
let isClassComponent = type.prototype && typeof type.prototype.render == 'function';
if (isClassComponent) {
rendered =
/**#__NOINLINE__**/
renderClassComponent(vnode, cctx);
component = vnode[COMPONENT];
} else {
vnode[COMPONENT] = component =
/**#__NOINLINE__**/
createComponent(vnode, cctx); // If a hook invokes setState() to invalidate the component during rendering,
// re-render it up to 25 times to allow "settling" of memoized states.
// Note:
// This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
// https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
let count = 0;
while (component[DIRTY] && count++ < 25) {
component[DIRTY] = false;
if (renderHook) renderHook(vnode);
rendered = type.call(component, props, cctx);
}
component[DIRTY] = true;
}
if (component.getChildContext != null) {
context = assign({}, context, component.getChildContext());
}
if (isClassComponent && preact.options.errorBoundaries && (type.getDerivedStateFromError || component.componentDidCatch)) {
// When a component returns a Fragment node we flatten it in core, so we
// need to mirror that logic here too
let isTopLevelFragment = rendered != null && rendered.type === preact.Fragment && rendered.key == null && rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
try {
return _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
} catch (err) {
if (type.getDerivedStateFromError) {
component[NEXT_STATE] = type.getDerivedStateFromError(err);
}
if (component.componentDidCatch) {
component.componentDidCatch(err, EMPTY_OBJ);
}
if (component[DIRTY]) {
rendered = renderClassComponent(vnode, context);
component = vnode[COMPONENT];
if (component.getChildContext != null) {
context = assign({}, context, component.getChildContext());
}
let isTopLevelFragment = rendered != null && rendered.type === preact.Fragment && rendered.key == null && rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
return _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
}
return EMPTY_STR;
} finally {
if (afterDiff) afterDiff(vnode);
if (ummountHook) ummountHook(vnode);
}
}
} // When a component returns a Fragment node we flatten it in core, so we
// need to mirror that logic here too
let isTopLevelFragment = rendered != null && rendered.type === preact.Fragment && rendered.key == null && rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
try {
// Recurse into children before invoking the after-diff hook
const str = _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
if (afterDiff) afterDiff(vnode); // when we are dealing with suspense we can't do this...
if (preact.options.unmount) preact.options.unmount(vnode);
return str;
} catch (error) {
if (!asyncMode && renderer && renderer.onError) {
let res = renderer.onError(error, vnode, (child, parent) => _renderToString(child, context, isSvgMode, selectValue, parent, asyncMode, renderer));
if (res !== undefined) return res;
let errorHook = preact.options[CATCH_ERROR];
if (errorHook) errorHook(error, vnode);
return EMPTY_STR;
}
if (!asyncMode) throw error;
if (!error || typeof error.then != 'function') throw error;
const renderNestedChildren = () => {
try {
return _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
} catch (e) {
if (!e || typeof e.then != 'function') throw e;
return e.then(() => _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer), renderNestedChildren);
}
};
return error.then(renderNestedChildren);
}
} // Serialize Element VNodes to HTML
let s = '<' + type,
html = EMPTY_STR,
children;
for (let name in props) {
let v = props[name];
if (typeof v == 'function' && name !== 'class' && name !== 'className') {
continue;
}
switch (name) {
case 'children':
children = v;
continue;
// VDOM-specific props
case 'key':
case 'ref':
case '__self':
case '__source':
continue;
// prefer for/class over htmlFor/className
case 'htmlFor':
if ('for' in props) continue;
name = 'for';
break;
case 'className':
if ('class' in props) continue;
name = 'class';
break;
// Form element reflected properties
case 'defaultChecked':
name = 'checked';
break;
case 'defaultSelected':
name = 'selected';
break;
// Special value attribute handling
case 'defaultValue':
case 'value':
name = 'value';
switch (type) {
// <textarea value="a&b"> --> <textarea>a&amp;b</textarea>
case 'textarea':
children = v;
continue;
// <select value> is serialized as a selected attribute on the matching option child
case 'select':
selectValue = v;
continue;
// Add a selected attribute to <option> if its value matches the parent <select> value
case 'option':
if (selectValue == v && !('selected' in props)) {
s = s + ' selected';
}
break;
}
break;
case 'dangerouslySetInnerHTML':
html = v && v.__html;
continue;
// serialize object styles to a CSS string
case 'style':
if (typeof v === 'object') {
v = styleObjToCss(v);
}
break;
case 'acceptCharset':
name = 'accept-charset';
break;
case 'httpEquiv':
name = 'http-equiv';
break;
default:
{
if (NAMESPACE_REPLACE_REGEX.test(name)) {
name = name.replace(NAMESPACE_REPLACE_REGEX, '$1:$2').toLowerCase();
} else if (UNSAFE_NAME.test(name)) {
continue;
} else if ((name[4] === '-' || HTML_ENUMERATED.has(name)) && v != null) {
// serialize boolean aria-xyz or enumerated attribute values as strings
v = v + EMPTY_STR;
} else if (isSvgMode) {
if (SVG_CAMEL_CASE.test(name)) {
name = name === 'panose1' ? 'panose-1' : name.replace(/([A-Z])/g, '-$1').toLowerCase();
}
} else if (HTML_LOWER_CASE.test(name)) {
name = name.toLowerCase();
}
}
} // write this attribute to the buffer
if (v != null && v !== false) {
if (v === true || v === EMPTY_STR) {
s = s + ' ' + name;
} else {
s = s + ' ' + name + '="' + (typeof v == 'string' ? encodeEntities(v) : v + EMPTY_STR) + '"';
}
}
}
if (UNSAFE_NAME.test(type)) {
// this seems to performs a lot better than throwing
// return '<!-- -->';
throw new Error(`${type} is not a valid HTML tag name in ${s}>`);
}
if (html) ; else if (typeof children === 'string') {
// single text child
html = encodeEntities(children);
} else if (children != null && children !== false && children !== true) {
// recurse into this element VNode's children
let childSvgMode = type === 'svg' || type !== 'foreignObject' && isSvgMode;
html = _renderToString(children, context, childSvgMode, selectValue, vnode, asyncMode, renderer);
}
if (afterDiff) afterDiff(vnode);
if (ummountHook) ummountHook(vnode); // Emit self-closing tag for empty void elements:
if (!html && SELF_CLOSING.has(type)) {
return s + '/>';
}
const endTag = '</' + type + '>';
const startTag = s + '>';
if (isArray(html)) return [startTag, ...html, endTag];else if (typeof html != 'string') return [startTag, html, endTag];
return startTag + html + endTag;
}
const SELF_CLOSING = new Set(['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
/* eslint-disable no-var, key-spacing, object-curly-spacing, prefer-arrow-callback, semi, keyword-spacing */
// function initPreactIslandElement() {
// class PreactIslandElement extends HTMLElement {
// connectedCallback() {
// var d = this;
// if (!d.isConnected) return;
// let i = this.getAttribute('data-target');
// if (!i) return;
// var s,
// e,
// c = document.createNodeIterator(document, 128);
// while (c.nextNode()) {
// let n = c.referenceNode;
// if (n.data == 'preact-island:' + i) s = n;
// else if (n.data == '/preact-island:' + i) e = n;
// if (s && e) break;
// }
// if (s && e) {
// requestAnimationFrame(() => {
// var p = e.previousSibling;
// while (p != s) {
// if (!p || p == s) break;
// e.parentNode.removeChild(p);
// p = e.previousSibling;
// }
// c = s;
// while (d.firstChild) {
// s = d.firstChild;
// d.removeChild(s);
// c.after(s);
// c = s;
// }
// d.parentNode.removeChild(d);
// });
// }
// }
// }
// customElements.define('preact-island', PreactIslandElement);
// }
// To modify the INIT_SCRIPT, uncomment the above code, modify it, and paste it into https://try.terser.org/.
const INIT_SCRIPT = `class e extends HTMLElement{connectedCallback(){var e=this;if(!e.isConnected)return;let t=this.getAttribute("data-target");if(t){for(var r,a,i=document.createNodeIterator(document,128);i.nextNode();){let e=i.referenceNode;if(e.data=="preact-island:"+t?r=e:e.data=="/preact-island:"+t&&(a=e),r&&a)break}r&&a&&requestAnimationFrame((()=>{for(var t=a.previousSibling;t!=r&&t&&t!=r;)a.parentNode.removeChild(t),t=a.previousSibling;for(i=r;e.firstChild;)r=e.firstChild,e.removeChild(r),i.after(r),i=r;e.parentNode.removeChild(e)}))}}}customElements.define("preact-island",e);`;
function createInitScript() {
return `<script>(function(){${INIT_SCRIPT}}())</script>`;
}
/**
* @param {string} id
* @param {string} content
* @returns {string}
*/
function createSubtree(id, content) {
return `<preact-island hidden data-target="${id}">${content}</preact-island>`;
}
/**
* @param {VNode} vnode
* @param {RenderToChunksOptions} options
* @returns {Promise<void>}
*/
async function renderToChunks(vnode, {
context,
onWrite,
abortSignal
}) {
context = context || {};
/** @type {RendererState} */
const renderer = {
start: Date.now(),
abortSignal,
onWrite,
onError: handleError,
suspended: []
}; // Synchronously render the shell
// @ts-ignore - using third internal RendererState argument
const shell = renderToString(vnode, context, renderer);
onWrite(shell); // Wait for any suspended sub-trees if there are any
const len = renderer.suspended.length;
if (len > 0) {
onWrite('<div hidden>');
onWrite(createInitScript()); // We should keep checking all promises
await forkPromises(renderer);
onWrite('</div>');
}
}
async function forkPromises(renderer) {
if (renderer.suspended.length > 0) {
const suspensions = [...renderer.suspended];
await Promise.all(renderer.suspended.map(s => s.promise));
renderer.suspended = renderer.suspended.filter(s => !suspensions.includes(s));
await forkPromises(renderer);
}
}
/** @type {RendererErrorHandler} */
function handleError(error, vnode, renderChild) {
if (!error || !error.then) return; // walk up to the Suspense boundary
while (vnode = vnode[PARENT]) {
let component = vnode[COMPONENT];
if (component && component[CHILD_DID_SUSPEND]) {
break;
}
}
if (!vnode) return;
const id = vnode.__v;
const found = this.suspended.find(x => x.id === id);
const race = new Deferred();
const abortSignal = this.abortSignal;
if (abortSignal) {
// @ts-ignore 2554 - implicit undefined arg
if (abortSignal.aborted) race.resolve();else abortSignal.addEventListener('abort', race.resolve);
}
const promise = error.then(() => {
if (abortSignal && abortSignal.aborted) return;
const child = renderChild(vnode.props.children, vnode);
if (child) this.onWrite(createSubtree(id, child));
}, // TODO: Abort and send hydration code snippet to client
// to attempt to recover during hydration
this.onError);
this.suspended.push({
id,
vnode,
promise: Promise.race([promise, race.promise])
});
const fallback = renderChild(vnode.props.fallback);
return found ? '' : `<!--preact-island:${id}-->${fallback}<!--/preact-island:${id}-->`;
}
/**
* @typedef {object} RenderToPipeableStreamOptions
* @property {() => void} [onShellReady]
* @property {() => void} [onAllReady]
* @property {(error) => void} [onError]
*/
/**
* @typedef {object} PipeableStream
* @property {() => void} abort
* @property {(writable: import('stream').Writable) => void} pipe
*/
/**
* @param {import('preact').VNode} vnode
* @param {RenderToPipeableStreamOptions} options
* @param {any} [context]
* @returns {PipeableStream}
*/
function renderToPipeableStream(vnode, options, context) {
const encoder = new TextEncoder('utf-8');
const controller = new AbortController();
const stream = new node_stream.PassThrough();
renderToChunks(vnode, {
context,
abortSignal: controller.signal,
onError: error => {
if (options.onError) {
options.onError(error);
}
controller.abort(error);
},
onWrite(s) {
stream.write(encoder.encode(s));
}
}).then(() => {
options.onAllReady && options.onAllReady();
stream.end();
}).catch(error => {
stream.destroy();
if (options.onError) {
options.onError(error);
} else {
throw error;
}
});
Promise.resolve().then(() => {
options.onShellReady && options.onShellReady();
});
return {
/**
* @param {unknown} [reason]
*/
abort(reason = new Error('The render was aborted by the server without a reason.')) {
controller.abort();
stream.destroy();
if (options.onError) {
options.onError(reason);
}
},
/**
* @param {import("stream").Writable} writable
*/
pipe(writable) {
stream.pipe(writable, {
end: true
});
}
};
}
exports.renderToPipeableStream = renderToPipeableStream;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,818 @@
import { PassThrough } from 'node:stream';
import { options, h, Fragment } from 'preact';
const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
const NAMESPACE_REPLACE_REGEX = /^(xlink|xmlns|xml)([A-Z])/;
const HTML_LOWER_CASE = /^(?:accessK|auto[A-Z]|cell|ch|col|cont|cross|dateT|encT|form[A-Z]|frame|hrefL|inputM|maxL|minL|noV|playsI|popoverT|readO|rowS|src[A-Z]|tabI|useM|item[A-Z])/;
const SVG_CAMEL_CASE = /^ac|^ali|arabic|basel|cap|clipPath$|clipRule$|color|dominant|enable|fill|flood|font|glyph[^R]|horiz|image|letter|lighting|marker[^WUH]|overline|panose|pointe|paint|rendering|shape|stop|strikethrough|stroke|text[^L]|transform|underline|unicode|units|^v[^i]|^w|^xH/; // Boolean DOM properties that translate to enumerated ('true'/'false') attributes
const HTML_ENUMERATED = new Set(['draggable', 'spellcheck']); // DOM properties that should NOT have "px" added when numeric
const ENCODED_ENTITIES = /["&<]/;
/** @param {string} str */
function encodeEntities(str) {
// Skip all work for strings with no entities needing encoding:
if (str.length === 0 || ENCODED_ENTITIES.test(str) === false) return str;
let last = 0,
i = 0,
out = '',
ch = ''; // Seek forward in str until the next entity char:
for (; i < str.length; i++) {
switch (str.charCodeAt(i)) {
case 34:
ch = '&quot;';
break;
case 38:
ch = '&amp;';
break;
case 60:
ch = '&lt;';
break;
default:
continue;
} // Append skipped/buffered characters and the encoded entity:
if (i !== last) out = out + str.slice(last, i);
out = out + ch; // Start the next seek/buffer after the entity's offset:
last = i + 1;
}
if (i !== last) out = out + str.slice(last, i);
return out;
}
const JS_TO_CSS = {};
const IS_NON_DIMENSIONAL = new Set(['animation-iteration-count', 'border-image-outset', 'border-image-slice', 'border-image-width', 'box-flex', 'box-flex-group', 'box-ordinal-group', 'column-count', 'fill-opacity', 'flex', 'flex-grow', 'flex-negative', 'flex-order', 'flex-positive', 'flex-shrink', 'flood-opacity', 'font-weight', 'grid-column', 'grid-row', 'line-clamp', 'line-height', 'opacity', 'order', 'orphans', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'tab-size', 'widows', 'z-index', 'zoom']);
const CSS_REGEX = /[A-Z]/g; // Convert an Object style to a CSSText string
function styleObjToCss(s) {
let str = '';
for (let prop in s) {
let val = s[prop];
if (val != null && val !== '') {
const name = prop[0] == '-' ? prop : JS_TO_CSS[prop] || (JS_TO_CSS[prop] = prop.replace(CSS_REGEX, '-$&').toLowerCase());
let suffix = ';';
if (typeof val === 'number' && // Exclude custom-attributes
!name.startsWith('--') && !IS_NON_DIMENSIONAL.has(name)) {
suffix = 'px;';
}
str = str + name + ':' + val + suffix;
}
}
return str || undefined;
}
function markAsDirty() {
this.__d = true;
}
function createComponent(vnode, context) {
return {
__v: vnode,
context,
props: vnode.props,
// silently drop state updates
setState: markAsDirty,
forceUpdate: markAsDirty,
__d: true,
// hooks
__h: new Array(0)
};
} // Necessary for createContext api. Setting this property will pass
/**
* @template T
*/
class Deferred {
constructor() {
// eslint-disable-next-line lines-around-comment
/** @type {Promise<T>} */
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
// Options hooks
const DIFF = '__b';
const RENDER = '__r';
const DIFFED = 'diffed';
const COMMIT = '__c';
const SKIP_EFFECTS = '__s';
const CATCH_ERROR = '__e'; // VNode properties
const COMPONENT = '__c';
const CHILDREN = '__k';
const PARENT = '__';
const VNODE = '__v';
const DIRTY = '__d';
const NEXT_STATE = '__s';
const CHILD_DID_SUSPEND = '__c';
const EMPTY_OBJ = {};
const EMPTY_ARR = [];
const isArray = Array.isArray;
const assign = Object.assign;
const EMPTY_STR = ''; // Global state for the current render pass
let beforeDiff, afterDiff, renderHook, ummountHook;
/**
* Render Preact JSX + Components to an HTML string.
* @param {VNode} vnode JSX Element / VNode to render
* @param {Object} [context={}] Initial root context object
* @param {RendererState} [_rendererState] for internal use
* @returns {string} serialized HTML
*/
function renderToString(vnode, context, _rendererState) {
// Performance optimization: `renderToString` is synchronous and we
// therefore don't execute any effects. To do that we pass an empty
// array to `options._commit` (`__c`). But we can go one step further
// and avoid a lot of dirty checks and allocations by setting
// `options._skipEffects` (`__s`) too.
const previousSkipEffects = options[SKIP_EFFECTS];
options[SKIP_EFFECTS] = true; // store options hooks once before each synchronous render call
beforeDiff = options[DIFF];
afterDiff = options[DIFFED];
renderHook = options[RENDER];
ummountHook = options.unmount;
const parent = h(Fragment, null);
parent[CHILDREN] = [vnode];
try {
const rendered = _renderToString(vnode, context || EMPTY_OBJ, false, undefined, parent, false, _rendererState);
if (isArray(rendered)) {
return rendered.join(EMPTY_STR);
}
return rendered;
} catch (e) {
if (e.then) {
throw new Error('Use "renderToStringAsync" for suspenseful rendering.');
}
throw e;
} finally {
// options._commit, we don't schedule any effects in this library right now,
// so we can pass an empty queue to this hook.
if (options[COMMIT]) options[COMMIT](vnode, EMPTY_ARR);
options[SKIP_EFFECTS] = previousSkipEffects;
EMPTY_ARR.length = 0;
}
}
/**
* @param {VNode} vnode
* @param {Record<string, unknown>} context
*/
function renderClassComponent(vnode, context) {
let type =
/** @type {import("preact").ComponentClass<typeof vnode.props>} */
vnode.type;
let isMounting = true;
let c;
if (vnode[COMPONENT]) {
isMounting = false;
c = vnode[COMPONENT];
c.state = c[NEXT_STATE];
} else {
c = new type(vnode.props, context);
}
vnode[COMPONENT] = c;
c[VNODE] = vnode;
c.props = vnode.props;
c.context = context; // turn off stateful re-rendering:
c[DIRTY] = true;
if (c.state == null) c.state = EMPTY_OBJ;
if (c[NEXT_STATE] == null) {
c[NEXT_STATE] = c.state;
}
if (type.getDerivedStateFromProps) {
c.state = assign({}, c.state, type.getDerivedStateFromProps(c.props, c.state));
} else if (isMounting && c.componentWillMount) {
c.componentWillMount(); // If the user called setState in cWM we need to flush pending,
// state updates. This is the same behaviour in React.
c.state = c[NEXT_STATE] !== c.state ? c[NEXT_STATE] : c.state;
} else if (!isMounting && c.componentWillUpdate) {
c.componentWillUpdate();
}
if (renderHook) renderHook(vnode);
return c.render(c.props, c.state, context);
}
/**
* Recursively render VNodes to HTML.
* @param {VNode|any} vnode
* @param {any} context
* @param {boolean} isSvgMode
* @param {any} selectValue
* @param {VNode} parent
* @param {boolean} asyncMode
* @param {RendererState | undefined} [renderer]
* @returns {string | Promise<string> | (string | Promise<string>)[]}
*/
function _renderToString(vnode, context, isSvgMode, selectValue, parent, asyncMode, renderer) {
// Ignore non-rendered VNodes/values
if (vnode == null || vnode === true || vnode === false || vnode === EMPTY_STR) {
return EMPTY_STR;
}
let vnodeType = typeof vnode; // Text VNodes: escape as HTML
if (vnodeType != 'object') {
if (vnodeType == 'function') return EMPTY_STR;
return vnodeType == 'string' ? encodeEntities(vnode) : vnode + EMPTY_STR;
} // Recurse into children / Arrays
if (isArray(vnode)) {
let rendered = EMPTY_STR,
renderArray;
parent[CHILDREN] = vnode;
const vnodeLength = vnode.length;
for (let i = 0; i < vnodeLength; i++) {
let child = vnode[i];
if (child == null || typeof child == 'boolean') continue;
const childRender = _renderToString(child, context, isSvgMode, selectValue, parent, asyncMode, renderer);
if (typeof childRender == 'string') {
rendered = rendered + childRender;
} else {
if (!renderArray) {
renderArray = new Array(vnodeLength);
}
if (rendered) renderArray.push(rendered);
rendered = EMPTY_STR;
if (isArray(childRender)) {
renderArray.push(...childRender);
} else {
renderArray.push(childRender);
}
}
}
if (renderArray) {
if (rendered) renderArray.push(rendered);
return renderArray;
}
return rendered;
} // VNodes have {constructor:undefined} to prevent JSON injection:
if (vnode.constructor !== undefined) return EMPTY_STR;
vnode[PARENT] = parent;
if (beforeDiff) beforeDiff(vnode);
let type = vnode.type,
props = vnode.props; // Invoke rendering on Components
if (typeof type == 'function') {
let cctx = context,
contextType,
rendered,
component;
if (type === Fragment) {
// Serialized precompiled JSX.
if ('tpl' in props) {
let out = EMPTY_STR;
for (let i = 0; i < props.tpl.length; i++) {
out = out + props.tpl[i];
if (props.exprs && i < props.exprs.length) {
const value = props.exprs[i];
if (value == null) continue; // Check if we're dealing with a vnode or an array of nodes
if (typeof value == 'object' && (value.constructor === undefined || isArray(value))) {
out = out + _renderToString(value, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
} else {
// Values are pre-escaped by the JSX transform
out = out + value;
}
}
}
return out;
} else if ('UNSTABLE_comment' in props) {
// Fragments are the least used components of core that's why
// branching here for comments has the least effect on perf.
return '<!--' + encodeEntities(props.UNSTABLE_comment) + '-->';
}
rendered = props.children;
} else {
contextType = type.contextType;
if (contextType != null) {
let provider = context[contextType.__c];
cctx = provider ? provider.props.value : contextType.__;
}
let isClassComponent = type.prototype && typeof type.prototype.render == 'function';
if (isClassComponent) {
rendered =
/**#__NOINLINE__**/
renderClassComponent(vnode, cctx);
component = vnode[COMPONENT];
} else {
vnode[COMPONENT] = component =
/**#__NOINLINE__**/
createComponent(vnode, cctx); // If a hook invokes setState() to invalidate the component during rendering,
// re-render it up to 25 times to allow "settling" of memoized states.
// Note:
// This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
// https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
let count = 0;
while (component[DIRTY] && count++ < 25) {
component[DIRTY] = false;
if (renderHook) renderHook(vnode);
rendered = type.call(component, props, cctx);
}
component[DIRTY] = true;
}
if (component.getChildContext != null) {
context = assign({}, context, component.getChildContext());
}
if (isClassComponent && options.errorBoundaries && (type.getDerivedStateFromError || component.componentDidCatch)) {
// When a component returns a Fragment node we flatten it in core, so we
// need to mirror that logic here too
let isTopLevelFragment = rendered != null && rendered.type === Fragment && rendered.key == null && rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
try {
return _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
} catch (err) {
if (type.getDerivedStateFromError) {
component[NEXT_STATE] = type.getDerivedStateFromError(err);
}
if (component.componentDidCatch) {
component.componentDidCatch(err, EMPTY_OBJ);
}
if (component[DIRTY]) {
rendered = renderClassComponent(vnode, context);
component = vnode[COMPONENT];
if (component.getChildContext != null) {
context = assign({}, context, component.getChildContext());
}
let isTopLevelFragment = rendered != null && rendered.type === Fragment && rendered.key == null && rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
return _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
}
return EMPTY_STR;
} finally {
if (afterDiff) afterDiff(vnode);
if (ummountHook) ummountHook(vnode);
}
}
} // When a component returns a Fragment node we flatten it in core, so we
// need to mirror that logic here too
let isTopLevelFragment = rendered != null && rendered.type === Fragment && rendered.key == null && rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
try {
// Recurse into children before invoking the after-diff hook
const str = _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
if (afterDiff) afterDiff(vnode); // when we are dealing with suspense we can't do this...
if (options.unmount) options.unmount(vnode);
return str;
} catch (error) {
if (!asyncMode && renderer && renderer.onError) {
let res = renderer.onError(error, vnode, (child, parent) => _renderToString(child, context, isSvgMode, selectValue, parent, asyncMode, renderer));
if (res !== undefined) return res;
let errorHook = options[CATCH_ERROR];
if (errorHook) errorHook(error, vnode);
return EMPTY_STR;
}
if (!asyncMode) throw error;
if (!error || typeof error.then != 'function') throw error;
const renderNestedChildren = () => {
try {
return _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
} catch (e) {
if (!e || typeof e.then != 'function') throw e;
return e.then(() => _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer), renderNestedChildren);
}
};
return error.then(renderNestedChildren);
}
} // Serialize Element VNodes to HTML
let s = '<' + type,
html = EMPTY_STR,
children;
for (let name in props) {
let v = props[name];
if (typeof v == 'function' && name !== 'class' && name !== 'className') {
continue;
}
switch (name) {
case 'children':
children = v;
continue;
// VDOM-specific props
case 'key':
case 'ref':
case '__self':
case '__source':
continue;
// prefer for/class over htmlFor/className
case 'htmlFor':
if ('for' in props) continue;
name = 'for';
break;
case 'className':
if ('class' in props) continue;
name = 'class';
break;
// Form element reflected properties
case 'defaultChecked':
name = 'checked';
break;
case 'defaultSelected':
name = 'selected';
break;
// Special value attribute handling
case 'defaultValue':
case 'value':
name = 'value';
switch (type) {
// <textarea value="a&b"> --> <textarea>a&amp;b</textarea>
case 'textarea':
children = v;
continue;
// <select value> is serialized as a selected attribute on the matching option child
case 'select':
selectValue = v;
continue;
// Add a selected attribute to <option> if its value matches the parent <select> value
case 'option':
if (selectValue == v && !('selected' in props)) {
s = s + ' selected';
}
break;
}
break;
case 'dangerouslySetInnerHTML':
html = v && v.__html;
continue;
// serialize object styles to a CSS string
case 'style':
if (typeof v === 'object') {
v = styleObjToCss(v);
}
break;
case 'acceptCharset':
name = 'accept-charset';
break;
case 'httpEquiv':
name = 'http-equiv';
break;
default:
{
if (NAMESPACE_REPLACE_REGEX.test(name)) {
name = name.replace(NAMESPACE_REPLACE_REGEX, '$1:$2').toLowerCase();
} else if (UNSAFE_NAME.test(name)) {
continue;
} else if ((name[4] === '-' || HTML_ENUMERATED.has(name)) && v != null) {
// serialize boolean aria-xyz or enumerated attribute values as strings
v = v + EMPTY_STR;
} else if (isSvgMode) {
if (SVG_CAMEL_CASE.test(name)) {
name = name === 'panose1' ? 'panose-1' : name.replace(/([A-Z])/g, '-$1').toLowerCase();
}
} else if (HTML_LOWER_CASE.test(name)) {
name = name.toLowerCase();
}
}
} // write this attribute to the buffer
if (v != null && v !== false) {
if (v === true || v === EMPTY_STR) {
s = s + ' ' + name;
} else {
s = s + ' ' + name + '="' + (typeof v == 'string' ? encodeEntities(v) : v + EMPTY_STR) + '"';
}
}
}
if (UNSAFE_NAME.test(type)) {
// this seems to performs a lot better than throwing
// return '<!-- -->';
throw new Error(`${type} is not a valid HTML tag name in ${s}>`);
}
if (html) ; else if (typeof children === 'string') {
// single text child
html = encodeEntities(children);
} else if (children != null && children !== false && children !== true) {
// recurse into this element VNode's children
let childSvgMode = type === 'svg' || type !== 'foreignObject' && isSvgMode;
html = _renderToString(children, context, childSvgMode, selectValue, vnode, asyncMode, renderer);
}
if (afterDiff) afterDiff(vnode);
if (ummountHook) ummountHook(vnode); // Emit self-closing tag for empty void elements:
if (!html && SELF_CLOSING.has(type)) {
return s + '/>';
}
const endTag = '</' + type + '>';
const startTag = s + '>';
if (isArray(html)) return [startTag, ...html, endTag];else if (typeof html != 'string') return [startTag, html, endTag];
return startTag + html + endTag;
}
const SELF_CLOSING = new Set(['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
/* eslint-disable no-var, key-spacing, object-curly-spacing, prefer-arrow-callback, semi, keyword-spacing */
// function initPreactIslandElement() {
// class PreactIslandElement extends HTMLElement {
// connectedCallback() {
// var d = this;
// if (!d.isConnected) return;
// let i = this.getAttribute('data-target');
// if (!i) return;
// var s,
// e,
// c = document.createNodeIterator(document, 128);
// while (c.nextNode()) {
// let n = c.referenceNode;
// if (n.data == 'preact-island:' + i) s = n;
// else if (n.data == '/preact-island:' + i) e = n;
// if (s && e) break;
// }
// if (s && e) {
// requestAnimationFrame(() => {
// var p = e.previousSibling;
// while (p != s) {
// if (!p || p == s) break;
// e.parentNode.removeChild(p);
// p = e.previousSibling;
// }
// c = s;
// while (d.firstChild) {
// s = d.firstChild;
// d.removeChild(s);
// c.after(s);
// c = s;
// }
// d.parentNode.removeChild(d);
// });
// }
// }
// }
// customElements.define('preact-island', PreactIslandElement);
// }
// To modify the INIT_SCRIPT, uncomment the above code, modify it, and paste it into https://try.terser.org/.
const INIT_SCRIPT = `class e extends HTMLElement{connectedCallback(){var e=this;if(!e.isConnected)return;let t=this.getAttribute("data-target");if(t){for(var r,a,i=document.createNodeIterator(document,128);i.nextNode();){let e=i.referenceNode;if(e.data=="preact-island:"+t?r=e:e.data=="/preact-island:"+t&&(a=e),r&&a)break}r&&a&&requestAnimationFrame((()=>{for(var t=a.previousSibling;t!=r&&t&&t!=r;)a.parentNode.removeChild(t),t=a.previousSibling;for(i=r;e.firstChild;)r=e.firstChild,e.removeChild(r),i.after(r),i=r;e.parentNode.removeChild(e)}))}}}customElements.define("preact-island",e);`;
function createInitScript() {
return `<script>(function(){${INIT_SCRIPT}}())</script>`;
}
/**
* @param {string} id
* @param {string} content
* @returns {string}
*/
function createSubtree(id, content) {
return `<preact-island hidden data-target="${id}">${content}</preact-island>`;
}
/**
* @param {VNode} vnode
* @param {RenderToChunksOptions} options
* @returns {Promise<void>}
*/
async function renderToChunks(vnode, {
context,
onWrite,
abortSignal
}) {
context = context || {};
/** @type {RendererState} */
const renderer = {
start: Date.now(),
abortSignal,
onWrite,
onError: handleError,
suspended: []
}; // Synchronously render the shell
// @ts-ignore - using third internal RendererState argument
const shell = renderToString(vnode, context, renderer);
onWrite(shell); // Wait for any suspended sub-trees if there are any
const len = renderer.suspended.length;
if (len > 0) {
onWrite('<div hidden>');
onWrite(createInitScript()); // We should keep checking all promises
await forkPromises(renderer);
onWrite('</div>');
}
}
async function forkPromises(renderer) {
if (renderer.suspended.length > 0) {
const suspensions = [...renderer.suspended];
await Promise.all(renderer.suspended.map(s => s.promise));
renderer.suspended = renderer.suspended.filter(s => !suspensions.includes(s));
await forkPromises(renderer);
}
}
/** @type {RendererErrorHandler} */
function handleError(error, vnode, renderChild) {
if (!error || !error.then) return; // walk up to the Suspense boundary
while (vnode = vnode[PARENT]) {
let component = vnode[COMPONENT];
if (component && component[CHILD_DID_SUSPEND]) {
break;
}
}
if (!vnode) return;
const id = vnode.__v;
const found = this.suspended.find(x => x.id === id);
const race = new Deferred();
const abortSignal = this.abortSignal;
if (abortSignal) {
// @ts-ignore 2554 - implicit undefined arg
if (abortSignal.aborted) race.resolve();else abortSignal.addEventListener('abort', race.resolve);
}
const promise = error.then(() => {
if (abortSignal && abortSignal.aborted) return;
const child = renderChild(vnode.props.children, vnode);
if (child) this.onWrite(createSubtree(id, child));
}, // TODO: Abort and send hydration code snippet to client
// to attempt to recover during hydration
this.onError);
this.suspended.push({
id,
vnode,
promise: Promise.race([promise, race.promise])
});
const fallback = renderChild(vnode.props.fallback);
return found ? '' : `<!--preact-island:${id}-->${fallback}<!--/preact-island:${id}-->`;
}
/**
* @typedef {object} RenderToPipeableStreamOptions
* @property {() => void} [onShellReady]
* @property {() => void} [onAllReady]
* @property {(error) => void} [onError]
*/
/**
* @typedef {object} PipeableStream
* @property {() => void} abort
* @property {(writable: import('stream').Writable) => void} pipe
*/
/**
* @param {import('preact').VNode} vnode
* @param {RenderToPipeableStreamOptions} options
* @param {any} [context]
* @returns {PipeableStream}
*/
function renderToPipeableStream(vnode, options, context) {
const encoder = new TextEncoder('utf-8');
const controller = new AbortController();
const stream = new PassThrough();
renderToChunks(vnode, {
context,
abortSignal: controller.signal,
onError: error => {
if (options.onError) {
options.onError(error);
}
controller.abort(error);
},
onWrite(s) {
stream.write(encoder.encode(s));
}
}).then(() => {
options.onAllReady && options.onAllReady();
stream.end();
}).catch(error => {
stream.destroy();
if (options.onError) {
options.onError(error);
} else {
throw error;
}
});
Promise.resolve().then(() => {
options.onShellReady && options.onShellReady();
});
return {
/**
* @param {unknown} [reason]
*/
abort(reason = new Error('The render was aborted by the server without a reason.')) {
controller.abort();
stream.destroy();
if (options.onError) {
options.onError(reason);
}
},
/**
* @param {import("stream").Writable} writable
*/
pipe(writable) {
stream.pipe(writable, {
end: true
});
}
};
}
export { renderToPipeableStream };
//# sourceMappingURL=index.module.js.map

View File

@@ -0,0 +1,818 @@
import { PassThrough } from 'node:stream';
import { options, h, Fragment } from 'preact';
const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
const NAMESPACE_REPLACE_REGEX = /^(xlink|xmlns|xml)([A-Z])/;
const HTML_LOWER_CASE = /^(?:accessK|auto[A-Z]|cell|ch|col|cont|cross|dateT|encT|form[A-Z]|frame|hrefL|inputM|maxL|minL|noV|playsI|popoverT|readO|rowS|src[A-Z]|tabI|useM|item[A-Z])/;
const SVG_CAMEL_CASE = /^ac|^ali|arabic|basel|cap|clipPath$|clipRule$|color|dominant|enable|fill|flood|font|glyph[^R]|horiz|image|letter|lighting|marker[^WUH]|overline|panose|pointe|paint|rendering|shape|stop|strikethrough|stroke|text[^L]|transform|underline|unicode|units|^v[^i]|^w|^xH/; // Boolean DOM properties that translate to enumerated ('true'/'false') attributes
const HTML_ENUMERATED = new Set(['draggable', 'spellcheck']); // DOM properties that should NOT have "px" added when numeric
const ENCODED_ENTITIES = /["&<]/;
/** @param {string} str */
function encodeEntities(str) {
// Skip all work for strings with no entities needing encoding:
if (str.length === 0 || ENCODED_ENTITIES.test(str) === false) return str;
let last = 0,
i = 0,
out = '',
ch = ''; // Seek forward in str until the next entity char:
for (; i < str.length; i++) {
switch (str.charCodeAt(i)) {
case 34:
ch = '&quot;';
break;
case 38:
ch = '&amp;';
break;
case 60:
ch = '&lt;';
break;
default:
continue;
} // Append skipped/buffered characters and the encoded entity:
if (i !== last) out = out + str.slice(last, i);
out = out + ch; // Start the next seek/buffer after the entity's offset:
last = i + 1;
}
if (i !== last) out = out + str.slice(last, i);
return out;
}
const JS_TO_CSS = {};
const IS_NON_DIMENSIONAL = new Set(['animation-iteration-count', 'border-image-outset', 'border-image-slice', 'border-image-width', 'box-flex', 'box-flex-group', 'box-ordinal-group', 'column-count', 'fill-opacity', 'flex', 'flex-grow', 'flex-negative', 'flex-order', 'flex-positive', 'flex-shrink', 'flood-opacity', 'font-weight', 'grid-column', 'grid-row', 'line-clamp', 'line-height', 'opacity', 'order', 'orphans', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'tab-size', 'widows', 'z-index', 'zoom']);
const CSS_REGEX = /[A-Z]/g; // Convert an Object style to a CSSText string
function styleObjToCss(s) {
let str = '';
for (let prop in s) {
let val = s[prop];
if (val != null && val !== '') {
const name = prop[0] == '-' ? prop : JS_TO_CSS[prop] || (JS_TO_CSS[prop] = prop.replace(CSS_REGEX, '-$&').toLowerCase());
let suffix = ';';
if (typeof val === 'number' && // Exclude custom-attributes
!name.startsWith('--') && !IS_NON_DIMENSIONAL.has(name)) {
suffix = 'px;';
}
str = str + name + ':' + val + suffix;
}
}
return str || undefined;
}
function markAsDirty() {
this.__d = true;
}
function createComponent(vnode, context) {
return {
__v: vnode,
context,
props: vnode.props,
// silently drop state updates
setState: markAsDirty,
forceUpdate: markAsDirty,
__d: true,
// hooks
__h: new Array(0)
};
} // Necessary for createContext api. Setting this property will pass
/**
* @template T
*/
class Deferred {
constructor() {
// eslint-disable-next-line lines-around-comment
/** @type {Promise<T>} */
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
// Options hooks
const DIFF = '__b';
const RENDER = '__r';
const DIFFED = 'diffed';
const COMMIT = '__c';
const SKIP_EFFECTS = '__s';
const CATCH_ERROR = '__e'; // VNode properties
const COMPONENT = '__c';
const CHILDREN = '__k';
const PARENT = '__';
const VNODE = '__v';
const DIRTY = '__d';
const NEXT_STATE = '__s';
const CHILD_DID_SUSPEND = '__c';
const EMPTY_OBJ = {};
const EMPTY_ARR = [];
const isArray = Array.isArray;
const assign = Object.assign;
const EMPTY_STR = ''; // Global state for the current render pass
let beforeDiff, afterDiff, renderHook, ummountHook;
/**
* Render Preact JSX + Components to an HTML string.
* @param {VNode} vnode JSX Element / VNode to render
* @param {Object} [context={}] Initial root context object
* @param {RendererState} [_rendererState] for internal use
* @returns {string} serialized HTML
*/
function renderToString(vnode, context, _rendererState) {
// Performance optimization: `renderToString` is synchronous and we
// therefore don't execute any effects. To do that we pass an empty
// array to `options._commit` (`__c`). But we can go one step further
// and avoid a lot of dirty checks and allocations by setting
// `options._skipEffects` (`__s`) too.
const previousSkipEffects = options[SKIP_EFFECTS];
options[SKIP_EFFECTS] = true; // store options hooks once before each synchronous render call
beforeDiff = options[DIFF];
afterDiff = options[DIFFED];
renderHook = options[RENDER];
ummountHook = options.unmount;
const parent = h(Fragment, null);
parent[CHILDREN] = [vnode];
try {
const rendered = _renderToString(vnode, context || EMPTY_OBJ, false, undefined, parent, false, _rendererState);
if (isArray(rendered)) {
return rendered.join(EMPTY_STR);
}
return rendered;
} catch (e) {
if (e.then) {
throw new Error('Use "renderToStringAsync" for suspenseful rendering.');
}
throw e;
} finally {
// options._commit, we don't schedule any effects in this library right now,
// so we can pass an empty queue to this hook.
if (options[COMMIT]) options[COMMIT](vnode, EMPTY_ARR);
options[SKIP_EFFECTS] = previousSkipEffects;
EMPTY_ARR.length = 0;
}
}
/**
* @param {VNode} vnode
* @param {Record<string, unknown>} context
*/
function renderClassComponent(vnode, context) {
let type =
/** @type {import("preact").ComponentClass<typeof vnode.props>} */
vnode.type;
let isMounting = true;
let c;
if (vnode[COMPONENT]) {
isMounting = false;
c = vnode[COMPONENT];
c.state = c[NEXT_STATE];
} else {
c = new type(vnode.props, context);
}
vnode[COMPONENT] = c;
c[VNODE] = vnode;
c.props = vnode.props;
c.context = context; // turn off stateful re-rendering:
c[DIRTY] = true;
if (c.state == null) c.state = EMPTY_OBJ;
if (c[NEXT_STATE] == null) {
c[NEXT_STATE] = c.state;
}
if (type.getDerivedStateFromProps) {
c.state = assign({}, c.state, type.getDerivedStateFromProps(c.props, c.state));
} else if (isMounting && c.componentWillMount) {
c.componentWillMount(); // If the user called setState in cWM we need to flush pending,
// state updates. This is the same behaviour in React.
c.state = c[NEXT_STATE] !== c.state ? c[NEXT_STATE] : c.state;
} else if (!isMounting && c.componentWillUpdate) {
c.componentWillUpdate();
}
if (renderHook) renderHook(vnode);
return c.render(c.props, c.state, context);
}
/**
* Recursively render VNodes to HTML.
* @param {VNode|any} vnode
* @param {any} context
* @param {boolean} isSvgMode
* @param {any} selectValue
* @param {VNode} parent
* @param {boolean} asyncMode
* @param {RendererState | undefined} [renderer]
* @returns {string | Promise<string> | (string | Promise<string>)[]}
*/
function _renderToString(vnode, context, isSvgMode, selectValue, parent, asyncMode, renderer) {
// Ignore non-rendered VNodes/values
if (vnode == null || vnode === true || vnode === false || vnode === EMPTY_STR) {
return EMPTY_STR;
}
let vnodeType = typeof vnode; // Text VNodes: escape as HTML
if (vnodeType != 'object') {
if (vnodeType == 'function') return EMPTY_STR;
return vnodeType == 'string' ? encodeEntities(vnode) : vnode + EMPTY_STR;
} // Recurse into children / Arrays
if (isArray(vnode)) {
let rendered = EMPTY_STR,
renderArray;
parent[CHILDREN] = vnode;
const vnodeLength = vnode.length;
for (let i = 0; i < vnodeLength; i++) {
let child = vnode[i];
if (child == null || typeof child == 'boolean') continue;
const childRender = _renderToString(child, context, isSvgMode, selectValue, parent, asyncMode, renderer);
if (typeof childRender == 'string') {
rendered = rendered + childRender;
} else {
if (!renderArray) {
renderArray = new Array(vnodeLength);
}
if (rendered) renderArray.push(rendered);
rendered = EMPTY_STR;
if (isArray(childRender)) {
renderArray.push(...childRender);
} else {
renderArray.push(childRender);
}
}
}
if (renderArray) {
if (rendered) renderArray.push(rendered);
return renderArray;
}
return rendered;
} // VNodes have {constructor:undefined} to prevent JSON injection:
if (vnode.constructor !== undefined) return EMPTY_STR;
vnode[PARENT] = parent;
if (beforeDiff) beforeDiff(vnode);
let type = vnode.type,
props = vnode.props; // Invoke rendering on Components
if (typeof type == 'function') {
let cctx = context,
contextType,
rendered,
component;
if (type === Fragment) {
// Serialized precompiled JSX.
if ('tpl' in props) {
let out = EMPTY_STR;
for (let i = 0; i < props.tpl.length; i++) {
out = out + props.tpl[i];
if (props.exprs && i < props.exprs.length) {
const value = props.exprs[i];
if (value == null) continue; // Check if we're dealing with a vnode or an array of nodes
if (typeof value == 'object' && (value.constructor === undefined || isArray(value))) {
out = out + _renderToString(value, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
} else {
// Values are pre-escaped by the JSX transform
out = out + value;
}
}
}
return out;
} else if ('UNSTABLE_comment' in props) {
// Fragments are the least used components of core that's why
// branching here for comments has the least effect on perf.
return '<!--' + encodeEntities(props.UNSTABLE_comment) + '-->';
}
rendered = props.children;
} else {
contextType = type.contextType;
if (contextType != null) {
let provider = context[contextType.__c];
cctx = provider ? provider.props.value : contextType.__;
}
let isClassComponent = type.prototype && typeof type.prototype.render == 'function';
if (isClassComponent) {
rendered =
/**#__NOINLINE__**/
renderClassComponent(vnode, cctx);
component = vnode[COMPONENT];
} else {
vnode[COMPONENT] = component =
/**#__NOINLINE__**/
createComponent(vnode, cctx); // If a hook invokes setState() to invalidate the component during rendering,
// re-render it up to 25 times to allow "settling" of memoized states.
// Note:
// This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
// https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
let count = 0;
while (component[DIRTY] && count++ < 25) {
component[DIRTY] = false;
if (renderHook) renderHook(vnode);
rendered = type.call(component, props, cctx);
}
component[DIRTY] = true;
}
if (component.getChildContext != null) {
context = assign({}, context, component.getChildContext());
}
if (isClassComponent && options.errorBoundaries && (type.getDerivedStateFromError || component.componentDidCatch)) {
// When a component returns a Fragment node we flatten it in core, so we
// need to mirror that logic here too
let isTopLevelFragment = rendered != null && rendered.type === Fragment && rendered.key == null && rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
try {
return _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
} catch (err) {
if (type.getDerivedStateFromError) {
component[NEXT_STATE] = type.getDerivedStateFromError(err);
}
if (component.componentDidCatch) {
component.componentDidCatch(err, EMPTY_OBJ);
}
if (component[DIRTY]) {
rendered = renderClassComponent(vnode, context);
component = vnode[COMPONENT];
if (component.getChildContext != null) {
context = assign({}, context, component.getChildContext());
}
let isTopLevelFragment = rendered != null && rendered.type === Fragment && rendered.key == null && rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
return _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
}
return EMPTY_STR;
} finally {
if (afterDiff) afterDiff(vnode);
if (ummountHook) ummountHook(vnode);
}
}
} // When a component returns a Fragment node we flatten it in core, so we
// need to mirror that logic here too
let isTopLevelFragment = rendered != null && rendered.type === Fragment && rendered.key == null && rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
try {
// Recurse into children before invoking the after-diff hook
const str = _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
if (afterDiff) afterDiff(vnode); // when we are dealing with suspense we can't do this...
if (options.unmount) options.unmount(vnode);
return str;
} catch (error) {
if (!asyncMode && renderer && renderer.onError) {
let res = renderer.onError(error, vnode, (child, parent) => _renderToString(child, context, isSvgMode, selectValue, parent, asyncMode, renderer));
if (res !== undefined) return res;
let errorHook = options[CATCH_ERROR];
if (errorHook) errorHook(error, vnode);
return EMPTY_STR;
}
if (!asyncMode) throw error;
if (!error || typeof error.then != 'function') throw error;
const renderNestedChildren = () => {
try {
return _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
} catch (e) {
if (!e || typeof e.then != 'function') throw e;
return e.then(() => _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer), renderNestedChildren);
}
};
return error.then(renderNestedChildren);
}
} // Serialize Element VNodes to HTML
let s = '<' + type,
html = EMPTY_STR,
children;
for (let name in props) {
let v = props[name];
if (typeof v == 'function' && name !== 'class' && name !== 'className') {
continue;
}
switch (name) {
case 'children':
children = v;
continue;
// VDOM-specific props
case 'key':
case 'ref':
case '__self':
case '__source':
continue;
// prefer for/class over htmlFor/className
case 'htmlFor':
if ('for' in props) continue;
name = 'for';
break;
case 'className':
if ('class' in props) continue;
name = 'class';
break;
// Form element reflected properties
case 'defaultChecked':
name = 'checked';
break;
case 'defaultSelected':
name = 'selected';
break;
// Special value attribute handling
case 'defaultValue':
case 'value':
name = 'value';
switch (type) {
// <textarea value="a&b"> --> <textarea>a&amp;b</textarea>
case 'textarea':
children = v;
continue;
// <select value> is serialized as a selected attribute on the matching option child
case 'select':
selectValue = v;
continue;
// Add a selected attribute to <option> if its value matches the parent <select> value
case 'option':
if (selectValue == v && !('selected' in props)) {
s = s + ' selected';
}
break;
}
break;
case 'dangerouslySetInnerHTML':
html = v && v.__html;
continue;
// serialize object styles to a CSS string
case 'style':
if (typeof v === 'object') {
v = styleObjToCss(v);
}
break;
case 'acceptCharset':
name = 'accept-charset';
break;
case 'httpEquiv':
name = 'http-equiv';
break;
default:
{
if (NAMESPACE_REPLACE_REGEX.test(name)) {
name = name.replace(NAMESPACE_REPLACE_REGEX, '$1:$2').toLowerCase();
} else if (UNSAFE_NAME.test(name)) {
continue;
} else if ((name[4] === '-' || HTML_ENUMERATED.has(name)) && v != null) {
// serialize boolean aria-xyz or enumerated attribute values as strings
v = v + EMPTY_STR;
} else if (isSvgMode) {
if (SVG_CAMEL_CASE.test(name)) {
name = name === 'panose1' ? 'panose-1' : name.replace(/([A-Z])/g, '-$1').toLowerCase();
}
} else if (HTML_LOWER_CASE.test(name)) {
name = name.toLowerCase();
}
}
} // write this attribute to the buffer
if (v != null && v !== false) {
if (v === true || v === EMPTY_STR) {
s = s + ' ' + name;
} else {
s = s + ' ' + name + '="' + (typeof v == 'string' ? encodeEntities(v) : v + EMPTY_STR) + '"';
}
}
}
if (UNSAFE_NAME.test(type)) {
// this seems to performs a lot better than throwing
// return '<!-- -->';
throw new Error(`${type} is not a valid HTML tag name in ${s}>`);
}
if (html) ; else if (typeof children === 'string') {
// single text child
html = encodeEntities(children);
} else if (children != null && children !== false && children !== true) {
// recurse into this element VNode's children
let childSvgMode = type === 'svg' || type !== 'foreignObject' && isSvgMode;
html = _renderToString(children, context, childSvgMode, selectValue, vnode, asyncMode, renderer);
}
if (afterDiff) afterDiff(vnode);
if (ummountHook) ummountHook(vnode); // Emit self-closing tag for empty void elements:
if (!html && SELF_CLOSING.has(type)) {
return s + '/>';
}
const endTag = '</' + type + '>';
const startTag = s + '>';
if (isArray(html)) return [startTag, ...html, endTag];else if (typeof html != 'string') return [startTag, html, endTag];
return startTag + html + endTag;
}
const SELF_CLOSING = new Set(['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
/* eslint-disable no-var, key-spacing, object-curly-spacing, prefer-arrow-callback, semi, keyword-spacing */
// function initPreactIslandElement() {
// class PreactIslandElement extends HTMLElement {
// connectedCallback() {
// var d = this;
// if (!d.isConnected) return;
// let i = this.getAttribute('data-target');
// if (!i) return;
// var s,
// e,
// c = document.createNodeIterator(document, 128);
// while (c.nextNode()) {
// let n = c.referenceNode;
// if (n.data == 'preact-island:' + i) s = n;
// else if (n.data == '/preact-island:' + i) e = n;
// if (s && e) break;
// }
// if (s && e) {
// requestAnimationFrame(() => {
// var p = e.previousSibling;
// while (p != s) {
// if (!p || p == s) break;
// e.parentNode.removeChild(p);
// p = e.previousSibling;
// }
// c = s;
// while (d.firstChild) {
// s = d.firstChild;
// d.removeChild(s);
// c.after(s);
// c = s;
// }
// d.parentNode.removeChild(d);
// });
// }
// }
// }
// customElements.define('preact-island', PreactIslandElement);
// }
// To modify the INIT_SCRIPT, uncomment the above code, modify it, and paste it into https://try.terser.org/.
const INIT_SCRIPT = `class e extends HTMLElement{connectedCallback(){var e=this;if(!e.isConnected)return;let t=this.getAttribute("data-target");if(t){for(var r,a,i=document.createNodeIterator(document,128);i.nextNode();){let e=i.referenceNode;if(e.data=="preact-island:"+t?r=e:e.data=="/preact-island:"+t&&(a=e),r&&a)break}r&&a&&requestAnimationFrame((()=>{for(var t=a.previousSibling;t!=r&&t&&t!=r;)a.parentNode.removeChild(t),t=a.previousSibling;for(i=r;e.firstChild;)r=e.firstChild,e.removeChild(r),i.after(r),i=r;e.parentNode.removeChild(e)}))}}}customElements.define("preact-island",e);`;
function createInitScript() {
return `<script>(function(){${INIT_SCRIPT}}())</script>`;
}
/**
* @param {string} id
* @param {string} content
* @returns {string}
*/
function createSubtree(id, content) {
return `<preact-island hidden data-target="${id}">${content}</preact-island>`;
}
/**
* @param {VNode} vnode
* @param {RenderToChunksOptions} options
* @returns {Promise<void>}
*/
async function renderToChunks(vnode, {
context,
onWrite,
abortSignal
}) {
context = context || {};
/** @type {RendererState} */
const renderer = {
start: Date.now(),
abortSignal,
onWrite,
onError: handleError,
suspended: []
}; // Synchronously render the shell
// @ts-ignore - using third internal RendererState argument
const shell = renderToString(vnode, context, renderer);
onWrite(shell); // Wait for any suspended sub-trees if there are any
const len = renderer.suspended.length;
if (len > 0) {
onWrite('<div hidden>');
onWrite(createInitScript()); // We should keep checking all promises
await forkPromises(renderer);
onWrite('</div>');
}
}
async function forkPromises(renderer) {
if (renderer.suspended.length > 0) {
const suspensions = [...renderer.suspended];
await Promise.all(renderer.suspended.map(s => s.promise));
renderer.suspended = renderer.suspended.filter(s => !suspensions.includes(s));
await forkPromises(renderer);
}
}
/** @type {RendererErrorHandler} */
function handleError(error, vnode, renderChild) {
if (!error || !error.then) return; // walk up to the Suspense boundary
while (vnode = vnode[PARENT]) {
let component = vnode[COMPONENT];
if (component && component[CHILD_DID_SUSPEND]) {
break;
}
}
if (!vnode) return;
const id = vnode.__v;
const found = this.suspended.find(x => x.id === id);
const race = new Deferred();
const abortSignal = this.abortSignal;
if (abortSignal) {
// @ts-ignore 2554 - implicit undefined arg
if (abortSignal.aborted) race.resolve();else abortSignal.addEventListener('abort', race.resolve);
}
const promise = error.then(() => {
if (abortSignal && abortSignal.aborted) return;
const child = renderChild(vnode.props.children, vnode);
if (child) this.onWrite(createSubtree(id, child));
}, // TODO: Abort and send hydration code snippet to client
// to attempt to recover during hydration
this.onError);
this.suspended.push({
id,
vnode,
promise: Promise.race([promise, race.promise])
});
const fallback = renderChild(vnode.props.fallback);
return found ? '' : `<!--preact-island:${id}-->${fallback}<!--/preact-island:${id}-->`;
}
/**
* @typedef {object} RenderToPipeableStreamOptions
* @property {() => void} [onShellReady]
* @property {() => void} [onAllReady]
* @property {(error) => void} [onError]
*/
/**
* @typedef {object} PipeableStream
* @property {() => void} abort
* @property {(writable: import('stream').Writable) => void} pipe
*/
/**
* @param {import('preact').VNode} vnode
* @param {RenderToPipeableStreamOptions} options
* @param {any} [context]
* @returns {PipeableStream}
*/
function renderToPipeableStream(vnode, options, context) {
const encoder = new TextEncoder('utf-8');
const controller = new AbortController();
const stream = new PassThrough();
renderToChunks(vnode, {
context,
abortSignal: controller.signal,
onError: error => {
if (options.onError) {
options.onError(error);
}
controller.abort(error);
},
onWrite(s) {
stream.write(encoder.encode(s));
}
}).then(() => {
options.onAllReady && options.onAllReady();
stream.end();
}).catch(error => {
stream.destroy();
if (options.onError) {
options.onError(error);
} else {
throw error;
}
});
Promise.resolve().then(() => {
options.onShellReady && options.onShellReady();
});
return {
/**
* @param {unknown} [reason]
*/
abort(reason = new Error('The render was aborted by the server without a reason.')) {
controller.abort();
stream.destroy();
if (options.onError) {
options.onError(reason);
}
},
/**
* @param {import("stream").Writable} writable
*/
pipe(writable) {
stream.pipe(writable, {
end: true
});
}
};
}
export { renderToPipeableStream };
//# sourceMappingURL=index.module.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,822 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('node:stream'), require('preact')) :
typeof define === 'function' && define.amd ? define(['exports', 'node:stream', 'preact'], factory) :
(global = global || self, factory(global.preactRenderToString = {}, global.node_stream, global.preact));
}(this, (function (exports, node_stream, preact) {
const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
const NAMESPACE_REPLACE_REGEX = /^(xlink|xmlns|xml)([A-Z])/;
const HTML_LOWER_CASE = /^(?:accessK|auto[A-Z]|cell|ch|col|cont|cross|dateT|encT|form[A-Z]|frame|hrefL|inputM|maxL|minL|noV|playsI|popoverT|readO|rowS|src[A-Z]|tabI|useM|item[A-Z])/;
const SVG_CAMEL_CASE = /^ac|^ali|arabic|basel|cap|clipPath$|clipRule$|color|dominant|enable|fill|flood|font|glyph[^R]|horiz|image|letter|lighting|marker[^WUH]|overline|panose|pointe|paint|rendering|shape|stop|strikethrough|stroke|text[^L]|transform|underline|unicode|units|^v[^i]|^w|^xH/; // Boolean DOM properties that translate to enumerated ('true'/'false') attributes
const HTML_ENUMERATED = new Set(['draggable', 'spellcheck']); // DOM properties that should NOT have "px" added when numeric
const ENCODED_ENTITIES = /["&<]/;
/** @param {string} str */
function encodeEntities(str) {
// Skip all work for strings with no entities needing encoding:
if (str.length === 0 || ENCODED_ENTITIES.test(str) === false) return str;
let last = 0,
i = 0,
out = '',
ch = ''; // Seek forward in str until the next entity char:
for (; i < str.length; i++) {
switch (str.charCodeAt(i)) {
case 34:
ch = '&quot;';
break;
case 38:
ch = '&amp;';
break;
case 60:
ch = '&lt;';
break;
default:
continue;
} // Append skipped/buffered characters and the encoded entity:
if (i !== last) out = out + str.slice(last, i);
out = out + ch; // Start the next seek/buffer after the entity's offset:
last = i + 1;
}
if (i !== last) out = out + str.slice(last, i);
return out;
}
const JS_TO_CSS = {};
const IS_NON_DIMENSIONAL = new Set(['animation-iteration-count', 'border-image-outset', 'border-image-slice', 'border-image-width', 'box-flex', 'box-flex-group', 'box-ordinal-group', 'column-count', 'fill-opacity', 'flex', 'flex-grow', 'flex-negative', 'flex-order', 'flex-positive', 'flex-shrink', 'flood-opacity', 'font-weight', 'grid-column', 'grid-row', 'line-clamp', 'line-height', 'opacity', 'order', 'orphans', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'tab-size', 'widows', 'z-index', 'zoom']);
const CSS_REGEX = /[A-Z]/g; // Convert an Object style to a CSSText string
function styleObjToCss(s) {
let str = '';
for (let prop in s) {
let val = s[prop];
if (val != null && val !== '') {
const name = prop[0] == '-' ? prop : JS_TO_CSS[prop] || (JS_TO_CSS[prop] = prop.replace(CSS_REGEX, '-$&').toLowerCase());
let suffix = ';';
if (typeof val === 'number' && // Exclude custom-attributes
!name.startsWith('--') && !IS_NON_DIMENSIONAL.has(name)) {
suffix = 'px;';
}
str = str + name + ':' + val + suffix;
}
}
return str || undefined;
}
function markAsDirty() {
this.__d = true;
}
function createComponent(vnode, context) {
return {
__v: vnode,
context,
props: vnode.props,
// silently drop state updates
setState: markAsDirty,
forceUpdate: markAsDirty,
__d: true,
// hooks
__h: new Array(0)
};
} // Necessary for createContext api. Setting this property will pass
/**
* @template T
*/
class Deferred {
constructor() {
// eslint-disable-next-line lines-around-comment
/** @type {Promise<T>} */
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
// Options hooks
const DIFF = '__b';
const RENDER = '__r';
const DIFFED = 'diffed';
const COMMIT = '__c';
const SKIP_EFFECTS = '__s';
const CATCH_ERROR = '__e'; // VNode properties
const COMPONENT = '__c';
const CHILDREN = '__k';
const PARENT = '__';
const VNODE = '__v';
const DIRTY = '__d';
const NEXT_STATE = '__s';
const CHILD_DID_SUSPEND = '__c';
const EMPTY_OBJ = {};
const EMPTY_ARR = [];
const isArray = Array.isArray;
const assign = Object.assign;
const EMPTY_STR = ''; // Global state for the current render pass
let beforeDiff, afterDiff, renderHook, ummountHook;
/**
* Render Preact JSX + Components to an HTML string.
* @param {VNode} vnode JSX Element / VNode to render
* @param {Object} [context={}] Initial root context object
* @param {RendererState} [_rendererState] for internal use
* @returns {string} serialized HTML
*/
function renderToString(vnode, context, _rendererState) {
// Performance optimization: `renderToString` is synchronous and we
// therefore don't execute any effects. To do that we pass an empty
// array to `options._commit` (`__c`). But we can go one step further
// and avoid a lot of dirty checks and allocations by setting
// `options._skipEffects` (`__s`) too.
const previousSkipEffects = preact.options[SKIP_EFFECTS];
preact.options[SKIP_EFFECTS] = true; // store options hooks once before each synchronous render call
beforeDiff = preact.options[DIFF];
afterDiff = preact.options[DIFFED];
renderHook = preact.options[RENDER];
ummountHook = preact.options.unmount;
const parent = preact.h(preact.Fragment, null);
parent[CHILDREN] = [vnode];
try {
const rendered = _renderToString(vnode, context || EMPTY_OBJ, false, undefined, parent, false, _rendererState);
if (isArray(rendered)) {
return rendered.join(EMPTY_STR);
}
return rendered;
} catch (e) {
if (e.then) {
throw new Error('Use "renderToStringAsync" for suspenseful rendering.');
}
throw e;
} finally {
// options._commit, we don't schedule any effects in this library right now,
// so we can pass an empty queue to this hook.
if (preact.options[COMMIT]) preact.options[COMMIT](vnode, EMPTY_ARR);
preact.options[SKIP_EFFECTS] = previousSkipEffects;
EMPTY_ARR.length = 0;
}
}
/**
* @param {VNode} vnode
* @param {Record<string, unknown>} context
*/
function renderClassComponent(vnode, context) {
let type =
/** @type {import("preact").ComponentClass<typeof vnode.props>} */
vnode.type;
let isMounting = true;
let c;
if (vnode[COMPONENT]) {
isMounting = false;
c = vnode[COMPONENT];
c.state = c[NEXT_STATE];
} else {
c = new type(vnode.props, context);
}
vnode[COMPONENT] = c;
c[VNODE] = vnode;
c.props = vnode.props;
c.context = context; // turn off stateful re-rendering:
c[DIRTY] = true;
if (c.state == null) c.state = EMPTY_OBJ;
if (c[NEXT_STATE] == null) {
c[NEXT_STATE] = c.state;
}
if (type.getDerivedStateFromProps) {
c.state = assign({}, c.state, type.getDerivedStateFromProps(c.props, c.state));
} else if (isMounting && c.componentWillMount) {
c.componentWillMount(); // If the user called setState in cWM we need to flush pending,
// state updates. This is the same behaviour in React.
c.state = c[NEXT_STATE] !== c.state ? c[NEXT_STATE] : c.state;
} else if (!isMounting && c.componentWillUpdate) {
c.componentWillUpdate();
}
if (renderHook) renderHook(vnode);
return c.render(c.props, c.state, context);
}
/**
* Recursively render VNodes to HTML.
* @param {VNode|any} vnode
* @param {any} context
* @param {boolean} isSvgMode
* @param {any} selectValue
* @param {VNode} parent
* @param {boolean} asyncMode
* @param {RendererState | undefined} [renderer]
* @returns {string | Promise<string> | (string | Promise<string>)[]}
*/
function _renderToString(vnode, context, isSvgMode, selectValue, parent, asyncMode, renderer) {
// Ignore non-rendered VNodes/values
if (vnode == null || vnode === true || vnode === false || vnode === EMPTY_STR) {
return EMPTY_STR;
}
let vnodeType = typeof vnode; // Text VNodes: escape as HTML
if (vnodeType != 'object') {
if (vnodeType == 'function') return EMPTY_STR;
return vnodeType == 'string' ? encodeEntities(vnode) : vnode + EMPTY_STR;
} // Recurse into children / Arrays
if (isArray(vnode)) {
let rendered = EMPTY_STR,
renderArray;
parent[CHILDREN] = vnode;
const vnodeLength = vnode.length;
for (let i = 0; i < vnodeLength; i++) {
let child = vnode[i];
if (child == null || typeof child == 'boolean') continue;
const childRender = _renderToString(child, context, isSvgMode, selectValue, parent, asyncMode, renderer);
if (typeof childRender == 'string') {
rendered = rendered + childRender;
} else {
if (!renderArray) {
renderArray = new Array(vnodeLength);
}
if (rendered) renderArray.push(rendered);
rendered = EMPTY_STR;
if (isArray(childRender)) {
renderArray.push(...childRender);
} else {
renderArray.push(childRender);
}
}
}
if (renderArray) {
if (rendered) renderArray.push(rendered);
return renderArray;
}
return rendered;
} // VNodes have {constructor:undefined} to prevent JSON injection:
if (vnode.constructor !== undefined) return EMPTY_STR;
vnode[PARENT] = parent;
if (beforeDiff) beforeDiff(vnode);
let type = vnode.type,
props = vnode.props; // Invoke rendering on Components
if (typeof type == 'function') {
let cctx = context,
contextType,
rendered,
component;
if (type === preact.Fragment) {
// Serialized precompiled JSX.
if ('tpl' in props) {
let out = EMPTY_STR;
for (let i = 0; i < props.tpl.length; i++) {
out = out + props.tpl[i];
if (props.exprs && i < props.exprs.length) {
const value = props.exprs[i];
if (value == null) continue; // Check if we're dealing with a vnode or an array of nodes
if (typeof value == 'object' && (value.constructor === undefined || isArray(value))) {
out = out + _renderToString(value, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
} else {
// Values are pre-escaped by the JSX transform
out = out + value;
}
}
}
return out;
} else if ('UNSTABLE_comment' in props) {
// Fragments are the least used components of core that's why
// branching here for comments has the least effect on perf.
return '<!--' + encodeEntities(props.UNSTABLE_comment) + '-->';
}
rendered = props.children;
} else {
contextType = type.contextType;
if (contextType != null) {
let provider = context[contextType.__c];
cctx = provider ? provider.props.value : contextType.__;
}
let isClassComponent = type.prototype && typeof type.prototype.render == 'function';
if (isClassComponent) {
rendered =
/**#__NOINLINE__**/
renderClassComponent(vnode, cctx);
component = vnode[COMPONENT];
} else {
vnode[COMPONENT] = component =
/**#__NOINLINE__**/
createComponent(vnode, cctx); // If a hook invokes setState() to invalidate the component during rendering,
// re-render it up to 25 times to allow "settling" of memoized states.
// Note:
// This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
// https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
let count = 0;
while (component[DIRTY] && count++ < 25) {
component[DIRTY] = false;
if (renderHook) renderHook(vnode);
rendered = type.call(component, props, cctx);
}
component[DIRTY] = true;
}
if (component.getChildContext != null) {
context = assign({}, context, component.getChildContext());
}
if (isClassComponent && preact.options.errorBoundaries && (type.getDerivedStateFromError || component.componentDidCatch)) {
// When a component returns a Fragment node we flatten it in core, so we
// need to mirror that logic here too
let isTopLevelFragment = rendered != null && rendered.type === preact.Fragment && rendered.key == null && rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
try {
return _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
} catch (err) {
if (type.getDerivedStateFromError) {
component[NEXT_STATE] = type.getDerivedStateFromError(err);
}
if (component.componentDidCatch) {
component.componentDidCatch(err, EMPTY_OBJ);
}
if (component[DIRTY]) {
rendered = renderClassComponent(vnode, context);
component = vnode[COMPONENT];
if (component.getChildContext != null) {
context = assign({}, context, component.getChildContext());
}
let isTopLevelFragment = rendered != null && rendered.type === preact.Fragment && rendered.key == null && rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
return _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
}
return EMPTY_STR;
} finally {
if (afterDiff) afterDiff(vnode);
if (ummountHook) ummountHook(vnode);
}
}
} // When a component returns a Fragment node we flatten it in core, so we
// need to mirror that logic here too
let isTopLevelFragment = rendered != null && rendered.type === preact.Fragment && rendered.key == null && rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
try {
// Recurse into children before invoking the after-diff hook
const str = _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
if (afterDiff) afterDiff(vnode); // when we are dealing with suspense we can't do this...
if (preact.options.unmount) preact.options.unmount(vnode);
return str;
} catch (error) {
if (!asyncMode && renderer && renderer.onError) {
let res = renderer.onError(error, vnode, (child, parent) => _renderToString(child, context, isSvgMode, selectValue, parent, asyncMode, renderer));
if (res !== undefined) return res;
let errorHook = preact.options[CATCH_ERROR];
if (errorHook) errorHook(error, vnode);
return EMPTY_STR;
}
if (!asyncMode) throw error;
if (!error || typeof error.then != 'function') throw error;
const renderNestedChildren = () => {
try {
return _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer);
} catch (e) {
if (!e || typeof e.then != 'function') throw e;
return e.then(() => _renderToString(rendered, context, isSvgMode, selectValue, vnode, asyncMode, renderer), renderNestedChildren);
}
};
return error.then(renderNestedChildren);
}
} // Serialize Element VNodes to HTML
let s = '<' + type,
html = EMPTY_STR,
children;
for (let name in props) {
let v = props[name];
if (typeof v == 'function' && name !== 'class' && name !== 'className') {
continue;
}
switch (name) {
case 'children':
children = v;
continue;
// VDOM-specific props
case 'key':
case 'ref':
case '__self':
case '__source':
continue;
// prefer for/class over htmlFor/className
case 'htmlFor':
if ('for' in props) continue;
name = 'for';
break;
case 'className':
if ('class' in props) continue;
name = 'class';
break;
// Form element reflected properties
case 'defaultChecked':
name = 'checked';
break;
case 'defaultSelected':
name = 'selected';
break;
// Special value attribute handling
case 'defaultValue':
case 'value':
name = 'value';
switch (type) {
// <textarea value="a&b"> --> <textarea>a&amp;b</textarea>
case 'textarea':
children = v;
continue;
// <select value> is serialized as a selected attribute on the matching option child
case 'select':
selectValue = v;
continue;
// Add a selected attribute to <option> if its value matches the parent <select> value
case 'option':
if (selectValue == v && !('selected' in props)) {
s = s + ' selected';
}
break;
}
break;
case 'dangerouslySetInnerHTML':
html = v && v.__html;
continue;
// serialize object styles to a CSS string
case 'style':
if (typeof v === 'object') {
v = styleObjToCss(v);
}
break;
case 'acceptCharset':
name = 'accept-charset';
break;
case 'httpEquiv':
name = 'http-equiv';
break;
default:
{
if (NAMESPACE_REPLACE_REGEX.test(name)) {
name = name.replace(NAMESPACE_REPLACE_REGEX, '$1:$2').toLowerCase();
} else if (UNSAFE_NAME.test(name)) {
continue;
} else if ((name[4] === '-' || HTML_ENUMERATED.has(name)) && v != null) {
// serialize boolean aria-xyz or enumerated attribute values as strings
v = v + EMPTY_STR;
} else if (isSvgMode) {
if (SVG_CAMEL_CASE.test(name)) {
name = name === 'panose1' ? 'panose-1' : name.replace(/([A-Z])/g, '-$1').toLowerCase();
}
} else if (HTML_LOWER_CASE.test(name)) {
name = name.toLowerCase();
}
}
} // write this attribute to the buffer
if (v != null && v !== false) {
if (v === true || v === EMPTY_STR) {
s = s + ' ' + name;
} else {
s = s + ' ' + name + '="' + (typeof v == 'string' ? encodeEntities(v) : v + EMPTY_STR) + '"';
}
}
}
if (UNSAFE_NAME.test(type)) {
// this seems to performs a lot better than throwing
// return '<!-- -->';
throw new Error(`${type} is not a valid HTML tag name in ${s}>`);
}
if (html) ; else if (typeof children === 'string') {
// single text child
html = encodeEntities(children);
} else if (children != null && children !== false && children !== true) {
// recurse into this element VNode's children
let childSvgMode = type === 'svg' || type !== 'foreignObject' && isSvgMode;
html = _renderToString(children, context, childSvgMode, selectValue, vnode, asyncMode, renderer);
}
if (afterDiff) afterDiff(vnode);
if (ummountHook) ummountHook(vnode); // Emit self-closing tag for empty void elements:
if (!html && SELF_CLOSING.has(type)) {
return s + '/>';
}
const endTag = '</' + type + '>';
const startTag = s + '>';
if (isArray(html)) return [startTag, ...html, endTag];else if (typeof html != 'string') return [startTag, html, endTag];
return startTag + html + endTag;
}
const SELF_CLOSING = new Set(['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
/* eslint-disable no-var, key-spacing, object-curly-spacing, prefer-arrow-callback, semi, keyword-spacing */
// function initPreactIslandElement() {
// class PreactIslandElement extends HTMLElement {
// connectedCallback() {
// var d = this;
// if (!d.isConnected) return;
// let i = this.getAttribute('data-target');
// if (!i) return;
// var s,
// e,
// c = document.createNodeIterator(document, 128);
// while (c.nextNode()) {
// let n = c.referenceNode;
// if (n.data == 'preact-island:' + i) s = n;
// else if (n.data == '/preact-island:' + i) e = n;
// if (s && e) break;
// }
// if (s && e) {
// requestAnimationFrame(() => {
// var p = e.previousSibling;
// while (p != s) {
// if (!p || p == s) break;
// e.parentNode.removeChild(p);
// p = e.previousSibling;
// }
// c = s;
// while (d.firstChild) {
// s = d.firstChild;
// d.removeChild(s);
// c.after(s);
// c = s;
// }
// d.parentNode.removeChild(d);
// });
// }
// }
// }
// customElements.define('preact-island', PreactIslandElement);
// }
// To modify the INIT_SCRIPT, uncomment the above code, modify it, and paste it into https://try.terser.org/.
const INIT_SCRIPT = `class e extends HTMLElement{connectedCallback(){var e=this;if(!e.isConnected)return;let t=this.getAttribute("data-target");if(t){for(var r,a,i=document.createNodeIterator(document,128);i.nextNode();){let e=i.referenceNode;if(e.data=="preact-island:"+t?r=e:e.data=="/preact-island:"+t&&(a=e),r&&a)break}r&&a&&requestAnimationFrame((()=>{for(var t=a.previousSibling;t!=r&&t&&t!=r;)a.parentNode.removeChild(t),t=a.previousSibling;for(i=r;e.firstChild;)r=e.firstChild,e.removeChild(r),i.after(r),i=r;e.parentNode.removeChild(e)}))}}}customElements.define("preact-island",e);`;
function createInitScript() {
return `<script>(function(){${INIT_SCRIPT}}())</script>`;
}
/**
* @param {string} id
* @param {string} content
* @returns {string}
*/
function createSubtree(id, content) {
return `<preact-island hidden data-target="${id}">${content}</preact-island>`;
}
/**
* @param {VNode} vnode
* @param {RenderToChunksOptions} options
* @returns {Promise<void>}
*/
async function renderToChunks(vnode, {
context,
onWrite,
abortSignal
}) {
context = context || {};
/** @type {RendererState} */
const renderer = {
start: Date.now(),
abortSignal,
onWrite,
onError: handleError,
suspended: []
}; // Synchronously render the shell
// @ts-ignore - using third internal RendererState argument
const shell = renderToString(vnode, context, renderer);
onWrite(shell); // Wait for any suspended sub-trees if there are any
const len = renderer.suspended.length;
if (len > 0) {
onWrite('<div hidden>');
onWrite(createInitScript()); // We should keep checking all promises
await forkPromises(renderer);
onWrite('</div>');
}
}
async function forkPromises(renderer) {
if (renderer.suspended.length > 0) {
const suspensions = [...renderer.suspended];
await Promise.all(renderer.suspended.map(s => s.promise));
renderer.suspended = renderer.suspended.filter(s => !suspensions.includes(s));
await forkPromises(renderer);
}
}
/** @type {RendererErrorHandler} */
function handleError(error, vnode, renderChild) {
if (!error || !error.then) return; // walk up to the Suspense boundary
while (vnode = vnode[PARENT]) {
let component = vnode[COMPONENT];
if (component && component[CHILD_DID_SUSPEND]) {
break;
}
}
if (!vnode) return;
const id = vnode.__v;
const found = this.suspended.find(x => x.id === id);
const race = new Deferred();
const abortSignal = this.abortSignal;
if (abortSignal) {
// @ts-ignore 2554 - implicit undefined arg
if (abortSignal.aborted) race.resolve();else abortSignal.addEventListener('abort', race.resolve);
}
const promise = error.then(() => {
if (abortSignal && abortSignal.aborted) return;
const child = renderChild(vnode.props.children, vnode);
if (child) this.onWrite(createSubtree(id, child));
}, // TODO: Abort and send hydration code snippet to client
// to attempt to recover during hydration
this.onError);
this.suspended.push({
id,
vnode,
promise: Promise.race([promise, race.promise])
});
const fallback = renderChild(vnode.props.fallback);
return found ? '' : `<!--preact-island:${id}-->${fallback}<!--/preact-island:${id}-->`;
}
/**
* @typedef {object} RenderToPipeableStreamOptions
* @property {() => void} [onShellReady]
* @property {() => void} [onAllReady]
* @property {(error) => void} [onError]
*/
/**
* @typedef {object} PipeableStream
* @property {() => void} abort
* @property {(writable: import('stream').Writable) => void} pipe
*/
/**
* @param {import('preact').VNode} vnode
* @param {RenderToPipeableStreamOptions} options
* @param {any} [context]
* @returns {PipeableStream}
*/
function renderToPipeableStream(vnode, options, context) {
const encoder = new TextEncoder('utf-8');
const controller = new AbortController();
const stream = new node_stream.PassThrough();
renderToChunks(vnode, {
context,
abortSignal: controller.signal,
onError: error => {
if (options.onError) {
options.onError(error);
}
controller.abort(error);
},
onWrite(s) {
stream.write(encoder.encode(s));
}
}).then(() => {
options.onAllReady && options.onAllReady();
stream.end();
}).catch(error => {
stream.destroy();
if (options.onError) {
options.onError(error);
} else {
throw error;
}
});
Promise.resolve().then(() => {
options.onShellReady && options.onShellReady();
});
return {
/**
* @param {unknown} [reason]
*/
abort(reason = new Error('The render was aborted by the server without a reason.')) {
controller.abort();
stream.destroy();
if (options.onError) {
options.onError(reason);
}
},
/**
* @param {import("stream").Writable} writable
*/
pipe(writable) {
stream.pipe(writable, {
end: true
});
}
};
}
exports.renderToPipeableStream = renderToPipeableStream;
})));
//# sourceMappingURL=index.umd.js.map

File diff suppressed because one or more lines are too long

24
node_modules/preact-render-to-string/jsx.d.ts generated vendored Normal file
View File

@@ -0,0 +1,24 @@
import { VNode } from 'preact';
interface Options {
jsx?: boolean;
xml?: boolean;
pretty?: boolean | string;
shallow?: boolean;
functions?: boolean;
functionNames?: boolean;
skipFalseAttributes?: boolean;
}
export default function renderToStringPretty(
vnode: VNode,
context?: any,
options?: Options
): string;
export function render(vnode: VNode, context?: any, options?: Options): string;
export function shallowRender(
vnode: VNode,
context?: any,
options?: Options
): string;

1
node_modules/preact-render-to-string/jsx.js generated vendored Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./dist/jsx'); // eslint-disable-line

174
node_modules/preact-render-to-string/package.json generated vendored Normal file
View File

@@ -0,0 +1,174 @@
{
"name": "preact-render-to-string",
"amdName": "preactRenderToString",
"version": "6.5.13",
"description": "Render JSX to an HTML string, with support for Preact components.",
"main": "dist/index.js",
"umd:main": "dist/index.umd.js",
"module": "dist/index.module.js",
"jsnext:main": "dist/index.module.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"browser": "./dist/index.module.js",
"umd": "./dist/index.umd.js",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./jsx": {
"types": "./dist/jsx.d.ts",
"browser": "./dist/jsx/index.module.js",
"umd": "./dist/jsx/index.umd.js",
"import": "./dist/jsx/index.mjs",
"require": "./dist/jsx/index.js"
},
"./stream": {
"types": "./dist/stream.d.ts",
"browser": "./dist/stream/index.module.js",
"import": "./dist/stream/index.mjs",
"require": "./dist/stream/index.js"
},
"./stream-node": {
"types": "./dist/stream-node.d.ts",
"import": "./dist/stream/node/index.mjs",
"require": "./dist/stream/node/index.js"
},
"./package.json": "./package.json"
},
"scripts": {
"prebench": "npm run build",
"bench": "BABEL_ENV=test node -r @babel/register benchmarks index.js",
"bench:v8": "BABEL_ENV=test microbundle benchmarks/index.js -f modern --alias benchmarkjs-pretty=benchmarks/lib/benchmark-lite.js --external none --target node --no-compress --no-sourcemap --raw -o benchmarks/.v8.mjs && v8 --module benchmarks/.v8.modern.js",
"build": "npm run -s transpile && npm run -s transpile:jsx && npm run -s transpile:stream && npm run -s transpile:stream-node && npm run -s copy-typescript-definition",
"postbuild": "node ./config/node-13-exports.js && node ./config/node-commonjs.js && node ./config/node-verify-exports.js && check-export-map",
"transpile": "microbundle src/index.js -f es,cjs,umd",
"transpile:stream": "microbundle src/stream.js -o dist/stream/index.js -f es,cjs,umd",
"transpile:stream-node": "microbundle src/stream-node.js -o dist/stream/node/index.js -f es,cjs,umd --target node",
"transpile:jsx": "microbundle src/jsx.js -o dist/jsx/index.js -f es,cjs,umd && microbundle dist/jsx/index.js -o dist/jsx/index.js -f cjs",
"copy-typescript-definition": "copyfiles -f src/*.d.ts dist",
"test": "eslint src test && tsc && npm run test:mocha && npm run test:mocha:compat && npm run test:mocha:debug && npm run bench",
"test:mocha": "BABEL_ENV=test mocha -r @babel/register -r test/setup.js test/*.test.jsx",
"test:mocha:compat": "BABEL_ENV=test mocha -r @babel/register -r test/setup.js 'test/compat/*.test.js' 'test/compat/*.test.jsx'",
"test:mocha:debug": "BABEL_ENV=test mocha -r @babel/register -r test/setup.js 'test/debug/*.test.jsx'",
"format": "prettier src/**/*.{d.ts,js} test/**/*.js --write",
"prepublishOnly": "npm run build",
"release": "npm run build && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish"
},
"keywords": [
"preact",
"render",
"universal",
"isomorphic"
],
"files": [
"src",
"dist",
"jsx.js",
"jsx.d.ts",
"typings.json"
],
"eslintConfig": {
"extends": "developit",
"rules": {
"react/prefer-stateless-function": 0,
"react/jsx-no-bind": 0,
"react/no-danger": 0,
"jest/valid-expect": 0,
"new-cap": 0,
"curly": "off",
"brace-style": "off",
"indent": "off",
"lines-around-comment": "off"
},
"settings": {
"react": {
"version": "16.8"
}
}
},
"babel": {
"env": {
"test": {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": true
}
}
]
],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "h"
}
]
]
}
}
},
"minify": {
"compress": {
"reduce_funcs": false
}
},
"author": "The Preact Authors (https://github.com/preactjs/preact/contributors)",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/preactjs/preact-render-to-string"
},
"bugs": "https://github.com/preactjs/preact-render-to-string/issues",
"homepage": "https://github.com/preactjs/preact-render-to-string",
"peerDependencies": {
"preact": ">=10"
},
"devDependencies": {
"@babel/plugin-transform-react-jsx": "^7.12.12",
"@babel/preset-env": "^7.12.11",
"@babel/register": "^7.12.10",
"@changesets/changelog-github": "^0.4.1",
"@changesets/cli": "^2.18.0",
"baseline-rts": "npm:preact-render-to-string@latest",
"benchmarkjs-pretty": "^2.0.1",
"chai": "^4.2.0",
"check-export-map": "^1.3.1",
"copyfiles": "^2.4.1",
"eslint": "^7.16.0",
"eslint-config-developit": "^1.2.0",
"husky": "^4.3.6",
"lint-staged": "^10.5.3",
"microbundle": "^0.15.1",
"mocha": "^8.2.1",
"preact": "^10.24.0",
"prettier": "^2.2.1",
"pretty-format": "^3.8.0",
"sinon": "^9.2.2",
"sinon-chai": "^3.5.0",
"typescript": "^5.0.0",
"web-streams-polyfill": "^3.2.1"
},
"prettier": {
"singleQuote": true,
"trailingComma": "none",
"useTabs": true,
"tabWidth": 2
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx,yml}": [
"prettier --write"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"publishConfig": {
"provenance": true
}
}

17
node_modules/preact-render-to-string/src/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,17 @@
import { VNode } from 'preact';
export default function renderToString<P = {}>(
vnode: VNode<P>,
context?: any
): string;
export function render<P = {}>(vnode: VNode<P>, context?: any): string;
export function renderToString<P = {}>(vnode: VNode<P>, context?: any): string;
export function renderToStringAsync<P = {}>(
vnode: VNode<P>,
context?: any
): string | Promise<string>;
export function renderToStaticMarkup<P = {}>(
vnode: VNode<P>,
context?: any
): string;

725
node_modules/preact-render-to-string/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,725 @@
import {
encodeEntities,
styleObjToCss,
UNSAFE_NAME,
NAMESPACE_REPLACE_REGEX,
HTML_LOWER_CASE,
HTML_ENUMERATED,
SVG_CAMEL_CASE,
createComponent
} from './lib/util.js';
import { options, h, Fragment } from 'preact';
import {
CHILDREN,
COMMIT,
COMPONENT,
DIFF,
DIFFED,
DIRTY,
NEXT_STATE,
PARENT,
RENDER,
SKIP_EFFECTS,
VNODE,
CATCH_ERROR
} from './lib/constants.js';
const EMPTY_OBJ = {};
const EMPTY_ARR = [];
const isArray = Array.isArray;
const assign = Object.assign;
const EMPTY_STR = '';
// Global state for the current render pass
let beforeDiff, afterDiff, renderHook, ummountHook;
/**
* Render Preact JSX + Components to an HTML string.
* @param {VNode} vnode JSX Element / VNode to render
* @param {Object} [context={}] Initial root context object
* @param {RendererState} [_rendererState] for internal use
* @returns {string} serialized HTML
*/
export function renderToString(vnode, context, _rendererState) {
// Performance optimization: `renderToString` is synchronous and we
// therefore don't execute any effects. To do that we pass an empty
// array to `options._commit` (`__c`). But we can go one step further
// and avoid a lot of dirty checks and allocations by setting
// `options._skipEffects` (`__s`) too.
const previousSkipEffects = options[SKIP_EFFECTS];
options[SKIP_EFFECTS] = true;
// store options hooks once before each synchronous render call
beforeDiff = options[DIFF];
afterDiff = options[DIFFED];
renderHook = options[RENDER];
ummountHook = options.unmount;
const parent = h(Fragment, null);
parent[CHILDREN] = [vnode];
try {
const rendered = _renderToString(
vnode,
context || EMPTY_OBJ,
false,
undefined,
parent,
false,
_rendererState
);
if (isArray(rendered)) {
return rendered.join(EMPTY_STR);
}
return rendered;
} catch (e) {
if (e.then) {
throw new Error('Use "renderToStringAsync" for suspenseful rendering.');
}
throw e;
} finally {
// options._commit, we don't schedule any effects in this library right now,
// so we can pass an empty queue to this hook.
if (options[COMMIT]) options[COMMIT](vnode, EMPTY_ARR);
options[SKIP_EFFECTS] = previousSkipEffects;
EMPTY_ARR.length = 0;
}
}
/**
* Render Preact JSX + Components to an HTML string.
* @param {VNode} vnode JSX Element / VNode to render
* @param {Object} [context={}] Initial root context object
* @returns {string} serialized HTML
*/
export async function renderToStringAsync(vnode, context) {
// Performance optimization: `renderToString` is synchronous and we
// therefore don't execute any effects. To do that we pass an empty
// array to `options._commit` (`__c`). But we can go one step further
// and avoid a lot of dirty checks and allocations by setting
// `options._skipEffects` (`__s`) too.
const previousSkipEffects = options[SKIP_EFFECTS];
options[SKIP_EFFECTS] = true;
// store options hooks once before each synchronous render call
beforeDiff = options[DIFF];
afterDiff = options[DIFFED];
renderHook = options[RENDER];
ummountHook = options.unmount;
const parent = h(Fragment, null);
parent[CHILDREN] = [vnode];
try {
const rendered = await _renderToString(
vnode,
context || EMPTY_OBJ,
false,
undefined,
parent,
true,
undefined
);
if (isArray(rendered)) {
let count = 0;
let resolved = rendered;
// Resolving nested Promises with a maximum depth of 25
while (
resolved.some(
(element) => element && typeof element.then === 'function'
) &&
count++ < 25
) {
resolved = (await Promise.all(resolved)).flat();
}
return resolved.join(EMPTY_STR);
}
return rendered;
} finally {
// options._commit, we don't schedule any effects in this library right now,
// so we can pass an empty queue to this hook.
if (options[COMMIT]) options[COMMIT](vnode, EMPTY_ARR);
options[SKIP_EFFECTS] = previousSkipEffects;
EMPTY_ARR.length = 0;
}
}
/**
* @param {VNode} vnode
* @param {Record<string, unknown>} context
*/
function renderClassComponent(vnode, context) {
let type = /** @type {import("preact").ComponentClass<typeof vnode.props>} */ (vnode.type);
let isMounting = true;
let c;
if (vnode[COMPONENT]) {
isMounting = false;
c = vnode[COMPONENT];
c.state = c[NEXT_STATE];
} else {
c = new type(vnode.props, context);
}
vnode[COMPONENT] = c;
c[VNODE] = vnode;
c.props = vnode.props;
c.context = context;
// turn off stateful re-rendering:
c[DIRTY] = true;
if (c.state == null) c.state = EMPTY_OBJ;
if (c[NEXT_STATE] == null) {
c[NEXT_STATE] = c.state;
}
if (type.getDerivedStateFromProps) {
c.state = assign(
{},
c.state,
type.getDerivedStateFromProps(c.props, c.state)
);
} else if (isMounting && c.componentWillMount) {
c.componentWillMount();
// If the user called setState in cWM we need to flush pending,
// state updates. This is the same behaviour in React.
c.state = c[NEXT_STATE] !== c.state ? c[NEXT_STATE] : c.state;
} else if (!isMounting && c.componentWillUpdate) {
c.componentWillUpdate();
}
if (renderHook) renderHook(vnode);
return c.render(c.props, c.state, context);
}
/**
* Recursively render VNodes to HTML.
* @param {VNode|any} vnode
* @param {any} context
* @param {boolean} isSvgMode
* @param {any} selectValue
* @param {VNode} parent
* @param {boolean} asyncMode
* @param {RendererState | undefined} [renderer]
* @returns {string | Promise<string> | (string | Promise<string>)[]}
*/
function _renderToString(
vnode,
context,
isSvgMode,
selectValue,
parent,
asyncMode,
renderer
) {
// Ignore non-rendered VNodes/values
if (
vnode == null ||
vnode === true ||
vnode === false ||
vnode === EMPTY_STR
) {
return EMPTY_STR;
}
let vnodeType = typeof vnode;
// Text VNodes: escape as HTML
if (vnodeType != 'object') {
if (vnodeType == 'function') return EMPTY_STR;
return vnodeType == 'string' ? encodeEntities(vnode) : vnode + EMPTY_STR;
}
// Recurse into children / Arrays
if (isArray(vnode)) {
let rendered = EMPTY_STR,
renderArray;
parent[CHILDREN] = vnode;
const vnodeLength = vnode.length;
for (let i = 0; i < vnodeLength; i++) {
let child = vnode[i];
if (child == null || typeof child == 'boolean') continue;
const childRender = _renderToString(
child,
context,
isSvgMode,
selectValue,
parent,
asyncMode,
renderer
);
if (typeof childRender == 'string') {
rendered = rendered + childRender;
} else {
if (!renderArray) {
renderArray = new Array(vnodeLength);
}
if (rendered) renderArray.push(rendered);
rendered = EMPTY_STR;
if (isArray(childRender)) {
renderArray.push(...childRender);
} else {
renderArray.push(childRender);
}
}
}
if (renderArray) {
if (rendered) renderArray.push(rendered);
return renderArray;
}
return rendered;
}
// VNodes have {constructor:undefined} to prevent JSON injection:
if (vnode.constructor !== undefined) return EMPTY_STR;
vnode[PARENT] = parent;
if (beforeDiff) beforeDiff(vnode);
let type = vnode.type,
props = vnode.props;
// Invoke rendering on Components
if (typeof type == 'function') {
let cctx = context,
contextType,
rendered,
component;
if (type === Fragment) {
// Serialized precompiled JSX.
if ('tpl' in props) {
let out = EMPTY_STR;
for (let i = 0; i < props.tpl.length; i++) {
out = out + props.tpl[i];
if (props.exprs && i < props.exprs.length) {
const value = props.exprs[i];
if (value == null) continue;
// Check if we're dealing with a vnode or an array of nodes
if (
typeof value == 'object' &&
(value.constructor === undefined || isArray(value))
) {
out =
out +
_renderToString(
value,
context,
isSvgMode,
selectValue,
vnode,
asyncMode,
renderer
);
} else {
// Values are pre-escaped by the JSX transform
out = out + value;
}
}
}
return out;
} else if ('UNSTABLE_comment' in props) {
// Fragments are the least used components of core that's why
// branching here for comments has the least effect on perf.
return '<!--' + encodeEntities(props.UNSTABLE_comment) + '-->';
}
rendered = props.children;
} else {
contextType = type.contextType;
if (contextType != null) {
let provider = context[contextType.__c];
cctx = provider ? provider.props.value : contextType.__;
}
let isClassComponent =
type.prototype && typeof type.prototype.render == 'function';
if (isClassComponent) {
rendered = /**#__NOINLINE__**/ renderClassComponent(vnode, cctx);
component = vnode[COMPONENT];
} else {
vnode[COMPONENT] = component = /**#__NOINLINE__**/ createComponent(
vnode,
cctx
);
// If a hook invokes setState() to invalidate the component during rendering,
// re-render it up to 25 times to allow "settling" of memoized states.
// Note:
// This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
// https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
let count = 0;
while (component[DIRTY] && count++ < 25) {
component[DIRTY] = false;
if (renderHook) renderHook(vnode);
rendered = type.call(component, props, cctx);
}
component[DIRTY] = true;
}
if (component.getChildContext != null) {
context = assign({}, context, component.getChildContext());
}
if (
isClassComponent &&
options.errorBoundaries &&
(type.getDerivedStateFromError || component.componentDidCatch)
) {
// When a component returns a Fragment node we flatten it in core, so we
// need to mirror that logic here too
let isTopLevelFragment =
rendered != null &&
rendered.type === Fragment &&
rendered.key == null &&
rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
try {
return _renderToString(
rendered,
context,
isSvgMode,
selectValue,
vnode,
asyncMode,
renderer
);
} catch (err) {
if (type.getDerivedStateFromError) {
component[NEXT_STATE] = type.getDerivedStateFromError(err);
}
if (component.componentDidCatch) {
component.componentDidCatch(err, EMPTY_OBJ);
}
if (component[DIRTY]) {
rendered = renderClassComponent(vnode, context);
component = vnode[COMPONENT];
if (component.getChildContext != null) {
context = assign({}, context, component.getChildContext());
}
let isTopLevelFragment =
rendered != null &&
rendered.type === Fragment &&
rendered.key == null &&
rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
return _renderToString(
rendered,
context,
isSvgMode,
selectValue,
vnode,
asyncMode,
renderer
);
}
return EMPTY_STR;
} finally {
if (afterDiff) afterDiff(vnode);
if (ummountHook) ummountHook(vnode);
}
}
}
// When a component returns a Fragment node we flatten it in core, so we
// need to mirror that logic here too
let isTopLevelFragment =
rendered != null &&
rendered.type === Fragment &&
rendered.key == null &&
rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
try {
// Recurse into children before invoking the after-diff hook
const str = _renderToString(
rendered,
context,
isSvgMode,
selectValue,
vnode,
asyncMode,
renderer
);
if (afterDiff) afterDiff(vnode);
// when we are dealing with suspense we can't do this...
if (options.unmount) options.unmount(vnode);
return str;
} catch (error) {
if (!asyncMode && renderer && renderer.onError) {
let res = renderer.onError(error, vnode, (child, parent) =>
_renderToString(
child,
context,
isSvgMode,
selectValue,
parent,
asyncMode,
renderer
)
);
if (res !== undefined) return res;
let errorHook = options[CATCH_ERROR];
if (errorHook) errorHook(error, vnode);
return EMPTY_STR;
}
if (!asyncMode) throw error;
if (!error || typeof error.then != 'function') throw error;
const renderNestedChildren = () => {
try {
return _renderToString(
rendered,
context,
isSvgMode,
selectValue,
vnode,
asyncMode,
renderer
);
} catch (e) {
if (!e || typeof e.then != 'function') throw e;
return e.then(
() =>
_renderToString(
rendered,
context,
isSvgMode,
selectValue,
vnode,
asyncMode,
renderer
),
renderNestedChildren
);
}
};
return error.then(renderNestedChildren);
}
}
// Serialize Element VNodes to HTML
let s = '<' + type,
html = EMPTY_STR,
children;
for (let name in props) {
let v = props[name];
if (typeof v == 'function' && name !== 'class' && name !== 'className') {
continue;
}
switch (name) {
case 'children':
children = v;
continue;
// VDOM-specific props
case 'key':
case 'ref':
case '__self':
case '__source':
continue;
// prefer for/class over htmlFor/className
case 'htmlFor':
if ('for' in props) continue;
name = 'for';
break;
case 'className':
if ('class' in props) continue;
name = 'class';
break;
// Form element reflected properties
case 'defaultChecked':
name = 'checked';
break;
case 'defaultSelected':
name = 'selected';
break;
// Special value attribute handling
case 'defaultValue':
case 'value':
name = 'value';
switch (type) {
// <textarea value="a&b"> --> <textarea>a&amp;b</textarea>
case 'textarea':
children = v;
continue;
// <select value> is serialized as a selected attribute on the matching option child
case 'select':
selectValue = v;
continue;
// Add a selected attribute to <option> if its value matches the parent <select> value
case 'option':
if (selectValue == v && !('selected' in props)) {
s = s + ' selected';
}
break;
}
break;
case 'dangerouslySetInnerHTML':
html = v && v.__html;
continue;
// serialize object styles to a CSS string
case 'style':
if (typeof v === 'object') {
v = styleObjToCss(v);
}
break;
case 'acceptCharset':
name = 'accept-charset';
break;
case 'httpEquiv':
name = 'http-equiv';
break;
default: {
if (NAMESPACE_REPLACE_REGEX.test(name)) {
name = name.replace(NAMESPACE_REPLACE_REGEX, '$1:$2').toLowerCase();
} else if (UNSAFE_NAME.test(name)) {
continue;
} else if (
(name[4] === '-' || HTML_ENUMERATED.has(name)) &&
v != null
) {
// serialize boolean aria-xyz or enumerated attribute values as strings
v = v + EMPTY_STR;
} else if (isSvgMode) {
if (SVG_CAMEL_CASE.test(name)) {
name =
name === 'panose1'
? 'panose-1'
: name.replace(/([A-Z])/g, '-$1').toLowerCase();
}
} else if (HTML_LOWER_CASE.test(name)) {
name = name.toLowerCase();
}
}
}
// write this attribute to the buffer
if (v != null && v !== false) {
if (v === true || v === EMPTY_STR) {
s = s + ' ' + name;
} else {
s =
s +
' ' +
name +
'="' +
(typeof v == 'string' ? encodeEntities(v) : v + EMPTY_STR) +
'"';
}
}
}
if (UNSAFE_NAME.test(type)) {
// this seems to performs a lot better than throwing
// return '<!-- -->';
throw new Error(`${type} is not a valid HTML tag name in ${s}>`);
}
if (html) {
// dangerouslySetInnerHTML defined this node's contents
} else if (typeof children === 'string') {
// single text child
html = encodeEntities(children);
} else if (children != null && children !== false && children !== true) {
// recurse into this element VNode's children
let childSvgMode =
type === 'svg' || (type !== 'foreignObject' && isSvgMode);
html = _renderToString(
children,
context,
childSvgMode,
selectValue,
vnode,
asyncMode,
renderer
);
}
if (afterDiff) afterDiff(vnode);
if (ummountHook) ummountHook(vnode);
// Emit self-closing tag for empty void elements:
if (!html && SELF_CLOSING.has(type)) {
return s + '/>';
}
const endTag = '</' + type + '>';
const startTag = s + '>';
if (isArray(html)) return [startTag, ...html, endTag];
else if (typeof html != 'string') return [startTag, html, endTag];
return startTag + html + endTag;
}
const SELF_CLOSING = new Set([
'area',
'base',
'br',
'col',
'command',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr'
]);
export default renderToString;
export const render = renderToString;
export const renderToStaticMarkup = renderToString;

35
node_modules/preact-render-to-string/src/internal.d.ts generated vendored Normal file
View File

@@ -0,0 +1,35 @@
import { ComponentChildren, ComponentChild, VNode } from 'preact';
interface Suspended {
id: string;
promise: Promise<any>;
context: any;
isSvgMode: boolean;
selectValue: any;
vnode: VNode;
parent: VNode | null;
}
interface RendererErrorHandler {
(
this: RendererState,
error: any,
vnode: VNode<{ fallback: any }>,
renderChild: (child: ComponentChildren, parent: ComponentChild) => string
): string | undefined;
}
interface RendererState {
start: number;
suspended: Suspended[];
abortSignal?: AbortSignal | undefined;
onWrite: (str: string) => void;
onError?: RendererErrorHandler;
}
interface RenderToChunksOptions {
context?: any;
onError?: (error: any) => void;
onWrite: (str: string) => void;
abortSignal?: AbortSignal;
}

24
node_modules/preact-render-to-string/src/jsx.d.ts generated vendored Normal file
View File

@@ -0,0 +1,24 @@
import { VNode } from 'preact';
interface Options {
jsx?: boolean;
xml?: boolean;
pretty?: boolean | string;
shallow?: boolean;
functions?: boolean;
functionNames?: boolean;
skipFalseAttributes?: boolean;
}
export default function renderToStringPretty(
vnode: VNode,
context?: any,
options?: Options
): string;
export function render(vnode: VNode, context?: any, options?: Options): string;
export function shallowRender(
vnode: VNode,
context?: any,
options?: Options
): string;

102
node_modules/preact-render-to-string/src/jsx.js generated vendored Normal file
View File

@@ -0,0 +1,102 @@
import './lib/polyfills.js';
import renderToString from './pretty.js';
import { indent, encodeEntities } from './lib/util.js';
import prettyFormat from 'pretty-format';
// we have to patch in Array support, Possible issue in npm.im/pretty-format
let preactPlugin = {
test(object) {
return (
object &&
typeof object === 'object' &&
'type' in object &&
'props' in object &&
'key' in object
);
},
print(val, print, indent) {
return renderToString(val, preactPlugin.context, preactPlugin.opts, true);
}
};
let prettyFormatOpts = {
plugins: [preactPlugin]
};
function attributeHook(name, value, context, opts, isComponent) {
let type = typeof value;
// Use render-to-string's built-in handling for these properties
if (name === 'dangerouslySetInnerHTML') return false;
// always skip null & undefined values, skip false DOM attributes, skip functions if told to
if (value == null || (type === 'function' && !opts.functions)) return '';
if (
opts.skipFalseAttributes &&
!isComponent &&
(value === false ||
((name === 'class' || name === 'style') && value === ''))
)
return '';
let indentChar = typeof opts.pretty === 'string' ? opts.pretty : '\t';
if (type !== 'string') {
if (type === 'function' && !opts.functionNames) {
value = 'Function';
} else {
preactPlugin.context = context;
preactPlugin.opts = opts;
value = prettyFormat(value, prettyFormatOpts);
if (~value.indexOf('\n')) {
value = `${indent('\n' + value, indentChar)}\n`;
}
}
return indent(`\n${name}={${value}}`, indentChar);
}
return `\n${indentChar}${name}="${encodeEntities(value)}"`;
}
let defaultOpts = {
attributeHook,
jsx: true,
xml: false,
functions: true,
functionNames: true,
skipFalseAttributes: true,
pretty: ' '
};
/**
* Render Preact JSX + Components to a pretty-printed HTML-like string.
* @param {VNode} vnode JSX Element / VNode to render
* @param {Object} [context={}] Initial root context object
* @param {Object} [options={}] Rendering options
* @param {Boolean} [options.jsx=true] Generate JSX/XML output instead of HTML
* @param {Boolean} [options.xml=false] Use self-closing tags for elements without children
* @param {Boolean} [options.shallow=false] Serialize nested Components (`<Foo a="b" />`) instead of rendering
* @param {Boolean} [options.pretty=false] Add whitespace for readability
* @param {RegExp|undefined} [options.voidElements] RegeEx to define which element types are self-closing
* @returns {String} a pretty-printed HTML-like string
*/
export default function renderToStringPretty(vnode, context, options) {
const opts = Object.assign({}, defaultOpts, options || {});
if (!opts.jsx) opts.attributeHook = null;
return renderToString(vnode, context, opts);
}
export { renderToStringPretty as render };
const SHALLOW = { shallow: true };
/** Only render elements, leaving Components inline as `<ComponentName ... />`.
* This method is just a convenience alias for `render(vnode, context, { shallow:true })`
* @name shallow
* @function
* @param {VNode} vnode JSX VNode to render.
* @param {Object} [context={}] Optionally pass an initial context object through the render path.
* @param {Parameters<typeof renderToStringPretty>[2]} [options] Optionally pass an initial context object through the render path.
*/
export function shallowRender(vnode, context, options) {
const opts = Object.assign({}, SHALLOW, options || {});
return renderToStringPretty(vnode, context, opts);
}

View File

@@ -0,0 +1,97 @@
import { renderToString } from '../index.js';
import { CHILD_DID_SUSPEND, COMPONENT, PARENT } from './constants.js';
import { Deferred } from './util.js';
import { createInitScript, createSubtree } from './client.js';
/**
* @param {VNode} vnode
* @param {RenderToChunksOptions} options
* @returns {Promise<void>}
*/
export async function renderToChunks(vnode, { context, onWrite, abortSignal }) {
context = context || {};
/** @type {RendererState} */
const renderer = {
start: Date.now(),
abortSignal,
onWrite,
onError: handleError,
suspended: []
};
// Synchronously render the shell
// @ts-ignore - using third internal RendererState argument
const shell = renderToString(vnode, context, renderer);
onWrite(shell);
// Wait for any suspended sub-trees if there are any
const len = renderer.suspended.length;
if (len > 0) {
onWrite('<div hidden>');
onWrite(createInitScript(len));
// We should keep checking all promises
await forkPromises(renderer);
onWrite('</div>');
}
}
async function forkPromises(renderer) {
if (renderer.suspended.length > 0) {
const suspensions = [...renderer.suspended];
await Promise.all(renderer.suspended.map((s) => s.promise));
renderer.suspended = renderer.suspended.filter(
(s) => !suspensions.includes(s)
);
await forkPromises(renderer);
}
}
/** @type {RendererErrorHandler} */
function handleError(error, vnode, renderChild) {
if (!error || !error.then) return;
// walk up to the Suspense boundary
while ((vnode = vnode[PARENT])) {
let component = vnode[COMPONENT];
if (component && component[CHILD_DID_SUSPEND]) {
break;
}
}
if (!vnode) return;
const id = vnode.__v;
const found = this.suspended.find((x) => x.id === id);
const race = new Deferred();
const abortSignal = this.abortSignal;
if (abortSignal) {
// @ts-ignore 2554 - implicit undefined arg
if (abortSignal.aborted) race.resolve();
else abortSignal.addEventListener('abort', race.resolve);
}
const promise = error.then(
() => {
if (abortSignal && abortSignal.aborted) return;
const child = renderChild(vnode.props.children, vnode);
if (child) this.onWrite(createSubtree(id, child));
},
// TODO: Abort and send hydration code snippet to client
// to attempt to recover during hydration
this.onError
);
this.suspended.push({
id,
vnode,
promise: Promise.race([promise, race.promise])
});
const fallback = renderChild(vnode.props.fallback);
return found
? ''
: `<!--preact-island:${id}-->${fallback}<!--/preact-island:${id}-->`;
}

62
node_modules/preact-render-to-string/src/lib/client.js generated vendored Normal file
View File

@@ -0,0 +1,62 @@
/* eslint-disable no-var, key-spacing, object-curly-spacing, prefer-arrow-callback, semi, keyword-spacing */
// function initPreactIslandElement() {
// class PreactIslandElement extends HTMLElement {
// connectedCallback() {
// var d = this;
// if (!d.isConnected) return;
// let i = this.getAttribute('data-target');
// if (!i) return;
// var s,
// e,
// c = document.createNodeIterator(document, 128);
// while (c.nextNode()) {
// let n = c.referenceNode;
// if (n.data == 'preact-island:' + i) s = n;
// else if (n.data == '/preact-island:' + i) e = n;
// if (s && e) break;
// }
// if (s && e) {
// requestAnimationFrame(() => {
// var p = e.previousSibling;
// while (p != s) {
// if (!p || p == s) break;
// e.parentNode.removeChild(p);
// p = e.previousSibling;
// }
// c = s;
// while (d.firstChild) {
// s = d.firstChild;
// d.removeChild(s);
// c.after(s);
// c = s;
// }
// d.parentNode.removeChild(d);
// });
// }
// }
// }
// customElements.define('preact-island', PreactIslandElement);
// }
// To modify the INIT_SCRIPT, uncomment the above code, modify it, and paste it into https://try.terser.org/.
const INIT_SCRIPT = `class e extends HTMLElement{connectedCallback(){var e=this;if(!e.isConnected)return;let t=this.getAttribute("data-target");if(t){for(var r,a,i=document.createNodeIterator(document,128);i.nextNode();){let e=i.referenceNode;if(e.data=="preact-island:"+t?r=e:e.data=="/preact-island:"+t&&(a=e),r&&a)break}r&&a&&requestAnimationFrame((()=>{for(var t=a.previousSibling;t!=r&&t&&t!=r;)a.parentNode.removeChild(t),t=a.previousSibling;for(i=r;e.firstChild;)r=e.firstChild,e.removeChild(r),i.after(r),i=r;e.parentNode.removeChild(e)}))}}}customElements.define("preact-island",e);`;
export function createInitScript() {
return `<script>(function(){${INIT_SCRIPT}}())</script>`;
}
/**
* @param {string} id
* @param {string} content
* @returns {string}
*/
export function createSubtree(id, content) {
return `<preact-island hidden data-target="${id}">${content}</preact-island>`;
}

View File

@@ -0,0 +1,19 @@
// Options hooks
export const DIFF = '__b';
export const RENDER = '__r';
export const DIFFED = 'diffed';
export const COMMIT = '__c';
export const SKIP_EFFECTS = '__s';
export const CATCH_ERROR = '__e';
// VNode properties
export const COMPONENT = '__c';
export const CHILDREN = '__k';
export const PARENT = '__';
export const MASK = '__m';
// Component properties
export const VNODE = '__v';
export const DIRTY = '__d';
export const NEXT_STATE = '__s';
export const CHILD_DID_SUSPEND = '__c';

View File

@@ -0,0 +1,8 @@
if (typeof Symbol !== 'function') {
let c = 0;
// eslint-disable-next-line
Symbol = function (s) {
return `@@${s}${++c}`;
};
Symbol.for = (s) => `@@${s}`;
}

181
node_modules/preact-render-to-string/src/lib/util.js generated vendored Normal file
View File

@@ -0,0 +1,181 @@
export const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
export const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
export const NAMESPACE_REPLACE_REGEX = /^(xlink|xmlns|xml)([A-Z])/;
export const HTML_LOWER_CASE = /^(?:accessK|auto[A-Z]|cell|ch|col|cont|cross|dateT|encT|form[A-Z]|frame|hrefL|inputM|maxL|minL|noV|playsI|popoverT|readO|rowS|src[A-Z]|tabI|useM|item[A-Z])/;
export const SVG_CAMEL_CASE = /^ac|^ali|arabic|basel|cap|clipPath$|clipRule$|color|dominant|enable|fill|flood|font|glyph[^R]|horiz|image|letter|lighting|marker[^WUH]|overline|panose|pointe|paint|rendering|shape|stop|strikethrough|stroke|text[^L]|transform|underline|unicode|units|^v[^i]|^w|^xH/;
// Boolean DOM properties that translate to enumerated ('true'/'false') attributes
export const HTML_ENUMERATED = new Set(['draggable', 'spellcheck']);
// DOM properties that should NOT have "px" added when numeric
const ENCODED_ENTITIES = /["&<]/;
/** @param {string} str */
export function encodeEntities(str) {
// Skip all work for strings with no entities needing encoding:
if (str.length === 0 || ENCODED_ENTITIES.test(str) === false) return str;
let last = 0,
i = 0,
out = '',
ch = '';
// Seek forward in str until the next entity char:
for (; i < str.length; i++) {
switch (str.charCodeAt(i)) {
case 34:
ch = '&quot;';
break;
case 38:
ch = '&amp;';
break;
case 60:
ch = '&lt;';
break;
default:
continue;
}
// Append skipped/buffered characters and the encoded entity:
if (i !== last) out = out + str.slice(last, i);
out = out + ch;
// Start the next seek/buffer after the entity's offset:
last = i + 1;
}
if (i !== last) out = out + str.slice(last, i);
return out;
}
export let indent = (s, char) =>
String(s).replace(/(\n+)/g, '$1' + (char || '\t'));
export let isLargeString = (s, length, ignoreLines) =>
String(s).length > (length || 40) ||
(!ignoreLines && String(s).indexOf('\n') !== -1) ||
String(s).indexOf('<') !== -1;
const JS_TO_CSS = {};
const IS_NON_DIMENSIONAL = new Set([
'animation-iteration-count',
'border-image-outset',
'border-image-slice',
'border-image-width',
'box-flex',
'box-flex-group',
'box-ordinal-group',
'column-count',
'fill-opacity',
'flex',
'flex-grow',
'flex-negative',
'flex-order',
'flex-positive',
'flex-shrink',
'flood-opacity',
'font-weight',
'grid-column',
'grid-row',
'line-clamp',
'line-height',
'opacity',
'order',
'orphans',
'stop-opacity',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'tab-size',
'widows',
'z-index',
'zoom'
]);
const CSS_REGEX = /[A-Z]/g;
// Convert an Object style to a CSSText string
export function styleObjToCss(s) {
let str = '';
for (let prop in s) {
let val = s[prop];
if (val != null && val !== '') {
const name =
prop[0] == '-'
? prop
: JS_TO_CSS[prop] ||
(JS_TO_CSS[prop] = prop.replace(CSS_REGEX, '-$&').toLowerCase());
let suffix = ';';
if (
typeof val === 'number' &&
// Exclude custom-attributes
!name.startsWith('--') &&
!IS_NON_DIMENSIONAL.has(name)
) {
suffix = 'px;';
}
str = str + name + ':' + val + suffix;
}
}
return str || undefined;
}
/**
* Get flattened children from the children prop
* @param {Array} accumulator
* @param {any} children A `props.children` opaque object.
* @returns {Array} accumulator
* @private
*/
export function getChildren(accumulator, children) {
if (Array.isArray(children)) {
children.reduce(getChildren, accumulator);
} else if (children != null && children !== false) {
accumulator.push(children);
}
return accumulator;
}
function markAsDirty() {
this.__d = true;
}
export function createComponent(vnode, context) {
return {
__v: vnode,
context,
props: vnode.props,
// silently drop state updates
setState: markAsDirty,
forceUpdate: markAsDirty,
__d: true,
// hooks
__h: new Array(0)
};
}
// Necessary for createContext api. Setting this property will pass
// the context value as `this.context` just for this component.
export function getContext(nodeName, context) {
let cxType = nodeName.contextType;
let provider = cxType && context[cxType.__c];
return cxType != null
? provider
? provider.props.value
: cxType.__
: context;
}
/**
* @template T
*/
export class Deferred {
constructor() {
// eslint-disable-next-line lines-around-comment
/** @type {Promise<T>} */
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}

446
node_modules/preact-render-to-string/src/pretty.js generated vendored Normal file
View File

@@ -0,0 +1,446 @@
import {
encodeEntities,
indent,
isLargeString,
styleObjToCss,
getChildren,
createComponent,
UNSAFE_NAME,
VOID_ELEMENTS,
NAMESPACE_REPLACE_REGEX,
SVG_CAMEL_CASE,
HTML_LOWER_CASE,
getContext
} from './lib/util.js';
import { COMMIT, DIFF, DIFFED, RENDER, SKIP_EFFECTS } from './lib/constants.js';
import { options, Fragment } from 'preact';
// components without names, kept as a hash for later comparison to return consistent UnnamedComponentXX names.
const UNNAMED = [];
const EMPTY_ARR = [];
/**
* Render Preact JSX + Components to a pretty-printed HTML-like string.
* @param {VNode} vnode JSX Element / VNode to render
* @param {Object} [context={}] Initial root context object
* @param {Object} [opts={}] Rendering options
* @param {Boolean} [opts.shallow=false] Serialize nested Components (`<Foo a="b" />`) instead of rendering
* @param {Boolean} [opts.xml=false] Use self-closing tags for elements without children
* @param {Boolean} [opts.pretty=false] Add whitespace for readability
* @param {RegExp|undefined} [opts.voidElements] RegeEx to define which element types are self-closing
* @param {boolean} [_inner]
* @returns {String} a pretty-printed HTML-like string
*/
export default function renderToStringPretty(vnode, context, opts, _inner) {
// Performance optimization: `renderToString` is synchronous and we
// therefore don't execute any effects. To do that we pass an empty
// array to `options._commit` (`__c`). But we can go one step further
// and avoid a lot of dirty checks and allocations by setting
// `options._skipEffects` (`__s`) too.
const previousSkipEffects = options[SKIP_EFFECTS];
options[SKIP_EFFECTS] = true;
try {
return _renderToStringPretty(vnode, context || {}, opts, _inner);
} finally {
// options._commit, we don't schedule any effects in this library right now,
// so we can pass an empty queue to this hook.
if (options[COMMIT]) options[COMMIT](vnode, EMPTY_ARR);
options[SKIP_EFFECTS] = previousSkipEffects;
EMPTY_ARR.length = 0;
}
}
function _renderToStringPretty(
vnode,
context,
opts,
inner,
isSvgMode,
selectValue
) {
if (vnode == null || typeof vnode === 'boolean') {
return '';
}
// #text nodes
if (typeof vnode !== 'object') {
if (typeof vnode === 'function') return '';
return encodeEntities(vnode + '');
}
let pretty = opts.pretty,
indentChar = pretty && typeof pretty === 'string' ? pretty : '\t';
if (Array.isArray(vnode)) {
let rendered = '';
for (let i = 0; i < vnode.length; i++) {
if (pretty && i > 0) rendered = rendered + '\n';
rendered =
rendered +
_renderToStringPretty(
vnode[i],
context,
opts,
inner,
isSvgMode,
selectValue
);
}
return rendered;
}
// VNodes have {constructor:undefined} to prevent JSON injection:
if (vnode.constructor !== undefined) return '';
if (options[DIFF]) options[DIFF](vnode);
let nodeName = vnode.type,
props = vnode.props,
isComponent = false;
// components
if (typeof nodeName === 'function') {
isComponent = true;
if (
opts.shallow &&
(inner || opts.renderRootComponent === false) &&
nodeName !== Fragment
) {
nodeName = getComponentName(nodeName);
} else if (nodeName === Fragment) {
const children = [];
getChildren(children, vnode.props.children);
return _renderToStringPretty(
children,
context,
opts,
opts.shallowHighOrder !== false,
isSvgMode,
selectValue
);
} else {
let rendered;
let c = (vnode.__c = createComponent(vnode, context));
let renderHook = options[RENDER];
if (
!nodeName.prototype ||
typeof nodeName.prototype.render !== 'function'
) {
let cctx = getContext(nodeName, context);
// If a hook invokes setState() to invalidate the component during rendering,
// re-render it up to 25 times to allow "settling" of memoized states.
// Note:
// This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
// https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
let count = 0;
while (c.__d && count++ < 25) {
c.__d = false;
if (renderHook) renderHook(vnode);
// stateless functional components
rendered = nodeName.call(vnode.__c, props, cctx);
}
} else {
let cctx = getContext(nodeName, context);
// c = new nodeName(props, context);
c = vnode.__c = new nodeName(props, cctx);
c.__v = vnode;
// turn off stateful re-rendering:
c._dirty = c.__d = true;
c.props = props;
if (c.state == null) c.state = {};
if (c._nextState == null && c.__s == null) {
c._nextState = c.__s = c.state;
}
c.context = cctx;
if (nodeName.getDerivedStateFromProps)
c.state = Object.assign(
{},
c.state,
nodeName.getDerivedStateFromProps(c.props, c.state)
);
else if (c.componentWillMount) {
c.componentWillMount();
// If the user called setState in cWM we need to flush pending,
// state updates. This is the same behaviour in React.
c.state =
c._nextState !== c.state
? c._nextState
: c.__s !== c.state
? c.__s
: c.state;
}
if (renderHook) renderHook(vnode);
rendered = c.render(c.props, c.state, c.context);
}
if (c.getChildContext) {
context = Object.assign({}, context, c.getChildContext());
}
const res = _renderToStringPretty(
rendered,
context,
opts,
opts.shallowHighOrder !== false,
isSvgMode,
selectValue
);
if (options[DIFFED]) options[DIFFED](vnode);
return res;
}
}
// render JSX to HTML
let s = '<' + nodeName,
propChildren,
html;
if (props) {
let attrs = Object.keys(props);
// allow sorting lexicographically for more determinism (useful for tests, such as via preact-jsx-chai)
if (opts && opts.sortAttributes === true) attrs.sort();
for (let i = 0; i < attrs.length; i++) {
let name = attrs[i],
v = props[name];
if (name === 'children') {
propChildren = v;
continue;
}
if (UNSAFE_NAME.test(name)) continue;
if (
!(opts && opts.allAttributes) &&
(name === 'key' ||
name === 'ref' ||
name === '__self' ||
name === '__source')
)
continue;
if (name === 'defaultValue') {
name = 'value';
} else if (name === 'defaultChecked') {
name = 'checked';
} else if (name === 'defaultSelected') {
name = 'selected';
} else if (name === 'className') {
if (typeof props.class !== 'undefined') continue;
name = 'class';
} else if (name === 'acceptCharset') {
name = 'accept-charset';
} else if (name === 'httpEquiv') {
name = 'http-equiv';
} else if (NAMESPACE_REPLACE_REGEX.test(name)) {
name = name.replace(NAMESPACE_REPLACE_REGEX, '$1:$2').toLowerCase();
} else if (isSvgMode) {
if (SVG_CAMEL_CASE.test(name)) {
name =
name === 'panose1'
? 'panose-1'
: name.replace(/([A-Z])/g, '-$1').toLowerCase();
}
} else if (HTML_LOWER_CASE.test(name)) {
name = name.toLowerCase();
}
if (name === 'htmlFor') {
if (props.for) continue;
name = 'for';
}
if (name === 'style' && v && typeof v === 'object') {
v = styleObjToCss(v);
}
// always use string values instead of booleans for aria attributes
// also see https://github.com/preactjs/preact/pull/2347/files
if (name[0] === 'a' && name['1'] === 'r' && typeof v === 'boolean') {
v = String(v);
}
let hooked =
opts.attributeHook &&
opts.attributeHook(name, v, context, opts, isComponent);
if (hooked || hooked === '') {
s = s + hooked;
continue;
}
if (name === 'dangerouslySetInnerHTML') {
html = v && v.__html;
} else if (nodeName === 'textarea' && name === 'value') {
// <textarea value="a&b"> --> <textarea>a&amp;b</textarea>
propChildren = v;
} else if ((v || v === 0 || v === '') && typeof v !== 'function') {
if (v === true || v === '') {
v = name;
// in non-xml mode, allow boolean attributes
if (!opts || !opts.xml) {
s = s + ' ' + name;
continue;
}
}
if (name === 'value') {
if (nodeName === 'select') {
selectValue = v;
continue;
} else if (
// If we're looking at an <option> and it's the currently selected one
nodeName === 'option' &&
selectValue == v &&
// and the <option> doesn't already have a selected attribute on it
typeof props.selected === 'undefined'
) {
s = s + ` selected`;
}
}
s = s + ` ${name}="${encodeEntities(v + '')}"`;
}
}
}
// account for >1 multiline attribute
if (pretty) {
let sub = s.replace(/\n\s*/, ' ');
if (sub !== s && !~sub.indexOf('\n')) s = sub;
else if (pretty && ~s.indexOf('\n')) s = s + '\n';
}
s = s + '>';
if (UNSAFE_NAME.test(nodeName))
throw new Error(`${nodeName} is not a valid HTML tag name in ${s}`);
let isVoid =
VOID_ELEMENTS.test(nodeName) ||
(opts.voidElements && opts.voidElements.test(nodeName));
let pieces = [];
let children;
if (html) {
// if multiline, indent.
if (pretty && isLargeString(html)) {
html = '\n' + indentChar + indent(html, indentChar);
}
s = s + html;
} else if (
propChildren != null &&
getChildren((children = []), propChildren).length
) {
let hasLarge = pretty && ~s.indexOf('\n');
let lastWasText = false;
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (child != null && child !== false) {
let childSvgMode =
nodeName === 'svg'
? true
: nodeName === 'foreignObject'
? false
: isSvgMode,
ret = _renderToStringPretty(
child,
context,
opts,
true,
childSvgMode,
selectValue
);
if (pretty && !hasLarge && isLargeString(ret)) hasLarge = true;
// Skip if we received an empty string
if (ret) {
if (pretty) {
let isText = ret.length > 0 && ret[0] != '<';
// We merge adjacent text nodes, otherwise each piece would be printed
// on a new line.
if (lastWasText && isText) {
pieces[pieces.length - 1] += ret;
} else {
pieces.push(ret);
}
lastWasText = isText;
} else {
pieces.push(ret);
}
}
}
}
if (pretty && hasLarge) {
for (let i = pieces.length; i--; ) {
pieces[i] = '\n' + indentChar + indent(pieces[i], indentChar);
}
}
}
if (options[DIFFED]) options[DIFFED](vnode);
if (pieces.length || html) {
s = s + pieces.join('');
} else if (opts && opts.xml) {
return s.substring(0, s.length - 1) + ' />';
}
if (isVoid && !children && !html) {
s = s.replace(/>$/, ' />');
} else {
if (pretty && ~s.indexOf('\n')) s = s + '\n';
s = s + `</${nodeName}>`;
}
return s;
}
function getComponentName(component) {
return (
component.displayName ||
(component !== Function && component.name) ||
getFallbackComponentName(component)
);
}
function getFallbackComponentName(component) {
let str = Function.prototype.toString.call(component),
name = (str.match(/^\s*function\s+([^( ]+)/) || '')[1];
if (!name) {
// search for an existing indexed name for the given component:
let index = -1;
for (let i = UNNAMED.length; i--; ) {
if (UNNAMED[i] === component) {
index = i;
break;
}
}
// not found, create a new indexed name:
if (index < 0) {
index = UNNAMED.push(component) - 1;
}
name = `UnnamedComponent${index}`;
}
return name;
}

View File

@@ -0,0 +1,19 @@
import { VNode } from 'preact';
import { WritableStream } from 'node:stream';
interface RenderToPipeableStreamOptions {
onShellReady?: () => void;
onAllReady?: () => void;
onError?: (error: any) => void;
}
interface PipeableStream {
abort: (reason?: unknown) => void;
pipe: (writable: WritableStream) => void;
}
export function renderToPipeableStream<P = {}>(
vnode: VNode<P>,
options: RenderToPipeableStreamOptions,
context?: any
): PipeableStream;

View File

@@ -0,0 +1,77 @@
import { PassThrough } from 'node:stream';
import { renderToChunks } from './lib/chunked.js';
/**
* @typedef {object} RenderToPipeableStreamOptions
* @property {() => void} [onShellReady]
* @property {() => void} [onAllReady]
* @property {(error) => void} [onError]
*/
/**
* @typedef {object} PipeableStream
* @property {() => void} abort
* @property {(writable: import('stream').Writable) => void} pipe
*/
/**
* @param {import('preact').VNode} vnode
* @param {RenderToPipeableStreamOptions} options
* @param {any} [context]
* @returns {PipeableStream}
*/
export function renderToPipeableStream(vnode, options, context) {
const encoder = new TextEncoder('utf-8');
const controller = new AbortController();
const stream = new PassThrough();
renderToChunks(vnode, {
context,
abortSignal: controller.signal,
onError: (error) => {
if (options.onError) {
options.onError(error);
}
controller.abort(error);
},
onWrite(s) {
stream.write(encoder.encode(s));
}
})
.then(() => {
options.onAllReady && options.onAllReady();
stream.end();
})
.catch((error) => {
stream.destroy();
if (options.onError) {
options.onError(error);
} else {
throw error;
}
});
Promise.resolve().then(() => {
options.onShellReady && options.onShellReady();
});
return {
/**
* @param {unknown} [reason]
*/
abort(reason = new Error('The render was aborted by the server without a reason.')) {
controller.abort();
stream.destroy();
if (options.onError) {
options.onError(reason);
}
},
/**
* @param {import("stream").Writable} writable
*/
pipe(writable) {
stream.pipe(writable, { end: true });
}
};
}

10
node_modules/preact-render-to-string/src/stream.d.ts generated vendored Normal file
View File

@@ -0,0 +1,10 @@
import { VNode } from 'preact';
interface RenderStream extends ReadableStream<Uint8Array> {
allReady: Promise<void>;
}
export function renderToReadableStream<P = {}>(
vnode: VNode<P>,
context?: any
): RenderStream;

43
node_modules/preact-render-to-string/src/stream.js generated vendored Normal file
View File

@@ -0,0 +1,43 @@
import { Deferred } from './lib/util.js';
import { renderToChunks } from './lib/chunked.js';
/** @typedef {ReadableStream<Uint8Array> & { allReady: Promise<void>}} RenderStream */
/**
* @param {import('preact').VNode} vnode
* @param {any} [context]
* @returns {RenderStream}
*/
export function renderToReadableStream(vnode, context) {
/** @type {Deferred<void>} */
const allReady = new Deferred();
const encoder = new TextEncoder('utf-8');
/** @type {RenderStream} */
const stream = new ReadableStream({
start(controller) {
renderToChunks(vnode, {
context,
onError: (error) => {
allReady.reject(error);
controller.abort(error);
},
onWrite(s) {
controller.enqueue(encoder.encode(s));
}
})
.then(() => {
controller.close();
allReady.resolve();
})
.catch((error) => {
controller.error(error);
allReady.reject(error);
});
}
});
stream.allReady = allReady.promise;
return stream;
}

5
node_modules/preact-render-to-string/typings.json generated vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"name": "preact-render-to-string",
"main": "src/index.d.ts",
"version": false
}