Repository: anuoua/unis Branch: main Commit: a40e220ce9e6 Files: 145 Total size: 203.9 KB Directory structure: gitextract_322wan4z/ ├── .github/ │ └── workflows/ │ ├── unis-babel-preset.yml │ ├── unis-core.yml │ ├── unis-dom.yml │ ├── unis-router.yml │ ├── unis-transition.yml │ └── unis-vite-preset.yml ├── .gitignore ├── .vscode/ │ └── settings.json ├── LICENSE ├── README-zh_CN.md ├── README.md ├── assets/ │ └── logo.txt ├── package.json ├── packages/ │ ├── unis-babel-preset/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src/ │ │ │ └── index.ts │ │ ├── test/ │ │ │ └── index.test.ts │ │ └── tsconfig.json │ ├── unis-core/ │ │ ├── .gitignore │ │ ├── index.d.ts │ │ ├── jsx-runtime/ │ │ │ ├── jsx-dev-runtime.d.ts │ │ │ ├── jsx-dev-runtime.js │ │ │ ├── jsx-dev-runtime.mjs │ │ │ ├── jsx-runtime.d.ts │ │ │ ├── jsx-runtime.js │ │ │ └── jsx-runtime.mjs │ │ ├── package.json │ │ ├── rollup.config.mjs │ │ ├── src/ │ │ │ ├── api/ │ │ │ │ ├── use.ts │ │ │ │ ├── useContext.ts │ │ │ │ ├── useEffect.ts │ │ │ │ ├── useId.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ ├── useMemo.ts │ │ │ │ ├── useProps.ts │ │ │ │ ├── useReducer.ts │ │ │ │ ├── useRef.ts │ │ │ │ ├── useState.ts │ │ │ │ └── utils.ts │ │ │ ├── commit.ts │ │ │ ├── context.ts │ │ │ ├── createTokTik.ts │ │ │ ├── diff.ts │ │ │ ├── fiber.ts │ │ │ ├── h.ts │ │ │ ├── index.ts │ │ │ ├── reconcile.ts │ │ │ ├── reconcileWalkHooks/ │ │ │ │ ├── context.ts │ │ │ │ ├── effect.ts │ │ │ │ └── preElFiber.ts │ │ │ ├── svg.ts │ │ │ └── utils.ts │ │ ├── test/ │ │ │ └── utils.test.ts │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── types/ │ │ │ └── jsx.d.ts │ │ └── vitest.config.ts │ ├── unis-dom/ │ │ ├── .gitignore │ │ ├── index.d.ts │ │ ├── package.json │ │ ├── rollup.config.mjs │ │ ├── server.d.ts │ │ ├── src/ │ │ │ ├── browser/ │ │ │ │ ├── __test__/ │ │ │ │ │ ├── context.test.tsx │ │ │ │ │ ├── dom.test.tsx │ │ │ │ │ ├── effect.test.tsx │ │ │ │ │ ├── hydrate.test.tsx │ │ │ │ │ ├── memo.test.tsx │ │ │ │ │ ├── portal.test.tsx │ │ │ │ │ ├── reconcile.test.tsx │ │ │ │ │ ├── util.ts │ │ │ │ │ └── utils.test.ts │ │ │ │ ├── const.ts │ │ │ │ ├── index.ts │ │ │ │ ├── operator.ts │ │ │ │ ├── render.ts │ │ │ │ └── toktik.ts │ │ │ └── server/ │ │ │ ├── __test__/ │ │ │ │ └── server.test.tsx │ │ │ ├── index.ts │ │ │ └── operator.ts │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ └── vitest.config.ts │ ├── unis-example/ │ │ ├── .gitignore │ │ ├── index.html │ │ ├── other.d.ts │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── src/ │ │ │ ├── Dialog.tsx │ │ │ ├── Todo.tsx │ │ │ ├── TodoItem/ │ │ │ │ ├── index.module.css │ │ │ │ └── index.tsx │ │ │ ├── Welcome/ │ │ │ │ ├── index.module.css │ │ │ │ └── index.tsx │ │ │ ├── global.css │ │ │ ├── hooks/ │ │ │ │ └── update.ts │ │ │ ├── index.module.css │ │ │ └── index.tsx │ │ ├── tailwind.config.js │ │ ├── tsconfig.json │ │ └── vite.config.js │ ├── unis-router/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package.json │ │ ├── rollup.config.mjs │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── BrowserRouter.tsx │ │ │ │ ├── Link.tsx │ │ │ │ ├── NavLink.tsx │ │ │ │ ├── Outlet.tsx │ │ │ │ ├── Redirect.tsx │ │ │ │ ├── Route.tsx │ │ │ │ └── Routes.tsx │ │ │ ├── context.tsx │ │ │ ├── hooks/ │ │ │ │ ├── uHistory.ts │ │ │ │ ├── uLocation.ts │ │ │ │ ├── uParams.ts │ │ │ │ ├── uRouter.tsx │ │ │ │ └── uTargetPath.tsx │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── utils.test.tsx │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ └── vitest.config.ts │ ├── unis-transition/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package.json │ │ ├── rollup.config.mjs │ │ ├── src/ │ │ │ ├── CSSTransition.ts │ │ │ ├── TransitionGroup.ts │ │ │ ├── hooks/ │ │ │ │ ├── uInstance.ts │ │ │ │ ├── uTransition.ts │ │ │ │ ├── uUpdate.ts │ │ │ │ └── uWatch.ts │ │ │ └── index.ts │ │ └── tsconfig.json │ └── unis-vite-preset/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src/ │ │ └── index.ts │ └── tsconfig.json ├── pnpm-workspace.yaml └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/unis-babel-preset.yml ================================================ name: "@unis/babel-preset CI/CD" on: push: branches: - 'main' paths: - 'packages/unis-babel-preset/**' jobs: "test": runs-on: ubuntu-latest if: ${{contains(github.event.head_commit.message, '(babel-preset):')}} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16.x registry-url: "https://registry.npmjs.org" - uses: pnpm/action-setup@v2.0.1 with: version: 7.9.0 - run: | pnpm install cd packages/unis-core pnpm build cd ../unis-babel-preset pnpm test "publish": needs: test runs-on: ubuntu-latest if: ${{startsWith(github.event.head_commit.message, 'release(babel-preset):')}} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16.x registry-url: "https://registry.npmjs.org" - uses: pnpm/action-setup@v2.0.1 with: version: 7.9.0 - run: | pnpm install cd packages/unis-core pnpm build cd ../unis-babel-preset pnpm build pnpm publish --no-git-checks --access public env: NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .github/workflows/unis-core.yml ================================================ name: "@unis/core CI/CD" on: push: branches: - 'main' paths: - 'packages/unis-core/**' jobs: "test": runs-on: ubuntu-latest if: ${{contains(github.event.head_commit.message, '(core):')}} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16.x registry-url: "https://registry.npmjs.org" - uses: pnpm/action-setup@v2.0.1 with: version: 7.9.0 - run: | pnpm install cd packages/unis-core pnpm test "publish": needs: test runs-on: ubuntu-latest if: ${{startsWith(github.event.head_commit.message, 'release(core):')}} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16.x registry-url: "https://registry.npmjs.org" - uses: pnpm/action-setup@v2.0.1 with: version: 7.1.2 - run: | pnpm install cd packages/unis-core pnpm build pnpm publish --no-git-checks --access public env: NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .github/workflows/unis-dom.yml ================================================ name: "@unis/dom CI/CD" on: push: branches: - 'main' paths: - 'packages/unis-dom/**' jobs: "test": runs-on: ubuntu-latest if: ${{contains(github.event.head_commit.message, '(dom):')}} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16.x registry-url: "https://registry.npmjs.org" - uses: pnpm/action-setup@v2.0.1 with: version: 7.9.0 - run: | pnpm install cd packages/unis-core pnpm build cd ../unis-vite-preset pnpm build cd ../unis-dom pnpm test "publish": needs: test runs-on: ubuntu-latest if: ${{startsWith(github.event.head_commit.message, 'release(dom):')}} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16.x registry-url: "https://registry.npmjs.org" - uses: pnpm/action-setup@v2.0.1 with: version: 7.1.2 - run: | pnpm install cd packages/unis-core pnpm build cd ../unis-dom pnpm build pnpm publish --no-git-checks --access public env: NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .github/workflows/unis-router.yml ================================================ name: "@unis/router CI/CD" on: push: branches: - 'main' paths: - 'packages/unis-router/**' jobs: "test": runs-on: ubuntu-latest if: ${{contains(github.event.head_commit.message, '(router):')}} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16.x registry-url: "https://registry.npmjs.org" - uses: pnpm/action-setup@v2.0.1 with: version: 7.9.0 - run: | pnpm install cd packages/unis-core pnpm build cd ../unis-vite-preset pnpm build cd ../unis-router pnpm test "publish": needs: test runs-on: ubuntu-latest if: ${{startsWith(github.event.head_commit.message, 'release(router):')}} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16.x registry-url: "https://registry.npmjs.org" - uses: pnpm/action-setup@v2.0.1 with: version: 7.9.0 - run: | pnpm install cd packages/unis-core pnpm build cd ../unis-router pnpm build pnpm publish --no-git-checks --access public env: NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .github/workflows/unis-transition.yml ================================================ name: "@unis/transition CI/CD" on: push: branches: - 'main' paths: - 'packages/unis-transition/**' jobs: "publish": runs-on: ubuntu-latest if: ${{startsWith(github.event.head_commit.message, 'release(transition):')}} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16.x registry-url: "https://registry.npmjs.org" - uses: pnpm/action-setup@v2.0.1 with: version: 7.9.0 - run: | pnpm install cd packages/unis-core pnpm build cd ../unis-transition pnpm build pnpm publish --no-git-checks --access public env: NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .github/workflows/unis-vite-preset.yml ================================================ name: "@unis/vite-preset CI/CD" on: push: branches: - 'main' paths: - 'packages/unis-vite-preset/**' jobs: "publish": runs-on: ubuntu-latest if: ${{startsWith(github.event.head_commit.message, 'release(vite-preset):')}} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: 16.x registry-url: "https://registry.npmjs.org" - uses: pnpm/action-setup@v2.0.1 with: version: 7.9.0 - run: | pnpm install cd packages/unis-core pnpm build cd ../unis-vite-preset pnpm build pnpm publish --no-git-checks --access public env: NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .gitignore ================================================ # Dependency directories node_modules/ .DS_Store ================================================ FILE: .vscode/settings.json ================================================ { "editor.defaultFormatter": "esbenp.prettier-vscode", "[typescriptreact]": { "editor.formatOnSave": true }, "[typescript]": { "editor.formatOnSave": true }, "[javascript]": { "editor.formatOnSave": true }, "[markdown]": { "editor.formatOnSave": true }, "editor.tabSize": 2, "editor.insertSpaces": true, "files.associations": { "*.json": "jsonc" } } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021-present anuoua Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README-zh_CN.md ================================================

