solid-mustache
v4.5.0
Published
{{mustache}} templates compiling to Solidity
Downloads
2
Readme
Solid Mustache
{{mustache}} templates compiling to Solidity
- Compiles templates to Solidity libraries
- Supports the following mustache/handlebars expressions:
- path expressions:
{{person.name}}
- conditionals:
#if
&#unless
- iterators:
#each
- partials:
{{> partial}}
- path expressions:
- Uses @handlebars/parser for parsing mustache syntax
Content type agnostic
Mustache templates are agnostic to the content type of the template document, meaning you can use solid-mustache to generate templates for a wide variety of use cases.
Logic-less
The expressiveness of the template syntax is deliberately limited, forcing you to put logic elsewhere and promoting separation of concerns.
Automatic and manual type narrowing
Since Solidity is statically typed, the input values for the template need type definitions. solid-mustache automatically derives types for template inputs, so you don't have to worry about this aspect. However, it also supports optional annotations in template expressions for using more gas efficient fixed length types. Learn more about it in section "Input data types".
How to use
Installation
Add solid-mustache as a dev dependency. With npm:
npm install --save-dev solid-mustache
Or with yarn:
yarn add -D solid-mustache
Compile template file
To compile a template file to Solidity, run:
npm run solid-mustache ./path/to/template.hbs
The compiled template library will be written to ./path/to/template.sol
.
Writing templates
Template expressions
solid-mustache uses the mustache syntax of double curly braces for template expressions:
Hello {{firstName}} {{lastName}}!
This template compiles to a library with a render
function taking an input argument of the following type:
struct __Input {
string firstName;
string lastName;
}
Template expressions can also contain path expressions, like:
{{planets[i].name}}
Warning: Contrarily to handlebars.js, interpolations won't be escaped automatically in solid-mustache. If necessary, this must be taken care of before passing the interpolation values to the template's render function.
Conditionals
For conditional rendering use if
block expressions:
{{#if active}}
ON
{{/if}}
For the parameter following #if
any kind of path to a boolean value may be used, but boolean expressions are not supported.
You can however realize else
constructs using the negated #unless
block expression:
{{#unless active}}
OFF
{{/unless}}
Iterators
The #each
block expressions allows you to iterate array type inputs, rendering a block of content repeatedly for each item:
{{#each planets}}
{{name}}
{{/each}}
Note that using an #each
block spawns a new context for its content block.
Any path expression within the content block is evaluated relative to the current item of the iteratee.
So in the example above {{name}}
is evaluated as planets[index].name
.
Input data types
The compiler auto-generates a struct type for the input data argument to the template's render function. It uses some heuristics for choosing appropriate types for struct fields:
| condition | example | type chosen |
| :------------------------------------- | :---------------- | :------------------------------------------------------------------------- |
| simple output | {{title}}
| string title;
|
| reference to field in path expression | {{person.name}}
| Person person;
creates new struct: struct Person { string name; }
|
| reference via index in path expression | {{items[0]}}
| string[] items;
|
| iterator | {{#each items}}
| string[] items;
|
| conditional | {{#if active}}
| bool active;
|
For gas cost reasons it might be preferable to use fixed length types when possible. This can be achieved by using built-in helper syntax:
| condition | example | type chosen |
| :------------------------------ | :------------------------- | :----------------- |
| iterator with length
hash arg | {{#each items length=4}}
| string[4] title;
|
| bytes<N>
helper | {{bytes8 title}}
| bytes8 title;
|
Templates also support integer to string conversion, so that input fields can be marked as uint
/int
:
| condition | example | type chosen |
| :--------------- | :----------------- | :-------------- |
| uint<N>
helper | {{uint number}}
| uint number;
|
| int<N>
helper | {{int16 number}}
| int16 number;
|
The integer to string conversion even allows printing integers with a fixed number of decimal places, for example:
| expression | myNumber
value | printed result |
| :------------------------------ | :--------------- | :------------- |
| {{uint8 myNumber decimals=2}}
| 123
| 1.23
|
| {{int16 myNumber decimals=3}}
| -9
| -0.009
|
Partials
Partials allow reusing templates from other templates.
Any normal template can be used as a partial.
In order to make it available, a partial must be registered under a name using the partials
option when compiling.
You can then call the partial through the partial call syntax:
{{> myPartial}}
It's possible to execute partials on a custom context by passing a path expression to the partial call:
{{> myPartial myStructField}}
Partials are useful for splitting large templates into multiple Solidity libraries to keep each one of them within the EVM contract size limit.
This is achieved using the extra
hash param, specifiying the name for the extra library to split out for the partial:
{{> myPartial extra="MyPartial" }}
Configuration
solid-mustache uses cosmiconfig for configuration file support. This means you can configure it using any of the following ways:
- A
"solid-mustache"
key in your package.json - A
.solid-mustacherc
file in JSON format. - A
.solid-mustacherc.json
file. - A
.solid-mustacherc.js
,.solid-mustacherc.cjs
,solid-mustache.config.js
, orsolid-mustache.config.cjs
file that exports an object usingmodule.exports
.
The configuration file will be resolved starting from the location of the template file and searching up the file tree until a config file is found.
All configuration options can also be specified as CLI arguments. CLI arguments override values from configuration files. Config files are not read if using solid-mustache via the JavaScript API.
Options
Name
Specify the name of the generated Solidity library or template.
| Default | Config field | CLI Override |
| ------------ | ---------------- | ----------------- |
| "Template"
| name: <string>
| --name <string>
|
Solidity pragma
Define the Solidity pragma for the compiled .sol file.
| Default | Config field | CLI Override |
| ----------- | -------------------------- | ---------------------------- |
| "^0.8.6""
| solidityPragma: <string>
| --solidity-pragma <string>
|
Header
Define a custom header for the .sol file.
| Default | Config field | CLI Override |
| ------------------------------------------- | ------------------ | ------------------- |
| "// SPDX-License-Identifier: UNLICENSED""
| header: <string>
| --header <string>
|
Condense whitespace
Condense sequences of consecutive whitespace characters into a single space character.
| Default | Config field | CLI Override |
| ------- | ---------------------------- | ------------ |
| false
| condenseWhitespace: <bool>
| --condense
|
Partials
Register partials.
When using the CLI, partials are specified as paths to the partial template files. The partials are registered under their respective file names (without extension).
By default, a glob pattern is used based on the dirname of the template file and the filename pattern **.partial.hbs
.
When using the API, partials are specified as an object, where keys are the partial names and values are the partial template strings.
| Default | Config field | CLI Override |
| ------- | ----------------------------------------- | ------------------- |
| | partials: { <name0>: <template0>, ... }
| --partials <glob>
|
Deduplication
Extract duplicate template substrings longer than the specified threshold into constants to potentially reduce the bytecode size.
| Default | Config field | CLI Override |
| ------- | ------------------------ | -------------------------- |
| | dedupeThreshold: <int>
| --dedupe-threshold <int>
|
Print Width
Specify the line length that the printer will wrap on.
| Default | Config field | CLI Override |
| ------- | ------------------- | --------------------- |
| 80
| printWidth: <int>
| --print-width <int>
|
Tab Width
Specify the number of spaces per indentation-level.
| Default | Config field | CLI Override |
| ------- | ----------------- | ------------------- |
| 2
| tabWidth: <int>
| --tab-width <int>
|
Tabs
Indent lines with tabs instead of spaces.
| Default | Config field | CLI Override |
| ------- | ----------------- | ------------ |
| false
| useTabs: <bool>
| --use-tabs
|
Quotes
Use single quotes instead of double quotes.
| Default | Config field | CLI Override |
| ------- | --------------------- | ---------------- |
| false
| singleQuote: <bool>
| --single-quote
|
Bracket Spacing
Print spaces between brackets.
| Default | Config field | CLI Override |
| ------- | ------------------------ | ---------------------- |
| true
| bracketSpacing: <bool>
| --no-bracket-spacing
|
Explicit Types
Use explicit types (uint256
) rather than aliases (uint
).
| Default | Config field | CLI Override |
| ------- | ----------------------- | --------------------- |
| true
| explicitTypes: <bool>
| --no-explicit-types
|
API
CLI
solid-mustache <template_file> [options]
The compiled .sol file will be written to the directory containing the template file. By default it will use the template's filename, but with a .sol extension.
For writing to a different path, use the --output <path>
option.
Additionally, all general options can be specified.
JavaScript
compile(template: string, options?: Options): string
The first argument (template
) is the template string to compile.
The optional second argument allows customizing the compile options.
Contribute
This package aims to be compatible with handlebars. Specifically, every template that can be compiled with solid-mustache shall also be supported in handlebars. The inverse is not necessary, but we aim for it as far as it's reasonable. If you see unexpected rendering results for your templates, submit an issue or, even better, create a PR adding your template as new test case in test/cases.
How test cases are structured
Each test case is represented by a folder, the folder name is the test case name.
In the folder there is a single template file. The file name must start with template
and end with .hbs
, e.g.: template.svg.hbs
.
The folder also contains one or more json files with different test inputs.
These json files must be named with incrementing index starting at 0
: 0.json
, 1.json
, 2.json
, ...
When running the tests, the template will be rendered for each input json and the result snapshot is written to a new file in that same folder.
For example, the template.svg.hbs
with input 0.json
is written to 0.svg
.
To update result snapshots, run yarn test:update
.