Handlebars
spider-silk-handlebars는 코어에 번들된 jte 대신 Handlebars.java로 렌더링한다.
Tomcat이 별도 모듈인 것과 같은 이유로 별도 모듈이다.
한 엔진에 대한 종속은 아티팩트 이름에 담기고, 코어는 템플릿 의존성이 셋이 아니라 하나인 웹 계층으로 남는다.
설치
dependencies {
implementation('net.benelog.spidersilk:spider-silk-handlebars:1.1.0') {
exclude group: 'gg.jte' // core's jte, unused here
}
}
jte 제외는 선택 사항이다.
쓰지 않는 엔진을 클래스패스에서 치울 뿐이며, 그대로 두면 templates(renderer)가 건네는 것을 무엇이든 받으므로 둘이 나란히 동작한다.
렌더링
new App()
.templates(new HandlebarsTemplates("hbs"))
.get("/decks/{deckId}", req ->
WebResponse.template("deck", Model.of("deck", service.deck(req.pathParamLong("deckId")))));
이는 classpath:/hbs/deck.hbs를 렌더링한다.
모델의 키가 템플릿이 읽는 이름이므로, {{deck.title}}은 Model.of("deck", …)의 deck 항목에 닿는다.
{{name}}은 HTML 이스케이프되고 {{{name}}}은 되지 않는데, 이는 Handlebars 자체의 기본값이며 여기서 그것을 위해 설정할 것이 없는 이유다.
직접 정하는 루트나 접미사
app.templates(new HandlebarsTemplates("hbs") // classpath:/hbs
.suffix(".html")); // .../deck.html
접미사는 덧붙일 뿐 검사하지 않으므로, 확장자를 아직 달고 있는 이름은 접미사가 두 번 붙은 채로 조회된다.
클래스패스 로더는 자체 확장자를 덧붙이지 않으므로, 작동하는 것은 suffix 하나뿐이다.
헬퍼와 캐시
다른 생성자는 설정된 Handlebars를 받으며, 헬퍼, 다른 이스케이프 전략, 파셜 로더는 여기에 들어간다:
Handlebars handlebars = new Handlebars(new ClassPathTemplateLoader("/hbs", ""))
.with(new ConcurrentMapTemplateCache())
.registerHelper("upper", (value, options) -> value.toString().toUpperCase());
app.templates(new HandlebarsTemplates(handlebars));
위처럼 로더에 빈 접미사를 전달하지 않으면, HandlebarsTemplates가 이미 덧붙인 접미사 위에 .hbs를 또 덧붙인다.
new HandlebarsTemplates("hbs")는 컴파일된 템플릿을 ConcurrentMapTemplateCache에 캐시한다.
Handlebars 자체의 기본값은 렌더링할 때마다 컴파일하는 것으로, 프로덕션에서는 요청마다 템플릿을 파싱하게 된다.
다르게 선택하려면 직접 만든 Handlebars를 건넨다.
리플렉션
Handlebars는 {{deck.title}}을 먼저 Map에서 찾지만, 레코드나 빈에 대해서는 리플렉션으로 돌아간다.
이는 코어가 아니라 모듈의 리플렉션이다.
규칙이 적용되는 범위는 "리플렉션 없음"의 범위를 참고한다.
모델 값을 레코드나 빈이 아니라 맵으로 두면 그 리플렉션마저 피할 수 있다.