chore(TypeScript): ship Liquid to class

BREAKING CHANGE: calling `Liquid()` without `new` now becomes invalid
This commit is contained in:
harttle
2019-02-14 23:53:40 +08:00
parent a626de632c
commit 1cc7249a71
27 changed files with 124 additions and 126 deletions
+2 -2
View File
@@ -60,7 +60,7 @@ Parse and Render:
```javascript
var Liquid = require('liquidjs');
var engine = Liquid();
var engine = new Liquid();
engine
.parseAndRender('{{name | capitalize}}', {name: 'alice'})
@@ -83,7 +83,7 @@ engine
## Render from File
```javascript
var engine = Liquid({
var engine = new Liquid({
root: path.resolve(__dirname, 'views/'), // dirs to lookup layouts/includes
extname: '.liquid' // the extname used for layouts/includes, defaults ""
});
+3 -3
View File
@@ -1,8 +1,8 @@
const express = require('express')
const Liquid = require('../..')
let app = express()
let engine = Liquid({
const app = express()
const engine = new Liquid({
root: __dirname, // for layouts and partials
extname: '.liquid'
})
@@ -12,7 +12,7 @@ app.set('views', ['./partials', './views']) // specify the views directory
app.set('view engine', 'liquid') // set to default
app.get('/', function (req, res) {
let todos = ['fork and clone', 'make it better', 'make a pull request']
const todos = ['fork and clone', 'make it better', 'make a pull request']
res.render('todolist', {
todos: todos,
title: 'Welcome to liquidjs!'
+1 -1
View File
@@ -14,7 +14,7 @@ class App extends Component {
componentDidMount() {
let engine = Liquid({
let engine = new Liquid({
root: path.resolve(__dirname, 'views/'), // dirs to lookup layouts/includes
extname: '.liquid' // the extname used for layouts/includes, defaults
});
+66 -68
View File
@@ -12,48 +12,68 @@ import { ParseError, TokenizationError, RenderBreakError, AssertionError } from
import tags from './tags/index'
import filters from './filters'
const _engine = {
init: function (tag, filter, options) {
export default class Liquid {
private cache: object
private options: any
private tags: any
private filters: any
private parser: any
private renderer: any
constructor (options) {
options = _.assign({
root: ['.'],
cache: false,
extname: '',
dynamicPartials: true,
trim_tag_right: false,
trim_tag_left: false,
trim_value_right: false,
trim_value_left: false,
greedy: true,
strict_filters: false,
strict_variables: false
}, options)
options.root = normalizeStringArray(options.root)
if (options.cache) {
this.cache = {}
}
this.options = options
this.tag = tag
this.filter = filter
this.parser = Parser(tag, filter)
this.tags = Tag()
this.filters = Filter(options)
this.parser = Parser(this.tags, this.filters)
this.renderer = Render()
tags(this, Liquid)
filters(this, Liquid)
return this
},
parse: function (html, filepath) {
}
parse(html: string, filepath?: string) {
const tokens = tokenizer.parse(html, filepath, this.options)
return this.parser.parse(tokens)
},
render: function (tpl, ctx, opts) {
}
render(tpl: string, ctx: any, opts: any) {
opts = _.assign({}, this.options, opts)
const scope = new Scope(ctx, opts)
return this.renderer.renderTemplates(tpl, scope)
},
parseAndRender: async function (html, ctx, opts) {
}
async parseAndRender(html, ctx, opts) {
const tpl = await this.parse(html)
return this.render(tpl, ctx, opts)
},
getTemplate: async function (file, root) {
}
async getTemplate(file, root) {
const filepath = await template.resolve(file, root, this.options)
return this.respectCache(filepath, async () => {
const str = await template.read(filepath)
return this.parse(str, filepath)
})
},
renderFile: async function (file, ctx, opts) {
}
async renderFile(file, ctx, opts) {
opts = _.assign({}, opts)
const templates = await this.getTemplate(file, opts.root)
return this.render(templates, ctx, opts)
},
respectCache: async function (key, getter) {
}
async respectCache (key, getter) {
const cacheEnabled = this.options.cache
if (cacheEnabled && this.cache[key]) {
return this.cache[key]
@@ -63,21 +83,21 @@ const _engine = {
this.cache[key] = value
}
return value
},
evalValue: function (str, scope) {
}
evalValue (str, scope) {
const tpl = this.parser.parseValue(str.trim())
return this.renderer.evalValue(tpl, scope)
},
registerFilter: function (name, filter) {
return this.filter.register(name, filter)
},
registerTag: function (name, tag) {
return this.tag.register(name, tag)
},
plugin: function (plugin) {
}
registerFilter (name, filter) {
return this.filters.register(name, filter)
}
registerTag (name, tag) {
return this.tags.register(name, tag)
}
plugin (plugin) {
return plugin.call(this, Liquid)
},
express: function (opts) {
}
express (opts) {
opts = opts || {}
const self = this
return function (filePath, ctx, cb) {
@@ -87,6 +107,21 @@ const _engine = {
self.renderFile(filePath, ctx, opts).then(html => cb(null, html), cb)
}
}
static default = Liquid
static isTruthy = isTruthy
static isFalsy = isFalsy
static evalExp = evalExp
static evalValue = evalValue
static Types = {
ParseError,
TokenizationError,
RenderBreakError,
AssertionError,
AssignScope: {},
CaptureScope: {},
IncrementScope: {},
DecrementScope: {}
}
}
function normalizeStringArray (value) {
@@ -94,40 +129,3 @@ function normalizeStringArray (value) {
if (_.isString(value)) return [value]
throw new TypeError('illegal root: ' + value)
}
export default function Liquid (options) {
options = _.assign({
root: ['.'],
cache: false,
extname: '',
dynamicPartials: true,
trim_tag_right: false,
trim_tag_left: false,
trim_value_right: false,
trim_value_left: false,
greedy: true,
strict_filters: false,
strict_variables: false
}, options)
options.root = normalizeStringArray(options.root)
const engine = _.create(_engine)
engine.init(Tag(), Filter(options), options)
return engine
}
Liquid.default = Liquid
Liquid.isTruthy = isTruthy
Liquid.isFalsy = isFalsy
Liquid.evalExp = evalExp
Liquid.evalValue = evalValue
Liquid.Types = {
ParseError,
TokenizationError,
RenderBreakError,
AssertionError,
AssignScope: {},
CaptureScope: {},
IncrementScope: {},
DecrementScope: {}
}
+3 -3
View File
@@ -3,8 +3,8 @@ import * as path from 'path'
import { anySeries } from './util/promise'
import * as fs from 'fs'
const statFileAsync = _.promisify(fs.stat)
const readFileAsync = _.promisify(fs.readFile)
const statFileAsync = <(filepath: string) => Promise<object>>_.promisify(fs.stat)
const readFileAsync = <(filepath: string, encoding: string) => Promise<string>>_.promisify(fs.readFile)
export async function resolve (filepath, root, options) {
if (!path.extname(filepath)) {
@@ -24,6 +24,6 @@ export async function resolve (filepath, root, options) {
})
}
export async function read (filepath) {
export async function read (filepath): Promise<string> {
return readFileAsync(filepath, 'utf8')
}
+1 -1
View File
@@ -13,7 +13,7 @@ describe('engine#express()', function () {
beforeEach(function () {
app = express()
engine = Liquid({
engine = new Liquid({
root: '/root',
extname: '.html'
})
+2 -2
View File
@@ -7,8 +7,8 @@ chai.use(require('chai-as-promised'))
describe('.parseAndRender()', function () {
var engine, strictEngine
beforeEach(function () {
engine = Liquid()
strictEngine = Liquid({
engine = new Liquid()
strictEngine = new Liquid({
strict_filters: true
})
})
+5 -5
View File
@@ -8,7 +8,7 @@ chai.use(require('chai-as-promised'))
describe('#renderFile()', function () {
var engine
beforeEach(function () {
engine = Liquid({
engine = new Liquid({
root: '/root/',
extname: '.html'
})
@@ -29,7 +29,7 @@ describe('#renderFile()', function () {
.to.eventually.equal('foo')
})
it('should find files without extname', function () {
var engine = Liquid({ root: '/root' })
var engine = new Liquid({ root: '/root' })
return expect(engine.renderFile('/root/files/bar', {}))
.to.eventually.equal('bar')
})
@@ -38,7 +38,7 @@ describe('#renderFile()', function () {
.to.eventually.equal('foo')
})
it('should resolve array as root', function () {
engine = Liquid({
engine = new Liquid({
root: ['/boo', '/root/'],
extname: '.html'
})
@@ -50,7 +50,7 @@ describe('#renderFile()', function () {
files[process.cwd() + '/foo.html'] = 'FOO'
mock(files)
engine = Liquid({
engine = new Liquid({
extname: '.html'
})
return expect(engine.renderFile('foo.html'))
@@ -64,7 +64,7 @@ describe('#renderFile()', function () {
return expect(engine.renderFile('files/name', { name: 'harttle' })).to.eventually.equal('My name is harttle.')
})
it('should throw with lookup list when file not exist', function () {
engine = Liquid({
engine = new Liquid({
root: ['/boo', '/root/'],
extname: '.html'
})
+7 -7
View File
@@ -23,7 +23,7 @@ describe('xhr', () => {
})
global.XMLHttpRequest = sinon.useFakeXMLHttpRequest()
global.document = dom.window.document
engine = Liquid({
engine = new Liquid({
root: 'https://example.com/views/',
extname: '.html'
})
@@ -78,7 +78,7 @@ describe('xhr', () => {
})
describe('#renderFile() with root specified', () => {
it('should support undefined root', () => {
engine = Liquid({
engine = new Liquid({
extname: '.html'
})
server.respondWith('GET', 'https://example.com/foo/hello.html',
@@ -87,7 +87,7 @@ describe('xhr', () => {
.to.eventually.equal('hello alice5')
})
it('should support empty root', () => {
engine = Liquid({
engine = new Liquid({
root: '',
extname: '.html'
})
@@ -97,7 +97,7 @@ describe('xhr', () => {
.to.eventually.equal('hello alice5')
})
it('should support with relative path', () => {
engine = Liquid({
engine = new Liquid({
root: './views/',
extname: '.html'
})
@@ -107,7 +107,7 @@ describe('xhr', () => {
.to.eventually.equal('hello alice5')
})
it('should support with absolute path', () => {
engine = Liquid({
engine = new Liquid({
root: '/views/',
extname: '.html'
})
@@ -117,7 +117,7 @@ describe('xhr', () => {
.to.eventually.equal('hello alice5')
})
it('should support with url', () => {
engine = Liquid({
engine = new Liquid({
root: 'https://foo.com/bar/',
extname: '.html'
})
@@ -141,7 +141,7 @@ describe('xhr', () => {
.then(html => expect(html).to.equal('foo2'))
})
it('should respect cache=true option', () => {
engine = Liquid({
engine = new Liquid({
root: '/views/',
extname: '.html',
cache: true
+2 -2
View File
@@ -9,7 +9,7 @@ chai.use(chaiAsPromised)
describe('cache options', function () {
let engine
beforeEach(function () {
engine = Liquid({
engine = new Liquid({
root: '/root/',
extname: '.html'
})
@@ -28,7 +28,7 @@ describe('cache options', function () {
.then(x => expect(x).to.equal('bar'))
})
it('should respect cache=true option', function () {
engine = Liquid({
engine = new Liquid({
root: '/root/',
extname: '.html',
cache: true
+1 -1
View File
@@ -8,7 +8,7 @@ describe('strict options', function () {
let engine
const ctx = {}
beforeEach(function () {
engine = Liquid({
engine = new Liquid({
root: '/root/',
extname: '.html'
})
+10 -10
View File
@@ -10,34 +10,34 @@ describe('trimming', function () {
describe('tag trimming', function () {
it('should respect trim_tag_left', function () {
const engine = Liquid({ trim_tag_left: true })
const engine = new Liquid({ trim_tag_left: true })
return expect(engine.parseAndRender(' \n \t{%if true%}foo{%endif%} '))
.to.eventually.equal('foo ')
})
it('should respect trim_tag_right', function () {
const engine = Liquid({ trim_tag_right: true })
const engine = new Liquid({ trim_tag_right: true })
return expect(engine.parseAndRender('\t{%if true%}foo{%endif%} \n'))
.to.eventually.equal('\tfoo')
})
it('should not trim value', function () {
const engine = Liquid({ trim_tag_left: true, trim_tag_right: true })
const engine = new Liquid({ trim_tag_left: true, trim_tag_right: true })
return expect(engine.parseAndRender('{%if true%}a {{name}} b{%endif%}', ctx))
.to.eventually.equal('a harttle b')
})
})
describe('value trimming', function () {
it('should respect trim_value_left', function () {
const engine = Liquid({ trim_value_left: true })
const engine = new Liquid({ trim_value_left: true })
return expect(engine.parseAndRender(' \n \t{{name}} ', ctx))
.to.eventually.equal('harttle ')
})
it('should respect trim_value_right', function () {
const engine = Liquid({ trim_value_right: true })
const engine = new Liquid({ trim_value_right: true })
return expect(engine.parseAndRender(' \n \t{{name}} ', ctx))
.to.eventually.equal(' \n \tharttle')
})
it('should respect not trim tag', function () {
const engine = Liquid({ trim_value_left: true, trim_value_right: true })
const engine = new Liquid({ trim_value_left: true, trim_value_right: true })
return expect(engine.parseAndRender('\t{% if true %} aha {%endif%}\t'))
.to.eventually.equal('\t aha \t')
})
@@ -45,19 +45,19 @@ describe('trimming', function () {
describe('greedy', function () {
const src = '\n {%-if true-%}\n a \n{{-name-}}{%-endif-%}\n '
it('should enable greedy by default', function () {
const engine = Liquid()
const engine = new Liquid()
return expect(engine.parseAndRender(src, ctx))
.to.eventually.equal('aharttle')
})
it('should respect to greedy:false by default', function () {
const engine = Liquid({ greedy: false })
const engine = new Liquid({ greedy: false })
return expect(engine.parseAndRender(src, ctx))
.to.eventually.equal('\n a \nharttle ')
})
})
describe('markup', function () {
it('should support trim using markup', function () {
const engine = Liquid()
const engine = new Liquid()
const src = [
'{%- assign username = "John G. Chalmers-Smith" -%}',
'{%- if username and username.length > 10 -%}',
@@ -70,7 +70,7 @@ describe('trimming', function () {
return expect(engine.parseAndRender(src)).to.eventually.equal(dst)
})
it('should not trim when not specified', function () {
const engine = Liquid()
const engine = new Liquid()
const src = [
'{% assign username = "John G. Chalmers-Smith" %}',
'{% if username and username.length > 10 %}',
+1 -1
View File
@@ -6,7 +6,7 @@ chai.use(sinonChai)
const expect = chai.expect
describe('tags/assign', function () {
const liquid = Liquid()
const liquid = new Liquid()
it('should throw when variable expression illegal', function () {
const src = '{% assign / %}'
const ctx = {}
+1 -1
View File
@@ -5,7 +5,7 @@ const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/capture', function () {
const liquid = Liquid()
const liquid = new Liquid()
it('should support capture', function () {
const src = '{% capture f %}{{"a" | capitalize}}{%endcapture%}{{f}}'
+1 -1
View File
@@ -5,7 +5,7 @@ const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/case', function () {
const liquid = Liquid()
const liquid = new Liquid()
it('should reject if not closed', function () {
const src = '{% case "foo"%}'
+1 -1
View File
@@ -5,7 +5,7 @@ const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/comment', function () {
const liquid = Liquid()
const liquid = new Liquid()
it('should support empty content', function () {
const src = '{% comment %}{% raw%}'
return expect(liquid.parseAndRender(src))
+1 -1
View File
@@ -5,7 +5,7 @@ const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/cycle', function () {
const liquid = Liquid()
const liquid = new Liquid()
it('should support cycle', function () {
const src = "{% cycle '1', '2', '3' %}"
+1 -1
View File
@@ -5,7 +5,7 @@ const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/decrement', function () {
const liquid = Liquid()
const liquid = new Liquid()
it('should throw when variable expression illegal', function () {
const src = '{% decrement / %}{{var}}'
const ctx = {}
+1 -1
View File
@@ -7,7 +7,7 @@ chai.use(require('chai-as-promised'))
describe('tags/for', function () {
let liquid, ctx
before(function () {
liquid = Liquid()
liquid = new Liquid()
liquid.registerTag('throwingTag', {
render: function () { throw new Error('intended render error') }
})
+1 -1
View File
@@ -5,7 +5,7 @@ const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/if', function () {
const liquid = Liquid()
const liquid = new Liquid()
const ctx = {
one: 1,
two: 2,
+1 -1
View File
@@ -8,7 +8,7 @@ chai.use(require('chai-as-promised'))
describe('tags/include', function () {
let liquid
before(function () {
liquid = Liquid({
liquid = new Liquid({
root: '/',
extname: '.html'
})
+1 -1
View File
@@ -5,7 +5,7 @@ const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/increment', function () {
const liquid = Liquid()
const liquid = new Liquid()
it('should increment undefined variable', function () {
const src = '{% increment one %}{% increment one %}{% increment one %}'
+4 -4
View File
@@ -8,7 +8,7 @@ chai.use(require('chai-as-promised'))
describe('tags/layout', function () {
let liquid
before(function () {
liquid = Liquid({
liquid = new Liquid({
root: '/',
extname: '.html'
})
@@ -113,7 +113,7 @@ describe('tags/layout', function () {
'/parent.html': '{{color}}{%block%}{%endblock%}',
'/main.html': '{% layout parent.html color:"black"%}{%block%}A{%endblock%}'
})
const staticLiquid = Liquid({ root: '/', dynamicPartials: false })
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
return expect(staticLiquid.renderFile('/main.html')).to
.eventually.equal('blackA')
})
@@ -123,7 +123,7 @@ describe('tags/layout', function () {
'/foo/parent.html': '{{color}}{%block%}{%endblock%}',
'/main.html': '{% layout bar/../foo/parent.html color:"black"%}{%block%}A{%endblock%}'
})
const staticLiquid = Liquid({ root: '/', dynamicPartials: false })
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
return expect(staticLiquid.renderFile('/main.html')).to
.eventually.equal('blackA')
})
@@ -133,7 +133,7 @@ describe('tags/layout', function () {
'/foo/parent.html': '{{color}}{%block%}{%endblock%}',
'/main.html': '{% layout foo/parent.html color:"black"%}{%block%}A{%endblock%}'
})
const staticLiquid = Liquid({ root: '/', dynamicPartials: false })
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
return expect(staticLiquid.renderFile('/main.html')).to
.eventually.equal('blackA')
})
+1 -1
View File
@@ -5,7 +5,7 @@ const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/raw', function () {
const liquid = Liquid()
const liquid = new Liquid()
it('should support raw 1', async function () {
const p = liquid.parseAndRender('{% raw%}')
return expect(p).be.rejectedWith(/{% raw%} not closed/)
+1 -1
View File
@@ -5,7 +5,7 @@ const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/tablerow', function () {
const liquid = Liquid()
const liquid = new Liquid()
it('should support tablerow', function () {
const src = '{% tablerow i in (1..3)%}{{ i }}{% endtablerow %}'
+1 -1
View File
@@ -5,7 +5,7 @@ const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/unless', function () {
const liquid = Liquid()
const liquid = new Liquid()
it('should render else when predicate yields true', function () {
// 0 is truthy
+4 -4
View File
@@ -6,8 +6,8 @@ import * as path from 'path'
const expect = chai.expect
chai.use(require('chai-as-promised'))
let engine = Liquid()
const strictEngine = Liquid({
let engine = new Liquid()
const strictEngine = new Liquid({
strict_variables: true,
strict_filters: true
})
@@ -76,7 +76,7 @@ describe('error', function () {
describe('RenderError', function () {
beforeEach(function () {
engine = Liquid({
engine = new Liquid({
root: '/'
})
engine.registerTag('throwingTag', {
@@ -217,7 +217,7 @@ describe('error', function () {
describe('ParseError', function () {
beforeEach(function () {
engine = Liquid()
engine = new Liquid()
engine.registerTag('throwsOnParse', {
parse: function () {
throw new Error('intended parse error')