wip(ssr): escape helpers

This commit is contained in:
Evan You 2020-01-26 17:35:21 -05:00
parent 4e40d0d7c8
commit 066ba82c7f
10 changed files with 81 additions and 23 deletions

View File

@ -18,7 +18,7 @@ import {
} from '../src' } from '../src'
import { import {
CREATE_VNODE, CREATE_VNODE,
TO_STRING, TO_DISPLAY_STRING,
RESOLVE_DIRECTIVE, RESOLVE_DIRECTIVE,
helperNameMap, helperNameMap,
RESOLVE_COMPONENT, RESOLVE_COMPONENT,
@ -164,7 +164,7 @@ describe('compiler: codegen', () => {
codegenNode: createInterpolation(`hello`, locStub) codegenNode: createInterpolation(`hello`, locStub)
}) })
) )
expect(code).toMatch(`return _${helperNameMap[TO_STRING]}(hello)`) expect(code).toMatch(`return _${helperNameMap[TO_DISPLAY_STRING]}(hello)`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -197,7 +197,9 @@ describe('compiler: codegen', () => {
]) ])
}) })
) )
expect(code).toMatch(`return _ctx.foo + _${helperNameMap[TO_STRING]}(bar)`) expect(code).toMatch(
`return _ctx.foo + _${helperNameMap[TO_DISPLAY_STRING]}(bar)`
)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })

View File

