gittech. site

for different kinds of informations and explorations.

JavaScript template strings, but slightly better

Published at
Jan 9, 2025

Slightly better templates

Motivation

The built-in string templating functionality of JavaScript is great for generating text or code, and with a few small additions, can do basically everything I'd want it to. The main thing missing was handling whitespace and indentation sensibly, both in the input and output, so your source code and the generated text can both have appropriate formatting.

Couldn't find an existing library that does that, so here we are.

Usage

Add templating.ts to your project, use its exports as needed. (Pre-built ES modules also available under dist) There are some automated tests in test.ts, and a usage example in example.ts.

Features

Tagged template literals are used to create the templated strings, which can then be formatted with various options:

import { t, format } from './templating';

format(t`Hello, World!`, { indentWith: '---' });
/* =>
---Hello, World!
*/

Indentation from the first non-blank line is used as a baseline, so it can match its surrounding context without changing the output.

format(t`

    Greetings,
        Earth!

`);
/* =>
Greetings,
    Earth!
*/

Interpolating expressions works the same way as in vanilla template strings:

format(t`Testing ${1 + 2}`) // => Testing 3

Interpolating arrays repeats the containing line for each item in the array:

format(t`
${[1, 2]} fish
    ${['red', 'blue']} fish
`)
/* =>
1 fish
2 fish
    red fish
    blue fish
*/

This works with multiple equal-length arrays as well:

const cols = ['A', 'B'];
const rows = ['1', '2'];
format(t`${cols} - ${rows}`)
/* =>
A - 1
B - 2
*/

A small utility can rearrange arrays-of-objects into objects-of-arrays to make this easier:

const { x, y } = columnsFrom([
    { x: 'A', y: '1' },
    { x: 'B', y: '2' },
]);
format(t`${x} - ${y}`)
/* =>
A - 1
B - 2
*/

Interpolating templates (or arrays of them) also works without any extra fuss:

const items = [1, 2, 3];
format(t`
START
    ${items.map(i => t`
        item ${i} 
    `)}
END
`);
/* =>
START
    item 1
    item 2
END
*/

Slightly better joins allow you to break long lists of items into lines in a pleasing way. Lines can be limited by total length, and the various separators are also customizable.

const items = [1, 2, 3, 4];
join(items, { maxLength: 5, separator: ', ', lineEnd: ';', trailing: '.' });
/* =>
1, 2;
3, 4.
*/

...And that's all! For more details, check the comments in the code.