A lightweight, Signal-driven JSX web application framework
Airx 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.
- 🔄 Signal-driven reactivity: Seamlessly integrates with TC39 Signal proposal
- 📝 TypeScript-first: Developed entirely in TypeScript with excellent type safety
- ⚡ Functional components: Define components using clean JSX functional syntax
- 🚫 No hooks complexity: Simple and straightforward API without React-style hooks
- 🪶 Lightweight: Minimal bundle size with zero dependencies
- 🔌 Extensible: Plugin system for advanced functionality
- 🌐 Universal: Works in both browser and server environments
npm install airx
# or
yarn add airx
# or
pnpm add airx
import * as airx from 'airx'
// Create reactive state using Signal
const count = new Signal.State(0)
const doubleCount = new Signal.Computed(() => count.get() * 2)
function Counter() {
const localState = new Signal.State(0)
const increment = () => {
count.set(count.get() + 1)
localState.set(localState.get() + 1)
}
// Return a render function
return () => (
<div>
<h1>Counter App</h1>
<p>Global count: {count.get()}</p>
<p>Double count: {doubleCount.get()}</p>
<p>Local count: {localState.get()}</p>
<button onClick={increment}>
Click me!
</button>
</div>
)
}
// Create and mount the app
const app = airx.createApp(<Counter />)
app.mount(document.getElementById('app'))
Components in Airx are simple functions that return a render function:
function MyComponent() {
const state = new Signal.State('Hello')
return () => (
<div>{state.get()} World!</div>
)
}
Airx leverages the Signal primitive for reactive state management:
// State
const count = new Signal.State(0)
// Computed values
const isEven = new Signal.Computed(() => count.get() % 2 === 0)
// Effects
const effect = new Signal.Effect(() => {
console.log('Count changed:', count.get())
})
const ThemeContext = Symbol('theme')
function App() {
// Provide values down the component tree
airx.provide(ThemeContext, 'dark')
return () => <Child />
}
function Child() {
// Inject values from parent components
const theme = airx.inject(ThemeContext)
return () => (
<div className={`theme-${theme}`}>
Current theme: {theme}
</div>
)
}
function Component() {
airx.onMounted(() => {
console.log('Component mounted')
// Return cleanup function
return () => {
console.log('Component unmounted')
}
})
airx.onUnmounted(() => {
console.log('Component will unmount')
})
return () => <div>My Component</div>
}
## 📚 API Reference
Airx follows a minimal API design philosophy. Here are the core APIs:
### `createApp(element)`
Creates an application instance.
```tsx
const app = airx.createApp(<App />)
app.mount(document.getElementById('root'))
Provides a value down the component tree through context. Must be called synchronously within a component.
function Parent() {
airx.provide('theme', 'dark')
return () => <Child />
}
Retrieves a provided value from the component tree. Must be called synchronously within a component.
function Child() {
const theme = airx.inject('theme')
return () => <div>Theme: {theme}</div>
}
Registers a callback for when the component is mounted to the DOM.
type MountedListener = () => (() => void) | void
airx.onMounted(() => {
console.log('Mounted!')
return () => console.log('Cleanup')
})
Registers a callback for when the component is unmounted from the DOM.
type UnmountedListener = () => void
airx.onUnmounted(() => {
console.log('Unmounted!')
})
Creates virtual DOM elements (usually handled by JSX transpiler).
A component for grouping multiple elements without adding extra DOM nodes.
function App() {
return () => (
<airx.Fragment>
<div>First</div>
<div>Second</div>
</airx.Fragment>
)
}
# Clone the repository
git clone https://github.com/airxjs/airx.git
cd airx
# Install dependencies
npm install
# Build the project
npm run build
# Run tests
npm test
# Run tests with UI
npm run test:ui
# Run tests with coverage
npm run test:coverage
source/
├── app/ # Application creation and management
├── element/ # Virtual DOM and JSX handling
├── logger/ # Internal logging utilities
├── render/ # Rendering engine
│ ├── basic/ # Core rendering logic
│ ├── browser/ # Browser-specific rendering
│ └── server/ # Server-side rendering
├── signal/ # Signal integration
├── symbol/ # Internal symbols
└── types/ # TypeScript type definitions
We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Make your changes
- Add tests for your changes
- Ensure all tests pass (
npm test
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Thanks to all contributors and supporters of the Airx project
- Inspired by the TC39 Signal proposal
- Built with ❤️ by the Airx community
Made with ☁️ by the Airx team