Dynamic Files
If you not using a version of templates above 1.1.0
, please refer to the
legacy documentation.
Dynamic files
Dynamic files are files that can be rendered with dynamic data, similar to how template engines work. You use a special syntax that gets replaced with the data you provide. This output is then saved into the file where the instance is being generated.
A dynamic file with these contents:
{{= tps.answers.name }} wants some more cake
will produce something like the following:
lino wants some more cake
Dont worry about the specifics, well touch more on the syntax later on this page.
Template engine
Our template engine was forked from the doT template engine. This provides great flexibility when creating your templates, along with additional features we provide to help with whitespace control.
Keep in mind that all white space in the file will be preserved during render. At times, you may find yourself adding extra whitespace to make your template more readable. However, avoid doing this because the whitespace will remain in your new instance.
Use our playground page to test dynamiclly files without having to generate them.
Our template engine supports two main syntaxes: inline
and block
.
Inline syntax
Inline syntax is implemented using double curly brackets at the start and end of
your expression {{ ... }}
. This syntax is more flexible and is recommended
when dealing with different control flows inline.
Take this template that is using a inline interpolation statement to render the name of instance as this react components name.
export const {{= tps.name }} = () => {
/* rest of react component code... */
}
null
We will be using references to the templates context
object like tps.name
, tps.answers.*
, tps.utils.*
and others throughout
this document. However, we won't cover this object in detail until later in the
guide. For now, keep in mind that tps.name
is the name of the instance you are
generating, utils
is a collection of helper functions designed to make
creating dynamic templates easier, and answers
is an object that holds the
information the user of your template passes to it.
Block syntax
Block syntax is implemented using three curly brackets at both the start and end
of your expression {{{ ... }}}
. The main difference is that block syntax needs
to be placed on a new line, and it will remove all whitespace in front of and
behind the brackets, completely removing the line from the output.
Take this example using a block if statement: when css
is false
notice how
there is no trace that anything was there before. No new lines, no spaces,
nothing. It was completely wiped.
import React from 'react';
{{{? tps.answers.css }}}
import './component.css';
{{{?}}}
null
Now when css
is true
notice how the only thing being rendered is whats
inside the if statement.
null
You can also indent block syntax and all tabs and spaces will be removed.
const App = () => {
{{{? tps.answers.state }}}
const [state, setState] = useState(null);
{{{?}}}
return (
/* other code ... */
);
};
null
Example: Why is there inline and block?
Block syntax plays a special role in file generation. By removing all existence of white space, your rendered output looks nice and clean. Templates aims to not only make development eaiser but also enforce standards and best practices.
Before block syntax, there was only inline syntax. However there was a lot of issues with the way these tags got parsed that left outputed files with unindented whitespace.
example:
Lets use the example template from above and change block syntax to inline
syntax. Now when css
& state
are false
, notice how there is extra
whitespace behind.
import React from 'react';
{{? tps.answers.css }}
import './component.css';
{{?}}
const App = () => {
{{? tps.answers.state }}
const [state, setState] = useState(null);
{{?}}
return (
/* other code ... */
);
};
null
Now when css
& state
are true
, you can also see extra lines.
null
In order to get the perfect output, you needed to sacrafice the readability of the template and do something like the following:
import React from 'react';
{{? tps.answers.css }}import './component.css';
{{?}}
const App = () => {
{{? tps.answers.state }}const [state, setState] = useState(null);
{{?}}
return (
/* other code ... */
);
};
null
Now when css
& state
are true
, you can also see extra lines.
null
While this might not look the worst for one answer, imagine 3 or 4. Now lets see how block syntax improves this:
import React from 'react';
{{{? tps.answers.css }}}
import './component.css';
{{{?}}}
const App = () => {
{{{? tps.answers.state }}}
const [state, setState] = useState(null);
{{{?}}}
return (
/* other code ... */
);
};
null
Now when css
& state
are true
,
null
Template Syntaxes
Evaluation
- Block
- Inline
{{{
<expressions...>
}}}
{{ <expressions...> }}
Evaluation are achieved by using inline or block brackets. This allows you to write any arbitrary JavaScript expressions. You can create variables, functions, conditions, the possibilities are endless. This can be helpful for simplifying more complex expressions. The semi-colon at the end of each expression is mandatory!
- Block
- Inline
{{{
const name = "Marcellino Ornelas";
const age = 21;
const ageCategory = age > 18 ? "Adult" : "Child";
}}}
/* rest of code... */
null
Inline evaluations are not recommend due to the white space they leave behind. However we still support this syntax due to legacy reasons but may be removed in future versions.
{{ const name = "Marcellino Ornelas"; }}
{{= name }}
null
Checkout our API docs for more examples on how you can take advantage of evaluations.
Interpolation
- Inline
{{= <expression> }}
Interpolation is achieved using the equal sign operator =
along with inline
brackets. This operator allows you to render dynamic output into the files
contents.
- Inline
Take this template that is using a inline interpolation statement to render the name of instance as this react components name.
export const {{= tps.name }} = () => {
/* rest of react component code... */
}
null
Checkout our API docs for more examples on how you can take advantage of interpolations.
Conditionals
- Block
- Inline
{{{? <condition> }}}
if output
{{{?? <else-if-condition> }}}
else if output
{{{??}}}
else output
{{{?}}}
{{? <condition> }}if output{{?? <else-if-condition> }}else if output{{??}}else output{{?}}
Conditionals are achieved using the question mark operator ?
, along with
inline or block brackets. The result of the provided expression will be rendered
into the output of the file when the expression returns a truthy value.
- Block
- Inline
Take this template below that is using a block if statement to conditionally
render an css import when the css
answer is true
.
import React from 'react';
{{{? tps.answers.css }}}
import "styles.css";
{{{?}}}
null
Now when css
is false
, no output will be displayed.
null
Take this template that is using a inline if statement to conditionally render a
typescript type to the variable when the typescript
answer is true
.
const name{{? tps.answers.typescript }}: string{{?}} = 'lino';
null
Now when typescript
is false
, no typescript typings will be added.
null
Else
You can also write else statements by adding an additional inline or block
brackets that contains two question mark operators ??
.
- Block
- Inline
Take this template that is using a block if-else statement to conditionally
render a import statement. If cssLang
is modules
, it will render an import
statement for CSS modules; otherwise, it will render a normal CSS import
statement.
import React from 'react';
{{{? tps.answers.cssLang === 'modules'}}}
import styles from './{{= tps.name}}.css';
{{{??}}}
import './{{= tps.name}}.css';
{{{?}}}
null
Now when cssLang
is not modules
, we will get a normal CSS import
statement.
null
Take this template that is using a inline if-else statement to conditionally
render a component name. If component
is set to a value, it will use the value
of component
as the component name; otherwise, it will render a div
.
const App = () => {
return (
<{{? tps.answers.component }}{{= tps.answers.component }}{{??}}div{{?}}>
/* contents... */
</{{? tps.answers.component }}{{= tps.answers.component }}{{??}}div{{?}}>
);
};
null
Now when component
is not set to a value, we will get a div
.
null
Else if
You can also write else-if statements by adding an additional inline or block
brackets that contains the two question mark operators ??
with a expression
afterwards.
- Block
- Inline
Take this template that is using a block else-if statement to conditonally
render a import statement. If cssLang
is modules
, it will render an import
statement for CSS modules. Else if css
is true
, it will render a CSS import
statement. Otherwise it will render nothing.
import React from 'react';
{{{? tps.answers.cssLang === 'modules'}}}
import styles from './{{= tps.name}}.css';
{{{?? tps.answers.css }}}
import './{{= tps.name}}.css';
{{{?}}}
null
Now when cssLang
is not modules
and css
is true
, you will get a normal
css import statement.
null
Lastly when cssLang
is not modules
and css
is false
, you will get no
rendered output.
null
Take this template that is using a inline else-if statement to conditonally
render a component name. If component
is page
, it will render the
PageComponent
with a block
prop. Else if component
is set to a value, it
will render the value as the component name. Otherwise, it will render a div
.
const App = () => {
return (
<{{? tps.answers.component === 'page' }}PageComponent block{{?? tps.answers.component }}{{= tps.answers.component }}{{??}}div{{?}}>
/* contents... */
</{{? tps.answers.component === 'page' }}PageComponent{{?? tps.answers.component }}{{= tps.answers.component }}{{??}}div{{?}}>
);
};
null
Now when the component
answer is not page
, it will use whatever the user
passed into it. In this case we passed in Box
.
null
Lastly, if no answer was given for component
, we will get a div
.
null
Checkout our API docs for more examples on how you can take advantage of conditionals.
Loops
- Block
- Inline
{{{~<array> :<value>:<index>}}}
loop output
{{{~}}}
{{~<array> :<value>:<index>}}loop output{{~}}
Loops are achieved using the ~
operator along with inline or block brackets.
The contents of the loop will be rendered into the output of the file for each
item in the array.
- Block
- Inline
Take this template using a block loop statement to render import statements to
the react file when modules
is specified.
import React from 'react';
{{{~tps.answers.modules :value}}}
import {{= value}} from "@app/{{= value}}";
{{{~}}}
null
Take this template using a inline loop statement to render props to the react
component when props
are passed.
{{{
const propsLastIndex = tps.answers.props.length - 1;
}}}
const App = ({ {{~tps.answers.props :value:index}}{{= value + (index !== propsLastIndex ? ", " : "") }}{{~}} }) => {
return (
/* react code .... */
);
};
null
If you dont need anything to complex, then use join
!
const App = ({ {{= tps.answers.props.join(", ")}} }) => {
return (
/* react code .... */
);
};
null
Checkout our API docs for more examples on how you can take advantage of loops.
Defs (Partials)
- Block
- Inline
{{{##def.<name>:<arg>:
def contents
#}}}
{{##def.<name>:<arg>:def contents#}}
Defs, or partials, are achieved using ##def
at the beginning and #
at the
end of inline or block brackets. Defs allow you to create reusable sections of a
template. Think of them as smaller templates that can be defined separately and
then included within larger templates. This provides modularity and simplifies
the process of maintaining and updating your templates.
To render the contents of the def, use #def
along with inline brackets.
{{#def.<name>:<arg>}}
- Block
- Inline
Take this template thats creating a complexExpression
def that will render a
complex expression in both layout options one
& two
but not in any others.
Utilizing a def for this allows you to maintain one version of the logic without
duplicating it in your template.
{{{##def.complexExpression:
const someComplexExpression = /* some complex expression */;
#}}}
{{{? tps.answers.layout === "one"}}}
/* layout one code... */
{{#def.complexExpression}}
{{{?? tps.answers.layout === 'two'}}}
/* layout two code... */
{{#def.complexExpression}}
{{{??}}}
/* default layout code... */
{{{?}}}
null
{{##def.intro:This is my intro#}}
{{#def.intro}}
null
Inline defs are not recommend due to the white space they leave behind. However, we still support this syntax for legacy reasons, though it may be removed in future versions. You can achieve the same result with a block def:
{{{##def.intro:
This is my intro
#}}}
{{#def.intro}}
null
Args
You can also pass one argument to a def. This argument can be any valid javascript value.
- Block
- Inline
{{{##def.complexExpression:useTypescript:
const someComplexExpression{{? useTypescript ?? false }}: string{{?}} = /* some complex expression */;
#}}}
{{#def.complexExpression:true}}
null
{{##def.intro:name:My name is {{= name}}#}}
{{#def.intro:"lino"}}
null
Inline defs are not recommend due to the white space they leave behind. However, we still support this syntax for legacy reasons, though it may be removed in future versions. You can achieve the same result with a block def:
{{{##def.intro:name:
My name is {{= name}}
#}}}
{{#def.intro:"lino"}}
null
If you need to pass more information to the def, you can pass an object as the
argument and add all the information to the object. Keep in mind that if you
pass an object directly, you need to follow it with a semicolon ;
or a space.
- Block
- Inline
{{{##def.someExpression:user:
const name = "{{= user.name}}";
const age = {{= user.age}};
#}}}
{{#def.someExpression:{ name: "lino", age: 24 }; }}
null
{{##def.someExpression:user:My name is {{= user.name}} and my age is {{= user.age}}#}}
{{#def.someExpression:{ name: "lino", age: 24 }; }}
null
Inline defs are not recommend due to the white space they leave behind. However, we still support this syntax for legacy reasons, though it may be removed in future versions. You can achieve the same result with a block def:
{{{##def.intro:user:
My name is {{= user.name}} and my age is {{= user.age}}
#}}}
{{#def.intro:{ name: "lino", age: 24 }; }}
null
Functions
You can also define defs as an javascript function.
- Block
- Inline
{{{##def.renderIntro = function(name) {
return `My name is ${name}`;
}#}}}
{{#def.renderIntro("lino")}}
null
{{##def.renderIntro = function(name) {
return `My name is ${name}`;
}#}}
{{#def.renderIntro("lino")}}
null
Inline defs are not recommend due to the white space they leave behind. However, we still support this syntax for legacy reasons, though it may be removed in future versions. You can achieve the same result with a block def:
{{{##def.renderIntro = function(name) {
return `My name is ${name}`;
}#}}}
{{#def.renderIntro("lino") }}
null
How to utilize the template engine
There are three main ways to use our template engine to make your template dynamic, in file contents, files names, and def files.
File contents
To use our template engine in files, you must add a .tps
extension to the end
of your file name. When a new instance is rendered, this extension will be
removed, leaving any other extension that was before the .tps
extension
intact.
const express = require("express");
{{{? tps.answers.security }}}
const helmet = require("helmet");
{{{?}}}
const app = express();
{{{? tps.answers.security }}}
app.use(helmet());
{{{?}}}
/* node code ... */
null
File names
Every file within your template can utilize the template engine's capabilities directly in its file name. This allows for dynamic naming of files when creating a new instances.
Take this template named react-component
, which includes a file named
{{=tps.name}}.js
:
| - .tps/
| - react-component
| - default
| - {{=tps.name}}.js
Now if you rendered a new instance of this template named Nav
:
tps react-component Nav
then you'll end up with:
| - Nav/
| - Nav.js
Any functionality of our template engine can be used in files names, however here are some common use cases that we use when we build templates.
- Interpolation
- utils
{{= tps.name}}.js
If name
was Nav
then the result would be:
Nav.js
{{= tps.utils.camelCase(tps.name)}}.js
If name
was my-nav
then the result would be:
myNav.js
Def files
Templates allow you to define defs inside files for easier use. Def files gets
loaded before any files are rendered so you have access to all defs you define
inside your template files. Any file with a .def
extension in any of your
packages is considered a def file.
| - .tps/
| - react-component
| - default/
| - helpers.def
| - some-other-file.js
If you want your def files to be accessible everywhere, put them inside your
default
package. If your def file is only used inside a specific package then
place it inside that package.
Def files placed inside packages may be usable inside template files inside other packages, however this is discouraged and may not be supported in other versions.
Def files can be used in two ways:
single def file
Single def files will use the entire file contents as the def contents. The name
of this def will be the filename. You can access this def inside your template
files by using {{#def.<file-name>}}
.
Take this react-component
template that has a imports.def
def file and
{{=tps.name}}.jsx.tps
file.
| - .tps/
| - react-component/
| - default/
| - imports.def
| - {{=tps.name}}.jsx.tps
Inside the imports.def
, we have all the imports statements you will need for
your react components.
import React from 'react';
import '{{= tps.name}}.css';
Now inside {{=tps.name}}.jsx.tps
we can use this def by the file name.
{{#def.imports}}
export const {{=tps.name}} = () => {
/* react component stuff ... */
};
null
Multi def file
Multi def file is a def file that contains multiple defs. Multi def files uses the defs syntax in order define more than one.
Take this react-component
template that has a helper.def
def file and
{{=tps.name}}.jsx.tps
file.
| - .tps/
| - react-component/
| - default/
| - helper.def
| - {{=tps.name}}.jsx.tps
Inside the helpers.def
, we have all the imports statements you will need for
your react components and the props interface for when typescript is used.
{{{##def.imports:
import React from 'react';
import '{{= tps.name}}.css';
#}}}
{{{##def.propInterface:
interface Props {
/* props */
}
#}}}
Now inside {{=tps.name}}.jsx.tps
we can use these defs.
{{#def.imports}}
{{{? tps.answers.typescript}}}
{{#def.propsInterface}}
{{{?}}}
export const {{=tps.name}} = () => {
/* react component stuff ... */
};
null
Templates Context
After converting a file to a dynamic file, you gain access to the template's context object. This object contains information about the template and rendering metadata. The context object can be referenced in any of our template syntaxes.
{{ tps }}
We'll go over the most common properties of this object. For a full list, you can refer to the tps context API.
Name
{{= tps.name }}
Name of new instance you are rendering. When rendering two or more instances at the same time. The same concept as above apply's but for each path you pass in.
If you generate a new instance of a template with no build path. Then tps.name
will be null.
Example
tps react-component Nav
const {{= tps.name}} = (props) => {
return (
<div></div>
)
}
null
Dont remember what the template name is? Refresh your mind here.
Answers
{{= tps.answers.<prompt-name> }}
tps.answers
is an object that holds the responses your user provided for your
prompts. Prompts are a way for template owners to ask users for
additional information about how they want their template created. However
prompts aren't covered until a bit later in this guide.
Example
when a user creates a new instance of a template:
tps react-component Nav
They will prompted a list of questions. In this case if they "want to use a css file". Template owners can now use the answers object to get the value the user answered.
import React from 'react';
{{{? tps.answers.css }}}
import "styles.css";
{{{?}}}
null
Utils
{{= tps.utils }}
tps.utils
is a collection of helper functions to make creating templates
easier. We've integrated powerful tools from
change-case and
inflection to streamline your
workflow.
here are a list of some:
- camelCase
- capitalCase
- constantCase
- dotCase
- headerCase
- paramCase
- pascalCase
- snakeCase
- pluralize
- singularize
- camelize
- underscore
- humanize
- capitalize
To explore all the available functions, check out the tps context utils API.
Example
tps react-component nav
import React from react;
const {{= tps.utils.pascalCase(tps.name)}} = (props) => {
return (
<div></div>
)
}
null