Skip to content

Commit ea6af06

Browse files
Add Xote and Xote SSR templates (#124)
* add Xote CSR and SSR templates * group Xote variants * Address Xote template review feedback * Format templates with ReScript 12 --------- Co-authored-by: Florian Hammerschmidt <florianh89@gmail.com>
1 parent 88d90d9 commit ea6af06

30 files changed

Lines changed: 4265 additions & 14 deletions

‎src/NewProject.res‎

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,35 @@ let updateViteConfig = async () => {
8080
let newProjectMessage = "Create a new ReScript project"
8181

8282
let getTemplateOptions = () =>
83-
Templates.templates->Array.map(({name, displayName, shortDescription}) => {
83+
Templates.templates->Array.map(({name, displayName, shortDescription, _}) => {
8484
P.value: name,
8585
label: displayName,
8686
hint: shortDescription,
8787
})
8888

89+
let getVariantOptions = (variants: array<Templates.variant>) =>
90+
variants->Array.map(({name, displayName, shortDescription}) => {
91+
P.value: name,
92+
label: displayName,
93+
hint: shortDescription,
94+
})
95+
96+
let promptTemplateName = async () => {
97+
let selectedName = await P.select({
98+
message: "Select a template",
99+
options: getTemplateOptions(),
100+
})->P.resultOrRaise
101+
102+
switch Templates.templates->Array.find(template => template.name === selectedName) {
103+
| Some({variants: Some(variants)}) =>
104+
await P.select({
105+
message: "Select a variant",
106+
options: getVariantOptions(variants),
107+
})->P.resultOrRaise
108+
| _ => selectedName
109+
}
110+
}
111+
89112
let createProject = async (~templateName, ~projectName, ~versions) => {
90113
let templatePath = CraPaths.getTemplatePath(~templateName)
91114
let projectPath = Path.join2(Process.cwd(), projectName)
@@ -156,11 +179,7 @@ let createNewProject = async () => {
156179

157180
let templateName = switch commandLineArguments.templateName {
158181
| Some(templateName) => templateName
159-
| None =>
160-
await P.select({
161-
message: "Select a template",
162-
options: getTemplateOptions(),
163-
})->P.resultOrRaise
182+
| None => await promptTemplateName()
164183
}
165184

166185
let versions = useDefaultVersions

‎src/Templates.res‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
1+
type variant = {
2+
name: string,
3+
displayName: string,
4+
shortDescription: string,
5+
}
6+
17
type t = {
28
name: string,
39
displayName: string,
410
shortDescription: string,
11+
variants: option<array<variant>>,
512
}
613

714
let basicTemplateName = "rescript-template-basic"
815
let viteTemplateName = "rescript-template-vite"
916
let nextjsTemplateName = "rescript-template-nextjs"
17+
let xoteTemplateName = "rescript-template-xote"
18+
let xoteSsrTemplateName = "rescript-template-xote-ssr"
1019
let templateNamePrefix = "rescript-template-"
1120

12-
let supportedTemplateNames = ["vite", "nextjs", "basic"]
21+
let supportedTemplateNames = ["vite", "nextjs", "xote", "xote-ssr", "basic"]
1322

1423
let getTemplateName = templateName => {
1524
let templateName = templateName->String.toLowerCase
@@ -24,15 +33,35 @@ let templates = [
2433
name: viteTemplateName,
2534
displayName: "Vite",
2635
shortDescription: "Vite 8, React and Tailwind 4",
36+
variants: None,
2737
},
2838
{
2939
name: nextjsTemplateName,
3040
displayName: "Next.js",
3141
shortDescription: "Next.js 15 with static export and Tailwind 3",
42+
variants: None,
43+
},
44+
{
45+
name: xoteTemplateName,
46+
displayName: "Xote",
47+
shortDescription: "Xote with Vite, signals and Tailwind 4",
48+
variants: Some([
49+
{
50+
name: xoteTemplateName,
51+
displayName: "Client-Side Rendering (CSR)",
52+
shortDescription: "Vite, signals and Tailwind 4",
53+
},
54+
{
55+
name: xoteSsrTemplateName,
56+
displayName: "Server-Side Rendering (SSR)",
57+
shortDescription: "Vite SSR via Node server, signals and Tailwind 4",
58+
},
59+
]),
3260
},
3361
{
3462
name: basicTemplateName,
3563
displayName: "Basic",
3664
shortDescription: "Command line hello world app",
65+
variants: None,
3766
},
3867
]

‎templates/rescript-template-nextjs/src/layouts/MainLayout.res‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ module Navigation = {
1717
<a
1818
className="px-3 font-bold"
1919
target="_blank"
20-
href="https://github.com/rescript-lang/create-rescript-app/tree/master/templates/rescript-template-nextjs">
20+
href="https://github.com/rescript-lang/create-rescript-app/tree/master/templates/rescript-template-nextjs"
21+
>
2122
{React.string("Github")}
2223
</a>
2324
</div>

‎templates/rescript-template-vite/src/App.res‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ let make = () => {
2525
<p className="text-center font-thin text-stone-400">
2626
{React.string("Learn more about ")}
2727
<a
28-
href="https://rescript-lang.org/" target="_blank" className="text-blue-500 hover:underline">
28+
href="https://rescript-lang.org/" target="_blank" className="text-blue-500 hover:underline"
29+
>
2930
{React.string("ReScript")}
3031
{React.string(".")}
3132
</a>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# xote · SSR template
2+
3+
A server-rendered starter for [xote](https://github.com/brnrdog/xote) apps. Renders HTML on a Node server, then hydrates on the client so reactivity takes over without re-rendering.
4+
5+
## Stack
6+
7+
- ReScript v12 (JSX transform: `XoteJSX`)
8+
- xote ^6.2 — uses `SSR.renderToString`, `SSRState`, and `Hydration`
9+
- rescript-signals
10+
- Vite 7 — runs in middleware mode for dev, and produces both client and SSR bundles for production
11+
- Tailwind CSS v4 (`@tailwindcss/vite`)
12+
13+
## Layout
14+
15+
```
16+
.
17+
├── index.html # template with <!--app-html--> and <!--app-state--> placeholders
18+
├── server.mjs # Node HTTP server (Vite middleware in dev, static + SSR in prod)
19+
├── vite.config.mjs
20+
├── rescript.json
21+
├── package.json
22+
└── src/
23+
├── styles.css
24+
├── Counter.res # uses SSRState.signal so state survives hydration
25+
├── Page.res # landing page composition
26+
├── App.res # shared `view` thunk used by server and client
27+
├── Server.res # exports render() → { html, stateScript }
28+
└── Client.res # imports styles, calls Hydration.hydrateById
29+
```
30+
31+
## How it fits together
32+
33+
1. `server.mjs` reads `index.html` and (in dev) hands it to Vite for asset/CSS transformation.
34+
2. It imports `Server.res` and calls `render()`, which uses `SSR.renderToString(App.view)` to produce HTML and `SSRState.generateScript()` to produce a `<script>` tag carrying initial signal values.
35+
3. The placeholders `<!--app-html-->` and `<!--app-state-->` in `index.html` are replaced with those two strings.
36+
4. The browser loads `Client.res.mjs`, which calls `Hydration.hydrateById(App.view, "root")`. xote walks the existing DOM, attaches reactive bindings, and reads back the serialized `SSRState` so signals start with the same values the server used.
37+
38+
## Scripts
39+
40+
| Script | What it does |
41+
| --- | --- |
42+
| `npm run res:dev` | Watches and compiles ReScript on save. Run during development. |
43+
| `npm run res:build` | One-shot ReScript build. |
44+
| `npm run res:clean` | Removes ReScript build artifacts. |
45+
| `npm run dev` | Runs `node server.mjs` with Vite in middleware mode. |
46+
| `npm run build` | Builds the client bundle and the SSR bundle. |
47+
| `npm run build:client` | Just the client bundle (`dist/client/`). |
48+
| `npm run build:ssr` | Just the SSR bundle (`dist/server/Server.res.js`). |
49+
| `npm run start` | Runs the server in production mode against the built bundles. |
50+
51+
## Develop
52+
53+
```bash
54+
npm install
55+
npm run res:dev
56+
# in another terminal:
57+
npm run dev
58+
```
59+
60+
Open http://localhost:3000. The page is server-rendered (view source — the counter HTML is already there) and becomes interactive after hydration.
61+
62+
## Build & run in production
63+
64+
```bash
65+
npm run res:build
66+
npm run build
67+
npm run start
68+
```
69+
70+
`server.mjs` defaults to port 3000; override with `PORT=8080 npm run start`.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
dist
3+
lib
4+
*.res.mjs
5+
*.res.js
6+
.DS_Store
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>xote · SSR template</title>
7+
<link rel="stylesheet" href="/src/styles.css" />
8+
</head>
9+
<body>
10+
<div id="root"><!--app-html--></div>
11+
<!--app-state-->
12+
<script type="module" src="/src/Client.res.mjs"></script>
13+
</body>
14+
</html>

0 commit comments

Comments
 (0)