parent
3b3bf8e5d6
commit
ef5ee212e3
|
@ -1,562 +1,6 @@
|
|||
# **Templating**
|
||||
|
||||
## JSX
|
||||
|
||||
### template
|
||||
|
||||
JSX整体编译为template模板,动态部分通过insert插入。
|
||||
template字符串不进行省略(end tag)。<font color="#c00000">待讨论</font>
|
||||
|
||||
```jsx
|
||||
<div id="count">Count value is <!>.</div>
|
||||
|
||||
// solid
|
||||
const $tmpl = /*#__PURE__*/ $$template(`<div id="count">Count value is <!>.`);
|
||||
|
||||
// inula
|
||||
const $tmpl = /*#__PURE__*/ $$template(`<div id="count">Count value is <!>.</div>`);
|
||||
# Debug
|
||||
See the compiled code in Vitest-UI on module graph tab (click the point of the file).
|
||||
```shell
|
||||
pnpm run test
|
||||
```
|
||||
|
||||
命名规则
|
||||
对于dom的变量,前缀用`$`,后缀用`_number`,用dom类型作为变量名。<font color="#c00000">待讨论</font>
|
||||
|
||||
```jsx
|
||||
const $tmpl = /*#__PURE__*/ $$template(`<div id="count">Count value is <!>.`);
|
||||
const $div = $tmpl(), // 使用$div
|
||||
$text = $div.firstChild, // 使用$text
|
||||
```
|
||||
|
||||
**内部函数用`$$`作为前缀**
|
||||
|
||||
### insert
|
||||
|
||||
```jsx
|
||||
const $tmpl = /*#__PURE__*/ $$template(`<div id="count">Count value is <!>.`);
|
||||
const $div = $tmpl(),
|
||||
$text = $div.firstChild,
|
||||
$text_1 = $text.nextSibling,
|
||||
$$insert
|
||||
($div, count, $text_1);
|
||||
return $div;
|
||||
```
|
||||
## Class
|
||||
### inline class
|
||||
直接修改dom的`className`
|
||||
```jsx
|
||||
/**
|
||||
* 源码:
|
||||
* 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 color = 'red';
|
||||
return (() => {
|
||||
const _el$ = $tmpl();
|
||||
$$effect(() => (_el$.className = color));
|
||||
return _el$;
|
||||
})();
|
||||
};
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
````
|
||||
|
||||
### Object class
|
||||
通过`$$className`设置class
|
||||
```jsx
|
||||
/**
|
||||
* 源码:
|
||||
* const Comp = () => {
|
||||
* return <div class={{ red: true }}>Count value is 0.</div>;
|
||||
* }
|
||||
*/
|
||||
// 编译后:
|
||||
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
|
||||
const Comp = () => {
|
||||
return (() => {
|
||||
const _el$ = $tmpl();
|
||||
$$className(_el$, {
|
||||
red: true,
|
||||
});
|
||||
return _el$;
|
||||
})();
|
||||
};
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
```
|
||||
|
||||
### Array class
|
||||
通过`$$className`设置class
|
||||
```jsx
|
||||
/**
|
||||
* 源码:
|
||||
* const Comp = () => {
|
||||
* return <div class={['red', 'green']}>Count value is 0.</div>;
|
||||
* }
|
||||
*/
|
||||
// 编译后:
|
||||
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
|
||||
const Comp = () => {
|
||||
return (() => {
|
||||
const _el$ = $tmpl();
|
||||
$$className(_el$, ['red', 'green']);
|
||||
return _el$;
|
||||
})();
|
||||
};
|
||||
render(() => $$runComponent(Comp, {}), container);
|
||||
```
|
||||
|
||||
## Attribute
|
||||
使用`$$setAttribute`设置属性
|
||||
```jsx
|
||||
function App() {
|
||||
return (
|
||||
<h1 id="test">parallel</h1>
|
||||
);
|
||||
}
|
||||
|
||||
// 编译后:
|
||||
const $tmpl = /*#__PURE__*/ $$template('<div id="test">Count value is 0.');
|
||||
const Comp = () => {
|
||||
return (() => {
|
||||
const $div = $tmpl();
|
||||
$$setAttribute($div, 'id', 'test');
|
||||
return $div;
|
||||
})();
|
||||
};
|
||||
```
|
||||
使用响应式时,包裹在`$$effect`中
|
||||
```jsx
|
||||
//源码:
|
||||
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 Comp = () => {
|
||||
return (() => {
|
||||
const _el$ = $tmpl();
|
||||
$$effect(() => $$setAttribute(_el$, 'id', id.get()));
|
||||
return _el$;
|
||||
})();
|
||||
};
|
||||
```
|
||||
|
||||
## Fragment
|
||||
Fragment编译为数组,并且每个元素都是一个IIFE,返回对应的dom。
|
||||
```jsx
|
||||
/**
|
||||
* 源码:
|
||||
* const CountingComponent = () => {
|
||||
* const [count, setCount] = createSignal(0);
|
||||
* const add = () => {
|
||||
* setCount((c) => c + 1);
|
||||
* }
|
||||
* return <>
|
||||
* <div id="count">Count value is {count()}.</div>
|
||||
* <div><button onClick={add}>add</button></div>
|
||||
* </>;
|
||||
* };
|
||||
*/
|
||||
|
||||
// 编译后:
|
||||
const $tmpl = /*#__PURE__*/ $$template('<div id="count">Count value is <!>.'),
|
||||
$tmpl_2 = /*#__PURE__*/ $$template('<div><button id="btn">add');
|
||||
const CountingComponent = () => {
|
||||
const count = reactive(0);
|
||||
const add = () => {
|
||||
count.set(c => c + 1);
|
||||
};
|
||||
return [
|
||||
(() => {
|
||||
const _el$ = $tmpl(),
|
||||
_el$2 = _el$.firstChild,
|
||||
_el$4 = _el$2.nextSibling;
|
||||
$$insert(_el$, count, _el$4);
|
||||
return _el$;
|
||||
})(),
|
||||
(() => {
|
||||
const _el$5 = $tmpl_2(),
|
||||
_el$6 = _el$5.firstChild;
|
||||
$$on(_el$6, 'click', add, true);
|
||||
return _el$5;
|
||||
})(),
|
||||
];
|
||||
};
|
||||
```
|
||||
## Event
|
||||
|
||||
### 事件代理
|
||||
收集每个文件中使用过的事件,通过`$$delegateEvents`挂载
|
||||
`$$delegateEvents`传入render中的dom,让代理事件挂载在dom上,而不是document。<font color="#c00000">待讨论</font>
|
||||
|
||||
```jsx
|
||||
$$render(() => $$createComponent(Main, {}), container);
|
||||
$$delegateEvents(['click'], container);
|
||||
```
|
||||
|
||||
### 事件绑定
|
||||
统一使用`$$on`,传入参数为:
|
||||
1. dom
|
||||
2. 事件类型
|
||||
3. 事件处理函数
|
||||
4. 是否冒泡,默认为false
|
||||
|
||||
```jsx
|
||||
<button type="button" onClick={handleDeleteClick}>x</button>
|
||||
// to
|
||||
$$on(_el$6, 'click', handleDeleteClick, true);
|
||||
|
||||
<input id="var-change" onChange={handler}/>
|
||||
// to
|
||||
$$on(_el$6, 'change', handleDeleteClick, true);
|
||||
```
|
||||
|
||||
## Dom Ref
|
||||
```jsx
|
||||
function App() {
|
||||
let myDiv;
|
||||
return <div ref={myDiv}>My Element</div>;
|
||||
}
|
||||
|
||||
// to
|
||||
|
||||
function App() {
|
||||
let myDiv;
|
||||
return (() => {
|
||||
const $div = _tmpl$();
|
||||
const $ref = myDiv;
|
||||
typeof $ref === "function" ? $$bindRef($ref, _el$) : myDiv = $div;
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
```
|
||||
## Styling
|
||||
### Inline Style
|
||||
```jsx
|
||||
/**
|
||||
* 源码:
|
||||
* 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);
|
||||
```
|
||||
### Object Style
|
||||
每个style属性通过`$$setProperty`设置。
|
||||
动态style通过`$$effect`包裹。
|
||||
```jsx
|
||||
|
||||
/**
|
||||
* 源码:
|
||||
* const Comp = () => {
|
||||
* const color = reactive('red');
|
||||
* return <div style={{ color: color.get(), display: 'flex' }}>Count value is 0.</div>;
|
||||
* }
|
||||
*/
|
||||
|
||||
// 编译后:
|
||||
const $tmpl = /*#__PURE__*/ $$template('<div>Count value is 0.');
|
||||
const Comp = () => {
|
||||
return (() => {
|
||||
const _el$ = $tmpl();
|
||||
$$effect(() =>
|
||||
color.get() != null ? _el$.style.setProperty('color', color.get()) : _el$.style.removeProperty('color')
|
||||
);
|
||||
_el$.style.setProperty('display', 'flex');
|
||||
return _el$;
|
||||
})();
|
||||
};
|
||||
|
||||
```
|
||||
## Loop
|
||||
|
||||
```jsx
|
||||
<For each={state.todoList}>
|
||||
{todo => <><Todo todo={todo}/><Todo todo={todo}/></>}
|
||||
</For>
|
||||
|
||||
// to
|
||||
$$runComponent(For, {
|
||||
get each() {
|
||||
return state.todoList;
|
||||
},
|
||||
children: todo => [
|
||||
$$runComponent(Todo, {
|
||||
todo: todo,
|
||||
}),
|
||||
$$runComponent(Todo, {
|
||||
todo: todo,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
## Conditional
|
||||
编译为`Cond`组件,`Cond`组件接收`branches`属性。
|
||||
`branches`属性为一个数组,数组中的每个元素都是一个数组,数组的第一个元素是条件,第二个元素是返回的dom。
|
||||
```ts
|
||||
// It's boolean when the condition is static of default branch
|
||||
// Otherwise it's a function that return boolean
|
||||
type CondExpression = boolean | (() => boolean);
|
||||
// When branch only include static JSXElement the branch can be a JSXElement
|
||||
// Otherwise, the branch should be a function that return JSXElement
|
||||
type Branch = JSXElement | (() => JSXElement);
|
||||
|
||||
export interface CondProps {
|
||||
// Array of tuples, first item is the condition, second is the branch to render
|
||||
branches: [CondExpression, Branch][];
|
||||
}
|
||||
```
|
||||
```jsx
|
||||
/**
|
||||
* 源码:
|
||||
* const fn = vi.fn();
|
||||
* function App() {
|
||||
* const x = reactive(7);
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <h1>if</h1>
|
||||
* <if cond={x.get() > 10}>
|
||||
* <p>{x.get()} is greater than 10</p>
|
||||
* </if>
|
||||
* <else-if cond={5 > x.get()}>
|
||||
* <p>{x.get()} is less than 5</p>
|
||||
* </else-if>
|
||||
* <else>
|
||||
* <p>{x.get()} is between 5 and 10</p>
|
||||
* </else>
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* render(() => <App />, container);
|
||||
*/
|
||||
|
||||
// 编译后:
|
||||
|
||||
const _tmpl$ = /*#__PURE__*/ _$template(`<p> is greater than 10`),
|
||||
_tmpl$2 = /*#__PURE__*/ _$template(`<p> is less than 5`),
|
||||
_tmpl$3 = /*#__PURE__*/ _$template(`<div><h1>xxx`),
|
||||
_tmpl$4 = /*#__PURE__*/ _$template(`<p> is between 5 and 10`);
|
||||
let change;
|
||||
|
||||
function App() {
|
||||
const x = reactive(7);
|
||||
change = v => x.set(v);
|
||||
return (() => {
|
||||
const _el$ = _tmpl$3(),
|
||||
_el$2 = _el$.firstChild;
|
||||
_$insert(
|
||||
_el$,
|
||||
_$runComponent(Cond, {
|
||||
get branches() {
|
||||
return [
|
||||
[
|
||||
() => x.get() > 10,
|
||||
() => {
|
||||
const _el$3 = _tmpl$(),
|
||||
_el$4 = _el$3.firstChild;
|
||||
_$insert(_el$3, x, _el$4);
|
||||
return _el$3;
|
||||
},
|
||||
],
|
||||
[
|
||||
() => 5 > x.get(),
|
||||
() => {
|
||||
const _el$5 = _tmpl$2(),
|
||||
_el$6 = _el$5.firstChild;
|
||||
_$insert(_el$5, x, _el$6);
|
||||
return _el$5;
|
||||
},
|
||||
],
|
||||
[
|
||||
true,
|
||||
() => {
|
||||
const _el$7 = _tmpl$4(),
|
||||
_el$8 = _el$7.firstChild;
|
||||
_$insert(_el$7, x, _el$8);
|
||||
return _el$7;
|
||||
},
|
||||
],
|
||||
];
|
||||
},
|
||||
}),
|
||||
null
|
||||
);
|
||||
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
|
||||
render(() => _$runComponent(App, {}), container);
|
||||
```
|
||||
## Early Return
|
||||
|
||||
# **Component composition**
|
||||
|
||||
## Props
|
||||
|
||||
使用`.get()`的表达式作为props时,转为对象时通过getter包装。
|
||||
使用props时,通过props.(参数名)访问来保证在组件时读取props时能够保持响应。
|
||||
|
||||
> 好处: 1. 使用props时无需感知是否为响应式,无需额外判断 2. 使用统一,JSX和函数体JS都通过.get() 读值
|
||||
> 问题: 1. 不能解构 -> 通过2.0 API编译语法糖解决
|
||||
|
||||
```jsx
|
||||
function App() {
|
||||
const name = reactive("init")
|
||||
return <Button name={signal()}/>
|
||||
}
|
||||
|
||||
function Button(props) {
|
||||
const greeting = createMemo(() => props.name + "!")
|
||||
return <h1>{greeting()}</h1>
|
||||
}
|
||||
|
||||
// 编译后
|
||||
function App() {
|
||||
const name = reactive("init")
|
||||
return $$runComponent(Button, {
|
||||
get name() {
|
||||
return name.get()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function Button(props) {
|
||||
const greeting = computed(() => props.name + "!")
|
||||
return <h1>{greeting.get()}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
预想2.0语法编译解决解构问题:
|
||||
|
||||
```jsx
|
||||
// 2.0 语法编译前
|
||||
function App() {
|
||||
let name = 'init'
|
||||
return <Button name={name}>x</button>
|
||||
}
|
||||
|
||||
function Button({name}) {
|
||||
cosnt
|
||||
greeting = name + '!'
|
||||
return <h1>{greeting}</h1>
|
||||
}
|
||||
|
||||
// 2.0 语法编译后
|
||||
function App() {
|
||||
const name = reactive('init');
|
||||
return <Button name={name.get()}>x</button>
|
||||
}
|
||||
|
||||
function Button(props) {
|
||||
cosnt
|
||||
greeting = computed(() => props.name + '!')
|
||||
return <h1>{greeting.get()}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
备选方案:
|
||||
JSX中传递响应式时直接传递响应式变量,无需`.get()`。
|
||||
使用时从props取对应的响应式值进行使用。
|
||||
|
||||
> 好处: 可以解构,组件显式处理props中的响应式
|
||||
> 问题: 1. 使用props时需额外判断props是否为响应式,2.0API 无法向下编译
|
||||
|
||||
2. JSX使用响应式不要get,JS使用要get
|
||||
|
||||
```jsx
|
||||
function App() {
|
||||
const name = reactive('init');
|
||||
return <Button class={name}>x</button>
|
||||
}
|
||||
|
||||
$$runComponent(Button, {
|
||||
class: name
|
||||
}
|
||||
})
|
||||
|
||||
function Button({name}) {
|
||||
cosnt
|
||||
greeting = computed(() => (isReactiveObj(props.name) ? props.name.get() : props.name) + '!')
|
||||
return <h1>{reeting.get()}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
## Slot
|
||||
通过children属性传递slot。IIFE包裹slot,返回对应的dom。
|
||||
```jsx
|
||||
/**
|
||||
* 源码:
|
||||
* const CountValue = (props) => {
|
||||
* return <div>Title: {props.children}</div>;
|
||||
* }
|
||||
*
|
||||
* const CountingComponent = () => {
|
||||
* const [count, setCount] = createSignal(0);
|
||||
*
|
||||
* return <div>
|
||||
* <CountValue><h1>FOO</h1></CountValue>
|
||||
* </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>`);
|
||||
const CountValue = props => {
|
||||
return (() => {
|
||||
const _el$ = _tmpl$(),
|
||||
_el$2 = _el$.firstChild;
|
||||
$$insert(_el$, () => props.children, null);
|
||||
return _el$;
|
||||
})();
|
||||
};
|
||||
const CountingComponent = () => {
|
||||
const count = reactive(0);
|
||||
const add = () => {
|
||||
count.set(c => c + 1);
|
||||
};
|
||||
return (() => {
|
||||
const _el$3 = _tmpl$3(),
|
||||
_el$8 = _el$3.firstChild,
|
||||
_el$9 = _el$8.firstChild;
|
||||
$$insert(
|
||||
_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,
|
||||
);
|
||||
return _el$3;
|
||||
})();
|
||||
};
|
||||
```
|
||||
## Context
|
||||
detail: https://github.com/vitest-dev/vitest/discussions/2242
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
||||
*
|
||||
* openInula is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
*
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
],
|
||||
[
|
||||
'@babel/preset-typescript',
|
||||
]
|
||||
]
|
||||
};
|
|
@ -385,3 +385,7 @@ export function bindRef<T>(node: T, ref: RefObject<T> | RefCallback<T>): void {
|
|||
throw new Error('Invalid ref');
|
||||
}
|
||||
}
|
||||
|
||||
export function createElement(tag: string) {
|
||||
return document.createElement(tag);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
style as $$style,
|
||||
className as $$className,
|
||||
setAttribute as $$attr,
|
||||
createElement
|
||||
} from '../src/dom';
|
||||
import { runComponent as $$runComponent, render } from '../src/core';
|
||||
import { delegateEvents as $$delegateEvents, addEventListener as $$on } from '../src/event';
|
||||
|
@ -40,9 +41,8 @@ describe('render', () => {
|
|||
*/
|
||||
|
||||
// 编译后:
|
||||
const $tmpl = /*#__PURE__*/ $$template('<div id="count">Count value is 0.');
|
||||
const CountingComponent = () => {
|
||||
return $tmpl();
|
||||
return <div id="count">Count value is 0.</div>;
|
||||
};
|
||||
render(() => $$runComponent(CountingComponent, {}), container);
|
||||
|
||||
|
@ -443,9 +443,7 @@ describe('render', () => {
|
|||
const color = reactive('red');
|
||||
return (() => {
|
||||
const _el$ = $tmpl();
|
||||
$$effect(() =>
|
||||
$$style(_el$, { color: color.get() })
|
||||
);
|
||||
$$effect(() => $$style(_el$, { color: color.get() }));
|
||||
return _el$;
|
||||
})();
|
||||
};
|
||||
|
|
|
@ -15,8 +15,19 @@
|
|||
|
||||
// vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import inula from 'vite-plugin-inula-no-vdom';
|
||||
|
||||
export default defineConfig({
|
||||
esbuild: {
|
||||
jsx: 'preserve',
|
||||
},
|
||||
resolve: {
|
||||
conditions: ['dev'],
|
||||
},
|
||||
plugins: [
|
||||
// @ts-expect-error TODO: fix vite plugin interface is not compatible
|
||||
inula(),
|
||||
],
|
||||
test: {
|
||||
environment: 'jsdom', // or 'jsdom', 'node'
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@ import { attributeMap, htmlTags, importMap } from './const';
|
|||
|
||||
|
||||
export class PluginProvider {
|
||||
private static readonly inulaPackageName = 'inula';
|
||||
private static readonly inulaPackageName = 'inula-reactive';
|
||||
// ---- Plugin Level ----
|
||||
private readonly babelApi: typeof babel
|
||||
private readonly t: typeof t
|
||||
|
|
|
@ -1,14 +1,59 @@
|
|||
import { describe, it } from 'vitest';
|
||||
import { expectView } from './mock';
|
||||
|
||||
|
||||
describe('Expression', () => {
|
||||
it('should generate a expression Node', () => {
|
||||
expectView(/*jsx*/`
|
||||
expectView(
|
||||
/*jsx*/ `
|
||||
<>{expr}</>
|
||||
`, /*js*/`
|
||||
`,
|
||||
/*js*/ `
|
||||
const $node0 = expr
|
||||
`);
|
||||
`
|
||||
);
|
||||
});
|
||||
|
||||
it('should generate a expression Node in middle of text', () => {
|
||||
expectView(
|
||||
/*jsx*/ `
|
||||
<div>111{expr}222</div>
|
||||
`,
|
||||
/*js*/ `
|
||||
const $node0 = createElement("div");
|
||||
const $node1 = createText("111");
|
||||
insert($node0, $node1);
|
||||
const $node2 = expr;
|
||||
insert($node0, $node2);
|
||||
const $node3 = createText("222");
|
||||
insert($node0, $node3);
|
||||
`
|
||||
);
|
||||
});
|
||||
|
||||
it('should generate a expression Node in middle of text in template', () => {
|
||||
expectView(
|
||||
/*jsx*/ `
|
||||
<div><div>111{expr}222</div></div>
|
||||
`,
|
||||
/*js*/ `
|
||||
const $node0 = $template0.cloneNode(true);
|
||||
const $node1 = $node0.firstChild;
|
||||
const $node2 = $node1.firstChild.nextSibling;
|
||||
const $node3 = expr;
|
||||
insert($node1, $node3, $node2);
|
||||
`
|
||||
, [
|
||||
`const $template0 = () => {
|
||||
const $node0 = createElement("div");
|
||||
const $node1 = createElement("div");
|
||||
const $node2 = createText("111");
|
||||
insert($node1, $node2);
|
||||
const $node3 = createText("222");
|
||||
insert($node1, $node3);
|
||||
insert($node0, $node1);
|
||||
return $node0;
|
||||
};`
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "vite-plugin-inula-no-vdom",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"build": "tsup --sourcemap"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"vite": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.23.9",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"babel-preset-inula-jsx": "workspace:*",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"minimatch": "^9.0.3",
|
||||
"tsup": "^6.7.0",
|
||||
"vite": "^5.0.0"
|
||||
},
|
||||
"tsup": {
|
||||
"entry": [
|
||||
"src/index.ts"
|
||||
],
|
||||
"format": [
|
||||
"cjs",
|
||||
"esm"
|
||||
],
|
||||
"clean": true,
|
||||
"dts": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
||||
*
|
||||
* openInula is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
*
|
||||
* http://license.coscl.org.cn/MulanPSL2
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
import * as babel from '@babel/core';
|
||||
import inula, { InulaOption } from 'babel-preset-inula-jsx';
|
||||
import { Plugin, TransformResult } from 'vite';
|
||||
import { minimatch } from 'minimatch';
|
||||
|
||||
function toArray<T>(arr: T | T[]): T[] {
|
||||
return Array.isArray(arr) ? arr : [arr];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the transformation should be applied
|
||||
* @param id the file path
|
||||
* @param files the files to apply the transformation
|
||||
* @param excludeFiles the files to exclude the transformation
|
||||
*/
|
||||
function shouldApplyTransformation(id: string, files: string | string[], excludeFiles: string | string[]) {
|
||||
let enter = false;
|
||||
for (const allowedPath of toArray(files)) {
|
||||
if (minimatch(id, allowedPath)) {
|
||||
enter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const notAllowedPath of toArray(excludeFiles)) {
|
||||
if (minimatch(id, notAllowedPath)) {
|
||||
enter = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return enter;
|
||||
}
|
||||
|
||||
export default function inulaPugin(options: InulaOption = {}): Plugin {
|
||||
const { files = '**/*.{js,jsx,ts,tsx}', excludeFiles = '**/{dist,node_modules,lib}/*.{js,ts}' } = options;
|
||||
return {
|
||||
name: 'inula-no-vdom',
|
||||
enforce: 'pre',
|
||||
transform(source, id) {
|
||||
if (!shouldApplyTransformation(id, files, excludeFiles)) return;
|
||||
|
||||
return babel.transform(source, {
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
filename: id,
|
||||
sourceMaps: true,
|
||||
presets: [[inula, options]],
|
||||
}) as TransformResult;
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"ts-node": {
|
||||
"esm": true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue