i spent the last year just spinning
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# Adapters
|
||||
|
||||
`ViewEngineAdapter` is the rendering boundary.
|
||||
|
||||
## Built-in
|
||||
|
||||
- `ViewEngineType.THYMELEAF`
|
||||
|
||||
## Custom adapter
|
||||
|
||||
```java
|
||||
public final class MyAdapter implements ViewEngineAdapter {
|
||||
@Override
|
||||
public EngineCapabilities capabilities() {
|
||||
return EngineCapabilities.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderOutput render(ViewTarget target,
|
||||
Map<String, Object> model,
|
||||
Request req,
|
||||
Response res) {
|
||||
String body = "...";
|
||||
return RenderOutput.html(body);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Adapter instances must be thread-safe after construction.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Architecture
|
||||
|
||||
`flash-ext-view` runs in two layers:
|
||||
|
||||
1. **Boot-time**
|
||||
- `ViewExtension` registers `ViewRuntime` and an annotation processor.
|
||||
- `ViewTargetResolver` validates handlers and maps annotations to `ViewTarget`.
|
||||
- Resolved targets are cached per handler class.
|
||||
|
||||
2. **Request-time**
|
||||
- Handler builds local `ViewModel`.
|
||||
- `ViewRuntime` injects extension globals under reserved `global` namespace, then merges local model.
|
||||
- `ViewEngineAdapter` renders `RenderOutput`.
|
||||
|
||||
## Valid Handler Contract
|
||||
|
||||
- Must extend `ViewHandler`.
|
||||
- Must have route annotation (`@Route`, `@GET`, `@POST`, ...).
|
||||
- Must declare exactly one view annotation:
|
||||
- `@Page`
|
||||
- `@Partial`
|
||||
|
||||
Invalid configurations fail fast at startup.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Handlers
|
||||
|
||||
Use `ViewHandler` for class-based SSR routes.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
- `onViewInit()` runs once at boot.
|
||||
- `render(...)` runs per request.
|
||||
|
||||
Use `onViewInit()` to cache dependencies via `require(...)`.
|
||||
|
||||
## Example
|
||||
|
||||
```java
|
||||
@GET("/dashboard")
|
||||
@Page("pages/dashboard")
|
||||
public final class DashboardPage extends ViewHandler {
|
||||
|
||||
private DashboardService service;
|
||||
|
||||
@Override
|
||||
protected void onViewInit() {
|
||||
service = require(DashboardService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewModel render(dev.relism.models.Request req) {
|
||||
return ViewModel.empty()
|
||||
.with("title", "Dashboard")
|
||||
.with("stats", service.stats());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use `render(Request, Response)` only when you need response access while building the model.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Migration from Legacy View API
|
||||
|
||||
Legacy API (`@View`, `ViewEngine`, `Renderer`, `Template`) has been removed.
|
||||
|
||||
## Replace annotations
|
||||
|
||||
- `@View("page")` -> `@Page("page")`
|
||||
- `@View(value = "x", fragment = true)` -> `@Partial(template = "x")`
|
||||
|
||||
`@Page` and `@Partial` must be declared on classes extending `ViewHandler`.
|
||||
|
||||
## Replace handler return contract
|
||||
|
||||
Before (legacy):
|
||||
|
||||
```java
|
||||
public Object handle(Request req, Response res) {
|
||||
return Map.of("name", "Flash");
|
||||
}
|
||||
```
|
||||
|
||||
Now:
|
||||
|
||||
```java
|
||||
public ViewModel render(Request req) {
|
||||
return ViewModel.of("name", "Flash");
|
||||
}
|
||||
```
|
||||
|
||||
## Replace engine integration
|
||||
|
||||
- Implement `ViewEngineAdapter` directly.
|
||||
- Or use `ViewEngineType.THYMELEAF`.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Model and Globals
|
||||
|
||||
`ViewModel` is the per-request model builder.
|
||||
|
||||
## Merge Order
|
||||
|
||||
Runtime merge order is:
|
||||
|
||||
1. all extension globals under reserved `global` namespace
|
||||
2. local handler model
|
||||
|
||||
Handlers cannot set top-level `global`; runtime throws fail-fast to prevent namespace collisions.
|
||||
|
||||
## Globals
|
||||
|
||||
Register globals on extension setup:
|
||||
|
||||
```java
|
||||
new ViewExtension(ViewEngineType.THYMELEAF)
|
||||
.addGlobal("appName", req -> "Flash")
|
||||
.addGlobal("path", req -> req.path());
|
||||
```
|
||||
|
||||
Template usage:
|
||||
|
||||
```html
|
||||
<span th:text="${global.appName}"></span>
|
||||
<span th:text="${global.path}"></span>
|
||||
```
|
||||
|
||||
Keep globals cheap: no blocking I/O or heavy allocations.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Partials
|
||||
|
||||
Use `@Partial` for fragment responses.
|
||||
|
||||
```java
|
||||
@GET("/users/table")
|
||||
@Partial(template = "fragments/users", slot = "rows")
|
||||
public final class UsersRows extends ViewHandler {
|
||||
@Override
|
||||
public ViewModel render(dev.relism.models.Request req) {
|
||||
return ViewModel.of("users", List.of());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If slot is empty, the adapter default slot is used.
|
||||
|
||||
If a slot is specified but the adapter does not support slot selection,
|
||||
startup fails with a clear error.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Performance
|
||||
|
||||
`flash-ext-view` is optimized for low overhead on request path.
|
||||
|
||||
## Current runtime choices
|
||||
|
||||
- Handler view metadata resolved once and cached.
|
||||
- Global/local model merge done in a single pass.
|
||||
- No legacy rendering branches in runtime pipeline.
|
||||
|
||||
## Best practices
|
||||
|
||||
- Cache services in `onViewInit()`.
|
||||
- Keep `addGlobal(...)` resolvers cheap and side-effect free.
|
||||
- Build only the model fields needed by template.
|
||||
- Avoid blocking I/O in `render(...)`; delegate to precomputed service data when possible.
|
||||
Reference in New Issue
Block a user