wip(ssr): escape helpers
This commit is contained in:
parent
4e40d0d7c8
commit
066ba82c7f
@ -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()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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', () => {
|
||||||
|
@ -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(`)`)
|
||||||
}
|
}
|
||||||
|
@ -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`,
|
||||||
|
@ -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
|
||||||
|
@ -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
|
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
|
||||||
|
|
||||||
|
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'
|
} 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)[]
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user