Novices Performing Miracles

    outie
    TypeScript icon, indicating that this package has built-in type declarations

    1.1.5 • Public • Published

    Build Status

    outie

    A customizable templating engine for node, written in TypeScript.

    Basic usage

    Render a simple string template

    import { Outie } from 'outie';
    const outie = new Outie();
    
    const template = `Hello, {name}!`;
    const data = { name: 'world' };
    const rendered = await outie.render(template, data);
    
    console.log(rendered); // "Hello, world!"

    Render a template file

    <!-- hello.html.outie -->
    <h1>Hello, {name}!</h1>
    import { Outie } from 'outie';
    const outie = new Outie();
    
    const absPath = path.join(__dirname, 'hello.html.outie');
    const data = { name: 'world' };
    const rendered = await outie.renderFile(absPath, data);
    
    console.log(rendered); // "<h1>Hello, world!</h1>"

    Configuration

    No configuration is required to get started.

    import { Outie } from 'outie';
    
    // use the default config
    const outie = new Outie();

    However, you have the option to configure almost all of the syntax you can see in the usage examples below.

    import { Outie, defaultConfig, MruCache, Template } from 'outie';
    
    // customize everything
    const customConfig = {
        // these are the defaults
        tokenStart: '{',
        tokenEnd: '}',
        closeTokenIdentifier: '/',
        
        // tokens lets you add, remove, or customize
        // the set of supported "tokens" (aka tags)
        tokens: {
            // you can easily rename the bundled tokens 
            // using the exported `defaultConfig`
    
            // rename "raw" token to "~"
            '~': defaultConfig.tokens.raw, 
            // rename "includeRaw" token to "incRaw"
            'incRaw': defaultConfig.tokens.includeRaw, 
            // rename "include" token to "inc"
            'inc': defaultConfig.tokens.include, 
            // rename "if" token to "?"
            '?': defaultConfig.tokens.if, 
            // rename "unless" token to "!"
            '!': defaultConfig.tokens.unless, 
            // rename "for" token to "each"
            'each': defaultConfig.tokens.for, 
    
            // you can also create your own token definitions
            'random': class RandomToken extends Token {
                async render() {
                    return Math.random().toString();
                }
            }
        },
    
        // cache up to 100 template files
        fileCache: new MruCache<Template>(100),
    };
    
    const outie = new Outie(customConfig);

    Precompiling templates from strings

    import { Outie } from 'outie';
    const outie = new Outie();
    
    const templateStr = `Hello, {name}!`;
    const data = { name: 'world' };
    const template = await outie.template(templateStr); // compile template
    const rendered = await template.render(data); // render pre-compiled template
    
    console.log(rendered); // "Hello, world!"

    Precompiling templates from files

    import { Outie } from 'outie';
    const outie = new Outie();
    
    const absPath = path.join(__dirname, 'hello.html.outie');
    const data = { name: 'world' };
    const template = await outie.templateFromFile(absPath); // compile template
    const rendered = await template.render(data); // render pre-compiled template
    
    console.log(rendered); // "Hello, world!"

    Logic and looping

    If/Unless

    import { Outie } from 'outie';
    const outie = new Outie();
    
    const template = `
        {if lastVisit}Welcome back!{/if}
        {unless lastVisit}Welcome!{/unless}
    `;
    const data = { lastVisit: null };
    const rendered = await outie.render(template, data);
    
    console.log(rendered.trim()); // "Welcome!"

    For loops

    You can loop through any collection that is iterable using Object.keys, including arrays and objects. You can access both the key and the value within the loop.

    import { Outie } from 'outie';
    const outie = new Outie();
    
    const template = `
        {for key:value in birds}
            The common name of {key} is {value}.
        {/for}
    `;
    const data = { 
        birds: {
            'Turdus migratorius': 'American robin',
            'Cardinalis cardinalis': 'Northern cardinal'
        }
    };
    const rendered = await outie.render(template, data);
    
    console.log(rendered.trim());
    // The common name of Turdus migratorius is American robin.
    // The common name of Cardinalis cardinalis is Northern cardinal.

    You can omit the key if you're only interested in the values.

    import { Outie } from 'outie';
    const outie = new Outie();
    
    const template = `
        <ul>
        {for city in cities}
            <li>{city}</li>
        {/for}
        </ul>
    `;
    const data = { 
        cities: ['London', 'Tokyo']
    };
    const rendered = await outie.render(template, data);
    
    console.log(rendered.trim());
    // <ul>
    //     <li>London</li>
    //     <li>Tokyo</li>
    // </ul>

    Includes/Partials

    Includes

    You can include templates from other templates using relative or absolute paths. Relative paths are based on the location of the template from which they are included.

    <!-- main.html.outie -->
    <h1>Hello, {name}!</h1>
    {include account.html.outie}

    Included templates inherit the data model that is present at the time they're included, so you can use any data that would have been available in the same spot in the including template.

    <!-- account.html.outie -->
    <h2>Your Account</h2>
    Your balance is {balance}.
    import { Outie } from 'outie';
    const outie = new Outie();
    
    const absPath = path.join(__dirname, 'main.html.outie');
    const data = { name: 'world', balance: '$1' };
    const rendered = await outie.renderFile(absPath, data);
    
    console.log(rendered);
    // <h1>Hello, world!</h1>
    // <h2>Your Account</h2>
    // Your balance is $1.

    Raw includes

    If you just want to dump the contents of another file into your template, you can use a raw include.

    <!-- main.html.outie -->
    <h1>Hello, {name}!</h1>
    {includeRaw raw.html.outie}
    <!-- raw.html.outie -->
    The contents of this {file} are left unparsed.
    import { Outie } from 'outie';
    const outie = new Outie();
    
    const absPath = path.join(__dirname, 'main.html.outie');
    const data = { name: 'world' };
    const rendered = await outie.renderFile(absPath, data);
    
    console.log(rendered);
    // <h1>Hello, world!</h1>
    // The contents of this {file} are left unparsed.

    HTML encoding and raw values

    By default, all data is HTML encoded when rendered in templates. You can, however, also render data unencoded.

    import { Outie } from 'outie';
    const outie = new Outie();
    
    const template = `Hello, {raw name}!`;
    const data = { name: '<script>alert("xss");</script>' };
    const rendered = await outie.render(template, data);
    
    console.log(rendered); // "Hello, <script>alert("xss");</script>!"

    Custom tokens

    Basic example

    Here's a complete example of creating a simple custom token that simply outputs a random number when it's used.

    We start by extending the abstract class Token:

    import { Token } from 'outie';
    
    class RandomToken extends Token {
        async render() {
            return Math.random().toString();
        }
    }

    Then add the token to your config and use it in a template:

    import { Outie, defaultConfig } from 'outie';
    
    const outie = new Outie({
        ...defaultConfig
        tokens: {
            ...defaultConfig.tokens,
            'random': RandomToken
        }
    });
    
    await outie.render('Your number is: {random}', {}); // Your number is: 0.24507892345

    Adding parameters

    Math.random() is great, but it would be better if we could control the range of the number that's generated. Let's add some parameters to our custom token to do just that.

    When we're done, we'll be able to use it like so to get a random number between 10 and 20:

    {random 10 20}
    

    We'll use our previous example as a starting point, but add a constructor and a couple of fields to keep track of the desired min and max.

    import { Token, Template } from 'outie';
    
    class RandomToken extends Token {
        private readonly min: number;
        private readonly max: number;
    
        constructor(content: string) {
            super(content);
    
            // `content` is the content of the token with the 
            // _identifier_ stripped away.
            // So, for "{random 10 20}", `content` is "10 20".
            const [min, max] = this.content.trim()
                .split(/\s+/)
                .map(s => parseInt(s));
    
            // a "real" implementation would include some
            // error handling
            this.min = min;
            this.max = max;
        }
    
        async render() {
            const n = (Math.random() * (this.max - this.min)) + this.min;
            return n.toString();
        }
    }

    Creating a block token

    Block tokens are used when a token should have a start and an end. This is commonly used for looping and conditionals but can be used anywhere that you need to handle nested content.

    To create a block token, extend the abstract class BlockStartToken.

    As an example, we'll create a simple token that wraps anything inside in an <h1> element.

    Note: Block tokens have full control over the rendering of any child (i.e. nested) tokens. If your block token doesn't render its child tokens, they will not be rendered.

    export class HeadingToken extends BlockStartToken {
    
        async render(model: RenderModel): Promise<string> {
            // all child tokens are stored in `this.children`
            const nestedTokens = this.children;
            // you can use `Token.renderTokens` to easily render all child tokens
            const renderedChildren = await Token.renderTokens(nestedTokens, model);
            
            return `<h1>${renderedChildren}</h1>`;
        }
    }

    Install

    npm i outie

    DownloadsWeekly Downloads

    1

    Version

    1.1.5

    License

    MIT

    Unpacked Size

    123 kB

    Total Files

    78

    Last publish

    Collaborators

    • dgrundel