Introduction
What is Adex?
adex
is a vite plugin to write full stack Node.js apps using preact.
It was authored to avoid writing glue code for writing server rendered preact apps. You can do most of what adex does by using existing tools,
a few examples of this can be found on preachjs/templates
Features
- Unified Routing
- Easy to use Islands (Opt-in feature)
- API Routes
- Works wherever Node.js does
Getting Started
Installation
Quick Setup (Recommended)
- You can use the
create-adex
cli
npm create adex@latest ./path/to/create/in
- or just manually clone the existing template
Manually
If you still wish to set it up manually.
- Create a preact based vite app
npm create vite@latest -- -t preact
- Add in the required deps
npm add -D adex
- Modify config to use adex
// vite.config.js
import { defineConfig } from 'vite'
import preact from '@preact/preset-vite'
+ import { adex } from 'adex'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
+ adex(),
preact()],
})
- Remove the default preact files and add in basic structure for adex
rm -rf ./src/\* index.html
mkdir -p src/pages src/api
touch src/global.css
Folder Structure
After the setup, you should see this basic folder structure.
public # -> Public assets
src # -> source files
--- pages
------ hello.js # -> Route binding for `/hello`
--- api
------ hello.js # -> Route binding for `/api/hello`
--- global.css # -> Global styles
vite.config.js # -> Vite config
Only pages
and api
in the src
folder hold special functionality, everything else will be processed
like another javascript/typescript file.
To understand more about it head over to Concepts ↓
Concepts
Routing
adex
core functionality is that of a router, it creates route bindings for pages and api's.
Route bindings can be created simply by creating a file in the src/pages
or src/api
folder.
Pages
Pages or files inside src/pages
are components that define a view. A view is what the user sees and so this is where you write the user facing elements for adex.
Each route is bound to a similar path in the pages
folder.
Eg: src/pages/hello.js
would be accessed as /hello
in the browser.
Rules
- The page needs a default export which defines the view
- Server sided code should not be executed in pages, they are strictly client sided views.
API Routes
The other side of the application is to handle server side data which can be sent from the API routes.
These route bindings are created by adding files to the src/api
directory.
Eg: src/api/hello.js
would be bound to /api/hello
API route files need to export simple node:http handlers.
export default function handler(req, res) {
return res.write("hello world");
}
API Helpers
Since a lot of what we write get's redundant while working with web apps, a few helpers are added to the request and response references
Response Helpers
text
- for plain text responsesjson
- for serialized json responseshtml
- for html strings
Request Helpers
parseBodyJSON
- to parse the request body as JSON
Examples
// JSON Response
export default function handler(req, res) {
return res.json({
ping: "pong",
});
}
export default async function handler(req, res) {
if (req.method !== "POST") {
res.statusCode = 404;
return res.end();
}
const payload = await req.parseBodyJSON();
return res.json({
...payload,
});
}
Islands
This is an additional feature of adex that significantly changes how the views are rendered. Instead of rendering fully interactive pages, you now render static pages with interactive bits.
This replicates a similar feature set to Deno's fresh except you don't need to define a separate folder for islands
, you just write them as components and adex
will take care of identifying them.
You can read more about the concept on preactjs.com/blog/simplifying-islands-arch.
To enable this you pass the islands
option as true
in the adex plugin
// vite.config.js
import { defineConfig } from "adex";
export default defineConfig({
//...remaining config
plugins: [
//...remaining plugins
adex({
islands: true,
}),
],
});
Caveats
Existing pages that were interactive will become static and will not bind any events or state handlers. The sub components that are interactive will stay interactive
For other options checkout the configuration reference
Configuration Reference
type Options = {
fonts?: FontOptions;
islands?: boolean;
};
type FontOptions = {
providers: string[];
families: FontFamilies[];
};
type FontFamilies = {
name: string;
weights: string[];
styles: Array<"normal" | "italic" | "oblique">;
};
Example Usage
import { defineConfig } from "vite";
import { providers } from "adex/fonts";
export default defineConfig({
plugins: [
adex({
// turn on `islands` components parsing on / off (default: false)
islands: false,
// Auto import fonts into the final bundled css so you just have
// to use them instead of figuring out the links
fonts: {
providers: [providers.google()],
families: [
{
name: "Inter",
weights: ["400", "600"],
styles: ["normal"],
},
],
},
}),
],
});