npm package: @knno/jsx
Description: A small toolkit for simplifying DOM operations
Author: Thor Qin
Latest version: 1.3.3

# @knno/jsx

> 一个轻量级的 JSX 运行时库，用于简化 DOM 操作，没有虚拟 DOM

[![npm version](https://img.shields.io/npm/v/@knno/jsx)](https://www.npmjs.com/package/@knno/jsx)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Node.js Version](https://img.shields.io/node/v/@knno/jsx)](https://nodejs.org)

## 简介

`@knno/jsx` 是一个轻量级的 JSX 运行时库，设计用于简化 DOM 操作。与传统的 React 不同，这个库没有虚拟 DOM，直接操作真实的 DOM 元素，提供了一种更轻量、更直接的 UI 构建方式。

### 主要特性

- **无虚拟 DOM**：直接操作 DOM，性能开销更小
- **TypeScript 支持**：完整的类型定义，包括 HTML、SVG 和 MathML 元素
- **多种组件模式**：支持类组件、函数组件和 WebComponent
- **SSR 支持**：可用于服务端渲染（如配合 jsdom）
- **命名空间支持**：处理 SVG 和 MathML 标签冲突
- **事件处理**：完整的事件映射和处理机制
- **双模块输出**：同时支持 ESM 和 CJS 格式

### 技术要求

- **Node.js**: >= 20.9.0
- **TypeScript**: 5.7+

## 安装

```bash
npm install @knno/jsx
```

## 配置 tsconfig.json

```json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@knno/jsx"
  }
}
```

或使用旧的 jsx factory 方式：

```json
{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "h",
    "jsxFragmentFactory": "fragment"
  }
}
```

## 渲染

```tsx
import { render } from '@knno/jsx';
// 默认渲染到 body 里
render(() => <div>Hello world!</div>);
// 或指定容器
render(() => <div>Hello world!</div>, '#app');
```

## 自定义组件

自定义组件需要继承 `Component` 类，并实现 `render` 方法。以下是一个示例：
```tsx
import { render, Props, CSSStyle } from '@knno/jsx';

type MyProps = Props<MyComponent> & {
  style?: CSSStyle;
};

class MyComponent extends Component<MyProps> {
  render() {
    return (
      <div style={this.props.style}>
        <h2>MyComponent {new Date()}</h2>
        <button onClick={() => this.update()}>
          Update Completely
        </button>
      </div>
    );
  }
}

render(() => (
  <MyComponent
    style={{ fontSize: '20pt', color: 'red' }} 
    ref={(m) => console.log('jsx component:', m)} />
  )
);

```

## 函数组件

```tsx
import { render, Child, BaseProps, CSSStyle } from '@knno/jsx';

type Prop = BaseProps & {
  name?: string;
  style?: string | CSSStyle;
};

function MyFunction(props: Prop) {
  return (
    <div name={props.name} style={props.style}>
      <h2>MyFunction</h2>
      Children: {[...props.children]}
    </div>
  );
}

render(() => (
  <MyFunction
    name="MyFunction"
    style={{ fontSize: '18pt' }}
  >
    {[1, 2, 3, 4].map((i) => (
      <button>button{i}</button>
    ))}
  </MyFunction>
));
```

## 可以使用 WebComponent 组件

```tsx
import { render, WebComponents } from '@knno/jsx';

class MyWebComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot?.append(<h2>MyWebComponent</h2>);
  }
  connectedCallback() {
    console.log('connectedCallback');
  }
}

// 注册并获得 WebComponent 组件的包装器类型
const MyComponentWrapper = WebComponents.register(MyWebComponent, 'my-component');

render(() => (
  <>
    使用包装器类型：
    <br />
    <MyComponentWrapper ref={(el) => {
      // 这里 el 是 MyWebComponent 组件的实例
      console.log(el);
    }} />
    <br />
    使用 webcomponent 标签：
    <br />
    <my-component />
  </>
));
```


## SSR 用 Linkedom 渲染（推荐）

```tsx
import { parseHTML } from 'linkedom';
import { render, useContext } from '@knno/jsx';

// 创建一个 jsdom 实例
const window = parseHTML('<html><body></body></html>');

// 定义一个组件
function App() {
  return (
    <div>
      <h1>Hello, SSR!</h1>
    </div>
  );
}

// 渲染组件
useContext(window, () => {
  render(() => (<App />));
});


// 获取渲染后的 HTML
const html = window.document.documentElement.outerHTML;
console.log(html);
```

## SSR 用 JSDOM 渲染

```tsx
import { JSDOM } from 'jsdom';
import { render, useContext } from '@knno/jsx';

// 创建一个 jsdom 实例
const dom = new JSDOM();

// 定义一个组件
function App() {
  return (
    <div>
      <h1>Hello, SSR!</h1>
    </div>
  );
}

// 渲染组件
useContext(dom.window, () => {
  render(() => (<App />));
});


// 获取渲染后的 HTML
const html = dom.serialize();
console.log(html);
```



## 避免 SVG 或 MathML 标签冲突

如 `title`、`script` 等标签，既是 SVG 标签同时也是 HTML 标签，此时需要使用命名空间来区分。

```tsx
import { render, NS } from '@knno/jsx';

// 使用命名空间前缀
render(() => (
  <svg>
    <svg:title>SVG title 标签</svg:title>
    <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
  </svg>
))

// 或使用 NS 指定命名空间
render(() => (
  NS.svg(() => (
    <svg>
      <title>SVG title 标签</title>
      <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
    </svg>
  ))
))
```

## 事件处理

`@knno/jsx` 支持完整的事件处理，包括事件捕获：

```tsx
import { render } from '@knno/jsx';

render(() => (
  <div>
    <button onClick={() => console.log('clicked')}>
      点击我
    </button>
    <input onInput={(e) => console.log(e.target.value)} />
    <div onMouseEnter={() => console.log('mouse enter')}>
      鼠标悬停
    </div>
  </div>
));
```

支持事件捕获（使用 `Capture` 后缀）：

```tsx
render(() => (
  <div onClick={() => console.log('parent')} onClickCapture={() => console.log('capture parent')}>
    <button onClick={() => console.log('child')} onClickCapture={() => console.log('capture child')}>
      点击
    </button>
  </div>
));
```

## Ref 使用

可以通过 `ref` 属性获取元素或组件的引用：

```tsx
import { render, Component, Props } from '@knno/jsx';

// 获取 DOM 元素引用
render(() => (
  <div ref={(el) => console.log('div element:', el)}>
    Hello
  </div>
));

// 获取组件引用
class MyComponent extends Component<MyProps> {
  render() {
    return <div>My Component</div>;
  }
}

render(() => (
  <MyComponent ref={(comp) => console.log('component:', comp)} />
));
```

## 样式处理

支持对象样式和字符串样式：

```tsx
import { render, CSSStyle } from '@knno/jsx';

const myStyle: CSSStyle = {
  fontSize: '20px',
  color: 'red',
  backgroundColor: 'blue',
};

render(() => (
  <div style={myStyle}>
    对象样式
  </div>
));

render(() => (
  <div style="font-size: 20px; color: red;">
    字符串样式
  </div>
));
```

## 更多示例

项目包含完整的示例代码，可以在 `demo/` 目录中找到：

- **browser/**: 浏览器端示例
- **server/**: 服务端渲染示例

运行示例：

```bash
# 构建示例
npm run build:demo

# 启动浏览器示例
npm run start
```

## 开发

### 克隆仓库

```bash
git clone https://gitee.com/thor.qin/knno-jsx.git
cd knno-jsx
```

### 安装依赖

```bash
npm install
```

### 构建

```bash
# 构建所有版本
npm run build

# 仅构建 ESM
npm run build:esm

# 仅构建 CJS
npm run build:cjs
```

### 调试

```bash
# 启动调试模式（带热重载）
npm run debug
```

## 相关资源

- **仓库**: https://gitee.com/thor.qin/knno-jsx.git
- **NPM**: https://www.npmjs.com/package/@knno/jsx
- **许可证**: MIT

## 贡献

欢迎提交 Issue 和 Pull Request！

## 作者

Thor Qin

## 许可证

MIT