From 9b62ab3724411042580bb5dc0c4d411e38c982ae Mon Sep 17 00:00:00 2001 From: Seyyed Morteza Moosavi Date: Sat, 16 Nov 2024 11:54:44 +0330 Subject: [PATCH] . --- index.ts | 1 + render.ts | 1 + src/index.ts | 5 - src/lib/CData.tsx | 7 - src/lib/Comment.tsx | 7 - src/lib/Fragment.tsx | 7 - src/lib/Ins.tsx | 6 - src/lib/builtin.ts | 17 +- src/lib/react-builtin.ts | 14 ++ src/lib/types.ts | 29 +++ src/react.ts | 89 +--------- src/react/CData.ts | 7 + src/react/Comment.ts | 7 + src/react/Fragment.ts | 7 + src/react/Ins.ts | 6 + src/render.test.tsx | 369 --------------------------------------- src/render.ts | 16 -- 17 files changed, 82 insertions(+), 513 deletions(-) create mode 100644 index.ts create mode 100644 render.ts delete mode 100644 src/index.ts delete mode 100644 src/lib/CData.tsx delete mode 100644 src/lib/Comment.tsx delete mode 100644 src/lib/Fragment.tsx delete mode 100644 src/lib/Ins.tsx create mode 100644 src/lib/react-builtin.ts create mode 100644 src/lib/types.ts create mode 100644 src/react/CData.ts create mode 100644 src/react/Comment.ts create mode 100644 src/react/Fragment.ts create mode 100644 src/react/Ins.ts delete mode 100644 src/render.test.tsx delete mode 100644 src/render.ts diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..5392708 --- /dev/null +++ b/index.ts @@ -0,0 +1 @@ +export { render } from './render'; diff --git a/render.ts b/render.ts new file mode 100644 index 0000000..63688bf --- /dev/null +++ b/render.ts @@ -0,0 +1 @@ +export function render() {} diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index feb541e..0000000 --- a/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { render } from './render'; -export { Comment } from './lib/Comment'; -export { CData } from './lib/CData'; -export { Ins } from './lib/Ins'; -export { Fragment } from './lib/Fragment'; diff --git a/src/lib/CData.tsx b/src/lib/CData.tsx deleted file mode 100644 index 8e0b8e4..0000000 --- a/src/lib/CData.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { createJsxXmlBuiltinElement } from './builtin'; -import { joinTextChildren, TextChildren } from './join'; - -export function CData(props: { children: TextChildren }) { - const children = joinTextChildren(props.children); - return createJsxXmlBuiltinElement('cdata', { children }); -} diff --git a/src/lib/Comment.tsx b/src/lib/Comment.tsx deleted file mode 100644 index 209b989..0000000 --- a/src/lib/Comment.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { createJsxXmlBuiltinElement } from './builtin'; -import { joinTextChildren, TextChildren } from './join'; - -export function Comment(props: { children: TextChildren }) { - const children = joinTextChildren(props.children); - return createJsxXmlBuiltinElement('comment', { children }); -} diff --git a/src/lib/Fragment.tsx b/src/lib/Fragment.tsx deleted file mode 100644 index 6801894..0000000 --- a/src/lib/Fragment.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { ReactNode } from 'react'; -import { createJsxXmlBuiltinElement } from './builtin'; - -export function Fragment(props: { children?: ReactNode }) { - const { children } = props; - return createJsxXmlBuiltinElement('fragment', { children }); -} diff --git a/src/lib/Ins.tsx b/src/lib/Ins.tsx deleted file mode 100644 index bd54eae..0000000 --- a/src/lib/Ins.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { createJsxXmlBuiltinElement } from './builtin'; - -export function Ins(props: { target: string; content?: string }) { - const { target, content = '' } = props; - return createJsxXmlBuiltinElement('ins', { target, content }); -} diff --git a/src/lib/builtin.ts b/src/lib/builtin.ts index a038aa9..165055c 100644 --- a/src/lib/builtin.ts +++ b/src/lib/builtin.ts @@ -1,20 +1,15 @@ -import { type ReactElement } from 'react'; - -export const JsxXmlBuiltin = Symbol('JSXXML.Builtin'); - -export function isJsxXmlBuiltinElement(value: any) { - return value && value.$$typeof === JsxXmlBuiltin; -} +import { JsxXML, JsxXmlBuiltinElement } from './types'; export function createJsxXmlBuiltinElement( type: string, props: any, -): ReactElement { + children: any, +): JsxXmlBuiltinElement { return { - // @ts-ignore - $$typeof: JsxXmlBuiltin, + $$typeof: JsxXML, + builtin: true, type, props, - key: null, + children, }; } diff --git a/src/lib/react-builtin.ts b/src/lib/react-builtin.ts new file mode 100644 index 0000000..29edb6a --- /dev/null +++ b/src/lib/react-builtin.ts @@ -0,0 +1,14 @@ +import { ReactElement } from 'react'; +import { createJsxXmlBuiltinElement } from './builtin'; + +export function createReactJsxXmlBuiltinElement( + type: string, + props: any, + children?: any, +): ReactElement { + return createJsxXmlBuiltinElement( + type, + props, + children, + ) as any as ReactElement; +} diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..c7ba65b --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,29 @@ +export const JsxXML = Symbol('JSXXML'); +export type JsxXMLElement = + | JsxXmlComponentElement + | JsxXmlTagElement + | JsxXmlBuiltinElement; + +export type JsxXmlBuiltinElement = { + $$typeof: typeof JsxXML; + builtin: true; + type: string; + props: any; + children: any; +}; + +export type JsxXmlTagElement = { + $$typeof: typeof JsxXML; + builtin: false; + type: string; + props: any; + children: any; +}; + +export type JsxXmlComponentElement = { + $$typeof: typeof JsxXML; + builtin: false; + type: (props: any) => JsxXMLElement; + props: any; + children: any; +}; diff --git a/src/react.ts b/src/react.ts index fbbbe45..ff983ec 100644 --- a/src/react.ts +++ b/src/react.ts @@ -1,85 +1,4 @@ -import type { ReactElement } from 'react'; -import { isFragment } from 'react-is'; -import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces'; -import { isJsxXmlBuiltinElement } from './lib/builtin'; - -export function renderReactElement(cur: XMLBuilder, element: ReactElement) { - renderTag(cur, element); - return cur; -} - -function renderTag(cur: XMLBuilder, element: ReactElement) { - if (isJsxXmlBuiltinElement(element)) { - renderBuiltinElement(cur, element); - return; - } - if (typeof element?.type === 'string') { - let el = cur.ele(element.type); - const attrs = mergeAttrs( - element.props, - element.key, - // @ts-ignore - element.ref, - ); - renderAttrs(el, attrs); - renderChildren(el, element.props.children); - return; - } - if (typeof element?.type === 'function') { - if (element.type.prototype?.isReactComponent) { - throw new Error('Class components are not supported'); - } - const attrs = mergeAttrs( - element.props, - element.key, - // @ts-ignore - element.ref, - ); - // @ts-ignore - renderTag(cur, element.type(attrs)); - } - if (isFragment(element)) { - renderChildren(cur, element.props.children); - } -} - -function mergeAttrs(attrs: any, key: string | null, ref: string | null) { - return { key, ref, ...attrs }; -} - -function renderAttrs(cur: XMLBuilder, attrs: any) { - for (let key in attrs) { - if (key === 'children') { - continue; - } - cur.att(key, attrs[key]); - } -} - -function renderChildren(cur: XMLBuilder, children: any) { - if (typeof children === 'string') { - cur.txt(children); - } else if (typeof children === 'number') { - cur.txt(children.toString()); - } else if (Array.isArray(children)) { - children.forEach((child) => renderChildren(cur, child)); - } else if (children) { - renderTag(cur, children); - } -} - -export function renderBuiltinElement(cur: XMLBuilder, element: any) { - if (element.type === 'cdata') { - cur.dat(element.props.children); - } - if (element.type === 'comment') { - cur.com(element.props.children); - } - if (element.type === 'ins') { - cur.ins(element.props.target, element.props.content); - } - if (element.type === 'fragment') { - return renderChildren(cur, element.props.children); - } - return cur; -} +export { CData } from './react/CData'; +export { Comment } from './react/Comment'; +export { Fragment } from './react/Fragment'; +export { Ins } from './react/Ins'; diff --git a/src/react/CData.ts b/src/react/CData.ts new file mode 100644 index 0000000..4486592 --- /dev/null +++ b/src/react/CData.ts @@ -0,0 +1,7 @@ +import { joinTextChildren, TextChildren } from '../lib/join'; +import { createReactJsxXmlBuiltinElement } from '../lib/react-builtin'; + +export function CData(props: { children: TextChildren }) { + const children = joinTextChildren(props.children); + return createReactJsxXmlBuiltinElement('cdata', {}, children); +} diff --git a/src/react/Comment.ts b/src/react/Comment.ts new file mode 100644 index 0000000..c809b16 --- /dev/null +++ b/src/react/Comment.ts @@ -0,0 +1,7 @@ +import { joinTextChildren, TextChildren } from '../lib/join'; +import { createReactJsxXmlBuiltinElement } from '../lib/react-builtin'; + +export function Comment(props: { children: TextChildren }) { + const children = joinTextChildren(props.children); + return createReactJsxXmlBuiltinElement('comment', {}, children); +} diff --git a/src/react/Fragment.ts b/src/react/Fragment.ts new file mode 100644 index 0000000..e71442f --- /dev/null +++ b/src/react/Fragment.ts @@ -0,0 +1,7 @@ +import { ReactNode } from 'react'; +import { createReactJsxXmlBuiltinElement } from '../lib/react-builtin'; + +export function Fragment(props: { children?: ReactNode }) { + const { children } = props; + return createReactJsxXmlBuiltinElement('fragment', {}, children); +} diff --git a/src/react/Ins.ts b/src/react/Ins.ts new file mode 100644 index 0000000..ad1b37f --- /dev/null +++ b/src/react/Ins.ts @@ -0,0 +1,6 @@ +import { createReactJsxXmlBuiltinElement } from '../lib/react-builtin'; + +export function Ins(props: { target: string; content?: string }) { + const { target, content = '' } = props; + return createReactJsxXmlBuiltinElement('ins', { target, content }); +} diff --git a/src/render.test.tsx b/src/render.test.tsx deleted file mode 100644 index 77f0ba9..0000000 --- a/src/render.test.tsx +++ /dev/null @@ -1,369 +0,0 @@ -import React from 'react'; -import { describe, expect, test } from 'vitest'; -import { CData, Comment, Fragment, Ins } from './index'; -import { render } from './render'; - -declare global { - namespace JSX { - interface IntrinsicElements { - test: any; - item: any; - root: any; - 'h:test': any; - 'h:item': any; - 'x:test': any; - 'x:item': any; - } - } -} - -describe('render', () => { - describe('errors', () => { - test('string as element', () => { - expect(() => render('other')).throws(); - }); - test('class component', () => { - class MyComponent extends React.Component { - render() { - return ; - } - } - expect(() => render()).throws( - 'Class components are not supported', - ); - }); - }); - describe('tag', () => { - test('render self-closing tag', () => { - expect(render().end({ headless: true })).toBe(''); - }); - test('render tag', () => { - expect(render(item).end({ headless: true })).toBe( - 'item', - ); - }); - test('render multiple child', () => { - expect(render(item {2}).end({ headless: true })).toBe( - 'item 2', - ); - }); - test('render nested tags', () => { - expect( - render( - - - , - ).end({ headless: true }), - ).toBe(''); - }); - test('render nested tags with text', () => { - expect( - render( - - text - , - ).end({ headless: true }), - ).toBe('text'); - }); - test('render nested tags mixed with text', () => { - expect( - render( - - the 2. - , - ).end({ headless: true }), - ).toBe('the 2.'); - }); - test('render with children array', () => { - expect( - render({['item', ]}).end({ headless: true }), - ).toBe('item'); - }); - test('render with nested children array', () => { - expect( - render({['item', , ['nested', ]]}).end({ - headless: true, - }), - ).toBe('itemnested'); - }); - }); - describe('render attrs', () => { - test('render with attr', () => { - expect(render().end({ headless: true })).toBe( - '', - ); - }); - - test('render with attr number', () => { - expect(render().end({ headless: true })).toBe( - '', - ); - }); - - test('render with children', () => { - expect(render(item).end({ headless: true })).toBe( - 'item', - ); - }); - - test('render with multiple attr', () => { - expect(render().end({ headless: true })).toBe( - '', - ); - expect(render().end({ headless: true })).toBe( - '', - ); - }); - - test('render with key', () => { - expect(render().end({ headless: true })).toBe( - '', - ); - }); - test('render with ref', () => { - expect(render().end({ headless: true })).toBe( - '', - ); - }); - test.skip('render with key and attr', () => { - expect( - render().end({ headless: true }), - ).toBe(''); - expect( - render().end({ headless: true }), - ).toBe(''); - }); - test('render with attr in children', () => { - expect( - render( - - - , - ).end({ headless: true }), - ).toBe(''); - }); - }); - describe('component', () => { - test('render component', () => { - const Test = ({ id }: { id: string }) => ; - expect(render().end({ headless: true })).toBe( - '', - ); - }); - - test('render component with children', () => { - const Test = ({ id }: { id: string }) => item; - expect(render().end({ headless: true })).toBe( - 'item', - ); - }); - test('render component with children array', () => { - const Test = ({ id }: { id: string }) => ( - {['item', 'item2']} - ); - expect(render().end({ headless: true })).toBe( - 'itemitem2', - ); - }); - - test('render component with nested tags', () => { - const Item = () => ; - const Test = ({ id }: { id: string }) => ( - - - - ); - expect(render().end({ headless: true })).toBe( - '', - ); - }); - test('render component with nested component', () => { - const Item = () => ; - const Test = ({ id }: { id: string }) => ( - - - - ); - const Outer = () => ; - expect(render().end({ headless: true })).toBe( - '', - ); - }); - }); - describe('fragment', () => { - test('render fragment', () => { - expect( - render( - <> - - , - ).end({ headless: true }), - ).toBe(''); - }); - test('render fragment with children', () => { - expect( - render( - - <> - test - - - , - ).end({ headless: true }), - ).toBe('test'); - }); - }); - describe('namespace', () => { - test('render with namespace', () => { - expect( - render( - - - - - - - - , - ).end({ headless: true }), - ).toBe( - '', - ); - }); - test('render attr whith namespace', () => { - expect( - render( - - - - - , - ).end({ headless: true }), - ).toBe( - '', - ); - }); - }); - describe('builtin fragment', () => { - test('empty', () => { - expect( - render( - - - , - ).end({ headless: true }), - ).toBe(''); - }); - test('one child', () => { - expect( - render( - - - - - , - ).end({ headless: true }), - ).toBe(''); - }); - test('multiple children', () => { - expect( - render( - - - test - - - , - ).end({ headless: true }), - ).toBe('test'); - }); - test('at root', () => { - expect( - render( - - - - test - - - , - ).end({ headless: true }), - ).toBe('test'); - }); - }); - describe('builtin', () => { - test('render cdata', () => { - const data = 'item'; - expect( - render( - - {data} - , - ).end({ headless: true }), - ).toBe('item]]>'); - }); - test('render cdata multiple children', () => { - const data = 'item'; - expect( - render( - - data is: {data} - , - ).end({ headless: true }), - ).toBe('item]]>'); - }); - test('render comment', () => { - const data = 'comment'; - expect( - render( - - {data} - , - ).end({ headless: true }), - ).toBe(''); - }); - test('render comment multiple children', () => { - const data = 'the comment'; - expect( - render( - - comment is: {data} - , - ).end({ headless: true }), - ).toBe(''); - }); - test('render ins', () => { - expect( - render( - - - - text - , - ).end({ headless: true }), - ).toBe('text'); - }); - }); - describe('customize', () => { - test('default', () => { - expect(render().end()).toBe(''); - }); - test('doctype', () => { - expect( - render() - .dtd() - .end(), - ).toBe(''); - }); - test('declaration', () => { - expect( - render() - .dec({ standalone: true }) - .end(), - ).toBe(''); - }); - }); -}); diff --git a/src/render.ts b/src/render.ts deleted file mode 100644 index 1cae092..0000000 --- a/src/render.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ReactElement } from 'react'; -import { create } from 'xmlbuilder2'; -import { isElement } from 'react-is'; - -import type { XMLBuilderCreateOptions } from 'xmlbuilder2/lib/interfaces'; -import { renderReactElement } from './react'; -export type { XMLBuilderCreateOptions, ReactElement }; - -export function render(element: any, options?: XMLBuilderCreateOptions) { - let cur = create(options ?? {}); - - if (isElement(element)) { - return renderReactElement(cur, element); - } - throw new Error('Not implemented'); -}