@ -8,7 +8,7 @@ import {
} from '../src/ast' } from '../src/ast'
import { ErrorCodes, createCompilerError } from '../src/errors' import { ErrorCodes, createCompilerError } from '../src/errors'
import { import {
TO_STRING, TO_DISPLAY_STRING,
OPEN_BLOCK, OPEN_BLOCK,
CREATE_BLOCK, CREATE_BLOCK,
FRAGMENT, FRAGMENT,
@ -227,7 +227,7 @@ describe('compiler: transform', () => {
test('should inject toString helper for interpolations', () => { test('should inject toString helper for interpolations', () => {
const ast = baseParse(`{{ foo }}`) const ast = baseParse(`{{ foo }}`)
transform(ast, {}) transform(ast, {})
expect(ast.helpers).toContain(TO_STRING) expect(ast.helpers).toContain(TO_DISPLAY_STRING)
}) })
test('should inject createVNode and Comment for comments', () => { test('should inject createVNode and Comment for comments', () => {

View File

@ -31,7 +31,7 @@ import {
import { isString, isArray, isSymbol } from '@vue/shared' import { isString, isArray, isSymbol } from '@vue/shared'
import { import {
helperNameMap, helperNameMap,
TO_STRING, TO_DISPLAY_STRING,
CREATE_VNODE, CREATE_VNODE,
RESOLVE_COMPONENT, RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE, RESOLVE_DIRECTIVE,
@ -491,7 +491,7 @@ function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
function genInterpolation(node: InterpolationNode, context: CodegenContext) { function genInterpolation(node: InterpolationNode, context: CodegenContext) {
const { push, helper } = context const { push, helper } = context
push(`${helper(TO_STRING)}(`) push(`${helper(TO_DISPLAY_STRING)}(`)
genNode(node.content, context) genNode(node.content, context)
push(`)`) push(`)`)
} }

View File

@ -17,7 +17,7 @@ export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``)
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``) export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``) export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
export const CREATE_SLOTS = Symbol(__DEV__ ? `createSlots` : ``) export const CREATE_SLOTS = Symbol(__DEV__ ? `createSlots` : ``)
export const TO_STRING = Symbol(__DEV__ ? `toString` : ``) export const TO_DISPLAY_STRING = Symbol(__DEV__ ? `toDisplayString` : ``)
export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``) export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``)
export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``) export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``) export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
@ -47,7 +47,7 @@ export const helperNameMap: any = {
[RENDER_LIST]: `renderList`, [RENDER_LIST]: `renderList`,
[RENDER_SLOT]: `renderSlot`, [RENDER_SLOT]: `renderSlot`,
[CREATE_SLOTS]: `createSlots`, [CREATE_SLOTS]: `createSlots`,
[TO_STRING]: `toString`, [TO_DISPLAY_STRING]: `toDisplayString`,
[MERGE_PROPS]: `mergeProps`, [MERGE_PROPS]: `mergeProps`,
[TO_HANDLERS]: `toHandlers`, [TO_HANDLERS]: `toHandlers`,
[CAMELIZE]: `camelize`, [CAMELIZE]: `camelize`,

View File

@ -27,7 +27,7 @@ import {
} from '@vue/shared' } from '@vue/shared'
import { defaultOnError } from './errors' import { defaultOnError } from './errors'
import { import {
TO_STRING, TO_DISPLAY_STRING,
FRAGMENT, FRAGMENT,
helperNameMap, helperNameMap,
WITH_DIRECTIVES, WITH_DIRECTIVES,
@ -365,7 +365,7 @@ export function traverseNode(
break break
case NodeTypes.INTERPOLATION: case NodeTypes.INTERPOLATION:
// no need to traverse, but we need to inject toString helper // no need to traverse, but we need to inject toString helper
context.helper(TO_STRING) context.helper(TO_DISPLAY_STRING)
break break
// for container types, further traverse downwards // for container types, further traverse downwards

View File

@ -1,10 +0,0 @@
import { isArray, isPlainObject, objectToString } from '@vue/shared'
// for converting {{ interpolation }} values to displayed strings.
export function toString(val: unknown): string {
return val == null
? ''
: isArray(val) || (isPlainObject(val) && val.toString === objectToString)
? JSON.stringify(val, null, 2)
: String(val)
}

View File

@ -81,7 +81,6 @@ export {
resolveDynamicComponent resolveDynamicComponent
} from './helpers/resolveAssets' } from './helpers/resolveAssets'
export { renderList } from './helpers/renderList' export { renderList } from './helpers/renderList'
export { toString } from './helpers/toString'
export { toHandlers } from './helpers/toHandlers' export { toHandlers } from './helpers/toHandlers'
export { renderSlot } from './helpers/renderSlot' export { renderSlot } from './helpers/renderSlot'
export { createSlots } from './helpers/createSlots' export { createSlots } from './helpers/createSlots'
@ -90,7 +89,12 @@ export { setBlockTracking, createTextVNode, createCommentVNode } from './vnode'
// Since @vue/shared is inlined into final builds, // Since @vue/shared is inlined into final builds,
// when re-exporting from @vue/shared we need to avoid relying on their original // when re-exporting from @vue/shared we need to avoid relying on their original
// types so that the bundled d.ts does not attempt to import from it. // types so that the bundled d.ts does not attempt to import from it.
import { capitalize as _capitalize, camelize as _camelize } from '@vue/shared' import {
toDisplayString as _toDisplayString,
capitalize as _capitalize,
camelize as _camelize
} from '@vue/shared'
export const toDisplayString = _toDisplayString as (s: unknown) => string
export const capitalize = _capitalize as (s: string) => string export const capitalize = _capitalize as (s: string) => string
export const camelize = _camelize as (s: string) => string export const camelize = _camelize as (s: string) => string

View File

@ -0,0 +1,51 @@
import { toDisplayString } from '@vue/shared'
const escapeRE = /["'&<>]/
export function escape(string: unknown) {
const str = '' + string
const match = escapeRE.exec(str)
if (!match) {
return str
}
let html = ''
let escaped: string
let index: number
let lastIndex = 0
for (index = match.index; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34: // "
escaped = '&quot;'
break
case 38: // &
escaped = '&amp;'
break
case 39: // '
escaped = '&#39;'
break
case 60: // <
escaped = '&lt;'
break
case 62: // >
escaped = '&gt;'
break
default:
continue
}
if (lastIndex !== index) {
html += str.substring(lastIndex, index)
}
lastIndex = index + 1
html += escaped
}
return lastIndex !== index ? html + str.substring(lastIndex, index) : html
}
export function interpolate(value: unknown) {
return escape(toDisplayString(value))
}

View File

@ -9,6 +9,8 @@ import {
} from 'vue' } from 'vue'
import { isString, isPromise, isArray } from '@vue/shared' import { isString, isPromise, isArray } from '@vue/shared'
export * from './helpers'
type SSRBuffer = SSRBufferItem[] type SSRBuffer = SSRBufferItem[]
type SSRBufferItem = string | ResolvedSSRBuffer | Promise<SSRBuffer> type SSRBufferItem = string | ResolvedSSRBuffer | Promise<SSRBuffer>
type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[] type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]

View File

@ -98,3 +98,12 @@ export const capitalize = cacheStringFunction(
// compare whether a value has changed, accounting for NaN. // compare whether a value has changed, accounting for NaN.
export const hasChanged = (value: any, oldValue: any): boolean => export const hasChanged = (value: any, oldValue: any): boolean =>
value !== oldValue && (value === value || oldValue === oldValue) value !== oldValue && (value === value || oldValue === oldValue)
// for converting {{ interpolation }} values to displayed strings.
export function toDisplayString(val: unknown): string {
return val == null
? ''
: isArray(val) || (isPlainObject(val) && val.toString === objectToString)
? JSON.stringify(val, null, 2)
: String(val)
}