← Back to Projects

Nikx - Custom Frontend Framework

Nikx - Custom Frontend Framework

In November and December of 2024, I built a minimal frontend framework called Nikx as part of the Programmeren 3 course. The goal was not to create a usable alternative to existing frameworks, but to deepen my understanding of how tools like React parse and transform JSX-style syntax into executable JavaScript. Nikx is extremely simple by design. It includes its own grammar, a compiler pipeline, and a demo web application. The focus was on learning how to go from source code with mixed syntax (logic plus markup) to generated DOM manipulation code.

Goal

The key objective of this project was to experience what happens "under the hood" in frameworks like React. That included:

  • Designing a language that mixes HTML-style elements with JavaScript-like logic

  • Building a lexer and parser using ANTLR

  • Walking a concrete parse tree to build a clean and usable AST

  • Writing a custom code generator that emits real JavaScript

  • Integrating the whole thing into a working Vite-based demo app

  • Separating responsibilities into clean, modular packages

Unlike React or real-world compilers, Nikx does not include dynamic behavior or reactivity. Instead, it compiles static Nikx code into real DOM-creating JavaScript.


Architecture Overview

Nikx is composed of several key parts, each with a distinct responsibility:

1. Parser and Lexer (ANTLR)

I used ANTLR to generate the parser, lexer, and base visitor from a custom EBNF grammar. The grammar supports constructs like variable declarations, function declarations, nested HTML elements, and function calls. ANTLR took care of parsing source files and producing a parse tree with all the syntactic elements I needed.

2. AST Construction

Instead of using the parse tree directly, I built a custom AST (Abstract Syntax Tree) by extending the ANTLR visitor. I implemented NikxAstVisitor, which walks the parse tree and returns simplified AST nodes. Each language feature is represented by a dedicated node class, including:

  • ProgramNode, VariableDeclarationNode, FunctionDeclarationNode

  • htmlElementNode (with support for nested and self-closing tags)

  • ExpressionStatementNode, FunctionCallNode, ArgumentListNode, and LiteralNode

The AST ensures that code generation is predictable and clean.

3. Code Generation

Once the AST is built, I pass it to my code generator: NikxVisitorImpl. This visitor converts AST nodes into real JavaScript strings that use document.createElement, appendChild, and document.createTextNode.

I use the nanoid library to generate unique variable names for each DOM element to avoid conflicts and ensure proper scoping. For example, each <button> tag becomes something like const _button_AB12CD = document.createElement("button");.

The generator also:

  • Adds DOM elements to document.getElementById("app")

  • Supports function calls and argument passing

  • Handles literals, blocks, and variable declarations


Features

Nikx supports a limited but complete set of features:

  • box keyword for declaring variables

  • fun keyword for declaring functions

  • String, number, and boolean literals

  • Function calls with arguments

  • Nested JSX-like HTML structure

  • Self-closing and normal HTML elements

  • Static rendering into the DOM

These features are enough to replicate a basic “Hello World” app, complete with layout, logic, and rendering.


Example

An example Nikx program might look like this:

Schermafbeelding 2025-07-16 185903

This will be compiled into valid JavaScript that creates all DOM elements and injects them into the HTML page using native DOM APIs.


Testing

I implemented tests at several levels:

  • AST construction tests: Ensuring correct node structures from Nikx input

  • Syntax error detection: Invalid code throws readable errors

  • End-to-end tests: Validating that full Nikx programs are compiled into working HTML output

For example, calling an undeclared function or mismatching HTML tags throws custom error messages like:

You cannot call function "greet", because it is not declared


Project Structure

The Nikx project is split into three independent directories:

  • nikx-compiler: ANTLR grammar, AST classes, parser, visitor, and code generator

  • nikx-vite-plugin: Vite plugin that compiles .nikx files during bundling

  • nikx-application: A demo frontend app that runs compiled Nikx files and renders the output

Everything is written in TypeScript, and each module has its own package.json.


Build and Run Instructions

You can build everything automatically by running the build-all.bat script.

Or follow the manual steps:

  • npm install && npm run generate in nikx-compiler

  • npm install && npm run build in nikx-vite-plugin

  • npm install && npm run build in nikx-application

  • Run the app with npm run dev in nikx-application

  • Run all tests with npm run test in nikx-compiler


What I Learned

This project taught me more about how compilers work than any theory lesson ever could. I learned:

  • How to define a formal language using EBNF

  • How ANTLR works and how to generate parse trees

  • How to build a custom AST and use the visitor pattern

  • How to translate high-level language features into low-level JavaScript

  • The structure and limitations of JSX-style syntax

  • How to separate logic cleanly into compiler, plugin, and application layers

It was the first time I worked this deeply with language parsing and generation. Despite its simplicity, Nikx gave me a complete experience of what building a programming tool feels like.


Result

Nikx is not intended to be used in production, but it is a fully functional mini-framework with its own language, compiler, and runtime. It can parse simple mixed-syntax files, generate JavaScript, and render real HTML output in the browser.

It also gave me the opportunity to apply compiler concepts, build a Vite plugin, write custom AST logic, and manage a multi-project structure in a clean and testable way. View the code of this project on my github