Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
/npm-debug.log
.DS_Store
/src/preact-render-to-string-tests.d.ts
/benchmarks/.v8.mjs
/benchmarks/.v8.modern.js
6 changes: 1 addition & 5 deletions jsx.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@ interface Options {
skipFalseAttributes?: boolean;
}

export default function renderToStringPretty(
export default function render(
vnode: VNode,
context?: any,
options?: Options
): string;

export function shallowRender(vnode: VNode, context?: any): string;

export default render;
33 changes: 32 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 18 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,31 @@
"browser": "./dist/jsx.module.js",
"require": "./dist/jsx.js"
},
"./stream": {
"types": "./stream.d.ts",
"import": "./dist/stream.mjs",
"browser": "./dist/stream.module.js",
"require": "./dist/stream.js"
},
"./stream-node": {
"types": "./stream-node.d.ts",
"import": "./dist/stream-node.mjs",
"browser": "./dist/stream-node.module.js",
"require": "./dist/stream-node.js"
},
"./package.json": "./package.json"
},
"scripts": {
"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.mjs",
"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 copy-typescript-definition",
"postbuild": "node ./config/node-13-exports.js && node ./config/node-commonjs.js",
"transpile": "microbundle src/index.js -f es,umd --target web --external preact",
"transpile:jsx": "microbundle src/jsx.js -o dist/jsx.js --target web --external preact && microbundle dist/jsx.js -o dist/jsx.js -f cjs --external preact",
"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.js",
"test:mocha:compat": "BABEL_ENV=test mocha -r @babel/register -r test/setup.js 'test/compat/index.test.js'",
"test:mocha:compat": "BABEL_ENV=test mocha -r @babel/register -r test/setup.js 'test/compat/*.test.js'",
"test:mocha:debug": "BABEL_ENV=test mocha -r @babel/register -r test/setup.js 'test/debug/index.test.js'",
"format": "prettier src/**/*.{d.ts,js} test/**/*.js --write",
"prepublishOnly": "npm run build",
Expand Down Expand Up @@ -61,7 +73,8 @@
"new-cap": 0,
"curly": "off",
"brace-style": "off",
"indent": "off"
"indent": "off",
"lines-around-comment": "off"
},
"settings": {
"react": {
Expand Down Expand Up @@ -125,7 +138,8 @@
"prettier": "^2.2.1",
"sinon": "^9.2.2",
"sinon-chai": "^3.5.0",
"typescript": "^4.1.3"
"typescript": "^4.1.3",
"web-streams-polyfill": "^3.2.1"
},
"dependencies": {
"pretty-format": "^3.8.0"
Expand Down
97 changes: 74 additions & 23 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { encodeEntities, styleObjToCss, UNSAFE_NAME, XLINK } from './util';
import { options, h, Fragment } from 'preact';
import { encodeEntities, styleObjToCss, UNSAFE_NAME, XLINK } from './lib/util';
import {
CHILDREN,
COMMIT,
Expand All @@ -11,10 +11,9 @@ import {
PARENT,
RENDER,
SKIP_EFFECTS,
VNODE
} from './constants';

/** @typedef {import('preact').VNode} VNode */
VNODE,
CATCH_ERROR
} from './lib/constants';

const EMPTY_ARR = [];
const isArray = Array.isArray;
Expand All @@ -27,9 +26,10 @@ let beforeDiff, afterDiff, renderHook;
* 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 default function renderToString(vnode, context) {
export default 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
Expand All @@ -47,7 +47,14 @@ export default function renderToString(vnode, context) {
parent[CHILDREN] = [vnode];

try {
return _renderToString(vnode, context || {}, false, undefined, parent);
return _renderToString(
vnode,
context || {},
false,
undefined,
parent,
_rendererState
);
} finally {
// options._commit, we don't schedule any effects in this library right now,
// so we can pass an empty queue to this hook.
Expand Down Expand Up @@ -111,9 +118,17 @@ function renderClassComponent(vnode, context) {
* @param {boolean} isSvgMode
* @param {any} selectValue
* @param {VNode} parent
* @param {RendererState | undefined} [renderer]
* @returns {string}
*/
function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
function _renderToString(
vnode,
context,
isSvgMode,
selectValue,
parent,
renderer
) {
// Ignore non-rendered VNodes/values
if (vnode == null || vnode === true || vnode === false || vnode === '') {
return '';
Expand All @@ -135,7 +150,14 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {

rendered =
rendered +
_renderToString(child, context, isSvgMode, selectValue, parent);
_renderToString(
child,
context,
isSvgMode,
selectValue,
parent,
renderer
);

if (
typeof child === 'string' ||
Expand Down Expand Up @@ -218,20 +240,42 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
rendered != null && rendered.type === Fragment && rendered.key == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;

// Recurse into children before invoking the after-diff hook
const str = _renderToString(
rendered,
context,
isSvgMode,
selectValue,
vnode
);
if (afterDiff) afterDiff(vnode);
vnode[PARENT] = undefined;

if (options.unmount) options.unmount(vnode);
try {
// Recurse into children before invoking the after-diff hook
const str = _renderToString(
rendered,
context,
isSvgMode,
selectValue,
vnode,
renderer
);

if (afterDiff) afterDiff(vnode);
vnode[PARENT] = undefined;

if (options.unmount) options.unmount(vnode);

return str;
} catch (error) {
if (renderer && renderer.onError) {
let res = renderer.onError(error, vnode, (child) =>
_renderToString(
child,
context,
isSvgMode,
selectValue,
vnode,
renderer
)
);
if (res !== undefined) return res;
}

return str;
let errorHook = options[CATCH_ERROR];
if (errorHook) errorHook(error, vnode);
return '';
}
}

// Serialize Element VNodes to HTML
Expand Down Expand Up @@ -341,7 +385,14 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
// recurse into this element VNode's children
let childSvgMode =
type === 'svg' || (type !== 'foreignObject' && isSvgMode);
html = _renderToString(children, context, childSvgMode, selectValue, vnode);
html = _renderToString(
children,
context,
childSvgMode,
selectValue,
vnode,
renderer
);
}

if (afterDiff) afterDiff(vnode);
Expand Down
38 changes: 38 additions & 0 deletions src/internal.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// import { VNode } from 'preact';

type VNode = import('preact').VNode;
type ComponentChildren = import('preact').ComponentChildren;

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) => 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;
}
6 changes: 5 additions & 1 deletion src/jsx.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export default function renderToStringPretty(
options?: Options
): string;

export function shallowRender(vnode: VNode, context?: any): string;
export function shallowRender(
vnode: VNode,
context?: any,
options?: Options
): string;

export default render;
6 changes: 2 additions & 4 deletions src/jsx.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import './polyfills';
import './lib/polyfills';
import renderToString from './pretty';
import { indent, encodeEntities } from './util';
import { indent, encodeEntities } from './lib/util';
import prettyFormat from 'pretty-format';

/** @typedef {import('preact').VNode} VNode */

// we have to patch in Array support, Possible issue in npm.im/pretty-format
let preactPlugin = {
test(object) {
Expand Down
Loading