Skip to content

airxjs/airx

Repository files navigation

Airx ☁️

npm build status TypeScript MIT License

A lightweight, Signal-driven JSX web application framework

中文文档English Documentation

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.

✨ Features

  • 🔄 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

🚀 Quick Start

Installation

npm install airx
# or
yarn add airx
# or
pnpm add airx

Basic Usage

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'))

📖 Core Concepts

Components

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>
  )
}

State Management

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())
})

Context & Dependency Injection

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>
  )
}

Lifecycle Hooks

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'))

provide<T>(key, value): ProvideUpdater<T>

Provides a value down the component tree through context. Must be called synchronously within a component.

function Parent() {
  airx.provide('theme', 'dark')
  return () => <Child />
}

inject<T>(key): T | undefined

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>
}

onMounted(listener): void

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')
})

onUnmounted(listener): void

Registers a callback for when the component is unmounted from the DOM.

type UnmountedListener = () => void

airx.onUnmounted(() => {
  console.log('Unmounted!')
})

createElement(type, props, ...children)

Creates virtual DOM elements (usually handled by JSX transpiler).

Fragment

A component for grouping multiple elements without adding extra DOM nodes.

function App() {
  return () => (
    <airx.Fragment>
      <div>First</div>
      <div>Second</div>
    </airx.Fragment>
  )
}

🔧 Development

Building from Source

# 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

Project Structure

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

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Workflow

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add tests for your changes
  5. Ensure all tests pass (npm test)
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Thanks to all contributors and supporters of the Airx project
  • Inspired by the TC39 Signal proposal
  • Built with ❤️ by the Airx community

📞 Support


Made with ☁️ by the Airx team

About

☁️ Airx is a simple enough JSX web application framework.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published