Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/export-build-file-routes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@geajs/core": patch
"@geajs/vite-plugin": patch
---

### @geajs/core (patch)

- **Export buildFileRoutes**: `buildFileRoutes` is now exported from the public `@geajs/core` entry point, making it available for the `router.setPath()` build-time transform and for any user who needs to integrate file-based routing manually.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### @geajs/vite-plugin (patch)

- **router.setPath() transform**: Include `buildFileRoutes` export and `router.setPath()` integration — the plugin now rewrites `router.setPath('./pages')` calls into the expanded `router.setRoutes(buildFileRoutes(...))` form at build time.
28 changes: 28 additions & 0 deletions examples/router-file-based/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# router-file-based

Demonstrates **file-based routing** in Gea using `router.setPath('./pages')`.

## Pages structure

```
src/pages/
layout.tsx # root layout — nav bar + <Outlet />
page.tsx # /
about/page.tsx # /about
blog/page.tsx # /blog
blog/[slug]/page.tsx # /blog/:slug (dynamic)
users/page.tsx # /users
users/[id]/page.tsx # /users/:id (dynamic)
[...all]/page.tsx # * (catch-all 404)
```

## How it works

`main.ts` calls `router.setPath('./pages')` once. The `@geajs/vite-plugin` transforms this at
build time into `import.meta.glob` calls — layouts are loaded eagerly, pages are lazy-loaded.

## Run

```bash
npx vite dev --port 5188
```
18 changes: 18 additions & 0 deletions examples/router-file-based/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>File-Based Router - Gea</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=JetBrains+Mono:wght@400;500&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/main.ts"></script>
</body>
</html>
18 changes: 18 additions & 0 deletions examples/router-file-based/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "router-file-based-gea",
"version": "1.0.0",
"type": "module",
Comment thread
izniburak marked this conversation as resolved.
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@geajs/core": "file:../../packages/gea"
},
"devDependencies": {
"vite": "^8.0.0",
"@geajs/vite-plugin": "file:../../packages/vite-plugin-gea",
"typescript": "~5.8.0"
}
}
17 changes: 17 additions & 0 deletions examples/router-file-based/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Component } from '@geajs/core'
import { router, RouterView } from '@geajs/core'

export default class App extends Component {
template() {
if (router.error) {
return (
<div class="error-page">
<h1>Something went wrong</h1>
<p>{router.error}</p>
<button click={() => router.replace('/')}>Go home</button>
</div>
)
}
return <RouterView />
}
}
11 changes: 11 additions & 0 deletions examples/router-file-based/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { router } from '@geajs/core'
import App from './App'
import './styles.css'

router.setPath('./pages')

const root = document.getElementById('app')
if (!root) throw new Error('App root element not found')

const app = new App()
app.render(root)
16 changes: 16 additions & 0 deletions examples/router-file-based/src/pages/[...all]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Component } from '@geajs/core'
import { router, Link } from '@geajs/core'

export default class NotFoundPage extends Component {
template() {
return (
<div class="view not-found">
<h1>404</h1>
<p>
No route matched <code>{router.path}</code>.
</p>
<Link to="/" label="Go Home" class="back-link" />
</div>
)
}
}
42 changes: 42 additions & 0 deletions examples/router-file-based/src/pages/about/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Component } from '@geajs/core'

const SETUP_CODE = 'router.setPath(\'./pages\')'

export default class AboutPage extends Component {
template() {
return (
<div class="view">
<h1>About</h1>
<p>
This example demonstrates file-based routing in Gea. Instead of defining a route map
by hand, call <code>{SETUP_CODE}</code> once in your entry file and the Vite plugin
generates routes automatically from the file system.
</p>
<h2>File conventions</h2>
<ul>
<li><code>page.tsx</code> — page component for the route</li>
<li><code>layout.tsx</code> — wraps all routes in the directory</li>
<li><code>[param]/page.tsx</code> — dynamic segment, becomes :param</li>
<li><code>[...slug]/page.tsx</code> — catch-all segment, becomes *</li>
</ul>
<h2>How it works</h2>
<p>
At build time the Vite plugin rewrites the <code>setPath()</code> call into
<code>import.meta.glob</code> statements. Layouts load eagerly; pages load lazily.
</p>
<h2>This example's page structure</h2>
<pre class="code-block">{
`pages/
layout.tsx root layout (nav + Outlet)
page.tsx /
about/page.tsx /about
blog/page.tsx /blog
blog/[slug]/page.tsx /blog/:slug
users/page.tsx /users
users/[id]/page.tsx /users/:id
[...all]/page.tsx * (404 catch-all)`
}</pre>
</div>
)
}
}
66 changes: 66 additions & 0 deletions examples/router-file-based/src/pages/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Component } from '@geajs/core'
import { Link } from '@geajs/core'

interface Post {
slug: string
title: string
date: string
content: string
}

const POSTS: Record<string, Post> = {
'getting-started': {
slug: 'getting-started',
title: 'Getting Started with Gea',
date: '2026-03-01',
content: `Gea is a lightweight reactive framework that compiles your component templates at build time.
To get started, create a new project with the CLI:

npm create gea@latest my-app

Then run the dev server and start building your app.`,
},
'file-based-routing': {
slug: 'file-based-routing',
title: 'File-Based Routing in Gea',
date: '2026-03-10',
content: `File-based routing lets you define routes by creating files in a pages directory.
Call router.setPath('./pages') once in your entry file and the Vite plugin does the rest.

The plugin transforms this call into import.meta.glob statements at build time, so you
get lazy-loaded pages and eager-loaded layouts with zero configuration.`,
},
'reactive-stores': {
slug: 'reactive-stores',
title: 'Reactive Stores',
date: '2026-03-18',
content: `Gea's reactivity system tracks property access at the field level using a Proxy-based Store class.
When a component reads a reactive field, it subscribes automatically. When the field changes,
only the components that read it are re-rendered — no virtual DOM diffing required.`,
},
}

export default class BlogPostPage extends Component {
template({ slug }: { slug: string }) {
const post = POSTS[slug]

if (!post) {
return (
<div class="view">
<h1>Post not found</h1>
<p>No post with slug <strong>{slug}</strong>.</p>
<Link to="/blog" label="Back to Blog" class="back-link" />
</div>
)
}

return (
<div class="view">
<Link to="/blog" label="← Back to Blog" class="back-link" />
<span class="post-date">{post.date}</span>
<h1>{post.title}</h1>
<pre class="post-body">{post.content}</pre>
</div>
)
}
}
28 changes: 28 additions & 0 deletions examples/router-file-based/src/pages/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Component } from '@geajs/core'
import { Link } from '@geajs/core'

const POSTS = [
{ slug: 'getting-started', title: 'Getting Started with Gea', date: '2026-03-01', excerpt: 'Learn how to set up your first Gea project from scratch.' },
{ slug: 'file-based-routing', title: 'File-Based Routing in Gea', date: '2026-03-10', excerpt: 'Discover how router.setPath() automatically generates routes from your file system.' },
{ slug: 'reactive-stores', title: 'Reactive Stores', date: '2026-03-18', excerpt: 'A deep dive into how Gea tracks reactivity without a virtual DOM.' },
]

export default class BlogPage extends Component {
template() {
return (
<div class="view">
<h1>Blog</h1>
<p>The latest articles from the Gea team.</p>
<div class="post-list">
{POSTS.map((post) => (
<Link key={post.slug} to={`/blog/${post.slug}`} class="post-card">
<span class="post-date">{post.date}</span>
<h2 class="post-title">{post.title}</h2>
<p class="post-excerpt">{post.excerpt}</p>
</Link>
))}
</div>
</div>
)
}
}
20 changes: 20 additions & 0 deletions examples/router-file-based/src/pages/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Component } from '@geajs/core'
import { router, Link, Outlet } from '@geajs/core'

