A mini meta-framework for VanJS developed around the awesome Vite. The plugin comes with a set of modules to simplify your workflow:
- @vanjs/router - one of the most important part of an application which allows you to split code and lazy load page like components with ease, handles both Client Side Rendering (SSR) and Server Side Rendering (CSR) and makes it really easy to work with;
- @vanjs/meta - allows you to create metadata for your pages as well as load additional assets with ease;
- @vanjs/jsx - enables JSX transformation;
- @vanjs/setup - enables loading VanJS modules isomorphically;
- @vanjs/server - provides various tools for Server Side Rendering;
- @vanjs/client - provides various tools for Client Side Rendering.
The plugin will automatically load the appropriate Van or VanX objects depending on the client/server environment with zero configuration needed. It uses the mini-van-plate/shared
module to register the required objects in an isomorphic enviroment.
Also in development mode, the plugin will load the van.debug.js
module to help you better troubleshoot your VanJS application.
-
The plugin uses
van-ext
along withmini-van-plate
so you can have everything ready from the start. -
Kickstart your VanJS project with
npm create vanjs@latest
. Some starter templates feature this plugin and most essential tools.
- Install the plugin:
npm install vite-plugin-vanjs@latest
pnpm add vite-plugin-vanjs@latest
deno add npm:vite-plugin-vanjs@latest
bun add vite-plugin-vanjs@latest
- To add Typescript support, edit your
src/vite-env.d.ts
(or any global types you have set in your app) as follows:
/// <reference types="vite/client" />
/// <reference types="vite-plugin-vanjs" />
Update your vite.config.ts
file:
// vite.config.mts
import { defineConfig } from 'vite';
import vanjs from 'vite-plugin-vanjs';
export default defineConfig({
plugins: [vanjs()],
});
Example:
While the plugin will resolve the appropriate modules automatically depending on the environment, for your convenience, you can also import the @vanjs/van
and @vanjs/vanX
virtual modules, so the plugin makes sure to load the right modules where needed.
// my-component.ts
import van from '@vanjs/van';
// use van as usual
const MyComponent = () => {
return van.tags.div("Hello from VanJS!");
};
Using vanX
in your code:
// my-list.ts
import van from '@vanjs/van';
import vanX from '@vanjs/vanX';
type ListItem = { text: string, done: boolean };
// use VanJS as usual
const MyList = () => {
const items = vanX.reactive<ListItem[]>([]);
const { div, button, span, a, input, del } = van.tags;
const inputDom = input({ type: "text", placeholder: "your new item" });
return div(
inputDom,
button({onclick: () => items.push({text: inputDom.value, done: false})}, "Add"),
vanX.list(div, items, ({val: v}, deleter) => div(
input({type: "checkbox", checked: () => v.done, onclick: (e) => v.done = e.target.checked}),
() => (v.done ? del : span)(v.text),
button({ onclick: deleter }, "Remove"),
)),
)
};
Note: Anywhere you have vite-plugin-vanjs enabled in your Vite apps, all imports from "vanjs-core"
or "vanjs-ext"
will be replaced with "@vanjs/van"
and "@vanjs/vanX"
respectivelly. In addition you have the isServer
getter in the @vanjs/setup
module, something you can use to exclude code execution in server/client environment.
The vite-plugin-vanjs plugin provides a router with load and preload capability, code splitting and lazy loading, all via the exported @vanjs/router
module. This functionality is still in early stages, but it manages to work with both Server Side Rendering (SSR) when using an appropriate starter template, as well as Client Side Rendering (CSR/SPA - your classic VanJS app), as we'll see in the example below.
Here's a basic example, let's start with the app.ts
:
// src/app.ts
import van from "vanjs-core";
import { Router, Route } from "@vanjs/router";
// define routes
Route({ path: '/', component: () => van.tags.div('Hello VanJS') });
Route({ path: '/about', component: lazy(() => import('./pages/about'))});
Route({ path: '*', component: () => van.tags.div('404 - Page Not Found') });
function App() {
// the Router is only an outlet
return Router();
}
// render the app
van.add(document.body, App());
IMPORTANT - using lazy components for the main route ("/"
) is not allowed. This is to prevent hydration waterfalls, especially for SSR apps.
Here's how a page should look like, pay attention to the comments:
// src/pages/about.ts
import van from "vanjs-core";
import { A, navigate } from "@vanjs/router";
// define routes
export const route = {
preload: async () => {
// in most cases you may want to enforce user access control
console.log('About preload triggered');
},
load: async () => {
// Load data if needed
// you might want to cache this data
console.log('About load triggered');
}
}
// you must export your page component as either
// a named export "Page" or a default export
export function Page() {
const { div, h1, p, button } = van.tags;
return div(
h1('About'),
p('This is the about page'),
A({ href: "/" }, "Back to Home"),
button({ onclick: () => navigate('/about-details') }, "Learn more..")
)
}
Notes
- when hovering the
A
component, if it links to a lazy component it will trigger that page component preload, but not preload any data; - if you use the regular
a
fromvan.tags
instead of theA
component, your application will work just like a classic Multi-Page App (MPA); - the
navigate
tool is used by theA
component and you can also use it to navigate to different routes, something you can use with your API/logic.
The vite-plugin-vanjs plugin provides metadata management via the exported @vanjs/meta
module. This module works with both SSR and CSR, and makes it easy to work with.
Depending on the type of application, it's generally very easy to setup the system to work properly, we only need to make sure that we execute functions in a specific order:
- first we define a set of default tags to be used by both client and server (if using an SSR template) on all pages that don't come with any metadata tags;
- next, on other pages, we define tags that override both the existing and the default tags.
Here's a quick example, first let's start again with the app.ts
:
import van from 'vanjs-core'
import { Style, Title, Link, Meta } from '@vanjs/meta'
function App() {
const { div, h1, p } van.tags;
// a good practice is to define some default tags
// they are used on pages where no tags are set
Title("VanJS + Vite App");
Meta({ name: "description", content: "Sample app description" });
Meta({ property: "og:description", content: "Some open graph description for your app" });
Link({ href: 'path-to/your-style.css', rel: "stylesheet" });
Script({ src: 'path-to/your-script.js' });
Style({ id: "app-style" },
`p { margin-bottom: 1rem }`
);
return div(
h1('Hello VanJS!'),
p('Sample paragraph.')
);
}
// render or hydrate the app
van.add(document.body, App());
Note
- the
Style
component doesn't have any unique attribute so we must use a unique ID to prevent duplicates; - all provided metadata components don't return any markup, their function is to register new tags or update existing ones.
To enable JSX transformation, you don't need to do anything except to add Typescript support if you need to. So edit your tsconfig.json
as follows:
{
"compilerOptions": {
/** other compilerOptions */
"jsx": "preserve",
"jsxImportSource": "@vanjs/jsx"
}
}
Example:
// App.tsx
import van from '@vanjs/van';
const App = () => {
const count = van.state(0);
const btnRef = van.state<{ current: HTMLElement }>();
return (
<button ref={btnRef} onClick={() => count.val++}>{count}</button>
);
}
const root = document.getElementById("app") as HTMLElement;
van.add(root, <App /> as HTMLElement);
Notes:
- in cases like this one, enforcing a certain typescript type via
as
might be in good order depending on who renders your component; - you can use
ref
as avan.state
,class
attribute instead ofclassName
,for
attribute instead ofhtmlFor
; - you can use style as both an object and a string;
- for a JSX starter template, check out the vite-starter-vanjs-ssr-jsx.
- for a pure vanilla starter template, check out the vite-starter-vanjs-ssr.
- van-jsx a simple Vanilla JSX implementation;
- vanjs-jsx the official VanJS addon;
- surplus for Typescript definitions also used by SolidJS;
- inferno also for typescript definitions.
Released under MIT.