[![@unis/core CI/CD](https://github.com/anuoua/unis/actions/workflows/unis-core.yml/badge.svg)](https://github.com/anuoua/unis/actions/workflows/unis-core.yml) [![@unis/dom CI/CD](https://github.com/anuoua/unis/actions/workflows/unis-dom.yml/badge.svg)](https://github.com/anuoua/unis/actions/workflows/unis-dom.yml) [![@unis/router CI/CD](https://github.com/anuoua/unis/actions/workflows/unis-router.yml/badge.svg)](https://github.com/anuoua/unis/actions/workflows/unis-router.yml) [![@unis/transition CI/CD](https://github.com/anuoua/unis/actions/workflows/unis-transition.yml/badge.svg)](https://github.com/anuoua/unis/actions/workflows/unis-transition.yml) [![@unis/vite-preset CI/CD](https://github.com/anuoua/unis/actions/workflows/unis-vite-preset.yml/badge.svg)](https://github.com/anuoua/unis/actions/workflows/unis-vite-preset.yml) [![@unis/babel-preset CI/CD](https://github.com/anuoua/unis/actions/workflows/unis-babel-preset.yml/badge.svg)](https://github.com/anuoua/unis/actions/workflows/unis-babel-preset.yml) # Unis Unis 是一款新的前端框架,创新的编译策略打造的组件 API 帮助你更加轻松的创建网页 UI。 ## 性能 ## 安装 ```bash npm i @unis/core @unis/dom ``` ## Vite 开发 ```shell npm i vite @unis/vite-preset -D ``` vite.config.js ```javascript import { defineConfig } from "vite"; import { unisPreset } from "@unis/vite-preset"; export default defineConfig({ plugins: [unisPreset()], }); ``` tsconfig.json ```json { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@unis/core" } } ``` index.html ```javascript ...
``` index.tsx ```javascript function App() { return () =>
hello
; } render(, document.querySelector("#root")); ``` ## 用法 Unis 并不是 React 的复刻,而是保留了 React 使用体验的全新框架,unis 的用法很简单,熟悉 React 的可以很快上手。 ### 组件 在 unis 中组件是一个高阶函数。 ```javascript import { render } from "@unis/dom"; const App = () => { return () => ( // 返回一个函数
hello world
); }; render(, document.querySelector("#root")); ``` ### 组件状态 Unis 中的 `useState` 用法和 React 相似,但是要注意的是 unis 中 `use` 系列方法,定义类型必须为 `let` ,因为 unis 使用了 Callback Reassign 编译策略,[@callback-reassign/rollup-plugin](https://github.com/anuoua/callback-reassign) 帮我们补全了 Callback Reassign 代码。 ```javascript import { useState } from "@unis/core"; const App = () => { let [msg, setMsg] = useState("hello"); /** * Compile to: * * let [msg, setMsg] = useState('hello', ([$0, $1]) => { msg = $0; setMsg = $1 }); */ return () =>
{msg}
; }; ``` ### Props Unis 直接使用 props 会无法获取最新值,所以 unis 提供了 useProps。 ```javascript import { useProps } from "@unis/core"; const App = (p) => { let { some } = useProps(p); /** * Compile to: * * let { some } = useProps(p, ({ some: $0 }) => { some = $0 }); */ return () =>
{some}
; }; ``` ### 副作用 Unis 保留了和 React 基本一致的 `useEffect` 和 `useLayoutEffect` ,但 deps 是一个返回数组的函数。 ```javascript import { useEffect } from "@unis/core"; const App = () => { useEffect( () => { // ... return () => { // 清理... }; }, () => [] // deps 是一个返回数组的函数 ); return () =>
hello
; }; ``` ### 自定义 hook Unis 的自定义 hook ,在有返回值的场景需要搭配 `use` 方法使用,原因则是前面提到的 Callback Reassign 编译策略。自定义 hook 的命名我们约定以小写字母 `u` 开头,目的是用于区分其他函数,同时在 IDE 的提示下更加方便的导入。 ```javascript import { use, useState } from "@unis/core"; // 创建自定义 hook 高阶函数 const uCount = () => { let [count, setCount] = useState(0); const add = () => setCount(count + 1); return () => [count, add]; }; // 通过 `use` 使用 hook function App() { let [count, add] = use(uCount()); /** * Compile to: * * let [count, add] = use(uCount(), ([$0, $1]) => { count = $0; add = $1 }); */ return () =>
{count}
; } ``` ## 特性 ### Fragment ```javascript import { Fragment } from "@unis/core"; function App() { return () => (
); } ``` ### Portal ```javascript import { createPortal } from "@unis/core"; function App() { return () => createPortal(
, document.body); } ``` ### Context ```javascript import { createContext } from "@unis/core"; import { render } from "@unis/dom"; const ThemeContext = createContext("light"); function App() { let theme = useContext(ThemeContext); return () =>
{theme}
; } render( , document.querySelector("#root") ); ``` ## SSR 服务端渲染 服务端 ```javascript import express from "express"; import { renderToString } from "@unis/dom/server"; const app = express(); app.get("/", (req, res) => { const SSR_CONTENT = renderToString(
hello world
); res.send(`
...
${SSR_CONTENT}
`); }); ``` 客户端 ```javascript import { render } from "@unis/dom"; render( , document.querySelector("#root"), true // true 代表使用 hydrate (水合)进行渲染,复用 server 端的内容。 ); ``` ## Todo 项目 完整项目请查看 - [packages/unis-example](packages/unis-example) Todo 示例 - [stackbliz](https://stackblitz.com/edit/vitejs-vite-8hn3pz) 试用 ## API - Core - h - h2 (for jsx2) - Fragment - createPortal - createContext - render - memo - Hooks - use - useProps - useState - useReducer - useContext - useMemo - useEffect - useRef - useId ## License MIT @anuoua ================================================ FILE: README.md ================================================

[![@unis/core CI/CD](https://github.com/anuoua/unis/actions/workflows/unis-core.yml/badge.svg)](https://github.com/anuoua/unis/actions/workflows/unis-core.yml) [![@unis/dom CI/CD](https://github.com/anuoua/unis/actions/workflows/unis-dom.yml/badge.svg)](https://github.com/anuoua/unis/actions/workflows/unis-dom.yml) [![@unis/router CI/CD](https://github.com/anuoua/unis/actions/workflows/unis-router.yml/badge.svg)](https://github.com/anuoua/unis/actions/workflows/unis-router.yml) [![@unis/transition CI/CD](https://github.com/anuoua/unis/actions/workflows/unis-transition.yml/badge.svg)](https://github.com/anuoua/unis/actions/workflows/unis-transition.yml) [![@unis/vite-preset CI/CD](https://github.com/anuoua/unis/actions/workflows/unis-vite-preset.yml/badge.svg)](https://github.com/anuoua/unis/actions/workflows/unis-vite-preset.yml) [![@unis/babel-preset CI/CD](https://github.com/anuoua/unis/actions/workflows/unis-babel-preset.yml/badge.svg)](https://github.com/anuoua/unis/actions/workflows/unis-babel-preset.yml) # Unis [中文](./README-zh_CN.md) Unis is a new front-end framework. Its innovative compilation strategy and component API built help you create web UI more easily. ## Performance ## Installation ```bash npm i @unis/core @unis/dom ``` ## Vite Development ```shell npm i vite @unis/vite-preset -D ``` vite.config.js ```javascript import { defineConfig } from "vite"; import { unisPreset } from "@unis/vite-preset"; export default defineConfig({ plugins: [unisPreset()], }); ``` tsconfig.json ```json { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@unis/core" } } ``` index.html ```javascript ...
``` index.tsx ```javascript function App() { return () =>
hello
; } render(, document.querySelector("#root")); ``` ## Usage Unis is not a replica of React, but a brand new framework that retains the user experience of React. Unis is easy to use, and those who are familiar with React can quickly get started. ### Components In Unis, the component is a higher-order function. ```javascript import { render } from "@unis/dom"; const App = () => { return () => ( // Returns a function
hello world
); }; render(, document.querySelector("#root")); ``` ### Component State The usage of `useState` in Unis is similar to React, but it should be noted that for the `use` method series in Unis, the defined type must be `let`. This is because Unis uses the Callback Reassign compilation strategy, and [@callback-reassign/rollup-plugin](https://github.com/anuoua/callback-reassign) helps us complete the Callback Reassign code. ```javascript import { useState } from "@unis/core"; const App = () => { let [msg, setMsg] = useState("hello"); /** * Compile to: * * let [msg, setMsg] = useState('hello', ([$0, $1]) => { msg = $0; setMsg = $1 }); */ return () =>
{msg}
; }; ``` ### Props Directly using `props` in Unis will be unable to get the latest value, so Unis provides `useProps`. ```javascript import { useProps } from "@unis/core"; const App = (p) => { let { some } = useProps(p); /** * Compile to: * * let { some } = useProps(p, ({ some: $0 }) => { some = $0 }); */ return () =>
{some}
; }; ``` ### Side Effects Unis retains the familiar `useEffect` and `useLayoutEffect` methods from React, but the `deps` parameter is a function that returns an array. ```javascript import { useEffect } from "@unis/core"; const App = () => { useEffect( () => { // ... return () => { // Clean up... }; }, () => [] // deps is a function that returns an array ); return () =>
hello
; }; ``` ### Custom Hook For Unis' custom hooks that have a return value, the `use` method should be used accordingly, due to the Callback Reassign compilation strategy mentioned earlier. We conventionally name custom hooks with a lowercase `u` at the beginning, to differentiate them from other functions and make them easy to import with IDE hints. ```javascript import { use, useState } from "@unis/core"; // Create a higher-order function for the custom hook const uCount = () => { let [count, setCount] = useState(0); const add = () => setCount(count + 1); return () => [count, add]; }; // Use the hook through `use` function App() { let [count, add] = use(uCount()); /** * Compile to: * * let [count, add] = use(uCount(), ([$0, $1]) => { count = $0; add = $1 }); */ return () =>
{count}
; } ``` ## Features ### Fragment ```javascript import { Fragment } from "@unis/core"; function App() { return () => (
); } ``` ### Portal ```javascript import { createPortal } from "@unis/core"; function App() { return () => createPortal(
, document.body); } ``` ### Context ```javascript import { createContext } from "@unis/core"; import { render } from "@unis/dom"; const ThemeContext = createContext("light"); function App() { let theme = useContext(ThemeContext); return () =>
{theme}
; } render( , document.querySelector("#root") ); ``` ## Server-Side Rendering Server ```javascript import express from "express"; import { renderToString } from "@unis/dom/server"; const app = express(); app.get("/", (req, res) => { const SSR_CONTENT = renderToString(
hello world
); res.send(`
...
${SSR_CONTENT}
`); }); ``` Client ```javascript import { render } from "@unis/dom"; render( , document.querySelector("#root"), true // true means using hydration to render and reuse the server-side rendered content. ); ``` ## Todo Project See complete project at - [packages/unis-example](packages/unis-example) Todo example - [stackbliz](https://stackblitz.com/edit/vitejs-vite-8hn3pz) Try it out ## API - Core - h - h2 (for jsx2) - Fragment - createPortal - createContext - render - memo - Hooks - use - useProps - useState - useReducer - useContext - useMemo - useEffect - useRef - useId ## License MIT @anuoua ================================================ FILE: assets/logo.txt ================================================ ████ ██ █ █ █ ███ █ █ █ ████ ██████ █ ██ ██ ██ ██ ███ █ █ █ ██ ██ ███ █ █ ██ ████ ██████████ ================================================ FILE: package.json ================================================ { "name": "unis", "version": "0.0.0", "description": "", "private": true, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "workspaces": { "packages": [ "./packages/*" ] }, "repository": { "type": "git", "url": "git+https://github.com/anuoua/unis.git" }, "author": "anuoua", "license": "MIT", "bugs": { "url": "https://github.com/anuoua/unis/issues" }, "homepage": "https://github.com/anuoua/unis#readme" } ================================================ FILE: packages/unis-babel-preset/.gitignore ================================================ build/ ================================================ FILE: packages/unis-babel-preset/README.md ================================================ # Unis Babel Preset Unis develop preset for babel. ## Install ```shell npm add -D @unis/babel-preset ``` ## Usage .babelrc.json or babel.config.js ```javascript { "presets": ["@unis/babel-preset"] } ``` If you use @babel/preset-env, please use relatively new targets. There is a bug in the babel transformation of destructuring syntax. e.g. ```javascript { "presets": [ [ "@babel/preset-env", { targets: "> 0.25%, not dead", } ], "@unis/babel-preset" ] } ``` ================================================ FILE: packages/unis-babel-preset/package.json ================================================ { "name": "@unis/babel-preset", "version": "0.0.2", "description": "Unis babel preset", "main": "build/index.js", "module": "build/index.mjs", "types": "build/index.d.ts", "typings": "build/index.d.ts", "scripts": { "build": "rimraf build && rollup --config && tsc", "build:dev": "cross-env NODE_ENV=development pnpm build", "test": "vitest run" }, "exports": { ".": { "require": "./build/index.js", "import": "./build/index.mjs" } }, "keywords": [ "babel", "preset", "unis" ], "files": [ "build" ], "author": "anuoua", "license": "MIT", "bugs": { "url": "https://github.com/anuoua/unis/issues" }, "homepage": "https://github.com/anuoua/unis/tree/main/packages/unis-babel-preset", "peerDependencies": { "@unis/core": "workspace:^" }, "dependencies": { "@babel/plugin-syntax-jsx": "^7.21.4", "@babel/plugin-transform-react-jsx": "^7.21.0", "@callback-reassign/babel-plugin": "^0.0.1" }, "devDependencies": { "@babel/core": "^7.21.4", "@rollup/plugin-node-resolve": "^13.0.6", "@types/babel__core": "^7.20.0", "@unis/core": "workspace:^", "cross-env": "^7.0.3", "esbuild": "^0.13.13", "rimraf": "^3.0.2", "rollup": "^2.72.0", "rollup-plugin-esbuild": "^4.6.0", "typescript": "^4.4.4", "vite": "^4.2.1", "vitest": "^0.29.8" } } ================================================ FILE: packages/unis-babel-preset/rollup.config.js ================================================ import { defineConfig } from "rollup"; import { nodeResolve } from "@rollup/plugin-node-resolve"; import esbuild from "rollup-plugin-esbuild"; const configGen = (format) => defineConfig({ input: "src/index.ts", external: [ /^@unis/, "@callback-reassign/rollup-plugin", "@babel/plugin-syntax-jsx", "@babel/plugin-transform-react-jsx", ], output: [ { dir: "build", entryFileNames: `index.${format === "esm" ? "mjs" : "js"}`, format, sourcemap: true, }, ], plugins: [ nodeResolve({ modulesOnly: true, }), esbuild({ sourceMap: true, minify: process.env.NODE_ENV === "development" ? false : true, target: "esnext", }), ], }); const config = [configGen("cjs"), configGen("esm")]; export default config; ================================================ FILE: packages/unis-babel-preset/src/index.ts ================================================ import { unisFns } from "@unis/core"; import reassign from "@callback-reassign/babel-plugin"; // @ts-ignore import syntaxJsx from "@babel/plugin-syntax-jsx"; // @ts-ignore import transformReactJsx from "@babel/plugin-transform-react-jsx"; export default function unisPreset() { return { plugins: [ syntaxJsx, [ transformReactJsx, { runtime: "automatic", importSource: "@unis/core", }, ], [ reassign, { targetFns: { "@unis/core": unisFns, }, }, ], ], }; } ================================================ FILE: packages/unis-babel-preset/test/index.test.ts ================================================ import { it, expect } from "vitest"; import { transform } from "@babel/core"; import unisPreset from "../src/index"; const code = ` import { useState } from "@unis/core"; let [a, seta] = useState(1); `; const transformed = `import { useState } from "@unis/core"; let [a, seta] = useState(1, ([$0, $1]) => { a = $0; seta = $1; });`; it("transform", () => { const result = transform(code, { presets: [unisPreset], }); expect(result?.code).toBe(transformed); }); ================================================ FILE: packages/unis-babel-preset/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "emitDeclarationOnly": true, "declarationMap": false, "outDir": "build" }, "include": ["src"] } ================================================ FILE: packages/unis-core/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next # Nuxt.js build / generate output .nuxt dist build # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and *not* Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port ================================================ FILE: packages/unis-core/index.d.ts ================================================ import "./jsx-runtime/jsx-runtime"; import "./jsx-runtime/jsx-dev-runtime"; export * from "./dist"; ================================================ FILE: packages/unis-core/jsx-runtime/jsx-dev-runtime.d.ts ================================================ declare module "@unis/core/jsx-dev-runtime"; ================================================ FILE: packages/unis-core/jsx-runtime/jsx-dev-runtime.js ================================================ const { h2, Fragment } = require("../dist/index.js"); exports.jsxDEV = h2; exports.Fragment = Fragment; ================================================ FILE: packages/unis-core/jsx-runtime/jsx-dev-runtime.mjs ================================================ export { h2 as jsxDEV, Fragment } from "../dist/index.mjs"; ================================================ FILE: packages/unis-core/jsx-runtime/jsx-runtime.d.ts ================================================ declare module "@unis/core/jsx-runtime"; ================================================ FILE: packages/unis-core/jsx-runtime/jsx-runtime.js ================================================ const { h2, Fragment } = require("../dist/index.mjs"); exports.jsx = h2; exports.jsxs = h2; exports.Fragment = Fragment; ================================================ FILE: packages/unis-core/jsx-runtime/jsx-runtime.mjs ================================================ export { h2 as jsx, h2 as jsxs, Fragment } from "../dist/index.mjs"; ================================================ FILE: packages/unis-core/package.json ================================================ { "name": "@unis/core", "version": "1.2.5", "description": "Unis is a simpler and easier to use front-end framework than React", "main": "dist/index.js", "module": "dist/index.mjs", "types": "index.d.ts", "typings": "index.d.ts", "exports": { ".": { "require": "./dist/index.js", "import": "./dist/index.mjs" }, "./jsx-runtime": { "require": "./jsx-runtime/jsx-runtime.js", "import": "./jsx-runtime/jsx-runtime.mjs" }, "./jsx-dev-runtime": { "require": "./jsx-runtime/jsx-dev-runtime.js", "import": "./jsx-runtime/jsx-dev-runtime.mjs" } }, "scripts": { "build": "rimraf build && rimraf dist && tsc -p tsconfig.build.json && rollup --config", "build:dev": "cross-env NODE_ENV=development pnpm build", "test": "vitest run --coverage", "test:watch": "vitest -w" }, "repository": { "type": "git", "url": "git+https://github.com/anuoua/unis.git" }, "keywords": [ "frontend", "web", "framwork" ], "files": [ "dist", "jsx-runtime", "index.d.ts" ], "author": "anuoua", "license": "MIT", "bugs": { "url": "https://github.com/anuoua/unis/issues" }, "homepage": "https://github.com/anuoua/unis#readme", "dependencies": { "@types/prop-types": "^15.7.5", "@types/scheduler": "^0.16.3", "csstype": "^3.1.2" }, "devDependencies": { "@rollup/plugin-node-resolve": "^15.0.2", "@types/jsdom": "^21.1.1", "@vitest/coverage-c8": "^0.29.8", "cross-env": "^7.0.3", "esbuild": "^0.17.15", "jsdom": "^21.1.1", "rimraf": "^4.4.1", "rollup": "^3.20.2", "rollup-plugin-dts": "^5.3.0", "rollup-plugin-esbuild": "^5.0.0", "tslib": "^2.5.0", "typescript": "^5.0.3", "vite": "^4.2.1", "vitest": "^0.29.8" } } ================================================ FILE: packages/unis-core/rollup.config.mjs ================================================ import dts from "rollup-plugin-dts"; import esbuild from "rollup-plugin-esbuild"; import { defineConfig } from "rollup"; import { nodeResolve } from "@rollup/plugin-node-resolve"; const configGen = (format) => defineConfig({ input: "src/index.ts", output: [ { dir: "dist", entryFileNames: `index.${format === "esm" ? "mjs" : "js"}`, format, sourcemap: true, }, ], plugins: [ nodeResolve(), esbuild({ sourceMap: true, target: "esnext", }), ], }); const dtsRollup = () => defineConfig({ input: "build/index.d.ts", output: [{ file: `dist/index.d.ts`, format: "es" }], plugins: [dts()], }); const config = [configGen("cjs"), configGen("esm"), dtsRollup()]; export default config; ================================================ FILE: packages/unis-core/src/api/use.ts ================================================ import { getWF } from "./utils"; export function use any>(fn: T): ReturnType; export function use any>( fn: T, raFn: Function ): ReturnType; export function use any>(fn: T, raFn?: Function) { const workingFiber = getWF(); const effect = () => { const result = fn(getWF()); return raFn?.(result); }; workingFiber.stateEffects?.push(effect) ?? (workingFiber.stateEffects = [effect]); return fn(workingFiber) as ReturnType; } ================================================ FILE: packages/unis-core/src/api/useContext.ts ================================================ import { Context } from "../context"; import { Fiber } from "../fiber"; import { use } from "./use"; export function useContext(ctx: T) { return use(contextHof(ctx), arguments[1]); } const contextHof = (context: T) => { const readContext = (fiber: Fiber): T["initial"] => { const { dependencyList = [] } = fiber.reconcileState!; const result = [...dependencyList] .reverse() .find((d) => d.context === context); if (result) { if (fiber.dependencies) { !fiber.dependencies.includes(result) && fiber.dependencies.push(result); } else { fiber.dependencies = [result]; } return result.value; } else { return context.initial; } }; return (WF: Fiber) => readContext(WF); }; ================================================ FILE: packages/unis-core/src/api/useEffect.ts ================================================ import { Effect, EFFECT_TYPE, getWF } from "./utils"; export const useEffect = (cb: Effect, depsFn?: () => any[]) => { const workingFiber = getWF(); cb.depsFn = depsFn; if (!cb.type) cb.type = EFFECT_TYPE.TICK; workingFiber.effects?.push(cb) ?? (workingFiber.effects = [cb]); }; ================================================ FILE: packages/unis-core/src/api/useId.ts ================================================ import { Fiber } from "../fiber"; import { getWF } from "./utils"; import { use } from "./use"; import { generateId } from "../utils"; export const idHof = () => { let workingFiber = getWF(); if (!workingFiber.id) { workingFiber.id = generateId(); } return (WF: Fiber) => WF.id; }; export function useId() { return use(idHof()); } ================================================ FILE: packages/unis-core/src/api/useLayoutEffect.ts ================================================ import { useEffect } from "./useEffect"; import { Effect, EFFECT_TYPE } from "./utils"; export const useLayoutEffect = (cb: Effect, depsFn?: () => any[]) => { cb.type = EFFECT_TYPE.LAYOUT; return useEffect(cb, depsFn); }; ================================================ FILE: packages/unis-core/src/api/useMemo.ts ================================================ import { use } from "./use"; import { Fiber, MemorizeState } from "../fiber"; import { arraysEqual } from "../utils"; import { addDispatchEffect, linkMemorizeState } from "./useReducer"; export const memoHof = ( handler: () => T, depsFn?: () => any[] ) => { let freshFiber: Fiber | undefined; let freshDeps: any[]; let freshMemorizeState: MemorizeState | undefined; let memorizeState: MemorizeState = { value: undefined, deps: undefined!, }; const effect = () => { memorizeState = freshMemorizeState!; memorizeState.deps = freshDeps; freshFiber = undefined; freshMemorizeState = undefined; }; return (WF: Fiber) => { freshFiber = WF; freshDeps = depsFn?.() ?? freshDeps; addDispatchEffect(freshFiber, effect); freshMemorizeState = { value: depsFn && arraysEqual(memorizeState.deps, freshDeps) ? memorizeState.value : handler(), deps: memorizeState?.deps, }; linkMemorizeState(freshFiber, freshMemorizeState); return freshMemorizeState.value as T; }; }; export function useMemo( handler: () => T, depsFn?: () => any[] ) { return use(memoHof(handler, depsFn), arguments[2]); } ================================================ FILE: packages/unis-core/src/api/useProps.ts ================================================ import { Fiber } from "../fiber"; import { use } from "./use"; export function useProps(p: T) { return use(propsHof(p), arguments[1]); } export const propsHof = (props: T) => { return (WF: Fiber) => WF.props as T; }; ================================================ FILE: packages/unis-core/src/api/useReducer.ts ================================================ import { Effect, markFiber } from "./utils"; import { use } from "./use"; import { Fiber, findRoot, findRuntime, MemorizeState, TokTik } from "../fiber"; import { readyForWork } from "../reconcile"; export type Reducer = (state: T, action: T2) => T; const readyList: (() => Fiber)[] = []; const triggerReconcile = () => { const fibers = new Set(readyList.map((getFiber) => getFiber())); fibers.forEach(markFiber); // multiple app trigger same time const rootFibers = new Set(Array.from(fibers).map(findRoot)); rootFibers.forEach((fiber) => readyForWork(fiber)); readyList.length = 0; }; export const reducerHof = ( reducerFn: Reducer, initial: T ) => { let currentFiber: Fiber | undefined; // do not getWF here, workingFiber should be assigned in effect. let freshFiber: Fiber | undefined; let freshMemorizeState: MemorizeState | undefined; let toktik: TokTik | undefined; let memorizeState: MemorizeState = { value: undefined, dispatchValue: initial, deps: [initial], }; const dispatch = (action: T2) => { if (!currentFiber) return console.warn("Component is not created"); if (currentFiber.isDestroyed) return console.warn("Component has been destroyed"); const newState = reducerFn(memorizeState.value, action); if (Object.is(newState, memorizeState.value)) return; memorizeState.dispatchValue = newState; memorizeState.deps = [newState]; if (freshFiber) { toktik!.clearTikTaskQueue(); } readyList.push(() => currentFiber!); if (readyList.length === 1) { toktik!.addTok(triggerReconcile, true); } }; const effect: Effect = () => { currentFiber = freshFiber!; memorizeState = freshMemorizeState!; if (!toktik) toktik = findRuntime(currentFiber).toktik; freshFiber = undefined; freshMemorizeState = undefined; }; return (WF: Fiber) => { freshFiber = WF; addDispatchEffect(freshFiber, effect); freshMemorizeState = { value: memorizeState.deps.length > 0 ? memorizeState.dispatchValue : memorizeState.value, deps: [], }; linkMemorizeState(freshFiber, freshMemorizeState); return [freshMemorizeState.value, dispatch] as const; }; }; export function useReducer(reducerFn: Reducer, initial: T) { return use(reducerHof(reducerFn, initial), arguments[2]); } export const addDispatchEffect = (freshFiber: Fiber, effect: Effect) => { freshFiber.reconcileState!.dispatchEffectList?.push(effect) ?? (freshFiber.reconcileState!.dispatchEffectList = [effect]); }; export const linkMemorizeState = ( freshFiber: Fiber, freshMemorizeState: MemorizeState ) => { if (freshFiber.memorizeState) { const first = freshFiber.memorizeState.next; freshFiber.memorizeState.next = freshMemorizeState; freshMemorizeState.next = first; } else { freshFiber.memorizeState = freshMemorizeState; freshMemorizeState.next = freshMemorizeState; } }; export const cutMemorizeState = (fiber: Fiber) => { const first = fiber.memorizeState?.next; fiber.memorizeState && (fiber.memorizeState.next = undefined); fiber.memorizeState = first; }; ================================================ FILE: packages/unis-core/src/api/useRef.ts ================================================ export interface Ref { current: T; } export function useRef(): Ref; export function useRef(value: T): Ref; export function useRef(value?: T) { return { current: value }; } ================================================ FILE: packages/unis-core/src/api/useState.ts ================================================ import { use } from "./use"; import { reducerHof } from "./useReducer"; export const stateHof = (initial: T) => { return reducerHof((preState, action) => action, initial); }; export function useState(): [ T | undefined, (value: T | undefined) => void ]; export function useState(initial: T): [T, (value: T) => void]; export function useState(initial?: T) { return use(stateHof(initial), arguments[1]); } ================================================ FILE: packages/unis-core/src/api/utils.ts ================================================ import { Fiber, FLAG, mergeFlag } from "../fiber"; import { getWorkingFiber } from "../reconcile"; import { arraysEqual } from "../utils"; export enum EFFECT_TYPE { LAYOUT = "layout", TICK = "tick", } export type Effect = (() => (() => void) | void) & { type?: EFFECT_TYPE; clear?: (() => void) | void; depsFn?: () => any; deps?: any; }; export const getWF = (): Fiber | never => { const workingFiber = getWorkingFiber(); if (workingFiber) { return workingFiber; } else { throw Error("Do not call use function outside of component"); } }; export const markFiber = (workingFiber: Fiber) => { workingFiber.flag = mergeFlag(workingFiber.flag, FLAG.UPDATE); let iFiber: Fiber | undefined = workingFiber; while ((iFiber = iFiber.parent)) { if (iFiber.childFlag) break; iFiber.childFlag = mergeFlag(iFiber.childFlag, FLAG.UPDATE); } }; export const runStateEffects = (fiber: Fiber) => { for (const effect of fiber.stateEffects ?? []) { effect(); } }; export const effectDepsEqual = (effect: Effect) => { const deps = effect.depsFn?.(); const equal = arraysEqual(deps, effect.deps); effect.deps = deps; return equal; }; export const clearEffects = (effects?: Effect[]) => { if (!effects) return; for (const effect of effects) { effect.clear?.(); } }; export const runEffects = (effects?: Effect[]) => { if (!effects) return; for (const effect of effects) { effect.clear = effect(); } }; export const clearAndRunEffects = (effects?: Effect[]) => { if (!effects) return; for (const effect of effects) { if (effectDepsEqual(effect)) continue; effect.clear?.(); effect.clear = effect(); } }; ================================================ FILE: packages/unis-core/src/commit.ts ================================================ import { clearEffects } from "./api/utils"; import { Fiber, findEls, FLAG, getContainerElFiber, graft, isComponent, isHostElement, matchFlag, isText, isElement, ReconcileState, isPortal, Operator, } from "./fiber"; export const commitDeletion = (fiber: Fiber, operator: Operator) => { let iFiber: Fiber | undefined = fiber; while (iFiber) { if (isHostElement(iFiber)) { iFiber.props.ref && (iFiber.props.ref.current = undefined); } if (isComponent(iFiber)) { clearEffects(iFiber.effects); } /** * remove input element may trigger blur sync event, * so isDestroyed must be true before remove to prevent dispatch in useReducer. */ iFiber.isDestroyed = true; if (isPortal(iFiber)) { iFiber.child && operator.remove(iFiber.child); } iFiber.dependencies = undefined; iFiber.reconcileState = undefined; if (iFiber.child) { iFiber = iFiber.child; continue; } else if (iFiber === fiber) { iFiber = undefined; continue; } while (iFiber) { if (iFiber.sibling) { iFiber = iFiber.sibling; break; } if (iFiber.parent !== fiber) { iFiber = iFiber.parent; } else { iFiber = undefined; break; } } } operator.remove(fiber); }; export const commitUpdate = (fiber: Fiber, operator: Operator) => { if (isText(fiber)) operator.updateTextProperties(fiber); if (isHostElement(fiber)) operator.updateElementProperties(fiber); }; export const commitInsert = (fiber: Fiber, operator: Operator) => { const container = getContainerElFiber(fiber)!; const insertElements = isElement(fiber) ? [fiber.el!] : findEls( matchFlag(fiber.commitFlag, FLAG.REUSE) ? fiber.alternate! : fiber ); const insertTarget = isPortal(container) ? null : fiber.preElFiber ? operator.nextSibling(fiber.preElFiber) : operator.firstChild(container); for (const insertElement of insertElements) { operator.insertBefore(container, insertElement, insertTarget); } }; export const commit = (reconcileState: ReconcileState) => { const { operator } = reconcileState.rootWorkingFiber.runtime!; for (const fiber of reconcileState.commitList) { if (matchFlag(fiber.commitFlag, FLAG.DELETE)) { commitDeletion(fiber.alternate!, operator); continue; } if (matchFlag(fiber.commitFlag, FLAG.UPDATE)) { commitUpdate(fiber, operator); } if (matchFlag(fiber.commitFlag, FLAG.INSERT)) { commitInsert(fiber, operator); } if (matchFlag(fiber.commitFlag, FLAG.REUSE)) { graft(fiber, fiber.alternate!); } fiber.preElFiber = undefined; fiber.alternate = undefined; fiber.commitFlag = undefined; } }; ================================================ FILE: packages/unis-core/src/context.ts ================================================ import { useProps } from "./api/useProps"; import { useContext } from "./api/useContext"; import { PROVIDER, Fiber } from "./fiber"; export interface Context { Provider: (props: { value: T; children: any }) => JSX.Element; Consumer: (props: { children: (value: T) => JSX.Element }) => JSX.Element; initial: T; } export interface Dependency { context: Context; value: T; } const providerContextMap = new WeakMap(); export const createDependency = (fiber: Fiber) => { return { context: providerContextMap.get(fiber.tag as Function)!, value: fiber.props.value, }; }; export const findDependency = (fiber: Fiber, contextFiber: Fiber) => fiber.dependencies?.find( (dependency) => dependency.context === providerContextMap.get(contextFiber.tag as Function) ); export function createContext(initial: T): Context; export function createContext(initial: T) { const Provider = (props: { value: T; children: any }) => props.children; Provider.take = { type: PROVIDER, }; const Consumer = (props: { children: (value: T) => JSX.Element }) => { let p = useProps( props, // @ts-ignore ($) => (p = $) ); let state = useContext( context, // @ts-ignore ($) => (state = $) ); return () => p.children(state); }; const context: Context = { Provider, Consumer, initial, }; providerContextMap.set(Provider, context); return context; } ================================================ FILE: packages/unis-core/src/createTokTik.ts ================================================ export type Task = Function & { isTok?: any }; export const createTokTik = (options: { nextTick: (cb: VoidFunction, pending: boolean) => void; now: () => number; interval?: number; }) => { const { nextTick, now, interval = 4 } = options; const timeSlicing = !!interval; let lastTime: number = 0; let looping = false; const tikQueue: Task[] = []; const tokQueue: Task[] = []; const next = () => tikQueue[0] ?? tokQueue[0]; const pick = () => tikQueue.shift() ?? tokQueue.shift(); const loop = (task: Task): void => { looping = true; runTask(task); const nextTask = next(); if (nextTask) { if (shouldYield() || nextTask.isTok) { nextTick(() => loop(pick()!), nextTask.isTok); } else { loop(pick()!); } return; } looping = false; }; const runTask = timeSlicing ? (task: Task) => { lastTime = now(); return task(); } : (task: Task) => task(); const addTok = (task: Task, pending = false) => { task.isTok = true; looping ? tokQueue.push(task) : pending ? nextTick(() => loop(task), pending) : loop(task); }; const addTik = (task: Task) => { looping && tikQueue.push(task); }; const clearTikTaskQueue = () => (tikQueue.length = 0); const shouldYield = timeSlicing ? () => now() - lastTime > interval : () => false; return { addTok, addTik, clearTikTaskQueue, shouldYield, }; }; ================================================ FILE: packages/unis-core/src/diff.ts ================================================ import { clearFlag, Fiber, FLAG, isElement, matchFlag, isPortal, isSame, mergeFlag, isComponent, ReconcileState, isText, findToRoot, } from "./fiber"; import { classes, isNullish, isStr, isEvent, keys, styleStr, svgKey, } from "./utils"; export type AttrDiff = [string, any, any][]; export const attrDiff = ( newFiber: Record, oldFiber: Record, onlyEvent = false ) => { const diff: AttrDiff = []; const newProps = newFiber.props; const oldProps = oldFiber.props; const getRealAttr = (attr: string) => { if (attr === "className") return "class"; if (attr === "htmlFor") return "for"; if (newFiber.isSvg) return svgKey(attr); return attr.toLowerCase(); }; const getRealValue = (newValue: any, key: string) => { if (isNullish(newValue)) return; switch (key) { case "className": return isStr(newValue) ? newValue : classes(newValue); case "style": return isStr(newValue) ? newValue : styleStr(newValue as Partial); default: return newValue; } }; for (const key of keys({ ...newProps, ...oldProps })) { if (onlyEvent && !isEvent(key)) continue; if (["xmlns", "children"].includes(key)) continue; const newValue = newProps[key]; const oldValue = oldProps[key]; const realNewValue = getRealValue(newValue, key); const realOldValue = getRealValue(oldValue, key); if ( !isNullish(newValue) && !isNullish(oldValue) && realNewValue === realOldValue ) continue; diff.push([getRealAttr(key), realNewValue, realOldValue]); } return diff; }; export const clone = (newFiber: Fiber, oldFiber: Fiber, commitFlag?: FLAG) => Object.assign( { ...newFiber, commitFlag, alternate: oldFiber, }, isComponent(newFiber) ? { renderFn: oldFiber.renderFn, rendered: oldFiber.rendered, stateEffects: oldFiber.stateEffects, effects: oldFiber.effects, id: oldFiber.id, } : isElement(newFiber) ? { el: oldFiber.el, isSvg: oldFiber.isSvg } : isPortal(newFiber) ? { to: oldFiber.to } : undefined ); export const reuse = (newFiber: Fiber, oldFiber: Fiber, commitFlag?: FLAG) => ({ ...newFiber, commitFlag, alternate: oldFiber, }); export const del = (oldFiber: Fiber): Fiber => ({ commitFlag: FLAG.DELETE, alternate: oldFiber, }); export const create = ( newFiber: Fiber, parentFiber: Fiber, hydrate = false ) => { const retFiber = { ...newFiber, commitFlag: matchFlag(parentFiber.commitFlag, FLAG.CREATE) ? FLAG.CREATE : FLAG.CREATE | FLAG.INSERT, } as Fiber; if (isElement(newFiber)) { retFiber.isSvg = newFiber.tag === "svg" || parentFiber.isSvg; if (!hydrate) { retFiber.attrDiff = isText(retFiber) ? undefined : attrDiff(retFiber, { props: {} }); } } if (isPortal(newFiber)) { retFiber.commitFlag = undefined; } return retFiber; }; export const keyIndexMapGen = ( children: Fiber[], start: number, end: number ) => { const map: any = {}; for (let i = start; i <= end; i++) { const key = children[i].props?.key; if (key !== undefined) map[key] = i; } return map; }; const determineCommitFlag = ( parentFiber: Fiber, newFiber: Fiber, oldFiber: Fiber, flag?: FLAG ) => { /** * the nearest parent component fiber */ const nearestComponent = isComponent(parentFiber) ? parentFiber : findToRoot(parentFiber, (fiber) => isComponent(fiber)); /** * when nearest parent component fiber with FLAG.UPDATE commitFlag, it should be FLAG.UPDATE. */ let commitFlag = !matchFlag(nearestComponent?.commitFlag, FLAG.UPDATE) && parentFiber.alternate!.childFlag ? !oldFiber.childFlag && !oldFiber.flag ? FLAG.REUSE : oldFiber.flag : FLAG.UPDATE; /** * when memo fiber compare result is true, it should be FLAG.REUSE. */ if ( isComponent(oldFiber) && !oldFiber.childFlag && !oldFiber.flag && (oldFiber.tag as Function & { compare?: Function }).compare?.( newFiber.props, oldFiber.props ) ) { commitFlag = mergeFlag(commitFlag, FLAG.REUSE); } if (isElement(newFiber) && matchFlag(commitFlag, FLAG.UPDATE)) { let diff = attrDiff(newFiber, oldFiber); if (diff.length) { newFiber.attrDiff = diff; } else { commitFlag = clearFlag(commitFlag, FLAG.UPDATE); } } flag && (commitFlag = mergeFlag(commitFlag, flag)); /** * portal don't need commitFlag */ if (isPortal(newFiber)) { commitFlag = undefined; } if (matchFlag(commitFlag, FLAG.REUSE)) { commitFlag = clearFlag(commitFlag, FLAG.UPDATE); } return commitFlag; }; const getSameNewFiber = ( parentFiber: Fiber, newFiber: Fiber, oldFiber: Fiber, flag?: FLAG ) => { const commitFlag = determineCommitFlag(parentFiber, newFiber, oldFiber, flag); return matchFlag(commitFlag, FLAG.REUSE) ? reuse(newFiber, oldFiber, commitFlag) : clone(newFiber, oldFiber, commitFlag); }; export const diff = ( parentFiber: Fiber, oldChildren: Fiber[] = [], newChildren: Fiber[] = [] ) => { const { reconcileState } = parentFiber as { reconcileState: ReconcileState }; let cloneChildren: Fiber[] = []; let newStartIndex = 0; let newEndIndex = newChildren.length - 1; let oldStartIndex = 0; let oldEndIndex = oldChildren.length - 1; let newStartFiber = newChildren[newStartIndex]; let newEndFiber = newChildren[newEndIndex]; let oldStartFiber = oldChildren[oldStartIndex]; let oldEndFiber = oldChildren[oldEndIndex]; let preStartFiber: Fiber | undefined; let preEndFiber: Fiber | undefined; const deletion = (fiber: Fiber) => { reconcileState.commitList.push(del(fiber)); }; const forward = () => { if (preStartFiber) preStartFiber.sibling = newStartFiber; newStartFiber.parent = parentFiber; newStartFiber.index = newStartIndex; newStartFiber.reconcileState = reconcileState; preStartFiber = newStartFiber; cloneChildren[newStartIndex] = newStartFiber; newStartFiber = newChildren[++newStartIndex]; }; const forwardEnd = () => { if (preEndFiber) newEndFiber.sibling = preEndFiber; newEndFiber.parent = parentFiber; newEndFiber.index = newEndIndex; newEndFiber.reconcileState = reconcileState; preEndFiber = newEndFiber; cloneChildren[newEndIndex] = newEndFiber; newEndFiber = newChildren[--newEndIndex]; }; const oldForward = () => { oldStartFiber = oldChildren[++oldStartIndex]; }; const oldForwardEnd = () => { oldEndFiber = oldChildren[--oldEndIndex]; }; let keyIndexMap: any; while (newStartIndex <= newEndIndex && oldStartIndex <= oldEndIndex) { if (oldStartFiber === undefined) { oldForward(); } else if (oldEndFiber === undefined) { oldForwardEnd(); } else if (isSame(newStartFiber, oldStartFiber)) { newStartFiber = getSameNewFiber( parentFiber, newStartFiber, oldStartFiber ); forward(); oldForward(); } else if (isSame(newEndFiber, oldEndFiber)) { newEndFiber = getSameNewFiber(parentFiber, newEndFiber, oldEndFiber); forwardEnd(); oldForwardEnd(); } else if (isSame(newStartFiber, oldEndFiber)) { newStartFiber = getSameNewFiber( parentFiber, newStartFiber, oldEndFiber, FLAG.INSERT ); forward(); oldForwardEnd(); } else if (isSame(newEndFiber, oldStartFiber)) { newEndFiber = getSameNewFiber( parentFiber, newEndFiber, oldStartFiber, FLAG.INSERT ); forwardEnd(); oldForward(); } else { if (!keyIndexMap) { keyIndexMap = keyIndexMapGen(oldChildren, oldStartIndex, oldEndIndex); } const index = keyIndexMap[newStartFiber.props.key]; if (isNaN(index)) { newStartFiber = create(newStartFiber, parentFiber); } else { const targetFiber = oldChildren[index]; if (isSame(newStartFiber, targetFiber)) { newStartFiber = getSameNewFiber( parentFiber, newStartFiber, targetFiber, FLAG.INSERT ); oldChildren[index] = undefined as unknown as Fiber; } else { newStartFiber = create(newStartFiber, parentFiber); } } forward(); } } if (oldStartIndex > oldEndIndex) { newChildren.slice(newStartIndex, newEndIndex + 1).forEach((fiber) => { newStartFiber = create( newStartFiber, parentFiber, reconcileState.hydrate ); forward(); }); } else if (newStartIndex > newEndIndex) { oldChildren .slice(oldStartIndex, oldEndIndex + 1) .forEach((fiber) => fiber && deletion(fiber)); } if (preStartFiber && preEndFiber) preStartFiber.sibling = preEndFiber; parentFiber.child = cloneChildren[0]; parentFiber.children = cloneChildren; }; ================================================ FILE: packages/unis-core/src/fiber.ts ================================================ import { Effect } from "./api/utils"; import { Dependency } from "./context"; import { AttrDiff } from "./diff"; import { isFun, isNullish } from "./utils"; export interface ReconcileState { rootWorkingFiber: Fiber; dispatchEffectList: Effect[]; commitList: Fiber[]; dependencyList: Dependency[]; workingPreElFiber?: Fiber; hydrate: boolean; hydrateEl?: FiberEl; } export enum FLAG { CREATE = 1 << 1, INSERT = 1 << 2, UPDATE = 1 << 3, DELETE = 1 << 4, REUSE = 1 << 5, } export type FlagName = "flag" | "childFlag" | "commitFlag"; export const mergeFlag = (a: FLAG | undefined, b: FLAG) => isNullish(a) ? b : a | b; export const clearFlag = (a: FLAG | undefined, b: FLAG) => isNullish(a) ? a : a & ~b; export const matchFlag = (a: FLAG | undefined, b: FLAG) => isNullish(a) ? false : a & b; export type FiberEl = unknown; export type FiberType = | string | Function | Symbol | ((...p: any[]) => () => any); export interface MemorizeState { value: any; dispatchValue?: any; deps: any[]; next?: MemorizeState; } export interface TokTik { addTok: (task: Function, pending?: boolean) => void; addTik: (task: Function) => void; clearTikTaskQueue: () => void; shouldYield: () => boolean; } export interface Operator { // for reuse element when hydrate nextElement(el: FiberEl): FiberEl | null; // for reuse element when hydrate matchElement(fiber: Fiber, el: FiberEl): boolean; createElement(fiber: Fiber): FiberEl; remove(fiber: Fiber): void; insertBefore( containerFiber: Fiber, insertElement: FiberEl, targetElement: FiberEl | null ): void; firstChild(fiber: Fiber): FiberEl | null; nextSibling(fiber: Fiber): FiberEl | null; updateTextProperties(fiber: Fiber): void; updateElementProperties(fiber: Fiber): void; } export interface Runtime { toktik: TokTik; operator: Operator; } export interface Fiber { id?: string; parent?: Fiber; child?: Fiber; sibling?: Fiber; index?: number; to?: FiberEl; el?: FiberEl; preElFiber?: Fiber; isSvg?: boolean; isDestroyed?: boolean; props?: any; compare?: Function; attrDiff?: AttrDiff; alternate?: Fiber; tag?: string | Function; type?: Symbol; renderFn?: Function; rendered?: any; flag?: FLAG; childFlag?: FLAG; commitFlag?: FLAG; children?: Fiber[]; stateEffects?: Effect[]; effects?: Effect[]; dependencies?: Dependency[]; reconcileState?: ReconcileState; memorizeState?: MemorizeState; runtime?: Runtime; } export const createFiber = (options: Partial = {}) => Object.assign( { id: undefined, parent: undefined, child: undefined, sibling: undefined, index: undefined, to: undefined, el: undefined, preElFiber: undefined, isSvg: undefined, isDestroyed: undefined, props: undefined, compare: undefined, attrDiff: undefined, alternate: undefined, tag: undefined, type: undefined, renderFn: undefined, rendered: undefined, flag: undefined, childFlag: undefined, commitFlag: undefined, children: undefined, stateEffects: undefined, effects: undefined, dependencies: undefined, reconcileState: undefined, memorizeState: undefined, }, options ); export const TEXT = Symbol("$$Text"); export const ELEMENT = Symbol("$$Element"); export const PORTAL = Symbol("$$Portal"); export const PROVIDER = Symbol("$$Provider"); export const COMPONENT = Symbol("$$Component"); export const isText = (fiber: Fiber) => fiber.type === TEXT; export const isHostElement = (fiber: Fiber) => fiber.type === ELEMENT; export const isElement = (fiber: Fiber) => isHostElement(fiber) || isText(fiber); export const isPortal = (fiber: Fiber) => fiber.type === PORTAL; export const isProvider = (fiber: Fiber) => fiber.type === PROVIDER; export const isCustomComponent = (fiber: Fiber) => fiber.type === COMPONENT; export const isComponent = (fiber: Fiber) => isFun(fiber.tag); export const isSame = (fiber1?: Fiber, fiber2?: Fiber) => fiber1 && fiber2 && fiber1.tag === fiber2.tag && fiber1.props?.key === fiber2.props?.key; export interface WalkHook { enter?: (currentFiber: Fiber, skipChild: boolean) => any; down?: (currentFiber: Fiber, nextFiber: Fiber) => any; sibling?: (currentFiber: Fiber, nextFiber?: Fiber) => any; up?: (currentFiber: Fiber, nextFiber?: Fiber) => any; return?: (currentFiber?: Fiber) => any; } export type WalkHookKeys = keyof WalkHook; export type WalkHookList = { [K in keyof WalkHook]: WalkHook[K][]; }; export const createNext = () => { const walkHooks: WalkHookList = {}; const addHook = (walkHook: WalkHook) => { Object.entries(walkHook).forEach(([key, value]) => { const list = walkHooks[key as WalkHookKeys]; list ? list.push(value) : (walkHooks[key as WalkHookKeys] = [value]); }); }; const runWalkHooks = ( key: T, ...args: Parameters[T]> ) => { return walkHooks[key]?.map((hook) => hook!(...(args as [any, any]))); }; const next = (fiber: Fiber, skipChild = false): Fiber | undefined => { if (runWalkHooks("enter", fiber, skipChild)?.includes(false)) return; const { child } = fiber; let nextFiber: Fiber | undefined = fiber; if (child && !skipChild) { runWalkHooks("down", nextFiber, child); nextFiber = child; } else { while (nextFiber) { const { sibling, parent } = nextFiber as Fiber; if (sibling) { runWalkHooks("sibling", nextFiber, sibling); nextFiber = sibling; break; } if (runWalkHooks("up", nextFiber, parent)?.includes(false)) { nextFiber = undefined; break; } nextFiber = parent; } } runWalkHooks("return", nextFiber); return nextFiber; }; return [next, addHook] as const; }; export const graft = (newFiber: Fiber, oldFiber: Fiber) => { const parent = newFiber.parent!; const parentChildren = parent.children!; const index = newFiber.index!; const preIndex = index - 1; if (index === 0) parent.child = oldFiber; if (preIndex >= 0) parentChildren[preIndex].sibling = oldFiber; parentChildren[index] = oldFiber; oldFiber.sibling = newFiber.sibling; oldFiber.parent = parent; }; export const findEls = (fiber: Fiber, findInPortal = false) => { const els: FiberEl[] = []; isElement(fiber) ? els.push(fiber.el!) : isPortal(fiber) && !findInPortal ? false : fiber.children?.forEach((child) => { els.push(...findEls(child, findInPortal)); }); return els; }; export const findLastElFiber = (fiber: Fiber): Fiber | undefined => { if (isElement(fiber)) { return fiber; } else if (isPortal(fiber)) { return undefined; } else { for (let i = 0; i < (fiber.children?.length ?? 0); i++) { return findLastElFiber(fiber.children!.at(-(i + 1))!); } } }; export type ContainerElement = Exclude; export const getContainerElFiber = ( fiber: Fiber | undefined ): Fiber | undefined => { while ((fiber = fiber?.parent)) { if (isPortal(fiber) || isElement(fiber)) return fiber; } }; export const findToRoot = ( fiber: Fiber | undefined, cb: (fiber: Fiber) => boolean ): Fiber | undefined => { while ((fiber = fiber?.parent)) { if (cb(fiber)) return fiber; } }; export const findRoot = (fiber: Fiber) => findToRoot(fiber, (fiber) => !fiber.parent)!; export const findRuntime = (fiber: Fiber) => fiber.reconcileState?.rootWorkingFiber ? fiber.reconcileState.rootWorkingFiber.runtime! : findRoot(fiber).runtime!; ================================================ FILE: packages/unis-core/src/h.ts ================================================ import { isNum, isStr, keys, toArray } from "./utils"; import { COMPONENT, createFiber, ELEMENT, Fiber, FiberEl, PORTAL, TEXT, } from "./fiber"; export const h = (tag: any, props: any, ...children: any[]) => { props = { ...props }; if (children.length === 1) props.children = children[0]; if (children.length > 1) props.children = children; return createFiber({ tag, type: isStr(tag) ? ELEMENT : COMPONENT, props, ...tag.take, }); }; export const h2 = (tag: any, props: any, key?: string | number) => { if (key !== undefined) props.key = key; return createFiber({ tag, type: isStr(tag) ? ELEMENT : COMPONENT, props, ...tag.take, }); }; export const formatChildren = (children: any) => { const formatedChildren: Fiber[] = []; for (const child of toArray(children)) { if ([null, false, true, undefined].includes(child)) { continue; } else { Array.isArray(child) ? formatedChildren.push(...formatChildren(child)) : formatedChildren.push( isStr(child) || isNum(child) ? createFiber({ type: TEXT, props: { nodeValue: child }, }) : child ); } } return formatedChildren; }; export const createRoot = (element: any, container: FiberEl): Fiber => { return { tag: (container as any).tagName.toLocaleLowerCase(), type: ELEMENT, el: container, index: 0, props: { children: toArray(element), }, }; }; export const createPortal = (child: JSX.Element, container: FiberEl) => createFiber({ type: PORTAL, props: { children: child }, to: container, }); const defaultCompare = (newProps: any = {}, oldProps: any = {}) => { const newKeys = keys(newProps); const oldKeys = keys(oldProps); if (newKeys.length !== oldKeys.length) return false; return newKeys.every((key) => Object.is(newProps[key], oldProps[key])); }; export const memo = < T extends ((props: any) => JSX.Element) & { compare?: Function } >( child: T, compare: Function = defaultCompare ) => { child.compare = compare; return child; }; export const cloneElement = ( element: Fiber, props = {}, ...children: JSX.Element[] ) => h(element.tag, { ...props, ...props }, ...children); export const createElement = h; export const Fragment = (props: any) => props.children; export const FGMT = Fragment; ================================================ FILE: packages/unis-core/src/index.ts ================================================ export * from "./h"; export * from "./fiber"; export * from "./context"; export * from "./reconcile"; export * from "./utils"; export * from "./diff"; export * from "./createTokTik"; export * from "./api/utils"; export * from "./api/use"; export * from "./api/useState"; export * from "./api/useContext"; export * from "./api/useProps"; export * from "./api/useReducer"; export * from "./api/useMemo"; export * from "./api/useEffect"; export * from "./api/useLayoutEffect"; export * from "./api/useId"; export * from "./api/useRef"; export type * from "../types/jsx"; export const unisFns = { use: 1, useState: 1, useProps: 1, useContext: 1, useReducer: 2, useMemo: 2, }; ================================================ FILE: packages/unis-core/src/reconcile.ts ================================================ import { clearAndRunEffects, clearEffects, Effect, EFFECT_TYPE, effectDepsEqual, runEffects, runStateEffects, } from "./api/utils"; import { createNext, Fiber, FLAG, ReconcileState, isComponent, createFiber, matchFlag, findRuntime, isElement, } from "./fiber"; import { commit } from "./commit"; import { preElFiberWalkHook } from "./reconcileWalkHooks/preElFiber"; import { effectWalkHook } from "./reconcileWalkHooks/effect"; import { formatChildren } from "./h"; import { isFun } from "./utils"; import { diff } from "./diff"; import { contextWalkHook } from "./reconcileWalkHooks/context"; import { cutMemorizeState } from "./api/useReducer"; let workingFiber: Fiber | undefined; export const getWorkingFiber = () => workingFiber; export const setWorkingFiber = (fiber: Fiber | undefined) => (workingFiber = fiber); // reconcile walker const [next, addHook] = createNext(); // preEl addHook(preElFiberWalkHook); // effect addHook(effectWalkHook); // context addHook(contextWalkHook); export const readyForWork = (rootCurrentFiber: Fiber, hydrate = false) => { rootCurrentFiber.runtime!.toktik.addTok(() => performWork(rootCurrentFiber, hydrate) ); }; const performWork = (rootCurrentFiber: Fiber, hydrate: boolean) => { const rootWorkingFiber = createFiber({ index: rootCurrentFiber.index, tag: rootCurrentFiber.tag, type: rootCurrentFiber.type, props: rootCurrentFiber.props, alternate: rootCurrentFiber, el: rootCurrentFiber.el, runtime: rootCurrentFiber.runtime, }); const initialReconcileState: ReconcileState = { rootWorkingFiber, dispatchEffectList: [], commitList: [], dependencyList: [], workingPreElFiber: undefined, hydrate, hydrateEl: rootCurrentFiber.el, }; rootWorkingFiber.reconcileState = initialReconcileState; setWorkingFiber(rootWorkingFiber); tickWork(rootWorkingFiber!); }; const tickWork = (workingFiber: Fiber) => { const { toktik } = findRuntime(workingFiber); let iFiber: Fiber | undefined = workingFiber; // work loop while (iFiber && !toktik.shouldYield()) { const isReuse = !!matchFlag(iFiber.commitFlag, FLAG.REUSE); { !isReuse && update(iFiber); !isReuse && isElement(iFiber) && compose(iFiber); complete(iFiber); } iFiber = next(iFiber, isReuse); setWorkingFiber(iFiber); } if (iFiber) { toktik.addTik(() => { setWorkingFiber(iFiber); tickWork(iFiber!); }); } else { const { reconcileState } = workingFiber; // switch dispatch bind fiber runEffects(reconcileState!.dispatchEffectList); // commit commit(reconcileState!); // call component effects callComponentEffects(reconcileState!); // clear reconcileState for (const prop of Object.keys(reconcileState!)) { delete reconcileState![prop as keyof ReconcileState]; } } }; const callComponentEffects = (reconcileState: ReconcileState) => { const { commitList, rootWorkingFiber } = reconcileState!; const { toktik } = rootWorkingFiber.runtime!; const triggeredLayoutEffects: Effect[] = []; const tickEffects: Effect[] = []; // clear and run layoutEffects for (const fiber of commitList) { if (!isComponent(fiber)) continue; for (const effect of fiber.effects ?? []) { if (effect.type === EFFECT_TYPE.TICK) { tickEffects.push(effect); } else { const equal = effectDepsEqual(effect); if (!equal) { triggeredLayoutEffects.push(effect); clearEffects([effect]); } } } } // run triggered layout effects runEffects(triggeredLayoutEffects); // clear and run tick effects toktik.addTik(() => clearAndRunEffects(tickEffects)); }; const update = (fiber: Fiber) => { if (isComponent(fiber)) { updateComponent(fiber); } else { updateHost(fiber); } }; const updateHost = (fiber: Fiber) => { diff(fiber, fiber.alternate?.children, formatChildren(fiber.props.children)); }; const updateComponent = (fiber: Fiber) => { if (!fiber.renderFn) { fiber.renderFn = fiber.tag as Function; let rendered = fiber.renderFn(fiber.props); if (isFun(rendered)) { fiber.renderFn = rendered; rendered = fiber.renderFn!(); } fiber.rendered = formatChildren(rendered); } else { runStateEffects(fiber); if (matchFlag(fiber.commitFlag, FLAG.UPDATE)) { fiber.rendered = formatChildren(fiber.renderFn(fiber.props)); } else { /** * this condition, means `fiber.alternate` is on childFlag marked chain, and `fiber.commitFlag` is undefined. * diff will keep going on. */ } } cutMemorizeState(fiber); diff(fiber, fiber.alternate?.children, fiber.rendered); }; const compose = (fiber: Fiber) => { const { hydrate, hydrateEl } = fiber.reconcileState!; const { operator } = findRuntime(fiber); if (hydrate && hydrateEl) { if (!operator.matchElement(fiber, hydrateEl)) throw new Error("Hydrate failed!"); fiber.el = hydrateEl; fiber.reconcileState!.hydrateEl = operator.nextElement(hydrateEl); } else if (matchFlag(fiber.commitFlag, FLAG.CREATE) && !hydrate) { fiber.el = operator.createElement(fiber); fiber.attrDiff?.length && operator.updateElementProperties(fiber); let iFiber: Fiber | undefined = fiber; while ((iFiber = iFiber.parent)) { if (!matchFlag(iFiber.commitFlag, FLAG.CREATE)) break; if (isElement(iFiber)) { operator.insertBefore(iFiber, fiber.el, null); break; } } } }; const complete = (fiber: Fiber) => { !fiber.commitFlag && (fiber.alternate = undefined); }; ================================================ FILE: packages/unis-core/src/reconcileWalkHooks/context.ts ================================================ import { createDependency, findDependency } from "../context"; import { createNext, Fiber, isProvider, WalkHook } from "../fiber"; import { markFiber } from "../api/utils"; export const contextWalkHook: WalkHook = { down: (from: Fiber, to?: Fiber) => { isProvider(from) && from.reconcileState!.dependencyList.push(createDependency(from)); }, up: (from: Fiber, to?: Fiber) => { to && isProvider(to) && from.reconcileState!.dependencyList.pop(); }, enter: (enter: Fiber, skipChild: boolean) => { if ( enter.alternate && isProvider(enter.alternate) && !Object.is(enter.alternate.props.value, enter.props.value) ) { let alternate = enter.alternate; let iFiber: Fiber | undefined = alternate; const [next, addHook] = createNext(); addHook({ up: (from, to) => to !== alternate }); do { findDependency(iFiber, enter) && markFiber(iFiber); iFiber = next( iFiber, iFiber !== alternate && isProvider(iFiber) && iFiber.tag === enter.tag ); } while (iFiber); } }, }; ================================================ FILE: packages/unis-core/src/reconcileWalkHooks/effect.ts ================================================ import { Fiber, WalkHook } from "../fiber"; export const pushEffect = (fiber: Fiber) => { fiber.reconcileState!.commitList.push(fiber); }; export const effectWalkHook: WalkHook = { up: (from, to) => { !from.child && from.commitFlag && pushEffect(from); to?.commitFlag && pushEffect(to); }, sibling: (from) => { !from.child && from.commitFlag && pushEffect(from); }, }; ================================================ FILE: packages/unis-core/src/reconcileWalkHooks/preElFiber.ts ================================================ import { Fiber, findLastElFiber, FLAG, isElement, isPortal, matchFlag, WalkHook, } from "../fiber"; const setWorkingPreElFiber = ( fiber: Fiber, workingPreElFiber: Fiber | undefined ) => { if (fiber.reconcileState) fiber.reconcileState.workingPreElFiber = workingPreElFiber; }; const setReuseFiberPreElFiber = (fiber: Fiber) => { if (!matchFlag(fiber.commitFlag, FLAG.REUSE)) return; const lastElFiber = findLastElFiber(fiber.alternate!); lastElFiber && setWorkingPreElFiber(fiber, lastElFiber); }; export const preElFiberWalkHook: WalkHook = { down: (from: Fiber, to?: Fiber) => { isElement(from) && setWorkingPreElFiber(from, undefined); isPortal(from) && setWorkingPreElFiber(from, undefined); }, up: (from: Fiber, to?: Fiber) => { if (from && !from.child) { isElement(from) && setWorkingPreElFiber(from, from); } if (to) { isElement(to) && setWorkingPreElFiber(from, to); isPortal(to) && setWorkingPreElFiber(from, to.preElFiber); } setReuseFiberPreElFiber(from); }, sibling: (from: Fiber, to?: Fiber) => { if (matchFlag(from.commitFlag, FLAG.REUSE)) { setReuseFiberPreElFiber(from); } else { isElement(from) && setWorkingPreElFiber(from, from); } }, return: (retn?: Fiber) => { if (retn && matchFlag(retn.commitFlag, FLAG.INSERT)) retn.preElFiber = retn.reconcileState!.workingPreElFiber; }, }; ================================================ FILE: packages/unis-core/src/svg.ts ================================================ // kebab svg attr keys export const displayAttrs = [ "baselineShift", "alignmentBaseline", "clip", "clipPath", "clipRule", "color", "colorInterpolation", "colorInterpolationFilters", "colorProfile", "colorRendering", "cursor", "direction", "display", "dominantBaseline", "enableBackground", "fill", "fillOpacity", "fillRule", "filter", "floodColor", "floodOpacity", "fontFamily", "fontSize", "fontSizeAdjust", "fontStretch", "fontStyle", "fontVariant", "fontWeight", "glyphOrientationHorizontal", "glyphOrientationVertical", "imageRendering", "kerning", "letterSpacing", "lightingColor", "markerEnd", "markerMid", "markerStart", "mask", "opacity", "overflow", "pointerEvents", "shapeRendering", "stopColor", "stopOpacity", "stroke", "strokeDasharray", "strokeDashoffset", "strokeLinecap", "strokeLinejoin", "strokeMiterlimit", "strokeOpacity", "strokeWidth", "textAnchor", "transform", "textDecoration", "textRendering", "unicodeBidi", "vectorEffect", "visibility", "wordSpacing", "writingMode", ]; ================================================ FILE: packages/unis-core/src/utils.ts ================================================ import type { CSArray, CSObject } from "../types/jsx"; import { displayAttrs } from "./svg"; export const keys = Object.keys; export const type = (a: any) => Object.prototype.toString.bind(a)().slice(8, -1); export const isFun = (a: any): a is Function => typeof a === "function"; export const isStr = (a: any): a is string => typeof a === "string"; export const isNum = (a: any): a is number => typeof a === "number"; export const isBool = (a: any): a is boolean => typeof a === "boolean"; export const isSymbol = (a: any): a is boolean => typeof a === "symbol"; export const isArray = Array.isArray; export const isObject = (a: any): a is object => type(a) === "Object"; export const isNullish = (a: any): a is null | undefined => a == null; export const isEvent = (a: string) => a.startsWith("on"); export const getEventName = (event: string) => { const [, eventName, capture] = event.match(/^on(.*)(Capture)?$/)!; return [eventName.toLowerCase(), !!capture] as const; }; export const camel2kebab = (text: string) => text.replace(/([A-Z])/g, "-$1").toLowerCase(); export const toArray = (a: T) => (Array.isArray(a) ? a : [a]); export const arraysEqual = (a: any, b: any) => { if (a == null || b == null) return false; if (a.length !== b.length) return false; for (var i = 0; i < a.length; ++i) { if (!Object.is(a[i], b[i])) return false; } return true; }; export const styleStr = (style: Partial) => keys(style) .map( (key) => `${camel2kebab(key)}: ${style[key as keyof CSSStyleDeclaration]}` ) .join("; ") + ";"; export const svgKey = (key: string) => { for (const str of ["xmlns", "xml", "xlink"]) { if (key.startsWith(str)) return key.toLowerCase().replace(str, `${str}:`); } return displayAttrs.includes(key) ? camel2kebab(key) : key; }; export const classes = (cs: CSArray | CSObject): string => { const objectClasses = (objcs: Record) => keys(objcs) .reduce((pre, cur) => pre + " " + (objcs[cur] ? cur : ""), "") .trim(); const arrayClasses = (arrcs: CSArray) => arrcs .reduce( (pre: string, cur) => pre + " " + `${ isNum(cur) || isStr(cur) ? cur : isObject(cur) ? objectClasses(cur) : isArray(cur) ? classes(cur) : "" }`, "" ) .trim(); return isArray(cs) ? arrayClasses(cs) : objectClasses(cs); }; let overflow = ""; let count = 0; export const generateId = () => { if (count === Number.MAX_SAFE_INTEGER) { overflow += count.toString(32); count = 0; } return `${overflow}${(count++).toString(32)}`; }; ================================================ FILE: packages/unis-core/test/utils.test.ts ================================================ /** * @vitest-environment jsdom */ import { expect, it } from "vitest"; import { classes, svgKey, styleStr } from "../src/utils"; it("classes", () => { expect(classes(["a", "b", 1, ["c", { d: true }]])).toBe("a b 1 c d"); expect(classes({ a: true, b: undefined, c: null })).toBe("a"); expect(classes({ a: false, b: true, c: null })).toBe("b"); }); it("realSVGAttr", () => { expect(svgKey("glyphOrientationVertical")).toBe("glyph-orientation-vertical"); }); it("style2String", () => { expect(styleStr({ background: "yellow", fontSize: "14px" })).toBe( "background: yellow; font-size: 14px;" ); }); ================================================ FILE: packages/unis-core/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "include": ["src"], "exclude": ["**/*.test.*"] } ================================================ FILE: packages/unis-core/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "build" }, "include": ["src", "test", "jsx-runtime", "types/jsx.d.ts"] } ================================================ FILE: packages/unis-core/types/jsx.d.ts ================================================ // Note: this file is auto concatenated to the end of the bundled d.ts during // build. // This code is based on react definition in DefinitelyTyped published under the MIT license. // Repository: https://github.com/DefinitelyTyped/DefinitelyTyped // Path in the repository: types/react/index.d.ts // // Copyrights of original definition are: // AssureSign // Microsoft // John Reilly // Benoit Benezech // Patricio Zavolinsky // Digiguru // Eric Anderson // Dovydas Navickas // Josh Rutherford // Guilherme Hübner // Ferdy Budhidharma // Johann Rakotoharisoa // Olivier Pascal // Martin Hochel // Frank Li // Jessica Franco // Saransh Kataria // Kanitkorn Sujautra // Sebastian Silbermann /** * The MIT License (MIT) Copyright (c) 2018-present, Yuxi (Evan) You Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- MIT License Copyright (c) Microsoft Corporation. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE * */ import * as CSS from "csstype"; export interface FiberNode { tag?: string | Function; type?: Symbol; props?: any; } export interface CSSProperties extends CSS.Properties, CSS.PropertiesHyphen { /** * The index signature was removed to enable closed typing for style * using CSSType. You're able to use type assertion or module augmentation * to add properties or an index signature of your own. * * For examples and more information, visit: * https://github.com/frenic/csstype#what-should-i-do-when-i-get-type-errors */ } type Booleanish = boolean | "true" | "false"; type Numberish = number | string; // All the WAI-ARIA 1.1 attributes from https://www.w3.org/TR/wai-aria-1.1/ interface AriaAttributes { /** Identifies the currently active element when DOM focus is on a composite widget, textbox, group, or application. */ "aria-activedescendant"?: string; /** Indicates whether assistive technologies will present all, or only parts of, the changed region based on the change notifications defined by the aria-relevant attribute. */ "aria-atomic"?: Booleanish; /** * Indicates whether inputting text could trigger display of one or more predictions of the user's intended value for an input and specifies how predictions would be * presented if they are made. */ "aria-autocomplete"?: "none" | "inline" | "list" | "both"; /** Indicates an element is being modified and that assistive technologies MAY want to wait until the modifications are complete before exposing them to the user. */ "aria-busy"?: Booleanish; /** * Indicates the current "checked" state of checkboxes, radio buttons, and other widgets. * @see aria-pressed @see aria-selected. */ "aria-checked"?: Booleanish | "mixed"; /** * Defines the total number of columns in a table, grid, or treegrid. * @see aria-colindex. */ "aria-colcount"?: Numberish; /** * Defines an element's column index or position with respect to the total number of columns within a table, grid, or treegrid. * @see aria-colcount @see aria-colspan. */ "aria-colindex"?: Numberish; /** * Defines the number of columns spanned by a cell or gridcell within a table, grid, or treegrid. * @see aria-colindex @see aria-rowspan. */ "aria-colspan"?: Numberish; /** * Identifies the element (or elements) whose contents or presence are controlled by the current element. * @see aria-owns. */ "aria-controls"?: string; /** Indicates the element that represents the current item within a container or set of related elements. */ "aria-current"?: Booleanish | "page" | "step" | "location" | "date" | "time"; /** * Identifies the element (or elements) that describes the object. * @see aria-labelledby */ "aria-describedby"?: string; /** * Identifies the element that provides a detailed, extended description for the object. * @see aria-describedby. */ "aria-details"?: string; /** * Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable. * @see aria-hidden @see aria-readonly. */ "aria-disabled"?: Booleanish; /** * Indicates what functions can be performed when a dragged object is released on the drop target. * @deprecated in ARIA 1.1 */ "aria-dropeffect"?: "none" | "copy" | "execute" | "link" | "move" | "popup"; /** * Identifies the element that provides an error message for the object. * @see aria-invalid @see aria-describedby. */ "aria-errormessage"?: string; /** Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed. */ "aria-expanded"?: Booleanish; /** * Identifies the next element (or elements) in an alternate reading order of content which, at the user's discretion, * allows assistive technology to override the general default of reading in document source order. */ "aria-flowto"?: string; /** * Indicates an element's "grabbed" state in a drag-and-drop operation. * @deprecated in ARIA 1.1 */ "aria-grabbed"?: Booleanish; /** Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element. */ "aria-haspopup"?: | Booleanish | "menu" | "listbox" | "tree" | "grid" | "dialog"; /** * Indicates whether the element is exposed to an accessibility API. * @see aria-disabled. */ "aria-hidden"?: Booleanish; /** * Indicates the entered value does not conform to the format expected by the application. * @see aria-errormessage. */ "aria-invalid"?: Booleanish | "grammar" | "spelling"; /** Indicates keyboard shortcuts that an author has implemented to activate or give focus to an element. */ "aria-keyshortcuts"?: string; /** * Defines a string value that labels the current element. * @see aria-labelledby. */ "aria-label"?: string; /** * Identifies the element (or elements) that labels the current element. * @see aria-describedby. */ "aria-labelledby"?: string; /** Defines the hierarchical level of an element within a structure. */ "aria-level"?: Numberish; /** Indicates that an element will be updated, and describes the types of updates the user agents, assistive technologies, and user can expect from the live region. */ "aria-live"?: "off" | "assertive" | "polite"; /** Indicates whether an element is modal when displayed. */ "aria-modal"?: Booleanish; /** Indicates whether a text box accepts multiple lines of input or only a single line. */ "aria-multiline"?: Booleanish; /** Indicates that the user may select more than one item from the current selectable descendants. */ "aria-multiselectable"?: Booleanish; /** Indicates whether the element's orientation is horizontal, vertical, or unknown/ambiguous. */ "aria-orientation"?: "horizontal" | "vertical"; /** * Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship * between DOM elements where the DOM hierarchy cannot be used to represent the relationship. * @see aria-controls. */ "aria-owns"?: string; /** * Defines a short hint (a word or short phrase) intended to aid the user with data entry when the control has no value. * A hint could be a sample value or a brief description of the expected format. */ "aria-placeholder"?: string; /** * Defines an element's number or position in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM. * @see aria-setsize. */ "aria-posinset"?: Numberish; /** * Indicates the current "pressed" state of toggle buttons. * @see aria-checked @see aria-selected. */ "aria-pressed"?: Booleanish | "mixed"; /** * Indicates that the element is not editable, but is otherwise operable. * @see aria-disabled. */ "aria-readonly"?: Booleanish; /** * Indicates what notifications the user agent will trigger when the accessibility tree within a live region is modified. * @see aria-atomic. */ "aria-relevant"?: | "additions" | "additions text" | "all" | "removals" | "text"; /** Indicates that user input is required on the element before a form may be submitted. */ "aria-required"?: Booleanish; /** Defines a human-readable, author-localized description for the role of an element. */ "aria-roledescription"?: string; /** * Defines the total number of rows in a table, grid, or treegrid. * @see aria-rowindex. */ "aria-rowcount"?: Numberish; /** * Defines an element's row index or position with respect to the total number of rows within a table, grid, or treegrid. * @see aria-rowcount @see aria-rowspan. */ "aria-rowindex"?: Numberish; /** * Defines the number of rows spanned by a cell or gridcell within a table, grid, or treegrid. * @see aria-rowindex @see aria-colspan. */ "aria-rowspan"?: Numberish; /** * Indicates the current "selected" state of various widgets. * @see aria-checked @see aria-pressed. */ "aria-selected"?: Booleanish; /** * Defines the number of items in the current set of listitems or treeitems. Not required if all elements in the set are present in the DOM. * @see aria-posinset. */ "aria-setsize"?: Numberish; /** Indicates if items in a table or grid are sorted in ascending or descending order. */ "aria-sort"?: "none" | "ascending" | "descending" | "other"; /** Defines the maximum allowed value for a range widget. */ "aria-valuemax"?: Numberish; /** Defines the minimum allowed value for a range widget. */ "aria-valuemin"?: Numberish; /** * Defines the current value for a range widget. * @see aria-valuetext. */ "aria-valuenow"?: Numberish; /** Defines the human readable text alternative of aria-valuenow for a range widget. */ "aria-valuetext"?: string; } // All the WAI-ARIA 1.1 role attribute values from https://www.w3.org/TR/wai-aria-1.1/#role_definitions type AriaRole = | "alert" | "alertdialog" | "application" | "article" | "banner" | "button" | "cell" | "checkbox" | "columnheader" | "combobox" | "complementary" | "contentinfo" | "definition" | "dialog" | "directory" | "document" | "feed" | "figure" | "form" | "grid" | "gridcell" | "group" | "heading" | "img" | "link" | "list" | "listbox" | "listitem" | "log" | "main" | "marquee" | "math" | "menu" | "menubar" | "menuitem" | "menuitemcheckbox" | "menuitemradio" | "navigation" | "none" | "note" | "option" | "presentation" | "progressbar" | "radio" | "radiogroup" | "region" | "row" | "rowgroup" | "rowheader" | "scrollbar" | "search" | "searchbox" | "separator" | "slider" | "spinbutton" | "status" | "switch" | "tab" | "table" | "tablist" | "tabpanel" | "term" | "textbox" | "timer" | "toolbar" | "tooltip" | "tree" | "treegrid" | "treeitem" | (string & {}); // Vue's style normalization supports nested arrays export type StyleValue = string | CSSProperties | Array; export type CSValue = string | number | boolean | undefined | null; export type CSObject = Record; export type CSArray = (CSValue | CSObject | CSArray)[]; export interface HTMLAttributes extends AriaAttributes, EventHandlers { // Standard HTML Attributes accessKey?: string | undefined; className?: string | CSObject | CSArray | undefined; contentEditable?: Booleanish | "inherit" | undefined; contextMenu?: string | undefined; dir?: string | undefined; draggable?: Booleanish | undefined; hidden?: boolean | undefined; id?: string | undefined; lang?: string | undefined; placeholder?: string | undefined; slot?: string | undefined; spellCheck?: Booleanish | undefined; style?: CSSProperties | string | undefined; tabIndex?: number | undefined; title?: string | undefined; translate?: "yes" | "no" | undefined; // Unknown radioGroup?: string | undefined; // , // WAI-ARIA role?: AriaRole | undefined; // RDFa Attributes about?: string | undefined; datatype?: string | undefined; inlist?: any; prefix?: string | undefined; property?: string | undefined; resource?: string | undefined; typeof?: string | undefined; vocab?: string | undefined; // Non-standard Attributes autoCapitalize?: string | undefined; autoCorrect?: string | undefined; autoSave?: string | undefined; color?: string | undefined; itemProp?: string | undefined; itemScope?: boolean | undefined; itemType?: string | undefined; itemID?: string | undefined; itemRef?: string | undefined; results?: number | undefined; security?: string | undefined; unselectable?: "on" | "off" | undefined; // Living Standard /** * Hints at the type of data that might be entered by the user while editing the element or its contents * @see https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute */ inputMode?: | "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search" | undefined; /** * Specify that a standard HTML element should behave like a defined custom built-in element * @see https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is */ is?: string | undefined; } type HTMLAttributeReferrerPolicy = | "" | "no-referrer" | "no-referrer-when-downgrade" | "origin" | "origin-when-cross-origin" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin" | "unsafe-url"; type HTMLAttributeAnchorTarget = | "_self" | "_blank" | "_parent" | "_top" | (string & {}); export interface AnchorHTMLAttributes extends HTMLAttributes { download?: any; href?: string | undefined; hrefLang?: string | undefined; media?: string | undefined; ping?: string | undefined; rel?: string | undefined; target?: HTMLAttributeAnchorTarget | undefined; type?: string | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | undefined; } export interface AudioHTMLAttributes extends MediaHTMLAttributes {} export interface AreaHTMLAttributes extends HTMLAttributes { alt?: string | undefined; coords?: string | undefined; download?: any; href?: string | undefined; hrefLang?: string | undefined; media?: string | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | undefined; rel?: string | undefined; shape?: string | undefined; target?: string | undefined; } export interface BaseHTMLAttributes extends HTMLAttributes { href?: string | undefined; target?: string | undefined; } export interface BlockquoteHTMLAttributes extends HTMLAttributes { cite?: string | undefined; } export interface ButtonHTMLAttributes extends HTMLAttributes { autoFocus?: boolean | undefined; disabled?: boolean | undefined; form?: string | undefined; formAction?: string | undefined; formEncType?: string | undefined; formMethod?: string | undefined; formNoValidate?: boolean | undefined; formTarget?: string | undefined; name?: string | undefined; type?: "submit" | "reset" | "button" | undefined; value?: string | ReadonlyArray | number | undefined; } export interface CanvasHTMLAttributes extends HTMLAttributes { height?: number | string | undefined; width?: number | string | undefined; } export interface ColHTMLAttributes extends HTMLAttributes { span?: number | undefined; width?: number | string | undefined; } export interface ColgroupHTMLAttributes extends HTMLAttributes { span?: number | undefined; } export interface DataHTMLAttributes extends HTMLAttributes { value?: string | ReadonlyArray | number | undefined; } export interface DetailsHTMLAttributes extends HTMLAttributes { open?: boolean | undefined; } export interface DelHTMLAttributes extends HTMLAttributes { cite?: string | undefined; dateTime?: string | undefined; } export interface DialogHTMLAttributes extends HTMLAttributes { open?: boolean | undefined; } export interface EmbedHTMLAttributes extends HTMLAttributes { height?: number | string | undefined; src?: string | undefined; type?: string | undefined; width?: number | string | undefined; } export interface FieldsetHTMLAttributes extends HTMLAttributes { disabled?: boolean | undefined; form?: string | undefined; name?: string | undefined; } export interface FormHTMLAttributes extends HTMLAttributes { acceptCharset?: string | undefined; action?: string | undefined; autoComplete?: string | undefined; encType?: string | undefined; method?: string | undefined; name?: string | undefined; noValidate?: boolean | undefined; target?: string | undefined; } export interface HtmlHTMLAttributes extends HTMLAttributes { manifest?: string | undefined; } export interface IframeHTMLAttributes extends HTMLAttributes { allow?: string | undefined; allowFullScreen?: boolean | undefined; allowTransparency?: boolean | undefined; /** @deprecated */ frameBorder?: number | string | undefined; height?: number | string | undefined; loading?: "eager" | "lazy" | undefined; /** @deprecated */ marginHeight?: number | undefined; /** @deprecated */ marginWidth?: number | undefined; name?: string | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | undefined; sandbox?: string | undefined; /** @deprecated */ scrolling?: string | undefined; seamless?: boolean | undefined; src?: string | undefined; srcDoc?: string | undefined; width?: number | string | undefined; } export interface ImgHTMLAttributes extends HTMLAttributes { alt?: string | undefined; crossOrigin?: "anonymous" | "use-credentials" | "" | undefined; decoding?: "async" | "auto" | "sync" | undefined; height?: number | string | undefined; loading?: "eager" | "lazy" | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | undefined; sizes?: string | undefined; src?: string | undefined; srcSet?: string | undefined; useMap?: string | undefined; width?: number | string | undefined; } export interface InsHTMLAttributes extends HTMLAttributes { cite?: string | undefined; dateTime?: string | undefined; } type HTMLInputTypeAttribute = | "button" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "hidden" | "image" | "month" | "number" | "password" | "radio" | "range" | "reset" | "search" | "submit" | "tel" | "text" | "time" | "url" | "week" | (string & {}); export interface InputHTMLAttributes extends HTMLAttributes { accept?: string | undefined; alt?: string | undefined; autoComplete?: string | undefined; autoFocus?: boolean | undefined; capture?: boolean | "user" | "environment" | undefined; // https://www.w3.org/TR/html-media-capture/#the-capture-attribute checked?: boolean | undefined; crossOrigin?: string | undefined; disabled?: boolean | undefined; enterKeyHint?: | "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined; form?: string | undefined; formAction?: string | undefined; formEncType?: string | undefined; formMethod?: string | undefined; formNoValidate?: boolean | undefined; formTarget?: string | undefined; height?: number | string | undefined; list?: string | undefined; max?: number | string | undefined; maxLength?: number | undefined; min?: number | string | undefined; minLength?: number | undefined; multiple?: boolean | undefined; name?: string | undefined; pattern?: string | undefined; placeholder?: string | undefined; readOnly?: boolean | undefined; required?: boolean | undefined; size?: number | undefined; src?: string | undefined; step?: number | string | undefined; type?: HTMLInputTypeAttribute | undefined; value?: string | ReadonlyArray | number | undefined; width?: number | string | undefined; } export interface KeygenHTMLAttributes extends HTMLAttributes { autoFocus?: boolean | undefined; challenge?: string | undefined; disabled?: boolean | undefined; form?: string | undefined; keyType?: string | undefined; keyParams?: string | undefined; name?: string | undefined; } export interface LabelHTMLAttributes extends HTMLAttributes { form?: string | undefined; htmlFor?: string | undefined; } export interface LiHTMLAttributes extends HTMLAttributes { value?: string | ReadonlyArray | number | undefined; } export interface LinkHTMLAttributes extends HTMLAttributes { as?: string | undefined; crossOrigin?: string | undefined; href?: string | undefined; hrefLang?: string | undefined; integrity?: string | undefined; media?: string | undefined; imageSrcSet?: string | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | undefined; rel?: string | undefined; sizes?: string | undefined; type?: string | undefined; charSet?: string | undefined; } export interface MapHTMLAttributes extends HTMLAttributes { name?: string | undefined; } export interface MenuHTMLAttributes extends HTMLAttributes { type?: string | undefined; } export interface MediaHTMLAttributes extends HTMLAttributes { autoPlay?: boolean | undefined; controls?: boolean | undefined; controlsList?: string | undefined; crossOrigin?: string | undefined; loop?: boolean | undefined; mediaGroup?: string | undefined; muted?: boolean | undefined; playsInline?: boolean | undefined; preload?: string | undefined; src?: string | undefined; } export interface MetaHTMLAttributes extends HTMLAttributes { charSet?: string | undefined; content?: string | undefined; httpEquiv?: string | undefined; name?: string | undefined; media?: string | undefined; } export interface MeterHTMLAttributes extends HTMLAttributes { form?: string | undefined; high?: number | undefined; low?: number | undefined; max?: number | string | undefined; min?: number | string | undefined; optimum?: number | undefined; value?: string | ReadonlyArray | number | undefined; } export interface QuoteHTMLAttributes extends HTMLAttributes { cite?: string | undefined; } export interface ObjectHTMLAttributes extends HTMLAttributes { classID?: string | undefined; data?: string | undefined; form?: string | undefined; height?: number | string | undefined; name?: string | undefined; type?: string | undefined; useMap?: string | undefined; width?: number | string | undefined; wmode?: string | undefined; } export interface OlHTMLAttributes extends HTMLAttributes { reversed?: boolean | undefined; start?: number | undefined; type?: "1" | "a" | "A" | "i" | "I" | undefined; } export interface OptgroupHTMLAttributes extends HTMLAttributes { disabled?: boolean | undefined; label?: string | undefined; } export interface OptionHTMLAttributes extends HTMLAttributes { disabled?: boolean | undefined; label?: string | undefined; selected?: boolean | undefined; value?: string | ReadonlyArray | number | undefined; } export interface OutputHTMLAttributes extends HTMLAttributes { form?: string | undefined; htmlFor?: string | undefined; name?: string | undefined; } export interface ParamHTMLAttributes extends HTMLAttributes { name?: string | undefined; value?: string | ReadonlyArray | number | undefined; } export interface ProgressHTMLAttributes extends HTMLAttributes { max?: number | string | undefined; value?: string | ReadonlyArray | number | undefined; } export interface SlotHTMLAttributes extends HTMLAttributes { name?: string | undefined; } export interface ScriptHTMLAttributes extends HTMLAttributes { async?: boolean | undefined; /** @deprecated */ charSet?: string | undefined; crossOrigin?: string | undefined; defer?: boolean | undefined; integrity?: string | undefined; noModule?: boolean | undefined; nonce?: string | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | undefined; src?: string | undefined; type?: string | undefined; } export interface SelectHTMLAttributes extends HTMLAttributes { autoComplete?: string | undefined; autoFocus?: boolean | undefined; disabled?: boolean | undefined; form?: string | undefined; multiple?: boolean | undefined; name?: string | undefined; required?: boolean | undefined; size?: number | undefined; value?: string | ReadonlyArray | number | undefined; } export interface SourceHTMLAttributes extends HTMLAttributes { height?: number | string | undefined; media?: string | undefined; sizes?: string | undefined; src?: string | undefined; srcSet?: string | undefined; type?: string | undefined; width?: number | string | undefined; } export interface StyleHTMLAttributes extends HTMLAttributes { media?: string | undefined; nonce?: string | undefined; scoped?: boolean | undefined; type?: string | undefined; } export interface TableHTMLAttributes extends HTMLAttributes { cellPadding?: number | string | undefined; cellSpacing?: number | string | undefined; summary?: string | undefined; width?: number | string | undefined; } export interface TextareaHTMLAttributes extends HTMLAttributes { autoComplete?: string | undefined; autoFocus?: boolean | undefined; cols?: number | undefined; dirName?: string | undefined; disabled?: boolean | undefined; form?: string | undefined; maxLength?: number | undefined; minLength?: number | undefined; name?: string | undefined; placeholder?: string | undefined; readOnly?: boolean | undefined; required?: boolean | undefined; rows?: number | undefined; value?: string | ReadonlyArray | number | undefined; wrap?: string | undefined; } export interface TdHTMLAttributes extends HTMLAttributes { align?: "left" | "center" | "right" | "justify" | "char" | undefined; colSpan?: number | undefined; headers?: string | undefined; rowSpan?: number | undefined; scope?: string | undefined; abbr?: string | undefined; height?: number | string | undefined; width?: number | string | undefined; valign?: "top" | "middle" | "bottom" | "baseline" | undefined; } export interface ThHTMLAttributes extends HTMLAttributes { align?: "left" | "center" | "right" | "justify" | "char" | undefined; colSpan?: number | undefined; headers?: string | undefined; rowSpan?: number | undefined; scope?: string | undefined; abbr?: string | undefined; } export interface TimeHTMLAttributes extends HTMLAttributes { dateTime?: string | undefined; } export interface TrackHTMLAttributes extends HTMLAttributes { default?: boolean | undefined; kind?: string | undefined; label?: string | undefined; src?: string | undefined; srcLang?: string | undefined; } export interface VideoHTMLAttributes extends MediaHTMLAttributes { height?: number | string | undefined; playsInline?: boolean | undefined; poster?: string | undefined; width?: number | string | undefined; disablePictureInPicture?: boolean | undefined; disableRemotePlayback?: boolean | undefined; } export interface WebViewHTMLAttributes extends HTMLAttributes { allowFullScreen?: boolean | undefined; allowpopups?: boolean | undefined; autoFocus?: boolean | undefined; autosize?: boolean | undefined; blinkfeatures?: string | undefined; disableblinkfeatures?: string | undefined; disableguestresize?: boolean | undefined; disablewebsecurity?: boolean | undefined; guestinstance?: string | undefined; httpreferrer?: string | undefined; nodeintegration?: boolean | undefined; partition?: string | undefined; plugins?: boolean | undefined; preload?: string | undefined; src?: string | undefined; useragent?: string | undefined; webpreferences?: string | undefined; } export interface SVGAttributes extends AriaAttributes, EventHandlers { innerHTML?: string; /** * SVG Styling Attributes * @see https://www.w3.org/TR/SVG/styling.html#ElementSpecificStyling */ class?: any; className?: string | CSObject | CSArray | undefined; color?: string | undefined; height?: number | string | undefined; id?: string | undefined; lang?: string | undefined; max?: number | string | undefined; media?: string | undefined; method?: string | undefined; min?: number | string | undefined; name?: string | undefined; style?: CSSProperties | string | undefined; target?: string | undefined; type?: string | undefined; width?: number | string | undefined; // Other HTML properties supported by SVG elements in browsers role?: AriaRole | undefined; tabIndex?: number | undefined; crossOrigin?: "anonymous" | "use-credentials" | "" | undefined; // SVG Specific attributes accentHeight?: number | string | undefined; accumulate?: "none" | "sum" | undefined; additive?: "replace" | "sum" | undefined; alignmentBaseline?: | "auto" | "baseline" | "before-edge" | "text-before-edge" | "middle" | "central" | "after-edge" | "text-after-edge" | "ideographic" | "alphabetic" | "hanging" | "mathematical" | "inherit" | undefined; allowReorder?: "no" | "yes" | undefined; alphabetic?: number | string | undefined; amplitude?: number | string | undefined; arabicForm?: "initial" | "medial" | "terminal" | "isolated" | undefined; ascent?: number | string | undefined; attributeName?: string | undefined; attributeType?: string | undefined; autoReverse?: Booleanish | undefined; azimuth?: number | string | undefined; baseFrequency?: number | string | undefined; baselineShift?: number | string | undefined; baseProfile?: number | string | undefined; bbox?: number | string | undefined; begin?: number | string | undefined; bias?: number | string | undefined; by?: number | string | undefined; calcMode?: number | string | undefined; capHeight?: number | string | undefined; clip?: number | string | undefined; clipPath?: string | undefined; clipPathUnits?: number | string | undefined; clipRule?: number | string | undefined; colorInterpolation?: number | string | undefined; colorInterpolationFilters?: | "auto" | "sRGB" | "linearRGB" | "inherit" | undefined; colorProfile?: number | string | undefined; colorRendering?: number | string | undefined; contentScriptType?: number | string | undefined; contentStyleType?: number | string | undefined; cursor?: number | string | undefined; cx?: number | string | undefined; cy?: number | string | undefined; d?: string | undefined; decelerate?: number | string | undefined; descent?: number | string | undefined; diffuseConstant?: number | string | undefined; direction?: number | string | undefined; display?: number | string | undefined; divisor?: number | string | undefined; dominantBaseline?: number | string | undefined; dur?: number | string | undefined; dx?: number | string | undefined; dy?: number | string | undefined; edgeMode?: number | string | undefined; elevation?: number | string | undefined; enableBackground?: number | string | undefined; end?: number | string | undefined; exponent?: number | string | undefined; externalResourcesRequired?: Booleanish | undefined; fill?: string | undefined; fillOpacity?: number | string | undefined; fillRule?: "nonzero" | "evenodd" | "inherit" | undefined; filter?: string | undefined; filterRes?: number | string | undefined; filterUnits?: number | string | undefined; floodColor?: number | string | undefined; floodOpacity?: number | string | undefined; focusable?: Booleanish | "auto" | undefined; fontFamily?: string | undefined; fontSize?: number | string | undefined; fontSizeAdjust?: number | string | undefined; fontStretch?: number | string | undefined; fontStyle?: number | string | undefined; fontVariant?: number | string | undefined; fontWeight?: number | string | undefined; format?: number | string | undefined; fr?: number | string | undefined; from?: number | string | undefined; fx?: number | string | undefined; fy?: number | string | undefined; g1?: number | string | undefined; g2?: number | string | undefined; glyphName?: number | string | undefined; glyphOrientationHorizontal?: number | string | undefined; glyphOrientationVertical?: number | string | undefined; glyphRef?: number | string | undefined; gradientTransform?: string | undefined; gradientUnits?: string | undefined; hanging?: number | string | undefined; horizAdvX?: number | string | undefined; horizOriginX?: number | string | undefined; href?: string | undefined; ideographic?: number | string | undefined; imageRendering?: number | string | undefined; in2?: number | string | undefined; in?: string | undefined; intercept?: number | string | undefined; k1?: number | string | undefined; k2?: number | string | undefined; k3?: number | string | undefined; k4?: number | string | undefined; k?: number | string | undefined; kernelMatrix?: number | string | undefined; kernelUnitLength?: number | string | undefined; kerning?: number | string | undefined; keyPoints?: number | string | undefined; keySplines?: number | string | undefined; keyTimes?: number | string | undefined; lengthAdjust?: number | string | undefined; letterSpacing?: number | string | undefined; lightingColor?: number | string | undefined; limitingConeAngle?: number | string | undefined; local?: number | string | undefined; markerEnd?: string | undefined; markerHeight?: number | string | undefined; markerMid?: string | undefined; markerStart?: string | undefined; markerUnits?: number | string | undefined; markerWidth?: number | string | undefined; mask?: string | undefined; maskContentUnits?: number | string | undefined; maskUnits?: number | string | undefined; mathematical?: number | string | undefined; mode?: number | string | undefined; numOctaves?: number | string | undefined; offset?: number | string | undefined; opacity?: number | string | undefined; operator?: number | string | undefined; order?: number | string | undefined; orient?: number | string | undefined; orientation?: number | string | undefined; origin?: number | string | undefined; overflow?: number | string | undefined; overlinePosition?: number | string | undefined; overlineThickness?: number | string | undefined; paintOrder?: number | string | undefined; panose1?: number | string | undefined; path?: string | undefined; pathLength?: number | string | undefined; patternContentUnits?: string | undefined; patternTransform?: number | string | undefined; patternUnits?: string | undefined; pointerEvents?: number | string | undefined; points?: string | undefined; pointsAtX?: number | string | undefined; pointsAtY?: number | string | undefined; pointsAtZ?: number | string | undefined; preserveAlpha?: Booleanish | undefined; preserveAspectRatio?: string | undefined; primitiveUnits?: number | string | undefined; r?: number | string | undefined; radius?: number | string | undefined; refX?: number | string | undefined; refY?: number | string | undefined; renderingIntent?: number | string | undefined; repeatCount?: number | string | undefined; repeatDur?: number | string | undefined; requiredExtensions?: number | string | undefined; requiredFeatures?: number | string | undefined; restart?: number | string | undefined; result?: string | undefined; rotate?: number | string | undefined; rx?: number | string | undefined; ry?: number | string | undefined; scale?: number | string | undefined; seed?: number | string | undefined; shapeRendering?: number | string | undefined; slope?: number | string | undefined; spacing?: number | string | undefined; specularConstant?: number | string | undefined; specularExponent?: number | string | undefined; speed?: number | string | undefined; spreadMethod?: string | undefined; startOffset?: number | string | undefined; stdDeviation?: number | string | undefined; stemh?: number | string | undefined; stemv?: number | string | undefined; stitchTiles?: number | string | undefined; stopColor?: string | undefined; stopOpacity?: number | string | undefined; strikethroughPosition?: number | string | undefined; strikethroughThickness?: number | string | undefined; string?: number | string | undefined; stroke?: string | undefined; strokeDasharray?: string | number | undefined; strokeDashoffset?: string | number | undefined; strokeLinecap?: "butt" | "round" | "square" | "inherit" | undefined; strokeLinejoin?: "miter" | "round" | "bevel" | "inherit" | undefined; strokeMiterlimit?: number | string | undefined; strokeOpacity?: number | string | undefined; strokeWidth?: number | string | undefined; surfaceScale?: number | string | undefined; systemLanguage?: number | string | undefined; tableValues?: number | string | undefined; targetX?: number | string | undefined; targetY?: number | string | undefined; textAnchor?: string | undefined; textDecoration?: number | string | undefined; textLength?: number | string | undefined; textRendering?: number | string | undefined; to?: number | string | undefined; transform?: string | undefined; u1?: number | string | undefined; u2?: number | string | undefined; underlinePosition?: number | string | undefined; underlineThickness?: number | string | undefined; unicode?: number | string | undefined; unicodeBidi?: number | string | undefined; unicodeRange?: number | string | undefined; unitsPerEm?: number | string | undefined; vAlphabetic?: number | string | undefined; values?: string | undefined; vectorEffect?: number | string | undefined; version?: string | undefined; vertAdvY?: number | string | undefined; vertOriginX?: number | string | undefined; vertOriginY?: number | string | undefined; vHanging?: number | string | undefined; vIdeographic?: number | string | undefined; viewBox?: string | undefined; viewTarget?: number | string | undefined; visibility?: number | string | undefined; vMathematical?: number | string | undefined; widths?: number | string | undefined; wordSpacing?: number | string | undefined; writingMode?: number | string | undefined; x1?: number | string | undefined; x2?: number | string | undefined; x?: number | string | undefined; xChannelSelector?: string | undefined; xHeight?: number | string | undefined; xlinkActuate?: string | undefined; xlinkArcrole?: string | undefined; xlinkHref?: string | undefined; xlinkRole?: string | undefined; xlinkShow?: string | undefined; xlinkTitle?: string | undefined; xlinkType?: string | undefined; xmlBase?: string | undefined; xmlLang?: string | undefined; xmlns?: string | undefined; xmlnsXlink?: string | undefined; xmlSpace?: string | undefined; y1?: number | string | undefined; y2?: number | string | undefined; y?: number | string | undefined; yChannelSelector?: string | undefined; z?: number | string | undefined; zoomAndPan?: string | undefined; } export interface IntrinsicElementAttributes { a: AnchorHTMLAttributes; abbr: HTMLAttributes; address: HTMLAttributes; area: AreaHTMLAttributes; article: HTMLAttributes; aside: HTMLAttributes; audio: AudioHTMLAttributes; b: HTMLAttributes; base: BaseHTMLAttributes; bdi: HTMLAttributes; bdo: HTMLAttributes; blockquote: BlockquoteHTMLAttributes; body: HTMLAttributes; br: HTMLAttributes; button: ButtonHTMLAttributes; canvas: CanvasHTMLAttributes; caption: HTMLAttributes; cite: HTMLAttributes; code: HTMLAttributes; col: ColHTMLAttributes; colgroup: ColgroupHTMLAttributes; data: DataHTMLAttributes; datalist: HTMLAttributes; dd: HTMLAttributes; del: DelHTMLAttributes; details: DetailsHTMLAttributes; dfn: HTMLAttributes; dialog: DialogHTMLAttributes; div: HTMLAttributes; dl: HTMLAttributes; dt: HTMLAttributes; em: HTMLAttributes; embed: EmbedHTMLAttributes; fieldset: FieldsetHTMLAttributes; figcaption: HTMLAttributes; figure: HTMLAttributes; footer: HTMLAttributes; form: FormHTMLAttributes; h1: HTMLAttributes; h2: HTMLAttributes; h3: HTMLAttributes; h4: HTMLAttributes; h5: HTMLAttributes; h6: HTMLAttributes; head: HTMLAttributes; header: HTMLAttributes; hgroup: HTMLAttributes; hr: HTMLAttributes; html: HtmlHTMLAttributes; i: HTMLAttributes; iframe: IframeHTMLAttributes; img: ImgHTMLAttributes; input: InputHTMLAttributes; ins: InsHTMLAttributes; kbd: HTMLAttributes; keygen: KeygenHTMLAttributes; label: LabelHTMLAttributes; legend: HTMLAttributes; li: LiHTMLAttributes; link: LinkHTMLAttributes; main: HTMLAttributes; map: MapHTMLAttributes; mark: HTMLAttributes; menu: MenuHTMLAttributes; meta: MetaHTMLAttributes; meter: MeterHTMLAttributes; nav: HTMLAttributes; noindex: HTMLAttributes; noscript: HTMLAttributes; object: ObjectHTMLAttributes; ol: OlHTMLAttributes; optgroup: OptgroupHTMLAttributes; option: OptionHTMLAttributes; output: OutputHTMLAttributes; p: HTMLAttributes; param: ParamHTMLAttributes; picture: HTMLAttributes; pre: HTMLAttributes; progress: ProgressHTMLAttributes; q: QuoteHTMLAttributes; rp: HTMLAttributes; rt: HTMLAttributes; ruby: HTMLAttributes; s: HTMLAttributes; samp: HTMLAttributes; script: ScriptHTMLAttributes; section: HTMLAttributes; select: SelectHTMLAttributes; slot: SlotHTMLAttributes; small: HTMLAttributes; source: SourceHTMLAttributes; span: HTMLAttributes; strong: HTMLAttributes; style: StyleHTMLAttributes; sub: HTMLAttributes; summary: HTMLAttributes; sup: HTMLAttributes; table: TableHTMLAttributes; template: HTMLAttributes; tbody: HTMLAttributes; td: TdHTMLAttributes; textarea: TextareaHTMLAttributes; tfoot: HTMLAttributes; th: ThHTMLAttributes; thead: HTMLAttributes; time: TimeHTMLAttributes; title: HTMLAttributes; tr: HTMLAttributes; track: TrackHTMLAttributes; u: HTMLAttributes; ul: HTMLAttributes; var: HTMLAttributes; video: VideoHTMLAttributes; wbr: HTMLAttributes; webview: WebViewHTMLAttributes; // SVG svg: SVGAttributes; animate: SVGAttributes; animateMotion: SVGAttributes; animateTransform: SVGAttributes; circle: SVGAttributes; clipPath: SVGAttributes; defs: SVGAttributes; desc: SVGAttributes; ellipse: SVGAttributes; feBlend: SVGAttributes; feColorMatrix: SVGAttributes; feComponentTransfer: SVGAttributes; feComposite: SVGAttributes; feConvolveMatrix: SVGAttributes; feDiffuseLighting: SVGAttributes; feDisplacementMap: SVGAttributes; feDistantLight: SVGAttributes; feDropShadow: SVGAttributes; feFlood: SVGAttributes; feFuncA: SVGAttributes; feFuncB: SVGAttributes; feFuncG: SVGAttributes; feFuncR: SVGAttributes; feGaussianBlur: SVGAttributes; feImage: SVGAttributes; feMerge: SVGAttributes; feMergeNode: SVGAttributes; feMorphology: SVGAttributes; feOffset: SVGAttributes; fePointLight: SVGAttributes; feSpecularLighting: SVGAttributes; feSpotLight: SVGAttributes; feTile: SVGAttributes; feTurbulence: SVGAttributes; filter: SVGAttributes; foreignObject: SVGAttributes; g: SVGAttributes; image: SVGAttributes; line: SVGAttributes; linearGradient: SVGAttributes; marker: SVGAttributes; mask: SVGAttributes; metadata: SVGAttributes; mpath: SVGAttributes; path: SVGAttributes; pattern: SVGAttributes; polygon: SVGAttributes; polyline: SVGAttributes; radialGradient: SVGAttributes; rect: SVGAttributes; stop: SVGAttributes; switch: SVGAttributes; symbol: SVGAttributes; text: SVGAttributes; textPath: SVGAttributes; tspan: SVGAttributes; use: SVGAttributes; view: SVGAttributes; } export interface Events { // Clipboard Events onCopy: ClipboardEvent; onCut: ClipboardEvent; onPaste: ClipboardEvent; // Composition Events onCompositionEnd: CompositionEvent; onCompositionStart: CompositionEvent; onCompositionUpdate: CompositionEvent; // Focus Events onFocus: FocusEvent; onBlur: FocusEvent; // Form Events onChange: Event; onBeforeInput: Event; onInput: Event; onReset: Event; onSubmit: Event; onInvalid: Event; // Image Events onLoad: Event; onError: Event; // Keyboard Events onKeyDown: KeyboardEvent; onKeyPress: KeyboardEvent; onKeyUp: KeyboardEvent; // Media Events onAbort: Event; onCanPlay: Event; onCanPlayThrough: Event; onDurationChange: Event; onEmptied: Event; onEncrypted: Event; onEnded: Event; onLoadedData: Event; onLoadedMetadata: Event; onLoadStart: Event; onPause: Event; onPlay: Event; onPlaying: Event; onProgress: Event; onRateChange: Event; onSeeked: Event; onSeeking: Event; onStalled: Event; onSuspend: Event; onTimeUpdate: Event; onVolumeChange: Event; onWaiting: Event; // DragEvent onDrag: DragEvent; onDragEnd: DragEvent; onDragEnter: DragEvent; onDragExit: DragEvent; onDragLeave: DragEvent; onDragOver: DragEvent; onDragStart: DragEvent; onDrop: DragEvent; // MouseEvents onAuxClick: MouseEvent; onClick: MouseEvent; onContextMenu: MouseEvent; onDoubleClick: MouseEvent; onMouseDown: MouseEvent; onMouseEnter: MouseEvent; onMouseLeave: MouseEvent; onMouseMove: MouseEvent; onMouseOut: MouseEvent; onMouseOver: MouseEvent; onMouseUp: MouseEvent; // Selection Events onSelect: Event; // Touch Events onTouchCancel: TouchEvent; onTouchEnd: TouchEvent; onTouchMove: TouchEvent; onTouchStart: TouchEvent; // Pointer Events onPointerDown: PointerEvent; onPointerMove: PointerEvent; onPointerUp: PointerEvent; onPointerCancel: PointerEvent; onPointerEnter: PointerEvent; onPointerLeave: PointerEvent; onPointerOver: PointerEvent; onPointerOut: PointerEvent; // UI Events onScroll: UIEvent; // Wheel Events onWheel: WheelEvent; // Animation Events onAnimationStart: AnimationEvent; onAnimationEnd: AnimationEvent; onAnimationIteration: AnimationEvent; // Transition Events onTransitionEnd: TransitionEvent; onTransitionStart: TransitionEvent; } type EventHandlers = { [K in keyof E]?: E[K] extends Function ? E[K] : (payload: E[K]) => void; }; type ReservedProps = { children?: unknown; key?: string | number | symbol; ref?: { current: any } | ((ref: Element | null) => void); }; export type ElementAttrs = T & ReservedProps; type NativeElements = { [K in keyof IntrinsicElementAttributes]: ElementAttrs< IntrinsicElementAttributes[K] >; }; declare global { namespace JSX { type ElementTypes = | string | number | null | boolean | void | undefined | FiberNode; type Element = | Element[] | ElementTypes | ElementTypes[] | (() => Element) | (() => Element[]); interface ElementClass { props: {}; } interface ElementAttributesProperty { props: {}; } interface ElementChildrenAttribute { children: {}; } interface IntrinsicElements extends NativeElements { // allow arbitrary elements // @ts-ignore suppress ts:2374 = Duplicate string index signature. [name: string]: any; } interface IntrinsicAttributes extends ReservedProps {} } } // suppress ts:2669 export {}; ================================================ FILE: packages/unis-core/vitest.config.ts ================================================ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { include: ["**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], coverage: { reporter: ["text", "json", "html"], }, }, }); ================================================ FILE: packages/unis-dom/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next # Nuxt.js build / generate output .nuxt dist build # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and *not* Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port ================================================ FILE: packages/unis-dom/index.d.ts ================================================ import "@unis/core"; export * from "./dist/browser"; ================================================ FILE: packages/unis-dom/package.json ================================================ { "name": "@unis/dom", "version": "1.2.5", "description": "Unis is a simpler and easier to use front-end framework than React", "main": "dist/browser.js", "module": "dist/browser.mjs", "types": "index.d.ts", "typings": "index.d.ts", "exports": { ".": { "require": "./dist/browser.js", "import": "./dist/browser.mjs" }, "./server": { "require": "./dist/server.js", "import": "./dist/server.mjs" } }, "scripts": { "build": "rimraf build && rimraf dist && tsc -p tsconfig.build.json && rollup --config", "build:dev": "cross-env NODE_ENV=development pnpm build", "build:server": "rollup --config rollup.config.server.mjs", "test": "vitest run --coverage", "test:watch": "vitest -w" }, "repository": { "type": "git", "url": "git+https://github.com/anuoua/unis.git" }, "keywords": [ "frontend", "web", "framwork" ], "files": [ "dist", "server.d.ts", "index.d.ts" ], "author": "anuoua", "license": "MIT", "bugs": { "url": "https://github.com/anuoua/unis/issues" }, "homepage": "https://github.com/anuoua/unis#readme", "peerDependencies": { "@unis/core": "workspace:^" }, "devDependencies": { "@rollup/plugin-node-resolve": "^15.0.2", "@types/jsdom": "^21.1.1", "@unis/core": "workspace:^", "@unis/vite-preset": "workspace:^", "@vitest/coverage-c8": "^0.28.5", "cross-env": "^7.0.3", "esbuild": "^0.17.15", "jsdom": "^21.1.1", "rimraf": "^4.4.1", "rollup": "^3.20.2", "rollup-plugin-dts": "^5.3.0", "rollup-plugin-esbuild": "^5.0.0", "tslib": "^2.5.0", "typescript": "^4.9.5", "vite": "^4.2.1", "vitest": "^0.29.8" } } ================================================ FILE: packages/unis-dom/rollup.config.mjs ================================================ import esbuild from "rollup-plugin-esbuild"; import dts from "rollup-plugin-dts"; import { defineConfig } from "rollup"; import { nodeResolve } from "@rollup/plugin-node-resolve"; const configGen = (format, plateform) => defineConfig({ input: `src/${plateform}/index.ts`, external: [/^@unis/], output: [ { dir: "dist", entryFileNames: `${plateform}.${format === "esm" ? "mjs" : "js"}`, format, sourcemap: true, }, ], plugins: [ nodeResolve(), esbuild({ sourceMap: true, target: "esnext", }), ], }); const dtsRollup = (which) => defineConfig({ input: `build/${which}/index.d.ts`, output: [{ file: `dist/${which}.d.ts`, format: "es" }], plugins: [dts()], }); const config = [ configGen("cjs", "browser"), configGen("esm", "browser"), configGen("cjs", "server"), configGen("esm", "server"), dtsRollup("browser"), dtsRollup("server"), ]; export default config; ================================================ FILE: packages/unis-dom/server.d.ts ================================================ export * from "./dist/server"; ================================================ FILE: packages/unis-dom/src/browser/__test__/context.test.tsx ================================================ /** * @vitest-environment jsdom */ import { afterEach, beforeEach, expect, it } from "vitest"; import { useContext } from "@unis/core"; import { useEffect } from "@unis/core"; import { useProps } from "@unis/core"; import { useState } from "@unis/core"; import { createContext } from "@unis/core"; import { Fragment, memo } from "@unis/core"; import { rendered, testRender } from "./util"; let root: Element; beforeEach(() => { root = document.createElement("div"); document.body.append(root); }); afterEach(() => { root.innerHTML = ""; }); it("context", async () => { const AppContext = createContext("initial"); const Cpp = memo(() => { let theme = useContext(AppContext); return () =>
Cpp: {theme}
; }); const Dpp = () => { return () => ( {(theme) =>
Dpp: {theme}
}
); }; const Epp = () => { return () => ( {(theme) =>
Epp: {theme}
}
); }; const Bpp = () => { let theme = useContext(AppContext); return () => (
Bpp: {theme}
); }; const App = () => { let [theme, setTheme] = useState("light"); useEffect( () => { setTheme("dark"); }, () => [] ); return () => (
App
); }; testRender(, root); expect(root.innerHTML).toBe( "
App
Bpp: light
Cpp: gray
Dpp: light
Epp: initial
" ); await rendered(); expect(root.innerHTML).toBe( "
App
Bpp: dark
Cpp: gray
Dpp: dark
Epp: initial
" ); }); it("context pass through", async () => { const AppContext = createContext({} as any); const App = () => { let [hello, setHello] = useState("hello"); return () => (
); }; const Bpp = () => { let { hello, setHello } = useContext(AppContext); return () => ; }; const Cpp = (p: { msg: string; setMsg: (msg: string) => void }) => { let { msg, setMsg } = useProps(p); let [count, setCount] = useState(0); useEffect( () => { setMsg("world"); setCount(count + 1); }, () => [] ); return () => msg; }; testRender(, root); expect(root.innerHTML).toBe("
hello
"); await rendered(); expect(root.innerHTML).toBe("
world
"); }); ================================================ FILE: packages/unis-dom/src/browser/__test__/dom.test.tsx ================================================ /** * @vitest-environment jsdom */ import { afterEach, beforeEach, expect, it } from "vitest"; import { useEffect } from "@unis/core"; import { useState } from "@unis/core"; import { rendered, testRender } from "./util"; let root: Element; beforeEach(() => { root = document.createElement("div"); document.body.append(root); }); afterEach(() => { root.innerHTML = ""; }); it("dom", async () => { const App = () => { let [toggle, setToggle] = useState(true); const getCurrentStyle = () => { return toggle ? { style: { background: "yellow", }, tabindex: "1", className: "class1", onClick: () => {}, } : { style: { background: "red", }, tabindex: "2", onClick: () => {}, }; }; useEffect( () => { setToggle(false); }, () => [] ); return () => { return
hello
; }; }; testRender(, root); expect(root.innerHTML).toBe( '
hello
' ); await rendered(); expect(root.innerHTML).toBe( '
hello
' ); }); ================================================ FILE: packages/unis-dom/src/browser/__test__/effect.test.tsx ================================================ /** * @vitest-environment jsdom */ import { afterEach, beforeEach, expect, it } from "vitest"; import { useEffect } from "@unis/core"; import { useState } from "@unis/core"; import { rendered, testRender } from "./util"; let root: Element; beforeEach(() => { root = document.createElement("div"); document.body.append(root); }); afterEach(() => { root.innerHTML = ""; }); it("effect", async () => { const Bpp = () => { useEffect( () => { return () => {}; }, () => [] ); return () => "bpp"; }; const App = () => { let [visible, setVisible] = useState(true); useEffect( () => { setVisible(false); }, () => [] ); return () => (visible ? : null); }; testRender(, root); expect(root.innerHTML).toBe("bpp"); await rendered(); expect(root.innerHTML).toBe(""); }); ================================================ FILE: packages/unis-dom/src/browser/__test__/hydrate.test.tsx ================================================ /** * @vitest-environment jsdom */ import { afterEach, beforeEach, expect, it } from "vitest"; import { rendered, testRender } from "./util"; import { use, useState } from "@unis/core"; let root: Element; beforeEach(() => { root = document.createElement("div"); document.body.append(root); }); afterEach(() => { root.innerHTML = ""; }); it("hydrate", async () => { root.innerHTML = "
Apphello
"; let setMsgOutter: any; const App = () => { let [msg, setMsg] = useState("hello"); use(() => { setMsgOutter = setMsg; }); return () => (
App{msg}
); }; testRender(, root, true); expect(root.innerHTML).toBe("
Apphello
"); setMsgOutter("world"); await rendered(); expect(root.innerHTML).toBe("
Appworld
"); }); ================================================ FILE: packages/unis-dom/src/browser/__test__/memo.test.tsx ================================================ /** * @vitest-environment jsdom */ import { afterEach, beforeEach, expect, it } from "vitest"; import { useEffect } from "@unis/core"; import { useState } from "@unis/core"; import { h2, memo } from "@unis/core"; import { rendered, testRender } from "./util"; let root: Element; beforeEach(() => { root = document.createElement("div"); document.body.append(root); }); afterEach(() => { root.innerHTML = ""; }); it("memo", async () => { const Bpp = memo(() => { let renderCount = 0; return () => { return
{renderCount++}
; }; }); const App = () => { let [msg, setMsg] = useState("hello"); useEffect( () => { setMsg("hello world"); }, () => [] ); return () => (
{msg} {h2(Bpp, {}, "key")}
); }; testRender(, root); expect(root.innerHTML).toBe("
hello
0
"); await rendered(); expect(root.innerHTML).toBe("
hello world
0
"); }); ================================================ FILE: packages/unis-dom/src/browser/__test__/portal.test.tsx ================================================ /** * @vitest-environment jsdom */ import { afterEach, beforeEach, expect, it } from "vitest"; import { useEffect } from "@unis/core"; import { useState } from "@unis/core"; import { createPortal, Fragment } from "@unis/core"; import { rendered, testRender } from "./util"; let root: Element; let dialog: Element; beforeEach(() => { root = document.createElement("div"); dialog = document.createElement("div"); document.body.append(root, dialog); }); afterEach(() => { root.innerHTML = ""; dialog.innerHTML = ""; }); it("portal", async () => { const App = () => { let [visible, setVisible] = useState(true); useEffect( () => { setVisible(false); }, () => [] ); return () => (
hello
{visible && createPortal(
hello dialog
, dialog)}
); }; testRender(, root); expect(document.body.innerHTML).toBe( "
hello
hello dialog
" ); await rendered(); expect(document.body.innerHTML).toBe( "
hello
" ); }); ================================================ FILE: packages/unis-dom/src/browser/__test__/reconcile.test.tsx ================================================ /** * @vitest-environment jsdom */ import { afterEach, beforeEach, expect, it } from "vitest"; import { useEffect } from "@unis/core"; import { useState } from "@unis/core"; import { rendered, testRender } from "./util"; let root: Element; beforeEach(() => { root = document.createElement("div"); document.body.append(root); }); afterEach(() => { root.innerHTML = ""; }); it("diff with key", async () => { const App = () => { let [toggle, setToggle] = useState(false); useEffect( () => { setToggle(true); }, () => [] ); return () => !toggle ? (
1 2 3
del
4
5
6
) : (
1
5
4 2 3
6
); }; testRender(, root); expect(root.innerHTML).toBe( '
123
del
4
5
6
' ); await rendered(); expect(root.innerHTML).toBe( '
1
5
423
6
' ); }); ================================================ FILE: packages/unis-dom/src/browser/__test__/util.ts ================================================ import { readyForWork, createRoot, createTokTik } from "@unis/core"; import { createOperator } from "../operator"; const toktik = createTokTik({ nextTick: (cb: VoidFunction) => Promise.resolve() .catch((err) => console.error(err)) .then(() => cb()), now: () => 0, }); const operator = createOperator(); export const testRender = ( element: any, container: Element, hydrate = false ) => { const rootFiber = createRoot(element, container); rootFiber.runtime = { toktik, operator, }; readyForWork(rootFiber, hydrate); }; export const rendered = () => new Promise((resolve) => { setTimeout(resolve, 0); }); ================================================ FILE: packages/unis-dom/src/browser/__test__/utils.test.ts ================================================ /** * @vitest-environment jsdom */ import { expect, it } from "vitest"; import { classes, svgKey, styleStr } from "@unis/core"; it("classes", () => { expect(classes(["a", "b", 1, ["c", { d: true }]])).toBe("a b 1 c d"); expect(classes({ a: true, b: undefined, c: null })).toBe("a"); expect(classes({ a: false, b: true, c: null })).toBe("b"); }); it("realSVGAttr", () => { expect(svgKey("glyphOrientationVertical")).toBe("glyph-orientation-vertical"); }); it("style2String", () => { expect(styleStr({ background: "yellow", fontSize: "14px" })).toBe( "background: yellow; font-size: 14px;" ); }); ================================================ FILE: packages/unis-dom/src/browser/const.ts ================================================ export const UNIS_ROOT = Symbol("unis_root"); ================================================ FILE: packages/unis-dom/src/browser/index.ts ================================================ export { UNIS_ROOT } from "./const"; export { render } from "./render"; ================================================ FILE: packages/unis-dom/src/browser/operator.ts ================================================ import { Fiber, findEls, isPortal, isText, Operator } from "@unis/core"; import { getEventName, isEvent, isNullish } from "@unis/core"; import { UNIS_ROOT } from "./const"; type FiberDomEl = Element | DocumentFragment | SVGAElement | Text | ParentNode; interface FiberDom extends Fiber { el?: FiberDomEl; } export const createOperator = (): Operator => { const createElement = (fiber: FiberDom) => { const { tag: type, isSvg } = fiber; return isText(fiber) ? document.createTextNode(fiber.props.nodeValue + "") : isSvg ? document.createElementNS("http://www.w3.org/2000/svg", type as string) : document.createElement(type as string); }; const nextElement = (el: FiberDomEl | null) => { while (el) { if (el.firstChild) return el.firstChild; if (el.nextSibling) return el.nextSibling; while ((el = el.parentNode)) { if ((el as any)[UNIS_ROOT]) return null; if (el.nextSibling) return el.nextSibling; } } return null; }; const matchElement = (fiber: FiberDom, el: Element | Text) => el.nodeType === Node.TEXT_NODE ? isText(fiber) : (el as Element).tagName.toLocaleLowerCase() === fiber.tag; const insertBefore = ( containerFiber: FiberDom, insertElement: FiberDomEl, targetElement: FiberDomEl | null ) => { ( (isPortal(containerFiber) ? containerFiber.to : containerFiber.el)! as FiberDomEl ).insertBefore(insertElement, targetElement); }; const nextSibling = (fiber: FiberDom) => fiber.el!.nextSibling; const firstChild = (fiber: FiberDom) => fiber.el!.firstChild; const remove = (fiber: FiberDom) => { const [first, ...rest] = findEls(fiber) as FiberDomEl[]; const parentNode = first?.parentNode; if (parentNode) { for (const el of [first, ...rest]) { parentNode.removeChild(el); } } }; const updateTextProperties = (fiber: FiberDom) => { (fiber.el! as Text).nodeValue = fiber.props.nodeValue + ""; }; const setAttr = ( el: SVGAElement | HTMLElement, isSvg: boolean, key: string, value: string ) => isSvg ? (el as SVGAElement).setAttributeNS(null, key, value) : (el as HTMLElement).setAttribute(key, value); const removeAttr = ( el: SVGAElement | HTMLElement, isSvg: boolean, key: string ) => isSvg ? (el as SVGAElement).removeAttributeNS(null, key) : (el as HTMLElement).removeAttribute(key); const updateElementProperties = (fiber: FiberDom) => { let { el, isSvg, attrDiff } = fiber; for (const [key, newValue, oldValue] of attrDiff || []) { const newExist = !isNullish(newValue); const oldExist = !isNullish(oldValue); if (key === "ref") { oldExist && (oldValue.current = undefined); newExist && (newValue.current = el); } else if (isEvent(key)) { const [eventName, capture] = getEventName(key); oldExist && el!.removeEventListener(eventName, oldValue); newExist && el!.addEventListener(eventName, newValue, capture); } else { newExist ? setAttr(el as SVGAElement | HTMLElement, isSvg!, key, newValue) : removeAttr(el as SVGAElement | HTMLElement, isSvg!, key); } } }; return { createElement, nextElement, matchElement, insertBefore, nextSibling, firstChild, remove, updateTextProperties, updateElementProperties, }; }; ================================================ FILE: packages/unis-dom/src/browser/render.ts ================================================ import { readyForWork, createRoot, createTokTik } from "@unis/core"; import { createOperator } from "./operator"; import { UNIS_ROOT } from "./const"; import { nextTick, now } from "./toktik"; const operator = createOperator(); const toktik = createTokTik({ now, nextTick, interval: (window as any).UNIS_INTERVAL, }); export const render = (element: any, container: Element, hydrate = false) => { (container as any)[UNIS_ROOT] = true; const rootFiber = createRoot(element, container); rootFiber.runtime = { toktik, operator, }; readyForWork(rootFiber, hydrate); }; ================================================ FILE: packages/unis-dom/src/browser/toktik.ts ================================================ export const nextTick = (cb: VoidFunction, pending = false) => { if (pending) { queueMicrotask(cb); } else if (window.MessageChannel) { const { port1, port2 } = new window.MessageChannel(); port1.postMessage(""); port2.onmessage = () => cb(); } else { setTimeout(() => cb()); } }; export const now = () => performance.now(); ================================================ FILE: packages/unis-dom/src/server/__test__/server.test.tsx ================================================ import { expect, it } from "vitest"; import { renderToString } from ".."; it("render to string", () => { const App = () => { return () => (
<>

hello

world
); }; const result = renderToString(); expect(result).toBe( '

hello

world
' ); }); ================================================ FILE: packages/unis-dom/src/server/index.ts ================================================ import { createRoot, createTokTik, readyForWork } from "@unis/core"; import { createOperator, ElementNode } from "./operator"; const operator = createOperator(); const toktik = createTokTik({ nextTick: (cb: VoidFunction) => Promise.resolve() .catch((err) => console.error(err)) .then(() => cb()), now: () => 0, }); export const renderToString = (element: any) => { const rootNode = new ElementNode(""); const rootFiber = createRoot(element, rootNode); rootFiber.runtime = { toktik, operator, }; readyForWork(rootFiber); return rootNode.renderToString(); }; ================================================ FILE: packages/unis-dom/src/server/operator.ts ================================================ import { isEvent, isNullish, Operator, Fiber, isText } from "@unis/core"; export class ElementNode { children: ServerNode[] = []; properties: Record = {}; constructor(public tagName: string) {} insertBefore( node: ElementNode | TextNode, child: ElementNode | TextNode | null ) { this.children.push(node); } append(...nodes: ServerNode[]) { this.children.push(...nodes); } renderToString(): string { const children = this.children .map((child) => child.renderToString()) .join(""); const propertiesStr = Object.keys(this.properties) .map((key) => `${key}="${this.properties[key]}"`) .join(" "); const gap = propertiesStr ? " " : ""; return this.tagName ? `<${this.tagName}${gap}${propertiesStr}>${children}` : children; } } export class TextNode { constructor(public nodeValue: string) {} renderToString() { return this.nodeValue; } } export type ServerNode = ElementNode | TextNode; interface FiberDomServer extends Fiber { el?: ServerNode; } export const createOperator = (): Operator => { const createElement = (fiber: FiberDomServer) => { const { tag: type } = fiber; return isText(fiber) ? new TextNode(fiber.props.nodeValue + "") : new ElementNode(type as string); }; const insertBefore = ( containerFiber: FiberDomServer, insertElement: ServerNode, targetElement: ServerNode ) => { (containerFiber.el as ElementNode).insertBefore( insertElement, targetElement ); }; const firstChild = (fiber: FiberDomServer) => (fiber.el as ElementNode).children[0] ?? null; const updateTextProperties = (fiber: FiberDomServer) => { (fiber.el as TextNode).nodeValue = fiber.props.nodeValue + ""; }; const updateElementProperties = (fiber: FiberDomServer) => { let { el, attrDiff } = fiber; for (const [key, newValue, oldValue] of attrDiff || []) { const newExist = !isNullish(newValue); const oldExist = !isNullish(oldValue); if (key === "ref") { oldExist && (oldValue.current = undefined); newExist && (newValue.current = el); } else if (isEvent(key)) { // nothing... } else { newExist ? ((el as ElementNode).properties[key] = newValue) : delete (el as ElementNode).properties[key]; } } }; return { nextElement() {}, matchElement: () => false, remove() {}, nextSibling() {}, createElement, insertBefore, firstChild, updateTextProperties, updateElementProperties, }; }; ================================================ FILE: packages/unis-dom/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["**/*.test.*", "**/__test__"] } ================================================ FILE: packages/unis-dom/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "build" }, "include": ["src"] } ================================================ FILE: packages/unis-dom/vitest.config.ts ================================================ import { defineConfig } from "vitest/config"; import { unisPreset } from "@unis/vite-preset"; export default defineConfig({ plugins: [unisPreset()], test: { include: ["**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], coverage: { reporter: ["text", "json", "html"], }, }, }); ================================================ FILE: packages/unis-example/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next # Nuxt.js build / generate output .nuxt dist build # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and *not* Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port .DS_Store ================================================ FILE: packages/unis-example/index.html ================================================ Unis
================================================ FILE: packages/unis-example/other.d.ts ================================================ declare module "*.css"; ================================================ FILE: packages/unis-example/package.json ================================================ { "name": "@unis/unis-example", "version": "0.0.0", "description": "", "main": "index.js", "private": "true", "scripts": { "dev": "vite", "build": "vite build" }, "repository": { "type": "git", "url": "git+https://github.com/anuoua/unis.git" }, "author": "anuoua", "license": "MIT", "bugs": { "url": "https://github.com/anuoua/unis/issues" }, "homepage": "https://github.com/anuoua/unis#readme", "dependencies": { "@unis/core": "workspace:^", "@unis/dom": "workspace:^", "@unis/router": "workspace:^", "@unis/transition": "workspace:^" }, "devDependencies": { "@types/lodash": "^4.14.182", "@unis/vite-preset": "workspace:^", "autoprefixer": "^10.4.0", "postcss": "^8.3.11", "rollup-plugin-reassign": "^1.0.2", "tailwindcss": "^3.3.1", "vite": "^4.1.2" } } ================================================ FILE: packages/unis-example/postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, }; ================================================ FILE: packages/unis-example/src/Dialog.tsx ================================================ import { createPortal, useProps, useRef } from "@unis/core"; import { Item } from "./TodoItem"; interface DialogProps { onClose: () => void; onConfirm: (item: Item) => void; item: Item | null; } export function Dialog(props: DialogProps) { let { item, onClose, onConfirm } = useProps(props); const portalRef = useRef(); const handleClose = (e: MouseEvent) => { if ((e.target as HTMLElement) === portalRef.current) { onClose(); } }; const handleConfirm = () => { onConfirm(item!); }; return () => createPortal(
{" "} Delete {item?.name} ?
, document.querySelector("#dialog")! ); } ================================================ FILE: packages/unis-example/src/Todo.tsx ================================================ import { useState } from "@unis/core"; import { CSSTransition, TransitionGroup } from "@unis/transition"; import { Dialog } from "./Dialog"; import { Item, TodoItem } from "./TodoItem"; let count = 0; let todos: any[] = []; for (; count < 0; count++) { todos.push({ id: count, name: "", editing: false, canceled: false, }); } export function ToDo() { let [todoList, setTodoList] = useState(todos); let [dialogVisible, setDialogVisible] = useState(false); let [titleVisible, setTitleVisible] = useState(true); let [currentItem, setCurrentItem] = useState(null); const handleToggleTitle = () => { setTitleVisible(!titleVisible); setTimeout(() => { setTitleVisible(true); }, 200); }; const handleAdd = (e: any) => { if (e.key !== "Enter") return; setTodoList([ ...todoList, { id: ++count, name: e.target.value, editing: false, canceled: false, }, ]); e.target.value = ""; }; const handleClose = () => { setDialogVisible(false); }; const handleConfirm = (item: Item) => { setDialogVisible(false); setTodoList(todoList.filter((i) => i !== item)); }; const handleDelete = (item: Item) => { setCurrentItem(item); setTodoList(todoList.filter((i) => i !== item)); // setDialogVisible(true); }; return () => (

TODO

    {todoList.map((i: any) => ( ))}
{dialogVisible && ( )}
); } ================================================ FILE: packages/unis-example/src/TodoItem/index.module.css ================================================ .deleteIcon { color: white; } ================================================ FILE: packages/unis-example/src/TodoItem/index.tsx ================================================ import { use, memo, useEffect, useProps, useRef } from "@unis/core"; import { Update } from "../hooks/update"; import s from "./index.module.css"; export interface Item { id: number; name: string; editing: boolean; canceled: boolean; } interface TodoItemProps { item: Item; onDelete: (item: Item) => any; } export const TodoItem = memo((props: TodoItemProps) => { let { item, onDelete } = useProps(props); let [render] = use(Update()); let { editing, canceled, name } = use(() => item); const inputRef = useRef(); const handleEditing = () => { if (canceled) return; item.editing = true; render(); }; const handleClick = () => { onDelete(item); }; const handleCancel = () => { console.log(inputRef.current); item.canceled = !item.canceled; render(); }; const handleKeyDown = (e: any) => { if (e.key !== "Enter") return; item.name = e.target.value; item.editing = false; render(); }; const handleBlur = () => { item.editing = false; render(); }; useEffect( () => { item.name = item.name + "x"; render(); }, () => [item.canceled] ); return () => { // console.log("TodoItem render"); return (
  • {editing ? ( ) : canceled ? ( {name} ) : ( {name} )} {canceled ? ( ) : ( )}
  • ); }; }); ================================================ FILE: packages/unis-example/src/Welcome/index.module.css ================================================ .msg { font-size: 50px; font-weight: bold; background: -webkit-linear-gradient(0deg,#e339bc 25%,#4f5b94); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; transition: all .3s ease; } .msg:hover { cursor: pointer; transform: scale(1.2); background: -webkit-linear-gradient(0deg,#4f5b94 25%,#e339bc); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; } ================================================ FILE: packages/unis-example/src/Welcome/index.tsx ================================================ import { Link } from "@unis/router"; import s from "./index.module.css"; export const Welcome = () => { return () => ( Unis Todo ); }; ================================================ FILE: packages/unis-example/src/global.css ================================================ @tailwind base; @tailwind components; @tailwind utilities; html, body, #root { height: 100%; } .scale-appear { transform: scale(0) translateZ(0); } .scale-appear-active { transform: scale(1) translateZ(0); transition: all 0.4s ease; } .scale-appear-done { transform: scale(1) translateZ(0); } .scale-enter { transform: scale(0) translateZ(0); } .scale-enter-active { transform: scale(1) translateZ(0); transition: all 0.4s ease; } .scale-enter-done { transform: scale(1) translateZ(0); } .scale-exit { transform: scale(1) translateZ(0); } .scale-exit-active { transform: scale(0) translateZ(0); transition: all 0.4s ease; } .scale-exit-done { transform: scale(0) translateZ(0); } .fade-enter { opacity: 0; } .fade-enter-active { opacity: 1; transition: all 0.4s ease; } .fade-enter-done { opacity: 1; } .fade-exit { opacity: 1; } .fade-exit-active { opacity: 0; transition: all 0.4s ease; } .fade-exit-done { opacity: 0; } ================================================ FILE: packages/unis-example/src/hooks/update.ts ================================================ import { useState } from "@unis/core"; export const Update = () => { let [count, setCount] = useState(1); const update = () => setCount(++count); return () => [update]; }; ================================================ FILE: packages/unis-example/src/index.module.css ================================================ .background_img { background: url(./bg.jpeg); z-index: -1; background-size: cover; background-position: center; } ================================================ FILE: packages/unis-example/src/index.tsx ================================================ import { Fragment, // useProps, // useState, // useEffect } from "@unis/core"; import { render } from "@unis/dom"; import { ToDo } from "./Todo"; import "./global.css"; import s from "./index.module.css"; import { BrowserRouter, Redirect, Outlet, Route, Routes } from "@unis/router"; import { Welcome } from "./Welcome"; // const Bpp = (props: { time: number; msg: string }) => { // let { time, msg } = useProps(props); // let [visible, setVisible] = useState(false); // useEffect( // () => { // setVisible(true); // setTimeout(() => { // console.log("timeout"); // setVisible(false); // }, 0); // }, // () => [] // ); // setTimeout(() => { // setVisible(true); // }, time); // return () => (visible ?
    {msg}
    : false); // }; const App = () => { return () => (
    <> {/* */} {/* */} {/* */}
    ); }; render( }> } /> } /> } /> , document.querySelector("#root")! ); ================================================ FILE: packages/unis-example/tailwind.config.js ================================================ module.exports = { content: ["./public/**/*.html", "./src/**/*.{js,jsx,ts,tsx,vue}"], theme: { extend: {}, }, variants: { extend: {}, }, plugins: [], }; ================================================ FILE: packages/unis-example/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "include": ["src", "other.d.ts"] } ================================================ FILE: packages/unis-example/vite.config.js ================================================ import { defineConfig } from "vite"; import { unisPreset } from "@unis/vite-preset"; export default defineConfig({ plugins: [unisPreset()], }); ================================================ FILE: packages/unis-router/.gitignore ================================================ build/ coverage/ dist/ ================================================ FILE: packages/unis-router/README.md ================================================ # Unis Router Router for unis, inspire by [React Router V6](https://github.com/remix-run/react-router). ## Install ```shell npm i @unis/router ``` ## Usage Unis router's api is partial same as React Router V6. example ```javascript import { BrowserRouter, Routes, Route, Outlet } from '@unis/router' const Dashboard = () => { return () => (
    dashboard // hello
    ) } const App = () => { return () => (
    App
    }> hello
    } /> ) } render( , document.querySelector('#app')) ``` ================================================ FILE: packages/unis-router/package.json ================================================ { "name": "@unis/router", "version": "0.1.0", "description": "Unis router component", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", "typings": "dist/index.d.ts", "scripts": { "build": "rimraf build && rimraf dist && tsc -p tsconfig.json && rollup --config", "build:dev": "cross-env NODE_ENV=development pnpm build", "test": "vitest run --coverage", "test:watch": "vitest -w" }, "exports": { ".": { "require": "./dist/index.js", "import": "./dist/index.mjs" } }, "keywords": [ "unis", "router" ], "files": [ "dist" ], "author": "anuoua", "peerDependencies": { "@unis/core": "workspace:^" }, "license": "MIT", "bugs": { "url": "https://github.com/anuoua/unis/issues" }, "homepage": "https://github.com/anuoua/unis/tree/main/packages/unis-router", "devDependencies": { "@rollup/plugin-node-resolve": "^15.0.2", "@types/node": "^18.15.11", "@unis/core": "workspace:^", "@unis/vite-preset": "workspace:^", "@vitest/coverage-c8": "^0.29.8", "cross-env": "^7.0.3", "esbuild": "^0.17.15", "rimraf": "^4.4.1", "rollup": "^3.20.2", "rollup-plugin-dts": "^5.3.0", "rollup-plugin-esbuild": "^5.0.0", "rollup-plugin-reassign": "^1.0.3", "typescript": "^5.0.3", "vitest": "^0.29.8" }, "dependencies": { "history": "^5.3.0" } } ================================================ FILE: packages/unis-router/rollup.config.mjs ================================================ import esbuild from "rollup-plugin-esbuild"; import dts from "rollup-plugin-dts"; import { defineConfig } from "rollup"; import { nodeResolve } from "@rollup/plugin-node-resolve"; import { reassign } from "rollup-plugin-reassign"; import { unisFns } from "@unis/core"; const configGen = (format) => defineConfig({ input: "src/index.ts", external: [/^@unis/, "history"], output: [ { dir: "dist", entryFileNames: `index.${format === "esm" ? "mjs" : "js"}`, format, sourcemap: true, }, ], plugins: [ nodeResolve(), esbuild({ sourceMap: true, target: "esnext", jsx: "automatic", jsxImportSource: "@unis/core", }), reassign({ include: ["**/*.(t|j)s?(x)"], targetFns: { "@unis/core": unisFns, }, }), ], }); const dtsRollup = () => defineConfig({ input: "build/index.d.ts", output: [{ file: "dist/index.d.ts", format: "es" }], plugins: [dts()], }); const config = [configGen("cjs"), configGen("esm"), dtsRollup()]; export default config; ================================================ FILE: packages/unis-router/src/components/BrowserRouter.tsx ================================================ import { useProps, useLayoutEffect, useState, useMemo } from "@unis/core"; import { createBrowserHistory, BrowserHistory } from "history"; import { LocationContext, RouterContext } from "../context"; export interface BrowserRouterProps { children?: JSX.Element | JSX.Element[]; history?: BrowserHistory; basename?: string; } export const BrowserRouter = (p: BrowserRouterProps) => { let { children, history, basename } = useProps(p); const historyInstance = history ?? createBrowserHistory(); let [location, setLocation] = useState(historyInstance.location); const navigationContextValue = { basename: basename ?? "", history: historyInstance, }; let locationContextValue = useMemo( () => ({ location }), () => [location] ); useLayoutEffect( () => { const unlisten = historyInstance.listen(({ location }) => { setLocation(location); }); return () => unlisten(); }, () => [] ); return () => ( {children} ); }; ================================================ FILE: packages/unis-router/src/components/Link.tsx ================================================ import { AnchorHTMLAttributes, ElementAttrs, use, useContext, useProps, } from "@unis/core"; import { RouterContext } from "../context"; import { uTargetPath } from "../hooks/uTargetPath"; export type LinkProps = Partial< Omit, "href"> & { to: string; } >; export const Link = (p: LinkProps) => { let { to, children, onClick, ...rest } = useProps(p); let { history } = useContext(RouterContext); let targetPath = use(uTargetPath(() => ({ to }))); const handleJump = (e: MouseEvent) => { const target = (e.target as HTMLAnchorElement).target; if ( e.button === 0 && (!target || target === "_self") && !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) ) { if (history.location.pathname !== targetPath) { history.push(targetPath); } else { history.replace(targetPath); } e.preventDefault(); } onClick?.(e); }; return () => ( {children} ); }; ================================================ FILE: packages/unis-router/src/components/NavLink.tsx ================================================ import { HTMLAttributes, use, useContext, useProps } from "@unis/core"; import { RouteContext } from "../context"; import { uTargetPath } from "../hooks/uTargetPath"; import { Link, LinkProps } from "./Link"; export type LinkStyle = (isActive: boolean) => HTMLAttributes["style"]; export type LinkClassName = (isActive: boolean) => HTMLAttributes["className"]; export type NavLinkProps = Omit & { style?: LinkStyle; className?: LinkClassName; }; export const NavLink = (p: NavLinkProps) => { let { style, className, to, ...rest } = useProps(p); let { matches } = useContext(RouteContext); let targetPath = use(uTargetPath(() => ({ to }))); let isActive = use(() => !!matches.find((i) => i.pathname === targetPath)); return () => ( ); }; ================================================ FILE: packages/unis-router/src/components/Outlet.tsx ================================================ import { use, useContext } from "@unis/core"; import { RouteContext } from "../context"; export const Outlet = () => { let { matches, route } = useContext(RouteContext); let nextRoute = use(() => matches[matches.findIndex((i) => i === route) + 1]); let { element, ...rest } = use(() => route ?? {}); return () => route ? ( {element} ) : null; }; ================================================ FILE: packages/unis-router/src/components/Redirect.tsx ================================================ import { useContext, useEffect, useProps } from "@unis/core"; import { RouteContext, RouterContext } from "../context"; import { resolvePath } from "../utils"; export interface RedirectProps { to?: string; replace?: boolean; } export const Redirect = (p: RedirectProps) => { let { to, replace = true } = useProps(p); let { history } = useContext(RouterContext); let { route } = useContext(RouteContext); useEffect( () => { if (route) { const pathname = resolvePath(route.pathname!, to ?? ""); replace ? history.replace(pathname) : history.push(pathname); } }, () => [] ); return () => null; }; ================================================ FILE: packages/unis-router/src/components/Route.tsx ================================================ import { RouteData } from "../types"; export type RouteProps = Omit & { children?: JSX.Element | JSX.Element[]; }; export const Route = (p: RouteProps) => null; ================================================ FILE: packages/unis-router/src/components/Routes.tsx ================================================ import { cloneElement, FiberNode, use, useProps } from "@unis/core"; import { uRouter } from "../hooks/uRouter"; import { Route } from "./Route"; import { RouteData } from "../types"; export type RoutesProps = Omit & { children?: JSX.Element | JSX.Element[]; }; export const Routes = (p: RoutesProps) => { let { children, path, element: incomeElement } = useProps(p); let realChildren = use(() => flatChildren(children)); let routes = use(() => realChildren.map((node) => pick(node))); let element = use( uRouter(() => [ { path, element: incomeElement, children: routes, } as RouteData, ]) ); function pick(node: FiberNode): RouteData { return { ...node.props, path: node.props.path, element: cloneElement(node.props.element), children: flatChildren(node.props.children).map(pick), }; } function flatChildren(children: JSX.Element | JSX.Element[]) { return ([] as FiberNode[]) .concat(children as FiberNode[]) .filter((child) => child?.tag === Route); } return () => element; }; ================================================ FILE: packages/unis-router/src/context.tsx ================================================ import { createContext } from "@unis/core"; import { BrowserHistory, Location } from "history"; import { MatchRoute } from "./types"; export interface RouteContextValue { route: MatchRoute; matches: MatchRoute[]; } export const RouteContext = createContext({ route: undefined!, matches: [], }); export interface RouterContextValue { history: BrowserHistory; basename: string; } export const RouterContext = createContext(undefined!); export interface LocationContextValue { location: Location; } export const LocationContext = createContext(undefined!); ================================================ FILE: packages/unis-router/src/hooks/uHistory.ts ================================================ import { useContext } from "@unis/core"; import { RouterContext } from "../context"; export const uHistory = () => { let { history } = useContext(RouterContext); return () => history!; }; ================================================ FILE: packages/unis-router/src/hooks/uLocation.ts ================================================ import { use } from "@unis/core"; import { uHistory } from "./uHistory"; export const uLocation = () => { let { location } = use(uHistory()); return () => location; }; ================================================ FILE: packages/unis-router/src/hooks/uParams.ts ================================================ import { useContext } from "@unis/core"; import { RouteContext } from "../context"; export const uParams = >() => { let { route } = useContext(RouteContext); return () => route?.params as unknown as T; }; ================================================ FILE: packages/unis-router/src/hooks/uRouter.tsx ================================================ import { use, useContext } from "@unis/core"; import { RouteContext, RouterContext } from "../context"; import { Outlet } from "../components/Outlet"; import { RouteData } from "../types"; import { matchRoutes } from "../utils"; export const uRouter = (configFn: () => RouteData[]) => { let routerData = use(configFn); let { history, basename } = useContext(RouterContext); let location = use(() => history?.location!); let wrapedRouterData = use(() => [ { path: basename, element: , children: routerData, } as RouteData, ]); let matches = use(() => matchRoutes(location.pathname, wrapedRouterData)); return () => ( ); }; ================================================ FILE: packages/unis-router/src/hooks/uTargetPath.tsx ================================================ import { use, useContext } from "@unis/core"; import { RouteContext } from "../context"; import { resolvePath } from "../utils"; export interface Options { to?: string; } export const uTargetPath = (opts: () => Options) => { let { to } = use(opts); let { route } = useContext(RouteContext); let targetPath = use(() => resolvePath(route.pathname ?? "", to ?? "")); return () => targetPath; }; ================================================ FILE: packages/unis-router/src/index.ts ================================================ export * from "./components/BrowserRouter"; export * from "./context"; export * from "./components/Routes"; export * from "./components/Route"; export * from "./components/Outlet"; export * from "./components/Link"; export * from "./components/NavLink"; export * from "./components/Redirect"; export * from "./hooks/uHistory"; export * from "./hooks/uRouter"; export * from "./hooks/uLocation"; export * from "./hooks/uHistory"; export * from "./hooks/uParams"; ================================================ FILE: packages/unis-router/src/types.ts ================================================ export interface RouteData { path?: string; element?: JSX.Element; children?: RouteData[]; } export interface MatchRoute extends RouteData { pathname?: string; params?: Record; } ================================================ FILE: packages/unis-router/src/utils.test.tsx ================================================ import { expect, it } from "vitest"; import { RouteData } from "./types"; import { matchRoutes, resolvePath } from "./utils"; const getRoutes = (): RouteData[] => [ { path: "/", children: [ { path: "home/:from/", children: [ { path: "*" }, { path: "post/:id", }, { path: "post/1", children: [ { children: [{ path: "xx" }], }, ], }, ], }, ], }, ]; it("root match", () => { const matchedRoutes = matchRoutes("/home/www", getRoutes()); expect(matchedRoutes).toMatchObject([ { path: "/", params: {}, pathname: "/" }, { path: "home/:from/", params: { from: "www" }, pathname: "/home/www", }, ]); }); it("match test1", () => { const matchedRoutes = matchRoutes("/home/www/post", getRoutes()); expect(matchedRoutes).toMatchObject([ { path: "/", params: {}, pathname: "/" }, { path: "home/:from/", params: { from: "www" }, pathname: "/home/www", }, { path: "*", params: { from: "www" }, pathname: "/home/www/post" }, ]); }); it("match test2", () => { const matchedRoutes = matchRoutes("/home/www/post/1", getRoutes()); expect(matchedRoutes).toMatchObject([ { path: "/", params: {}, pathname: "/" }, { path: "home/:from/", params: { from: "www" }, pathname: "/home/www", }, { path: "post/1", params: { from: "www" }, pathname: "/home/www/post/1", }, { params: { from: "www" }, pathname: "/home/www/post/1" }, ]); }); it("match test3", () => { const matchedRoutes = matchRoutes("/home/www/post/2", getRoutes()); expect(matchedRoutes).toMatchObject([ { path: "/", params: {}, pathname: "/" }, { path: "home/:from/", params: { from: "www" }, pathname: "/home/www", }, { path: "post/:id", params: { from: "www", id: "2" }, pathname: "/home/www/post/2", }, ]); }); it("match test4", () => { const matchedRoutes = matchRoutes("/home/www/post/1/x", getRoutes()); expect(matchedRoutes).toMatchObject([ { path: "/", params: {}, pathname: "/" }, { path: "home/:from/", params: { from: "www" }, pathname: "/home/www", }, { path: "*", params: { from: "www" }, pathname: "/home/www/post/1/x", }, ]); }); it("match test5", () => { const matchedRoutes = matchRoutes("/home/www/post/1/xx", getRoutes()); expect(matchedRoutes).toMatchObject([ { path: "/", params: {}, pathname: "/" }, { path: "home/:from/", params: { from: "www" }, pathname: "/home/www", }, { path: "post/1", params: { from: "www" }, pathname: "/home/www/post/1", }, { params: { from: "www" }, pathname: "/home/www/post/1", }, { path: "xx", params: { from: "www" }, pathname: "/home/www/post/1/xx", }, ]); }); it("match test6", () => { const matchedRoutes = matchRoutes("/app/home/www/post/1/x", getRoutes(), [ { path: "/app" }, ]); expect(matchedRoutes).toMatchObject([ { path: "/app", params: {}, pathname: "/app" }, { path: "/", params: {}, pathname: "/app" }, { path: "home/:from/", params: { from: "www" }, pathname: "/app/home/www", }, { path: "*", params: { from: "www" }, pathname: "/app/home/www/post/1/x", }, ]); }); it("resolvePath", () => { const path = resolvePath("/home/c", "../a"); expect(path).toBe("/a"); const path2 = resolvePath("/home/c", "./a"); expect(path2).toBe("/home/a"); const path3 = resolvePath("/home/c", "/a"); expect(path3).toBe("/a"); }); ================================================ FILE: packages/unis-router/src/utils.ts ================================================ import { MatchRoute, RouteData } from "./types"; const SLASH = "/"; const DOT = "."; const trimSlash = (str: string) => { const reg = new RegExp(`^[${SLASH}${DOT}]*`); const reg2 = new RegExp(`[${SLASH}${DOT}]*$`); return str.replace(reg, "").replace(reg2, ""); }; const split = (path: string) => trimSlash(path) ? trimSlash(path).split(SLASH) : []; const analysePath = (locationPathname: string, routePath: string) => { const locationChunks = split(locationPathname); const routeChunks = split(routePath); const params: Record = {}; for (let i = 0; i < routeChunks.length; i++) { const routeChunk = routeChunks[i]; if (routeChunk.startsWith(":")) { params[routeChunk.slice(1)] = locationChunks[i]; } } return { params, pathname: SLASH + (locationChunks .slice( 0, routeChunks?.at(-1) === "*" ? undefined : routeChunks?.length ?? 0 ) .join(SLASH) ?? ""), }; }; const testPath = ( locationPathname: string, routePath: string, final = false ) => { const locationChunks = split(locationPathname); const routeChunks = split(routePath); for (let i = 0; i < routeChunks.length; i++) { const routeChunk = routeChunks[i]!; const locationChunk = locationChunks[i]; if ( !locationChunk || (!routeChunk.startsWith(":") && routeChunk !== "*" && routeChunk !== locationChunk) ) { return false; } } if (final && locationChunks.length > routeChunks.length) { return routeChunks.at(-1) === "*"; } return true; }; const getScore = (locationPathname: string, routeChain: RouteData[]) => { const routePath = resolveRoutesPath(routeChain); const locationChunks = split(locationPathname); const routeChunks = split(routePath); let score = 0; routeChunks.forEach((chunk, index) => { let base = 10 ^ ((locationChunks.length ?? 0) - index); if (chunk === "*") score += 1 * base; if (chunk.startsWith(":")) score += 2 * base; if (chunk === locationChunks[index]) score += 3 * base; }); return score; }; const resolveRoutesPath = (routes: RouteData[]) => routes.reduce((pre, cur) => { const path = trimSlash(cur.path ?? ""); return `${pre}${path ? SLASH + path : ""}`; }, "") || "/"; const isLocationEnd = (locationPathname: string, routePath: string) => { return split(locationPathname).length === split(routePath).length; }; const matchRoute = ( locationPathname: string, route: RouteData, parentRouteChain: RouteData[] = [] ) => { const routeStack: RouteData[] = parentRouteChain; let matchedRouteChains: RouteData[][] = []; const walk = (route: RouteData) => { const routeChain = [...routeStack, route]; const routePath = resolveRoutesPath(routeChain); const isEnd = (route.children?.length ?? 0) === 0; const result = testPath(locationPathname, routePath, isEnd); if (result) { if (isEnd) { matchedRouteChains.push(routeChain); } else { const preChainSize = matchedRouteChains.length; route.children?.forEach((childRoute) => { routeStack.push(route); walk(childRoute); routeStack.pop(); }); const afterChainSize = matchedRouteChains.length; if ( afterChainSize === preChainSize && isLocationEnd(locationPathname, routePath) ) { matchedRouteChains.push(routeChain); } } } }; walk(route); return matchedRouteChains; }; export const matchRoutes = ( locationPathname: string, routes: RouteData[], parentRouteChain: RouteData[] = [] ) => { const matchedRouteChains: RouteData[][] = []; routes.forEach((route) => { matchedRouteChains.push( ...matchRoute(locationPathname, route, parentRouteChain) ); }); matchedRouteChains.sort((a, b) => { const result = getScore(locationPathname, b) - getScore(locationPathname, a); if (result !== 0) return result; return b.length - a.length; }); const finalChain = matchedRouteChains.at(0) ?? []; if (finalChain) { finalChain.forEach((route, index) => { const subChain = finalChain.slice(0, index + 1); const { params, pathname } = analysePath( locationPathname, resolveRoutesPath(subChain) ); (route as MatchRoute).params = params; (route as MatchRoute).pathname = pathname; }); } return finalChain; }; export const resolvePath = (from: string, to: string) => new URL(to, `http://x${from}`).pathname; ================================================ FILE: packages/unis-router/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "build" }, "include": ["src"] } ================================================ FILE: packages/unis-router/vitest.config.ts ================================================ import { defineConfig } from "vitest/config"; import { unisPreset } from "@unis/vite-preset"; export default defineConfig({ plugins: [unisPreset()], test: { include: ["**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], coverage: { reporter: ["text", "json", "html"], }, }, }); ================================================ FILE: packages/unis-transition/.gitignore ================================================ build/ dist/ ================================================ FILE: packages/unis-transition/README.md ================================================ # Unis Transition Transition component for unis inspired by `React Transition Group`. ## Install ```shell npm i @unis/transition ``` ## Usage ```javascript import { useState } from '@unis/unis'; import { CSSTransition, TransitionGroup } from '@unis/transition' const App = () => { let [visible, setVisible] = useState(false); const handleToggle = () => { setVisible(!visible); } return () => (
    hello
    ) } ``` ```css .fade-appear { opacity: 0; } .fade-appear-active { opacity: 1; transition: all 0.4s ease; } .fade-appear-done { opacity: 1; } .fade-enter { opacity: 0; } .fade-enter-active { opacity: 1; transition: all 0.4s ease; } .fade-enter-done { opacity: 1; } .fade-exit { opacity: 1; } .fade-exit-active { opacity: 0; transition: all 0.4s ease; } .fade-exit-done { opacity: 0; } ``` Online [demo](https://stackblitz.com/edit/vitejs-vite-4cfy2b) here ## CSSTransition Component API as close to `React Transition Group` as possible. ### in Show the component. type: `boolean` default: `false` ### mountOnEnter By default the child component is mounted on the first `in={true}`. you can set `mountOnEnter={false}` child component will be mounted immediately with parent component. type: `boolean` default: `true` ### unmountOnExit By default the child component is unmounted after it finishes exiting. you can set `unmountOnExit={false}` child component stays mounted after it reaches the 'exited' state. type: `boolean` default: `true` ### classNames type: ```typescript string | { appear?: string, appearActive?: string, appearDone?: string, enter?: string, enterActive?: string, enterDone?: string, exit?: string, exitActive?: string, exitDone?: string, } ``` default: `''` for example `classNames="fade"` it will apply classes below - `fade-appear`, `fade-appear-active`, `fade-appear-done` - `fade-enter`, `fade-enter-active`, `fade-enter-done` - `fade-exit`, `fade-exit-active`, `fade-exit-done` ### onEnter Callback fired immediately after the 'enter' or 'appear' class is applied. type: `Function(node: HtmlElement)` ### onEntering Callback fired immediately after the 'enter-active' or 'appear-active' class is applied. type: `Function(node: HtmlElement)` ### onEntered Callback fired immediately after the 'enter' or 'appear' classes are removed and the done class is added to the DOM node. type: `Function(node: HtmlElement)` ### onExit Callback fired immediately after the 'exit' class is applied. type: `Function(node: HtmlElement)` ### onExiting Callback fired immediately after the 'exit-active' is applied. type: `Function(node: HtmlElement)` ### onExited Callback fired immediately after the 'exit' classes are removed and the exit-done class is added to the DOM node. type: `Function(node?: HtmlElement)` ## TransitionGroup Easy to use, just wrap on `CSSTransition` with key. ```javascript {list.map((item, index) => (
    {item.name}
    ))}
    ``` ================================================ FILE: packages/unis-transition/package.json ================================================ { "name": "@unis/transition", "version": "0.1.0", "description": "Unis transition component", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", "typings": "dist/index.d.ts", "scripts": { "build": "rimraf build && rimraf dist && tsc -p tsconfig.json && rollup --config", "build:dev": "cross-env NODE_ENV=development pnpm build" }, "exports": { ".": { "require": "./dist/index.js", "import": "./dist/index.mjs" } }, "keywords": [ "transition", "animation", "unis" ], "files": [ "dist" ], "author": "anuoua", "peerDependencies": { "@unis/core": "workspace:^" }, "license": "MIT", "bugs": { "url": "https://github.com/anuoua/unis/issues" }, "homepage": "https://github.com/anuoua/unis/tree/main/packages/unis-transition", "devDependencies": { "@rollup/plugin-node-resolve": "^15.0.2", "@unis/core": "workspace:^", "cross-env": "^7.0.3", "esbuild": "^0.17.15", "rimraf": "^4.4.1", "rollup": "^3.20.2", "rollup-plugin-dts": "^5.3.0", "rollup-plugin-esbuild": "^5.0.0", "rollup-plugin-reassign": "^1.0.3", "typescript": "^5.0.3" } } ================================================ FILE: packages/unis-transition/rollup.config.mjs ================================================ import dts from "rollup-plugin-dts"; import esbuild from "rollup-plugin-esbuild"; import { defineConfig } from "rollup"; import { nodeResolve } from "@rollup/plugin-node-resolve"; import { reassign } from "rollup-plugin-reassign"; import { unisFns } from "@unis/core"; const configGen = (format) => defineConfig({ input: "src/index.ts", external: [/^@unis/], output: [ { dir: "dist", entryFileNames: `index.${format === "esm" ? "mjs" : "js"}`, format, sourcemap: true, }, ], plugins: [ nodeResolve(), esbuild({ sourceMap: true, target: "esnext", jsx: "automatic", jsxImportSource: "@unis/core", }), reassign({ include: ["**/*.(t|j)s?(x)"], targetFns: { "@unis/core": unisFns, }, }), ], }); const dtsRollup = () => defineConfig({ input: "build/index.d.ts", output: [{ file: "dist/index.d.ts", format: "es" }], plugins: [dts()], }); const config = [configGen("cjs"), configGen("esm"), dtsRollup()]; export default config; ================================================ FILE: packages/unis-transition/src/CSSTransition.ts ================================================ import { findEls, use, useLayoutEffect, useProps } from "@unis/core"; import { uInstance } from "./hooks/uInstance"; import { APPEARED, APPEARING, ENTERED, ENTERING, EXITED, EXITING, TransitionTimeout, uTransition, } from "./hooks/uTransition"; import { uWatch } from "./hooks/uWatch"; export interface TransitionProps { children: JSX.Element; classNames: string; timeout?: TransitionTimeout; in?: boolean; mountOnEnter?: boolean; unmountOnExit?: boolean; appear?: boolean; enter?: boolean; onEnter?: (el: HTMLElement) => void; onEntering?: (el: HTMLElement) => void; onEntered?: (el: HTMLElement) => void; onExit?: (el: HTMLElement) => void; onExiting?: (el: HTMLElement) => void; onExited?: (el?: HTMLElement) => void; } export const CSSTransition = (p: TransitionProps) => { let { children, timeout, in: inProp, classNames, unmountOnExit, mountOnEnter, appear, enter, onEnter, onEntering, onEntered, onExit, onExiting, onExited, } = useProps(p); let [instance] = use(uInstance()); let { childrenState, status } = use( uTransition(() => ({ in: inProp, timeout, enter, unmountOnExit, mountOnEnter, appear, })) ); let currentChildren = use(() => (childrenState ? children : null)); let cls = use(() => ({ appear: `${classNames}-appear`, appearActive: `${classNames}-appear-active`, appearDone: `${classNames}-appear-done`, enter: `${classNames}-enter`, enterActive: `${classNames}-enter-active`, enterDone: `${classNames}-enter-done`, exit: `${classNames}-exit`, exitActive: `${classNames}-exit-active`, exitDone: `${classNames}-exit-done`, })); let el: HTMLElement | undefined; const getElement = () => { const els = findEls(instance, true).filter( (el) => el instanceof HTMLElement ) as HTMLElement[]; return els[0] as HTMLElement | undefined; }; const clearAll = () => { if (!el) return; el.classList.remove( cls.appear, cls.appearActive, cls.appearDone, cls.enter, cls.enterActive, cls.enterDone, cls.exit, cls.exitActive, cls.exitDone ); }; const entered = (appear: boolean) => { if (!el) return; clearAll(); el.classList.add(appear ? cls.appearDone : cls.enterDone); onEntered?.(el); }; const exited = () => { if (el) { clearAll(); el.classList.add(cls.exitDone); } onExited?.(el); }; const entering = (reflow = false, apear: boolean) => { if (!el) return; clearAll(); el.classList.add(apear ? cls.appear : cls.enter); onEnter?.(el); reflow && forceReflow(el); clearAll(); el.classList.add(apear ? cls.appearActive : cls.enterActive); onEntering?.(el); }; const exiting = (reflow = false) => { if (!el) return; clearAll(); el.classList.add(cls.exit); onExit?.(el); reflow && forceReflow(el); clearAll(); el.classList.add(cls.exitActive); onExiting?.(el); }; useLayoutEffect( () => { el = getElement(); }, () => [status, inProp] ); uWatch( (current, previous) => { switch (current) { case APPEARING: entering(previous !== EXITING, true); break; case APPEARED: entered(true); break; case ENTERING: entering(previous !== EXITING, false); break; case EXITING: exiting(previous !== ENTERING); break; case ENTERED: entered(false); break; case EXITED: exited(); break; } }, () => [status] ); return () => currentChildren; }; const forceReflow = (el: HTMLElement) => el.scrollTop; ================================================ FILE: packages/unis-transition/src/TransitionGroup.ts ================================================ import { FiberNode, use, useEffect, useProps, useState } from "@unis/core"; import { CSSTransition } from "./CSSTransition"; export interface TransitionGroupProps { children: JSX.Element | JSX.Element[]; } export const TransitionGroup = (p: TransitionGroupProps) => { let { children } = useProps(p); let [transitionChildren, setTransitionChildren] = useState([]); let flatChildren = use(() => ([] as FiberNode[]) .concat(children as unknown as FiberNode) .filter((child) => child.tag === CSSTransition) ); let childrenKeys = use(() => flatChildren.map((child) => child.props.key).join(",") ); const remove = (key: string) => { setTransitionChildren( transitionChildren.filter((child) => child.props.key !== key) ); }; const handleChildren = () => { const flatMap: Record = {}; flatChildren.forEach((child) => { flatMap[child.props.key] = child; }); const transitionMap: Record = {}; transitionChildren.forEach((child) => { transitionMap[child.props.key] = child; }); const newFlatChildren = flatChildren.map((child) => { const existingNode = transitionMap[child.props.key]; if (existingNode) return existingNode; const newChild = { ...child }; const originProps = newChild.props; const onExited = newChild.props.onExited; newChild.props = { ...originProps, in: true, onExited: (...args: unknown[]) => { onExited?.(...args); remove(originProps.key); }, }; return newChild; }); transitionChildren.forEach((child, index) => { const newChild = { ...child }; if (!flatMap[newChild.props.key]) { newChild.props = { ...newChild.props, in: false, }; newFlatChildren.splice(index, 0, newChild); } }); setTransitionChildren(newFlatChildren); }; useEffect( () => { handleChildren(); }, () => [childrenKeys] ); return () => transitionChildren; }; ================================================ FILE: packages/unis-transition/src/hooks/uInstance.ts ================================================ import { Fiber, use } from "@unis/core"; export const uInstance = () => { let instance = use((WF: Fiber) => WF); return () => [instance]; }; ================================================ FILE: packages/unis-transition/src/hooks/uTransition.ts ================================================ import { use, useEffect, useState } from "@unis/core"; export const UNMOUNTED = "unmounted"; export const APPEARING = "appearing"; export const APPEARED = "appeared"; export const ENTERING = "entering"; export const ENTERED = "entered"; export const EXITING = "exiting"; export const EXITED = "exited"; export type TimeoutObject = { appear?: number; enter?: number; exit?: number; }; export type TransitionTimeout = number | TimeoutObject; export interface uTransitionProps { in?: boolean; enter?: boolean; unmountOnExit?: boolean; mountOnEnter?: boolean; appear?: boolean; timeout?: TransitionTimeout; } export const uTransition = (optsFn: () => uTransitionProps) => { let { in: inProp = false, enter = true, unmountOnExit = true, mountOnEnter = true, timeout = 0, appear = false, } = use(optsFn); let timer: number; let mounted = false; let realMountOnEnter = use(() => (unmountOnExit ? true : mountOnEnter)); let timeoutObject = use(() => { if (typeof timeout === "number") { return { appear: timeout, enter: timeout, exit: timeout, } as TimeoutObject; } else { const enter = timeout.enter ?? 0; return { appear: enter, enter, exit: timeout.exit ?? 0, } as TimeoutObject; } }); let [childrenState, setChildrenState] = useState( !realMountOnEnter ? true : inProp ); let [initialStatus] = useState( childrenState && appear && inProp ? enter ? APPEARING : ENTERED : UNMOUNTED ); useEffect( () => { if (childrenState && initialStatus === APPEARING) { setTimer(timeoutObject.appear!); } }, () => [] ); let [status, setStatus] = useState(initialStatus); const switchChildren = () => { if (status === ENTERING) setStatus(ENTERED); if (status === EXITING) setStatus(EXITED); if (status === APPEARING) setStatus(APPEARED); setChildrenState(inProp ? true : !unmountOnExit); }; function setTimer(statusTimeout: number) { clearTimeout(timer); timer = setTimeout(() => { switchChildren(); }, statusTimeout); } useEffect( () => { if (!mounted) { mounted = true; return; } if (inProp) { if (!enter) return setStatus(ENTERED); setStatus(ENTERING); setChildrenState(true); setTimer(timeoutObject.enter!); } else { setStatus(EXITING); setTimer(timeoutObject.exit!); } }, () => [inProp] ); return () => ({ childrenState, status, }); }; ================================================ FILE: packages/unis-transition/src/hooks/uUpdate.ts ================================================ import { useReducer } from "@unis/core"; export const uUpdate = () => { let [, dispatch] = useReducer((a) => a + 1, 0); const update = () => dispatch(undefined); return () => [update]; }; ================================================ FILE: packages/unis-transition/src/hooks/uWatch.ts ================================================ import { use, useLayoutEffect } from "@unis/core"; export const uWatch = ( handler: (currentValue: T, previousValue: T | undefined) => void, depsFn: () => [T] ) => { let [value] = use(depsFn); let preValue: T | undefined = undefined; useLayoutEffect( () => { handler(value, preValue); preValue = value; }, () => [value] ); }; ================================================ FILE: packages/unis-transition/src/index.ts ================================================ export * from "./CSSTransition"; export * from "./TransitionGroup"; export * from "./hooks/uTransition"; ================================================ FILE: packages/unis-transition/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "build" }, "include": ["src"] } ================================================ FILE: packages/unis-vite-preset/.gitignore ================================================ build/ ================================================ FILE: packages/unis-vite-preset/README.md ================================================ # Unis Vite Preset Unis develop preset for vite. ## Install ```shell npm add -D @unis/vite-preset ``` ## Usage vite.config.js ```javascript import { defineConfig } from "vite"; import { unisPreset } from '@unis/vite-preset' export default defineConfig({ plugins: [ unisPreset() ] }); ``` ================================================ FILE: packages/unis-vite-preset/package.json ================================================ { "name": "@unis/vite-preset", "version": "0.1.0", "description": "Unis vite preset", "main": "build/index.js", "module": "build/index.mjs", "types": "build/index.d.ts", "typings": "build/index.d.ts", "scripts": { "build": "rimraf build && rollup --config && tsc", "build:dev": "cross-env NODE_ENV=development pnpm build" }, "exports": { ".": { "require": "./build/index.js", "import": "./build/index.mjs" } }, "keywords": [ "vite", "preset", "unis" ], "files": [ "build" ], "author": "anuoua", "peerDependencies": { "@unis/core": "workspace:^", "vite": "^4.2.1" }, "license": "MIT", "bugs": { "url": "https://github.com/anuoua/unis/issues" }, "homepage": "https://github.com/anuoua/unis/tree/main/packages/unis-vite-preset", "dependencies": { "@callback-reassign/rollup-plugin": "^0.0.1" }, "devDependencies": { "@rollup/plugin-node-resolve": "^13.0.6", "@unis/core": "workspace:^", "cross-env": "^7.0.3", "esbuild": "^0.13.13", "rimraf": "^3.0.2", "rollup": "^2.72.0", "rollup-plugin-esbuild": "^4.6.0", "typescript": "^4.4.4", "vite": "^4.2.1" } } ================================================ FILE: packages/unis-vite-preset/rollup.config.js ================================================ import { defineConfig } from "rollup"; import { nodeResolve } from "@rollup/plugin-node-resolve"; import esbuild from "rollup-plugin-esbuild"; const configGen = (format) => defineConfig({ input: "src/index.ts", external: [/^@unis/, "@callback-reassign/rollup-plugin"], output: [ { dir: "build", entryFileNames: `index.${format === "esm" ? "mjs" : "js"}`, format, sourcemap: true, }, ], plugins: [ nodeResolve(), esbuild({ sourceMap: true, minify: process.env.NODE_ENV === "development" ? false : true, target: "esnext", }), ], }); const config = [configGen("cjs"), configGen("esm")]; export default config; ================================================ FILE: packages/unis-vite-preset/src/index.ts ================================================ import type { PluginOption } from "vite"; import { reassign } from "@callback-reassign/rollup-plugin"; import { unisFns } from "@unis/core"; export function unisPreset(): PluginOption[] { return [ { name: "unis-preset", enforce: "pre", config(config) { return { esbuild: { jsx: "automatic", jsxImportSource: "@unis/core", ...config.esbuild, }, }; }, }, reassign({ include: ["**/*.(t|j)s?(x)"], targetFns: { "@unis/core": unisFns, }, }) as PluginOption, ]; } ================================================ FILE: packages/unis-vite-preset/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "emitDeclarationOnly": true, "declarationMap": false, "outDir": "build" }, "include": ["src"] } ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - "packages/**" ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "lib": [ "DOM", "ESNext" ], "jsx": "react-jsx", "jsxImportSource": "@unis/core", "module": "ESNext", "moduleResolution": "node", "declaration": true, "emitDeclarationOnly": true, "sourceMap": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "noUnusedLocals": true, "skipLibCheck": false }, }