mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-17 05:10:40 -07:00
@@ -1,8 +1,5 @@
|
|||||||
name: Release
|
name: Release
|
||||||
on:
|
on: workflow_dispatch
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import { Emitter, Context } from '../../types'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
render: function (ctx: Context, emitter: Emitter) {
|
render: function (ctx: Context, emitter: Emitter) {
|
||||||
emitter.break = true
|
emitter['break'] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import { Emitter, Context } from '../../types'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
render: function (ctx: Context, emitter: Emitter) {
|
render: function (ctx: Context, emitter: Emitter) {
|
||||||
emitter.continue = true
|
emitter['continue'] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ export default {
|
|||||||
for (const item of collection) {
|
for (const item of collection) {
|
||||||
scope[this.variable] = item
|
scope[this.variable] = item
|
||||||
yield r.renderTemplates(this.templates, ctx, emitter)
|
yield r.renderTemplates(this.templates, ctx, emitter)
|
||||||
if (emitter.break) {
|
if (emitter['break']) {
|
||||||
emitter.break = false
|
emitter['break'] = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
emitter.continue = false
|
emitter['continue'] = false
|
||||||
scope.forloop.next()
|
scope.forloop.next()
|
||||||
}
|
}
|
||||||
ctx.pop()
|
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;
|
fs?: FS;
|
||||||
/** the global environment passed down to all partial templates, i.e. templates included by `include`, `layout` and `render` tags. */
|
/** the global environment passed down to all partial templates, i.e. templates included by `include`, `layout` and `render` tags. */
|
||||||
globals?: object;
|
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;
|
keepOutputType?: boolean;
|
||||||
/** An object of operators for conditional statements. Defaults to the regular Liquid operators. */
|
/** An object of operators for conditional statements. Defaults to the regular Liquid operators. */
|
||||||
operators?: 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 { LiquidOptions, normalizeStringArray, NormalizedFullOptions, applyDefault, normalize } from './liquid-options'
|
||||||
import { FilterImplOptions } from './template/filter/filter-impl-options'
|
import { FilterImplOptions } from './template/filter/filter-impl-options'
|
||||||
import { toPromise, toValue } from './util/async'
|
import { toPromise, toValue } from './util/async'
|
||||||
import { Emitter } from './render/emitter'
|
|
||||||
|
|
||||||
export * from './util/error'
|
export * from './util/error'
|
||||||
export * from './types'
|
export * from './types'
|
||||||
@@ -24,7 +23,7 @@ export class Liquid {
|
|||||||
public parser: Parser
|
public parser: Parser
|
||||||
public filters: FilterMap
|
public filters: FilterMap
|
||||||
public tags: TagMap
|
public tags: TagMap
|
||||||
private parseFileImpl: (file: string, sync?: boolean) => Iterator<Template[]>
|
public parseFileImpl: (file: string, sync?: boolean) => Iterator<Template[]>
|
||||||
|
|
||||||
public constructor (opts: LiquidOptions = {}) {
|
public constructor (opts: LiquidOptions = {}) {
|
||||||
this.options = applyDefault(normalize(opts))
|
this.options = applyDefault(normalize(opts))
|
||||||
@@ -45,8 +44,7 @@ export class Liquid {
|
|||||||
|
|
||||||
public _render (tpl: Template[], scope?: object, sync?: boolean): IterableIterator<any> {
|
public _render (tpl: Template[], scope?: object, sync?: boolean): IterableIterator<any> {
|
||||||
const ctx = new Context(scope, this.options, sync)
|
const ctx = new Context(scope, this.options, sync)
|
||||||
const emitter = new Emitter(this.options.keepOutputType)
|
return this.renderer.renderTemplates(tpl, ctx)
|
||||||
return this.renderer.renderTemplates(tpl, ctx, emitter)
|
|
||||||
}
|
}
|
||||||
public async render (tpl: Template[], scope?: object): Promise<any> {
|
public async render (tpl: Template[], scope?: object): Promise<any> {
|
||||||
return toPromise(this._render(tpl, scope, false))
|
return toPromise(this._render(tpl, scope, false))
|
||||||
@@ -54,6 +52,10 @@ export class Liquid {
|
|||||||
public renderSync (tpl: Template[], scope?: object): any {
|
public renderSync (tpl: Template[], scope?: object): any {
|
||||||
return toValue(this._render(tpl, scope, true))
|
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> {
|
public _parseAndRender (html: string, scope?: object, sync?: boolean): IterableIterator<any> {
|
||||||
const tpl = this.parse(html)
|
const tpl = this.parse(html)
|
||||||
|
|||||||
+14
-28
@@ -1,29 +1,15 @@
|
|||||||
import { stringify, toValue } from '../util/underscore'
|
export interface Emitter {
|
||||||
|
/**
|
||||||
export class Emitter {
|
* Write a html value into emitter
|
||||||
public html: any = '';
|
* @param html string, Drop or other primitive value
|
||||||
public break = false;
|
*/
|
||||||
public continue = false;
|
write (html: any): void;
|
||||||
private keepOutputType? = false;
|
/**
|
||||||
|
* Notify the emitter render has ended
|
||||||
constructor (keepOutputType: boolean|undefined) {
|
*/
|
||||||
this.keepOutputType = keepOutputType
|
end (): void;
|
||||||
}
|
/**
|
||||||
|
* Collect rendered string value immediately
|
||||||
public write (html: any) {
|
*/
|
||||||
if (this.keepOutputType === true) {
|
collect (): string;
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-4
@@ -1,23 +1,34 @@
|
|||||||
import { RenderError } from '../util/error'
|
import { RenderError } from '../util/error'
|
||||||
import { Context } from '../context/context'
|
import { Context } from '../context/context'
|
||||||
import { Template } from '../template/template'
|
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 {
|
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> {
|
public * renderTemplates (templates: Template[], ctx: Context, emitter?: Emitter): IterableIterator<any> {
|
||||||
if (!emitter) {
|
if (!emitter) {
|
||||||
emitter = new Emitter(ctx.opts.keepOutputType)
|
emitter = ctx.opts.keepOutputType ? new KeepingTypeEmitter() : new SimpleEmitter()
|
||||||
}
|
}
|
||||||
for (const tpl of templates) {
|
for (const tpl of templates) {
|
||||||
try {
|
try {
|
||||||
|
// if tpl.render supports emitter, it'll return empty `html`
|
||||||
const html = yield tpl.render(ctx, emitter)
|
const html = yield tpl.render(ctx, emitter)
|
||||||
|
// if not, it'll return an `html`, write to the emitter for it
|
||||||
html && emitter.write(html)
|
html && emitter.write(html)
|
||||||
if (emitter.break || emitter.continue) break
|
if (emitter['break'] || emitter['continue']) break
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const err = RenderError.is(e) ? e : new RenderError(e, tpl)
|
const err = RenderError.is(e) ? e : new RenderError(e, tpl)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return emitter.html
|
return emitter.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { TemplateImpl } from '../template/template-impl'
|
|||||||
import { Template } from '../template/template'
|
import { Template } from '../template/template'
|
||||||
import { HTMLToken } from '../tokens/html-token'
|
import { HTMLToken } from '../tokens/html-token'
|
||||||
import { Context } from '../context/context'
|
import { Context } from '../context/context'
|
||||||
import { Emitter } from '../render/emitter'
|
import { Emitter } from '../emitters/emitter'
|
||||||
|
|
||||||
export class HTML extends TemplateImpl<HTMLToken> implements Template {
|
export class HTML extends TemplateImpl<HTMLToken> implements Template {
|
||||||
private str: string
|
private str: string
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Value } from './value'
|
|||||||
import { TemplateImpl } from '../template/template-impl'
|
import { TemplateImpl } from '../template/template-impl'
|
||||||
import { Template } from '../template/template'
|
import { Template } from '../template/template'
|
||||||
import { Context } from '../context/context'
|
import { Context } from '../context/context'
|
||||||
import { Emitter } from '../render/emitter'
|
import { Emitter } from '../emitters/emitter'
|
||||||
import { OutputToken } from '../tokens/output-token'
|
import { OutputToken } from '../tokens/output-token'
|
||||||
import { Liquid } from '../liquid'
|
import { Liquid } from '../liquid'
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { TagToken } from '../../tokens/tag-token'
|
|||||||
import { TopLevelToken } from '../../tokens/toplevel-token'
|
import { TopLevelToken } from '../../tokens/toplevel-token'
|
||||||
import { TagImpl } from './tag-impl'
|
import { TagImpl } from './tag-impl'
|
||||||
import { Hash } from '../../template/tag/hash'
|
import { Hash } from '../../template/tag/hash'
|
||||||
import { Emitter } from '../../render/emitter'
|
import { Emitter } from '../../emitters/emitter'
|
||||||
|
|
||||||
export interface TagImplOptions {
|
export interface TagImplOptions {
|
||||||
parse?: (this: TagImpl, token: TagToken, remainingTokens: TopLevelToken[]) => void;
|
parse?: (this: TagImpl, token: TagToken, remainingTokens: TopLevelToken[]) => void;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Context } from '../context/context'
|
import { Context } from '../context/context'
|
||||||
import { Token } from '../tokens/token'
|
import { Token } from '../tokens/token'
|
||||||
import { Emitter } from '../render/emitter'
|
import { Emitter } from '../emitters/emitter'
|
||||||
|
|
||||||
export interface Template {
|
export interface Template {
|
||||||
token: Token;
|
token: Token;
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ export { TypeGuards }
|
|||||||
export { ParseError, TokenizationError, AssertionError } from './util/error'
|
export { ParseError, TokenizationError, AssertionError } from './util/error'
|
||||||
export { assert } from './util/assert'
|
export { assert } from './util/assert'
|
||||||
export { Drop } from './drop/drop'
|
export { Drop } from './drop/drop'
|
||||||
export { Emitter } from './render/emitter'
|
export { Emitter } from './emitters/emitter'
|
||||||
export { Expression } from './render/expression'
|
export { Expression } from './render/expression'
|
||||||
export { isFalsy, isTruthy } from './render/boolean'
|
export { isFalsy, isTruthy } from './render/boolean'
|
||||||
export { TagToken } from './tokens/tag-token'
|
export { TagToken } from './tokens/tag-token'
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import { Context } from '../../../src/context/context'
|
|||||||
import { HTMLToken } from '../../../src/tokens/html-token'
|
import { HTMLToken } from '../../../src/tokens/html-token'
|
||||||
import { Render } from '../../../src/render/render'
|
import { Render } from '../../../src/render/render'
|
||||||
import { HTML } from '../../../src/template/html'
|
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 { toThenable } from '../../../src/util/async'
|
||||||
|
import { Tag } from '../../../src/template/tag/tag'
|
||||||
|
import { TagToken } from '../../../src/types'
|
||||||
|
|
||||||
describe('render', function () {
|
describe('render', function () {
|
||||||
let render: Render
|
let render: Render
|
||||||
@@ -16,8 +18,52 @@ describe('render', function () {
|
|||||||
it('should render html', async function () {
|
it('should render html', async function () {
|
||||||
const scope = new Context()
|
const scope = new Context()
|
||||||
const token = { getContent: () => '<p>' } as HTMLToken
|
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>')
|
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