mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 12:20:40 -07:00
perf: make the most of streamed rendering
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
[](https://www.npmjs.org/package/liquidjs)
|
||||
[](https://www.npmjs.org/package/liquidjs)
|
||||
[](https://coveralls.io/github/harttle/liquidjs?branch=master)
|
||||
[](https://travis-ci.org/harttle/liquidjs)
|
||||
[](https://github.com/harttle/liquidjs/actions/workflows/check.yml?query=branch%3Amaster)
|
||||
[](https://david-dm.org/harttle/liquidjs)
|
||||
[](https://github.com/harttle/liquidjs/blob/master/LICENSE)
|
||||
[](https://github.com/harttle/liquidjs)
|
||||
|
||||
+14
-4
@@ -3,7 +3,7 @@ const { Liquid } = require('liquidjs')
|
||||
const engine = new Liquid({
|
||||
root: __dirname,
|
||||
extname: '.liquid',
|
||||
globals: {title: 'LiquidJS Demo'}
|
||||
globals: { title: 'LiquidJS Demo' }
|
||||
})
|
||||
|
||||
engine.registerTag('header', {
|
||||
@@ -21,6 +21,16 @@ const ctx = {
|
||||
todos: ['fork and clone', 'make it better', 'make a pull request']
|
||||
}
|
||||
|
||||
engine.renderFile('todolist', ctx)
|
||||
.then(console.log)
|
||||
.catch(err => console.error(err.stack))
|
||||
async function main () {
|
||||
console.log('==========renderFile===========')
|
||||
const html = await engine.renderFile('todolist', ctx)
|
||||
console.log(html)
|
||||
|
||||
console.log('===========Streamed===========')
|
||||
const tpls = await engine.parseFile('todolist')
|
||||
engine.renderToNodeStream(tpls, ctx)
|
||||
.on('data', data => process.stdout.write(data))
|
||||
.on('end', () => console.log(''))
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"liquidjs": "*"
|
||||
"liquidjs": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
+17
-23
@@ -1,45 +1,39 @@
|
||||
import BlockMode from '../../context/block-mode'
|
||||
import { BlockDrop } from '../../drop/block-drop'
|
||||
import { ParseStream, TagToken, TopLevelToken, Template, Context, TagImpl, Emitter } from '../../types'
|
||||
import { TagToken, TopLevelToken, Template, Context, TagImpl, Emitter } from '../../types'
|
||||
|
||||
export default {
|
||||
parse (this: TagImpl, token: TagToken, remainTokens: TopLevelToken[]) {
|
||||
const match = /\w+/.exec(token.args)
|
||||
this.block = match ? match[0] : ''
|
||||
this.tpls = [] as Template[]
|
||||
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||
.on('tag:endblock', () => stream.stop())
|
||||
this.liquid.parser.parseStream(remainTokens)
|
||||
.on('tag:endblock', function () { this.stop() })
|
||||
.on('template', (tpl: Template) => this.tpls.push(tpl))
|
||||
.on('end', () => {
|
||||
throw new Error(`tag ${token.getText()} not closed`)
|
||||
})
|
||||
stream.start()
|
||||
.on('end', () => { throw new Error(`tag ${token.getText()} not closed`) })
|
||||
.start()
|
||||
},
|
||||
|
||||
* render (this: TagImpl, ctx: Context, emitter: Emitter) {
|
||||
const blockRender = this.getBlockRender(ctx)
|
||||
yield this.emitHTML(ctx, emitter, blockRender)
|
||||
if (ctx.getRegister('blockMode') === BlockMode.STORE) {
|
||||
ctx.getRegister('blocks')[this.block] = blockRender
|
||||
} else {
|
||||
yield blockRender(new BlockDrop(), emitter)
|
||||
}
|
||||
},
|
||||
|
||||
getBlockRender (this: TagImpl, ctx: Context) {
|
||||
const { liquid, tpls } = this
|
||||
const extendedBlockRender = ctx.getRegister('blocks')[this.block]
|
||||
const defaultBlockRender = function * (superBlock: BlockDrop) {
|
||||
const renderChild = ctx.getRegister('blocks')[this.block]
|
||||
const renderCurrent = function * (superBlock: BlockDrop, emitter: Emitter) {
|
||||
// add {{ block.super }} support when rendering
|
||||
ctx.push({ block: superBlock })
|
||||
const result = yield liquid.renderer.renderTemplates(tpls, ctx)
|
||||
yield liquid.renderer.renderTemplates(tpls, ctx, emitter)
|
||||
ctx.pop()
|
||||
return result
|
||||
}
|
||||
return extendedBlockRender
|
||||
? (superBlock: BlockDrop) => extendedBlockRender(new BlockDrop(() => defaultBlockRender(superBlock)))
|
||||
: defaultBlockRender
|
||||
},
|
||||
|
||||
* emitHTML (this: TagImpl, ctx: Context, emitter: Emitter, blockRender: (block: BlockDrop) => string) {
|
||||
if (ctx.getRegister('blockMode', BlockMode.OUTPUT) === BlockMode.STORE) {
|
||||
ctx.getRegister('blocks')[this.block] = blockRender
|
||||
} else {
|
||||
emitter.write(yield blockRender(new BlockDrop()))
|
||||
}
|
||||
return renderChild
|
||||
? (superBlock: BlockDrop, emitter: Emitter) => renderChild(new BlockDrop(() => renderCurrent(superBlock, emitter)), emitter)
|
||||
: renderCurrent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { assert, Tokenizer, Emitter, Hash, TagToken, TopLevelToken, Context, TagImplOptions } from '../../types'
|
||||
import BlockMode from '../../context/block-mode'
|
||||
import { parseFilePath, renderFilePath } from './render'
|
||||
import { BlankDrop } from '../../drop/blank-drop'
|
||||
|
||||
export default {
|
||||
parseFilePath,
|
||||
@@ -16,8 +17,7 @@ export default {
|
||||
const { renderer } = liquid
|
||||
if (file === null) {
|
||||
ctx.setRegister('blockMode', BlockMode.OUTPUT)
|
||||
const html = yield renderer.renderTemplates(this.tpls, ctx)
|
||||
emitter.write(html)
|
||||
yield renderer.renderTemplates(this.tpls, ctx, emitter)
|
||||
return
|
||||
}
|
||||
const filepath = yield this.renderFilePath(this['file'], ctx, liquid)
|
||||
@@ -28,13 +28,14 @@ export default {
|
||||
ctx.setRegister('blockMode', BlockMode.STORE)
|
||||
const html = yield renderer.renderTemplates(this.tpls, ctx)
|
||||
const blocks = ctx.getRegister('blocks')
|
||||
if (blocks[''] === undefined) blocks[''] = () => html
|
||||
|
||||
// set whole content to anonymous block if anonymous doesn't specified
|
||||
if (blocks[''] === undefined) blocks[''] = (parent: BlankDrop, emitter: Emitter) => emitter.write(html)
|
||||
ctx.setRegister('blockMode', BlockMode.OUTPUT)
|
||||
|
||||
// render the layout file use stored blocks
|
||||
ctx.push(yield hash.render(ctx))
|
||||
const partial = yield renderer.renderTemplates(templates, ctx)
|
||||
yield renderer.renderTemplates(templates, ctx, emitter)
|
||||
ctx.pop()
|
||||
emitter.write(partial)
|
||||
}
|
||||
} as TagImplOptions
|
||||
|
||||
@@ -18,8 +18,8 @@ export class Context {
|
||||
this.globals = opts.globals
|
||||
this.environments = env
|
||||
}
|
||||
public getRegister (key: string, defaultValue = {}) {
|
||||
return (this.registers[key] = this.registers[key] || defaultValue)
|
||||
public getRegister (key: string) {
|
||||
return (this.registers[key] = this.registers[key] || {})
|
||||
}
|
||||
public setRegister (key: string, value: any) {
|
||||
return (this.registers[key] = value)
|
||||
|
||||
@@ -3,10 +3,14 @@ import { Drop } from './drop'
|
||||
export class BlockDrop extends Drop {
|
||||
constructor (
|
||||
// the block render from layout template
|
||||
private superBlockRender: () => Iterable<string> = () => ''
|
||||
private superBlockRender: () => Iterable<any> = () => ''
|
||||
) {
|
||||
super()
|
||||
}
|
||||
/**
|
||||
* Provide parent access in child block by
|
||||
* {{ block.super }}
|
||||
*/
|
||||
public super () {
|
||||
return this.superBlockRender()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
export interface Emitter {
|
||||
/**
|
||||
* Write a html value into emitter
|
||||
* @param html string, Drop or other primitive value
|
||||
*/
|
||||
write (html: any): void;
|
||||
end (): void;
|
||||
/**
|
||||
* Buffered string
|
||||
*/
|
||||
buffer: string;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { stringify, toValue } from '../util/underscore'
|
||||
import { Emitter } from '../types'
|
||||
|
||||
export class KeepingTypeEmitter {
|
||||
public html: any = '';
|
||||
export class KeepingTypeEmitter implements Emitter {
|
||||
public buffer: any = '';
|
||||
|
||||
public write (html: any) {
|
||||
html = toValue(html)
|
||||
@@ -9,14 +10,10 @@ export class KeepingTypeEmitter {
|
||||
// I.E:
|
||||
// {{ my-port }} -> 42
|
||||
// {{ my-host }}:{{ my-port }} -> 'host:42'
|
||||
if (typeof html !== 'string' && this.html === '') {
|
||||
this.html = html
|
||||
if (typeof html !== 'string' && this.buffer === '') {
|
||||
this.buffer = html
|
||||
} else {
|
||||
this.html = stringify(this.html) + stringify(html)
|
||||
this.buffer = stringify(this.buffer) + stringify(html)
|
||||
}
|
||||
}
|
||||
|
||||
public end () {
|
||||
return this.html
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,9 @@ import { stringify } from '../util/underscore'
|
||||
import { Emitter } from './emitter'
|
||||
|
||||
export class SimpleEmitter implements Emitter {
|
||||
public html: any = '';
|
||||
public buffer = '';
|
||||
|
||||
public write (html: any) {
|
||||
this.html += stringify(html)
|
||||
}
|
||||
|
||||
public end () {
|
||||
return this.html
|
||||
this.buffer += stringify(html)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { stringify } from '../util/underscore'
|
||||
|
||||
export class StreamedEmitter {
|
||||
public html: any = '';
|
||||
public buffer = '';
|
||||
public stream = new (require('stream').PassThrough)()
|
||||
public write (html: any) {
|
||||
this.stream.write(stringify(html))
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ 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))
|
||||
toThenable(this.renderTemplates(templates, ctx, emitter)).then(() => emitter.end())
|
||||
return emitter.stream
|
||||
}
|
||||
public * renderTemplates (templates: Template[], ctx: Context, emitter?: Emitter): IterableIterator<any> {
|
||||
@@ -29,6 +29,6 @@ export class Render {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
return emitter.end()
|
||||
return emitter.buffer
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user