UNPKG

closure-builder

Version:

Simple Closure, Soy and JavaScript Build system

184 lines (134 loc) 7.55 kB
# Migrating to SoySauce from Tofu The SoySauce backend for server side rendered `Closure Templates` has a number of benefits over the Tofu interpreter * Advanced support for asynchronous IO (see [Advanced Java rendering](adv-java.md) for details) * It allocates less memory and consumes less CPU to render the same templates * Benchmarks show speedups on the low end at 40% though the actual results you will see depend on exactly what your templates are doing * It compiles to Java bytecode instead of using a runtime interpreter, so your application will no longer need to compile a new Tofu during startup. * It supports new features like * streaming escaping directives which reduce the number data copies and intermediate buffers * [structured document logging](doc-logging.md) is not supported by Tofu and there are no plans to backport support * ... and basically all future performance/feature development For all these reasons the Tofu backend is discouraged for new users and all users are encouraged to migrate. [TOC] ## Migration Steps ## 1. Setup compilation First you will need to compile your templates with the new compiler. This is theoretically as simple as changing your calls to `SoyFileSet.compileToTofu()` to call `SoyFileSet.compileTemplates()` instead. This may reveal a few issues: * `compileTemplates()` implies `SoyGeneralOptions.setAllowExternalCalls(false)`, so if you have any unresolved `{call ...}` statements (most likely in dead templates), you will need to fix those. * `compileTemplates()` requires that there are Java implementations of all the referenced plugins function/directives at compile time. (In Tofu this is only required at runtime.) Such calls are most likely in dead code so the templates should either be excluded from the compilation set, or the plugins should be modified to support Java rendering. ## 2. Setup precompilation Next, you will want to get precompilation working. The Tofu backend interprets the `.soy` source files at runtime, so it requires you to pass all the `Closure Templates` files to `SoyFileSet` at runtime in your application. SoySauce also supports this mode for compatibility reasons (and it is also useful to implement dynamic recompilation for an edit/refresh style of development), but because it is a compiler it is signficantly slower the Tofu (you can expect `compileTemplates()` to take about 50% longer than an identical call to `compileToTofu()`). For this reason, there is an ahead of time compiler for `SoySauce`. See [how to compile your template](dir.md#java) for more information. Once you have precompiled all your templates, you can construct a `SoySauce` object at runtime without `SoyFileSet` using 2 strategies. <!--zippy--> via `SoySauceBuilder`. The `SoySauceBuilder` api allows you to construct a SoySauce object directly. ```java import com.google.template.soy.jbcsrc.api.SoySauce; import com.google.template.soy.jbcsrc.api.SoySauceBuilder; ... SoySauce soySauce = new SoySauceBuilder().build(); ``` See [Rendering from java](java.md#create-soysauce) for more details. <!--endzippy--> <!--zippy--> via `Guice` If you are using an application built around `Guice` it will be convenient to use `com.google.template.soy.jbcsrc.api.PrecompiledSoyModule` to construct a `SoySauce` object. For example, ```java SoySauce soy = Guice.createInjector(new PrecompiledSoyModule()) .getInstance(Key.get(SoySauce.class, Precompiled.class)); ``` This is useful if you are supplying plugins and plugin instances via `Guice` multibinders. <!--endzippy--> At this point you have everthing compiling and loaded correctly, now it is time to start rendering. ## 3. Migrate the rendering code In your application you will need to migrate every interaction with `SoyTofu` to instead use the `SoySauce` object you created above. The rendering APIs are very similar so most of this should be fairly mechanical. Though depending on how many callsites you have it may make sense to introduce a facade interface first so you can easily switch back and forth. ... and that is it! of course besides better performance and new features there are some behavior differences. NOTE: the `SoyTofu.Renderer` has a `setData` overload that accepts a `SoyRecord` object, but `SoySauce.Renderer` does not. This means that if you are constructing `SoyData` objects to pass to the renderer that you will need to switch to passing plain Java objects (maps, lists, strings, protos). ## Incompatibilities SoySauce is mostly compatible with Tofu, but some behaviors are slightly different and some bugs were not ported. ### Different Exceptions will be thrown When rendering in Tofu fails, a `SoyTofuException` will be thrown. In contrast, SoySauce uses 'native' exception types, for example: * When a runtime type error occurs in Tofu, you will get a `SoyTofuException` wrapping a `SoyDataException`, in `SoySauce` you will get a `ClassCastException` * When you dereference a `null` value in Tofu you will get a `SoyTofuException`, in SoySauce you will get a `NullPointerException` ### Stricter runtime type checking SoySauce has somewhat stricter runtime type checking. For example, * If you declare that a template has a param `{@param foos: list<string>}` Tofu will assert that the value is actually a `list.` SoySauce will do that, but it will also assert that `$foos[0]` is a string by checking it on access (this is the same strategy that java uses for generics). Depending on how the value was used this may cause a template that previously rendered succesfully to fail in SoySauce with a `ClassCastException`. * If a template has a typed parameter, e.g. `{@param href: uri}` and you call it with an expression of unknown type, Tofu has a bug such that it will not check that the parameter is a `uri` at runtime, but `SoySauce` will. ### Stricter `null` handling SoySauce is stricter about dereferencing null objects. For example, given the expression `isNonnull($foo.bar.baz)` if `bar` is `null` then accessing `.baz` on it should cause an error, and it does in SoySauce and the JS backend, however, in Tofu this doesn’t happen (though there is a TODO), instead it only causes an error if you perform certain operations with the result of the expression (calling `isNonnull` and simple comparisons are the only thing you can do). An appropriate fix would be to rewrite it as `isNonnull($foo.bar?.baz)`. ### Required parameter semantics SoySauce interprets 'required' template parameters slightly differently than Tofu. Imagine this template: ```soy {template .foo} {@param p: string} {$p} {/template} ``` In Tofu, if you call `.foo` without passing `$p` there are a few things that can happen: * If it is a top level call (Java code calling .foo), then you will get a SoyTofuException saying that a required parameter is missing. * If it is a `Closure Templates`->`Closure Templates` call then you will get null for `$p` In SoySauce you always get `null`. We chose this option because it is more internally consistent (`Closure Templates`->`Closure Templates` and java->`Closure Templates` calls are treated equivalently) and it is more consistent with the behavior of the JavaScript backend.