jte is a secure and lightweight template engine for Java.

Overview

jtejte is a secure and lightweight template engine for Java. All jte templates are compiled to Java class files, meaning jte adds essentially zero overhead to your application. jte is designed to introduce as few new keywords as possible and builds upon existing Java features, so that it is very easy to reason about what a template does. The IntelliJ plugin offers full completion and refactoring support for Java parts as well as for jte keywords. Supports Java 8 or higher.

Build Status Coverage Status License Maven Central

Features

TLDR

jte is a lot of fun to work with! This is IntelliJ with the jte plugin installed:

jte in IntelliJ

5 minutes example

Here is a small jte template example.jte:

@import org.example.Page

@param Page page

<head>
    @if(page.getDescription() != null)
        <meta name="description" content="${page.getDescription()}">
    @endif
    <title>${page.getTitle()}</title>
</head>
<body>
    <h1>${page.getTitle()}</h1>
    <p>Welcome to my example page!</p>
</body>

So what is going on here?

  • @import directly translates to Java imports, in this case so that org.example.Page is known to the template.
  • @param Page page is the parameter that needs to be passed to this template.
  • @if/@endif is an if-block. The stuff inside the braces (page.getDescription() != null) is plain Java code. @JSP users: Yes, there is @elseif() and @else in jte ❤️ .
  • ${} writes to the underlying template output, as known from various other template engines.

To render this template, an instance of TemplateEngine is required. Typically you create it once per application (it is safe to share the engine between threads):

CodeResolver codeResolver = new DirectoryCodeResolver(Path.of("jte")); // This is the directory where your .jte files are located.
TemplateEngine templateEngine = TemplateEngine.create(codeResolver, ContentType.Html); // Two choices: Plain or Html

The content type passed to the engine determines how user output will be escaped. If you render HTML files, Html is highly recommended. This enables the engine to analyze HTML templates at compile time and perform context sensitive output escaping of user data, to prevent you from XSS attacks.

With the TemplateEngine ready, templates are rendered like this:

TemplateOutput output = new StringOutput();
templateEngine.render("example.jte", page, output);
System.out.println(output);

Besides StringOutput, there are several other TemplateOutput implementations you can use, or create your own if required.

example.jte works, but imagine you have more than one page. You would have to duplicate a lot of shared template code. Let's extract the shared code into a tag. Tags are template snippets that can be called by other templates.

All tags must be created in a directory called tag in your template root directory.

Let's move stuff from our example page to tag/page.jte:

@import org.example.Page
@import gg.jte.Content

@param Page page
@param Content content

<head>
    @if(page.getDescription() != null)
        <meta name="description" content="${page.getDescription()}">
    @endif
    <title>${page.getTitle()}</title>
</head>
<body>
    <h1>${page.getTitle()}</h1>
    ${content}
</body>

The @param Content content is a content block that can be provided by callers of the template. ${content} renders this content block. Let's refactor example.jte to use the new tag:

@import org.example.Page

@param Page page

@tag.page(page = page, content = @`
    <p>Welcome to my example page!</p>
`)

The shorthand to create content blocks within jte templates is an @followed by two backticks. For advanced stuff, you can even create Java methods that return custom Content implementation and call it from your template code!

Check out the syntax documentation, for a more comprehensive introduction.

Performance

By design, jte provides very fast output. This is a fork of mbosecke/template-benchmark with jte included, running on a MacBook Pro 2015 (single thread):

alt Template Benchmark

Note that the above is with ContentType.Plain, so no output escaping is done. This is basically what the other engines in the benchmark are set-up with. Well, except Thymeleaf I think. Since jte 0.8.0, you will want to render HTML pages with ContentType.Html, so that output is automatically escaped by the engine, depending on where in the HTML data is written to. With ContentType.Html, jte is still extremly fast, thanks to owasp-java-encoder:

alt Template Benchmark

High concurrency

This is a fork of mbosecke/template-benchmark with jte included, running on an AMD Ryzen 5950x:

alt Template Benchmark

For this benchmark, the amount of threads was manually set @Threads(32), to fully utilize all cores. jte has pretty much zero serialization bottlenecks and runs very concurrent on servers with a lot of CPU cores.

Getting started

jte is available on Maven Central:

Maven

<dependency>
    <groupId>gg.jte</groupId>
    <artifactId>jte</artifactId>
    <version>1.9.0</version>
</dependency>

Gradle

implementation group: 'gg.jte', name: 'jte', version: '1.9.0'

No further dependencies required! Check out the syntax documentation and start hacking :-)

Framework integration

Websites rendered with jte

