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
-
-
-// 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 (
- *
xxx`),
- _tmpl$4 = /*#__PURE__*/ _$template(` 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
-}
-
-function Button(props) {
- const greeting = createMemo(() => props.name + "!")
- return {greeting()}
-}
-
-// 编译后
-function App() {
- const name = reactive("init")
- return $$runComponent(Button, {
- get name() {
- return name.get()
- },
- })
-}
-
-function Button(props) {
- const greeting = computed(() => props.name + "!")
- return {greeting.get()}
-}
-```
-
-预想2.0语法编译解决解构问题:
-
-```jsx
-// 2.0 语法编译前
-function App() {
- let name = 'init'
- return x
-}
-
-function Button({name}) {
- cosnt
- greeting = name + '!'
- return {greeting}
-}
-
-// 2.0 语法编译后
-function App() {
- const name = reactive('init');
- return x
-}
-
-function Button(props) {
- cosnt
- greeting = computed(() => props.name + '!')
- return {greeting.get()}
-}
-```
-
-备选方案:
-JSX中传递响应式时直接传递响应式变量,无需`.get()`。
-使用时从props取对应的响应式值进行使用。
-
-> 好处: 可以解构,组件显式处理props中的响应式
-> 问题: 1. 使用props时需额外判断props是否为响应式,2.0API 无法向下编译
-
-2. JSX使用响应式不要get,JS使用要get
-
-```jsx
-function App() {
- const name = reactive('init');
- return x
-}
-
-$$runComponent(Button, {
- class: name
-}
-})
-
-function Button({name}) {
- cosnt
- greeting = computed(() => (isReactiveObj(props.name) ? props.name.get() : props.name) + '!')
- return {reeting.get()}
-}
-```
-
-## Slot
-通过children属性传递slot。IIFE包裹slot,返回对应的dom。
-```jsx
-/**
- * 源码:
- * const CountValue = (props) => {
- * return Title: {props.children}
;
- * }
- *
- * const CountingComponent = () => {
- * const [count, setCount] = createSignal(0);
- *
- * return
- * FOO
- *
;
- * };
- *
- * render(() => , document.getElementById("app"));
- */
-
- // 编译后:
-const _tmpl$ = /*#__PURE__*/ $$template(`Title: `),
- _tmpl$2 = /*#__PURE__*/ $$template(`
Your count is .`),
- _tmpl$3 = /*#__PURE__*/ $$template(``);
-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
diff --git a/packages/inula-novdom/babel.config.js b/packages/inula-novdom/babel.config.js
deleted file mode 100644
index 9fd8d3d1..00000000
--- a/packages/inula-novdom/babel.config.js
+++ /dev/null
@@ -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',
- ]
- ]
-};
diff --git a/packages/inula-novdom/src/dom.ts b/packages/inula-novdom/src/dom.ts
index 41af470f..27153578 100644
--- a/packages/inula-novdom/src/dom.ts
+++ b/packages/inula-novdom/src/dom.ts
@@ -385,3 +385,7 @@ export function bindRef
(node: T, ref: RefObject | RefCallback): void {
throw new Error('Invalid ref');
}
}
+
+export function createElement(tag: string) {
+ return document.createElement(tag);
+}
diff --git a/packages/inula-novdom/tests/render.test.tsx b/packages/inula-novdom/tests/render.test.tsx
index 89509b80..c3420b20 100644
--- a/packages/inula-novdom/tests/render.test.tsx
+++ b/packages/inula-novdom/tests/render.test.tsx
@@ -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('Count value is 0.');
const CountingComponent = () => {
- return $tmpl();
+ return
Count value is 0.
;
};
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$;
})();
};
diff --git a/packages/inula-novdom/vitest.config.ts b/packages/inula-novdom/vitest.config.ts
index c3ae41c7..20abc8ae 100644
--- a/packages/inula-novdom/vitest.config.ts
+++ b/packages/inula-novdom/vitest.config.ts
@@ -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'
},
diff --git a/packages/transpiler/babel-preset-inula-jsx/src/pluginProvider.ts b/packages/transpiler/babel-preset-inula-jsx/src/pluginProvider.ts
index 5ccd8f32..0c5d80a8 100644
--- a/packages/transpiler/babel-preset-inula-jsx/src/pluginProvider.ts
+++ b/packages/transpiler/babel-preset-inula-jsx/src/pluginProvider.ts
@@ -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
diff --git a/packages/transpiler/jsx-view-generator/src/test/exp.test.ts b/packages/transpiler/jsx-view-generator/src/test/exp.test.ts
index d9585f3c..f424392c 100644
--- a/packages/transpiler/jsx-view-generator/src/test/exp.test.ts
+++ b/packages/transpiler/jsx-view-generator/src/test/exp.test.ts
@@ -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*/ `
+
111{expr}222
+ `,
+ /*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*/ `
+
+ `,
+ /*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;
+ };`
+ ]
+ );
});
});
-
\ No newline at end of file
diff --git a/packages/transpiler/vite-plugin-inula-no-vdom/package.json b/packages/transpiler/vite-plugin-inula-no-vdom/package.json
new file mode 100644
index 00000000..01602723
--- /dev/null
+++ b/packages/transpiler/vite-plugin-inula-no-vdom/package.json
@@ -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
+ }
+}
diff --git a/packages/transpiler/vite-plugin-inula-no-vdom/src/index.ts b/packages/transpiler/vite-plugin-inula-no-vdom/src/index.ts
new file mode 100644
index 00000000..c5eef4d4
--- /dev/null
+++ b/packages/transpiler/vite-plugin-inula-no-vdom/src/index.ts
@@ -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
(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;
+ },
+ };
+}
diff --git a/packages/transpiler/vite-plugin-inula-no-vdom/tsconfig.json b/packages/transpiler/vite-plugin-inula-no-vdom/tsconfig.json
new file mode 100644
index 00000000..e0932d78
--- /dev/null
+++ b/packages/transpiler/vite-plugin-inula-no-vdom/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "lib": ["ESNext", "DOM"],
+ "moduleResolution": "Node",
+ "strict": true,
+ "esModuleInterop": true
+ },
+ "ts-node": {
+ "esm": true
+ }
+}
\ No newline at end of file