type safe mustache templates

Viewed 5469

Is there a solution where you could do the following?:

my-template.mustache

Hello {{name}}!

index.ts

import { readFileSync, writeFileSync } from 'fs';
import * as Mustache from 'mustache';

export interface Person {
    name: string;
}

const hash: Person = {
    name: 'Jon'
};

const template = readFileSync('my-template.mustache', 'utf-8');

// somehow let the IDE know the hash type
const result = Mustache.render(template, hash);

writeFileSync('my-template.html', result, 'utf-8');

Then if you did:

my-template.mustache

Hello {{name}}, {{age}} <!-- red squiggles under age -->

So age is not a property of type Person and the hash type is Person so you get red squiggles under age. Preferably a mechanism that would work in in Visual Studio Code.

Update:
To be clear Hello {{name}}, {{age}} <!-- red squiggles under age --> is what I'm trying to accomplish, not the problem I'm having.

3 Answers

There's no easy way to do this; however, there are some complex ones. The easiest of them that came to my head is to create a tool that will compile your *.mustache templates into typescript modules, and then you just import those modules as regular typescript files instead of fs.readFileSyncing them. Here's an example of what compilation result may look like for your template with age:

import * as Mustache from 'mustache';

const template = 'Hello {{name}}, {{age}} <!-- red squiggles under age -->';
export interface TemplateParams {
    name: string;
    age: string;
}
export default function render(params: TemplateParams): string {
    return Mustache.render(template, params);
}

This tool also will need to be inserted into your scripts that you use to build your app and incrementally build in watch mode.

As Nikita mentioned, there isn't any tooling to accomplish this with Mustache and you would need to write a compiler. If you're willing to move away from mustache, you could use template literals.

I wrote embedded-typescript which uses a compiler to generate type safe templates with an ejs inspired syntax. It's open source, so you could use the code as a basis to build something similar for a mustache inspired syntax.

One approach is to declare a type instead of using an interface. Type declaration functions a bit as Traits. In the following it allows you to map any JS Object into a type with new properties, but it will fail if you try to use the wrong type for the given property.

import { readFileSync, writeFileSync } from 'fs';
import * as Mustache from 'mustache';

export interface PersonWithName {
    name: string;
}

export declare type Person = PersonWithName | any;

const hash: Person = {
    name: 'Jon'
};

const hashWithAge: Person = {
    name: 'Jon',
    age: 10,
    newAge: 20
};

const template = readFileSync('my-template.mustache', 'utf-8');
Related