Comments
  • Quarkus integration

    Quarkus integration

    @casid I'm creating this thread to track the integration (or example) with Quarkus as there may be more interested people in the topic.

    When we're finally able to integrate, I would suggest to possibly contribute the example to this repository so that this can be part of your pipeline + showing other people how to do it.

    Here's the repository where I'll be doing the tests (contributions are welcome): https://github.com/renannprado/quarkus-jte-example

    It would be very nice if I could get a bit of guidance from you to be able to integrate with Quarkus, but I could also seek for help in the Quarkus community if that's necessary.

    enhancement help wanted 
    opened by renannprado 38
  • Can't precompile KTE page (likely after upgrading Kotlin libs)

    Can't precompile KTE page (likely after upgrading Kotlin libs)

    with jte-kotlin-2.2.0 and kotlin-scipting-common-1.7.20

    > Failed to compile template, error at
      [java.lang.NoSuchMethodError: 'void kotlin.script.experimental.api.KotlinType.<init>(kotlin.reflect.KClass, boolean, int, kotlin.jvm.internal.DefaultConstructorMarker)'
      	at org.jetbrains.kotlin.scripting.definitions.ScriptCompilationConfigurationFromDefinition$1.invoke(ScriptCompilationConfigurationFromDefinition.kt:32)
      	at org.jetbrains.kotlin.scripting.definitions.ScriptCompilationConfigurationFromDefinition$1.invoke(ScriptCompilationConfigurationFromDefinition.kt:28)
      	at kotlin.script.experimental.api.ScriptCompilationConfiguration.<init>(scriptCompilation.kt:22)
      	at kotlin.script.experimental.api.ScriptCompilationConfiguration.<init>(scriptCompilation.kt:24)
      	at org.jetbrains.kotlin.scripting.definitions.ScriptCompilationConfigurationFromDefinition.<init>(ScriptCompilationConfigurationFromDefinition.kt:27)
      	at org.jetbrains.kotlin.scripting.definitions.ScriptDefinition$Companion.getDefault(ScriptDefinition.kt:221)
      	at org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationExtension.updateConfiguration(ScriptingCompilerConfigurationExtension.kt:67)
      	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment$Companion.configureProjectEnvironment(KotlinCoreEnvironment.kt:578)
      	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment.<init>(KotlinCoreEnvironment.kt:199)
      	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment.<init>(KotlinCoreEnvironment.kt:108)
      	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment$Companion.createForProduction(KotlinCoreEnvironment.kt:445)
      	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.createCoreEnvironment(K2JVMCompiler.kt:192)
      	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:143)
      	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:53)
      	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:99)
      	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:47)
      	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
      	at gg.jte.compiler.kotlin.KotlinClassCompiler.compile(KotlinClassCompiler.java:39)
      	at gg.jte.compiler.TemplateCompiler.precompileClasses(TemplateCompiler.java:157)
      	at gg.jte.compiler.TemplateCompiler.precompile(TemplateCompiler.java:137)
      	at gg.jte.compiler.TemplateCompiler.precompileAll(TemplateCompiler.java:84)
      	at gg.jte.TemplateEngine.precompileAll(TemplateEngine.java:325)
      	at gg.jte.gradle.PrecompileJteTask.execute(PrecompileJteTask.java:95)
      	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
      	at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
      	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58)
      	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:51)
      	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:29)
      	at org.gradle.api.internal.tasks.execution.TaskExecution$2.run(TaskExecution.java:239)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
      	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
      	at org.gradle.api.internal.tasks.execution.TaskExecution.executeAction(TaskExecution.java:224)
      	at org.gradle.api.internal.tasks.execution.TaskExecution.executeActions(TaskExecution.java:207)
      	at org.gradle.api.internal.tasks.execution.TaskExecution.executeWithPreviousOutputFiles(TaskExecution.java:190)
      	at org.gradle.api.internal.tasks.execution.TaskExecution.execute(TaskExecution.java:168)
      	at org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:89)
      	at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:40)
      	at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:53)
      	at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:50)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
      	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
      	at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:50)
      	at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:40)
      	at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:68)
      	at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:38)
      	at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:48)
      	at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:36)
      	at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:41)
      	at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:74)
      	at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55)
      	at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:51)
      	at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:29)
      	at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:61)
      	at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:42)
      	at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:60)
      	at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:27)
      	at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:188)
      	at org.gradle.internal.execution.steps.BuildCacheStep.lambda$execute$1(BuildCacheStep.java:75)
      	at org.gradle.internal.Either$Right.fold(Either.java:175)
      	at org.gradle.internal.execution.caching.CachingState.fold(CachingState.java:59)
      	at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:73)
      	at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:48)
      	at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:38)
      	at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:27)
      	at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:36)
      	at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:22)
      	at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:109)
      	at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$2(SkipUpToDateStep.java:56)
      	at java.base/java.util.Optional.orElseGet(Optional.java:369)
      	at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:56)
      	at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:38)
      	at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:73)
      	at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:44)
      	at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)
      	at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)
      	at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:89)
      	at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:50)
      	at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:114)
      	at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:57)
      	at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:76)
      	at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:50)
      	at org.gradle.internal.execution.steps.SkipEmptyWorkStep.lambda$execute$2(SkipEmptyWorkStep.java:93)
      	at java.base/java.util.Optional.orElseGet(Optional.java:369)
      	at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:93)
      	at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:34)
      	at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
      	at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:43)
      	at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:31)
      	at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:40)
      	at org.gradle.api.internal.tasks.execution.TaskExecution$3.withWorkspace(TaskExecution.java:284)
      	at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:40)
      	at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:30)
      	at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:37)
      	at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:27)
      	at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:44)
      	at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:33)
      	at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:76)
      	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:142)
      	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:131)
      	at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)
      	at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
      	at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
      	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
      	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
      	at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
      	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
      	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
      	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
      	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
      	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
      	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
      	at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
      	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:402)
      	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:389)
      	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:382)
      	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:368)
      	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
      	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
      	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
      	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
      	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
      	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
      	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
      	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
      	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:61)
      	at java.base/java.lang.Thread.run(Thread.java:829)
      ]```
    
    opened by IvanPizhenko 31
  • Question: How to escape the template engine syntax?

    Question: How to escape the template engine syntax?

    For example, let's say I have an HTML page that has some inline javascript, like the below one.

    console.log(`testing ${myVar}`);
    

    How can I escape the jte syntax so to say, in that piece of code, to make sure it won't be processed?

    enhancement 
    opened by renannprado 19
  • Javalin + JTE Hot reload doesn't work

    Javalin + JTE Hot reload doesn't work

    I've set up things for the hot reload in the my Javalin + JTE based application as follows:

    class MyServer{
    
    // ...
    
      private fun createTemplateEngine(): TemplateEngine {
        if (isDevSystem()) {
          val path = Path.of("src", "main", "jte")
          val codeResolver = DirectoryCodeResolver(path)
          logger.info("DEVELOPMENT SYSTEM DETECTED. JTE source folder: '${path.toAbsolutePath()}'")
          return TemplateEngine.create(codeResolver, ContentType.Html)
        } else {
          return TemplateEngine.createPrecompiled(ContentType.Html)
        }
      }
    
      private fun configureJavalin(): Javalin {
        JavalinJte.configure(createTemplateEngine())
        // ... more stuff here ...
      }
    
    // ...
    
    }
    

    But hot reload just doesn't work. When I change JTE file and referesh page in browser, it just doesn't change.

    I've create descendant class from the DirectoryCodeResolver and overridden interface methods to output stuff to log and forcibly report that file has changed even if it is not.

    package mypackage
    
    import gg.jte.resolve.DirectoryCodeResolver
    import java.nio.file.Path
    import mu.KotlinLogging
    
    class CustomCodeResolver(val path: Path) : DirectoryCodeResolver(path) {
      private val logger = KotlinLogging.logger {}
    
      override fun resolve(name: String): String {
        val s = super.resolve(name)
        logger.info("***JTE*** resolve: $name, size=${s.length}")
        return s
      }
    
      override fun hasChanged(name: String): Boolean {
        val changed = super.hasChanged(name)
        logger.info("***JTE*** hasChanged: $name -> $changed")
        return true // force "always changed"
      }
    
      override fun resolveAllTemplateNames(): List<String> {
        logger.info("***JTE*** resolveAllTemplateNames")
        return super.resolveAllTemplateNames()
      }
    }
    
    

    But still even with forced "always changed" file it still doesn't work. According to log outputs, this part works correctly - when I change JTE file and refresh browser, method from DirectoryCodeResolver reports file as changed, and my custom resolve() shows different file size.

    However, rendering result still as if old version is used. Actually, rendering result corresponds to JTE template content faced on the first rendering - no matter how much times I change page and refresh browser, the page is always as it was first time, before any changes.

    Please fix.

    bug enhancement 
    opened by IvanPizhenko 18
  • Load templates dynamically from a database

    Load templates dynamically from a database

    I really like jte syntax and its simplicity, but I'm wondering if jte is suitable for the following use cases:

    1. Our system should be able to render templates that are stored in external datastore, e.g. relational DB.
    2. Also we should be able to adjust templates without the need to restart or redeploy our application but only change template body in a DB
    question 
    opened by isaranchuk 17
  • `Failed to init template entry.kte, no method named 'render' found in class gg.jte.generated.precompiled.JteentryGenerated` when argument is Kotlin value class

    `Failed to init template entry.kte, no method named 'render' found in class gg.jte.generated.precompiled.JteentryGenerated` when argument is Kotlin value class

    Hello,

    I use jte 2.1.1 inside my ktor application with Gradle. Gradle setup is following:

    jte {
        generate()
        sourceDirectory.set(Paths.get("templates"))
        contentType.set(gg.jte.ContentType.Html)
        binaryStaticContent.set(true)
        trimControlStructures.set(true)
    }
    

    Most part of templates work fine, but for one of them, namely entry.kte seemingly there is no "render" method. At least during debug I can't find it too. Here's the entry.kte content:

    @import io.github.asm0dey.plugins.*
    @import java.time.*
    
    @param content: String
    @param summary: String
    @param book: BookWithInfo
    @param imageType: String
    <?xml version="1.0" encoding="UTF-8"?>
    <entry xmlns="http://www.w3.org/2005/Atom" xmlns:dcterms="http://purl.org/dc/terms/">
        <title>${book.book.name}</title>
        <id>/opds/book/${book.id}/info</id>
        @for(author in book.authors)
        <author>
            <name>${author.buildName()}</name>
            <uri>/opds/author/browse/${author.id}</uri>
        </author>
        @endfor
        <published>${formatDate(ZonedDateTime.now())}</published>
        <updated>${formatDate(book.book.added)}</updated>
        @if(book.book.lang != null)
        <dcterms:language>${book.book.lang}</dcterms:language>
        @endif
        @if(book.book.date != null)
        <dcterms:date>${book.book.date}</dcterms:date>
        @endif
        @for(genre in book.genres)
        <category term="${genre}" label="${genre}"/>
        @endfor
        @if(imageType != null)
        <link type="${imageType}" rel="http://opds-spec.org/image" href="/opds/image/${book.id}"/>
        @endif
        <link type="application/fb2+zip" rel="http://opds-spec.org/acquisition/open-access" href="/opds/book/${book.id}/download"/>
        @if(summary != null)
        <summary type="text">${summary}</summary>
        <content type="html">$unsafe{content}</content>
        @endif
    </entry>
    

    Looking at javap output I can see following:

    ❯ javap ./build/classes/kotlin/main/gg/jte/generated/precompiled/JteentryGenerated.class | grep render
      public static final void render-JMhnnco(gg.jte.html.HtmlTemplateOutput, gg.jte.html.HtmlInterceptor, java.lang.String, java.lang.String, org.jooq.Record4<java.lang.Long, java.util.List<? extends io.github.asm0dey.opdsko.jooq.tables.pojos.Book>, java.util.List<? extends io.github.asm0dey.opdsko.jooq.tables.pojos.Author>, java.util.List<? extends java.lang.String>>, java.lang.String);
      public static final void renderMap(gg.jte.html.HtmlTemplateOutput, gg.jte.html.HtmlInterceptor, java.util.Map<java.lang.String, ? extends java.lang.Object>);
    

    Further investigation:

    BookWithInfo is a value class:

    @JvmInline
    value class BookWithInfo(private val record: Record4<Long, List<BookPojo>, List<Author>, List<String>>) {
        val book get() = record.component2()[0]
        val authors get() = record.component3()!!
        val genres get() = record.component4().map { genreNames[it] ?: it }
        val id get() = record.get(BOOK.ID)!!
    }
    

    Maybe this is the reason why we can see a bridge-like render-JMhnnco method in the JteentryGenerated.class?

    However, for other template, where BookWithInfo participates too, but in a List, not by itself, everything is being generated fine:

    ❯ javap ./build/classes/kotlin/main/gg/jte/generated/precompiled/JtebooksGenerated.class | grep render
      public static final void render(gg.jte.html.HtmlTemplateOutput, gg.jte.html.HtmlInterceptor, java.util.List<io.github.asm0dey.plugins.BookWithInfo>, java.util.Map<java.lang.Long, java.lang.String>, java.util.Map<java.lang.Long, java.lang.String>, java.lang.String, java.lang.String, java.lang.String, java.time.temporal.TemporalAccessor, java.util.List<io.github.asm0dey.model.Entry$Link>);
      public static final void renderMap(gg.jte.html.HtmlTemplateOutput, gg.jte.html.HtmlInterceptor, java.util.Map<java.lang.String, ? extends java.lang.Object>);
    

    Maybe this is an error on Kotlin compiler's side, but it appears to me that template detection method should be just a bit more sophisticated and be aware that Kotlin value classes exist as well as such bridge methods.

    bug 
    opened by asm0dey 17
  • Lots of warnings during compliation of JTE generated Kotlin code

    Lots of warnings during compliation of JTE generated Kotlin code

    After we have swithed to generate() in the build.gradle.kts (see our another issue #177), we are getting lots of Kotlin compiler warnings which we have never had before.

    Below is list of warnings that we've faced:

    // Unchecked cast: Any? to Response<ServiceResponse>
    // many similar ones for different parameter names and types
    // these all are our custom parameters which we pass into a KTE page, by declaring them like
    // @param keyHistoryDetailsResponse: Response<KeyHistoryDetailsResponse>
        val fesServiceResponse = params["fesServiceResponse"] as Response<com.flowcrypt.eap.clients.fes.ServiceResponse>
    
    // Parameter 'params' is never used
    	@JvmStatic fun renderMap(jteOutput:gg.jte.html.HtmlTemplateOutput, jteHtmlInterceptor:gg.jte.html.HtmlInterceptor?, params:Map<String, Any?>) {
    		render(jteOutput, jteHtmlInterceptor);
    	}
    
    // Parameter 'jteHtmlInterceptor' is never used
    	@JvmStatic fun render(jteOutput:gg.jte.html.HtmlTemplateOutput, jteHtmlInterceptor:gg.jte.html.HtmlInterceptor?) {
    		jteOutput.writeContent("....")
            }
    
    // 'capitalize(): String' is deprecated. Use replaceFirstChar instead
           jteOutput.writeUserContent(provider.capitalize())
    

    I suggest to add proper @Suppress(...) statements into the generated code. In the last case I'd suggest to use recommended method instead of deprecated one. On my end, I'm looking into how to disable such warnings for certain paths (generated code) in the project but no luck so far.

    opened by IvanPizhenko 16
  • JTE is slower than regular String.replace approach

    JTE is slower than regular String.replace approach

    Hello, first of all, thanks for the great library.

    I made a simple benchmark in order to test JTE performance and seems JTE is much slower than regular String.replace() at least for simple use cases.

    Benchmark:

    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    @State(Scope.Thread)
    @Fork(1)
    @Warmup(iterations = 5, time = 1)
    @Measurement(iterations = 5, time = 1)
    public class JTETest {
    
        String primaryColor = "#ffffff";
        String templateTxt;
        CodeResolver codeResolver = new DirectoryCodeResolver(Path.of("xxx/templates"));
        TemplateEngine templateEngine = TemplateEngine.create(codeResolver, ContentType.Plain);
    
    
        @Setup
        public void setup() {
            templateTxt = "<!DOCTYPE html>\n"
                    + "<html>\n"
                    + "<head>\n"
                    + "${primaryColor}\n"
                    + "</head>\n"
                    + "</html>";
    
        }
    
        @Benchmark
        public String concat() {
            return "<!DOCTYPE html>\n"
                    + "<html>\n"
                    + "<head>\n"
                    + primaryColor
                    + "\n"
                    + "</head>\n"
                    + "</html>";
        }
    
        @Benchmark
        public String replace() {
            return templateTxt.replace("${primaryColor}", primaryColor);
        }
    
        @Benchmark
        public String templateEngine() {
            TemplateOutput output = new StringOutput();
            templateEngine.render("replace_test.jte", primaryColor, output);
            return output.toString();
        }
    
    }
    

    replace_test.jte:

    @param String primaryColor
    
    <!DOCTYPE html>
    <html>
    <head>
    ${primaryColor}
    </head>
    </html>
    
    

    Result:

    Benchmark               Mode  Cnt     Score     Error  Units
    JTETest.concat          avgt    5    25.720 ±   2.108  ns/op
    JTETest.replace         avgt    5    75.095 ±   8.815  ns/op
    JTETest.templateEngine  avgt    5  2867.906 ± 183.810  ns/op
    

    For the large template 1kb and many replacements, JTE is still slower ~30%. Is that expected?

    In theory, if the template doesn't have any control flow, it could be compiled to String concat pattern:

    "" + primaryColor + "", where OptimizeStringConcat kicks in. That should make it even faster than regular String.replace pattern. WDYT?

    question 
    opened by doom369 16
  • ktor integration - models allowed?

    ktor integration - models allowed?

    Hi, trying to use jte with ktor (https://ktor.io/docs/jte.html) however the example and the JteContent function appears to only accept a map of params, rather than any models or objects.

    The example code is

    get("/index") {
        // ktor example (no model, just a map of params)
          val params = mapOf("id" to 1, "name" to "John")
          call.respond(JteContent("index.kte", params))
        // my attempt, using a model - type mismatch
          call.respond(JteContent("test.kte", MyModel(id = 1, name = "John")))
    }
    
    // my attempt requires a data class (but fails anyway)
    data class MyModel(val id: Int, val name: String)
    

    This would seem to limit compatibility with jte templates that expect models, plus make creating new templates more tedious. Am I missing a way to use models with Ktor and jte or is it limited to Maps/Params? Thanks!

    bug 
    opened by 2x2xplz 14
  • Add gradle plugin configuration example with Kotlin script

    Add gradle plugin configuration example with Kotlin script

    Please add Kotlin script equivalent code. Currently it is only in the Groovy:

    import gg.jte.ContentType
    import java.nio.file.Paths
    
    plugins {
        id 'java'
        id 'gg.jte.gradle' version '${jte.version}'
    }
    
    dependencies {
        implementation('gg.jte:jte:${jte.version}')
    }
    
    tasks.precompileJte {
        sourceDirectory = Paths.get(project.projectDir.absolutePath, "src", "main", "jte")
        targetDirectory = Paths.get(project.projectDir.absolutePath, "jte-classes")
        compilePath = sourceSets.main.runtimeClasspath
        contentType = ContentType.Html
    }
    
    tasks.precompileJte {
        dependsOn(tasks.compileJava)
    }
    
    tasks.test {
        dependsOn(tasks.precompileJte)
    }
    

    Struggling with converting this part to Kotlin:

    tasks.precompileJte {
        sourceDirectory = Paths.get(project.projectDir.absolutePath, "src", "main", "jte")
        targetDirectory = Paths.get(project.projectDir.absolutePath, "jte-classes")
        compilePath = sourceSets.main.runtimeClasspath
        contentType = ContentType.Html
    }
    
    documentation help wanted 
    opened by IvanPizhenko 14
  • Add gradle plugin

    Add gradle plugin

    This PR adds a gradle plugin with two tasks, CompilerMojo & GeneratorMojo. I'm not a pro at gradle, so something might be of low quality.

    Related issue #16.

    To use the tasks locally

    Put that into settings.gradle.build:

    pluginManagement {
        buildscript {
            repositories {
                flatDir {
                    dir("PATH TO A FOLDER WITH A BUILT PLUGIN")
                }
            }
    
            dependencies {
                classpath("gg.jte:jte-gradle-plugin:1.6.1-SNAPSHOT") // Remember to keep the version updated
            }
        }
    }
    

    And from build.gradle.kts you can use the tasks:

    import java.nio.file.Path
    import gg.jte.ContentType
    
    plugins {
        id("gg.jte.gradle")
    }
    
    val jtePath = Path.of(project.projectDir.absolutePath,"src", "jte")
    
    tasks.generatorMojo {
        sourceDirectory = jtePath
        contentType = ContentType.Html
    }
    
    tasks.compilerMojo {
        sourceDirectory = jtePath
        targetDirectory = Path.of(project.projectDir.absolutePath, "artifacts", "jte-compiled")
        contentType = ContentType.Html
    }
    
    tasks.build {
        dependsOn(tasks.compilerMojo)
    }
    
    enhancement 
    opened by iRebbok 13
  • Multiple DirectoryCodeResolver

    Multiple DirectoryCodeResolver

    I'm writing a "combined" app which uses JTEs from multiple source directories. There doesn't seem to be any way to do this at present. As far as I can tell, I'd need to create a MultipleDirectoryCodeResolver, which would contain multiple DirectoryCodeResolvers and then have a way to differentiate which one to use based on the file name.

    It's a bit weird. Does that sound like what you'd do?
    Don't suppose this would be of interest to you at all?

    opened by Jerbell 14
  • improve typesafe API experience

    improve typesafe API experience

    I wanted to get this on your radar though I can't guarantee I'll actually work on it any time soon.

    I like JTE for being able to pre-generate templates into Java classes. I've been thinking about how to use them in a typesafe way.

    Given a template:

    hello.jte

    @param String message
    @param int count
    
    Hello, ${message}
    Count is ${count}
    

    Then my Java code should be able to access it by name, give parameters and render it. Maybe something like this:

    MyClass.java

    var counter = 1;
    templateFactory.hello("world", counter++).render(output);
    

    This involves a factory class being generated along with the templates. It can be made available via dependency injection or a static method if you aren't using DI. The underlying TemplateEngine is hidden behind this facade.

    opened by edward3h 1
  • [Question] Who is using jte?

    [Question] Who is using jte?

    Hey everyone,

    just curious if you use jte on your projects and what for?

    I'll make a start - I use jte for HMTL rendering of

    • the jte website: https://jte.gg/
    • my game website https://mazebert.com/
    question 
    opened by casid 18
  • Compression of HTML template output

    Compression of HTML template output

    It would be nice to to have a setting to produce compressed ouput for HTML templates, by removing all unneeded indentations from the output.

    For instance this template:

    <div>
        @if(true)
            <span>Hello</span>
        @endif
    </div>
    

    Currently produces this output:

    <div>
        
            <span>Hello</span>
        
    </div>
    

    With compression on, the output would be this instead:

    <div><span>Hello</span></div>
    

    This could save quite a few bytes that need to be sent to each client, plus reduces the server resources needed for rendering, if done in the template compiler.

    It's a tricky feature and things could go wrong, so it should be optional. Also, pre and code tags should be left as is and it would be a good idea to allow the user to specify css classes that indicate no compression.

    enhancement 
    opened by casid 11
Releases(2.2.4)
  • 2.2.4(Nov 29, 2022)

    • #185 Rendering Enums calls name(), instead of toString()
    • #186 Add hot reload support to ResourceCodeResolver
    • #189 properly reset escaping context after attribute value
    Source code(tar.gz)
    Source code(zip)
  • 2.2.3(Nov 6, 2022)

  • 2.2.2(Nov 6, 2022)

    jte-kotlin

    • Revert to Kotlin 1.6.21 until Gradle issues are solved
    • #179 suppress warnings in generated Kotlin templates
    • #179 suppress Linter in generated Kotlin templates
    Source code(tar.gz)
    Source code(zip)
  • 2.2.1(Oct 3, 2022)

    jte

    • #176 null string passed to $unsafe{} behaves the same as if passed to ${}

    jte-kotlin

    • #177 bump kotlin version to 1.7.20
    • #161 unsafe can only be used for rendering strings, was missing in jte-kotlin
    Source code(tar.gz)
    Source code(zip)
  • 2.2.0(Sep 21, 2022)

    jte

    • #174 do not output semicolon for !{} expressions in the Java code generator, to be in line with the documentation and the IntelliJ plugin (minor, but possibly breaking change)
    • Removed StringOutputPool, as it was a rather arbitrary and minimal class, easily created if needed and also won't work well together with Loom's upcoming virtual threads
    • Minor hot reload synchronization improvement

    jte-watcher

    • Bump io.methvin.directory-watcher to 0.16.1

    jte-kotlin

    • Bump kotlin version to 1.7.10
    • Remove special handling of \f escape sequence that is not supported in Kotlin
    Source code(tar.gz)
    Source code(zip)
  • 2.1.2(Jul 8, 2022)

    • #163 support for Kotlin value classes
    • #166 fix for using JS string interpolation inside @raw in template content
    • #168 fix Kotlin code generation in case there are whitespaces between parameter name and colon
    • #169 prevent Kotlin String interpolation in @raw blocks

    Spring Boot integration

    • #165 Add the ability to override the jte template suffix, so spring boot can also use kte templates
    Source code(tar.gz)
    Source code(zip)
  • 2.1.1(Jun 16, 2022)

  • 2.1.0(Jun 16, 2022)

    Caution: There is an output escaping bug in this release (content blocks in HTML attributes are escaped twice). Please skip this version and upgrade to 2.1.1 or later.

    • #161 Unsafe can only be used for rendering strings
    Source code(tar.gz)
    Source code(zip)
  • 2.0.4(Jun 10, 2022)

    • #156 Fix ResourceCodeResolver lookup for templates in the root of resources
    • #159 mark sourceDirectory and targetDirectory as required for maven precompile
    • #152 remove readonly=true from maven plugin parameters
    • Create required directories for generated binary static content resources
    Source code(tar.gz)
    Source code(zip)
  • 2.0.3(May 12, 2022)

    • #128 throw exception for self contained jars without precompiled templates
    • #149 Fix execution optimizations have been disabled warning in gradle plugin
    • #151 Fix escaping of js comments on same line
    • Bump kotlin version to 1.6.21 (jte-kotlin only)
    • Adjust JSP converter for less strict jte 2 template structure
    Source code(tar.gz)
    Source code(zip)
  • 2.0.2(Apr 4, 2022)

    • #144 spring boot starter contribution by @atomfrede
    • Attribute map values in html intercepter are properly escaped
    • Remove onHtmlAttributeStarted from HtmlInterceptor
    Source code(tar.gz)
    Source code(zip)
  • 2.0.1(Mar 25, 2022)

    • Fix escaping issue with content blocks containing HTML attribute output in localizer params.
    • Old template is removed from template cache before hot reload, so that compilation errors are reproducible.
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0(Feb 26, 2022)

    Features

    • #126 The specialized @tag and @layout keywords have been removed in favor of @template. This change allows to structure template files freely. Templates can now live close to the place where they are used. For example:
      • Create high level module directories that contain all templates and are not scattered in tag/layout directories
      • Split a large page in smaller templates that live next to that page
    • #125 Added @raw keyword for output that is unprocessed by jte
    • #122 Faster directory watcher on MacOS

    Migration Guide

    #126 is a breaking change, the old @tag/@layout is no longer supported. If jte 2 finds those keywords during compilation it will fail and output instructions how to migrate your project automatically.

    How to upgrade:

    • Bump jte dependency to the latest 2.x release
    • Compile your templates (either through maven, gradle or by simply opening your website locally)
    • In case you use any of the removed keywords, the compiler will fail and output a Java class that does the migration
    • Create a class with the migration code in your project and run the main method
    • Make sure to upgrade the IntelliJ plugin to 2.x

    Migration class, in case you want to migrate right away without the compile step:

    public class Migration {
        public static void main(String[] args) {
            gg.jte.migrate.MigrateV1To2.migrateTemplates(java.nio.file.Paths.get("your jte source root directory"));
        }
    }
    

    IntelliJ plugin features

    You also want to make sure to use the latest IntelliJ plugin to get support for the new syntax.

    • Support for @template keyword
    • Support for @raw keyword
    • Move template file refactoring is working now

    IntelliJ can no longer guess the jte root directory by moving up the directory tree until a tag/layout directory is found. You will need to create a .jteroot file within the root directory to help the plugin here (the plugin will complain if this file doesn't exist).

    Source code(tar.gz)
    Source code(zip)
  • 1.12.1(Nov 13, 2021)

    • Add support for kebab-case directory names
    • Use Gradle 7.3 to support building with Java 17
    • Improved error handling of unclosed or not matching braces
    Source code(tar.gz)
    Source code(zip)
  • 1.12.0(Sep 25, 2021)

    Features

    • Drastically improved hot reload speed. Now, only changed classes are recompiled. This results in huge speedups during development, when deep template hierarchies are adjusted and hot reloaded.
    • Fixed a bug that tags shared between multiple templates were only hot reloaded for one template.
    • Removed side effects from DirectoryCodeResolver

    Breaking changes

    To improve hot reloading, changes had to be made to the CodeResolver interface.

    In case you use one of the supplied implementations DirectoryCodeResolver or ResourceCodeResolver, no changes are required.

    In case you have your own CodeResolver implementation, you need to make the following adjustments:

    • boolean hasChanged(String name); was removed from the interface, you will need to delete this method from your implementation.
    • long getLastModified(String name); was added to the interface, you will need to implement this method:
      • In case your previous method always returned false, simply return 0L.
      • Otherwise, you need to return the last modified timestamp in milliseconds. For example, if you'd use the File API, that would be File::lastModified().
    Source code(tar.gz)
    Source code(zip)
  • 1.11.4(Sep 3, 2021)

  • 1.11.3(Aug 22, 2021)

  • 1.11.2(Aug 13, 2021)

    • #92 DirectoryCodeResolver follows symlinks
    • #105 Show calling template and line number in compile error for non-existing tag parameters
    • #95 Add debug template line information for parameters used in renderMap()
    • #94 Provide better exception message if template engine is initialized with ContentType.Plain instead of ContentType.Html
    • #93 jte-kotlin: bump Kotlin version to 1.5.20
    • #111 Resolve conflict with alpine.js syntax in OWASP policy, improve the quality of PreventOutputInTagsAndAttributes error messages
    • Ensure instanceof pattern matching works with Java 14+
    • gradle-plugin: Bump gradle version to 7.1.1 for Java 16 support

    jsp-converter

    • #96 Fix import of array types in converted JSP tags
    • #96 Fix import of generic types in converted JSP tags
    • #101 Fix conversion of string concatenation in JSP EL expression
    • #102 Support conversion of JSP files containing AstListData
    • #103 JSP bridging tags with body are closed correctly when replacing usages after conversion
    • #106 String AST nodes stay properly escaped when converting JSP to jte
    • #108 Improve JSP comment and control expression collapse during conversion
    • #104 Parameters of converted JSP tags are put on seperate lines
    Source code(tar.gz)
    Source code(zip)
  • 1.11.1(Jul 6, 2021)

    • #86 Ensure UTF-8 encoding is used for compiling Java templates
    • #87 Maven Plugin: Add property keepGeneratedSourceFiles to CompilerMojo (defaults to false)
    • #89 Maven Plugin: Pass maven-compiler-plugin specified Java version to jte compiler
    Source code(tar.gz)
    Source code(zip)
  • 1.11.0(Jun 17, 2021)

    • Stacktraces of TemplateException are rewritten, so that in case of rendering errors all template line numbers are provided
    • Boolean attributes are passed to HtmlInterceptor
    • #78 up-to-date check now works with classpath, fixes Gradle plugin getting stuck on gradle clean
    Source code(tar.gz)
    Source code(zip)
  • 1.10.0(May 8, 2021)

    Features

    • #72 GraalVM native-image support, more details and how to use here. Kudos to @edward3h for this contribution!
    • #81 A new jte extension for the Gradle plugin removes lots of boilerplate. Make sure to have a look at the updated examples. Big thanks to @edward3h for contributing this extension!

    Fixes

    • #77 Prevent Kotlin compiler to leak file handles
    • #75 Better generics handling in Kotlin templates
    • #80 Improvements for trimControlStructures
    Source code(tar.gz)
    Source code(zip)
  • 1.9.0(Mar 12, 2021)

    • #67 DirectoryCodeResolver no longer does file watching. This removes a problematic com.sun.nio dependency and was likely not used by 99% of jte users. In case you need the file watching on your project, please add the jte-watcher dependency as described here.
    • #61 You may now configure the class path used by the compiler via TemplateEngine::setClassPath
    • #65 modification times are only stored for existing files in DirectoryCodeResolver
    • Empty values for localization keys now result in null Content, since it makes no sense to render them.
    • #68 fixed Gradle path setup in examples
    Source code(tar.gz)
    Source code(zip)
  • 1.8.0(Feb 28, 2021)

    This release adds the optional jte-kotlin module.

    It can compile .kte template files, which use Kotlin instead of Java as expression language. See https://github.com/casid/jte/issues/57 for more background information.

    This is how the example.jte file from the front page looks like as example.kte:

    @import org.example.Page
    
    @param page:Page
    
    <head>
        @if(page.description != null)
            <meta name="description" content="${page.description}">
        @endif
        <title>${page.title}</title>
    </head>
    <body>
        <h1>${page.title}</h1>
        <p>Welcome to my example page!</p>
    </body>
    

    To compile templates with Kotlin as expression language, you need to add the jte-kotlin module to your project:

    <dependency>
        <groupId>gg.jte</groupId>
        <artifactId>jte-kotlin</artifactId>
        <version>1.8.0</version>
    </dependency>
    

    Since 1.8.0 both Maven and Gradle plugins can precompile jte and kte files. You can seamlessly call jte templates from kte templates and vice versa. This might be handy if you'd like to migrate the expression language of existing jte templates from Java to Kotlin.

    The IntelliJ plugin is pretty far already to support kte files (https://github.com/casid/jte-intellij/tree/kotlin-support). There's still one issue with unresolved backwards references. If anyone has an idea why this doesn't work with Kotlin injections (https://intellij-support.jetbrains.com/hc/en-us/community/posts/360008349720-Kotlin-references-in-MultiHostInjector-are-not-working-correctly), I'm very happy about any hint :-) Regardless there's gonna be a first beta release of the IntelliJ plugin with Kotlin support next week!

    Other bugfixes and improvements:

    • #63 package name of template classes can be specified
    • #63 better hot reload experience with generated template sources
    • #60 Gradle plugin no longer includes source sets which caused problems on some builds
    • #52 Fix hasTemplate method with DirectoryCodeResolver
    Source code(tar.gz)
    Source code(zip)
  • 1.7.0(Feb 19, 2021)

  • 1.6.0(Feb 10, 2021)

  • 1.5.2(Feb 9, 2021)

  • 1.5.1(Feb 6, 2021)

    • #47 add setting htmlCommentsPreserved to preserve comments with ContentType.Html.
    • #46 do not fail build for empty HTML attributes, even if they are not boolean.
    • #39 fix error message for wrong param with precompiled classes.
    Source code(tar.gz)
    Source code(zip)
  • 1.5.0(Dec 13, 2020)

    • #25 Fix compilation with too long constant strings.
    • Small performance optimization: Use forHtmlContent to escape HTML tag body contents, since we know that we are not within an HTML attribute.
    • #34 Allow to pass compiler arguments to template compiler.
    • Add first draft of hot reload for precompiled template engines.
    • #35 allow to configure parent class loader for jte templates.
    • Remove experimental feature null safe template mode.
    Source code(tar.gz)
    Source code(zip)
  • 1.4.0(Oct 30, 2020)

    • HTML attributes with a single output that evaluates to an empty string, null, or false, are not rendered. For instance, <span data-title="${null}">Info</span>, will result in <span>Info</span>. The existing feature for HTML boolean attributes is unaffected by this, e.g. <input required="${true}" still renders as <input required> and <input required="${false}"> results in <input>.
    • Content block output in html attributes is escaped. For instance, <span data-title="@`This is "the way"!`">Info</span> will be rendered as <span data-title="This is &#34;the way&#34;!">Info</span> (See these unit tests for detailled examples)
    • #22 HTML, CSS and js comments are omitted with ContentType.Html
    • Experimental utility to concat CSS classes, located in gg.jte.html.support.HtmlSupport
    • Allow composition of different HTML policies.
    • Optional HTML policy that forbids single quoted HTML attributes. (gg.jte.html.policy.PreventSingleQuotedAttributes)
    • #20 Optional HTML policy to forbid inline event handlers (gg.jte.html.policy.PreventInlineEventHandlers)
    • #10 improve trimming of control structures, it now also works with ContentType.Html
    • Fix import statement in css file being misinterpreted as jte import.
    • Compilation error if parameters are passed to a tag without parameters.
    • #24 Better error message for param declared without name
    • #15 Allow ClassLoader to be provided to ResourceCodeResolver
    • Map of parameter name, parameter class can be obtained for each jte template. (gg.jte.TemplateEngine#getParamInfo)

    Maven plugin

    • #10 trimControlStructures can be enabled
    • Custom HTML policy can be defined in CompilerMojo, with htmlPolicyClass.
    Source code(tar.gz)
    Source code(zip)
  • 1.3.0(Sep 19, 2020)

    • #11 Suppress imports after output was already written
    • Fix ommitted comment if it's the first control structure after parameters
    • #10 Initial insert of experimental feature trimControlStructures (please don't use in production yet!)
    • Added gg.jte.WriterOutput
    • Improved error message if template is called with wrong parameter
    • Wrap arguments passed to Content parameters of templates if the passed type is not gg.jte.Content, in order to allow incremental migration to jte
    Source code(tar.gz)
    Source code(zip)
Owner
Andreas Hager
Andreas Hager
ST (StringTemplate) is a java template engine (with ports for C#, Python, and Objective-C coming) for generating source code

ST (StringTemplate) is a java template engine (with ports for C#, Python, and Objective-C coming) for generating source code, web pages, emails, or an

Antlr Project 851 Jan 5, 2023
Java 8 optimized, memory efficient, speedy template engine producing statically typed, plain java objects

Rocker Templates by Fizzed Fizzed, Inc. (Follow on Twitter: @fizzed_inc) Sponsored by Rocker is proudly sponsored by Greenback. We love the service an

Fizzed, Inc. 669 Dec 29, 2022
Java modern template engine

Jtwig Project Documentation Project Status Coverage Version jtwig-reflection jtwig-core jtwig-web jtwig-pluralization jtwig-spring jtwig-translate-ext

Jtwig 298 May 19, 2022
Multiproject template for ForgeGradle development environments

ForgeGradle Multiproject Template This repository provides a template for multiproject development environments using ForgeGradle. Project structure T

Néstor Amador 10 Jun 3, 2022
A little template project to

FX Modules This is a little project that can be used as a template for modularized JavaFX projects. The main branch is based on JDK17, other version c

Gerrit Grunwald 27 Dec 12, 2022
Pebble is a java templating engine inspired by Twig

Pebble Pebble is a java templating engine inspired by Twig. It separates itself from the crowd with its inheritance feature and its easy-to-read synta

null 985 Dec 23, 2022
a pug implementation written in Java (formerly known as jade)

Attention: jade4j is now pug4j In alignment with the javascript template engine we renamed jade4j to pug4j. You will find it under https://github.com/

neuland - Büro für Informatik 700 Oct 16, 2022
Te4j (Template Engine For Java) - Fastest and easy template engine

Te4j About the project Te4j (Template Engine For Java) - Fastest and easy template engine Pros Extremely fast (127k renders per second on 4790K) Easy

Lero4ka16 19 Nov 11, 2022
Easy to use cryptographic framework for data protection: secure messaging with forward secrecy and secure data storage. Has unified APIs across 14 platforms.

Themis provides strong, usable cryptography for busy people General purpose cryptographic library for storage and messaging for iOS (Swift, Obj-C), An

Cossack Labs 1.6k Dec 29, 2022
Drools is a rule engine, DMN engine and complex event processing (CEP) engine for Java.

An open source rule engine, DMN engine and complex event processing (CEP) engine for Java™ and the JVM Platform. Drools is a business rule management

KIE (Drools, OptaPlanner and jBPM) 4.9k Dec 31, 2022
ST (StringTemplate) is a java template engine (with ports for C#, Python, and Objective-C coming) for generating source code

ST (StringTemplate) is a java template engine (with ports for C#, Python, and Objective-C coming) for generating source code, web pages, emails, or an

Antlr Project 851 Jan 5, 2023
Java 8 optimized, memory efficient, speedy template engine producing statically typed, plain java objects

Rocker Templates by Fizzed Fizzed, Inc. (Follow on Twitter: @fizzed_inc) Sponsored by Rocker is proudly sponsored by Greenback. We love the service an

Fizzed, Inc. 669 Dec 29, 2022
A query language for JSON and a template engine to generate text output.

Josson & Jossons Josson is a query language for JSON. Jossons is a template engine to generate text output. Features and Capabilities of Josson Query

Octomix Software 16 Dec 14, 2022
Java modern template engine

Jtwig Project Documentation Project Status Coverage Version jtwig-reflection jtwig-core jtwig-web jtwig-pluralization jtwig-spring jtwig-translate-ext

Jtwig 298 May 19, 2022
A Java-based template project for the FastJ Game Engine.

FastJ Java Template Program Requirements Java 16 JDK Basic understanding of Java Initial Setup Download the Template You have a few options for gettin

Andrew Dey 13 May 15, 2022
Payara Server is an open source middleware platform that supports reliable and secure deployments of Java EE (Jakarta EE) and MicroProfile applications in any environment: on premise, in the cloud or hybrid.

Payara Platform Community Edition Create. Innovate. Elevate. Payara Platform Community Edition features open source server runtimes for development pr

Payara Foundation 847 Dec 27, 2022
A fast and secure browser for standalone virtual-reality and augmented-reality headsets.

Wolvic VR Browser The goal of the Wolvic project is to create a full-featured browser exclusively for standalone AR and VR headsets. You can find us i

Igalia 465 Jan 7, 2023