Skip to content

Latest commit

 

History

2,592 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Avenx.js

A frontend framework that can explain itself.

Single-file components, compiled templates, and a compiler that keeps a model of your whole application —
so you can ask it what a change will break before you make it.

npm docs runtime deps node license contributors

Documentation  ·  Quick start  ·  Why Avenx  ·  CLI  ·  Contributing


🧭 What Avenx is

Avenx-JS is a compiler-first frontend framework. You write a component as one file — declarations and markup together — and the compiler turns it into a render program: a static HTML skeleton plus one binding per dynamic part. There is no virtual DOM, and there is no expression interpreter in your production bundle, because every expression was compiled to a JavaScript function at build time.

Tip

What is unusual is what the compiler does with what it learns. It keeps a semantic model of the whole application — every state key, computed value, action, template binding, route and guard, and the relationships between them — and exposes it as a tool you can query from the command line.

The whole build, end to end:

flowchart LR

subgraph P["① project pass"]
  direction TB
  P1["<b>bridges</b><br/>.bridge.js → descriptors"]
  P2["<b>names</b><br/>every component + page"]
  P3["<b>route params</b><br/>per routed page"]
  P4["<b>guards</b><br/>.guard.js"]
  P1 --> P2 --> P3 --> P4
end

subgraph U["② per component / page"]
  direction TB
  U1["<b>declarations</b><br/>state · computed<br/>action · resource"]
  U2["<b>stylesheet</b><br/>@def vars · named<br/>blocks → hashed classes"]
  U3["<b>template front-end</b><br/>two-way bindings<br/>@css · component tags"]
  SEM(["semantic template"])
  U4{"IR builds<br/>and lowers?"}
  U5["<b>render program</b><br/>skeleton + one op<br/>per dynamic part"]
  U6["<b>string renderer</b><br/>AVX_W47"]
  U7["<b>codegen</b><br/>expressions · actions<br/>→ closures"]
  U8["<b>component class</b><br/>virtual ES module"]
  U1 --> U3
  U2 --> U3
  U3 --> SEM --> U4
  U4 -- yes --> U5 --> U7
  U4 -- no --> U6 --> U7
  U7 --> U8
end

subgraph B["③ bundler"]
  direction TB
  B1["resolve"] --> B2["link graph"] --> B3["tree-shake"] --> B4["emit + minify"]
end

subgraph F["④ finish"]
  direction TB
  F1["<b>Atlas model</b><br/>render edges<br/>routes · guards"]
  F2["<b>diagnostics</b><br/>W40 W41 W42<br/>W43 W44 W47 W48"]
  F3["<b>verify</b><br/>parses · capabilities"]
  F1 --> F2 --> F3
end

SRC["<b>src/</b>"] --> P --> U --> B --> F --> OUT["<b>dist/</b><br/>bundle.js<br/>bundle.css<br/>bundle.atlas.json<br/>bundle.trace.json"]
Loading

Two things in there are worth pointing at. The semantic template is the fork: the render program is compiled from the template as you wrote it, before any directive has been rewritten into markup, because <@for item in items> still says what it means at that stage and afterwards it does not. And nothing is written until everything is produced — the bundle is checked for parseability and for the runtime capabilities the build decided to link, staged, and only then promoted, so a failure never leaves a new script beside a stale stylesheet.

🧑‍💻 You write a component… ⚙️ …the compiler emits this
<state name="World" />

<action name="shout">
  name = name.toUpperCase();
</action>

<div @css card>
  <h1>Hello, {{ name }}!</h1>
  <button @click="shout()">Shout</button>
</div>
// skeleton — parsed once per class
html: '<div class="avenx-a050b589">
         <h1>Hello, <!--axt:0-->!</h1>
         <button data-axb="0">Shout</button>
       </div>'

// one op per dynamic part
ops: [ { k: 'text',  t: 0, x: 0 },
       { k: 'event', e: 0, n: 'click' } ]

// compiled at build time
exprs: [ ($s) => axGet($s, 'name') ]
stmts: [ ($s) => axCall(…, 'shout') ]

No imports, no setState, no hooks, no build configuration — and nothing left to interpret at run time. That right-hand column is real output, lightly wrapped to fit: {{ name }} became a comment marker plus one text op, the handler became a data-axb hook plus one event op, and both expressions became ordinary JavaScript functions. The matching .component.css holds styles scoped to this component and nothing else.

