wip(ssr): escape helpers
This commit is contained in:
parent
4e40d0d7c8
commit
066ba82c7f
@ -18,7 +18,7 @@ import {
|
||||
} from '../src'
|
||||
import {
|
||||
CREATE_VNODE,
|
||||
TO_STRING,
|
||||
TO_DISPLAY_STRING,
|
||||
RESOLVE_DIRECTIVE,
|
||||
helperNameMap,
|
||||
RESOLVE_COMPONENT,
|
||||
@ -164,7 +164,7 @@ describe('compiler: codegen', () => {
|
||||
codegenNode: createInterpolation(`hello`, locStub)
|
||||
})
|
||||
)
|
||||
expect(code).toMatch(`return _${helperNameMap[TO_STRING]}(hello)`)
|
||||
expect(code).toMatch(`return _${helperNameMap[TO_DISPLAY_STRING]}(hello)`)
|
||||
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()
|
||||
})
|
||||
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
} from '../src/ast'
|
||||
import { ErrorCodes, createCompilerError } from '../src/errors'
|
||||
import {
|
||||
TO_STRING,
|
||||
TO_DISPLAY_STRING,
|
||||
OPEN_BLOCK,
|
||||
CREATE_BLOCK,
|
||||
FRAGMENT,
|
||||
@ -227,7 +227,7 @@ describe('compiler: transform', () => {
|
||||
test('should inject toString helper for interpolations', () => {
|
||||
const ast = baseParse(`{{ foo }}`)
|
||||
transform(ast, {})
|
||||
expect(ast.helpers).toContain(TO_STRING)
|
||||
expect(ast.helpers).toContain(TO_DISPLAY_STRING)
|
||||
})
|
||||
|
||||
test('should inject createVNode and Comment for comments', () => {
|
||||
|
@ -31,7 +31,7 @@ import {
|
||||
import { isString, isArray, isSymbol } from '@vue/shared'
|
||||
import {
|
||||
helperNameMap,
|
||||
TO_STRING,
|
||||
TO_DISPLAY_STRING,
|
||||
CREATE_VNODE,
|
||||
RESOLVE_COMPONENT,
|
||||
RESOLVE_DIRECTIVE,
|
||||
@ -491,7 +491,7 @@ function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
|
||||
|
||||
function genInterpolation(node: InterpolationNode, context: CodegenContext) {
|
||||
const { push, helper } = context
|
||||
push(`${helper(TO_STRING)}(`)
|
||||
push(`${helper(TO_DISPLAY_STRING)}(`)
|
||||
genNode(node.content, context)
|
||||
push(`)`)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``)
|
||||
export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``)
|
||||
export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
|
||||
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 TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
|
||||
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
|
||||
@ -47,7 +47,7 @@ export const helperNameMap: any = {
|
||||
[RENDER_LIST]: `renderList`,
|
||||
[RENDER_SLOT]: `renderSlot`,
|
||||
[CREATE_SLOTS]: `createSlots`,
|
||||
[TO_STRING]: `toString`,
|
||||
[TO_DISPLAY_STRING]: `toDisplayString`,
|
||||
[MERGE_PROPS]: `mergeProps`,
|
||||
[TO_HANDLERS]: `toHandlers`,
|
||||
[CAMELIZE]: `camelize`,
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
} from '@vue/shared'
|
||||
import { defaultOnError } from './errors'
|
||||
import {
|
||||
TO_STRING,
|
||||
TO_DISPLAY_STRING,
|
||||
FRAGMENT,
|
||||
helperNameMap,
|
||||
WITH_DIRECTIVES,
|
||||
@ -365,7 +365,7 @@ export function traverseNode(
|
||||
break
|
||||
case NodeTypes.INTERPOLATION:
|
||||
// no need to traverse, but we need to inject toString helper
|
||||
context.helper(TO_STRING)
|
||||
context.helper(TO_DISPLAY_STRING)
|
||||
break
|
||||
|
||||
// for container types, further traverse downwards
|
||||
|
@ -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)
|
||||
}
|
@ -81,7 +81,6 @@ export {
|
||||
resolveDynamicComponent
|
||||
} from './helpers/resolveAssets'
|
||||
export { renderList } from './helpers/renderList'
|
||||
export { toString } from './helpers/toString'
|
||||
export { toHandlers } from './helpers/toHandlers'
|
||||
export { renderSlot } from './helpers/renderSlot'
|
||||
export { createSlots } from './helpers/createSlots'
|
||||
@ -90,7 +89,12 @@ export { setBlockTracking, createTextVNode, createCommentVNode } from './vnode'
|
||||
// Since @vue/shared is inlined into final builds,
|
||||
// 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.
|
||||
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 camelize = _camelize as (s: string) => string
|
||||
|
||||
|
51
packages/server-renderer/src/helpers.ts
Normal file
51
packages/server-renderer/src/helpers.ts
Normal 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 = '"'
|
||||
break
|
||||
case 38: // &
|
||||
escaped = '&'
|
||||
break
|
||||
case 39: // '
|
||||
escaped = '''
|
||||
break
|
||||
case 60: // <
|
||||
escaped = '<'
|
||||
break
|
||||
case 62: // >
|
||||
escaped = '>'
|
||||
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))
|
||||
}
|
@ -9,6 +9,8 @@ import {
|
||||
} from 'vue'
|
||||
import { isString, isPromise, isArray } from '@vue/shared'
|
||||
|
||||
export * from './helpers'
|
||||
|
||||
type SSRBuffer = SSRBufferItem[]
|
||||
type SSRBufferItem = string | ResolvedSSRBuffer | Promise<SSRBuffer>
|
||||
type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]
|
||||
|
@ -98,3 +98,12 @@ export const capitalize = cacheStringFunction(
|
||||
// compare whether a value has changed, accounting for NaN.
|
||||
export const hasChanged = (value: any, oldValue: any): boolean =>
|
||||
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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user