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/', 'views/partials/'],
    extname: '.liquid'
});
Relative Paths

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

When {% render "foo" %} is rendered or liquid.renderFile('foo') is called, the following files will be looked up and the first existing file will be used:

  • cwd()/views/foo.liquid
  • cwd()/views/partials/foo.liquid

If none of the above files exists, an ENOENT error will be thrown. Here’s a demo for Node.js: demo/nodejs.

When LiquidJS is used in browser, say current location is https://example.com/bar/index.html, only the first root will be used and the file to be fetched is:

If fetch fails, a 404/500 error or network failures for example, an ENOENT error will be thrown.
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.