feat(compiler): ensure interpolation expressions are wrapped with toString()
This commit is contained in:
parent
e09e887219
commit
b3b67b8c7d
@ -3835,6 +3835,7 @@ Object {
|
|||||||
"children": Array [
|
"children": Array [
|
||||||
Object {
|
Object {
|
||||||
"content": "a < b",
|
"content": "a < b",
|
||||||
|
"isInterpolation": true,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -6873,6 +6874,7 @@ Object {
|
|||||||
"children": Array [
|
"children": Array [
|
||||||
Object {
|
Object {
|
||||||
"content": "'</div>'",
|
"content": "'</div>'",
|
||||||
|
"isInterpolation": true,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7223,6 +7225,7 @@ Object {
|
|||||||
"children": Array [
|
"children": Array [
|
||||||
Object {
|
Object {
|
||||||
"content": "",
|
"content": "",
|
||||||
|
"isInterpolation": true,
|
||||||
"isStatic": true,
|
"isStatic": true,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7360,6 +7363,7 @@ Object {
|
|||||||
Object {
|
Object {
|
||||||
"arg": Object {
|
"arg": Object {
|
||||||
"content": "class",
|
"content": "class",
|
||||||
|
"isInterpolation": false,
|
||||||
"isStatic": true,
|
"isStatic": true,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7378,6 +7382,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"exp": Object {
|
"exp": Object {
|
||||||
"content": "{ some: condition }",
|
"content": "{ some: condition }",
|
||||||
|
"isInterpolation": false,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7438,6 +7443,7 @@ Object {
|
|||||||
Object {
|
Object {
|
||||||
"arg": Object {
|
"arg": Object {
|
||||||
"content": "style",
|
"content": "style",
|
||||||
|
"isInterpolation": false,
|
||||||
"isStatic": true,
|
"isStatic": true,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7456,6 +7462,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"exp": Object {
|
"exp": Object {
|
||||||
"content": "{ color: 'red' }",
|
"content": "{ color: 'red' }",
|
||||||
|
"isInterpolation": false,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7542,6 +7549,7 @@ Object {
|
|||||||
Object {
|
Object {
|
||||||
"arg": Object {
|
"arg": Object {
|
||||||
"content": "style",
|
"content": "style",
|
||||||
|
"isInterpolation": false,
|
||||||
"isStatic": true,
|
"isStatic": true,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7560,6 +7568,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"exp": Object {
|
"exp": Object {
|
||||||
"content": "{ color: 'red' }",
|
"content": "{ color: 'red' }",
|
||||||
|
"isInterpolation": false,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7639,6 +7648,7 @@ Object {
|
|||||||
Object {
|
Object {
|
||||||
"arg": Object {
|
"arg": Object {
|
||||||
"content": "class",
|
"content": "class",
|
||||||
|
"isInterpolation": false,
|
||||||
"isStatic": true,
|
"isStatic": true,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
@ -7657,6 +7667,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"exp": Object {
|
"exp": Object {
|
||||||
"content": "{ some: condition }",
|
"content": "{ some: condition }",
|
||||||
|
"isInterpolation": false,
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"loc": Object {
|
"loc": Object {
|
||||||
"end": Object {
|
"end": Object {
|
||||||
|
@ -13,7 +13,7 @@ describe('compiler: codegen', () => {
|
|||||||
with (this) {
|
with (this) {
|
||||||
return [
|
return [
|
||||||
"hello ",
|
"hello ",
|
||||||
world
|
toString(world)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
@ -25,7 +25,7 @@ describe('compiler: codegen', () => {
|
|||||||
const consumer = await new SourceMapConsumer(map as RawSourceMap)
|
const consumer = await new SourceMapConsumer(map as RawSourceMap)
|
||||||
const pos = consumer.originalPositionFor({
|
const pos = consumer.originalPositionFor({
|
||||||
line: 5,
|
line: 5,
|
||||||
column: 6
|
column: 15
|
||||||
})
|
})
|
||||||
expect(pos).toMatchObject({
|
expect(pos).toMatchObject({
|
||||||
line: 1,
|
line: 1,
|
||||||
|
@ -296,6 +296,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'message',
|
content: 'message',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isInterpolation: true,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 0, line: 1, column: 1 },
|
start: { offset: 0, line: 1, column: 1 },
|
||||||
end: { offset: 11, line: 1, column: 12 },
|
end: { offset: 11, line: 1, column: 12 },
|
||||||
@ -312,6 +313,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'a<b',
|
content: 'a<b',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isInterpolation: true,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 0, line: 1, column: 1 },
|
start: { offset: 0, line: 1, column: 1 },
|
||||||
end: { offset: 9, line: 1, column: 10 },
|
end: { offset: 9, line: 1, column: 10 },
|
||||||
@ -329,6 +331,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'a<b',
|
content: 'a<b',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isInterpolation: true,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 0, line: 1, column: 1 },
|
start: { offset: 0, line: 1, column: 1 },
|
||||||
end: { offset: 9, line: 1, column: 10 },
|
end: { offset: 9, line: 1, column: 10 },
|
||||||
@ -339,6 +342,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'c>d',
|
content: 'c>d',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isInterpolation: true,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 9, line: 1, column: 10 },
|
start: { offset: 9, line: 1, column: 10 },
|
||||||
end: { offset: 18, line: 1, column: 19 },
|
end: { offset: 18, line: 1, column: 19 },
|
||||||
@ -356,6 +360,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: '"</div>"',
|
content: '"</div>"',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isInterpolation: true,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 5, line: 1, column: 6 },
|
start: { offset: 5, line: 1, column: 6 },
|
||||||
end: { offset: 19, line: 1, column: 20 },
|
end: { offset: 19, line: 1, column: 20 },
|
||||||
@ -887,6 +892,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'a',
|
content: 'a',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isInterpolation: false,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 10, line: 1, column: 11 },
|
start: { offset: 10, line: 1, column: 11 },
|
||||||
end: { offset: 13, line: 1, column: 14 },
|
end: { offset: 13, line: 1, column: 14 },
|
||||||
@ -909,9 +915,10 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.DIRECTIVE,
|
type: NodeTypes.DIRECTIVE,
|
||||||
name: 'on',
|
name: 'on',
|
||||||
arg: {
|
arg: {
|
||||||
type: 4,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'click',
|
content: 'click',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isInterpolation: false,
|
||||||
loc: {
|
loc: {
|
||||||
source: 'click',
|
source: 'click',
|
||||||
start: {
|
start: {
|
||||||
@ -980,9 +987,10 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.DIRECTIVE,
|
type: NodeTypes.DIRECTIVE,
|
||||||
name: 'on',
|
name: 'on',
|
||||||
arg: {
|
arg: {
|
||||||
type: 4,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'click',
|
content: 'click',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isInterpolation: false,
|
||||||
loc: {
|
loc: {
|
||||||
source: 'click',
|
source: 'click',
|
||||||
start: {
|
start: {
|
||||||
@ -1015,9 +1023,10 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.DIRECTIVE,
|
type: NodeTypes.DIRECTIVE,
|
||||||
name: 'bind',
|
name: 'bind',
|
||||||
arg: {
|
arg: {
|
||||||
type: 4,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'a',
|
content: 'a',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isInterpolation: false,
|
||||||
loc: {
|
loc: {
|
||||||
source: 'a',
|
source: 'a',
|
||||||
start: {
|
start: {
|
||||||
@ -1037,6 +1046,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'b',
|
content: 'b',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isInterpolation: false,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 8, line: 1, column: 9 },
|
start: { offset: 8, line: 1, column: 9 },
|
||||||
end: { offset: 9, line: 1, column: 10 },
|
end: { offset: 9, line: 1, column: 10 },
|
||||||
@ -1059,9 +1069,10 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.DIRECTIVE,
|
type: NodeTypes.DIRECTIVE,
|
||||||
name: 'bind',
|
name: 'bind',
|
||||||
arg: {
|
arg: {
|
||||||
type: 4,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'a',
|
content: 'a',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isInterpolation: false,
|
||||||
loc: {
|
loc: {
|
||||||
source: 'a',
|
source: 'a',
|
||||||
start: {
|
start: {
|
||||||
@ -1081,6 +1092,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'b',
|
content: 'b',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isInterpolation: false,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 13, line: 1, column: 14 },
|
start: { offset: 13, line: 1, column: 14 },
|
||||||
end: { offset: 14, line: 1, column: 15 },
|
end: { offset: 14, line: 1, column: 15 },
|
||||||
@ -1103,9 +1115,10 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.DIRECTIVE,
|
type: NodeTypes.DIRECTIVE,
|
||||||
name: 'on',
|
name: 'on',
|
||||||
arg: {
|
arg: {
|
||||||
type: 4,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'a',
|
content: 'a',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isInterpolation: false,
|
||||||
loc: {
|
loc: {
|
||||||
source: 'a',
|
source: 'a',
|
||||||
start: {
|
start: {
|
||||||
@ -1125,6 +1138,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'b',
|
content: 'b',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isInterpolation: false,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 8, line: 1, column: 9 },
|
start: { offset: 8, line: 1, column: 9 },
|
||||||
end: { offset: 9, line: 1, column: 10 },
|
end: { offset: 9, line: 1, column: 10 },
|
||||||
@ -1147,9 +1161,10 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.DIRECTIVE,
|
type: NodeTypes.DIRECTIVE,
|
||||||
name: 'on',
|
name: 'on',
|
||||||
arg: {
|
arg: {
|
||||||
type: 4,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'a',
|
content: 'a',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
|
isInterpolation: false,
|
||||||
loc: {
|
loc: {
|
||||||
source: 'a',
|
source: 'a',
|
||||||
start: {
|
start: {
|
||||||
@ -1169,6 +1184,7 @@ describe('compiler: parse', () => {
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'b',
|
content: 'b',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isInterpolation: false,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 14, line: 1, column: 15 },
|
start: { offset: 14, line: 1, column: 15 },
|
||||||
end: { offset: 15, line: 1, column: 16 },
|
end: { offset: 15, line: 1, column: 16 },
|
||||||
|
@ -2,16 +2,9 @@ import { SourceMapConsumer } from 'source-map'
|
|||||||
import { compile } from '../../src'
|
import { compile } from '../../src'
|
||||||
|
|
||||||
test(`should work`, async () => {
|
test(`should work`, async () => {
|
||||||
const { code, map } = compile(
|
const { code, map } = compile(`<div>{{ foo }} bar</div>`, {
|
||||||
`<div v-for="i in foo">
|
|
||||||
{{ ({ a }, b) => a + b + i + c }} {{ i + 'fe' }} {{ i }}
|
|
||||||
</div>
|
|
||||||
<p>{{ i }}</p>
|
|
||||||
`,
|
|
||||||
{
|
|
||||||
prefixIdentifiers: true
|
prefixIdentifiers: true
|
||||||
}
|
})
|
||||||
)
|
|
||||||
console.log(code)
|
console.log(code)
|
||||||
const consumer = await new SourceMapConsumer(map!)
|
const consumer = await new SourceMapConsumer(map!)
|
||||||
const pos = consumer.originalPositionFor({
|
const pos = consumer.originalPositionFor({
|
||||||
|
@ -110,6 +110,7 @@ export interface ExpressionNode extends Node {
|
|||||||
type: NodeTypes.EXPRESSION
|
type: NodeTypes.EXPRESSION
|
||||||
content: string
|
content: string
|
||||||
isStatic: boolean
|
isStatic: boolean
|
||||||
|
isInterpolation: boolean
|
||||||
children?: (ExpressionNode | string)[]
|
children?: (ExpressionNode | string)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +209,8 @@ export function createExpression(
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
loc,
|
loc,
|
||||||
content,
|
content,
|
||||||
isStatic
|
isStatic,
|
||||||
|
isInterpolation: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||||
import { advancePositionWithMutation, assert } from './utils'
|
import { advancePositionWithMutation, assert } from './utils'
|
||||||
import { isString, isArray } from '@vue/shared'
|
import { isString, isArray } from '@vue/shared'
|
||||||
import { RENDER_LIST } from './runtimeConstants'
|
import { RENDER_LIST, TO_STRING } from './runtimeConstants'
|
||||||
|
|
||||||
type CodegenNode = ChildNode | JSChildNode
|
type CodegenNode = ChildNode | JSChildNode
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ export function generate(
|
|||||||
indent()
|
indent()
|
||||||
}
|
}
|
||||||
push(`return `)
|
push(`return `)
|
||||||
genChildren(ast.children, context)
|
genChildren(ast.children, context, true /* asRoot */)
|
||||||
if (!prefixIdentifiers) {
|
if (!prefixIdentifiers) {
|
||||||
deindent()
|
deindent()
|
||||||
push(`}`)
|
push(`}`)
|
||||||
@ -162,10 +162,23 @@ export function generate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will generate a single vnode call if the list has length === 1.
|
// This will generate a single vnode call if:
|
||||||
function genChildren(children: ChildNode[], context: CodegenContext) {
|
// - The list has length === 1, AND:
|
||||||
if (children.length === 1) {
|
// - This is a root node, OR:
|
||||||
genNode(children[0], context)
|
// - The only child is a text or expression.
|
||||||
|
function genChildren(
|
||||||
|
children: ChildNode[],
|
||||||
|
context: CodegenContext,
|
||||||
|
asRoot: boolean = false
|
||||||
|
) {
|
||||||
|
const child = children[0]
|
||||||
|
if (
|
||||||
|
children.length === 1 &&
|
||||||
|
(asRoot ||
|
||||||
|
child.type === NodeTypes.TEXT ||
|
||||||
|
child.type == NodeTypes.EXPRESSION)
|
||||||
|
) {
|
||||||
|
genNode(child, context)
|
||||||
} else {
|
} else {
|
||||||
genNodeListAsArray(children, context)
|
genNodeListAsArray(children, context)
|
||||||
}
|
}
|
||||||
@ -192,14 +205,9 @@ function genNodeList(
|
|||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
const node = nodes[i]
|
const node = nodes[i]
|
||||||
if (isString(node)) {
|
if (isString(node)) {
|
||||||
// plain code string
|
|
||||||
// note not adding quotes here because this can be any code,
|
|
||||||
// not just plain strings.
|
|
||||||
push(node)
|
push(node)
|
||||||
} else if (isArray(node)) {
|
} else if (isArray(node)) {
|
||||||
// child VNodes in a h() call
|
genChildren(node, context)
|
||||||
// not using genChildren here because we want them to always be an array
|
|
||||||
genNodeListAsArray(node, context)
|
|
||||||
} else {
|
} else {
|
||||||
genNode(node, context)
|
genNode(node, context)
|
||||||
}
|
}
|
||||||
@ -264,11 +272,19 @@ function genText(node: TextNode | ExpressionNode, context: CodegenContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function genExpression(node: ExpressionNode, context: CodegenContext) {
|
function genExpression(node: ExpressionNode, context: CodegenContext) {
|
||||||
if (node.children) {
|
const { push } = context
|
||||||
return genCompoundExpression(node, context)
|
const { content, children, isStatic, isInterpolation } = node
|
||||||
|
if (isInterpolation) {
|
||||||
|
push(`${TO_STRING}(`)
|
||||||
|
}
|
||||||
|
if (children) {
|
||||||
|
genCompoundExpression(node, context)
|
||||||
|
} else {
|
||||||
|
push(isStatic ? JSON.stringify(content) : content, node)
|
||||||
|
}
|
||||||
|
if (isInterpolation) {
|
||||||
|
push(`)`)
|
||||||
}
|
}
|
||||||
const text = node.isStatic ? JSON.stringify(node.content) : node.content
|
|
||||||
context.push(text, node)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function genExpressionAsPropertyKey(
|
function genExpressionAsPropertyKey(
|
||||||
|
@ -523,6 +523,7 @@ function parseAttribute(
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content,
|
content,
|
||||||
isStatic,
|
isStatic,
|
||||||
|
isInterpolation: false,
|
||||||
loc
|
loc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -540,6 +541,7 @@ function parseAttribute(
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: value.content,
|
content: value.content,
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isInterpolation: false,
|
||||||
loc: value.loc
|
loc: value.loc
|
||||||
},
|
},
|
||||||
arg,
|
arg,
|
||||||
@ -626,7 +628,8 @@ function parseInterpolation(
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content,
|
content,
|
||||||
loc: getSelection(context, start),
|
loc: getSelection(context, start),
|
||||||
isStatic: content === ''
|
isStatic: content === '',
|
||||||
|
isInterpolation: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
// Name mapping constants for runtime helpers that need to be imported in
|
// Name mapping constants for runtime helpers that need to be imported in
|
||||||
// generated code. Make sure these are correctly exported in the runtime!
|
// generated code. Make sure these are correctly exported in the runtime!
|
||||||
export const CREATE_ELEMENT = `h`
|
export const CREATE_VNODE = `createVNode`
|
||||||
export const RESOLVE_COMPONENT = `resolveComponent`
|
export const RESOLVE_COMPONENT = `resolveComponent`
|
||||||
export const RESOLVE_DIRECTIVE = `resolveDirective`
|
export const RESOLVE_DIRECTIVE = `resolveDirective`
|
||||||
export const APPLY_DIRECTIVES = `applyDirectives`
|
export const APPLY_DIRECTIVES = `applyDirectives`
|
||||||
export const RENDER_LIST = `renderList`
|
export const RENDER_LIST = `renderList`
|
||||||
export const CAPITALIZE = `capitalize`
|
export const CAPITALIZE = `capitalize`
|
||||||
|
export const TO_STRING = `toString`
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
} from './ast'
|
} from './ast'
|
||||||
import { isString, isArray } from '@vue/shared'
|
import { isString, isArray } from '@vue/shared'
|
||||||
import { CompilerError, defaultOnError } from './errors'
|
import { CompilerError, defaultOnError } from './errors'
|
||||||
|
import { TO_STRING } from './runtimeConstants'
|
||||||
|
|
||||||
// There are two types of transforms:
|
// There are two types of transforms:
|
||||||
//
|
//
|
||||||
@ -178,8 +179,15 @@ export function traverseNode(node: ChildNode, context: TransformContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// further traverse downwards
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
|
case NodeTypes.EXPRESSION:
|
||||||
|
// no need to traverse, but we need to inject toString helper
|
||||||
|
if (node.isInterpolation) {
|
||||||
|
context.imports.add(TO_STRING)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
// for container types, further traverse downwards
|
||||||
case NodeTypes.IF:
|
case NodeTypes.IF:
|
||||||
for (let i = 0; i < node.branches.length; i++) {
|
for (let i = 0; i < node.branches.length; i++) {
|
||||||
traverseChildren(node.branches[i], context)
|
traverseChildren(node.branches[i], context)
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
import { isArray } from '@vue/shared'
|
import { isArray } from '@vue/shared'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import {
|
import {
|
||||||
CREATE_ELEMENT,
|
CREATE_VNODE,
|
||||||
APPLY_DIRECTIVES,
|
APPLY_DIRECTIVES,
|
||||||
RESOLVE_DIRECTIVE,
|
RESOLVE_DIRECTIVE,
|
||||||
RESOLVE_COMPONENT
|
RESOLVE_COMPONENT
|
||||||
@ -67,8 +67,8 @@ export const prepareElementForCodegen: NodeTransform = (node, context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { loc } = node
|
const { loc } = node
|
||||||
context.imports.add(CREATE_ELEMENT)
|
context.imports.add(CREATE_VNODE)
|
||||||
const vnode = createCallExpression(CREATE_ELEMENT, args, loc)
|
const vnode = createCallExpression(CREATE_VNODE, args, loc)
|
||||||
|
|
||||||
if (runtimeDirectives && runtimeDirectives.length) {
|
if (runtimeDirectives && runtimeDirectives.length) {
|
||||||
context.imports.add(APPLY_DIRECTIVES)
|
context.imports.add(APPLY_DIRECTIVES)
|
||||||
|
@ -40,6 +40,9 @@ const simpleIdRE = /^[a-zA-Z$_][\w$]*$/
|
|||||||
let _parseScript: typeof parseScript
|
let _parseScript: typeof parseScript
|
||||||
let _walk: typeof walk
|
let _walk: typeof walk
|
||||||
|
|
||||||
|
// Important: since this function uses Node.js only dependencies, it should
|
||||||
|
// always be used with a leading !__BROWSER__ check so that it can be
|
||||||
|
// tree-shaken from the browser build.
|
||||||
export function processExpression(
|
export function processExpression(
|
||||||
node: ExpressionNode,
|
node: ExpressionNode,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
@ -73,7 +76,7 @@ export function processExpression(
|
|||||||
if (node.type === 'Identifier') {
|
if (node.type === 'Identifier') {
|
||||||
if (ids.indexOf(node) === -1) {
|
if (ids.indexOf(node) === -1) {
|
||||||
ids.push(node)
|
ids.push(node)
|
||||||
if (!knownIds[node.name] && shouldPrependContext(node, parent)) {
|
if (!knownIds[node.name] && shouldPrefix(node, parent)) {
|
||||||
node.name = `_ctx.${node.name}`
|
node.name = `_ctx.${node.name}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,7 +144,7 @@ const globals = new Set(
|
|||||||
const isFunction = (node: Node): node is Function =>
|
const isFunction = (node: Node): node is Function =>
|
||||||
/Function(Expression|Declaration)$/.test(node.type)
|
/Function(Expression|Declaration)$/.test(node.type)
|
||||||
|
|
||||||
function shouldPrependContext(identifier: Identifier, parent: Node) {
|
function shouldPrefix(identifier: Identifier, parent: Node) {
|
||||||
if (
|
if (
|
||||||
// not id of a FunctionDeclaration
|
// not id of a FunctionDeclaration
|
||||||
!(parent.type === 'FunctionDeclaration' && parent.id === identifier) &&
|
!(parent.type === 'FunctionDeclaration' && parent.id === identifier) &&
|
||||||
|
@ -1 +0,0 @@
|
|||||||
// TODO
|
|
@ -1 +0,0 @@
|
|||||||
// TODO
|
|
9
packages/compiler-core/src/transforms/vBindClass.ts
Normal file
9
packages/compiler-core/src/transforms/vBindClass.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Optimizations
|
||||||
|
// - b -> normalize(b)
|
||||||
|
// - ['foo', b] -> 'foo' + normalize(b)
|
||||||
|
// - { a, b: c } -> (a ? a : '') + (b ? c : '')
|
||||||
|
// - ['a', b, { c }] -> 'a' + normalize(b) + (c ? c : '')
|
||||||
|
|
||||||
|
// Also merge dynamic and static class into a single prop
|
||||||
|
|
||||||
|
// Attach CLASS patchFlag if necessary
|
14
packages/compiler-core/src/transforms/vBindStyle.ts
Normal file
14
packages/compiler-core/src/transforms/vBindStyle.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Optimizations
|
||||||
|
// The compiler pre-compiles static string styles into static objects
|
||||||
|
// + detects and hoists inline static objects
|
||||||
|
|
||||||
|
// e.g. `style="color: red"` and `:style="{ color: 'red' }"` both get hoisted as
|
||||||
|
|
||||||
|
// ``` js
|
||||||
|
// const style = { color: 'red' }
|
||||||
|
// render() { return e('div', { style }) }
|
||||||
|
// ```
|
||||||
|
|
||||||
|
// Also nerge dynamic and static style into a single prop
|
||||||
|
|
||||||
|
// Attach STYLE patchFlag if necessary
|
@ -21,10 +21,10 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||||||
'for',
|
'for',
|
||||||
(node, dir, context) => {
|
(node, dir, context) => {
|
||||||
if (dir.exp) {
|
if (dir.exp) {
|
||||||
context.imports.add(RENDER_LIST)
|
|
||||||
const parseResult = parseForExpression(dir.exp, context)
|
const parseResult = parseForExpression(dir.exp, context)
|
||||||
|
|
||||||
if (parseResult) {
|
if (parseResult) {
|
||||||
|
context.imports.add(RENDER_LIST)
|
||||||
const { source, value, key, index } = parseResult
|
const { source, value, key, index } = parseResult
|
||||||
|
|
||||||
context.replaceNode({
|
context.replaceNode({
|
||||||
|
@ -115,6 +115,7 @@ describe('DOM parser', () => {
|
|||||||
type: NodeTypes.EXPRESSION,
|
type: NodeTypes.EXPRESSION,
|
||||||
content: 'a < b',
|
content: 'a < b',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
isInterpolation: true,
|
||||||
loc: {
|
loc: {
|
||||||
start: { offset: 5, line: 1, column: 6 },
|
start: { offset: 5, line: 1, column: 6 },
|
||||||
end: { offset: 19, line: 1, column: 20 },
|
end: { offset: 19, line: 1, column: 20 },
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { isObject } from '@vue/shared'
|
import { isObject, toTypeString } from '@vue/shared'
|
||||||
import { mutableHandlers, readonlyHandlers } from './baseHandlers'
|
import { mutableHandlers, readonlyHandlers } from './baseHandlers'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -35,7 +35,7 @@ const canObserve = (value: any): boolean => {
|
|||||||
return (
|
return (
|
||||||
!value._isVue &&
|
!value._isVue &&
|
||||||
!value._isVNode &&
|
!value._isVNode &&
|
||||||
observableValueRE.test(Object.prototype.toString.call(value)) &&
|
observableValueRE.test(toTypeString(value)) &&
|
||||||
!nonReactiveValues.has(value)
|
!nonReactiveValues.has(value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ import {
|
|||||||
isArray,
|
isArray,
|
||||||
isObject,
|
isObject,
|
||||||
isReservedProp,
|
isReservedProp,
|
||||||
hasOwn
|
hasOwn,
|
||||||
|
toTypeString
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { Data, ComponentInternalInstance } from './component'
|
import { Data, ComponentInternalInstance } from './component'
|
||||||
@ -374,7 +375,7 @@ function styleValue(value: any, type: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toRawType(value: any): string {
|
function toRawType(value: any): string {
|
||||||
return Object.prototype.toString.call(value).slice(8, -1)
|
return toTypeString(value).slice(8, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isExplicable(type: string): boolean {
|
function isExplicable(type: string): boolean {
|
||||||
|
10
packages/runtime-core/src/helpers/toString.ts
Normal file
10
packages/runtime-core/src/helpers/toString.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { isArray, isPlainObject, objectToString } from '@vue/shared'
|
||||||
|
|
||||||
|
// for conversting {{ interpolation }} values to displayed strings.
|
||||||
|
export function toString(val: any): string {
|
||||||
|
return val == null
|
||||||
|
? ''
|
||||||
|
: isArray(val) || (isPlainObject(val) && val.toString === objectToString)
|
||||||
|
? JSON.stringify(val, null, 2)
|
||||||
|
: String(val)
|
||||||
|
}
|
@ -36,9 +36,11 @@ export {
|
|||||||
} from './errorHandling'
|
} from './errorHandling'
|
||||||
|
|
||||||
// Internal, for compiler generated code
|
// Internal, for compiler generated code
|
||||||
|
// should sync with '@vue/compiler-core/src/runtimeConstants.ts'
|
||||||
export { applyDirectives } from './directives'
|
export { applyDirectives } from './directives'
|
||||||
export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
|
export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
|
||||||
export { renderList } from './helpers/renderList'
|
export { renderList } from './helpers/renderList'
|
||||||
|
export { toString } from './helpers/toString'
|
||||||
export { capitalize } from '@vue/shared'
|
export { capitalize } from '@vue/shared'
|
||||||
|
|
||||||
// Internal, for integration with runtime compiler
|
// Internal, for integration with runtime compiler
|
||||||
|
@ -30,6 +30,13 @@ export const isString = (val: any): val is string => typeof val === 'string'
|
|||||||
export const isObject = (val: any): val is Record<any, any> =>
|
export const isObject = (val: any): val is Record<any, any> =>
|
||||||
val !== null && typeof val === 'object'
|
val !== null && typeof val === 'object'
|
||||||
|
|
||||||
|
export const objectToString = Object.prototype.toString
|
||||||
|
export const toTypeString = (value: unknown): string =>
|
||||||
|
objectToString.call(value)
|
||||||
|
|
||||||
|
export const isPlainObject = (val: any): val is object =>
|
||||||
|
toTypeString(val) === '[object Object]'
|
||||||
|
|
||||||
const vnodeHooksRE = /^vnode/
|
const vnodeHooksRE = /^vnode/
|
||||||
export const isReservedProp = (key: string): boolean =>
|
export const isReservedProp = (key: string): boolean =>
|
||||||
key === 'key' || key === 'ref' || vnodeHooksRE.test(key)
|
key === 'key' || key === 'ref' || vnodeHooksRE.test(key)
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import * as Vue from '../src'
|
||||||
import { createApp } from '../src'
|
import { createApp } from '../src'
|
||||||
|
|
||||||
|
;(window as any).Vue = Vue
|
||||||
|
|
||||||
it('should support on-the-fly template compilation', () => {
|
it('should support on-the-fly template compilation', () => {
|
||||||
const container = document.createElement('div')
|
const container = document.createElement('div')
|
||||||
const App = {
|
const App = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user