Render Files

For a typical project there could be a directory of template files, you’ll need to set the template root and call renderFile or renderFileSync to render a specific file.

Render a File

For example you have a directory of templates like this:

.
├── index.js
└── views/
  ├── hello.liquid
  └── world.liquid

hello.liquid contains a single line `name: {{name}}`.
Now save the following contents into index.js:

var engine = new Liquid({
    root: path.resolve(__dirname, 'views/'),  // root for layouts/includes lookup
    extname: '.liquid'          // used for layouts/includes, defaults ""
});
engine
    .renderFile("hello", {name: 'alice'})   // will read and render `views/hello.liquid`
    .then(console.log)  // outputs "Alice"

Run node index.js and you’ll get output like this:

> node index.js
name: alice

Template Lookup

Template files names passed to renderFile, parseFile, renderFileSync, parseFileSync APIs,
and include, layout tags are resolved against the root option.

It can be a string-typed path (see above example), or a list of root directories, in which case templates will be looked up in that order. e.g.

var engine = new Liquid({
    root: ['views/'],
    partials: ['views/partials/'],
    layouts: ['views/layouts/'],
    extname: '.liquid'
});
Relative Paths

Relative paths in root will be resolved against cwd().

  • When parse(), render() functions are called, for example liquid.renderFile('foo'), templates under root will be looked up.
  • When a partial is requested, for example {% render "foo" %}, templates under partials will be looked up.
  • When a layout is requested, for example {% layout "foo" %}, templates under layouts will be looked up.

When LiquidJS is used in browser, the paths will be resolved based on current location. Here’s a demo for browsers: demo/browser.

Abstract File System

LiquidJS defines an abstract file system interface and the default implementation is src/fs/fs-impl.ts for Node.js and src/build/fs-impl-browser.ts for the browser bundle.

The Liquid constructor provides a fs option to specify the file system implementation. It’s supposed to be used to define customized template fetching logic, i.e. fetch template from a database table, like:

var engine = new Liquid({
    fs: {
        readFileSync (file) {
            return db.model('Template').findByIdSync(file).text
        },
        async readFile (file) {
            const template = await db.model('Template').findById(file)
            return template.text
        },
        existsSync () {
            return true
        },
        async exists () {
            return true
        },
        contains () {
            return true
        },
        resolve(root, file, ext) {
            return file
        }
    }
});
Path Traversal Vulnerability

The default value of contains() always returns true. That means when specifying an abstract file system, you’ll need to provide a proper contains() to avoid expose such vulnerabilities.

In-memory Template

To facilitate rendering w/o files, there’s a templates option to specify a mapping of filenames and their content. LiquidJS will read templates from the mapping.

const engine = new Liquid({
  templates: {
    'views/entry': 'header {% include "../partials/footer" %}',
    'partials/footer': 'footer'
  }
})
engine.renderFileSync('views/entry'))
// Result: 'header footer'

Note that file system options like root, layouts, partials, relativeReference will be ignored when templates is specified.