Important

Avenx is pre-1.0 and under active development. The latest published release is 0.4.3; a production-readiness cycle is in progress ahead of a release candidate. See Project status for what that means in practice.


✨ Why Avenx is different

Most of what a framework offers is table stakes: components, reactivity, routing, scoped styles. Avenx has those. These four are the reasons to look at it instead of something else.


🔭

Atlas

Ask what a change will break —
and see where the analysis stopped.



🔍

Trace

Record why a bug happened,
then replay it as a test.



🔄

Rewind

Actions that undo their writes —
and warn what they cannot.



🔒

Compiled

No eval, no interpreter.
Runs under script-src 'self'.



🔭 Atlas — ask the compiler what a change will break

avenx impact and avenx why answer data-flow questions about your own code. This is real output from a small store application — a bridge holding items, a getter that sums them, and a page that reads it:

$ npx avenx impact cart.items

What depends on: cart.items
   state  src/global/cart.bridge.js:5

├─ reads cart.count .reduce  src/global/cart.bridge.js:9
│  └─ reads Home {{ }} "cart.count"  src/pages/home.page.js:6
└─ writes cart.add  src/global/cart.bridge.js:17
   └─ invokes Home @click="cart.add(product.id)"  src/pages/home.page.js:12
      └─ declares Home  src/pages/home.page.js
         ├─ routes-to /  src/main.app.js:9
         └─ routes-to *  src/main.app.js:11

7 related nodes

1 unresolved relationship — this answer may be incomplete:
  ? spread  ...  src/global/cart.bridge.js:17

Note

Note how that output ends. This is a data-flow map, not a module graph, and it tells you where its own analysis stopped instead of quietly presenting a partial answer as a complete one. Every answer prints that count, including when it is zero.

The same model produces build-time diagnostics — a state key nothing reads, an action nothing invokes — and both refuse to fire when the analysis behind them was incomplete. Atlas runs at compile time only: avenx build writes dist/bundle.atlas.json beside the bundle and the runtime never reads it.

→ Atlas guide

↑ back to features


🔍 Trace — record why a bug happened, then replay it

Serve with --trace, reproduce the behaviour, and read back the causal chain from the event to the DOM write. Real output from the same application:

$ npx avenx trace view latest

Trace trace-3b96  ·  6 nodes  ·  recorded 2026-09-21T19:32:48.861Z
  http://localhost:4700/

▸ click <button> Home
  └─ bridge cart · add("a")
     ├─ write items [0 items] → [1 item]
     │  └─ woke text cart.count
     │     └─ patched <h1> text "0" → "1"
     └─ emit cart:added → 0 listeners

Determinism: deterministic — this trace can be exported as a regression test.

avenx trace export latest --out test/cart-add.test.js turns that recording into a test. The generated test calls replay(), which drives the recorded inputs back through the real framework and compares every state and DOM change against the recording, so a regression reports its cause rather than a bare mismatch. The convenience assertions it writes alongside are a starting point the file tells you to prune.

Recording is opt-in, development-only, and absent from production bundles — with tracing off, each instrumented site is one boolean check.

→ Trace guide

↑ back to features


🔄 Rewind — optimistic updates that undo themselves

Mark an action atomic and every state write it makes is journaled. If it throws, or returns a promise that rejects, the journal is played backwards: component state, bridge state, nested properties, array and Map/Set mutations, and keys the action created or deleted all come back. Because the restore goes through the same reactive machinery as an ordinary write, the DOM corrects itself.

<action name="incQty" atomic>
  busy = true;
  cart.addQty(props.id, 1);
  return api.setQty(props.id, qty);
</action>

A library can do that much. What a library cannot do is tell you at build time which of an action's effects a rewind will not undo:

$ npx avenx build

