Skip to content

Commit a4174b7

Browse files
committed
Massive performance improvement for partials fix #588
1 parent a1296c8 commit a4174b7

5 files changed

Lines changed: 147 additions & 70 deletions

File tree

handlebars/src/main/java/com/github/jknack/handlebars/Handlebars.java

Lines changed: 81 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,6 @@ public static CharSequence escapeExpression(final CharSequence input) {
261261
*/
262262
private boolean prettyPrint;
263263

264-
/**
265-
* If true, given partial blocks are not evaluated when defined but when used.
266-
* If false, partial blocks are evaluated when defined and used.
267-
*/
268-
private boolean lazyPartialBlockEvaluation;
269-
270264
/**
271265
* The helper registry.
272266
*/
@@ -321,6 +315,33 @@ public static CharSequence escapeExpression(final CharSequence input) {
321315
/** True, if we want to extend lookup to parent scope. */
322316
private boolean parentScopeResolution = true;
323317

318+
/**
319+
* If true partial blocks will be evaluated to allow side effects by defining inline
320+
* blocks within the partials blocks.
321+
* Attention: This feature slows down the performance severly if your templates use
322+
* deeply nested partial blocks.
323+
* Handlebars works *much* faster if this feature is set to false.
324+
*
325+
* Example of a feature that is usable when this is set to true:
326+
* <pre>
327+
* {{#> myPartial}}{{#*inline 'myInline'}}Wow!!!{{/inline}}{{/myPartial}}
328+
* </pre>
329+
* With a myPartial.hbs template like this:
330+
* <pre>
331+
* {{> myInline}}
332+
* </pre>
333+
* The text "Wow!!!" will actually be rendered.
334+
*
335+
* If this flag is set to false, you need to explicitly evaluate the partial block.
336+
* The template myPartial.hbs will have to look like this:
337+
* <pre>
338+
* {{> @partial-block}}{{> myInline}}
339+
* </pre>
340+
*
341+
* Default is: true for compatibility reasons
342+
*/
343+
private boolean preEvaluatePartialBlocks = true;
344+
324345
/**
325346
* Creates a new {@link Handlebars} with no cache.
326347
*
@@ -775,38 +796,6 @@ public boolean stringParams() {
775796
}
776797

777798

778-
/**
779-
* If true, given partial blocks are not evaluated when defined but when used.
780-
* If false, partial blocks are evaluated when defined and used.
781-
* @return If true, given partial blocks are not evaluated when defined but when used.
782-
* If false, partial blocks are evaluated when defined and used.
783-
*/
784-
public boolean lazyPartialBlockEvaluation() {
785-
return lazyPartialBlockEvaluation;
786-
}
787-
788-
789-
/**
790-
* If true, given partial blocks are not evaluated when defined but when used.
791-
* If false, partial blocks are evaluated when defined and used.
792-
* @param lazyPartialBlockEvaluation Flag to turn it off and on
793-
*/
794-
public void setLazyPartialBlockEvaluation(final boolean lazyPartialBlockEvaluation) {
795-
this.lazyPartialBlockEvaluation = lazyPartialBlockEvaluation;
796-
}
797-
798-
/**
799-
* If true, given partial blocks are not evaluated when defined but when used.
800-
* If false, partial blocks are evaluated when defined and used.
801-
* @param lazyPartialBlockEvaluation Flag to turn it off and on
802-
* @return The handlebars object.
803-
*/
804-
public Handlebars lazyPartialBlockEvaluation(final boolean lazyPartialBlockEvaluation) {
805-
setLazyPartialBlockEvaluation(lazyPartialBlockEvaluation);
806-
return this;
807-
}
808-
809-
810799
/**
811800
* If true, unnecessary spaces and new lines will be removed from output. Default is: false.
812801
*
@@ -1206,6 +1195,60 @@ public Handlebars parentScopeResolution(final boolean parentScopeResolution) {
12061195
return this;
12071196
}
12081197

1198+
/**
1199+
* If true, partial blocks will implicitly be evaluated before the partials will actually
1200+
* be executed. If false, you need to explicitly evaluate and render partial blocks with
1201+
* <pre>
1202+
* {{> @partial-block}}
1203+
* </pre>
1204+
* Attention: If this is set to true, Handlebars works *much* slower!
1205+
*
1206+
* @return If true partial blocks will be evaluated before the partial will be rendered
1207+
* to allow inline block side effects.
1208+
* If false, you will have to evaluate and render partial blocks explitly (this
1209+
* option is *much* faster).
1210+
*/
1211+
public boolean preEvaluatePartialBlocks() { return preEvaluatePartialBlocks; }
1212+
1213+
/**
1214+
* If true, partial blocks will implicitly be evaluated before the partials will actually
1215+
* be executed. If false, you need to explicitly evaluate and render partial blocks with
1216+
* <pre>
1217+
* {{> @partial-block}}
1218+
* </pre>
1219+
* Attention: If this is set to true, Handlebars works *much* slower!
1220+
*
1221+
* @param preEvaluatePartialBlocks If true partial blocks will be evaluated before the
1222+
* partial will be rendered to allow inline block side
1223+
* effects.
1224+
* If false, you will have to evaluate and render partial
1225+
* blocks explitly (this option is *much* faster).
1226+
*/
1227+
public void setPreEvaluatePartialBlocks(final boolean preEvaluatePartialBlocks) {
1228+
this.preEvaluatePartialBlocks = preEvaluatePartialBlocks;
1229+
}
1230+
1231+
/**
1232+
* If true, partial blocks will implicitly be evaluated before the partials will actually
1233+
* be executed. If false, you need to explicitly evaluate and render partial blocks with
1234+
*
1235+
* <pre>
1236+
* {{> @partial-block}}
1237+
* </pre>
1238+
* Attention: If this is set to true, Handlebars works *much* slower!
1239+
*
1240+
* @param preEvaluatePartialBlocks If true partial blocks will be evaluated before the
1241+
* partial will be rendered to allow inline block side
1242+
* effects.
1243+
* If false, you will have to evaluate and render partial
1244+
* blocks explitly (this option is *much* faster).
1245+
* @return The Handlebars object
1246+
*/
1247+
public Handlebars preEvaluatePartialBlocks(final boolean preEvaluatePartialBlocks) {
1248+
setPreEvaluatePartialBlocks(preEvaluatePartialBlocks);
1249+
return this;
1250+
}
1251+
12091252
/**
12101253
* Return a parser factory.
12111254
*

handlebars/src/main/java/com/github/jknack/handlebars/internal/Partial.java

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -123,18 +123,10 @@ protected void merge(final Context context, final Writer writer)
123123
Map<String, Template> inlineTemplates = partials.getLast();
124124
Template callee = context.data(Context.CALLEE);
125125

126-
final boolean pathIsPartialBlock = "@partial-block".equals(path);
127126
final Template lastPartialBlock = inlineTemplates.get("@partial-block");
128-
final boolean parentIsNotLastPartialBlock = !isCalleeOf(callee, lastPartialBlock);
129-
130-
if (pathIsPartialBlock && parentIsNotLastPartialBlock) {
131-
throw new IllegalArgumentException(
132-
callee + " does not provide a @partial-block for " + this
133-
);
134-
}
135127

136128
if (this.partial != null) {
137-
if (!handlebars.lazyPartialBlockEvaluation()) {
129+
if (handlebars.preEvaluatePartialBlocks()) {
138130
this.partial.apply(context);
139131
}
140132

@@ -207,23 +199,6 @@ protected void merge(final Context context, final Writer writer)
207199
}
208200
}
209201

210-
/**
211-
* @param callee parent template of the currently traversed template
212-
* @param partialBlock partial block candidate
213-
* @return returns if callee and partialBlock are the same
214-
*/
215-
private boolean isCalleeOf(final Template callee, final Template partialBlock) {
216-
if (callee == null || partialBlock == null) {
217-
return false;
218-
}
219-
220-
if (!callee.filename().equalsIgnoreCase(partialBlock.filename())) {
221-
return false;
222-
}
223-
224-
return Arrays.equals(callee.position(), partialBlock.position());
225-
}
226-
227202
/**
228203
* True, if the file was already processed.
229204
*

handlebars/src/test/java/com/github/jknack/handlebars/LazyPartialBlockEvaluationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
public class LazyPartialBlockEvaluationTest extends AbstractTest {
1010
@Override
1111
protected void configure(Handlebars handlebars) {
12-
handlebars.setLazyPartialBlockEvaluation(true);
12+
handlebars.setPreEvaluatePartialBlocks(false);
1313
}
1414

1515
@Test

handlebars/src/test/java/com/github/jknack/handlebars/PartialBlockTest.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@
88

99
public class PartialBlockTest extends AbstractTest {
1010

11-
@Override
12-
protected Handlebars newHandlebars() {
13-
return super.newHandlebars().lazyPartialBlockEvaluation(false);
14-
}
15-
1611
@Test
1712
public void text() throws IOException {
1813
assertEquals("{{#>dude}}{{#*inline \"myPartial\"}}success{{/inline}}{{/dude}}",
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.github.jknack.handlebars.issues;
2+
3+
import com.github.jknack.handlebars.Handlebars;
4+
import com.github.jknack.handlebars.Helper;
5+
import com.github.jknack.handlebars.Options;
6+
import com.github.jknack.handlebars.v4Test;
7+
import org.junit.Test;
8+
9+
import java.io.IOException;
10+
11+
public class Issue588 extends v4Test {
12+
@Override protected void configure(Handlebars handlebars) {
13+
super.configure(handlebars);
14+
handlebars.setPreEvaluatePartialBlocks(false);
15+
handlebars.setInfiniteLoops(true);
16+
}
17+
18+
@Test
19+
public void shouldNotDefineInlinePartialsInPartialBlockCallWithoutPreEvaluation()
20+
throws IOException {
21+
shouldCompileTo("{{#> dude}}{{#*inline \"myPartial\"}}success{{/inline}}{{/dude}}",
22+
$("hash", $(), "partials", $("dude", "{{#> myPartial }}{{/myPartial}}")), "");
23+
24+
}
25+
26+
@Test
27+
public void shouldDefineInlinePartialsInPartialBlockCall() throws IOException {
28+
shouldCompileTo("{{#> dude}}{{#*inline \"myPartial\"}}success{{/inline}}{{/dude}}",
29+
$("hash", $(), "partials",
30+
$("dude", "{{> @partial-block}}{{#> myPartial }}{{/myPartial}}")), "success");
31+
}
32+
33+
@Test
34+
public void shouldOverrideBlockParams() throws IOException {
35+
shouldCompileTo("{{#> dude x=23}}{{#> dude x=12}}{{/dude}}{{/dude}}",
36+
$("hash", $(), "partials",
37+
$("dude", "<div {{#if x}}x={{x}}{{/if}}>{{> @partial-block}}</div>")),
38+
"<div x=23><div x=12></div></div>");
39+
}
40+
41+
@Test
42+
public void shouldOverrideBlockParamsWithoutPreEvaluation() throws IOException {
43+
shouldCompileTo("{{#> dude x=23}}{{#> dude x=12}}{{/dude}}{{/dude}}",
44+
$("hash", $(), "partials",
45+
$("dude", "<div {{#if x}}x={{x}}{{/if}}>{{> @partial-block}}</div>")),
46+
"<div x=23><div x=12></div></div>");
47+
}
48+
49+
@Test
50+
public void shouldOverrideBlockParamsWithFalse() throws IOException {
51+
shouldCompileTo("{{#> dude x=23}}{{#> dude x=false}}{{/dude}}{{/dude}}",
52+
$("hash", $(), "partials",
53+
$("dude", "<div {{#if x}}x={{x}}{{/if}}>{{> @partial-block}}</div>")),
54+
"<div x=23><div ></div></div>");
55+
}
56+
57+
@Test
58+
public void shouldDefineInlinePartialsInPartialCall() throws IOException {
59+
shouldCompileTo("{{#> dude}}{{#*inline \"myPartial\"}}success{{/inline}}{{/dude}}",
60+
$("hash", $(), "partials",
61+
$("dude", "{{> @partial-block}}{{> myPartial }}")),
62+
"success");
63+
}
64+
}

0 commit comments

Comments
 (0)