feat: drop keepOutputType option (#838)

Remove KeepingTypeEmitter and always stringify via SimpleEmitter. On next, map breaking commits to patch so alpha stays on 11.x.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Yang Jun
2026-07-09 22:32:18 +08:00
co-authored by Cursor
parent cc4a9ce0a7
commit 6e48c4e39f
7 changed files with 16 additions and 125 deletions
+14 -4
View File
@@ -17,16 +17,26 @@ const githubPlugin = [
}
]
// next is branch-protected (PR-only); skip @semantic-release/git there and publish to npm only.
const onMaster = process.env.GITHUB_REF === 'refs/heads/master'
const onNext = process.env.GITHUB_REF === 'refs/heads/next'
// On next, breaking changes are v11 WIP — bump alpha prerelease only, not major.
const commitAnalyzer = onNext
? ['@semantic-release/commit-analyzer', {
releaseRules: [
{ breaking: true, release: 'patch' }
]
}]
: '@semantic-release/commit-analyzer'
const basePlugins = [
'@semantic-release/commit-analyzer',
commitAnalyzer,
'@semantic-release/release-notes-generator',
'@semantic-release/changelog',
'@semantic-release/npm'
]
// next is branch-protected (PR-only); skip @semantic-release/git there and publish to npm only.
const onMaster = process.env.GITHUB_REF === 'refs/heads/master'
module.exports = {
branches: [
'master',
-1
View File
@@ -1,4 +1,3 @@
export * from './emitter'
export * from './simple-emitter'
export * from './streamed-emitter'
export * from './keeping-type-emitter'
-19
View File
@@ -1,19 +0,0 @@
import { stringify, toValue } from '../util'
import { Emitter } from './emitter'
export class KeepingTypeEmitter implements Emitter {
public buffer: 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.buffer === '') {
this.buffer = html
} else {
this.buffer = stringify(this.buffer) + stringify(html)
}
}
}
-4
View File
@@ -79,8 +79,6 @@ export interface LiquidOptions {
templates?: {[key: string]: string};
/** the global scope passed down to all partial and layout templates, i.e. templates included by `include`, `layout` and `render` tags. */
globals?: object;
/** Whether or not to keep value type when writing the Output, not working for streamed rendering. Defaults to `false`. */
keepOutputType?: boolean;
/** Default escape filter applied to output values, when set, you'll have to add `| raw` for values don't need to be escaped. Defaults to `undefined`. */
outputEscape?: OutputEscapeOption;
/** An object of operators for conditional statements. Defaults to the regular Liquid operators. */
@@ -160,7 +158,6 @@ export interface NormalizedFullOptions extends NormalizedOptions {
preserveTimezones: boolean;
greedy: boolean;
globals: object;
keepOutputType: boolean;
operators: Operators;
parseLimit: number;
renderLimit: number;
@@ -196,7 +193,6 @@ export const defaultOptions: NormalizedFullOptions = {
ownPropertyOnly: true,
lenientIf: false,
globals: {},
keepOutputType: false,
operators: defaultOperators,
memoryLimit: Infinity,
parseLimit: Infinity,
+2 -2
View File
@@ -2,7 +2,7 @@ import { getPerformance } from '../util/performance'
import { toPromise, RenderError, LiquidErrors, LiquidError } from '../util'
import { Context } from '../context'
import { Template } from '../template'
import { Emitter, KeepingTypeEmitter, StreamedEmitter, SimpleEmitter } from '../emitters'
import { Emitter, StreamedEmitter, SimpleEmitter } from '../emitters'
export class Render {
public renderTemplatesToNodeStream (templates: Template[], ctx: Context): NodeJS.ReadableStream {
@@ -13,7 +13,7 @@ export class Render {
}
public * renderTemplates (templates: Template[], ctx: Context, emitter?: Emitter): IterableIterator<any> {
if (!emitter) {
emitter = ctx.opts.keepOutputType ? new KeepingTypeEmitter() : new SimpleEmitter()
emitter = new SimpleEmitter()
}
ctx.renderLimit.check(getPerformance().now())
const errors = []
-56
View File
@@ -2,7 +2,6 @@ import { toPromise } from '../util'
import { Context } from '../context'
import { Output } from '../template'
import { OutputToken } from '../tokens'
import { defaultOptions } from '../liquid-options'
describe('Output', function () {
const emitter: any = { write: (html: string) => (emitter.html += html), html: '' }
@@ -30,59 +29,4 @@ describe('Output', function () {
await toPromise(output.render(scope, emitter))
return expect(emitter.html).toBe('FOO')
})
it('should respect to .toString()', async () => {
const scope = new Context({ obj: { toString: () => 'FOO' } })
const output = new Output(token, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).toBe('FOO')
})
describe('when keepOutputType is enabled', () => {
const emitter: any = {
write: (html: any) => {
if (emitter.keepOutputType && typeof html !== 'string') {
emitter.html = html
} else {
emitter.html += html as string
}
},
html: '',
keepOutputType: true
}
const token = { content: 'foo', input: 'foo' } as OutputToken
beforeEach(() => { emitter.html = '' })
it('should respect output variable number type', async () => {
const scope = new Context({
foo: 42
}, { ...defaultOptions, keepOutputType: true })
const output = new Output(token, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).toBe(42)
})
it('should respect output variable boolean type', async () => {
const scope = new Context({
foo: true
}, { ...defaultOptions, keepOutputType: true })
const output = new Output(token, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).toBe(true)
})
it('should respect output variable object type', async () => {
const scope = new Context({
foo: 'test'
}, { ...defaultOptions, keepOutputType: true })
const output = new Output(token, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).toBe('test')
})
it('should respect output variable string type', async () => {
const scope = new Context({
foo: { a: { b: 42 } }
}, { ...defaultOptions, keepOutputType: true })
const output = new Output(token, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).toEqual({ a: { b: 42 } })
})
})
})
@@ -1,39 +0,0 @@
import { Liquid } from '../../../src/liquid'
describe('LiquidOptions#*keepOutputType*', function () {
it('should respect keepOutputType', async function () {
const engine = new Liquid({
keepOutputType: true
})
const context = {
'my-boolean': true,
'my-number': 42,
'my-string': 'test'
}
const booleanHtml = await engine.parseAndRender('{{my-boolean}}', context)
expect(booleanHtml).toBe(true)
const numberHtml = await engine.parseAndRender('{{my-number}}', context)
expect(numberHtml).toBe(42)
const html = await engine.parseAndRender('{{my-string}}', context)
expect(html).toBe('test')
const composedHtml = await engine.parseAndRender('{{my-string}}:{{my-number}}', context)
expect(composedHtml).toBe('test:42')
})
it('should respect keepOutputType = false as default', async function () {
const engine = new Liquid()
const context = {
'my-boolean': true,
'my-number': 42,
'my-string': 'test'
}
const booleanHtml = await engine.parseAndRender('{{my-boolean}}', context)
expect(booleanHtml).toBe('true')
const numberHtml = await engine.parseAndRender('{{my-number}}', context)
expect(numberHtml).toBe('42')
const html = await engine.parseAndRender('{{my-string}}', context)
expect(html).toBe('test')
const composedHtml = await engine.parseAndRender('{{my-string}}:{{my-number}}', context)
expect(composedHtml).toBe('test:42')
})
})