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 PathsRelative paths in
root
will be resolved againstcwd()
.
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.liquidcwd()
/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 VulnerabilityThe default value of
contains()
always returns true. That means when specifying an abstract file system, you’ll need to provide a propercontains()
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.