继续上一篇 目前先记录过程,后续补充说明
自定义运行时
在 src 目录, 新增 es-runtime.ts 文件,用于自定义 jsx 运行时相关的方法,完整如下:
/**
* 创建 DOM 元素
* @param tag
* @param props
* @param children
* @returns
*/
export function createElement(
tag: string | Function,
props: Record<string, any>,
...children: (HTMLElement | HTMLElement[] | string)[]
): HTMLElement | string {
// 支持函数组件
if (typeof tag === "function") {
return tag(props, children);
}
const element = document.createElement(tag);
// 添加属性
for (let [name, val] of Object.entries(props ?? {})) {
if (name.startsWith("on") && name.toLowerCase() in window) {
element.addEventListener(name.toLowerCase().substring(2), val);
} else if (name === "ref") {
val(element);
} else if (name === "style") {
Object.assign(element.style, val);
} else if (val === true) {
element.setAttribute(name, name);
} else if (val !== false && val != null) {
element.setAttribute(name, val);
} else if (val === false) {
element.removeAttribute(name);
}
}
// 添加子元素
children.forEach((child) => {
appendChild(element, child);
});
return element;
}
/**
* 创建元素片段
* @param _tag
* @param children
* @returns
*/
export function createFragment(
_tag: string,
...children: (HTMLElement | string)[]
): (HTMLElement | string)[] {
return children;
}
/**
* 挂载子元素
* @param parent
* @param child
* @returns
*/
export function appendChild(
parent: HTMLElement,
child: JSX.Children | (() => JSX.Children)
): any {
if (typeof child === "function") return appendChild(parent, child());
if (Array.isArray(child)) {
return child.forEach((nestedChild) => appendChild(parent, nestedChild));
}
if (typeof child === "string") {
return parent.appendChild(document.createTextNode(child));
}
return parent.appendChild(
(child as HTMLElement).nodeType === null
? document.createTextNode(child.toString())
: (child as Node)
);
}
export function initRuntime() {
window.$createElement = createElement;
window.$createFragment = createFragment;
}
配置使用运行时
JSX 全局接口定义
src/vite-env.d.ts
/// <reference types="vite/client" />
declare interface Window {
$createElement(
tag: string | Function,
props: Record<string, any>,
...children: (HTMLElement | HTMLElement[] | string)[]
): HTMLElement | string;
$createFragment(
tag: string,
...children: (HTMLElement | HTMLElement[] | string)[]
): (HTMLElement | string)[];
}
declare namespace JSX {
interface IntrinsicElements {
div: Element;
}
type Element = HTMLElement;
type Children = HTMLElement | HTMLElement[] | string;
}
使用 tsc 编译时的配置
完整 tsconfig.json,修改项见注释
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": [
"ESNext",
"DOM"
],
"moduleResolution": "Node",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true,
"jsx": "react", // 如果不配置这里不会触发编译
"jsxFactory": "$createElement", // 运行时自定义的方法
"jsxFragmentFactory": "$createFragment" // 运行时自定义的方法
},
"include": [
"src"
]
}
使用 Vite(esbuild) 编译时的配置
待补充
示例的TSX文件
src/components/SXEample.tsx
const SXExample = () => (
<>
<div>
Hi SX
<>
<p>row one</p>
<p>row two</p>
</>
</div>
</>
);
export default SXExample;
main.ts
import "./style.css";
import typescriptLogo from "./typescript.svg";
import { setupCounter } from "./counter";
import SXExample from "./components/SXExample";
import { appendChild, initRuntime } from "./sx-runtime";
// 初始化运行时
initRuntime();
const app = document.querySelector<HTMLDivElement>("#app");
app!.innerHTML = `
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://www.typescriptlang.org/" target="_blank">
<img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" />
</a>
<h1>Vite + TypeScript</h1>
<div class="card">
<button id="counter" type="button"></button>
</div>
<p class="read-the-docs">
Click on the Vite and TypeScript logos to learn more
</p>
</div>
`;
// 挂载 dom
appendChild(app!, SXExample);
setupCounter(document.querySelector<HTMLButtonElement>("#counter")!);
运行
jsx基本解析正常
开始 DIM runtime
以下待完善
支持语句模板,例如
/// jsx
const ShowExample = ()=>(
<show when={true} fallback={<div>这是隐藏时的内容</div>}>
<div>这是显示的内容</div>
</show>
)
const EachEaxmple = ()=>(
<each src={dataList}>
<Item ...data />
</each>
)