jte (Java Template Engine)

repository·main·Indexed 22 days ago

https://github.com/casid/jte

A secure, lightweight, and high-performance template engine for Java and Kotlin that uses existing language syntax instead of a custom expression language. It features type safety, context-sensitive HTML escaping at compile time, and binary rendering for maximum throughput. jte provides integrations for Spring Boot (versions 2, 3, and 4), Javalin, Ktor, Micronaut, Quarkus, and others, along with a Gradle plugin for precompilation and source generation.

Tokens
19.4K
Snippets
63
Records
87
Agent score
77%

What's inside jte

  1. Overview of jte: Java Template Engine

    main

    jte (Java Template Engine) is a secure, lightweight template engine designed for Java and Kotlin. It focuses on simplicity by using existing language features for expressions rather than introducing a new expression language.

    Key characteristics include:

    • Type Safety: Uses plain Java or Kotlin for expressions, providing a productive experience similar to standard application code.
    • Security: Provides context-sensitive HTML escaping at compile time.
    • Performance: Designed for high-speed execution.
    • Developer Experience: Supports hot reloading of templates during development and offers an IntelliJ plugin for completion and refactoring support.
  2. Understand the output of the NullMarked extension

    main

    When the build runs, the extension generates a package-info.java file for every package that contains generated classes.

    Important Note: If a package-info.java file already exists in a package directory, the extension will leave it unchanged and will not overwrite it.

    @NullMarked
    package gg.jte.generated.precompiled;
    
    import org.jspecify.annotations.NullMarked;
  3. Understand `compileArgs` defaults in the jte Maven plugin

    main

    The compileArgs parameter in the precompile goal automatically attempts to align with your project's Java version settings in pom.xml:

    1. If <maven.compiler.release> is configured (e.g., <maven.compiler.release>21</maven.compiler.release>), compileArgs defaults to --release 21.
    2. If release is absent, but <maven.compiler.source> and <maven.compiler.target> are configured (e.g., both set to 21), compileArgs defaults to --source 21 --target 21.
    3. If neither is configured, the default is empty.

    Using release is preferred as it ensures the correct API versions are used.

  4. Choose between StaticTemplates and DynamicTemplates

    main

    The jte-models extension provides two implementations of the Templates interface, optimized for different environments:

    ImplementationBest ForBehavior
    StaticTemplatesProductionCalls generated render classes directly with no reflection. Requires precompiled templates.
    DynamicTemplatesDevelopmentDelegates to a TemplateEngine, allowing for hot-reloading of templates.

    Note: Even when using DynamicTemplates, you must rerun the build if the @param definitions of a template change.

  5. How HTML escaping is applied in different contexts

    main

    When using ContentType.Html, jte applies context-sensitive escaping automatically:

    • HTML tag bodies: Uses gg.jte.html.escape.Escape.htmlContent. Escapes characters like < and >.
    • HTML attributes: Uses gg.jte.html.escape.Escape.htmlAttribute. Escapes quotes (e.g., " becomes &#34;) to prevent attribute escaping.
    • JavaScript attributes: For attributes starting with on (e.g., onclick), jte uses gg.jte.html.escape.Escape.javaScriptAttribute to escape characters like single quotes to prevent breaking out of JS strings.

    Example of attribute escaping:

    <div data-title="Hello ${userName}"></div>
    <!-- If userName is "><script>alert('xss')</script>, it escapes the quote to prevent breaking the tag -->
  6. Precompile vs. Generate templates

    main

    JTE offers two ways to handle templates during the build process to optimize production performance:

    • Generate: JTE source code is generated from templates before application code is compiled. The generated classes are added to the application source path and compiled alongside your application code, ending up in the same .jar. Use this if you want a self-contained JAR but don't mind the compilation lifecycle.
    • Precompile: JTE classes are generated and compiled after application classes. The resulting .class files can either be loaded from a specific directory on your server or bundled into your application .jar. This is the fastest method for production as it avoids runtime compilation and can work without a JDK on the production server.
  7. How Smart Attributes work in jte

    main

    jte evaluates expressions within HTML attributes to optimize the output.

    1. Omission of null/false: Attributes with a single output that evaluates to #!java null or #!java false are omitted from the rendered HTML.
    2. Boolean attributes: For boolean attributes (like selected), provide a boolean expression. If it evaluates to false, the attribute is not rendered. If true, the attribute is rendered without a value.

    Example of boolean attribute handling:

    <option value="saab" selected="${true}">Saab</option>
    <option value="opel" selected="${false}">Opel</option>

    Results in:

    <option value="saab" selected>Saab</option>
    <option value="opel">Opel</option>
    <span data-title="${null}">Info</span>
    <!-- Renders as: <span>Info</span> -->
  8. Pass template blocks using gg.jte.Content

    main

    The gg.jte.Content type allows you to pass chunks of template code as parameters to other templates (similar to lambdas). This is ideal for layout patterns.

    Use the @ followed by two backticks (`@```) shorthand to define a content block inline when calling a template.

    @* Passing content to a layout template *@
    @template.layout.page(
        page = myPage,
        content = @`
            <p>This is the main content.</p>
        `,
        footer = @`
            <p>Copyright 2024</p>
        `
    )
  9. Implement a bridging tag for incremental migration

    main

    When migrating a large project, you may want to convert JSP tags to jte incrementally. The converter uses a "bridging tag" to allow jte templates to be embedded within existing JSP files.

    If the converter encounters an un-converted tag (e.g., <my:example/>), it will replace it in the JSP with the bridging tag syntax: <my:jte jte="path/to/template.jte" param1="value" />.

    To make this work, you must:

    1. Implement a custom JSP Tag class (extending BodyTagSupport and DynamicAttributes) that uses a TemplateEngine to render the specified jte template.
    2. Handle parameter conversion: Ensure that parameters passed from JSP (like Strings) are correctly converted to the types expected by the jte template (like Content or Enum).
    3. Implement TemplateOutput: Create a class implementing gg.jte.TemplateOutput (e.g., JspWriterOutput) that writes the rendered jte content directly to the javax.servlet.jsp.JspWriter.
    4. Register the tag in your TLD file so the application recognizes the bridging tag name.

    This allows your application to remain fully functional even when only a portion of the templates have been migrated to jte.

    <!-- Example TLD registration for a bridging tag named 'jte' -->
    <tag>
       <name>jte</name>
       <tag-class>my.JteTag</tag-class>
       <body-content>scriptless</body-content>
       <attribute>
           <name>jte</name>
           <required>true</required>
           <rtexprvalue>true</rtexprvalue>
       </attribute>
       <dynamic-attributes>true</dynamic-attributes>
    </tag>