{"id":47625651,"url":"https://github.com/airxjs/airx","last_synced_at":"2026-04-01T22:46:40.024Z","repository":{"id":173830427,"uuid":"651059497","full_name":"airxjs/airx","owner":"airxjs","description":"☁️ Airx is a simple enough JSX web application framework.","archived":false,"fork":false,"pushed_at":"2026-03-29T15:02:50.000Z","size":543,"stargazers_count":16,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-29T17:39:16.260Z","etag":null,"topics":["airx","framework","jsx","react","reactive","tsx","vue"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/airxjs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-06-08T12:01:20.000Z","updated_at":"2026-03-28T08:21:37.000Z","dependencies_parsed_at":null,"dependency_job_id":"8b7f97d7-a57e-4e7f-b771-b0d2fa18c99f","html_url":"https://github.com/airxjs/airx","commit_stats":null,"previous_names":["airxjs/airx"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/airxjs/airx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/airxjs%2Fairx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/airxjs%2Fairx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/airxjs%2Fairx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/airxjs%2Fairx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/airxjs","download_url":"https://codeload.github.com/airxjs/airx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/airxjs%2Fairx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31292695,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["airx","framework","jsx","react","reactive","tsx","vue"],"created_at":"2026-04-01T22:46:39.321Z","updated_at":"2026-04-01T22:46:39.960Z","avatar_url":"https://github.com/airxjs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Airx ☁️\n\n[![npm](https://img.shields.io/npm/v/airx.svg)](https://www.npmjs.com/package/airx)\n[![build status](https://github.com/airxjs/airx/actions/workflows/check.yml/badge.svg?branch=main)](https://github.com/airxjs/airx/actions/workflows/check.yml)\n[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)\n[![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)\n\n\u003e A lightweight, Signal-driven JSX web application framework\n\n[中文文档](./README_CN.md) • [English Documentation](./README.md)\n\nAirx is a modern frontend framework built on **JSX** and **Signal** primitives, designed to provide a simple, performant, and intuitive solution for building reactive web applications.\n\n## ✨ Features\n\n- 🔄 **Signal-driven reactivity**: Seamlessly integrates with [TC39 Signal proposal](https://github.com/tc39/proposal-signals)\n- 📝 **TypeScript-first**: Developed entirely in TypeScript with excellent type safety\n- ⚡ **Functional components**: Define components using clean JSX functional syntax\n- 🚫 **No hooks complexity**: Simple and straightforward API without React-style hooks\n- 🪶 **Lightweight**: Minimal bundle size with zero dependencies\n- 🔌 **Extensible**: Plugin system for advanced functionality\n- 🌐 **Universal**: Works in both browser and server environments\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\nnpm install airx\n# or\nyarn add airx\n# or\npnpm add airx\n```\n\n### Basic Usage\n\n```tsx\nimport * as airx from 'airx'\n\n// Create reactive state using Signal\nconst count = new Signal.State(0)\nconst doubleCount = new Signal.Computed(() =\u003e count.get() * 2)\n\nfunction Counter() {\n  const localState = new Signal.State(0)\n\n  const increment = () =\u003e {\n    count.set(count.get() + 1)\n    localState.set(localState.get() + 1)\n  }\n\n  // Return a render function\n  return () =\u003e (\n    \u003cdiv\u003e\n      \u003ch1\u003eCounter App\u003c/h1\u003e\n      \u003cp\u003eGlobal count: {count.get()}\u003c/p\u003e\n      \u003cp\u003eDouble count: {doubleCount.get()}\u003c/p\u003e\n      \u003cp\u003eLocal count: {localState.get()}\u003c/p\u003e\n      \u003cbutton onClick={increment}\u003e\n        Click me!\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\n// Create and mount the app\nconst app = airx.createApp(\u003cCounter /\u003e)\napp.mount(document.getElementById('app'))\n```\n\n## 🌐 Server-Side Rendering (SSR)\n\nAirx supports server-side rendering (SSR) out of the box. SSR allows you to render your components to HTML strings on the server, which can improve initial page load performance and SEO.\n\n### Quick Start\n\n```tsx\nimport * as airx from 'airx'\n\n// Create an SSR app\nconst app = airx.createSSRApp(\u003cMyComponent /\u003e)\n\n// Render to HTML string\nconst html = await app.renderToString()\n// html === '\u003cdiv\u003e\u003ch1\u003eHello World\u003c/h1\u003e\u003c/div\u003e'\n```\n\n### Full SSR Example\n\n```tsx\nimport { createSSRApp } from 'airx'\n\n// Define a component\nfunction UserCard({ name, email }: { name: string; email: string }) {\n  return () =\u003e (\n    \u003cdiv className=\"user-card\"\u003e\n      \u003ch2\u003e{name}\u003c/h2\u003e\n      \u003cp\u003e{email}\u003c/p\u003e\n    \u003c/div\u003e\n  )\n}\n\n// Server-side rendering\nasync function renderPage() {\n  const app = createSSRApp(\n    \u003cUserCard name=\"Alice\" email=\"alice@example.com\" /\u003e\n  )\n  \n  const html = await app.renderToString()\n  console.log(html)\n  // \u003cdiv class=\"user-card\"\u003e\u003ch2\u003eAlice\u003c/h2\u003e\u003cp\u003ealice@example.com\u003c/p\u003e\u003c/div\u003e\n  \n  return html\n}\n```\n\n### Hydration (Client-Side Activation)\n\n\u003e ⚠️ **Note**: Hydration support is planned for 0.8.x release. Currently, `hydrate()` is available as a stub for future implementation.\n\n```tsx\n// Future: Activate SSR HTML on the client\nimport { createSSRApp, hydrate } from 'airx'\n\nasync function hydrateApp(ssrHtml: string) {\n  const app = createSSRApp(\u003cApp /\u003e)\n  \n  const container = document.getElementById('app')\n  if (container) {\n    hydrate(ssrHtml, container, app)\n  }\n}\n```\n\n### API Reference\n\n#### `createSSRApp(element)`\n\nCreates an SSR application instance for server-side rendering.\n\n```tsx\nconst app = airx.createSSRApp(\u003cMyComponent /\u003e)\n```\n\n#### `renderToString(app)`\n\nRenders an SSR app to an HTML string (returns a Promise).\n\n```tsx\nconst html = await airx.renderToString(app)\n```\n\n#### `hydrate(html, container, app)`\n\nActivates server-rendered HTML on the client (planned for 0.8.x).\n\n## 📖 Core Concepts\n\n### Components\n\nComponents in Airx are simple functions that return a render function:\n\n```tsx\nfunction MyComponent() {\n  const state = new Signal.State('Hello')\n  \n  return () =\u003e (\n    \u003cdiv\u003e{state.get()} World!\u003c/div\u003e\n  )\n}\n```\n\n### State Management\n\nAirx leverages the Signal primitive for reactive state management:\n\n```tsx\n// State\nconst count = new Signal.State(0)\n\n// Computed values\nconst isEven = new Signal.Computed(() =\u003e count.get() % 2 === 0)\n\n// Effects\nconst effect = new Signal.Effect(() =\u003e {\n  console.log('Count changed:', count.get())\n})\n```\n\n### Context \u0026 Dependency Injection\n\n```tsx\nconst ThemeContext = Symbol('theme')\n\nfunction App() {\n  // Provide values down the component tree\n  airx.provide(ThemeContext, 'dark')\n  \n  return () =\u003e \u003cChild /\u003e\n}\n\nfunction Child() {\n  // Inject values from parent components\n  const theme = airx.inject(ThemeContext)\n  \n  return () =\u003e (\n    \u003cdiv className={`theme-${theme}`}\u003e\n      Current theme: {theme}\n    \u003c/div\u003e\n  )\n}\n```\n\n### Lifecycle Hooks\n\n```tsx\nfunction Component() {\n  airx.onMounted(() =\u003e {\n    console.log('Component mounted')\n    \n    // Return cleanup function\n    return () =\u003e {\n      console.log('Component unmounted')\n    }\n  })\n\n  airx.onUnmounted(() =\u003e {\n    console.log('Component will unmount')\n  })\n  \n  return () =\u003e \u003cdiv\u003eMy Component\u003c/div\u003e\n}\n\n## 📚 API Reference\n\nAirx follows a minimal API design philosophy. Here are the core APIs:\n\n### `createApp(element)`\n\nCreates an application instance.\n\n```tsx\nconst app = airx.createApp(\u003cApp /\u003e)\napp.mount(document.getElementById('root'))\n```\n\n### `provide\u003cT\u003e(key, value): ProvideUpdater\u003cT\u003e`\n\nProvides a value down the component tree through context. Must be called synchronously within a component.\n\n```tsx\nfunction Parent() {\n  airx.provide('theme', 'dark')\n  return () =\u003e \u003cChild /\u003e\n}\n```\n\n### `inject\u003cT\u003e(key): T | undefined`\n\nRetrieves a provided value from the component tree. Must be called synchronously within a component.\n\n```tsx\nfunction Child() {\n  const theme = airx.inject('theme')\n  return () =\u003e \u003cdiv\u003eTheme: {theme}\u003c/div\u003e\n}\n```\n\n### `onMounted(listener): void`\n\nRegisters a callback for when the component is mounted to the DOM.\n\n```tsx\ntype MountedListener = () =\u003e (() =\u003e void) | void\n\nairx.onMounted(() =\u003e {\n  console.log('Mounted!')\n  return () =\u003e console.log('Cleanup')\n})\n```\n\n### `onUnmounted(listener): void`\n\nRegisters a callback for when the component is unmounted from the DOM.\n\n```tsx\ntype UnmountedListener = () =\u003e void\n\nairx.onUnmounted(() =\u003e {\n  console.log('Unmounted!')\n})\n```\n\n### `createElement(type, props, ...children)`\n\nCreates virtual DOM elements (usually handled by JSX transpiler).\n\n### `Fragment`\n\nA component for grouping multiple elements without adding extra DOM nodes.\n\n```tsx\nfunction App() {\n  return () =\u003e (\n    \u003cairx.Fragment\u003e\n      \u003cdiv\u003eFirst\u003c/div\u003e\n      \u003cdiv\u003eSecond\u003c/div\u003e\n    \u003c/airx.Fragment\u003e\n  )\n}\n```\n\n## 🔧 Development\n\n### Building from Source\n\n```bash\n# Clone the repository\ngit clone https://github.com/airxjs/airx.git\ncd airx\n\n# Install dependencies\nnpm install\n\n# Build the project\nnpm run build\n\n# Run tests\nnpm test\n\n# Run tests with UI\nnpm run test:ui\n\n# Run tests with coverage\nnpm run test:coverage\n```\n\n### Project Structure\n\n```text\nsource/\n├── app/           # Application creation and management\n├── element/       # Virtual DOM and JSX handling\n├── logger/        # Internal logging utilities\n├── render/        # Rendering engine\n│   ├── basic/     # Core rendering logic\n│   ├── browser/   # Browser-specific rendering\n│   └── server/    # Server-side rendering\n├── signal/        # Signal integration\n├── symbol/        # Internal symbols\n└── types/         # TypeScript type definitions\n```\n\n## 🤝 Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n### Development Workflow\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Make your changes\n4. Add tests for your changes\n5. Ensure all tests pass (`npm test`)\n6. Commit your changes (`git commit -m 'Add amazing feature'`)\n7. Push to the branch (`git push origin feature/amazing-feature`)\n8. Open a Pull Request\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## 🙏 Acknowledgments\n\n- Thanks to all contributors and supporters of the Airx project\n- Inspired by the [TC39 Signal proposal](https://github.com/tc39/proposal-signals)\n- Built with ❤️ by the Airx community\n\n## 📞 Support\n\n- 📖 [Documentation](https://github.com/airxjs/airx)\n- 🐛 [Issue Tracker](https://github.com/airxjs/airx/issues)\n- 💬 [Discussions](https://github.com/airxjs/airx/discussions)\n\n---\n\nMade with ☁️ by the Airx team\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fairxjs%2Fairx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fairxjs%2Fairx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fairxjs%2Fairx/lists"}