!161 fix(no-vdom): fix render.test.tsx

* fix(no-vdom): fix render.test.tsx
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* fix(no-vdom): modify env test
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* fix(no-vdom): optimize dom.ts file
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* fix(no-vdom): modify $$style
* fix(no-vdom): modify test name
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* fix(no-vdom): modify event
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* fix(no-vdom): modify test code's name rule
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* fix(no-vdom): delete no-vnode module
* fix(no-vdom): update render function
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* Merge branch 'reactive' of https://gitee.com/openInula/inula into reactive
* Merge remote-tracking branch 'origin/reactive' into reactive
* fix(no-vdom): add TS
* fix(no-vdom): change js 2 ts
This commit is contained in:
陈超涛 2024-02-26 09:12:39 +00:00
parent 09c5522af3
commit 98ed1f2fc5
15 changed files with 171 additions and 390 deletions

View File

@ -85,10 +85,14 @@ function insertExpression(parent: Node, value: any, prevValue: any, marker?: Nod
} }
result = cleanChildren(parent, prevValue, marker, node); result = cleanChildren(parent, prevValue, marker, node);
} else { } else {
if (prevValue !== '' && typeof prevValue === 'string') { if (prevValue && prevValue.nodeType === 3) {
result = (parent.firstChild as Text).data = value; // result = (parent.firstChild as Text).data = value;
result = prevValue;
(prevValue as Text).data = value;
} else { } else {
result = parent.textContent = value; // result = parent.textContent = value;
result = document.createTextNode(value);
parent.appendChild(result);
} }
} }
} else if (value == null || t === 'boolean') { } else if (value == null || t === 'boolean') {
@ -315,7 +319,16 @@ export function setAttribute(node: Node, name: string, value: string | null): vo
} }
} }
export function className(node: Element, value: string | string[] | Record<string, boolean> | null): void { // 帮我实现一个完善的setProperty
export function setProperty(node: Element, name: string, value: any): void {
if (name in node) {
node[name] = value;
} else {
node.setAttribute(name, value);
}
}
export function setClassName(node: Element, value: string | string[] | Record<string, boolean> | null): void {
if (value == null) { if (value == null) {
node.removeAttribute('class'); node.removeAttribute('class');
} else { } else {
@ -341,9 +354,9 @@ export function className(node: Element, value: string | string[] | Record<strin
} }
} }
export const effect = watch; export { watch };
export function style( export function setStyle(
node: HTMLElement, node: HTMLElement,
value: Record<string, string> | string | null, value: Record<string, string> | string | null,
prevVal?: Record<string, string> | string prevVal?: Record<string, string> | string

View File

@ -18,7 +18,7 @@ import {
template as $$template, template as $$template,
insert as $$insert, insert as $$insert,
setAttribute as $$attr, setAttribute as $$attr,
effect as $$effect, watch as $$watch,
} from '../src/dom'; } from '../src/dom';
import { runComponent as $$runComponent, render } from '../src/core'; import { runComponent as $$runComponent, render } from '../src/core';
import { delegateEvents as $$delegateEvents, addEventListener as $$on } from '../src/event'; import { delegateEvents as $$delegateEvents, addEventListener as $$on } from '../src/event';
@ -179,7 +179,7 @@ bench('For', () => {
_el$4 = _el$3.firstChild; _el$4 = _el$3.firstChild;
$$on(_el$4, 'click', props.cb, true); $$on(_el$4, 'click', props.cb, true);
$$insert(_el$4, () => props.title); $$insert(_el$4, () => props.title);
$$effect(() => $$attr(_el$4, 'id', props.id)); $$watch(() => $$attr(_el$4, 'id', props.id));
return _el$3; return _el$3;
})(); })();
const Main = () => { const Main = () => {

View File

@ -20,7 +20,7 @@ import {
template as $$template, template as $$template,
insert as $$insert, insert as $$insert,
setAttribute as $$attr, setAttribute as $$attr,
effect as $$effect, watch as $$watch,
} from '../src/dom'; } from '../src/dom';
import { runComponent as $$runComponent, render } from '../src/core'; import { runComponent as $$runComponent, render } from '../src/core';
import { delegateEvents as $$delegateEvents, addEventListener as $$on } from '../src/event'; import { delegateEvents as $$delegateEvents, addEventListener as $$on } from '../src/event';
@ -178,7 +178,7 @@ describe('For', () => {
); );
}); });
it('使用effect, setAttribute, addEventListener', ({ container }) => { it('使用watch, setAttribute, addEventListener', ({ container }) => {
/** /**
* *
* const A = ['pretty', 'large', 'big', 'small', 'tall', 'short', 'long', 'handsome', 'plain', 'quaint', 'clean', * const A = ['pretty', 'large', 'big', 'small', 'tall', 'short', 'long', 'handsome', 'plain', 'quaint', 'clean',
@ -330,7 +330,7 @@ describe('For', () => {
_el$4 = _el$3.firstChild; _el$4 = _el$3.firstChild;
$$on(_el$4, 'click', props.cb, true); $$on(_el$4, 'click', props.cb, true);
$$insert(_el$4, () => props.title); $$insert(_el$4, () => props.title);
$$effect(() => $$attr(_el$4, 'id', props.id)); $$watch(() => $$attr(_el$4, 'id', props.id));
return _el$3; return _el$3;
})(); })();
const Main = () => { const Main = () => {

View File

@ -1,4 +1,4 @@
import { template, insert, setAttribute, className } from '../src/dom'; import { template, insert, setAttribute, setClassName } from '../src/dom';
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
describe('DOM manipulation functions', () => { describe('DOM manipulation functions', () => {
@ -23,13 +23,6 @@ describe('DOM manipulation functions', () => {
insert(parent, 'Test'); insert(parent, 'Test');
expect(parent.textContent).toBe('Test'); expect(parent.textContent).toBe('Test');
}); });
it('should replace existing content in the parent node', () => {
const parent = document.createElement('div');
parent.textContent = 'Old content';
insert(parent, 'New content');
expect(parent.textContent).toBe('New content');
});
}); });
describe('setAttribute function', () => { describe('setAttribute function', () => {
@ -50,14 +43,14 @@ describe('DOM manipulation functions', () => {
describe('className function', () => { describe('className function', () => {
it('should set the class of a node', () => { it('should set the class of a node', () => {
const node = document.createElement('div'); const node = document.createElement('div');
className(node, 'test'); setClassName(node, 'test');
expect(node.className).toBe('test'); expect(node.className).toBe('test');
}); });
it('should remove the class from a node if value is null', () => { it('should remove the class from a node if value is null', () => {
const node = document.createElement('div'); const node = document.createElement('div');
node.className = 'test'; node.className = 'test';
className(node, null); setClassName(node, null);
expect(node.className).toBe(''); expect(node.className).toBe('');
}); });
}); });

View File

@ -16,18 +16,8 @@
// @ts-nocheck For the compiled code. // @ts-nocheck For the compiled code.
import { reactive } from 'inula-reactive'; import { reactive } from 'inula-reactive';
import { import {
template as $$template, render
insert as $$insert, } from '@inula/no-vdom';
effect as $$effect,
style as $$style,
className as $$className,
setAttribute as $$attr,
createElement,
createText,
insert,
} from '../src/dom';
import { runComponent as $$runComponent, render, runComponent as createComponent } from '../src/core';
import { delegateEvents as $$delegateEvents, addEventListener as $$on, delegateEvent } from '../src/event';
import { describe, expect } from 'vitest'; import { describe, expect } from 'vitest';
import { domTest as it } from './utils'; import { domTest as it } from './utils';
@ -94,155 +84,63 @@ describe('render', () => {
container.querySelector('#btn').click(); container.querySelector('#btn').click();
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1.'); expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1 .');
}); });
it('should render components with slot', ({ container }) => { it('should render components with slot', ({ container }) => {
/** const CountValue = (props) => {
* return <div>Title: {props.children}</div>;
* const CountValue = (props) => {
* return <div>Title: {props.children}</div>;
* }
*
* const CountingComponent = () => {
* const [count, setCount] = createSignal(0);
* const add = () => {
* setCount((c) => c + 1);
* }
*
* return <div>
* <CountValue><h1>FOO</h1></CountValue>
* <div><button onClick={add}>add</button></div>
* </div>;
* };
*
* render(() => <CountingComponent />, document.getElementById("app"));
*/
// 编译后:
const _tmpl$ = /*#__PURE__*/ $$template(`<div>Title: `),
_tmpl$2 = /*#__PURE__*/ $$template(`<h1>Your count is <!>.`),
_tmpl$3 = /*#__PURE__*/ $$template(`<div><div><button>add`);
const CountValue = props => {
return (() => {
const _el$ = _tmpl$(),
_el$2 = _el$.firstChild;
$$insert(_el$, () => props.children, null);
return _el$;
})();
}; };
const CountingComponent = () => { const CountingComponent = () => {
const count = reactive(0); const count = reactive(0);
const add = () => { const add = () => {
count.set(c => c + 1); count.set(c => c + 1);
}; };
return (() => {
const _el$3 = _tmpl$3(), return <div>
_el$8 = _el$3.firstChild, <CountValue><h1>Your count is {count.get()}.</h1></CountValue>
_el$9 = _el$8.firstChild; <div><button onClick={add}>add</button></div>
$$insert( </div>;
_el$3,
$$runComponent(CountValue, {
get children() {
const _el$4 = _tmpl$2(),
_el$5 = _el$4.firstChild,
_el$7 = _el$5.nextSibling,
_el$6 = _el$7.nextSibling;
$$insert(_el$4, count, _el$7);
return _el$4;
},
}),
_el$8
);
$$on(_el$9, 'click', add, true);
return _el$3;
})();
}; };
render(() => $$runComponent(CountingComponent, {}), container);
$$delegateEvents(['click']); render(() => <CountingComponent />, container);
expect(container.innerHTML).toMatchInlineSnapshot( expect(container.innerHTML).toMatchInlineSnapshot(
`"<div><div>Title: <h1>Your count is 0<!---->.</h1></div><div><button>add</button></div></div>"` `"<div><div>Title: <h1>Your count is 0.</h1></div><div><button>add</button></div></div>"`
); );
container.querySelector('button').click(); container.querySelector('button').click();
expect(container.innerHTML).toMatchInlineSnapshot( expect(container.innerHTML).toMatchInlineSnapshot(
`"<div><div>Title: <h1>Your count is 1<!---->.</h1></div><div><button>add</button></div></div>"` `"<div><div>Title: <h1>Your count is 1.</h1></div><div><button>add</button></div></div>"`
); );
}); });
it('should render sub components', ({ container }) => { it('should render sub components', ({ container }) => {
/** const CountValue = (props) => {
* return <div>Count value is {props.count} .</div>;
* const CountValue = (props) => {
* return <div>Count value is {props.count} .</div>;
* };
*
* const CountingComponent = () => {
* const count = reactive(0);
* const add = () => {
* count.set(c => c + 1);
* };
* const Nested = () => {
* return <h1>{count()}</h1>
* }
* return (
* <div>
* <CountValue count={count} />
* <div>
* <button onClick={add}>add</button>
* </div>
* <Nested />
* </div>
* );
* };
*
* render(() => <CountingComponent />, document.getElementById("app"));
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is <!> .'),
$tmpl_2 = /*#__PURE__*/ $$template('<h1>'),
$tmpl_3 = /*#__PURE__*/ $$template('<div><div><button>add');
const CountValue = props => {
return (() => {
const _el$ = $tmpl(),
_el$2 = _el$.firstChild,
_el$4 = _el$2.nextSibling;
$$insert(_el$, () => props.count, _el$4);
return _el$;
})();
}; };
const CountingComponent = () => { const CountingComponent = () => {
const count = reactive(0); const count = reactive(0);
const add = () => { const add = () => {
count.set(c => c + 1); count.set(c => c + 1);
}; };
const Nested = () => { const Nested = () => {
return (() => { return <h1>{count.get()}</h1>;
const _el$5 = $tmpl_2();
$$insert(_el$5, count);
return _el$5;
})();
}; };
return (() => { return (
const _el$6 = $tmpl_3(), <div>
_el$7 = _el$6.firstChild, <CountValue count={count.get()} />
_el$8 = _el$7.firstChild; <div>
$$insert( <button onClick={add}>add</button>
_el$6, </div>
$$runComponent(CountValue, { <Nested />
count: count, </div>
}), );
_el$7
);
$$on(_el$8, 'click', add, true);
$$insert(_el$6, $$runComponent(Nested, {}), null);
return _el$6;
})();
}; };
render(() => $$runComponent(CountingComponent, {}), container); render(() => $$runComponent(CountingComponent, {}), container);
$$delegateEvents(['click']);
expect(container.querySelector('h1').innerHTML).toMatchInlineSnapshot('"0"'); expect(container.querySelector('h1').innerHTML).toMatchInlineSnapshot('"0"');
container.querySelector('button').click(); container.querySelector('button').click();
@ -258,40 +156,17 @@ describe('render', () => {
}); });
it('should render string of style', ({ container }) => { it('should render string of style', ({ container }) => {
/** const CountingComponent = () => {
* return <div style="color: red;">Count value is 0.</div>;
* const CountingComponent = () => {
* return <div style="color: red;">Count value is 0.</div>;
* };
*
* render(() => <CountingComponent />, container);
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div style="color: red;">Count value is 0.');
const Comp = () => {
return $tmpl();
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(CountingComponent, {}), container);
expect(container.querySelector('div').style.color).toEqual('red');
}); });
it('should render string of style with expression', ({ container }) => { it('should render string of style with expression', ({ container }) => {
/**
*
* const Comp = () => {
* const color = 'red';
* return <div style={`color: ${color};`}>Count value is 0.</div>;
* }
* render(() => <Comp />, container);
*
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div style="color: red;">Count value is 0.');
const Comp = () => { const Comp = () => {
const color = 'red'; const color = 'red';
return $tmpl(); return <div style={`color: ${color};`}>Count value is 0.</div>;
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(Comp, {}), container);
@ -299,42 +174,18 @@ describe('render', () => {
}); });
it('should render static object of style', ({ container }) => { it('should render static object of style', ({ container }) => {
/**
*
* const Comp = () => {
* return <div style={{ color: 'red', display: 'flex' }}>Count value is 0.</div>;
* }
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
const Comp = () => { const Comp = () => {
return (() => { return <div style={{ color: 'red', display: 'flex' }}>Count value is 0.</div>;
const _el$ = $tmpl();
$$style(_el$, { color: 'red', display: 'flex' });
return _el$;
})();
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(Comp, {}), container);
expect(container.querySelector('div').style.color).toEqual('red'); expect(container.querySelector('div').style.color).toEqual('red');
}); });
it('should render object of style with expression', ({ container }) => { it('should render object of style with expression', ({ container }) => {
/**
*
* const Comp = () => {
* const color = 'red';
* return <div style={{ color }}>Count value is 0.</div>;
* }
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
const Comp = () => { const Comp = () => {
const color = 'red'; const color = 'red';
return (() => { return <div style={{ color }}>Count value is 0.</div>;
const _el$ = $tmpl();
$$style(_el$, { color: 'red' });
return _el$;
})();
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(Comp, {}), container);
@ -342,22 +193,9 @@ describe('render', () => {
}); });
it('should update style when properties change', ({ container }) => { it('should update style when properties change', ({ container }) => {
/**
*
* const Comp = () => {
* const color = reactive('red');
* return <div style={{ color: color.get() }}>Count value is 0.</div>;
* }
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
const Comp = () => { const Comp = () => {
const color = reactive('red'); const color = reactive('red');
return (() => { return <div style={{ color: color.get() }}>Count value is 0.</div>;
const _el$ = $tmpl();
$$effect(() => $$style(_el$, { color: color.get() }));
return _el$;
})();
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(Comp, {}), container);
@ -367,22 +205,9 @@ describe('render', () => {
}); });
it('should update style when style object change', ({ container }) => { it('should update style when style object change', ({ container }) => {
/**
*
* const Comp = () => {
* const style = reactive({ color: 'red' });
* return <div style={style}>Count value is 0.</div>;
* }
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
const Comp = () => { const Comp = () => {
const style = reactive({ color: 'red' }); const style = reactive({ color: 'red' });
return (() => { return <div style={style.get()}>Count value is 0.</div>;
const _el$ = $tmpl();
$$effect(_$p => $$style(_el$, style.get(), _$p));
return _el$;
})();
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(Comp, {}), container);
@ -392,102 +217,43 @@ describe('render', () => {
}); });
it('should render class', ({ container }) => { it('should render class', ({ container }) => {
/**
*
* const Comp = () => {
* return <div class="red">Count value is 0.</div>;
* }
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div class="red">Count value is 0.');
const Comp = () => { const Comp = () => {
return $tmpl(); return <div className="red">Count value is 0.</div>;
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(Comp, {}), container);
expect(container.querySelector('div').className).toEqual('red'); expect(container.querySelector('div').className).toEqual('red');
}); });
it('should render class with expression', ({ container }) => { it('should render class with expression', ({ container }) => {
/**
*
* const Comp = () => {
* const color = 'red';
* return <div class={color}>Count value is 0.</div>;
* }
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
const Comp = () => { const Comp = () => {
const color = 'red'; const color = 'red';
return (() => { return <div className={color}>Count value is 0.</div>;
const _el$ = $tmpl();
$$effect(() => (_el$.className = color));
return _el$;
})();
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(Comp, {}), container);
expect(container.querySelector('div').className).toEqual('red'); expect(container.querySelector('div').className).toEqual('red');
}); });
it('should render class with object', ({ container }) => { it('should render class with object', ({ container }) => {
/**
*
* const Comp = () => {
* return <div class={{ red: true }}>Count value is 0.</div>;
* }
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
const Comp = () => { const Comp = () => {
return (() => { return <div className={{ red: true }}>Count value is 0.</div>;
const _el$ = $tmpl();
$$className(_el$, {
red: true,
});
return _el$;
})();
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(Comp, {}), container);
expect(container.querySelector('div').className).toEqual('red'); expect(container.querySelector('div').className).toEqual('red');
}); });
it('should render class with array', ({ container }) => { it('should render class with array', ({ container }) => {
/**
*
* const Comp = () => {
* return <div class={['red', 'green']}>Count value is 0.</div>;
* }
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
const Comp = () => { const Comp = () => {
return (() => { return <div className={['red', 'green']}>Count value is 0.</div>;
const _el$ = $tmpl();
$$className(_el$, ['red', 'green']);
return _el$;
})();
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(Comp, {}), container);
expect(container.querySelector('div').className).toEqual('red green'); expect(container.querySelector('div').className).toEqual('red green');
}); });
it('should update class', ({ container }) => { it('should update class', ({ container }) => {
/**
*
* const Comp = () => {
* const color = reactive('red');
* return <div class={color.get()}>Count value is 0.</div>;
* }
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
const Comp = () => { const Comp = () => {
const color = reactive('red'); const color = reactive('red');
return (() => { return <div className={color.get()}>Count value is 0.</div>;
const _el$ = $tmpl();
$$effect(() => $$className(_el$, color.get()));
return _el$;
})();
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(Comp, {}), container);
expect(container.querySelector('div').className).toEqual('red'); expect(container.querySelector('div').className).toEqual('red');
@ -496,96 +262,44 @@ describe('render', () => {
}); });
it('should update class with object', ({ container }) => { it('should update class with object', ({ container }) => {
/**
*
* const Comp = () => {
* const color = reactive('red');
* return <div class={{ [color.get()]: true }}
* >Count value is 0.</div>;
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
const Comp = () => { const Comp = () => {
const color = reactive('red'); const color = reactive('red');
return (() => { return <div className={{ [color.get()]: true }}>Count value is 0.</div>;
const _el$ = $tmpl();
$$effect(() =>
$$className(_el$, {
[color.get()]: true,
})
);
return _el$;
})();
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(Comp, {}), container);
expect(container.querySelector('div').className).toEqual('red'); expect(container.querySelector('div').className).toEqual('red');
}); });
it('should update class with array', ({ container }) => { it('should update class with array', ({ container }) => {
/**
*
* const Comp = () => {
* const color = reactive('red');
* return <div class={[color.get(), 'green']}
* >Count value is 0.</div>;
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
const Comp = () => { const Comp = () => {
const color = reactive('red'); const color = reactive('red');
return (() => { return <div className={[color.get(), 'green']}
const _el$ = $tmpl(); >Count value is 0.</div>;
$$effect(() => $$className(_el$, [color.get(), 'green']));
return _el$;
})();
}; };
render(() => $$runComponent(Comp, {}), container); render(() => $$runComponent(Comp, {}), container);
expect(container.querySelector('div').className).toEqual('red green'); expect(container.querySelector('div').className).toEqual('red green');
}); });
it('should render attribute', ({ container }) => { it('should render attribute', ({ container }) => {
/** function App() {
* return (
* function App() { <div id="test">parallel</div>
* return ( );
* <h1 id="test">parallel</h1> }
* ); render(() => $$runComponent(App, {}), container);
* }
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div id="test">Count value is 0.');
const Comp = () => {
return (() => {
const _el$ = $tmpl();
$$attr(_el$, 'id', 'test');
return _el$;
})();
};
render(() => $$runComponent(Comp, {}), container);
expect(container.querySelector('div').id).toEqual('test'); expect(container.querySelector('div').id).toEqual('test');
}); });
it('should update attribute', ({ container }) => { it('should update attribute', ({ container }) => {
/**
*
* function App() {
* const id = reactive('el');
* return (
* <h1 id={id.get()}>parallel</h1>
* );
* }
*/
// 编译后:
const $tmpl = /*#__PURE__*/ $$template('<div id="test">Count value is 0.');
const id = reactive('el'); const id = reactive('el');
const Comp = () => { function App() {
return (() => {
const _el$ = $tmpl(); return (
$$effect(() => $$attr(_el$, 'id', id.get())); <div id={id.get()}>parallel</div>
return _el$; );
})(); }
}; render(() => $$runComponent(App, {}), container);
render(() => $$runComponent(Comp, {}), container);
expect(container.querySelector('div').id).toEqual('el'); expect(container.querySelector('div').id).toEqual('el');
id.set('test'); id.set('test');
expect(container.querySelector('div').id).toEqual('test'); expect(container.querySelector('div').id).toEqual('test');

View File

@ -2,6 +2,7 @@ const importAliasPrefix = '$$';
export const importMap = [ export const importMap = [
'createElement', 'createElement',
'setStyle', 'setStyle',
'setClassName',
'setAttribute', 'setAttribute',
'setDataset', 'setDataset',
'setProperty', 'setProperty',

View File

@ -110,6 +110,7 @@ export default class BaseGenerator {
} }
addWatch(value: t.Expression) { addWatch(value: t.Expression) {
this.addUsedApi('watch');
return this.t.callExpression( return this.t.callExpression(
this.t.identifier(this.importMap.watch), this.t.identifier(this.importMap.watch),
[this.t.arrowFunctionExpression([], value)] [this.t.arrowFunctionExpression([], value)]

View File

@ -55,6 +55,21 @@ export class HTMLPropGenerator extends BaseGenerator {
); );
} }
/**
* @View
* setClassName($node, value)
*/
private setClassName(
nodeName: string,
value: t.Expression,
) {
this.addUsedApi('setClassName');
return this.t.callExpression(
this.t.identifier(this.importMap.setClassName),
[this.t.identifier(nodeName), value]
);
}
/** /**
* @View * @View
* setDataset($node, value) * setDataset($node, value)
@ -198,6 +213,7 @@ export class HTMLPropGenerator extends BaseGenerator {
dynamic: boolean dynamic: boolean
): t.Expression { ): t.Expression {
if (key === 'style') return this.setStyle(nodeName, value); if (key === 'style') return this.setStyle(nodeName, value);
if (key === 'className') return this.setClassName(nodeName, value);
if (key === 'dataset') return this.setDataset(nodeName, value); if (key === 'dataset') return this.setDataset(nodeName, value);
if (key.startsWith('on')) { if (key.startsWith('on')) {
const event = key.slice(2).toLowerCase(); const event = key.slice(2).toLowerCase();

View File

@ -12,13 +12,14 @@ export class ExpressionGenerator extends BaseGenerator {
return nodeName; return nodeName;
} }
declareExpressionNode(expression: t.Expression): [string, t.Statement] { declareExpressionNode(expression: t.Expression): [string, t.Statement] {
const name = this.geneNodeName(); const name = this.geneNodeName();
const shouldWrapFun = this.checkReactive(expression);
return [name, this.t.variableDeclaration('const', [ return [name, this.t.variableDeclaration('const', [
this.t.variableDeclarator( this.t.variableDeclarator(
this.t.identifier(name), this.t.identifier(name),
expression shouldWrapFun ? this.t.arrowFunctionExpression([], expression) : expression
) )
])]; ])];
} }

View File

@ -4,7 +4,7 @@ import { HTMLPropGenerator } from '../HelperGenerators/HTMLPropGenerator';
import { generateNew, generateView } from '../generate'; import { generateNew, generateView } from '../generate';
export class HTMLGenerator extends HTMLPropGenerator { export class HTMLGenerator extends HTMLPropGenerator {
run(){ run() {
const { tag, props: propsWithView, children } = this.viewUnit as HTMLUnit; const { tag, props: propsWithView, children } = this.viewUnit as HTMLUnit;
const props = this.parseProps(propsWithView, generateView); const props = this.parseProps(propsWithView, generateView);
const [nodeName, statement] = this.declareHTMLNode(tag); const [nodeName, statement] = this.declareHTMLNode(tag);

View File

@ -2,6 +2,7 @@
export const importMap = [ export const importMap = [
'createElement', 'createElement',
'setStyle', 'setStyle',
'setClassName',
'setAttribute', 'setAttribute',
'setDataset', 'setDataset',
'setProperty', 'setProperty',

View File

@ -29,6 +29,15 @@ describe('HTML', () => {
`); `);
}); });
it('should generate a div element with a static className', () => {
expectView(/*jsx*/`
<div className="myClass"></div>
`, /*js*/`
const $node0 = createElement("div")
setClassName($node0, "myClass")
`);
});
it('should generate a div element with a static for', () => { it('should generate a div element with a static for', () => {
expectView(/*jsx*/` expectView(/*jsx*/`
<label for="myFor"></label> <label for="myFor"></label>
@ -126,4 +135,36 @@ describe('HTML', () => {
insert($node0, $node1) insert($node0, $node1)
`); `);
}); });
it('should generate a div element with a dynamic text', () => {
expectView(/*jsx*/`
<div id="count">Count value is {0}.</div>
`, /*js*/`
const $node0 = createElement("div");
$node0.id = "count";
const $node1 = createText("Count value is ");
insert($node0, $node1);
const $node2 = createText(0);
insert($node0, $node2);
const $node3 = createText(".");
insert($node0, $node3);
`);
});
it('should generate a div element with dynamic value', () => {
expectView(/*jsx*/`
<div id="count">Count value is {count.get()} {val.get()}.</div>
`, /*js*/`
const $node0 = createElement("div");
$node0.id = "count";
const $node1 = createText("Count value is ");
insert($node0, $node1);
const $node2 = () => count.get();
insert($node0, $node2);
const $node3 = () => val.get();
insert($node0, $node3);
const $node4 = createText(".");
insert($node0, $node4);
`);
});
}); });

View File

@ -30,7 +30,7 @@ describe('Template', () => {
it('should generate a Template with props', () => { it('should generate a Template with props', () => {
expectView(/*jsx*/` expectView(/*jsx*/`
<div id="myDiv"> <div id="myDiv">
<p class="ok">ok</p> <p className="ok">ok</p>
<h1>fine</h1> <h1>fine</h1>
</div> </div>
`, /*js*/` `, /*js*/`
@ -41,7 +41,7 @@ describe('Template', () => {
const $node0 = createElement("div") const $node0 = createElement("div")
$node0.id = "myDiv" $node0.id = "myDiv"
const $node1 = createElement("p") const $node1 = createElement("p")
$node1.className = "ok" setClassName($node1, "ok")
$node1.textContent = "ok" $node1.textContent = "ok"
insert($node0, $node1) insert($node0, $node1)
const $node2 = createElement("h1") const $node2 = createElement("h1")
@ -56,14 +56,14 @@ describe('Template', () => {
it('should generate a Template with dynamic props', () => { it('should generate a Template with dynamic props', () => {
expectView(/*jsx*/` expectView(/*jsx*/`
<div id={id}> <div id={id}>
<p class={cls}></p> <p className={cls}></p>
<h1></h1> <h1></h1>
</div> </div>
`, /*js*/` `, /*js*/`
const $node0 = $template0.cloneNode(true) const $node0 = $template0.cloneNode(true)
const $node1 = $node0.firstChild const $node1 = $node0.firstChild
$node0.id = id $node0.id = id
$node1.className = cls setClassName($node1, cls)
`, [ `, [
/*js*/` /*js*/`
const $template0 = (() => { const $template0 = (() => {
@ -81,14 +81,14 @@ describe('Template', () => {
it('should generate a Template with reactive props', () => { it('should generate a Template with reactive props', () => {
expectView(/*jsx*/` expectView(/*jsx*/`
<div id={id.get()}> <div id={id.get()}>
<p class={cls.get()}></p> <p className={cls.get()}></p>
<h1></h1> <h1></h1>
</div> </div>
`, /*js*/` `, /*js*/`
const $node0 = $template0.cloneNode(true) const $node0 = $template0.cloneNode(true)
const $node1 = $node0.firstChild const $node1 = $node0.firstChild
watch(() => setProperty($node0, "id", id.get())) watch(() => setProperty($node0, "id", id.get()))
watch(() => setProperty($node1, "className", cls.get())) watch(() => setClassName($node1, cls.get()))
`, [ `, [
/*js*/` /*js*/`
const $template0 = (() => { const $template0 = (() => {
@ -206,17 +206,17 @@ describe('Template', () => {
<Comp myProp={prop} reactiveProp={prop.get()}/> <Comp myProp={prop} reactiveProp={prop.get()}/>
<section> <section>
<div onClick={() => {console.log(prop.get())}}>second temp</div> <div onClick={() => {console.log(prop.get())}}>second temp</div>
<p class={cls}>ok</p> <p className={cls}>ok</p>
<h1 id={id.get()}>fine</h1> <h1 id={id.get()}>fine</h1>
</section> </section>
</div> </div>
<div> <div>
<p class="pp"/> <p className="pp"/>
<Comp /> <Comp />
<section> <section>
<div>second temp</div> <div>second temp</div>
<div id={id.get()} class="very deep"> <div id={id.get()} className="very deep">
<div class="nono"> <div className="nono">
<div> <div>
<div> <div>
<p>stop here</p> <p>stop here</p>
@ -236,7 +236,7 @@ describe('Template', () => {
delegateEvent($node2, "click", () => { delegateEvent($node2, "click", () => {
console.log(prop.get()); console.log(prop.get());
}); });
$node3.className = cls; setClassName($node3, cls);
watch(() => setProperty($node4, "id", id.get())); watch(() => setProperty($node4, "id", id.get()));
const $node5 = runComponent(Comp, { const $node5 = runComponent(Comp, {
myProp: prop, myProp: prop,
@ -274,16 +274,16 @@ describe('Template', () => {
const $template1 = (() => { const $template1 = (() => {
const $node0 = createElement("div") const $node0 = createElement("div")
const $node1 = createElement("p") const $node1 = createElement("p")
$node1.className = "pp" setClassName($node1, "pp")
insert($node0, $node1) insert($node0, $node1)
const $node2 = createElement("section"); const $node2 = createElement("section");
const $node3 = createElement("div"); const $node3 = createElement("div");
$node3.textContent = "second temp"; $node3.textContent = "second temp";
insert($node2, $node3); insert($node2, $node3);
const $node4 = createElement("div"); const $node4 = createElement("div");
$node4.className = "very deep"; setClassName($node4, "very deep");
const $node5 = createElement("div"); const $node5 = createElement("div");
$node5.className = "nono"; setClassName($node5, "nono");
const $node6 = createElement("div"); const $node6 = createElement("div");
const $node7 = createElement("div"); const $node7 = createElement("div");
const $node8 = createElement("p"); const $node8 = createElement("p");

View File

@ -23,11 +23,11 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.12", "@babel/core": "^7.20.12",
"@types/babel__core": "^7.20.5", "@types/babel__core": "^7.20.5",
"@vitest/ui": "^0.34.5", "@vitest/ui": "^1.2.1",
"tsup": "^6.7.0", "tsup": "^6.7.0",
"typescript": "^5.3.2", "typescript": "^5.3.2",
"@babel/plugin-syntax-jsx": "7.16.7", "@babel/plugin-syntax-jsx": "7.16.7",
"vitest": "^0.34.5" "vitest": "^1.2.1"
}, },
"tsup": { "tsup": {
"entry": [ "entry": [

View File

@ -69,7 +69,7 @@ export class ViewParser {
if (!text) return; if (!text) return;
this.viewUnits.push({ this.viewUnits.push({
type: 'text', type: 'text',
content: this.t.stringLiteral(text), content: this.t.stringLiteral(node.value),
}); });
} }