[AVX_W43] session.save is atomic, but 2 effect(s) cannot be rewound:
  storage localStorage.setItem(  src/global/session.bridge.js:8
  emit emit('saved'  src/global/session.bridge.js:9
Everything it writes through Avenx state will be restored; the effects above
will not. Move them after the transaction, or accept that a rewind leaves them
in place.

When the analysis cannot resolve an action's write set it says so (AVX_W42) and marks its own effect list as incomplete, rather than reporting a shorter list as if it were the whole one.

→ Rewind guide

↑ back to features


🔒 Compiled expressions — no eval, no new Function

Every interpolation, computed value, directive binding, inline handler and <action> body is compiled to an ordinary JavaScript function at build time and linked into the bundle:

count * 2                ->  ($s) => (axGet($s, "count") * 2)
if (!text) { return; }   ->  ($s) => { if (!axGet($s, "text")) { return; } … }

A production bundle therefore contains no eval, no new Function and no with, and runs under script-src 'self' with no unsafe-eval and no unsafe-inline. The build's own test suite asserts this against the emitted bundle. A development build still carries the interpreter so a half-finished template keeps rendering; AVX_W48 names anything it could not compile.

→ Template expressions · Deployment


🚀 Quick start

Note

Requires Node.js 18 or newer. There is no avenx package on npm — the package is avenx-core, and it provides the avenx binary, so install it before using npx avenx.

mkdir my-app && cd my-app
npm install avenx-core

npx avenx init        # scaffold
npx avenx g counter   # a working component
npx avenx serve       # http://localhost:3000

init scaffolds the project. g counter generates a component and registers it in src/main.app.js. serve builds and starts a dev server with live reload — add --open to launch a browser.

You have a running application before writing a line of code.

my-app/
├── src/
│   ├── components/counter/
│   │   ├── counter.component.js  # template
│   │   └── counter.component.css # scoped styles
│   ├── pages/        # routed pages
│   ├── global/       # bridges, shared styles
│   ├── guards/       # route guards
│   └── main.app.js   # entry, registration, routes
├── .vscode/          # editor support
├── avenx.config.json
├── index.html
├── package.json
└── dist/             # build output (generated)

Write a component

Open src/components/counter/counter.component.js:

<state count="0" label="Clicks" />

<computed name="doubled" value="count * 2" />

<action name="increment"> count++; </action>
<action name="reset"> count = 0; </action>

<div @css card>
  <h1 @css title>{{ label }}: {{ count }}</h1>
  <p>Doubled: {{ doubled }}</p>

  <@if (count > 0)>
    <button @css btn @click="reset()">Reset</button>
  <@else>
    <p @css hint>Press the button to start.</p>
  </@if>

  <button @css btn @click="increment()">+1</button>
</div>

And counter.component.css. A <@css> block contains named blocks, not selectors: card { … } declares a block that the template attaches with @css card. Nothing is matched by class name, so an element with no @css directive is unstyled.

<@global>
    @def brand #6366f1;
</@global>

<@css>
    card {
        font-family: system-ui, sans-serif;
        max-width: 20rem;
        margin: 3rem auto;
        padding: 2rem;
        border-radius: 12px;
        background: #f9fafb;
        text-align: center;
    }

    title { color: #1e1b4b; }
    hint  { color: #9ca3af; font-size: 0.85rem; }

    btn {
        background: @brand;
        color: white;
        border: none;
        border-radius: 6px;
        padding: 0.5rem 1rem;
        margin: 0.25rem;
        cursor: pointer;
    }
</@css>

Save the file and the browser updates. The counter increments, the computed value follows it, the <@if> arm switches when the count leaves zero, and the styles reach this component and nothing else.

Ship it

npx avenx check    # validate every template binding; exits 1 on any warning
npx avenx build    # production build into dist/

→ Full quick start · Project structure


📖 A tour of the syntax

Component anatomy

A component is two files in one directory: <name>.component.js (declarations and markup) and <name>.component.css (scoped styles). Pages are the same, as <name>.page.js / <name>.page.css, and live in src/pages/.

Declaration Purpose
<state count="0" title="Home" /> Reactive local state. Values are coerced: numbers, booleans, JSON and JavaScript literals.
<computed name="x" value="a * 2" /> A derived value, re-evaluated when what it reads changes.
<action name="go"> … </action> A method. The body is ordinary JavaScript. Add atomic to make its writes undoable.
<resource name="users"> … </resource> A declarative async read, usable with <@suspense>.

State is reachable bare (count) or through its root (state.count); both resolve to the same value.

Templates

Syntax What it does
{{ expr }} Interpolation, HTML-escaped.
{{{ expr }}} Raw HTML. Never give it untrusted input.
<@if cond> / <@elseif> / <@else> / </@if> Conditional blocks. Bracket a top-level comparison: <@if (n > 3)>.
<@for item in list key="item.id"> / <@empty> Keyed lists over arrays, objects, Map, Set or a number. index is always bound.
<Child prop="{{ value }}" /> A child component. Also data-props-name="expr".
<slot> / <slot name="x"> Transclusion, with fallback content.
@click="handler()" Event binding, with .prevent, .stop, .self, .once, .passive and key modifiers.
data-ax-bind="state.x" Two-way binding for inputs, textareas, selects, checkboxes and radios.
data-ax-show / data-ax-class / data-ax-style / data-ax-html Reactive visibility, classes, inline styles and HTML.
:[nameExpr]="valueExpr" An attribute whose name is computed. Both slots are expressions.
<@defer> Render a block on idle, on a timer, on interaction or when visible.

Shared state with Bridges

A Bridge is a module holding state several components need, plus the actions that change it. You create one with bridge() and use it by importing it — there is no provider, no hook and no subscription to clean up.

// src/global/cart.bridge.js
import { bridge } from 'avenx-core/runtime';

export default bridge({
  state: {
    items: [],
  },

  get count() {
    return this.items.reduce((n, item) => n + item.qty, 0);
  },

  add(id) {
    const existing = this.items.find((item) => item.id === id);
    this.items = existing
      ? this.items.map((item) => (item.id === id ? { ...item, qty: item.qty + 1 } : item))
      : [...this.items, { id, qty: 1 }];
    this.emit('added', id);
  },
});
<!-- src/pages/home.page.js -->
import cart from '../global/cart.bridge.js';

<div>
  <h1>Cart: {{ cart.count }}</h1>
  <button @click="cart.add('keyboard')">Add</button>
</div>

Because the import is the connection, the compiler sees every consumer: unused bridges are left out of the bundle, and a mistyped member or an unemitted event name is reported at build time. State is read-only outside the bridge, so every mutation has one traceable origin.

→ Bridges

Pages, routing and guards

Pages in src/pages/ are registered by the compiler. You map them to routes in src/main.app.js:

import { AvenxApp } from 'avenx-core/runtime';
import UserCard from './components/user-card/user-card.component.js';

const app = new AvenxApp({ target: '#app' });

app.register('UserCard', UserCard);

app.initRouter({
  '/': 'Home',
  '/profile/:userId': 'Profile',
  '*': 'Home',
});

Route parameters land in the page's state, so /profile/42 makes {{ userId }} render 42. Query strings are parsed into state.query with type coercion, * is a catch-all, and a route can carry keepAlive to cache the page instance or guards to gate navigation.

→ Routing · Routing tutorial


📦 What's in the box

Everything listed here is implemented and usable today. What differs is how a construct renders, which is worth knowing because it has a cost.

Templates compile to a render program: a skeleton parsed once per component class, with one reactive effect per binding, so an update touches only the bindings whose dependencies changed. A template using a construct the compiler cannot yet model renders through the older string renderer instead — it renders correctly, but re-renders and diffs the whole template on every update. The build says which and why (AVX_W47), and there is no partial mode: a template is compiled entirely or not at all.


⚡ Compiled — an update touches only the bindings whose dependencies changed.

Components, pages, props slots, named + fallback
Nested components, any depth bounded at 50 levels, AVX_R36
State, computed values, actions watchers, lifecycle hooks
<@if> / <@elseif> / <@else> keyed <@for> / <@empty>
Events and modifiers two-way binding, boolean attributes
data-ax-show / -class / -style / -html <@defer>, <resource> declarations

🧱 No rendering cost of their own — these are not template constructs.

Scoped CSS, <@global> @def variables Sass / SCSS / Less / PostCSS
Bridges, provide & inject atomic actions (Rewind)
Router: params, query, wildcards guards, keep-alive, history, prefixes
Atlas — atlas / impact / why Trace, diagnostics catalogue (compile-time)

🐢 Available, but through the string renderer — correct, and re-rendered whole on every update.

<@suspense> and <@errorBoundary> transitions, template refs
Virtualised lists, form validation dynamic component tags
Dynamic attribute names, router view <@deadlock> boundary ⚠️ (see below)

Warning

<@deadlock> is the one qualified entry. Scheduler and watcher guards do detect runaway reactive work and log AVX_R18, and you can trip a boundary manually with $tripDeadlockBoundary(name, error) — but detection does not automatically trip the nearest boundary, and the maxDepth, action and isolated attributes are currently metadata rather than active controls. The deadlock guide states the current behaviour and its limits.

Size and dependencies

The published package has one dependency, acorn, used only by the compiler; it does not appear in an application bundle. The runtime you ship has no third-party dependencies.

A freshly scaffolded application, production build, measured with avenx build:

Asset Raw Gzipped
bundle.js 341 KB 78 KB
bundle.css 0.7 KB 0.3 KB

That number is honest rather than flattering, and worth understanding: Avenx's minifier strips comments and indentation and nothing else. It does not rename identifiers or remove dead branches, because doing that safely needs a full ECMAScript parser and a minifier that guesses produces a bundle that is smaller and wrong. Line numbers therefore survive into production stack traces. Unused modules are tree-shaken out of the graph — a scaffolded build reports 65 modules · 13 shaken out — and comments and indentation are what gzip compresses best, so the gap on the wire is much narrower than on disk.


🔧 CLI reference

Project

Command Description
avenx init Scaffold a new project.
avenx generate component <name> Generate a component (alias g; avenx g <name> also works).
avenx generate page <name> Generate a routed page (alias g p).
avenx generate bridge <name> Generate a shared reactive bridge.
avenx generate guard <name> Generate a route guard.
avenx destroy <type> <name> Delete a component, page, bridge or guard and its registrations (alias d).

Build and serve

Command Description
avenx serve [port] Dev server with live reload (default 3000).
avenx watch Rebuild on file change (alias w).
avenx build Production build into dist/ (alias b).
avenx clean Clear the build output directory.
avenx check Validate templates without building (alias lint). Exits 1 on any warning.
avenx format Format component, page, CSS, JavaScript, Markdown and JSON files in place with Prettier.

Understand and diagnose

Command Description
avenx atlas The compiler's semantic map of the application.
avenx impact <symbol> What can be affected if this changes.
avenx why <symbol> Where this value comes from.
avenx inspect Route and component hierarchy (alias i).
avenx stats Component and bundle footprint metrics (alias s).
avenx doctor Diagnose environment, config and project health.
avenx env Print and validate active environment variables.
avenx explain <CODE> Explain a diagnostic code, e.g. avenx explain AVX_W53.
avenx trace list / view <id|latest> / export <id|latest> / prune Record, read and export causal traces.

Options

⚙️ All flags, and the commands they apply to
Option Applies to Description
--dev / --prod build Development build (readable runtime, inline CSS source maps) or production (the default).
--dry-run, -d generate, destroy Preview without writing or deleting anything.
--force, -f generate Overwrite existing files.
--with-test / --no-test generate component Generate or skip a colocated unit test.
--template, -t <name> generate Use a custom scaffold template.
--json, -j check, atlas, impact, why Machine-readable output.
--depth=<n> impact, why How many hops to follow (default 12).
--watch, -w check Continuous template linting.
--port, -p <port> serve Port to listen on. Also honours PORT.
--host, -h <host> serve Host to bind (default localhost).
--open, -o serve Open a browser on start.
--no-live-reload serve Disable live reload.
--trace serve Record a causal trace. Development only, off by default.
--out, -o <file> trace export Where the generated regression test is written.
--keep=<n>, --all trace prune How much to remove.
--no-color all Disable colour (NO_COLOR is honoured too).
--version, -v — Print the version.
--layout <name> init Project layout template (blank, routing).
--style <preprocessor> init CSS preprocessor (none, sass, less, postcss).
--yes, -y init Skip interactive prompts and use default values.
--dev / --prod build Development build (readable runtime, inline CSS source maps) or production (the default).
--dry-run, -d generate, destroy Preview without writing or deleting anything.

→ CLI reference

Configuration

avenx.config.json sits at the project root. Every key is optional:

{
  "srcDir": "src",
  "distDir": "dist",
  "style": { "preprocessor": "scss" },
  "server": { "port": 3000, "host": "localhost", "liveReload": true },
  "warnings": { "AVX_W47": "off" },
  "trace": { "redact": ["auth.token", "user.*", "*.password"] },
  "rewind": { "onConflict": "safe" },
  "alias": {},
  "incremental": false
}

→ Configuration


🧩 Tooling and ecosystem

Testing. avenx-core/testing exports mountTestComponent, fireEvent, replay, snapshot helpers and the trace recorder. It is a separate entry point, so nothing in it can reach an application bundle.

Editor support. Avenx ships TypeScript declarations for the runtime, so main.app.js, bridges and guards get full type checking, completion and go-to-definition with no build step. Component and page files are markup rather than JavaScript, so they get tag and attribute completion and hover documentation instead; avenx init configures this. What checks the expressions inside them is the compiler — avenx check and avenx build. There is no Avenx language server today.

ESLint. avenx-core/tooling exports an ESLint-compatible parser for .component.js / .page.js files and a rule that validates component tag casing against your registered components. → ESLint guide

Official plugins live in this repository under plugins/. They are not yet published to npm — use them from a checkout for now.

Plugin What it adds
@avenx/i18n Reactive translations, locale fallback, Intl formatting.
@avenx/persistence Bridge state that survives reloads.
@avenx/charts Declarative, reactive charts.
@avenx/vite Component and page compilation, CSS processing and HMR under Vite.

→ i18n · Persistence · Vite plugin · Writing a plugin

Coming from another framework? There are migration guides for React, Vue, Svelte, Angular and Next.js.


📍 Project status

Avenx is pre-1.0. Under SemVer that means a minor release may change public behaviour; every such change is listed in CHANGELOG.md with its migration.

  • Published: avenx-core@1.0.0-rc.1 on npm.
  • In progress: a production-readiness cycle ahead of a release candidate. The unreleased section of the changelog records what it has found and fixed.
  • Node: 18 or newer. CI runs the suite on 18, 20 and 22.

The repository's own verification, as it stands on develop:

Suite Count How to run
Unit 218 files npm run test:unit
Integration 23 files npm run test:integration
System / CLI 28 files npm run test:system
Browser (E2E, Chromium) 166 tests npm run test:e2e
Plugins 3 suites npm run test:plugins

The E2E suite compiles 12 real applications with the actual CLI and drives the compiled bundles in a browser, so a test cannot pass unless the compiler and the runtime both ran. Chromium gates every pull request; Firefox and WebKit run nightly. Some fixtures exist to pin known gaps rather than to demonstrate correct behaviour, and test/e2e/README.md says which.

Caution

Known limitations, stated plainly: no language server; <@deadlock> detection does not auto-trip boundaries; the plugins are not on npm; and the constructs marked String renderer above re-render their whole template on every update.


📚 Documentation

Full documentation lives at docs.avenx-js.com.

🗺️ Jump straight to a topic
Section Start here
Getting started Introduction · Installation · Quick start · Project structure
Writing components Components · Templates & slots · Events · Lifecycle hooks
State Reactivity · Computed · Bridges · Provide & inject
Styling Scoped CSS · Transitions · Directives
Routing Pages & routing · Router & guards API
Async Resources · Defer
The compiler Rendering · Template expressions · Atlas · Trace · Rewind
Reference CLI · App · Component · Testing · Diagnostics
Going further Deployment · Best practices · TypeScript & JSDoc

Diagnostics carry stable codes, and the catalogued ones explain themselves from the terminal — avenx explain AVX_W53 prints the cause, the fix and a link. The diagnostics reference covers the rest.


🤝 Contributing

Avenx is open source and welcomes contributions, from typo fixes to compiler work. It is a friendly place for a first open-source pull request, and 112 people have contributed so far.

git clone https://github.com/Avenx-JS/avenx-js.git
cd avenx-js
npm install
npm test

Before you start, read CONTRIBUTING.md — it covers the workflow, how to add a diagnostic code, and how to run the plugin suites. For the compiler, runtime or CLI, the architecture guide maps the compile pipeline, the runtime data flow and the test tiers.

Please also see our Code of Conduct and Security Policy. Bugs and proposals belong in issues.


📄 License

MIT © the Avenx Team.


Documentation  ·  npm  ·  Issues  ·  Changelog  ·  Contributors

If Avenx is useful to you, a ⭐ on GitHub helps other people find it.

About

Avenx.js is a compiler-driven, blazingly fast JavaScript framework with proxy-based reactivity.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

77 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages