A Vite plugin that integrates TomorrowJS with TSX/JSX, SSR, SSG, file-based routing, nested layouts, and enhanced hot module reloading (HMR).
Modern web development often presents a choice between the flexibility of frameworks like React or Next.js and the simplicity of DOM-first approaches. TomorrowJS is a minimalist library that enhances vanilla web development with attribute-based bindings, simple state management, and no heavy virtual DOM.
@tmrw/vite bridges the gap, delivering a Next.js-like developer experience—complete with TSX pages, nested layouts, SSR, and optional SSG—while preserving TomorrowJS's lean philosophy. It allows you to:
- Write pages and layouts as TSX/JSX components without manual imports
- Dynamically render content on the server (SSR) during development and production
- Optionally pre-generate static HTML for all pages at build time (SSG)
- Organize your application using file-based routing and nested layouts
- Benefit from enhanced HMR that tries to patch the DOM on changes, minimizing full reloads and preserving application state
This plugin aims to offer a modern developer experience in a lightweight, DOM-centric environment.
Use familiar JSX syntax for writing pages and layouts. You don't have to import TomorrowJS utilities in every file—just focus on your markup and state updates.
Enjoy SSR out-of-the-box. On npm run dev
, pages are rendered on the server. This makes local development feel like working in a robust, production-ready environment.
Set ssg: true
in the plugin options to pre-generate static HTML for all routes at build time. Perfect for deploying fully static sites to a CDN.
Place page.tsx
files in an app/
directory to define routes (app/about/page.tsx
-> /about
). Add layout.tsx
files in directories to wrap child pages. Build complex page structures without explicit routing code.
On code changes, the plugin tries to re-import changed modules, re-render the current page and layouts on the client, and perform minimal DOM patches. This means fewer full reloads and better preservation of current state—an even smoother development experience than vanilla Vite HMR.
npm install @tmrw/vite @tmrw/core
Example:
project/
├── app/
│ ├── layout.tsx
│ ├── page.tsx
│ └── about/
│ ├── layout.tsx
│ └── page.tsx
├── main.ts
├── vite.config.ts
└── package.json
-
app/page.tsx
: Root page for/
-
app/about/page.tsx
: Page for/about
-
app/layout.tsx
: Global layout wrapping all pages -
app/about/layout.tsx
: Nested layout wrapping pages under/about
import { createStore, registerAction, initTmrw } from "@tmrw/core";
const store = createStore({ title: "Hello TomorrowJS", count: 0 });
registerAction("incrementCount", ({ store }) => {
const current = store?.get("count") || 0;
store?.set("count", current + 1);
});
initTmrw({ store, root: document.getElementById("app")! });
import { defineConfig } from "vite";
import { tomorrowPlugin } from "@tmrw/vite";
export default defineConfig({
plugins: [
tomorrowPlugin({
appDir: "./app",
ssg: false,
}),
],
});
npm run dev
Open http://localhost:5173. You'll see your page rendered on the server. Changes to app files should update instantly thanks to HMR, often without a full page reload.
-
appDir (string, default: 'app'): The directory containing your pages and layouts. The plugin expects
page.tsx
files to define routes and optionallayout.tsx
files for nested layouts. -
ssg (boolean, default: false): If true, running
npm run build
will statically pre-render all discovered pages intodist/
, producing ready-to-serve HTML files. If false, pages are rendered at runtime in production SSR mode.
The plugin configures the Vite ESBuild pipeline to use a custom JSX runtime. This runtime returns a lightweight tree that is converted to HTML on the server for SSR or SSG.
During development, Vite's dev server calls the plugin's SSR middleware. It finds the corresponding page.tsx
file for the requested URL, along with any parent layout.tsx
files. It then:
- Creates a store (via TomorrowJS) and calls the page's
render()
function to produce a VNode tree - Wraps the tree with nested layouts
- Converts the final tree to HTML and serves it
In production, a similar process occurs (you can run Vite's preview server or integrate with a Node server), providing SSR by default.
When you run npm run build
with ssg: true
, the plugin:
- Discovers all routes by finding
page.tsx
files - For each route, it simulates SSR at build time, producing an HTML file in
dist/
- Deploy
dist/
to a static host or CDN for extremely fast, CDN-delivered pages
The plugin enhances the default HMR:
- On code changes, instead of always doing a full page reload, it tries to re-import the affected modules on the client
- It re-renders the current page and layouts in memory and attempts a minimal DOM patch
- If the structure of the page changed too much, it falls back to a full reload, ensuring consistency
This means that when you tweak text or minor structure, the page updates in place, preserving scroll position, store data, and other UI states.
Q: Do I need React or a similar framework?
A: No. This plugin uses TomorrowJS and a custom minimal TSX runtime. You can write JSX/TSX, but there's no virtual DOM or React dependency. It's a simpler, DOM-first approach.
Q: Can I use client-side routing or complex SPA patterns?
A: TomorrowJS encourages minimalism. While you can navigate between pages via standard links, if you need SPA-like transitions, you may implement custom code or another lightweight router. This plugin focuses on SSR, SSG, and file-based routing for multi-page apps.
Q: Will it work with TypeScript?
A: Yes. TypeScript is fully supported. The plugin encourages a typed environment, and TomorrowJS's store also benefits from strong typings.
Q: How does it handle dynamic routes (e.g., [slug].tsx)?
A: The plugin maps [param]
to :param
in routes, similar to Next.js. So app/blog/[slug]/page.tsx
maps to /blog/:slug
. The SSR logic matches routes accordingly.
Q: What if I rely on heavy client-side interactivity?
A: TomorrowJS offers attribute-based bindings for reactivity. You can register actions with data-t-on
attributes. If you need more complexity, integrate your preferred state management or UI libraries. This plugin does not limit your approach.
Because we do not tear down and re-initialize the store on every change, your TomorrowJS store state remains intact. Minor edits to your page or layout modules update the DOM without losing application state, improving development speed and comfort.
The plugin doesn't do partial hydration by default. Everything is SSR'd and then TomorrowJS enhances it. If you want island architecture or partial hydration, you can compose those patterns on top of this approach by controlling where initTmrw()
runs or how you structure your pages.
You can add other libraries for AJAX, animations, or state management. The plugin doesn't interfere with standard JS usage in the browser or Node environment.
A sample project structure and code snippet are provided in the plugin's documentation. After installing, simply:
npm run dev
Open http://localhost:5173 and see your pages live, SSR rendered, and TSX-based. Edit app/page.tsx
and watch the page update instantly.
MIT
By following this guide, you can confidently adopt @tmrw/vite
for a modern, TSX-based TomorrowJS development experience that feels as powerful and convenient as major frameworks, but remains lean, DOM-first, and fully in line with TomorrowJS's philosophy.