Release Notes
MiniJinja 3 is a major release. It aligns template behavior and the value
model more closely with Jinja2, makes Serde optional, makes mutable State the
canonical way to call back into the engine, adds built-in automatic reloading
of templates, reworks the JavaScript bindings and makes the Python bindings
considerably faster. Errors involving undefined values and the debug info
shown for errors were improved as well.
Most projects will need some changes when upgrading. See UPDATING.md for
migration instructions. The changes below are relative to 2.24.0 and include
everything from the 3.0.0 alpha releases.
Breaking Changes
- Made
serdeoptional, explicit and disabled by default. The rendering APIs,context!andargs!now consume values and convert exclusively throughInto<Value>. Serde conversion is requested with the newvalue::Serdewrapper which also replacesViaDeserializefor deserializing function arguments.Value::from_serializewas removed. Thedeserializationfeature was removed and is now part of theserdefeature. Disabling the feature fully removes the dependency. #528 - The
jsonfeature no longer depends onserdeandserde_json. Thetojsonfilter and JSON auto escaping now use a built-in JSON serializer. Invalid values now fail the serialization rather than being emitted asnull. Floats are formatted like recent versions ofserde_jsondo (for instance1e+16instead of1e16). Thespeedupsfeature now useszmijanditoato format numbers in JSON; the output is the same without it. - Made mutable
Statethe canonical path for functions, filters, tests, objects, methods, formatters, unknown method callbacks and nested macro calls. Read-only typed callbacks can continue to use&State; state mutation and dynamic calls now require&mut State. Added typed render-local extensions that persist through includes, blocks and macros.State::get_or_set_temp_objectwas removed in favor of these extensions. - Removed Rust APIs deprecated before 3.0:
Template::render_and_return_state,Template::render_to_write,Template::eval_to_state, theFilter,Test,TestResultandViaDeserializealiases,value::internand the no-opkey_interningfeature. The no-oploaderfeature was also removed; loader APIs remain available unconditionally. - Moved
format_filterandFormatStylefrom the crate root into the newformattingmodule and renamedformat_filtertoformatting::format. - Moved the
trim_blocks,lstrip_blocksandkeep_trailing_newlinesettings fromEnvironmentintoSyntaxConfig. AddedSyntaxConfig::to_builderto modify an existing configuration. Template introspection viaTemplate::undeclared_variablesnow honors the whitespace settings.WhitespaceConfigwas removed from theunstable_machineryAPIs. #470 - Removed the
custom_syntaxfeature. Custom delimiters, line statements and line comments are now always available. Theaho-corasickdependency was removed; start markers are now found with a simple scan which is as fast or faster and makes building a customSyntaxConfigabout 100 times faster. - Empty end delimiters are now rejected with
ErrorKind::InvalidDelimiter. - Changed
AutoEscape::Customto hold aCow<'static, str>so custom auto escape formats can be determined at runtime.AutoEscapeis no longerCopyandState::auto_escapenow returns a reference. - Changed
Valuefunction arguments to reject implicit keyword-argument values. Variadic functions that intentionally capture them can useValueOrKwargs. #596 - Added native Rust tuple conversions and
Value::from_pairs. Collecting pairs directly intoValuenow creates a sequence of tuples rather than a map. - Iteration now consistently fails on invalid values that iterators yield to report errors. Previously only loops did this while filters such as
list,joinorsort, theinoperator and serialization processed them like other items. The newValueIter::checkedhelper provides the same behavior for custom code. Environment::set_loaderis now generic over the returned source, which can be aStringor aTemplateSource. Closures that relied on type inference through.into()or only returnOk(None)need an explicit type.- Discontinued the
minijinja-autoreloadcrate in favor of built-in automatic reloading (see below).
Template Behavior
These changes apply to Rust and Go and therefore also to the Python and
JavaScript bindings.
- Added first-class tuple literals and public tuple value types. Tuples now preserve their type through serialization and sequence operations, render like Python tuples, and roundtrip as tuples through the Python bindings. JavaScript receives evaluated tuples as arrays. #785
- Changed sequence and map representations to use Python-style string quoting, and changed
tojsonto use Jinja2-compatible separator spacing. #785 - Changed floor division and modulo to match Jinja2 semantics. #935
- Changed division by zero to produce an error. #949
- Changed the
roundfilter to use round-half-even semantics and fixed decimal rounding edge cases. #948 - Changed built-ins to treat booleans as numbers for Jinja2 compatibility. #950
- Changed the
sequencetest to classify undefined values as sequences. #951 - Fixed the
upperandlowertests to reject non-strings and handle titlecase characters correctly. #952 - Fixed the
titlefilter treating every ASCII punctuation character as a word boundary. Words now start after whitespace or one of-,(,{,[and<as in Jinja2, so"don't"|titlerenders asDon'tinstead ofDon'T. #930 - Fixed the precedence of unary minus. Like in Jinja2,
-foo.barnow negatesfoo.barrather than looking upbaron-foo. - Fixed the
formatfilter accepting undefined values with strict and semi-strict undefined behavior. - Fixed constant-folded
andandorexpressions returning booleans instead of preserving their operands. #945 - Added support for format precisions beyond Rust's formatter limit. #946
- Limited repeated sequence sizes to prevent excessive allocations. #947
- Fixed custom delimiters where a start marker overlaps a longer one (for instance
<%and<%%=) sometimes picking the wrong marker.
Features and Improvements
- Added automatic reloading of individual templates. Loaders can now return a
TemplateSourcewith an up-to-date check in addition to a plainString, and the environment re-invokes the loader when a template is looked up that is no longer up to date.path_loaderattaches a check based on the file's modification time and size. Auto reloading is enabled by default and can be disabled withEnvironment::set_auto_reload. Templates are reloaded through a shared reference so the environment no longer needs to be recreated or guarded by a lock. Thememo-mapdependency was removed. #819 - Undefined values now remember where they were created when debug mode is enabled, and errors caused by undefined values report the expression that produced them (for instance
`user.name` is undefined), including where it came from if that was elsewhere. This adds no memory overhead to values. #871 - Limited the size of values shown in the referenced variables of debug info in Rust and Go. Long strings, sequences and maps (such as the environment in the CLI) and deeply nested values are now truncated. #871
- Improved the selection of referenced variables in debug info in Rust. Variables referenced before a
withblock or a filtered loop ({% for x in seq if cond %}) are no longer omitted when the error happens within or after it, and errors within macros no longer show variables from outside of the macro. Variables only referenced within the bodies of declared macros are no longer shown. - Improved the source excerpt in debug info in Rust and Go. Spans across multiple lines are now underlined on their first line, the marker lines up with tab indented source, and errors without a location no longer point to the first line. Rendering debug info in Rust no longer panics if the underlying writer fails.
- Reduced the size of the compiled code of
minijinjaby about 14% in a default release build. Sorting by attribute andgroupbyare roughly twice as fast.
Go
- Changed the MiniJinja-Go module path from
/v2to/v3. - Added
Environment.SetUnknownMethodCallback. #934 - Fixed
loop.changed()always reporting a change. #936 - Fixed context values converting whole-number floats into integers. #937
- Fixed empty iterators being incorrectly treated as non-iterable and truthy. #933
Python Bindings
- The Python bindings now require Python 3.10 or later.
- Updated PyO3 to 0.29.3 and dropped the deprecated
extension-modulefeature. Building now requires maturin 1.9.4 or later. - Significantly reduced the overhead of the Python bindings when templates access Python data. Values are converted without probing for types through exceptions, the shape of Python objects is determined once instead of on every access, attribute names are cached and filters no longer look up whether they want the state on every call. Renders no longer release the GIL as re-acquiring it for every callback into Python made renders orders of magnitude slower when other Python threads were busy. On free-threaded Python renders no longer contend on a process wide lock in PyO3 and now scale with the number of threads. Templates accessing Python data typically render 3 to 10 times faster.
- Fixed deadlocks when an environment is modified while another thread renders from it, or when it is used from within a callback during a render. Renders now work on a snapshot of the environment and no longer serialize on a shared lock. #955
- Fixed Python iterators that raise during iteration looping forever. The error is now raised from the render instead. #956
- Fixed large Python integers within MiniJinja's native integer range being silently rounded through
f64conversion. #944 - Fixed
pass_statenot working on methods invoked from templates. - Custom auto escape names are no longer leaked.
JavaScript Bindings
The JavaScript bindings were largely rewritten:
- Exceptions thrown by filters, tests and functions no longer panic and permanently break the environment. They now fail the render with an error whose
causeis the original exception. - Filters may now render from the same environment. Modifying the environment while it renders raises an error instead of breaking it, and so does setting an invalid
undefinedBehavior. - Fixed the spelling of the
semi_strictundefined behavior (wassemi_strct). - Filters, tests and functions now receive keyword arguments as a trailing object.
- Replaced the serde based value conversion. Plain objects and maps preserve their key order,
Map,Set,Date,BigInt,Uint8Arrayand typed arrays are supported, and class instances are accessed lazily with methods called on the instance. Cyclic structures fail with an error instead of overflowing the stack. - Maps with string keys are returned to JavaScript as plain objects instead of
Maps, none is returned asnulland integers outside of the safe range asBigInt. Functions and objects passed in are returned unchanged. - Added
SafeStringfor strings that should not be auto escaped. Callbacks receive safe strings asSafeStringand can return them to bypass auto escaping. - Errors are now raised as
TemplateErrorwithkind,detail,templateName,line,rangeandtemplateSourceproperties. - Added
addFunction,removeFilter,removeTest,setAutoEscapeCallback,setFinalizer,undeclaredVariablesInTemplateandundeclaredVariablesInStras well as thesyntaxproperty for custom delimiters and line statements and thepycompatproperty. - Improved the TypeScript declarations. Callbacks and contexts are now typed and
Context,SyntaxConfig,AutoEscapeandUndefinedBehaviorare exported. - Callbacks wrapped with
passStatereceive aStateobject to look up variables, inspect the template name and auto escape mode, and apply filters and tests. Callbacks can throwTemplateErrorto fail with a specific error kind. - Added optional date and time filters (
datetimeformat,dateformat,timeformatandnow()) inminijinja-js/datetimewhich use the native time zone support of the JavaScript runtime. - Enabled the
randcontrib feature (random,randrangeandlipsum) seeded fromMath.random. - Enabled the
urlencode,loop_controlsandunicodefeatures as well as thehtml_entitiesandwordwrapcontrib features to match the Python bindings. - The context argument of
renderStr,renderTemplate,renderNamedStrandevalExpris now optional. - The npm package is now an ES module with a single wasm build and an
exportsmap. Node.js loads the wasm module synchronously (and supportsrequire()on versions withrequire(esm)), browsers, bundlers, Deno and Bun load it with top-level await. Theminijinja-js/initentry point allows initializing the module manually. Thedist/node,dist/webanddist/bundlerpaths are gone. The package is now about a quarter of its previous size. - Removed the
fragileandserde-wasm-bindgendependencies and updatedwasm-bindgento 0.2.129.
Contrib
- Switched the date and time filters from
time/time-tzto Jiff. Custom formats now usestrftime-style syntax instead oftimeformat descriptions. With thetimezonefeature, time zones are looked up through the system time zone database when available. Thedatetimefeature no longer depends onserde. #694 - Added
minijinja_contrib::rand::set_seed_sourceto provide seeds for the random functions on platforms without a source of randomness.
CLI
- The REPL now shows
undefinedfor expressions that evaluate to undefined. - Substantially reduced the dependencies of the CLI.
clap,rustylineand theserdebased data format crates were replaced byargument,minilineanddeser. Fig completions are no longer supported. The minimum supported Rust version of the CLI is now 1.88.
Install minijinja-cli 3.0.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/mitsuhiko/minijinja/releases/download/3.0.0/minijinja-cli-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/mitsuhiko/minijinja/releases/download/3.0.0/minijinja-cli-installer.ps1 | iex"Download minijinja-cli 3.0.0
| File | Platform | Checksum |
|---|---|---|
| minijinja-cli-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| minijinja-cli-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| minijinja-cli-aarch64-pc-windows-msvc.zip | ARM64 Windows | checksum |
| minijinja-cli-i686-pc-windows-msvc.zip | x86 Windows | checksum |
| minijinja-cli-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| minijinja-cli-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| minijinja-cli-i686-unknown-linux-gnu.tar.xz | x86 Linux | checksum |
| minijinja-cli-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |
| minijinja-cli-armv7-unknown-linux-gnueabihf.tar.xz | ARMv7 Linux | checksum |
| minijinja-cli-aarch64-unknown-linux-musl.tar.xz | ARM64 MUSL Linux | checksum |
| minijinja-cli-i686-unknown-linux-musl.tar.xz | x86 MUSL Linux | checksum |
| minijinja-cli-x86_64-unknown-linux-musl.tar.xz | x64 MUSL Linux | checksum |