create-fluixi 0.1.0-alpha.5 → 0.1.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +18 -7
- package/package.json +6 -2
- package/template-spa/package.json +6 -5
- package/template-spa/tsconfig.json +2 -1
- package/template-spa-html/_gitignore +4 -0
- package/template-spa-html/index.html +12 -0
- package/template-spa-html/package.json +21 -0
- package/template-spa-html/src/app.ts +13 -0
- package/template-spa-html/src/main.ts +5 -0
- package/template-spa-html/tsconfig.json +13 -0
- package/template-spa-html/vite.config.ts +6 -0
- package/template-ssr/package.json +8 -7
- package/template-ssr/tsconfig.json +2 -1
- package/template-ssr-html/README.md +19 -0
- package/template-ssr-html/_gitignore +4 -0
- package/template-ssr-html/fluixi.config.ts +5 -0
- package/template-ssr-html/index.html +12 -0
- package/template-ssr-html/package.json +24 -0
- package/template-ssr-html/src/app.ts +34 -0
- package/template-ssr-html/src/entry-client.ts +10 -0
- package/template-ssr-html/src/entry-server.ts +17 -0
- package/template-ssr-html/src/fluixi-env.d.ts +7 -0
- package/template-ssr-html/src/routes/about.ts +10 -0
- package/template-ssr-html/src/routes/index.ts +25 -0
- package/template-ssr-html/tsconfig.json +13 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* `npm create fluixi <dir>` — scaffold a new Fluixi app. Prompts for a name
|
|
4
|
-
*
|
|
5
|
-
* the command line: `create-fluixi my-app --spa`
|
|
6
|
-
*
|
|
3
|
+
* `npm create fluixi <dir>` — scaffold a new Fluixi app. Prompts for a name, a mode
|
|
4
|
+
* (SSR via @fluixi/start, or SPA via vite + @fluixi/vite-plugin), and a template syntax
|
|
5
|
+
* (JSX or html``), unless given on the command line: `create-fluixi my-app --spa --html`
|
|
6
|
+
* (`--ssr`/`--spa`, `--jsx`/`--html`). The html`` variant is JSX-free — plain `.ts`
|
|
7
|
+
* sources, no `jsx` in tsconfig. Zero runtime deps — the styled prompts (gradient
|
|
8
|
+
* wordmark, gutter, arrow-key select) are hand-rolled ANSI.
|
|
7
9
|
*/
|
|
8
10
|
import { cpSync, renameSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
9
11
|
import { resolve, basename, join } from 'node:path';
|
|
@@ -97,7 +99,8 @@ async function main() {
|
|
|
97
99
|
const args = argv.slice(2);
|
|
98
100
|
let dir = args.find((a) => !a.startsWith('-'));
|
|
99
101
|
let mode = args.includes('--spa') ? 'spa' : args.includes('--ssr') ? 'ssr' : undefined;
|
|
100
|
-
|
|
102
|
+
let format = args.includes('--html') ? 'html' : args.includes('--jsx') ? 'jsx' : undefined;
|
|
103
|
+
const interactive = input.isTTY && (!dir || !mode || !format);
|
|
101
104
|
if (interactive) {
|
|
102
105
|
output.write(`\n${c.gray('┌')} ${gradient('◆')} ${c.bold(gradient('create-fluixi'))}\n${BAR}\n`);
|
|
103
106
|
if (!dir)
|
|
@@ -108,16 +111,24 @@ async function main() {
|
|
|
108
111
|
{ value: 'spa', label: 'SPA', hint: 'client-only · vite' },
|
|
109
112
|
]));
|
|
110
113
|
}
|
|
114
|
+
if (!format) {
|
|
115
|
+
format = (await askSelect('Template syntax', [
|
|
116
|
+
{ value: 'jsx', label: 'JSX', hint: 'default · .tsx · html`` works inline too' },
|
|
117
|
+
{ value: 'html', label: 'html``', hint: 'no JSX · plain .ts · @fluixi/ts-plugin' },
|
|
118
|
+
]));
|
|
119
|
+
}
|
|
111
120
|
}
|
|
112
121
|
dir = dir ?? 'fluixi-app';
|
|
113
122
|
mode = mode ?? 'ssr';
|
|
123
|
+
format = format ?? 'jsx';
|
|
114
124
|
const target = resolve(dir);
|
|
115
125
|
const name = basename(target);
|
|
116
126
|
if (existsSync(target)) {
|
|
117
127
|
output.write(`${c.gray('└')} ${c.red('✗')} "${c.bold(name)}" already exists — pick another name or remove it.\n\n`);
|
|
118
128
|
exit(1);
|
|
119
129
|
}
|
|
120
|
-
|
|
130
|
+
const template = `../template-${mode}${format === 'html' ? '-html' : ''}`;
|
|
131
|
+
cpSync(here(template), target, { recursive: true });
|
|
121
132
|
// npm strips a real .gitignore from published packages → restore it from _gitignore.
|
|
122
133
|
const gi = join(target, '_gitignore');
|
|
123
134
|
if (existsSync(gi))
|
|
@@ -127,7 +138,7 @@ async function main() {
|
|
|
127
138
|
pkg.name = name;
|
|
128
139
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
129
140
|
// Outro + next steps. The leading gutter only makes sense after the intro/prompts.
|
|
130
|
-
const tag = mode
|
|
141
|
+
const tag = `${c.cyan(mode.toUpperCase())} ${c.gray('·')} ${c.cyan(format === 'html' ? 'html``' : 'JSX')}`;
|
|
131
142
|
if (interactive)
|
|
132
143
|
output.write(`${BAR}\n`);
|
|
133
144
|
else
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-fluixi",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.7",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"description": "Scaffold a new Fluixi app — `npm create fluixi <dir>`.",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"bin": {
|
|
@@ -10,7 +11,10 @@
|
|
|
10
11
|
"dist",
|
|
11
12
|
"template-ssr",
|
|
12
13
|
"template-spa",
|
|
13
|
-
"
|
|
14
|
+
"template-ssr-html",
|
|
15
|
+
"template-spa-html",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
14
18
|
],
|
|
15
19
|
"publishConfig": {
|
|
16
20
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
"preview": "vite preview"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@fluixi/core": "
|
|
13
|
-
"@fluixi/jsx": "
|
|
14
|
-
"@fluixi/dom": "
|
|
15
|
-
"@fluixi/reactive": "
|
|
12
|
+
"@fluixi/core": "alpha",
|
|
13
|
+
"@fluixi/jsx": "alpha",
|
|
14
|
+
"@fluixi/dom": "alpha",
|
|
15
|
+
"@fluixi/reactive": "alpha"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@fluixi/
|
|
18
|
+
"@fluixi/ts-plugin": "alpha",
|
|
19
|
+
"@fluixi/vite-plugin": "alpha",
|
|
19
20
|
"vite": "^5.0.0"
|
|
20
21
|
}
|
|
21
22
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
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>Fluixi App</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fluixi-app",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@fluixi/core": "alpha",
|
|
13
|
+
"@fluixi/dom": "alpha",
|
|
14
|
+
"@fluixi/reactive": "alpha"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@fluixi/ts-plugin": "alpha",
|
|
18
|
+
"@fluixi/vite-plugin": "alpha",
|
|
19
|
+
"vite": "^5.0.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createSignal, html } from '@fluixi/core';
|
|
2
|
+
|
|
3
|
+
export function App() {
|
|
4
|
+
const [count, setCount] = createSignal(0);
|
|
5
|
+
return html`
|
|
6
|
+
<div class="app">
|
|
7
|
+
<h1>Fluixi</h1>
|
|
8
|
+
<button @click=${() => setCount(count() - 1)}>-</button>
|
|
9
|
+
<span>${count()}</span>
|
|
10
|
+
<button @click=${() => setCount(count() + 1)}>+</button>
|
|
11
|
+
</div>
|
|
12
|
+
`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2021",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": false,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"types": ["vite/client"],
|
|
10
|
+
"plugins": [{ "name": "@fluixi/ts-plugin" }]
|
|
11
|
+
},
|
|
12
|
+
"include": ["src", "vite.config.ts"]
|
|
13
|
+
}
|
|
@@ -9,15 +9,16 @@
|
|
|
9
9
|
"start": "fluixi start"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@fluixi/cli": "
|
|
13
|
-
"@fluixi/start": "
|
|
14
|
-
"@fluixi/core": "
|
|
15
|
-
"@fluixi/jsx": "
|
|
16
|
-
"@fluixi/server": "
|
|
17
|
-
"@fluixi/reactive": "
|
|
18
|
-
"@fluixi/dom": "
|
|
12
|
+
"@fluixi/cli": "alpha",
|
|
13
|
+
"@fluixi/start": "alpha",
|
|
14
|
+
"@fluixi/core": "alpha",
|
|
15
|
+
"@fluixi/jsx": "alpha",
|
|
16
|
+
"@fluixi/server": "alpha",
|
|
17
|
+
"@fluixi/reactive": "alpha",
|
|
18
|
+
"@fluixi/dom": "alpha"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
+
"@fluixi/ts-plugin": "alpha",
|
|
21
22
|
"@types/node": "^20.0.0",
|
|
22
23
|
"vite": "^5.0.0"
|
|
23
24
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Fluixi app
|
|
2
|
+
|
|
3
|
+
Scaffolded with `npm create fluixi`. SSR + file-based routing + hydration via
|
|
4
|
+
[`@fluixi/start`](https://www.npmjs.com/package/@fluixi/start).
|
|
5
|
+
|
|
6
|
+
This variant is authored with `` html`` `` templates (no JSX), so the sources are plain
|
|
7
|
+
`.ts` files and `tsconfig.json` doesn't configure `jsx`. `@fluixi/ts-plugin` gives the
|
|
8
|
+
templates full TypeScript support (hover, completion, type-checking) in your editor.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm install
|
|
12
|
+
pnpm dev # dev server (SSR + HMR)
|
|
13
|
+
pnpm build # production build
|
|
14
|
+
pnpm start # serve the production build
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- Routes live in `src/routes/` (file-based).
|
|
18
|
+
- `src/app.ts` is the shell layout; `src/entry-{server,client}.ts` are the SSR/hydration entries.
|
|
19
|
+
- Config in `fluixi.config.ts`.
|
|
@@ -0,0 +1,12 @@
|
|
|
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>Fluixi Start Example</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/entry-client.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fluixi-app",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "fluixi dev",
|
|
8
|
+
"build": "fluixi build",
|
|
9
|
+
"start": "fluixi start"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@fluixi/cli": "alpha",
|
|
13
|
+
"@fluixi/start": "alpha",
|
|
14
|
+
"@fluixi/core": "alpha",
|
|
15
|
+
"@fluixi/server": "alpha",
|
|
16
|
+
"@fluixi/reactive": "alpha",
|
|
17
|
+
"@fluixi/dom": "alpha"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@fluixi/ts-plugin": "alpha",
|
|
21
|
+
"@types/node": "^20.0.0",
|
|
22
|
+
"vite": "^5.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Router, Outlet, createMemoryHistory } from '@fluixi/start/router';
|
|
2
|
+
import { Suspense } from '@fluixi/core';
|
|
3
|
+
import { html } from '@fluixi/start';
|
|
4
|
+
import { routes } from 'virtual:fluixi-routes';
|
|
5
|
+
|
|
6
|
+
// Shell layout around the routed page. File-routes (src/routes/*) are lazy, so the
|
|
7
|
+
// <Outlet/> sits under <Suspense> — async SSR resolves their content server-side.
|
|
8
|
+
// Components are interpolated as `<${Comp}/>` so TypeScript sees them referenced.
|
|
9
|
+
function RootLayout() {
|
|
10
|
+
return html`
|
|
11
|
+
<div class="app">
|
|
12
|
+
<nav>
|
|
13
|
+
<a href="/">home</a> · <a href="/about">about</a>
|
|
14
|
+
</nav>
|
|
15
|
+
<main>
|
|
16
|
+
<${Suspense} fallback=${html`<p>loading…</p>`}>
|
|
17
|
+
<${Outlet} />
|
|
18
|
+
</${Suspense}>
|
|
19
|
+
</main>
|
|
20
|
+
</div>
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Exported so entry-client can hand it to startClient — it preloads the current route's
|
|
25
|
+
// lazy chunk before hydrating, so the server-rendered DOM is adopted in place (flash-free).
|
|
26
|
+
export const appRoutes = [{ path: '/', component: RootLayout, children: routes as any }];
|
|
27
|
+
|
|
28
|
+
export default function App(props?: { url?: string }) {
|
|
29
|
+
// On the server, match the request URL (memory history); on the client, default
|
|
30
|
+
// to browser history.
|
|
31
|
+
const history =
|
|
32
|
+
typeof window === 'undefined' ? createMemoryHistory(props?.url ?? '/') : undefined;
|
|
33
|
+
return html`<${Router} routes=${appRoutes as any} history=${history as any} />`;
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Client entry — hydrates the server-rendered markup. The app HTML is injected into
|
|
2
|
+
// `#root` (the @fluixi/start mountId), so hydrate that container.
|
|
3
|
+
import { startClient } from '@fluixi/core/client';
|
|
4
|
+
import App, { appRoutes } from './app.js';
|
|
5
|
+
|
|
6
|
+
// Passing the route tree lets startClient preload the matched route's lazy chunk before
|
|
7
|
+
// hydrating — the lazy file-route then resolves synchronously and adopts the server DOM in
|
|
8
|
+
// place (no first-paint flash, no duplicate). It also enables auto-hydration of the
|
|
9
|
+
// server-rendered root.
|
|
10
|
+
startClient(App, { root: '#root', routes: appRoutes });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// SSR entry — @fluixi/start's dev/build/start load this. It calls `renderStream`
|
|
2
|
+
// (streaming: head flushed first, body streamed) when present, else `render` (string).
|
|
3
|
+
import App from './app.js';
|
|
4
|
+
import { renderRequestAsync, renderToBodyStream } from '@fluixi/server';
|
|
5
|
+
|
|
6
|
+
export function render(url: string): Promise<string> {
|
|
7
|
+
return renderRequestAsync(App, { url });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function renderStream(url: string): ReadableStream<Uint8Array> {
|
|
11
|
+
return renderToBodyStream(App, { url });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Server-function RPC dispatch — re-exported so the dispatcher shares the registry that
|
|
15
|
+
// this app's `"use server"` modules populate. @fluixi/start routes RPC calls here.
|
|
16
|
+
import 'virtual:fluixi-server-fns'; // registers every "use server" module (incl. lazy routes)
|
|
17
|
+
export { isServerFnRequest, handleServerFn } from '@fluixi/start/server-fn';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Auto-generated by @fluixi/core fluixiRoutesPlugin — do not edit manually
|
|
2
|
+
declare module 'virtual:fluixi-routes' {
|
|
3
|
+
import type { RouteDefinition } from '@fluixi/core/router-next';
|
|
4
|
+
export const routes: RouteDefinition[];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
declare module 'virtual:fluixi-server-fns';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createResource } from '@fluixi/reactive/signal';
|
|
2
|
+
import { seo } from '@fluixi/start/head';
|
|
3
|
+
import { html } from '@fluixi/start';
|
|
4
|
+
|
|
5
|
+
const tick = <T,>(v: T, ms = 30) => new Promise<T>((r) => setTimeout(() => r(v), ms));
|
|
6
|
+
|
|
7
|
+
// A data-gated route, like a real page: the resource is awaited during SSR so the
|
|
8
|
+
// content (not the spinner) is server-rendered, then hydrated.
|
|
9
|
+
export default function Home() {
|
|
10
|
+
const [data] = createResource(() => tick('Hello from Fluixi Start 👋'));
|
|
11
|
+
|
|
12
|
+
// Per-route document head — rendered into <head> on the server (great for crawlers/links)
|
|
13
|
+
// and kept reactive on the client. seo() is optional; call it where you want metadata.
|
|
14
|
+
seo({
|
|
15
|
+
title: 'Fluixi Start',
|
|
16
|
+
description: 'A server-rendered Fluixi app, scaffolded by the fluixi CLI.',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return html`
|
|
20
|
+
<section class="home">
|
|
21
|
+
<h1>${data()}</h1>
|
|
22
|
+
<p>Server-rendered through the fluixi CLI, then hydrated.</p>
|
|
23
|
+
</section>
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2021",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": false,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"types": ["node", "vite/client"],
|
|
10
|
+
"plugins": [{ "name": "@fluixi/ts-plugin" }]
|
|
11
|
+
},
|
|
12
|
+
"include": ["src", "fluixi.config.ts"]
|
|
13
|
+
}
|