From 79a9c25563a87db38da47a241e193aeeb3035ca8 Mon Sep 17 00:00:00 2001
From: Garth Poitras <411908+gpoitch@users.noreply.github.com>
Date: Wed, 26 Jul 2023 10:45:26 -0400
Subject: [PATCH 1/2] Attribute hook render option
---
src/index.d.ts | 28 ++++++++++++++++++++++++----
src/index.js | 7 +++++--
test/render.test.js | 43 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 72 insertions(+), 6 deletions(-)
diff --git a/src/index.d.ts b/src/index.d.ts
index 113cbcb7..728d13dd 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -1,7 +1,27 @@
import { VNode } from 'preact';
-export default function renderToString(vnode: VNode, context?: any): string;
+interface RenderOptions {
+ attrHook?: (name: string) => string;
+}
-export function render(vnode: VNode, context?: any): string;
-export function renderToString(vnode: VNode, context?: any): string;
-export function renderToStaticMarkup(vnode: VNode, context?: any): string;
+export default function renderToString(
+ vnode: VNode,
+ context?: any,
+ renderOpts?: RenderOptions
+): string;
+
+export function render(
+ vnode: VNode,
+ context?: any,
+ renderOpts?: RenderOptions
+): string;
+export function renderToString(
+ vnode: VNode,
+ context?: any,
+ renderOpts?: RenderOptions
+): string;
+export function renderToStaticMarkup(
+ vnode: VNode,
+ context?: any,
+ renderOpts?: RenderOptions
+): string;
diff --git a/src/index.js b/src/index.js
index 45bb9bfc..4ce64d24 100644
--- a/src/index.js
+++ b/src/index.js
@@ -21,7 +21,7 @@ const isArray = Array.isArray;
const assign = Object.assign;
// Global state for the current render pass
-let beforeDiff, afterDiff, renderHook, ummountHook;
+let beforeDiff, afterDiff, renderHook, ummountHook, attrHook;
/**
* Render Preact JSX + Components to an HTML string.
@@ -29,7 +29,7 @@ let beforeDiff, afterDiff, renderHook, ummountHook;
* @param {Object} [context={}] Initial root context object
* @returns {string} serialized HTML
*/
-export function renderToString(vnode, context) {
+export function renderToString(vnode, context, renderOpts) {
// 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
@@ -43,6 +43,7 @@ export function renderToString(vnode, context) {
afterDiff = options[DIFFED];
renderHook = options[RENDER];
ummountHook = options.unmount;
+ attrHook = renderOpts?.attrHook;
const parent = h(Fragment, null);
parent[CHILDREN] = [vnode];
@@ -399,6 +400,8 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
}
}
+ if (attrHook) name = attrHook(name);
+
// write this attribute to the buffer
if (v != null && v !== false && typeof v !== 'function') {
if (v === true || v === '') {
diff --git a/test/render.test.js b/test/render.test.js
index bd264b6b..12946197 100644
--- a/test/render.test.js
+++ b/test/render.test.js
@@ -1598,4 +1598,47 @@ describe('render', () => {
});
});
});
+
+ describe('Render Options', () => {
+ describe('Attribute Hook', () => {
+ it('Transforms attributes with custom attrHook option', () => {
+ function attrHook(name) {
+ const DASHED_ATTRS = /^(acceptC|httpE|(clip|color|fill|font|glyph|marker|stop|stroke|text|vert)[A-Z])/;
+ const CAMEL_ATTRS = /^(isP|viewB)/;
+ const COLON_ATTRS = /^(xlink|xml|xmlns)([A-Z])/;
+ const CAPITAL_REGEXP = /([A-Z])/g;
+ if (CAMEL_ATTRS.test(name)) return name;
+ if (DASHED_ATTRS.test(name))
+ return name.replace(CAPITAL_REGEXP, '-$1').toLowerCase();
+ if (COLON_ATTRS.test(name))
+ return name.replace(CAPITAL_REGEXP, ':$1').toLowerCase();
+ return name.toLowerCase();
+ }
+
+ const content = (
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ const expected =
+ '
';
+
+ const rendered = render(content, {}, { attrHook });
+ expect(rendered).to.equal(expected);
+ });
+ });
+ });
});
From ed1913ec4c6e272ed985bd4e1e045af46157e1be Mon Sep 17 00:00:00 2001
From: Garth Poitras <411908+gpoitch@users.noreply.github.com>
Date: Wed, 26 Jul 2023 17:02:46 -0400
Subject: [PATCH 2/2] support pretty mode
---
jsx.d.ts | 1 +
src/index.d.ts | 18 +++++-------
src/index.js | 8 ++---
src/jsx.d.ts | 2 ++
src/jsx.js | 6 ++--
src/pretty.js | 8 +++--
test/pretty.test.js | 42 ++++++++++++++++++++++++++-
test/render.test.js | 71 ++++++++++++++++++++++-----------------------
8 files changed, 100 insertions(+), 56 deletions(-)
diff --git a/jsx.d.ts b/jsx.d.ts
index 90042308..25131e32 100644
--- a/jsx.d.ts
+++ b/jsx.d.ts
@@ -8,6 +8,7 @@ interface Options {
functions?: boolean;
functionNames?: boolean;
skipFalseAttributes?: boolean;
+ attributeHook?: (name: string) => string;
}
export default function renderToStringPretty(
diff --git a/src/index.d.ts b/src/index.d.ts
index 728d13dd..7a65cd7b 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -1,27 +1,25 @@
import { VNode } from 'preact';
-interface RenderOptions {
- attrHook?: (name: string) => string;
+interface Options {
+ attributeHook?: (name: string) => string;
}
export default function renderToString(
vnode: VNode,
context?: any,
- renderOpts?: RenderOptions
+ options?: Options
): string;
-export function render(
- vnode: VNode,
- context?: any,
- renderOpts?: RenderOptions
-): string;
+export function render(vnode: VNode, context?: any, options?: Options): string;
+
export function renderToString(
vnode: VNode,
context?: any,
- renderOpts?: RenderOptions
+ options?: Options
): string;
+
export function renderToStaticMarkup(
vnode: VNode,
context?: any,
- renderOpts?: RenderOptions
+ options?: Options
): string;
diff --git a/src/index.js b/src/index.js
index 4ce64d24..99ef1662 100644
--- a/src/index.js
+++ b/src/index.js
@@ -21,7 +21,7 @@ const isArray = Array.isArray;
const assign = Object.assign;
// Global state for the current render pass
-let beforeDiff, afterDiff, renderHook, ummountHook, attrHook;
+let beforeDiff, afterDiff, renderHook, ummountHook, attributeHook;
/**
* Render Preact JSX + Components to an HTML string.
@@ -29,7 +29,7 @@ let beforeDiff, afterDiff, renderHook, ummountHook, attrHook;
* @param {Object} [context={}] Initial root context object
* @returns {string} serialized HTML
*/
-export function renderToString(vnode, context, renderOpts) {
+export function renderToString(vnode, context, opts) {
// 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
@@ -43,7 +43,7 @@ export function renderToString(vnode, context, renderOpts) {
afterDiff = options[DIFFED];
renderHook = options[RENDER];
ummountHook = options.unmount;
- attrHook = renderOpts?.attrHook;
+ attributeHook = opts && opts.attributeHook;
const parent = h(Fragment, null);
parent[CHILDREN] = [vnode];
@@ -400,7 +400,7 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) {
}
}
- if (attrHook) name = attrHook(name);
+ if (attributeHook) name = attributeHook(name);
// write this attribute to the buffer
if (v != null && v !== false && typeof v !== 'function') {
diff --git a/src/jsx.d.ts b/src/jsx.d.ts
index 752f3e55..9b79cd4e 100644
--- a/src/jsx.d.ts
+++ b/src/jsx.d.ts
@@ -8,6 +8,7 @@ interface Options {
functions?: boolean;
functionNames?: boolean;
skipFalseAttributes?: boolean;
+ attributeHook?: (name: string) => string;
}
export default function renderToStringPretty(
@@ -15,6 +16,7 @@ export default function renderToStringPretty(
context?: any,
options?: Options
): string;
+
export function render(vnode: VNode, context?: any, options?: Options): string;
export function shallowRender(
diff --git a/src/jsx.js b/src/jsx.js
index 6007311a..c16e259b 100644
--- a/src/jsx.js
+++ b/src/jsx.js
@@ -25,7 +25,7 @@ let prettyFormatOpts = {
plugins: [preactPlugin]
};
-function attributeHook(name, value, context, opts, isComponent) {
+function jsxAttributeHook(name, value, context, opts, isComponent) {
let type = typeof value;
// Use render-to-string's built-in handling for these properties
@@ -60,7 +60,7 @@ function attributeHook(name, value, context, opts, isComponent) {
}
let defaultOpts = {
- attributeHook,
+ jsxAttributeHook,
jsx: true,
xml: false,
functions: true,
@@ -83,7 +83,7 @@ let defaultOpts = {
*/
export default function renderToStringPretty(vnode, context, options) {
const opts = Object.assign({}, defaultOpts, options || {});
- if (!opts.jsx) opts.attributeHook = null;
+ if (!opts.jsx || opts.attributeHook) opts.jsxAttributeHook = null;
return renderToString(vnode, context, opts);
}
export { renderToStringPretty as render };
diff --git a/src/pretty.js b/src/pretty.js
index 4e37d92c..7c0c00aa 100644
--- a/src/pretty.js
+++ b/src/pretty.js
@@ -209,6 +209,8 @@ function _renderToStringPretty(
propChildren,
html;
+ const attributeHook = opts && opts.attributeHook;
+
if (props) {
let attrs = Object.keys(props);
@@ -263,8 +265,8 @@ function _renderToStringPretty(
}
let hooked =
- opts.attributeHook &&
- opts.attributeHook(name, v, context, opts, isComponent);
+ opts.jsxAttributeHook &&
+ opts.jsxAttributeHook(name, v, context, opts, isComponent);
if (hooked || hooked === '') {
s = s + hooked;
continue;
@@ -280,6 +282,7 @@ function _renderToStringPretty(
v = name;
// in non-xml mode, allow boolean attributes
if (!opts || !opts.xml) {
+ if (attributeHook) name = attributeHook(name);
s = s + ' ' + name;
continue;
}
@@ -299,6 +302,7 @@ function _renderToStringPretty(
s = s + ` selected`;
}
}
+ if (attributeHook) name = attributeHook(name);
s = s + ` ${name}="${encodeEntities(v + '')}"`;
}
}
diff --git a/test/pretty.test.js b/test/pretty.test.js
index 7d6ecd1a..792e0f94 100644
--- a/test/pretty.test.js
+++ b/test/pretty.test.js
@@ -5,7 +5,7 @@ import { expect } from 'chai';
import { dedent } from './utils.js';
describe('pretty', () => {
- let prettyRender = (jsx) => render(jsx, {}, { pretty: true });
+ let prettyRender = (jsx, opts) => render(jsx, {}, { pretty: true, ...opts });
it('should render no whitespace by default', () => {
let rendered = basicRender(
@@ -196,4 +196,44 @@ describe('pretty', () => {
it('should not render function children', () => {
expect(prettyRender({() => {}}
)).to.equal('');
});
+
+ it('transforms attributes with custom attributeHook option', () => {
+ function attributeHook(name) {
+ const DASHED_ATTRS = /^(acceptC|httpE|(clip|color|fill|font|glyph|marker|stop|stroke|text|vert)[A-Z])/;
+ const CAMEL_ATTRS = /^(isP|viewB)/;
+ const COLON_ATTRS = /^(xlink|xml|xmlns)([A-Z])/;
+ const CAPITAL_REGEXP = /([A-Z])/g;
+ if (CAMEL_ATTRS.test(name)) return name;
+ if (DASHED_ATTRS.test(name))
+ return name.replace(CAPITAL_REGEXP, '-$1').toLowerCase();
+ if (COLON_ATTRS.test(name))
+ return name.replace(CAPITAL_REGEXP, ':$1').toLowerCase();
+ return name.toLowerCase();
+ }
+
+ const content = (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ const expected =
+ '\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\n\t\n\t\t
\n\t\t\n\t\n';
+
+ const rendered = prettyRender(content, { attributeHook });
+ expect(rendered).to.equal(expected);
+ });
});
diff --git a/test/render.test.js b/test/render.test.js
index 12946197..b8ad6578 100644
--- a/test/render.test.js
+++ b/test/render.test.js
@@ -1600,45 +1600,44 @@ describe('render', () => {
});
describe('Render Options', () => {
- describe('Attribute Hook', () => {
- it('Transforms attributes with custom attrHook option', () => {
- function attrHook(name) {
- const DASHED_ATTRS = /^(acceptC|httpE|(clip|color|fill|font|glyph|marker|stop|stroke|text|vert)[A-Z])/;
- const CAMEL_ATTRS = /^(isP|viewB)/;
- const COLON_ATTRS = /^(xlink|xml|xmlns)([A-Z])/;
- const CAPITAL_REGEXP = /([A-Z])/g;
- if (CAMEL_ATTRS.test(name)) return name;
- if (DASHED_ATTRS.test(name))
- return name.replace(CAPITAL_REGEXP, '-$1').toLowerCase();
- if (COLON_ATTRS.test(name))
- return name.replace(CAPITAL_REGEXP, ':$1').toLowerCase();
- return name.toLowerCase();
- }
+ it('transforms attributes with custom attributeHook option', () => {
+ function attributeHook(name) {
+ const DASHED_ATTRS = /^(acceptC|httpE|(clip|color|fill|font|glyph|marker|stop|stroke|text|vert)[A-Z])/;
+ const CAMEL_ATTRS = /^(isP|viewB)/;
+ const COLON_ATTRS = /^(xlink|xml|xmlns)([A-Z])/;
+ const CAPITAL_REGEXP = /([A-Z])/g;
+ if (CAMEL_ATTRS.test(name)) return name;
+ if (DASHED_ATTRS.test(name))
+ return name.replace(CAPITAL_REGEXP, '-$1').toLowerCase();
+ if (COLON_ATTRS.test(name))
+ return name.replace(CAPITAL_REGEXP, ':$1').toLowerCase();
+ return name.toLowerCase();
+ }
- const content = (
-
-
-
-
-
-
-
-
-
-
-
- );
+ const content = (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
- const expected =
- '
';
+ const expected =
+ '
';
- const rendered = render(content, {}, { attrHook });
- expect(rendered).to.equal(expected);
- });
+ const rendered = render(content, {}, { attributeHook });
+ expect(rendered).to.equal(expected);
});
});
});