Skip to main content

Advanced Templates

By default CodeBaker copies every file of your template into the folder you generated from, keeping the template's folder structure, and applies your variables to file names and file contents.

Advanced templates go beyond that. A template can:

  • put a file somewhere else, or under a different name
  • name or place a file depending on your variables and generate a file only when a condition holds (Loops and Conditions)
  • generate one source file or a whole folder several times (a loop, same page)
  • write a file only once and never overwrite your later edits (seed), update one block inside an existing file (insert) or swap the whole content of a block (replace) (Seed, Insert and Replace)
  • add or update an entry in a JSON list (json-upsert, JSON Upsert)
  • read data from another file, another part of the document or a web address (Data Sources)
  • leave a template file out of the output

Where it is set up​

All of this is described in one optional file, codebaker.advanced.json5, in the template folder. A template without it simply copies its files.

.CodeBaker/
└── MyTemplate/
├── codebaker.config.json5 ← variables, name, version
├── codebaker.advanced.json5 ← file placement and generation rules (optional)
└── ...your template files

The file has two sections:

{
// Runs first, in the order written. Use it to prepare shared files other files depend on.
init: { /* ... */ },

// Everything else.
mapping: { /* ... */ },
}
The mapping file is rendered first

Before it is read, codebaker.advanced.json5 is run through the template engine with all your variables. So {{ name }} works anywhere in it (in a target, an id, a data value), and so does a style you declare under placeholders, such as ___name___. A for loop can create items (see Loops and Conditions in the Mapping File). The examples on this page use ___name___-style placeholders, so their templates declare placeholders: ["___VAR___"] in the config.


Mapping entries​

Each entry is "path of a file or folder inside the template": <value>.

  • The key is the source path inside the template folder, written with forward slashes (js/entities.js).
  • A key that matches nothing in the template is silently ignored. (A missing source under init logs a warning in the generation log instead.)

The value is one of:

ValueMeaning
"path/name.ext"Generate it once, at that path. Shorthand for a list with a single string.
[ "path/name.ext", ... ]Generate it once per string, at that path.
{ target: "...", ... }Generate it once, with its own target, data and type. Shorthand for a list with a single item.
[ { target: "...", ... }, ... ]Generate it once per item, each with its own target, data and type.
false or []Do not generate this file or folder at all.
mapping: {
// Put a file somewhere else (one target, written as a plain string)
"Model.php": "app/Models/___modelName___.php",

// Do not copy this file
"notes.md": false,

// One source, two outputs, each with its own data
"card.html": [
{ target: "cards/a.html", data: { title: "Alpha" } },
{ target: "cards/b.html", data: { title: "Beta" } },
],
}

Files you do not list are copied as they are. A file that is claimed in init is left out of this ordinary copy automatically: you do not need a second false entry for it.

What an item can contain​

KeyMeaning
targetWhere to write it (required). See below.
dataAn object of extra variables for this output. Its keys override variables of the same name from codebaker.config.json5.
typeseed, insert, replace or json-upsert. No type means an ordinary file.
formatWhat the text of the file is turned into. "json" writes it as plain JSON; false or "json5" writes it as it is. Not set: a .json5 source with a .json target is converted to JSON, everything else is written as it is. See Choosing the format.

insert, replace and json-upsert items take a few more keys, described on their own pages.

Where does target point?​

  • A plain path is relative to where the source file's own folder is generated.
  • A leading / makes it relative to the root of the generated output (the folder you generated into).
  • .. goes one folder up (it never goes above the output root).
  • Declared placeholders (such as ___name___) and {{ name }} in a target are replaced like everywhere else.

Folders​

An entry can name a folder. A target on a folder renames its output location, and everything inside follows. A list with several items clones the whole folder, each clone with its own data. data on a folder is inherited by every file inside it (a file's own data with the same key replaces the folder's value for that key, as a whole: nested objects are not merged).

A seed on a folder applies to every file inside

type: "seed" on a folder item makes every file inside it, at any depth, a seed: written once and kept on the next generations. A file that has a mapping entry of its own uses that entry instead, so you can exclude one file (false) or write it as an ordinary file. The other types (insert, replace, json-upsert) change one file, so on a folder they stop the generation with a message that says to set them on the file. So does format.



The init phase​

init is the first section of the mapping file. Its entries are written before everything else, in the order you wrote them, and they are meant for a file that other entries read.

{
init: {
// 1. First: add this run's entry to the registry file
"entity-record.json5": [{ target: "entities.json5", type: "json-upsert" }],
},

mapping: {
// 2. Later: a file that reads the registry, with everything this run has already put in it
"js/entities.js": [
{ target: "entities.js", data: { entities: { "$file": "target://entities.json5" } } },
],
},
}
  • It runs on every generation, not only the first. The name says it runs first, not once.
  • It names files. An init entry is a source file of the template, written with the same keys as a mapping entry (target, type, data, ...). A source named in init is not copied again by the ordinary pass, so you do not need a second false entry for it. A source that does not exist logs a warning in the step 2 log instead of stopping the generation.
  • The order is the order you wrote. The ordinary files, the seed files and the json-upsert entries that are not in init have no defined order among themselves. That is the reason to put anything another file reads into init.
  • When a later file reads it, use $file with target://, which sees what this very run has already produced even before it is written to disk.

Entry types and run order​

No type means an ordinary file: it is written on every generation, and the diff preview is your safety net.

Generation runs in three passes, one after another:

  1. init entries, in the order you wrote them.
  2. All ordinary files, seed files and json-upsert entries (the order inside this pass is not defined).
  3. insert and replace entries, last.

Anything that other files read, for example a json-upsert data file, belongs in init. Anything an insert writes into is produced earlier, in pass 1 or 2.

Which one do I use?​

I want to...Use
write a file again on every generationan ordinary entry: no type
write a file once and never touch it againtype: "seed"
add or update one block inside a file that existstype: "insert" with a slot
swap the whole content of a slottype: "replace"
add or update an entry of a JSON listtype: "json-upsert"
write the same file several timesa list of items
leave a file outfalse
prepare a file that other entries readan entry in init

The types in detail:


Things to watch out for​

  • The mapping file is processed as a template. Do not write a literal {{, {% or a CodeBaker: comment as example text in a comment. It is read as real syntax. Describe it in words instead.
  • A .json5 source written as .json is converted to plain JSON (comments, trailing commas and the other JSON5 forms are removed). Set format: false on the entry to write the text as it is. See Choosing the format.
  • Preview first. Step 3 of the wizard, Code Preview, shows every file before anything is written, and you can edit the text there.


See also​