export default class RootLayout extends Component {
template() {
return (
<div class="app">
<nav class="nav">
<Link to="/" label="Home" class={router.isExact('/') ? 'nav-link active' : 'nav-link'} />
<Link to="/about" label="About" class={router.isActive('/about') ? 'nav-link active' : 'nav-link'} />
<Link to="/blog" label="Blog" class={router.isActive('/blog') ? 'nav-link active' : 'nav-link'} />
<Link to="/users" label="Users" class={router.isActive('/users') ? 'nav-link active' : 'nav-link'} />
</nav>
<main class="content">
<Outlet />
</main>
</div>
)
}
}
34 changes: 34 additions & 0 deletions examples/router-file-based/src/pages/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Component } from '@geajs/core'
import { Link } from '@geajs/core'

export default class HomePage extends Component {
template() {
return (
<div class="view">
<h1>Home</h1>
<p>
Welcome to the Gea file-based router example. Routes are automatically generated from
the <code>pages/</code> directory — no manual route map required.
</p>
<p>
Each <code>page.tsx</code> file becomes a route, and <code>layout.tsx</code> files
wrap their directory's routes with a shared layout.
</p>
<div class="card-grid">
<Link to="/about" class="card">
<span class="card-title">About</span>
<span class="card-desc">Learn about this example</span>
</Link>
<Link to="/blog" class="card">
<span class="card-title">Blog</span>
<span class="card-desc">Read the latest posts</span>
</Link>
<Link to="/users" class="card">
<span class="card-title">Users</span>
<span class="card-desc">Browse user profiles</span>
</Link>
</div>
</div>
)
}
}
42 changes: 42 additions & 0 deletions examples/router-file-based/src/pages/users/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Component } from '@geajs/core'
import { Link } from '@geajs/core'

interface User {
id: string
name: string
role: string
bio: string
}

const USERS: Record<string, User> = {
'1': { id: '1', name: 'Alice', role: 'Engineer', bio: 'Loves building compilers and reactive frameworks.' },
'2': { id: '2', name: 'Bob', role: 'Designer', bio: 'Passionate about minimal interfaces and typography.' },
'3': { id: '3', name: 'Charlie', role: 'PM', bio: 'Keeps the trains running on time.' },
'4': { id: '4', name: 'Dana', role: 'DevRel', bio: 'Makes developers feel welcome and builds great demos.' },
}

export default class UserProfilePage extends Component {
template({ id }: { id: string }) {
const user = USERS[id]

if (!user) {
return (
<div class="view">
<h1>User not found</h1>
<p>No user with id <strong>{id}</strong>.</p>
<Link to="/users" label="← Back to Users" class="back-link" />
</div>
)
}

return (
<div class="view user-profile">
<Link to="/users" label="← Back to Users" class="back-link" />
<div class="avatar">{user.name[0]}</div>
<h1>{user.name}</h1>
<span class="role-badge">{user.role}</span>
<p>{user.bio}</p>
</div>
)
}
}
31 changes: 31 additions & 0 deletions examples/router-file-based/src/pages/users/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Component } from '@geajs/core'
import { Link } from '@geajs/core'

const USERS = [
{ id: '1', name: 'Alice', role: 'Engineer' },
{ id: '2', name: 'Bob', role: 'Designer' },
{ id: '3', name: 'Charlie', role: 'PM' },
{ id: '4', name: 'Dana', role: 'DevRel' },
]

export default class UsersPage extends Component {
template() {
return (
<div class="view">
<h1>Users</h1>
<p>Click a user to view their profile.</p>
<div class="user-list">
{USERS.map((user) => (
<Link key={user.id} to={`/users/${user.id}`} class="user-row">
<div class="avatar-sm">{user.name[0]}</div>
<div class="user-info">
<span class="user-name">{user.name}</span>
<span class="role">{user.role}</span>
</div>
</Link>
))}
</div>
</div>
)
}
}
Loading