Skip to content

Commit 19ad362

Browse files
Added comments to converter
1 parent d2d23a8 commit 19ad362

File tree

3 files changed

+61
-35
lines changed

3 files changed

+61
-35
lines changed

.github/workflows/deploy-storybook.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Deploy Storybook
22

33
on:
44
push:
5-
branches: [mm-storybook-deployment]
5+
branches: [mm-storybook-deployment-stencil]
66
paths:
77
- 'src/**'
88
- '.storybook/**'

.storybook/jsxToHtml.ts

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,107 @@
1-
type VNodeStencil = {
1+
// Interface representing a Stencil VNode structure
2+
interface VNodeStencil {
23
$tag$?: string;
34
$attrs$?: Record<string, unknown> | null;
45
$children$?: Array<VNodeStencil | VNodeLike | string> | null;
56
$text$?: string | null;
6-
};
7+
}
78

8-
type VNodeLike = {
9+
// Interface representing a React-like VNode structure
10+
interface VNodeLike {
911
type?: string | ((...args: unknown[]) => unknown);
1012
props?: Record<string, unknown> & { children?: Array<VNodeLike | string> | VNodeLike | string };
1113
children?: Array<VNodeLike | string> | VNodeLike | string;
12-
};
14+
}
1315

14-
function escapeHtml(value: string): string {
15-
return value
16+
// Escapes HTML special characters to prevent XSS attacks
17+
const escapeHtml = (value: string): string =>
18+
value
1619
.replace(/&/g, '&amp;')
1720
.replace(/</g, '&lt;')
1821
.replace(/>/g, '&gt;')
1922
.replace(/"/g, '&quot;')
2023
.replace(/'/g, '&#39;');
21-
}
2224

23-
function attrsToString(attrs: Record<string, unknown> | null | undefined): string {
24-
if (!attrs) return '';
25+
// Converts attribute object to HTML attribute string
26+
const attrsToString = (attrs: Record<string, unknown> | null | undefined): string => {
27+
if (!attrs) {
28+
return '';
29+
}
30+
2531
return Object.entries(attrs)
2632
.filter(([, v]) => v !== undefined && v !== null && v !== false)
2733
.map(([k, v]) => (v === true ? k : `${k}="${escapeHtml(String(v))}"`))
2834
.join(' ');
29-
}
35+
};
36+
37+
// Normalizes children to a consistent array format
38+
const normalizeChildren = (children: unknown): Array<VNodeStencil | VNodeLike | string> => {
39+
if (children == null) {
40+
return [];
41+
}
42+
43+
if (Array.isArray(children)) {
44+
return children as Array<VNodeStencil | VNodeLike | string>;
45+
}
3046

31-
function normalizeChildren(children: unknown): Array<VNodeStencil | VNodeLike | string> {
32-
if (children == null) return [];
33-
if (Array.isArray(children)) return children as Array<VNodeStencil | VNodeLike | string>;
3447
return [children as VNodeStencil | VNodeLike | string];
35-
}
48+
};
3649

37-
function renderStencil(node: VNodeStencil): string {
50+
// Renders a Stencil VNode to HTML string
51+
const renderStencil = (node: VNodeStencil): string => {
3852
if (node.$text$ != null) {
3953
return escapeHtml(String(node.$text$));
4054
}
55+
4156
const tag = node.$tag$ || 'div';
4257
const attrs = attrsToString(node.$attrs$ || undefined);
4358
const children = (node.$children$ || []).map(renderAny).join('');
4459
const attrsStr = attrs ? ` ${attrs}` : '';
60+
4561
return `<${tag}${attrsStr}>${children}</${tag}>`;
46-
}
62+
};
4763

48-
function renderLike(node: VNodeLike): string {
64+
// Renders a React-like VNode to HTML string
65+
const renderLike = (node: VNodeLike): string => {
4966
const tag = typeof node.type === 'string' ? node.type : 'div';
5067
const props = node.props || {};
5168
const { children, ...rest } = props as Record<string, unknown> & { children?: unknown };
5269
const attrs = attrsToString(rest);
5370
const childrenArr = normalizeChildren(children ?? node.children);
5471
const childrenStr = childrenArr.map(renderAny).join('');
5572
const attrsStr = attrs ? ` ${attrs}` : '';
73+
5674
return `<${tag}${attrsStr}>${childrenStr}</${tag}>`;
57-
}
75+
};
5876

59-
export function renderJsxToHtml(node: unknown): string {
60-
return renderAny(node);
61-
}
77+
// Main export function that converts JSX to HTML string
78+
export const renderJsxToHtml = (node: unknown): string => renderAny(node);
79+
80+
// Renders any node type to HTML string with type detection
81+
const renderAny = (node: unknown): string => {
82+
if (node == null || node === false) {
83+
return '';
84+
}
85+
86+
if (typeof node === 'string' || typeof node === 'number') {
87+
return escapeHtml(String(node));
88+
}
6289

63-
function renderAny(node: unknown): string {
64-
if (node == null || node === false) return '';
65-
if (typeof node === 'string' || typeof node === 'number') return escapeHtml(String(node));
66-
// Heuristics for Stencil VNode
90+
// Heuristics for detecting node types
6791
if (typeof node === 'object') {
6892
const o = node as Record<string, unknown>;
93+
94+
// Check for Stencil VNode
6995
if ('$tag$' in o || '$text$' in o || '$children$' in o) {
7096
return renderStencil(node as VNodeStencil);
7197
}
98+
99+
// Check for React-like VNode
72100
if ('type' in o || 'props' in o || 'children' in o) {
73101
return renderLike(node as VNodeLike);
74102
}
75103
}
76-
// Fallback
104+
105+
// Fallback for unknown node types
77106
return escapeHtml(String(node));
78-
}
107+
};

.storybook/preview.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,12 @@ import './tailwind.css';
1212
defineCustomElements();
1313

1414
export const decorators: Preview['decorators'] = [
15-
(Story, context) => {
16-
const vnode = (
15+
(Story, context) =>
16+
renderJsxToHtml(
1717
<div data-mvx-theme={`mvx:${context.globals.backgrounds.value}-theme`}>
1818
<Story />
19-
</div>
20-
);
21-
// Convert Stencil JSX vnode to HTML string for web-components renderer
22-
return renderJsxToHtml(vnode);
23-
},
19+
</div>,
20+
),
2421
];
2522

2623
export const initialGlobals: Preview['initialGlobals'] = {

0 commit comments

Comments
 (0)