Init
This commit is contained in:
21
node_modules/preact-render-to-string/LICENSE
generated
vendored
Normal file
21
node_modules/preact-render-to-string/LICENSE
generated
vendored
Normal 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
149
node_modules/preact-render-to-string/README.md
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
# preact-render-to-string
|
||||
|
||||
[](https://www.npmjs.com/package/preact-render-to-string)
|
||||
[](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/)
|
2
node_modules/preact-render-to-string/dist/commonjs.js
generated
vendored
Normal file
2
node_modules/preact-render-to-string/dist/commonjs.js
generated
vendored
Normal file
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
17
node_modules/preact-render-to-string/dist/index.d.ts
generated
vendored
Normal 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
6
node_modules/preact-render-to-string/dist/index.js
generated
vendored
Normal 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;
|
1
node_modules/preact-render-to-string/dist/index.js.map
generated
vendored
Normal file
1
node_modules/preact-render-to-string/dist/index.js.map
generated
vendored
Normal file
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
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
2
node_modules/preact-render-to-string/dist/index.module.js
generated
vendored
Normal file
2
node_modules/preact-render-to-string/dist/index.module.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/preact-render-to-string/dist/index.module.js.map
generated
vendored
Normal file
1
node_modules/preact-render-to-string/dist/index.module.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
node_modules/preact-render-to-string/dist/index.umd.js
generated
vendored
Normal file
2
node_modules/preact-render-to-string/dist/index.umd.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/preact-render-to-string/dist/index.umd.js.map
generated
vendored
Normal file
1
node_modules/preact-render-to-string/dist/index.umd.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
35
node_modules/preact-render-to-string/dist/internal.d.ts
generated
vendored
Normal file
35
node_modules/preact-render-to-string/dist/internal.d.ts
generated
vendored
Normal 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
24
node_modules/preact-render-to-string/dist/jsx.d.ts
generated
vendored
Normal 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;
|
2
node_modules/preact-render-to-string/dist/jsx/commonjs.js
generated
vendored
Normal file
2
node_modules/preact-render-to-string/dist/jsx/commonjs.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
4
node_modules/preact-render-to-string/dist/jsx/index.js
generated
vendored
Normal file
4
node_modules/preact-render-to-string/dist/jsx/index.js
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
const entry = require('./commonjs');
|
||||
entry.default.render = entry.render;
|
||||
entry.default.shallowRender = entry.shallowRender;
|
||||
module.exports = entry.default;
|
1
node_modules/preact-render-to-string/dist/jsx/index.js.map
generated
vendored
Normal file
1
node_modules/preact-render-to-string/dist/jsx/index.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
node_modules/preact-render-to-string/dist/jsx/index.mjs
generated
vendored
Normal file
2
node_modules/preact-render-to-string/dist/jsx/index.mjs
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
node_modules/preact-render-to-string/dist/jsx/index.module.js
generated
vendored
Normal file
2
node_modules/preact-render-to-string/dist/jsx/index.module.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/preact-render-to-string/dist/jsx/index.module.js.map
generated
vendored
Normal file
1
node_modules/preact-render-to-string/dist/jsx/index.module.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
node_modules/preact-render-to-string/dist/jsx/index.umd.js
generated
vendored
Normal file
2
node_modules/preact-render-to-string/dist/jsx/index.umd.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/preact-render-to-string/dist/jsx/index.umd.js.map
generated
vendored
Normal file
1
node_modules/preact-render-to-string/dist/jsx/index.umd.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
19
node_modules/preact-render-to-string/dist/stream-node.d.ts
generated
vendored
Normal file
19
node_modules/preact-render-to-string/dist/stream-node.d.ts
generated
vendored
Normal 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
10
node_modules/preact-render-to-string/dist/stream.d.ts
generated
vendored
Normal 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;
|
2
node_modules/preact-render-to-string/dist/stream/index.js
generated
vendored
Normal file
2
node_modules/preact-render-to-string/dist/stream/index.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/preact-render-to-string/dist/stream/index.js.map
generated
vendored
Normal file
1
node_modules/preact-render-to-string/dist/stream/index.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
node_modules/preact-render-to-string/dist/stream/index.mjs
generated
vendored
Normal file
2
node_modules/preact-render-to-string/dist/stream/index.mjs
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
node_modules/preact-render-to-string/dist/stream/index.module.js
generated
vendored
Normal file
2
node_modules/preact-render-to-string/dist/stream/index.module.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/preact-render-to-string/dist/stream/index.module.js.map
generated
vendored
Normal file
1
node_modules/preact-render-to-string/dist/stream/index.module.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
node_modules/preact-render-to-string/dist/stream/index.umd.js
generated
vendored
Normal file
2
node_modules/preact-render-to-string/dist/stream/index.umd.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/preact-render-to-string/dist/stream/index.umd.js.map
generated
vendored
Normal file
1
node_modules/preact-render-to-string/dist/stream/index.umd.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
818
node_modules/preact-render-to-string/dist/stream/node/index.js
generated
vendored
Normal file
818
node_modules/preact-render-to-string/dist/stream/node/index.js
generated
vendored
Normal 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 = '"';
|
||||
break;
|
||||
|
||||
case 38:
|
||||
ch = '&';
|
||||
break;
|
||||
|
||||
case 60:
|
||||
ch = '<';
|
||||
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&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
|
1
node_modules/preact-render-to-string/dist/stream/node/index.js.map
generated
vendored
Normal file
1
node_modules/preact-render-to-string/dist/stream/node/index.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
818
node_modules/preact-render-to-string/dist/stream/node/index.mjs
generated
vendored
Normal file
818
node_modules/preact-render-to-string/dist/stream/node/index.mjs
generated
vendored
Normal 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 = '"';
|
||||
break;
|
||||
|
||||
case 38:
|
||||
ch = '&';
|
||||
break;
|
||||
|
||||
case 60:
|
||||
ch = '<';
|
||||
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&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
|
818
node_modules/preact-render-to-string/dist/stream/node/index.module.js
generated
vendored
Normal file
818
node_modules/preact-render-to-string/dist/stream/node/index.module.js
generated
vendored
Normal 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 = '"';
|
||||
break;
|
||||
|
||||
case 38:
|
||||
ch = '&';
|
||||
break;
|
||||
|
||||
case 60:
|
||||
ch = '<';
|
||||
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&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
|
1
node_modules/preact-render-to-string/dist/stream/node/index.module.js.map
generated
vendored
Normal file
1
node_modules/preact-render-to-string/dist/stream/node/index.module.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
822
node_modules/preact-render-to-string/dist/stream/node/index.umd.js
generated
vendored
Normal file
822
node_modules/preact-render-to-string/dist/stream/node/index.umd.js
generated
vendored
Normal 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 = '"';
|
||||
break;
|
||||
|
||||
case 38:
|
||||
ch = '&';
|
||||
break;
|
||||
|
||||
case 60:
|
||||
ch = '<';
|
||||
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&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
|
1
node_modules/preact-render-to-string/dist/stream/node/index.umd.js.map
generated
vendored
Normal file
1
node_modules/preact-render-to-string/dist/stream/node/index.umd.js.map
generated
vendored
Normal file
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
24
node_modules/preact-render-to-string/jsx.d.ts
generated
vendored
Normal 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
1
node_modules/preact-render-to-string/jsx.js
generated
vendored
Normal 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
174
node_modules/preact-render-to-string/package.json
generated
vendored
Normal 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
17
node_modules/preact-render-to-string/src/index.d.ts
generated
vendored
Normal 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
725
node_modules/preact-render-to-string/src/index.js
generated
vendored
Normal 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&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
35
node_modules/preact-render-to-string/src/internal.d.ts
generated
vendored
Normal 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
24
node_modules/preact-render-to-string/src/jsx.d.ts
generated
vendored
Normal 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
102
node_modules/preact-render-to-string/src/jsx.js
generated
vendored
Normal 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);
|
||||
}
|
97
node_modules/preact-render-to-string/src/lib/chunked.js
generated
vendored
Normal file
97
node_modules/preact-render-to-string/src/lib/chunked.js
generated
vendored
Normal 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
62
node_modules/preact-render-to-string/src/lib/client.js
generated
vendored
Normal 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>`;
|
||||
}
|
19
node_modules/preact-render-to-string/src/lib/constants.js
generated
vendored
Normal file
19
node_modules/preact-render-to-string/src/lib/constants.js
generated
vendored
Normal 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';
|
8
node_modules/preact-render-to-string/src/lib/polyfills.js
generated
vendored
Normal file
8
node_modules/preact-render-to-string/src/lib/polyfills.js
generated
vendored
Normal 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
181
node_modules/preact-render-to-string/src/lib/util.js
generated
vendored
Normal 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 = '"';
|
||||
break;
|
||||
case 38:
|
||||
ch = '&';
|
||||
break;
|
||||
case 60:
|
||||
ch = '<';
|
||||
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
446
node_modules/preact-render-to-string/src/pretty.js
generated
vendored
Normal 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&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;
|
||||
}
|
19
node_modules/preact-render-to-string/src/stream-node.d.ts
generated
vendored
Normal file
19
node_modules/preact-render-to-string/src/stream-node.d.ts
generated
vendored
Normal 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;
|
77
node_modules/preact-render-to-string/src/stream-node.js
generated
vendored
Normal file
77
node_modules/preact-render-to-string/src/stream-node.js
generated
vendored
Normal 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
10
node_modules/preact-render-to-string/src/stream.d.ts
generated
vendored
Normal 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
43
node_modules/preact-render-to-string/src/stream.js
generated
vendored
Normal 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
5
node_modules/preact-render-to-string/typings.json
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "preact-render-to-string",
|
||||
"main": "src/index.d.ts",
|
||||
"version": false
|
||||
}
|
Reference in New Issue
Block a user