{"id":21045375,"url":"https://github.com/ymzuiku/aoife","last_synced_at":"2025-05-15T17:33:33.934Z","repository":{"id":45715732,"uuid":"323234271","full_name":"ymzuiku/aoife","owner":"ymzuiku","description":"Developing native JavaScript programs using JSX, where each component is a raw HTMLElement, allows for seamless compatibility with all native JavaScript libraries.","archived":false,"fork":false,"pushed_at":"2023-12-14T10:18:29.000Z","size":6362,"stargazers_count":63,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-06T15:17:52.211Z","etag":null,"topics":["js","jsx","react","vue"],"latest_commit_sha":null,"homepage":"https://aoife-one.vercel.app/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ymzuiku.png","metadata":{"files":{"readme":"README-zh.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-12-21T04:55:35.000Z","updated_at":"2025-04-24T06:05:54.000Z","dependencies_parsed_at":"2023-12-14T12:12:52.068Z","dependency_job_id":null,"html_url":"https://github.com/ymzuiku/aoife","commit_stats":{"total_commits":149,"total_committers":2,"mean_commits":74.5,"dds":0.3355704697986577,"last_synced_commit":"ff1fbc80e7d6bb1fa540c40aea3de968033d3160"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ymzuiku%2Faoife","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ymzuiku%2Faoife/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ymzuiku%2Faoife/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ymzuiku%2Faoife/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ymzuiku","download_url":"https://codeload.github.com/ymzuiku/aoife/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254388443,"owners_count":22063056,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["js","jsx","react","vue"],"created_at":"2024-11-19T14:21:52.273Z","updated_at":"2025-05-15T17:33:33.338Z","avatar_url":"https://github.com/ymzuiku.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"./document/aoife.svg\" style=\"width:50%; margin:60px 0px 40px 25%;\" /\u003e\n\n# Aoife 简介\n\n## [完整文档](https://aoife-one.vercel.app/)\n\n使用 jsx 开发 native-js 程序, 每个组件都是一个原始的 HTMLElment，可以和所有原生 js 库很好的兼容使用。\n\n\u003e aoife 非常小, gzip 5kb\n\n社区已经有了 React/Vue/Ag 为什么还需要 Aoife？\n\n## 特性\n\n使用 jsx 开发 native-js 程序, 每个组件都是一个原始的 HTMLElment，可以和所有原生 js 库很好的兼容使用。\n\n- 核心 API 只有一个: aoife.next\n- 极简的组件声明\n- 每次更新只会更新一次，不会有重复渲染\n- 拥抱原生 JS 生态，可以和原生 JS 库很好的兼容使用\n\n## 安装 / 启动\n\n安装\n\n```bash\n$ npm init aoife-app \u003cproject-name\u003e\n$ cd \u003cproject-name\u003e\n$ yarn install\n```\n\n启动：\n\n```bash\n$ yarn dev # 开发环境\n$ yarn build # 编译\n```\n\n## API\n\naoife 是一个全局函数, 用于 jsx 解析，其中 aoife.next 用于更新元素\n\n```typescript\ndeclare const aoife: {\n  \u003cK extends keyof HTMLElementTagNameMap\u003e(\n    tag: K,\n    attrs?: PartialDetail\u003cHTMLElementTagNameMap[K]\u003e,\n    ...child: any[]\n  ): HTMLElementTagNameMap[K];\n  next: (\n    focusUpdateTargets?: string | HTMLElement | undefined,\n    ignoreUpdateTargets?: string | HTMLElement | HTMLElement[]\n  ) =\u003e void;\n  attributeKeys: {\n    [key: string]: boolean;\n  };\n  useMiddleware: (fn: (ele: HTMLElement, props: IProps) =\u003e any) =\u003e void;\n};\n```\n\n## 很短且完整的教程\n\n如果你会 React，学习 aoife 只需要 5 分钟，`注意 aoife 并不是 React 的轮子`。\n\naoife 仅仅保留了 JSX 相关的概念，移除了 React 所有非 JSX 相关的概念，所以 aoife 没有生命周期，hooks、diffDOM。\n\n但是 aoife 可以完成所有 React 能完成的项目，为了弥补缺少 React 相关的概念，看看我们是怎么做的：\n\n前端开发可以抽象为两部分：页面绘制、页面更新；在 aoife 中，页面绘制就是使用 jsx 语法组织原始的 HTMLElement；然后使用 **函数赋值** 来解决元素更新。\n\n**函数赋值**: 即在声明元素的过程中，给属性绑定一个函数，jsx 解析过程中，若发现属性是一个函数，记录一个发布订阅任务，然后则执行函数，并且赋值；在未来需要更新此属性时，使用 `aoife.next` 函数对文档进行选择，命中的**元素及其子元素**会执行之前订阅的任务，更新属性。\n\n我们看一个例子\n\n```tsx\nimport \"aoife\"; // 在项目入口处引入一次，注册全局 dom 对象\n\n// 这是一个普通的 jsx 组件\nfunction App() {\n  return (\n    \u003cdiv class=\"app\"\u003e\n      \u003ch1\u003eHello World\u003c/h1\u003e\n      \u003cStatefulExample name=\"Add Num\" /\u003e\n    \u003c/div\u003e\n  );\n}\n\n// 这是一个用于演示 函数赋值/更新 的组件\nfunction StatefulExample({ name }: { name: string }) {\n  console.log(\n    \"这个日志仅会打印一次，因为 aoife.next 更新仅仅会派发元素的子属性，不会重绘整个组件\"\n  );\n  let num = 0;\n  return (\n    \u003cdiv\u003e\n      \u003cbutton\n        onclick={() =\u003e {\n          num += 1;\n          aoife.next(\".add\");\n        }}\n      \u003e\n        {name}\n      \u003c/button\u003e\n      \u003cdiv\n        class=\"add\"\n        style={() =\u003e ({\n          fontSize: 20 + num + \"px\",\n        })}\n      \u003e\n        \u003cp\u003e{() =\u003e num}\u003c/p\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n}\n\ndocument.body.append(\u003cApp /\u003e);\n```\n\n## 异步 JSX\n\naoife 可以异步取值和异步插入 children，这可以简化远程获取数据渲染的业务。 注意，aoife.next 仅仅是一个派发更新，并不会等待所有异步更新的回调\n\n```jsx\nimport \"aoife\";\n\nfunction App() {\n  return (\n    \u003cdiv\u003e\n      \u003cinput\n        placeholder=\"Input\"\n        value={() =\u003e {\n          // 异步取值\n          return new Promise((res) =\u003e {\n            setTimeout(() =\u003e res(\"hello\"), 500);\n          });\n        }}\n      /\u003e\n      {() =\u003e {\n        // 异步插入元素\n        return new Promise((res) =\u003e {\n          setTimeout(() =\u003e {\n            res(\u003cdiv\u003elist-a\u003c/div\u003e);\n          }, 1000);\n        });\n      }}\n      {() =\u003e {\n        // 异步插入元素\n        return new Promise((res) =\u003e {\n          setTimeout(() =\u003e {\n            res(\u003cdiv\u003elist-b\u003c/div\u003e);\n          }, 300);\n        });\n      }}\n    \u003c/div\u003e\n  );\n}\n```\n\n## 设计细节\n\n1. 为了延续声明式的开发方式，`aoife.next` 函数并没有传递值，仅仅是派发了更新命令，元素的属性还是由内部状态管理的逻辑来解决状态分支问题\n2. 我们移除了类似 React 中 SCU，purecomponent、memo 等解决重绘问题的概念，因为**一次** aoife.next 执行仅仅更新**一次**局部元素的**属性**，并不会造成大规模重绘\n3. `aoife.next` 已经是全局可选则的更新，所以失去了传统的状态管理库的必要；合理规范好 `aoife.next` 的调用即可。\n\n### 编写 css\n\n```jsx\nconst css = (\n  \u003cstyle\u003e{`\n.hello {\n  background: #f00;\n}\n`}\u003c/style\u003e\n);\n\ndocument.body.append(css);\n```\n\n## 生态\n\naoife 的核心设计理念就是用原生 JS 解决生态问题，任何一个函数，其返回值是一个 HTMLElement，就可以在 aoife 中作为标签进行使用。\n\n### 原生 JS 和 aoife 混用的例子\n\nvanilla-pop 组件是一个由 tippy.js 封装的函数，内部并无引入 aoife， 使用方法：\n\n```jsx\n// npm i --save vanilla-app\nimport Pop from \"vanilla-pop\";\n\nconst App = () =\u003e {\n  return (\n    \u003cPop placement=\"top\"\u003e\n      \u003cdiv\u003elabel\u003c/div\u003e\n      \u003cdiv\u003epop tip\u003c/div\u003e\n    \u003c/Pop\u003e\n  );\n};\n```\n\n从这个案例可以看到，一个原生 JS 组件，本身可以不需要包含 aoife，也可以被 aoife 使用；只需要此组件满足 3 个规则：\n\n- 1. 组件是一个函数，返回值是一个 HTMLElement 类型\n- 1. 组件的参数是一个对象\n- 1. 若 JSX 传递了 children，在组件第一个参数中会包含 children 字段，值是一个 HTMLElement 数组\n\n## 完整文档\n\n[aoife.writeflowy.com](https://aoife.writeflowy.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fymzuiku%2Faoife","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fymzuiku%2Faoife","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fymzuiku%2Faoife/lists"}