loly 0.1.0
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/README.md +374 -0
- package/bin/loly.js +3015 -0
- package/dist/bootstrap-DgWagZ79.d.ts +16 -0
- package/dist/client.d.ts +101 -0
- package/dist/client.js +727 -0
- package/dist/client.js.map +1 -0
- package/dist/components.d.ts +39 -0
- package/dist/components.js +147 -0
- package/dist/components.js.map +1 -0
- package/dist/index.d.ts +458 -0
- package/dist/index.js +3826 -0
- package/dist/index.js.map +1 -0
- package/package.json +108 -0
package/README.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# loly
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
Modern full-stack web framework with streaming SSR, async server components, islands architecture, and fine-grained reactivity.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install loly
|
|
12
|
+
# or
|
|
13
|
+
yarn add loly
|
|
14
|
+
# or
|
|
15
|
+
pnpm add loly
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
Create a new project structure:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
my-app/
|
|
24
|
+
├── src/
|
|
25
|
+
│ └── app/
|
|
26
|
+
│ ├── layout.tsx
|
|
27
|
+
│ └── page.tsx
|
|
28
|
+
└── package.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**src/app/layout.tsx:**
|
|
32
|
+
```tsx
|
|
33
|
+
import type { LayoutProps } from "loly";
|
|
34
|
+
|
|
35
|
+
export default function Layout({ children }: LayoutProps) {
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
<nav>Navigation</nav>
|
|
39
|
+
<main>{children}</main>
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**src/app/page.tsx:**
|
|
46
|
+
```tsx
|
|
47
|
+
export default function Home() {
|
|
48
|
+
return <h1>Hello, loly!</h1>;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Start the development server:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx loly dev
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Core Concepts
|
|
59
|
+
|
|
60
|
+
### File-based Routing
|
|
61
|
+
|
|
62
|
+
loly uses a file-based routing system similar to Next.js. Routes are defined by the file structure in `src/app/`:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
src/app/
|
|
66
|
+
├── layout.tsx # Root layout
|
|
67
|
+
├── page.tsx # Home page (/)
|
|
68
|
+
├── about/
|
|
69
|
+
│ └── page.tsx # /about
|
|
70
|
+
└── users/
|
|
71
|
+
└── [id]/
|
|
72
|
+
└── page.tsx # /users/:id
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Dynamic routes:**
|
|
76
|
+
- `[id]` - Single dynamic segment
|
|
77
|
+
- `[...slug]` - Catch-all route
|
|
78
|
+
- `[[...slug]]` - Optional catch-all
|
|
79
|
+
- `(group)` - Route groups (not in URL)
|
|
80
|
+
|
|
81
|
+
**Route files:**
|
|
82
|
+
- `page.tsx` - Page component
|
|
83
|
+
- `layout.tsx` - Layout component (wraps child routes)
|
|
84
|
+
- `route.ts` - API route handler
|
|
85
|
+
|
|
86
|
+
### SSR and Streaming
|
|
87
|
+
|
|
88
|
+
loly supports two rendering strategies:
|
|
89
|
+
|
|
90
|
+
**SSR Strategy** - Full page streaming:
|
|
91
|
+
```tsx
|
|
92
|
+
import { renderPageStream } from "loly";
|
|
93
|
+
|
|
94
|
+
const result = await renderPageStream({
|
|
95
|
+
pathname: "/",
|
|
96
|
+
searchParams: {},
|
|
97
|
+
params: {},
|
|
98
|
+
}, routeConfig, appDir);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**RSC Strategy** - SPA navigation with partial updates:
|
|
102
|
+
```tsx
|
|
103
|
+
import { renderPageContent } from "loly";
|
|
104
|
+
|
|
105
|
+
const result = await renderPageContent({
|
|
106
|
+
pathname: "/",
|
|
107
|
+
searchParams: {},
|
|
108
|
+
params: {},
|
|
109
|
+
}, routeConfig, appDir);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Islands Architecture
|
|
113
|
+
|
|
114
|
+
Use islands to selectively hydrate components on the client:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import { defineIsland, state } from "loly-jsx";
|
|
118
|
+
|
|
119
|
+
const CounterIsland = defineIsland("Counter", function Counter() {
|
|
120
|
+
const [count, setCount] = state(0);
|
|
121
|
+
return <button onClick={() => setCount(count + 1)}>{count}</button>;
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// In your page
|
|
125
|
+
export default function Page() {
|
|
126
|
+
return (
|
|
127
|
+
<div>
|
|
128
|
+
<h1>Server-rendered content</h1>
|
|
129
|
+
<CounterIsland client:load />
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Features
|
|
136
|
+
|
|
137
|
+
### Development Server
|
|
138
|
+
|
|
139
|
+
Start a development server with hot reload:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
loly dev [dir] [port]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The dev server:
|
|
146
|
+
- Watches for file changes
|
|
147
|
+
- Supports hot module replacement
|
|
148
|
+
- Provides error overlays
|
|
149
|
+
- Handles TypeScript/JSX automatically
|
|
150
|
+
|
|
151
|
+
### Build System
|
|
152
|
+
|
|
153
|
+
Build your application for production:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
loly build [dir] [--out-dir=<path>] [--dev]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The build process:
|
|
160
|
+
- Scans routes from `src/app/`
|
|
161
|
+
- Builds client bundle with rspack
|
|
162
|
+
- Builds server files with esbuild
|
|
163
|
+
- Generates route manifests
|
|
164
|
+
- Optimizes assets
|
|
165
|
+
|
|
166
|
+
### API Routes
|
|
167
|
+
|
|
168
|
+
Create API endpoints using `route.ts` files:
|
|
169
|
+
|
|
170
|
+
**src/app/api/users/route.ts:**
|
|
171
|
+
```tsx
|
|
172
|
+
import type { Request, Response } from "express";
|
|
173
|
+
|
|
174
|
+
export async function GET(req: Request, res: Response) {
|
|
175
|
+
const users = await fetchUsers();
|
|
176
|
+
return res.json(users);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function POST(req: Request, res: Response) {
|
|
180
|
+
const data = await req.json();
|
|
181
|
+
const user = await createUser(data);
|
|
182
|
+
return res.json(user);
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Routes support: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`
|
|
187
|
+
|
|
188
|
+
### Image Optimization
|
|
189
|
+
|
|
190
|
+
Use the `Image` component for optimized images:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import { Image } from "loly";
|
|
194
|
+
|
|
195
|
+
export default function Page() {
|
|
196
|
+
return (
|
|
197
|
+
<Image
|
|
198
|
+
src="/hero.jpg"
|
|
199
|
+
alt="Hero image"
|
|
200
|
+
width={800}
|
|
201
|
+
height={600}
|
|
202
|
+
quality={90}
|
|
203
|
+
/>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Features:
|
|
209
|
+
- Automatic format optimization (WebP, AVIF)
|
|
210
|
+
- Responsive images
|
|
211
|
+
- Lazy loading
|
|
212
|
+
- Image caching
|
|
213
|
+
|
|
214
|
+
### Client Bootstrap
|
|
215
|
+
|
|
216
|
+
Bootstrap your client application:
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
import { bootstrapClient } from "loly/client";
|
|
220
|
+
|
|
221
|
+
bootstrapClient({
|
|
222
|
+
routes: [
|
|
223
|
+
{ path: "/", component: () => import("./app/page") },
|
|
224
|
+
{ path: "/about", component: () => import("./app/about/page") },
|
|
225
|
+
],
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Project Structure
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
my-app/
|
|
233
|
+
├── src/
|
|
234
|
+
│ ├── app/ # App directory (routes)
|
|
235
|
+
│ │ ├── layout.tsx # Root layout
|
|
236
|
+
│ │ ├── page.tsx # Home page
|
|
237
|
+
│ │ └── [routes]/ # Route segments
|
|
238
|
+
│ ├── components/ # Shared components
|
|
239
|
+
│ └── globals.css # Global styles
|
|
240
|
+
├── public/ # Static assets
|
|
241
|
+
├── .loly/ # Build output (generated)
|
|
242
|
+
│ ├── dist/ # Production build
|
|
243
|
+
│ └── client/ # Client bundle
|
|
244
|
+
└── package.json
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## API Reference
|
|
248
|
+
|
|
249
|
+
### Server
|
|
250
|
+
|
|
251
|
+
- `startDevServer(options)` - Start development server
|
|
252
|
+
- `startProdServer(options)` - Start production server
|
|
253
|
+
- `renderPageStream(context, config, appDir)` - Render page with streaming
|
|
254
|
+
- `renderPageContent(context, config, appDir)` - Render page content (RSC)
|
|
255
|
+
- `BaseServer` - Base server class for custom implementations
|
|
256
|
+
|
|
257
|
+
### Router
|
|
258
|
+
|
|
259
|
+
- `scanRoutes(appDir)` - Scan routes from app directory
|
|
260
|
+
- `matchRoute(pathname, config)` - Match route by pathname
|
|
261
|
+
- `extractParams(pathname, route)` - Extract route parameters
|
|
262
|
+
|
|
263
|
+
### Build
|
|
264
|
+
|
|
265
|
+
- `buildProject(options)` - Build project for production
|
|
266
|
+
- `generateRoutesManifest(config, projectDir, appDir)` - Generate route manifest
|
|
267
|
+
- `generateBootstrap(projectDir, routes)` - Generate client bootstrap
|
|
268
|
+
|
|
269
|
+
### Client
|
|
270
|
+
|
|
271
|
+
- `bootstrapClient(options)` - Bootstrap client application
|
|
272
|
+
- `navigate(to, options?)` - Navigate programmatically
|
|
273
|
+
|
|
274
|
+
### Components
|
|
275
|
+
|
|
276
|
+
- `Image` - Optimized image component
|
|
277
|
+
|
|
278
|
+
### Utilities
|
|
279
|
+
|
|
280
|
+
- `getEnv(key)` - Get environment variable
|
|
281
|
+
- `getPublicEnv()` - Get public environment variables
|
|
282
|
+
- `generatePublicEnvScript()` - Generate script tag for public env
|
|
283
|
+
|
|
284
|
+
## CLI Commands
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Development
|
|
288
|
+
loly dev [dir] [port]
|
|
289
|
+
|
|
290
|
+
# Build
|
|
291
|
+
loly build [dir] [--out-dir=<path>] [--dev]
|
|
292
|
+
|
|
293
|
+
# Production server
|
|
294
|
+
loly start [dir] [port]
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Environment Variables
|
|
298
|
+
|
|
299
|
+
Create a `.env` file in your project root:
|
|
300
|
+
|
|
301
|
+
```env
|
|
302
|
+
# Public variables (exposed to client)
|
|
303
|
+
# Use any prefix you want, e.g. LOLY_PUBLIC_API_URL or VITE_API_URL
|
|
304
|
+
LOLY_PUBLIC_API_URL=https://api.example.com
|
|
305
|
+
|
|
306
|
+
# Private variables (server only)
|
|
307
|
+
DATABASE_URL=postgresql://...
|
|
308
|
+
API_SECRET=secret
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Access in your code:
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
import { getEnv, getPublicEnv } from "loly";
|
|
315
|
+
|
|
316
|
+
// Server only
|
|
317
|
+
const dbUrl = getEnv("DATABASE_URL");
|
|
318
|
+
|
|
319
|
+
// Public (available on client)
|
|
320
|
+
const apiUrl = getPublicEnv().PUBLIC_API_URL;
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Examples
|
|
324
|
+
|
|
325
|
+
### Page with Layout
|
|
326
|
+
|
|
327
|
+
**src/app/layout.tsx:**
|
|
328
|
+
```tsx
|
|
329
|
+
import type { LayoutProps } from "loly";
|
|
330
|
+
|
|
331
|
+
export default function Layout({ children }: LayoutProps) {
|
|
332
|
+
return (
|
|
333
|
+
<>
|
|
334
|
+
<nav>Navigation</nav>
|
|
335
|
+
<main>{children}</main>
|
|
336
|
+
</>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**src/app/page.tsx:**
|
|
342
|
+
```tsx
|
|
343
|
+
export default function Home() {
|
|
344
|
+
return <h1>Home Page</h1>;
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Dynamic Route
|
|
349
|
+
|
|
350
|
+
**src/app/users/[id]/page.tsx:**
|
|
351
|
+
```tsx
|
|
352
|
+
import type { PageProps } from "loly";
|
|
353
|
+
|
|
354
|
+
export default function UserPage({ params }: PageProps) {
|
|
355
|
+
return <h1>User {params.id}</h1>;
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### API Route
|
|
360
|
+
|
|
361
|
+
**src/app/api/hello/route.ts:**
|
|
362
|
+
```tsx
|
|
363
|
+
import type { Request, Response } from "express";
|
|
364
|
+
|
|
365
|
+
export async function GET(req: Request, res: Response) {
|
|
366
|
+
return res.json({ message: "Hello" });
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Links
|
|
371
|
+
|
|
372
|
+
- [Repository](https://github.com/MenvielleValen/loly)
|
|
373
|
+
- [Homepage](https://loly.dev)
|
|
374
|
+
|