@samveys/template
Dari packages/template/README.md di repository framework.
Halaman ini belum diterjemahkan. Kamu sedang membaca versi bahasa Inggris.
Server-side templates (.tpl.html) for @samveys/view. No runtime dependencies.
Each template is compiled to an ES module (a JavaScript string written to storage/views/<hash>.mjs) and loaded with import().
The framework never uses eval or new Function, and a template can only read the data it was rendered with.
Set up
import { http } from '@samveys/http'
import { template } from '@samveys/template'
import { view } from '@samveys/view'
export const plugins = [http(), view(), template({ viewsDir: 'resources/views', cacheDir: 'storage/views' })]Names map to files: users.index → resources/views/users/index.tpl.html.
With app.env === 'local' a changed source is recompiled on the next render; otherwise compiled modules are kept.
Syntax
| Syntax | Result |
|---|---|
{{ expr }} | Value, HTML-escaped (& < > " ' \``). nullandundefined` print nothing |
{!! expr !!} | Value, raw |
{{-- comment --}} | Nothing |
@@if | A literal @if |
@if(a) @elseif(b) @else @endif | Conditions |
@each(item in items) … @empty … @endeach | Loop; $loop has index, iteration, first, last, count, remaining, parent |
@layout('layouts.base') | Wrap the template in a layout |
@section('name') … @endsection | Content for a layout |
@yield('name', default) | Print a section (or the escaped default) |
@include('partial', { a: 1 }) | Another template with the current data plus { a: 1 } |
<x-card title="Hi" :count="n + 1">…</x-card> | Component components/card.tpl.html |
@csrf | <input type="hidden" name="_csrf" value="…"> (needs @samveys/security and its csrf middleware on the route) |
@vite('resources/app.ts') | Asset tags (needs @samveys/vite) |
@inertia, @inertiaHead | Inertia root element and SSR head (used by @samveys/inertia) |
{{-- resources/views/layouts/base.tpl.html --}}
<title>@yield('title', 'Samveys')</title>
<main>@yield('content')</main>{{-- resources/views/users/index.tpl.html --}}
@layout('layouts.base')
@section('content')
<h1>{{ title }}</h1>
@each(user in users)
<x-user-card :user="user" />
@empty
<p>No users yet.</p>
@endeach
@endsectionA component receives its attributes as variables (title, count), the default slot as $slot and named slots (<x-slot:footer>) as $slots.footer.
It does not see the data of the page that uses it.
Expressions
Expressions are a safe subset that is parsed, not evaluated: identifiers, a.b, a[b], a?.b, calls (name.toUpperCase(), fmt(price)),
literals (strings, numbers, true, false, null, arrays, objects), + - * / %, comparisons, && || ??, !, and a ? b : c.
Assignments, new, functions, template literals and statements are errors.
Member names constructor, prototype and __* are rejected when the template is compiled, and again at run time for computed keys:
import { compileTemplate } from '@samveys/template'
// Throws SVS_E109: "views/evil.tpl.html:2: Access to "constructor" is not allowed in templates"
compileTemplate('line one\n{{ constructor.constructor("x")() }}', 'views/evil.tpl.html')Errors are SVS_E109 and name the file and line.
Render
import type { Router } from '@samveys/http'
export function routes(router: Router): void {
router.get('/', (ctx) => ctx.view.render('home', { title: 'Home' }))
}Use createTemplateEngine({ viewsDir, cacheDir, watch }) to render without the plugin (scripts, mail bodies).
import { createTemplateEngine } from '@samveys/template'
const engine = createTemplateEngine({ viewsDir: 'resources/views', cacheDir: 'storage/views' })
export const html = (): Promise<string> => engine.render('home', { title: '<Home>' })