mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 12:20:40 -07:00
@@ -1,8 +1,5 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
|
||||
@@ -2,6 +2,6 @@ import { Emitter, Context } from '../../types'
|
||||
|
||||
export default {
|
||||
render: function (ctx: Context, emitter: Emitter) {
|
||||
emitter.break = true
|
||||
emitter['break'] = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,6 @@ import { Emitter, Context } from '../../types'
|
||||
|
||||
export default {
|
||||
render: function (ctx: Context, emitter: Emitter) {
|
||||
emitter.continue = true
|
||||
emitter['continue'] = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,11 +55,11 @@ export default {
|
||||
for (const item of collection) {
|
||||
scope[this.variable] = item
|
||||
yield r.renderTemplates(this.templates, ctx, emitter)
|
||||
if (emitter.break) {
|
||||
emitter.break = false
|
||||
if (emitter['break']) {
|
||||
emitter['break'] = false
|
||||
break
|
||||
}
|
||||
emitter.continue = false
|
||||
emitter['continue'] = false
|
||||
scope.forloop.next()
|
||||
}
|
||||
ctx.pop()
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface Emitter {
|
||||
write (html: any): void;
|
||||
end (): void;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { stringify, toValue } from '../util/underscore'
|
||||
|
||||
export class KeepingTypeEmitter {
|
||||
public html: any = '';
|
||||
|
||||
public write (html: any) {
|
||||
html = toValue(html)
|
||||
// This will only preserve the type if the value is isolated.
|
||||
// I.E:
|
||||
// {{ my-port }} -> 42
|
||||
// {{ my-host }}:{{ my-port }} -> 'host:42'
|
||||
if (typeof html !== 'string' && this.html === '') {
|
||||
this.html = html
|
||||
} else {
|
||||
this.html = stringify(this.html) + stringify(html)
|
||||
}
|
||||
}
|
||||
|
||||
public end () {
|
||||
return this.html
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { stringify } from '../util/underscore'
|
||||
import { Emitter } from './emitter'
|
||||
|
||||
export class SimpleEmitter implements Emitter {
|
||||
public html: any = '';
|
||||
|
||||
public write (html: any) {
|
||||
this.html += stringify(html)
|
||||
}
|
||||
|
||||
public end () {
|
||||
return this.html
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { stringify } from '../util/underscore'
|
||||
|
||||
export class StreamedEmitter {
|
||||
public html: any = '';
|
||||
public stream = new (require('stream').PassThrough)()
|
||||
public write (html: any) {
|
||||
this.stream.write(stringify(html))
|
||||
}
|
||||
public end () {
|
||||
this.stream.end()
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ export interface LiquidOptions {
|
||||
fs?: FS;
|
||||
/** the global environment passed down to all partial templates, i.e. templates included by `include`, `layout` and `render` tags. */
|
||||
globals?: object;
|
||||
/** Whether or not to keep value type when writing the Output. Defaults to `false`. */
|
||||
/** Whether or not to keep value type when writing the Output, not working for streamed rendering. Defaults to `false`. */
|
||||
keepOutputType?: boolean;
|
||||
/** An object of operators for conditional statements. Defaults to the regular Liquid operators. */
|
||||
operators?: Operators;
|
||||
|
||||
+6
-4
@@ -13,7 +13,6 @@ import { FilterMap } from './template/filter/filter-map'
|
||||
import { LiquidOptions, normalizeStringArray, NormalizedFullOptions, applyDefault, normalize } from './liquid-options'
|
||||
import { FilterImplOptions } from './template/filter/filter-impl-options'
|
||||
import { toPromise, toValue } from './util/async'
|
||||
import { Emitter } from './render/emitter'
|
||||
|
||||
export * from './util/error'
|
||||
export * from './types'
|
||||
@@ -24,7 +23,7 @@ export class Liquid {
|
||||
public parser: Parser
|
||||
public filters: FilterMap
|
||||
public tags: TagMap
|
||||
private parseFileImpl: (file: string, sync?: boolean) => Iterator<Template[]>
|
||||
public parseFileImpl: (file: string, sync?: boolean) => Iterator<Template[]>
|
||||
|
||||
public constructor (opts: LiquidOptions = {}) {
|
||||
this.options = applyDefault(normalize(opts))
|
||||
@@ -45,8 +44,7 @@ export class Liquid {
|
||||
|
||||
public _render (tpl: Template[], scope?: object, sync?: boolean): IterableIterator<any> {
|
||||
const ctx = new Context(scope, this.options, sync)
|
||||
const emitter = new Emitter(this.options.keepOutputType)
|
||||
return this.renderer.renderTemplates(tpl, ctx, emitter)
|
||||
return this.renderer.renderTemplates(tpl, ctx)
|
||||
}
|
||||
public async render (tpl: Template[], scope?: object): Promise<any> {
|
||||
return toPromise(this._render(tpl, scope, false))
|
||||
@@ -54,6 +52,10 @@ export class Liquid {
|
||||
public renderSync (tpl: Template[], scope?: object): any {
|
||||
return toValue(this._render(tpl, scope, true))
|
||||
}
|
||||
public renderToNodeStream (tpl: Template[], scope?: object): NodeJS.ReadableStream {
|
||||
const ctx = new Context(scope, this.options)
|
||||
return this.renderer.renderTemplatesToNodeStream(tpl, ctx)
|
||||
}
|
||||
|
||||
public _parseAndRender (html: string, scope?: object, sync?: boolean): IterableIterator<any> {
|
||||
const tpl = this.parse(html)
|
||||
|
||||
+14
-28
@@ -1,29 +1,15 @@
|
||||
import { stringify, toValue } from '../util/underscore'
|
||||
|
||||
export class Emitter {
|
||||
public html: any = '';
|
||||
public break = false;
|
||||
public continue = false;
|
||||
private keepOutputType? = false;
|
||||
|
||||
constructor (keepOutputType: boolean|undefined) {
|
||||
this.keepOutputType = keepOutputType
|
||||
}
|
||||
|
||||
public write (html: any) {
|
||||
if (this.keepOutputType === true) {
|
||||
html = toValue(html)
|
||||
} else {
|
||||
html = stringify(html)
|
||||
}
|
||||
// This will only preserve the type if the value is isolated.
|
||||
// I.E:
|
||||
// {{ my-port }} -> 42
|
||||
// {{ my-host }}:{{ my-port }} -> 'host:42'
|
||||
if (this.keepOutputType === true && typeof html !== 'string' && this.html === '') {
|
||||
this.html = html
|
||||
} else {
|
||||
this.html = stringify(this.html) + stringify(html)
|
||||
}
|
||||
}
|
||||
export interface Emitter {
|
||||
/**
|
||||
* Write a html value into emitter
|
||||
* @param html string, Drop or other primitive value
|
||||
*/
|
||||
write (html: any): void;
|
||||
/**
|
||||
* Notify the emitter render has ended
|
||||
*/
|
||||
end (): void;
|
||||
/**
|
||||
* Collect rendered string value immediately
|
||||
*/
|
||||
collect (): string;
|
||||
}
|
||||
|
||||
+15
-4
@@ -1,23 +1,34 @@
|
||||
import { RenderError } from '../util/error'
|
||||
import { Context } from '../context/context'
|
||||
import { Template } from '../template/template'
|
||||
import { Emitter } from './emitter'
|
||||
import { Emitter } from '../emitters/emitter'
|
||||
import { SimpleEmitter } from '../emitters/simple-emitter'
|
||||
import { StreamedEmitter } from '../emitters/streamed-emitter'
|
||||
import { toThenable } from '../util/async'
|
||||
import { KeepingTypeEmitter } from '../emitters/keeping-type-emitter'
|
||||
|
||||
export class Render {
|
||||
public renderTemplatesToNodeStream (templates: Template[], ctx: Context): NodeJS.ReadableStream {
|
||||
const emitter = new StreamedEmitter()
|
||||
toThenable(this.renderTemplates(templates, ctx, emitter))
|
||||
return emitter.stream
|
||||
}
|
||||
public * renderTemplates (templates: Template[], ctx: Context, emitter?: Emitter): IterableIterator<any> {
|
||||
if (!emitter) {
|
||||
emitter = new Emitter(ctx.opts.keepOutputType)
|
||||
emitter = ctx.opts.keepOutputType ? new KeepingTypeEmitter() : new SimpleEmitter()
|
||||
}
|
||||
for (const tpl of templates) {
|
||||
try {
|
||||
// if tpl.render supports emitter, it'll return empty `html`
|
||||
const html = yield tpl.render(ctx, emitter)
|
||||
// if not, it'll return an `html`, write to the emitter for it
|
||||
html && emitter.write(html)
|
||||
if (emitter.break || emitter.continue) break
|
||||
if (emitter['break'] || emitter['continue']) break
|
||||
} catch (e) {
|
||||
const err = RenderError.is(e) ? e : new RenderError(e, tpl)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
return emitter.html
|
||||
return emitter.end()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { TemplateImpl } from '../template/template-impl'
|
||||
import { Template } from '../template/template'
|
||||
import { HTMLToken } from '../tokens/html-token'
|
||||
import { Context } from '../context/context'
|
||||
import { Emitter } from '../render/emitter'
|
||||
import { Emitter } from '../emitters/emitter'
|
||||
|
||||
export class HTML extends TemplateImpl<HTMLToken> implements Template {
|
||||
private str: string
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Value } from './value'
|
||||
import { TemplateImpl } from '../template/template-impl'
|
||||
import { Template } from '../template/template'
|
||||
import { Context } from '../context/context'
|
||||
import { Emitter } from '../render/emitter'
|
||||
import { Emitter } from '../emitters/emitter'
|
||||
import { OutputToken } from '../tokens/output-token'
|
||||
import { Liquid } from '../liquid'
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TagToken } from '../../tokens/tag-token'
|
||||
import { TopLevelToken } from '../../tokens/toplevel-token'
|
||||
import { TagImpl } from './tag-impl'
|
||||
import { Hash } from '../../template/tag/hash'
|
||||
import { Emitter } from '../../render/emitter'
|
||||
import { Emitter } from '../../emitters/emitter'
|
||||
|
||||
export interface TagImplOptions {
|
||||
parse?: (this: TagImpl, token: TagToken, remainingTokens: TopLevelToken[]) => void;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Context } from '../context/context'
|
||||
import { Token } from '../tokens/token'
|
||||
import { Emitter } from '../render/emitter'
|
||||
import { Emitter } from '../emitters/emitter'
|
||||
|
||||
export interface Template {
|
||||
token: Token;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ export { TypeGuards }
|
||||
export { ParseError, TokenizationError, AssertionError } from './util/error'
|
||||
export { assert } from './util/assert'
|
||||
export { Drop } from './drop/drop'
|
||||
export { Emitter } from './render/emitter'
|
||||
export { Emitter } from './emitters/emitter'
|
||||
export { Expression } from './render/expression'
|
||||
export { isFalsy, isTruthy } from './render/boolean'
|
||||
export { TagToken } from './tokens/tag-token'
|
||||
|
||||
@@ -3,8 +3,10 @@ import { Context } from '../../../src/context/context'
|
||||
import { HTMLToken } from '../../../src/tokens/html-token'
|
||||
import { Render } from '../../../src/render/render'
|
||||
import { HTML } from '../../../src/template/html'
|
||||
import { Emitter } from '../../../src/render/emitter'
|
||||
import { SimpleEmitter } from '../../../src/emitters/simple-emitter'
|
||||
import { toThenable } from '../../../src/util/async'
|
||||
import { Tag } from '../../../src/template/tag/tag'
|
||||
import { TagToken } from '../../../src/types'
|
||||
|
||||
describe('render', function () {
|
||||
let render: Render
|
||||
@@ -16,8 +18,52 @@ describe('render', function () {
|
||||
it('should render html', async function () {
|
||||
const scope = new Context()
|
||||
const token = { getContent: () => '<p>' } as HTMLToken
|
||||
const html = await toThenable(render.renderTemplates([new HTML(token)], scope, new Emitter(scope.opts.keepOutputType)))
|
||||
const html = await toThenable(render.renderTemplates([new HTML(token)], scope, new SimpleEmitter()))
|
||||
return expect(html).to.equal('<p>')
|
||||
})
|
||||
})
|
||||
|
||||
describe('.renderTemplatesToNodeStream()', function () {
|
||||
it('should render to html stream', function (done) {
|
||||
const scope = new Context()
|
||||
const tpls = [
|
||||
new HTML({ getContent: () => '<p>' } as HTMLToken),
|
||||
new HTML({ getContent: () => '</p>' } as HTMLToken)
|
||||
]
|
||||
const stream = render.renderTemplatesToNodeStream(tpls, scope)
|
||||
let result = ''
|
||||
stream.on('data', (data) => {
|
||||
result += data
|
||||
})
|
||||
stream.on('end', () => {
|
||||
expect(result).to.equal('<p></p>')
|
||||
done()
|
||||
})
|
||||
})
|
||||
it('should render to html stream asyncly', function (done) {
|
||||
const scope = new Context()
|
||||
const tpls = [
|
||||
new HTML({ getContent: () => '<p>' } as HTMLToken),
|
||||
new Tag({ content: 'foo', args: '', name: 'foo' } as TagToken, [], {
|
||||
tags: {
|
||||
get: () => ({
|
||||
render: () => new Promise(
|
||||
resolve => setTimeout(() => resolve('async tag'), 10)
|
||||
)
|
||||
})
|
||||
}
|
||||
} as any),
|
||||
new HTML({ getContent: () => '</p>' } as HTMLToken)
|
||||
]
|
||||
const stream = render.renderTemplatesToNodeStream(tpls, scope)
|
||||
let result = ''
|
||||
stream.on('data', (data) => {
|
||||
result += data
|
||||
})
|
||||
stream.on('end', () => {
|
||||
expect(result).to.equal('<p>async tag</p>')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user