melina 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/melina +0 -0
- package/package.json +75 -75
- package/src/client.ts +154 -21
- package/src/jsx-dom.ts +96 -0
- package/src/loader.ts +27 -116
- package/src/runtime.ts +108 -86
- package/src/web.ts +137 -322
package/bin/melina
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "melina",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "A lightweight, islands-architecture web framework for Bun with Next.js-style routing.",
|
|
5
|
-
"module": "./src/web.ts",
|
|
6
|
-
"main": "./src/web.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"melina": "./bin/melina"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"src",
|
|
12
|
-
"bin",
|
|
13
|
-
"docs",
|
|
14
|
-
"GUIDE.md",
|
|
15
|
-
"README.md",
|
|
16
|
-
"CHANGELOG.md",
|
|
17
|
-
"CONTRIBUTING.md",
|
|
18
|
-
"ARCHITECTURE.md"
|
|
19
|
-
],
|
|
20
|
-
"exports": {
|
|
21
|
-
".": "./src/web.ts",
|
|
22
|
-
"./web": "./src/web.ts",
|
|
23
|
-
"./Link": "./src/Link.tsx",
|
|
24
|
-
"./island": "./src/island.ts",
|
|
25
|
-
"./client": "./src/client.ts",
|
|
26
|
-
"./navigation": "./src/runtime/navigation.tsx",
|
|
27
|
-
"./client/jsx-runtime": "./src/client.ts",
|
|
28
|
-
"./client/jsx-dev-runtime": "./src/client.ts"
|
|
29
|
-
},
|
|
30
|
-
"repository": {
|
|
31
|
-
"type": "git",
|
|
32
|
-
"url": "https://github.com/mements/melina.js"
|
|
33
|
-
},
|
|
34
|
-
"author": {
|
|
35
|
-
"name": "Mements Team"
|
|
36
|
-
},
|
|
37
|
-
"bugs": "https://github.com/mements/melina.js/issues",
|
|
38
|
-
"homepage": "https://github.com/mements/melina.js#readme",
|
|
39
|
-
"license": "MIT",
|
|
40
|
-
"keywords": [
|
|
41
|
-
"bun",
|
|
42
|
-
"server",
|
|
43
|
-
"framework",
|
|
44
|
-
"typescript",
|
|
45
|
-
"web",
|
|
46
|
-
"ssr",
|
|
47
|
-
"islands",
|
|
48
|
-
"islands-architecture",
|
|
49
|
-
"hydration",
|
|
50
|
-
"nextjs",
|
|
51
|
-
"app-router",
|
|
52
|
-
"react",
|
|
53
|
-
"partial-hydration"
|
|
54
|
-
],
|
|
55
|
-
"type": "module",
|
|
56
|
-
"private": false,
|
|
57
|
-
"sideEffects": false,
|
|
58
|
-
"devDependencies": {
|
|
59
|
-
"@types/bun": "latest",
|
|
60
|
-
"@types/react": "^19.2.10",
|
|
61
|
-
"@types/react-dom": "^19.2.3"
|
|
62
|
-
},
|
|
63
|
-
"dependencies": {
|
|
64
|
-
"@ments/utils": "^1.2.1",
|
|
65
|
-
"@modelcontextprotocol/sdk": "^1.11.0",
|
|
66
|
-
"@solana/kit": "^2.1.0",
|
|
67
|
-
"@tailwindcss/postcss": "^4.1.10",
|
|
68
|
-
"autoprefixer": "^10.4.21",
|
|
69
|
-
"postcss": "^8.5.6",
|
|
70
|
-
"react": "^19.1.1",
|
|
71
|
-
"react-dom": "^19.1.1",
|
|
72
|
-
"ts-dedent": "^2.2.0",
|
|
73
|
-
"html-react-parser": "^5.2.5",
|
|
74
|
-
"zod": "^3.24.4"
|
|
75
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "melina",
|
|
3
|
+
"version": "1.1.3",
|
|
4
|
+
"description": "A lightweight, islands-architecture web framework for Bun with Next.js-style routing.",
|
|
5
|
+
"module": "./src/web.ts",
|
|
6
|
+
"main": "./src/web.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"melina": "./bin/melina"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"bin",
|
|
13
|
+
"docs",
|
|
14
|
+
"GUIDE.md",
|
|
15
|
+
"README.md",
|
|
16
|
+
"CHANGELOG.md",
|
|
17
|
+
"CONTRIBUTING.md",
|
|
18
|
+
"ARCHITECTURE.md"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./src/web.ts",
|
|
22
|
+
"./web": "./src/web.ts",
|
|
23
|
+
"./Link": "./src/Link.tsx",
|
|
24
|
+
"./island": "./src/island.ts",
|
|
25
|
+
"./client": "./src/client.ts",
|
|
26
|
+
"./navigation": "./src/runtime/navigation.tsx",
|
|
27
|
+
"./client/jsx-runtime": "./src/client.ts",
|
|
28
|
+
"./client/jsx-dev-runtime": "./src/client.ts"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/mements/melina.js"
|
|
33
|
+
},
|
|
34
|
+
"author": {
|
|
35
|
+
"name": "Mements Team"
|
|
36
|
+
},
|
|
37
|
+
"bugs": "https://github.com/mements/melina.js/issues",
|
|
38
|
+
"homepage": "https://github.com/mements/melina.js#readme",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"keywords": [
|
|
41
|
+
"bun",
|
|
42
|
+
"server",
|
|
43
|
+
"framework",
|
|
44
|
+
"typescript",
|
|
45
|
+
"web",
|
|
46
|
+
"ssr",
|
|
47
|
+
"islands",
|
|
48
|
+
"islands-architecture",
|
|
49
|
+
"hydration",
|
|
50
|
+
"nextjs",
|
|
51
|
+
"app-router",
|
|
52
|
+
"react",
|
|
53
|
+
"partial-hydration"
|
|
54
|
+
],
|
|
55
|
+
"type": "module",
|
|
56
|
+
"private": false,
|
|
57
|
+
"sideEffects": false,
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/bun": "latest",
|
|
60
|
+
"@types/react": "^19.2.10",
|
|
61
|
+
"@types/react-dom": "^19.2.3"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"@ments/utils": "^1.2.1",
|
|
65
|
+
"@modelcontextprotocol/sdk": "^1.11.0",
|
|
66
|
+
"@solana/kit": "^2.1.0",
|
|
67
|
+
"@tailwindcss/postcss": "^4.1.10",
|
|
68
|
+
"autoprefixer": "^10.4.21",
|
|
69
|
+
"postcss": "^8.5.6",
|
|
70
|
+
"react": "^19.1.1",
|
|
71
|
+
"react-dom": "^19.1.1",
|
|
72
|
+
"ts-dedent": "^2.2.0",
|
|
73
|
+
"html-react-parser": "^5.2.5",
|
|
74
|
+
"zod": "^3.24.4"
|
|
75
|
+
}
|
|
76
76
|
}
|
package/src/client.ts
CHANGED
|
@@ -683,6 +683,115 @@ export function jsxDEV(
|
|
|
683
683
|
};
|
|
684
684
|
}
|
|
685
685
|
|
|
686
|
+
// =============================================================================
|
|
687
|
+
// SERVER-SIDE RENDERING
|
|
688
|
+
// =============================================================================
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Render a VNode tree to an HTML string (SSR)
|
|
692
|
+
* This is the React-free equivalent of ReactDOMServer.renderToString()
|
|
693
|
+
*/
|
|
694
|
+
export function renderToString(vnode: VNode | string | number | boolean | null | undefined): string {
|
|
695
|
+
if (vnode == null || typeof vnode === 'boolean') {
|
|
696
|
+
return '';
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (typeof vnode === 'string') {
|
|
700
|
+
return escapeHtml(vnode);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (typeof vnode === 'number') {
|
|
704
|
+
return String(vnode);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const { type, props } = vnode;
|
|
708
|
+
|
|
709
|
+
// Handle Fragment
|
|
710
|
+
if (type === Fragment) {
|
|
711
|
+
return renderChildren(props.children);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Handle function components
|
|
715
|
+
if (typeof type === 'function') {
|
|
716
|
+
const result = type(props);
|
|
717
|
+
return renderToString(result);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Handle HTML elements
|
|
721
|
+
const tagName = type as string;
|
|
722
|
+
const attrs = renderAttributes(props);
|
|
723
|
+
const children = renderChildren(props.children);
|
|
724
|
+
|
|
725
|
+
// Void elements (self-closing)
|
|
726
|
+
const voidElements = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
|
|
727
|
+
if (voidElements.includes(tagName)) {
|
|
728
|
+
return `<${tagName}${attrs}>`;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return `<${tagName}${attrs}>${children}</${tagName}>`;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function escapeHtml(str: string): string {
|
|
735
|
+
return str
|
|
736
|
+
.replace(/&/g, '&')
|
|
737
|
+
.replace(/</g, '<')
|
|
738
|
+
.replace(/>/g, '>')
|
|
739
|
+
.replace(/"/g, '"')
|
|
740
|
+
.replace(/'/g, ''');
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function renderAttributes(props: Props): string {
|
|
744
|
+
const attrs: string[] = [];
|
|
745
|
+
|
|
746
|
+
for (const [key, value] of Object.entries(props)) {
|
|
747
|
+
if (key === 'children' || key === 'key' || key === 'ref') continue;
|
|
748
|
+
if (value == null || value === false) continue;
|
|
749
|
+
|
|
750
|
+
// Handle className -> class
|
|
751
|
+
const attrName = key === 'className' ? 'class' : key;
|
|
752
|
+
|
|
753
|
+
// Handle style object
|
|
754
|
+
if (key === 'style' && typeof value === 'object') {
|
|
755
|
+
const styleStr = Object.entries(value)
|
|
756
|
+
.map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}:${v}`)
|
|
757
|
+
.join(';');
|
|
758
|
+
attrs.push(`style="${escapeHtml(styleStr)}"`);
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Handle event handlers (skip on server)
|
|
763
|
+
if (key.startsWith('on') && typeof value === 'function') continue;
|
|
764
|
+
|
|
765
|
+
// Handle dangerouslySetInnerHTML (handled in children)
|
|
766
|
+
if (key === 'dangerouslySetInnerHTML') continue;
|
|
767
|
+
|
|
768
|
+
// Boolean attributes
|
|
769
|
+
if (value === true) {
|
|
770
|
+
attrs.push(attrName);
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
attrs.push(`${attrName}="${escapeHtml(String(value))}"`);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return attrs.length > 0 ? ' ' + attrs.join(' ') : '';
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function renderChildren(children: Child | Child[] | undefined): string {
|
|
781
|
+
if (children == null) return '';
|
|
782
|
+
|
|
783
|
+
// Handle dangerouslySetInnerHTML
|
|
784
|
+
if (typeof children === 'object' && '__html' in (children as any)) {
|
|
785
|
+
return (children as any).__html;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (Array.isArray(children)) {
|
|
789
|
+
return children.map(child => renderToString(child as VNode)).join('');
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return renderToString(children as VNode);
|
|
793
|
+
}
|
|
794
|
+
|
|
686
795
|
// =============================================================================
|
|
687
796
|
// HOOKS
|
|
688
797
|
// =============================================================================
|
|
@@ -706,12 +815,15 @@ function getHook<T extends HookState>(initializer: () => T): T {
|
|
|
706
815
|
* useState - Reactive state
|
|
707
816
|
*/
|
|
708
817
|
export function useState<T>(initial: T | (() => T)): [T, (v: T | ((prev: T) => T)) => void] {
|
|
818
|
+
// Capture the fiber at hook creation time (during render)
|
|
819
|
+
const fiber = currentFiber!;
|
|
820
|
+
|
|
709
821
|
const hook = getHook(() => {
|
|
710
822
|
const value = typeof initial === 'function' ? (initial as () => T)() : initial;
|
|
711
823
|
return { type: 'state' as const, value, setter: null as any };
|
|
712
824
|
});
|
|
713
825
|
|
|
714
|
-
// Create setter that triggers re-render
|
|
826
|
+
// Create setter that triggers re-render using captured fiber
|
|
715
827
|
if (!hook.setter) {
|
|
716
828
|
hook.setter = (newValue: T | ((prev: T) => T)) => {
|
|
717
829
|
const next = typeof newValue === 'function'
|
|
@@ -720,7 +832,8 @@ export function useState<T>(initial: T | (() => T)): [T, (v: T | ((prev: T) => T
|
|
|
720
832
|
|
|
721
833
|
if (next !== hook.value) {
|
|
722
834
|
hook.value = next;
|
|
723
|
-
|
|
835
|
+
// Use captured fiber, not currentFiber (which is null outside render)
|
|
836
|
+
scheduleUpdate(fiber);
|
|
724
837
|
}
|
|
725
838
|
};
|
|
726
839
|
}
|
|
@@ -780,14 +893,41 @@ export function useCallback<T extends Function>(fn: T, deps: any[]): T {
|
|
|
780
893
|
// =============================================================================
|
|
781
894
|
|
|
782
895
|
function scheduleUpdate(fiber: Fiber) {
|
|
783
|
-
// Find root fiber
|
|
784
|
-
let root = fiber;
|
|
785
|
-
while (root.parent) root = root.parent;
|
|
786
|
-
|
|
787
896
|
// Schedule microtask re-render
|
|
788
897
|
queueMicrotask(() => {
|
|
789
|
-
if (fiber.vnode) {
|
|
790
|
-
|
|
898
|
+
if (fiber.vnode && typeof fiber.vnode.type === 'function') {
|
|
899
|
+
// This is a component fiber - re-render it properly
|
|
900
|
+
const prevFiber = currentFiber;
|
|
901
|
+
currentFiber = fiber;
|
|
902
|
+
fiber.hookIndex = 0; // Reset hook index for re-render
|
|
903
|
+
|
|
904
|
+
// Re-run the component function (hooks will reuse stored state)
|
|
905
|
+
const result = (fiber.vnode.type as any)(fiber.vnode.props);
|
|
906
|
+
|
|
907
|
+
currentFiber = prevFiber;
|
|
908
|
+
|
|
909
|
+
// Update the DOM with new result
|
|
910
|
+
if (fiber.node && fiber.node.parentNode) {
|
|
911
|
+
const container = fiber.node.parentNode as HTMLElement;
|
|
912
|
+
|
|
913
|
+
// Create new DOM from result
|
|
914
|
+
const newFiber: Fiber = {
|
|
915
|
+
node: null,
|
|
916
|
+
vnode: result,
|
|
917
|
+
hooks: [],
|
|
918
|
+
hookIndex: 0,
|
|
919
|
+
parent: fiber.parent,
|
|
920
|
+
children: [],
|
|
921
|
+
cleanup: [],
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
const newNode = createNode(result, newFiber);
|
|
925
|
+
if (newNode) {
|
|
926
|
+
container.replaceChild(newNode, fiber.node);
|
|
927
|
+
fiber.node = newNode as HTMLElement;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
791
931
|
runEffects();
|
|
792
932
|
}
|
|
793
933
|
});
|
|
@@ -1204,15 +1344,11 @@ async function loadComponent(name: string): Promise<Component<any> | null> {
|
|
|
1204
1344
|
|
|
1205
1345
|
/**
|
|
1206
1346
|
* Hydrate all islands in the current DOM
|
|
1207
|
-
* Uses
|
|
1347
|
+
* Uses melina/client's render function (React-free!)
|
|
1208
1348
|
*/
|
|
1209
1349
|
export async function hydrateIslands(): Promise<void> {
|
|
1210
1350
|
initHangar();
|
|
1211
1351
|
|
|
1212
|
-
// Dynamic import of React and ReactDOM for island hydration
|
|
1213
|
-
const React = await import('react');
|
|
1214
|
-
const ReactDOM = await import('react-dom/client');
|
|
1215
|
-
|
|
1216
1352
|
const placeholders = document.querySelectorAll('[data-melina-island]');
|
|
1217
1353
|
const seenIds = new Set<string>();
|
|
1218
1354
|
|
|
@@ -1244,22 +1380,19 @@ export async function hydrateIslands(): Promise<void> {
|
|
|
1244
1380
|
storageNode.setAttribute('data-storage', instanceId);
|
|
1245
1381
|
el.appendChild(storageNode);
|
|
1246
1382
|
|
|
1247
|
-
// Render component using
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
// Store root for cleanup and re-rendering
|
|
1252
|
-
(storageNode as any).__reactRoot = root;
|
|
1383
|
+
// Render component using melina's render function (React-free!)
|
|
1384
|
+
const vnode = createElement(Component as any, props);
|
|
1385
|
+
render(vnode, storageNode);
|
|
1253
1386
|
|
|
1254
1387
|
islandRegistry.set(instanceId, {
|
|
1255
1388
|
name,
|
|
1256
1389
|
Component,
|
|
1257
1390
|
props,
|
|
1258
|
-
fiber: null as any,
|
|
1391
|
+
fiber: null as any,
|
|
1259
1392
|
storageNode,
|
|
1260
1393
|
});
|
|
1261
1394
|
|
|
1262
|
-
console.log('[Melina Client]
|
|
1395
|
+
console.log('[Melina Client] Hydrated island:', instanceId);
|
|
1263
1396
|
}
|
|
1264
1397
|
}
|
|
1265
1398
|
}
|
package/src/jsx-dom.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Melina.js JSX-to-DOM Runtime
|
|
3
|
+
*
|
|
4
|
+
* JSX in client.tsx files creates REAL DOM elements, not virtual DOM.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const el = <div class="toast"><span>Hello</span></div>;
|
|
8
|
+
* document.body.appendChild(el); // Works directly!
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type Child = Node | string | number | boolean | null | undefined | Child[];
|
|
12
|
+
|
|
13
|
+
export function jsx(
|
|
14
|
+
tag: string | ((props: any) => Node),
|
|
15
|
+
props: Record<string, any> | null,
|
|
16
|
+
...children: Child[]
|
|
17
|
+
): Node {
|
|
18
|
+
// Function component
|
|
19
|
+
if (typeof tag === 'function') {
|
|
20
|
+
const finalProps = { ...props };
|
|
21
|
+
// Use varargs children only if props.children is missing (Classic Runtime fallback)
|
|
22
|
+
if ((!props || props.children === undefined) && children.length > 0) {
|
|
23
|
+
finalProps.children = children.length === 1 ? children[0] : children;
|
|
24
|
+
}
|
|
25
|
+
return tag(finalProps);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const el = document.createElement(tag);
|
|
29
|
+
|
|
30
|
+
// Set attributes/properties
|
|
31
|
+
if (props) {
|
|
32
|
+
for (const [key, value] of Object.entries(props)) {
|
|
33
|
+
if (key === 'children') continue;
|
|
34
|
+
if (value === null || value === undefined || value === false) continue;
|
|
35
|
+
|
|
36
|
+
if (key === 'style' && typeof value === 'object') {
|
|
37
|
+
Object.assign(el.style, value);
|
|
38
|
+
} else if (key === 'className' || key === 'class') {
|
|
39
|
+
el.className = String(value);
|
|
40
|
+
} else if (key === 'htmlFor') {
|
|
41
|
+
el.setAttribute('for', String(value));
|
|
42
|
+
} else if (key === 'dangerouslySetInnerHTML') {
|
|
43
|
+
el.innerHTML = value.__html || '';
|
|
44
|
+
} else if (key.startsWith('on') && typeof value === 'function') {
|
|
45
|
+
// Event handlers: onClick -> click
|
|
46
|
+
const event = key.slice(2).toLowerCase();
|
|
47
|
+
el.addEventListener(event, value);
|
|
48
|
+
} else if (key === 'ref' && typeof value === 'function') {
|
|
49
|
+
value(el);
|
|
50
|
+
} else if (value === true) {
|
|
51
|
+
el.setAttribute(key, '');
|
|
52
|
+
} else {
|
|
53
|
+
el.setAttribute(key, String(value));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handle children passed as prop
|
|
58
|
+
// Handle children passed as prop (Automatic Runtime)
|
|
59
|
+
if (props.children !== undefined) {
|
|
60
|
+
appendChildren(el, Array.isArray(props.children) ? props.children : [props.children]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Append direct children (Classic Runtime fallback)
|
|
65
|
+
if ((!props || props.children === undefined) && children.length > 0) {
|
|
66
|
+
appendChildren(el, children);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return el;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function appendChildren(parent: Element, children: Child[]) {
|
|
73
|
+
for (const child of children) {
|
|
74
|
+
if (child === null || child === undefined || child === false || child === true) continue;
|
|
75
|
+
if (Array.isArray(child)) {
|
|
76
|
+
appendChildren(parent, child);
|
|
77
|
+
} else if (child instanceof Node) {
|
|
78
|
+
parent.appendChild(child);
|
|
79
|
+
} else {
|
|
80
|
+
parent.appendChild(document.createTextNode(String(child)));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// JSX runtime entry points (used by Bun/esbuild JSX transform)
|
|
86
|
+
export const jsxs = jsx;
|
|
87
|
+
export const jsxDEV = jsx;
|
|
88
|
+
export const Fragment = ({ children }: { children: Child | Child[] }) => {
|
|
89
|
+
const frag = document.createDocumentFragment();
|
|
90
|
+
if (children !== undefined) {
|
|
91
|
+
appendChildren(frag as any, Array.isArray(children) ? children : [children]);
|
|
92
|
+
}
|
|
93
|
+
return frag;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default { jsx, jsxs, jsxDEV, Fragment };
|