refactor(compiler): separate Interpolation, SimpleExpression & CompoundExpression types
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { isString } from '@vue/shared'
|
||||
|
||||
// Vue template is a platform-agnostic superset of HTML (syntax only).
|
||||
// More namespaces like SVG and MathML are declared by platform specific
|
||||
// compilers.
|
||||
@@ -12,7 +14,8 @@ export const enum NodeTypes {
|
||||
ELEMENT,
|
||||
TEXT,
|
||||
COMMENT,
|
||||
EXPRESSION,
|
||||
SIMPLE_EXPRESSION,
|
||||
INTERPOLATION,
|
||||
ATTRIBUTE,
|
||||
DIRECTIVE,
|
||||
// containers
|
||||
@@ -55,9 +58,11 @@ export interface Position {
|
||||
|
||||
export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode
|
||||
|
||||
export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode
|
||||
|
||||
export type ChildNode =
|
||||
| ElementNode
|
||||
| ExpressionNode
|
||||
| InterpolationNode
|
||||
| TextNode
|
||||
| CommentNode
|
||||
| IfNode
|
||||
@@ -107,12 +112,21 @@ export interface DirectiveNode extends Node {
|
||||
modifiers: string[]
|
||||
}
|
||||
|
||||
export interface ExpressionNode extends Node {
|
||||
type: NodeTypes.EXPRESSION
|
||||
export interface SimpleExpressionNode extends Node {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION
|
||||
content: string
|
||||
isStatic: boolean
|
||||
isInterpolation: boolean
|
||||
children?: (ExpressionNode | string)[]
|
||||
}
|
||||
|
||||
export interface InterpolationNode extends Node {
|
||||
type: NodeTypes.INTERPOLATION
|
||||
content: ExpressionNode
|
||||
}
|
||||
|
||||
// always dynamic
|
||||
export interface CompoundExpressionNode extends Node {
|
||||
type: NodeTypes.COMPOUND_EXPRESSION
|
||||
children: (SimpleExpressionNode | string)[]
|
||||
}
|
||||
|
||||
export interface IfNode extends Node {
|
||||
@@ -129,9 +143,9 @@ export interface IfBranchNode extends Node {
|
||||
export interface ForNode extends Node {
|
||||
type: NodeTypes.FOR
|
||||
source: ExpressionNode
|
||||
valueAlias: ExpressionNode | undefined
|
||||
keyAlias: ExpressionNode | undefined
|
||||
objectIndexAlias: ExpressionNode | undefined
|
||||
valueAlias: SimpleExpressionNode | undefined
|
||||
keyAlias: SimpleExpressionNode | undefined
|
||||
objectIndexAlias: SimpleExpressionNode | undefined
|
||||
children: ChildNode[]
|
||||
}
|
||||
|
||||
@@ -190,7 +204,7 @@ export function createObjectExpression(
|
||||
|
||||
export function createObjectProperty(
|
||||
key: ExpressionNode,
|
||||
value: ExpressionNode,
|
||||
value: JSChildNode,
|
||||
loc: SourceLocation
|
||||
): Property {
|
||||
return {
|
||||
@@ -201,18 +215,40 @@ export function createObjectProperty(
|
||||
}
|
||||
}
|
||||
|
||||
export function createExpression(
|
||||
export function createSimpleExpression(
|
||||
content: string,
|
||||
isStatic: boolean,
|
||||
loc: SourceLocation,
|
||||
isInterpolation = false
|
||||
): ExpressionNode {
|
||||
loc: SourceLocation
|
||||
): SimpleExpressionNode {
|
||||
return {
|
||||
type: NodeTypes.EXPRESSION,
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
loc,
|
||||
content,
|
||||
isStatic,
|
||||
isInterpolation
|
||||
isStatic
|
||||
}
|
||||
}
|
||||
|
||||
export function createInterpolation(
|
||||
content: string | CompoundExpressionNode,
|
||||
loc: SourceLocation
|
||||
): InterpolationNode {
|
||||
return {
|
||||
type: NodeTypes.INTERPOLATION,
|
||||
loc,
|
||||
content: isString(content)
|
||||
? createSimpleExpression(content, false, loc)
|
||||
: content
|
||||
}
|
||||
}
|
||||
|
||||
export function createCompoundExpression(
|
||||
children: CompoundExpressionNode['children'],
|
||||
loc: SourceLocation
|
||||
): CompoundExpressionNode {
|
||||
return {
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
loc,
|
||||
children
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,10 @@ import {
|
||||
ObjectExpression,
|
||||
IfBranchNode,
|
||||
SourceLocation,
|
||||
Position
|
||||
Position,
|
||||
InterpolationNode,
|
||||
CompoundExpressionNode,
|
||||
SimpleExpressionNode
|
||||
} from './ast'
|
||||
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||
import {
|
||||
@@ -110,11 +113,7 @@ function createCodegenContext(
|
||||
if (!__BROWSER__ && context.map) {
|
||||
if (node) {
|
||||
let name
|
||||
if (
|
||||
node.type === NodeTypes.EXPRESSION &&
|
||||
!node.children &&
|
||||
!node.isStatic
|
||||
) {
|
||||
if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) {
|
||||
const content = node.content.replace(/^_ctx\./, '')
|
||||
if (content !== node.content && isSimpleIdentifier(content)) {
|
||||
name = content
|
||||
@@ -263,7 +262,7 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
|
||||
|
||||
// This will generate a single vnode call if:
|
||||
// - The target position explicitly allows a single node (root, if, for)
|
||||
// - The list has length === 1, AND The only child is a text or expression.
|
||||
// - The list has length === 1, AND The only child is a text, expression or comment.
|
||||
function genChildren(
|
||||
children: ChildNode[],
|
||||
context: CodegenContext,
|
||||
@@ -277,7 +276,8 @@ function genChildren(
|
||||
children.length === 1 &&
|
||||
(allowSingle ||
|
||||
child.type === NodeTypes.TEXT ||
|
||||
child.type == NodeTypes.EXPRESSION)
|
||||
child.type === NodeTypes.INTERPOLATION ||
|
||||
child.type === NodeTypes.COMMENT)
|
||||
) {
|
||||
genNode(child, context)
|
||||
} else {
|
||||
@@ -331,9 +331,15 @@ function genNode(node: CodegenNode, context: CodegenContext) {
|
||||
case NodeTypes.TEXT:
|
||||
genText(node, context)
|
||||
break
|
||||
case NodeTypes.EXPRESSION:
|
||||
case NodeTypes.SIMPLE_EXPRESSION:
|
||||
genExpression(node, context)
|
||||
break
|
||||
case NodeTypes.INTERPOLATION:
|
||||
genInterpolation(node, context)
|
||||
break
|
||||
case NodeTypes.COMPOUND_EXPRESSION:
|
||||
genCompoundExpression(node, context)
|
||||
break
|
||||
case NodeTypes.COMMENT:
|
||||
genComment(node, context)
|
||||
break
|
||||
@@ -369,23 +375,36 @@ function genElement(node: ElementNode, context: CodegenContext) {
|
||||
genCallExpression(node.codegenNode!, context, false)
|
||||
}
|
||||
|
||||
function genText(node: TextNode | ExpressionNode, context: CodegenContext) {
|
||||
function genText(
|
||||
node: TextNode | SimpleExpressionNode,
|
||||
context: CodegenContext
|
||||
) {
|
||||
context.push(JSON.stringify(node.content), node)
|
||||
}
|
||||
|
||||
function genExpression(node: ExpressionNode, context: CodegenContext) {
|
||||
function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
|
||||
const { content, isStatic } = node
|
||||
context.push(isStatic ? JSON.stringify(content) : content, node)
|
||||
}
|
||||
|
||||
function genInterpolation(node: InterpolationNode, context: CodegenContext) {
|
||||
const { push, helper } = context
|
||||
const { content, children, isStatic, isInterpolation } = node
|
||||
if (isInterpolation) {
|
||||
push(`${helper(TO_STRING)}(`)
|
||||
}
|
||||
if (children) {
|
||||
genCompoundExpression(node, context)
|
||||
} else {
|
||||
push(isStatic ? JSON.stringify(content) : content, node)
|
||||
}
|
||||
if (isInterpolation) {
|
||||
push(`)`)
|
||||
push(`${helper(TO_STRING)}(`)
|
||||
genNode(node.content, context)
|
||||
push(`)`)
|
||||
}
|
||||
|
||||
function genCompoundExpression(
|
||||
node: CompoundExpressionNode,
|
||||
context: CodegenContext
|
||||
) {
|
||||
for (let i = 0; i < node.children!.length; i++) {
|
||||
const child = node.children![i]
|
||||
if (isString(child)) {
|
||||
context.push(child)
|
||||
} else {
|
||||
genExpression(child, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,28 +413,18 @@ function genExpressionAsPropertyKey(
|
||||
context: CodegenContext
|
||||
) {
|
||||
const { push } = context
|
||||
const { content, children, isStatic } = node
|
||||
if (children) {
|
||||
if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
|
||||
push(`[`)
|
||||
genCompoundExpression(node, context)
|
||||
push(`]`)
|
||||
} else if (isStatic) {
|
||||
} else if (node.isStatic) {
|
||||
// only quote keys if necessary
|
||||
const text = isSimpleIdentifier(content) ? content : JSON.stringify(content)
|
||||
const text = isSimpleIdentifier(node.content)
|
||||
? node.content
|
||||
: JSON.stringify(node.content)
|
||||
push(text, node)
|
||||
} else {
|
||||
push(`[${content}]`, node)
|
||||
}
|
||||
}
|
||||
|
||||
function genCompoundExpression(node: ExpressionNode, context: CodegenContext) {
|
||||
for (let i = 0; i < node.children!.length; i++) {
|
||||
const child = node.children![i]
|
||||
if (isString(child)) {
|
||||
context.push(child)
|
||||
} else {
|
||||
genExpression(child, context)
|
||||
}
|
||||
push(`[${node.content}]`, node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,10 +454,14 @@ function genIfBranch(
|
||||
if (condition) {
|
||||
// v-if or v-else-if
|
||||
const { push, indent, deindent, newline } = context
|
||||
const needsQuote = !isSimpleIdentifier(condition.content)
|
||||
needsQuote && push(`(`)
|
||||
genExpression(condition, context)
|
||||
needsQuote && push(`)`)
|
||||
if (condition.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
const needsQuote = !isSimpleIdentifier(condition.content)
|
||||
needsQuote && push(`(`)
|
||||
genExpression(condition, context)
|
||||
needsQuote && push(`)`)
|
||||
} else {
|
||||
genCompoundExpression(condition, context)
|
||||
}
|
||||
indent()
|
||||
context.indentLevel++
|
||||
push(`? `)
|
||||
@@ -473,7 +486,7 @@ function genFor(node: ForNode, context: CodegenContext) {
|
||||
const { push, helper, indent, deindent } = context
|
||||
const { source, keyAlias, valueAlias, objectIndexAlias, children } = node
|
||||
push(`${helper(RENDER_LIST)}(`, node, true)
|
||||
genExpression(source, context)
|
||||
genNode(source, context)
|
||||
push(`, (`)
|
||||
if (valueAlias) {
|
||||
genExpression(valueAlias, context)
|
||||
|
||||
@@ -128,7 +128,8 @@ export const errorMessages: { [code: number]: string } = {
|
||||
[ErrorCodes.X_MISSING_INTERPOLATION_END]:
|
||||
'Interpolation end sign was not found.',
|
||||
[ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END]:
|
||||
'End bracket for dynamic directive argument was not found.',
|
||||
'End bracket for dynamic directive argument was not found. ' +
|
||||
'Note that dynamic directive argument connot contain spaces.',
|
||||
|
||||
// transform errors
|
||||
[ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if`,
|
||||
|
||||
@@ -23,7 +23,8 @@ import {
|
||||
RootNode,
|
||||
SourceLocation,
|
||||
TextNode,
|
||||
ChildNode
|
||||
ChildNode,
|
||||
InterpolationNode
|
||||
} from './ast'
|
||||
|
||||
export interface ParserOptions {
|
||||
@@ -122,7 +123,7 @@ function parseChildren(
|
||||
while (!isEnd(context, mode, ancestors)) {
|
||||
__DEV__ && assert(context.source.length > 0)
|
||||
const s = context.source
|
||||
let node: any = null
|
||||
let node: ChildNode | ChildNode[] | undefined = undefined
|
||||
|
||||
if (startsWith(s, context.options.delimiters[0])) {
|
||||
// '{{'
|
||||
@@ -529,10 +530,9 @@ function parseAttribute(
|
||||
}
|
||||
|
||||
arg = {
|
||||
type: NodeTypes.EXPRESSION,
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content,
|
||||
isStatic,
|
||||
isInterpolation: false,
|
||||
loc
|
||||
}
|
||||
}
|
||||
@@ -555,10 +555,9 @@ function parseAttribute(
|
||||
? 'on'
|
||||
: 'slot'),
|
||||
exp: value && {
|
||||
type: NodeTypes.EXPRESSION,
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: value.content,
|
||||
isStatic: false,
|
||||
isInterpolation: false,
|
||||
loc: value.loc
|
||||
},
|
||||
arg,
|
||||
@@ -633,7 +632,7 @@ function parseAttributeValue(
|
||||
function parseInterpolation(
|
||||
context: ParserContext,
|
||||
mode: TextModes
|
||||
): ExpressionNode | undefined {
|
||||
): InterpolationNode | undefined {
|
||||
const [open, close] = context.options.delimiters
|
||||
__DEV__ && assert(startsWith(context.source, open))
|
||||
|
||||
@@ -643,28 +642,32 @@ function parseInterpolation(
|
||||
return undefined
|
||||
}
|
||||
|
||||
advanceBy(context, open.length)
|
||||
const start = getCursor(context)
|
||||
const end = getCursor(context)
|
||||
advanceBy(context, open.length)
|
||||
const innerStart = getCursor(context)
|
||||
const innerEnd = getCursor(context)
|
||||
const rawContentLength = closeIndex - open.length
|
||||
const rawContent = context.source.slice(0, rawContentLength)
|
||||
const preTrimContent = parseTextData(context, rawContentLength, mode)
|
||||
const content = preTrimContent.trim()
|
||||
const startOffset = preTrimContent.indexOf(content)
|
||||
if (startOffset > 0) {
|
||||
advancePositionWithMutation(start, rawContent, startOffset)
|
||||
advancePositionWithMutation(innerStart, rawContent, startOffset)
|
||||
}
|
||||
const endOffset =
|
||||
rawContentLength - (preTrimContent.length - content.length - startOffset)
|
||||
advancePositionWithMutation(end, rawContent, endOffset)
|
||||
advancePositionWithMutation(innerEnd, rawContent, endOffset)
|
||||
advanceBy(context, close.length)
|
||||
|
||||
return {
|
||||
type: NodeTypes.EXPRESSION,
|
||||
content,
|
||||
loc: getSelection(context, start, end),
|
||||
isStatic: content === '',
|
||||
isInterpolation: true
|
||||
type: NodeTypes.INTERPOLATION,
|
||||
content: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
isStatic: false,
|
||||
content,
|
||||
loc: getSelection(context, innerStart, innerEnd)
|
||||
},
|
||||
loc: getSelection(context, start)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,3 +13,4 @@ export const RENDER_LIST = `renderList`
|
||||
export const TO_STRING = `toString`
|
||||
export const MERGE_PROPS = `mergeProps`
|
||||
export const TO_HANDLERS = `toHandlers`
|
||||
export const CAMELIZE = `camelize`
|
||||
|
||||
@@ -7,8 +7,9 @@ import {
|
||||
DirectiveNode,
|
||||
Property,
|
||||
ExpressionNode,
|
||||
createExpression,
|
||||
JSChildNode
|
||||
createSimpleExpression,
|
||||
JSChildNode,
|
||||
SimpleExpressionNode
|
||||
} from './ast'
|
||||
import { isString, isArray } from '@vue/shared'
|
||||
import { CompilerError, defaultOnError } from './errors'
|
||||
@@ -63,8 +64,8 @@ export interface TransformContext extends Required<TransformOptions> {
|
||||
replaceNode(node: ChildNode): void
|
||||
removeNode(node?: ChildNode): void
|
||||
onNodeRemoved: () => void
|
||||
addIdentifier(exp: ExpressionNode): void
|
||||
removeIdentifier(exp: ExpressionNode): void
|
||||
addIdentifier(exp: SimpleExpressionNode): void
|
||||
removeIdentifier(exp: SimpleExpressionNode): void
|
||||
hoist(exp: JSChildNode): ExpressionNode
|
||||
}
|
||||
|
||||
@@ -138,7 +139,7 @@ function createTransformContext(
|
||||
},
|
||||
hoist(exp) {
|
||||
context.hoists.push(exp)
|
||||
return createExpression(
|
||||
return createSimpleExpression(
|
||||
`_hoisted_${context.hoists.length}`,
|
||||
false,
|
||||
exp.loc
|
||||
@@ -205,11 +206,9 @@ export function traverseNode(node: ChildNode, context: TransformContext) {
|
||||
// comment nodes with `createVNode`
|
||||
context.helper(COMMENT)
|
||||
break
|
||||
case NodeTypes.EXPRESSION:
|
||||
case NodeTypes.INTERPOLATION:
|
||||
// no need to traverse, but we need to inject toString helper
|
||||
if (node.isInterpolation) {
|
||||
context.helper(TO_STRING)
|
||||
}
|
||||
context.helper(TO_STRING)
|
||||
break
|
||||
|
||||
// for container types, further traverse downwards
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
createCallExpression,
|
||||
createArrayExpression,
|
||||
createObjectProperty,
|
||||
createExpression,
|
||||
createSimpleExpression,
|
||||
createObjectExpression,
|
||||
Property
|
||||
} from '../ast'
|
||||
@@ -122,8 +122,12 @@ function buildProps(
|
||||
const { loc, name, value } = prop
|
||||
properties.push(
|
||||
createObjectProperty(
|
||||
createExpression(name, true, getInnerRange(loc, 0, name.length)),
|
||||
createExpression(
|
||||
createSimpleExpression(
|
||||
name,
|
||||
true,
|
||||
getInnerRange(loc, 0, name.length)
|
||||
),
|
||||
createSimpleExpression(
|
||||
value ? value.content : '',
|
||||
true,
|
||||
value ? value.loc : loc
|
||||
@@ -236,8 +240,8 @@ function dedupeProperties(properties: Property[]): Property[] {
|
||||
const deduped: Property[] = []
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
const prop = properties[i]
|
||||
// dynamic key named are always allowed
|
||||
if (!prop.key.isStatic) {
|
||||
// dynamic keys are always allowed
|
||||
if (prop.key.type === NodeTypes.COMPOUND_EXPRESSION || !prop.key.isStatic) {
|
||||
deduped.push(prop)
|
||||
continue
|
||||
}
|
||||
@@ -273,16 +277,7 @@ function mergeAsArray(existing: Property, incoming: Property) {
|
||||
// :class="expression" class="string"
|
||||
// -> class: expression + "string"
|
||||
function mergeClasses(existing: Property, incoming: Property) {
|
||||
const e = existing.value as ExpressionNode
|
||||
const children =
|
||||
e.children ||
|
||||
(e.children = [
|
||||
{
|
||||
...e,
|
||||
children: undefined
|
||||
}
|
||||
])
|
||||
children.push(` + " " + `, incoming.value as ExpressionNode)
|
||||
// TODO
|
||||
}
|
||||
|
||||
function createDirectiveArgs(
|
||||
@@ -305,8 +300,8 @@ function createDirectiveArgs(
|
||||
createObjectExpression(
|
||||
dir.modifiers.map(modifier =>
|
||||
createObjectProperty(
|
||||
createExpression(modifier, true, loc),
|
||||
createExpression(`true`, false, loc),
|
||||
createSimpleExpression(modifier, true, loc),
|
||||
createSimpleExpression(`true`, false, loc),
|
||||
loc
|
||||
)
|
||||
),
|
||||
|
||||
@@ -11,26 +11,39 @@
|
||||
import { parseScript } from 'meriyah'
|
||||
import { walk } from 'estree-walker'
|
||||
import { NodeTransform, TransformContext } from '../transform'
|
||||
import { NodeTypes, createExpression, ExpressionNode } from '../ast'
|
||||
import {
|
||||
NodeTypes,
|
||||
createSimpleExpression,
|
||||
ExpressionNode,
|
||||
SimpleExpressionNode,
|
||||
CompoundExpressionNode,
|
||||
createCompoundExpression
|
||||
} from '../ast'
|
||||
import { Node, Function, Identifier, Property } from 'estree'
|
||||
import { advancePositionWithClone } from '../utils'
|
||||
|
||||
export const transformExpression: NodeTransform = (node, context) => {
|
||||
if (node.type === NodeTypes.EXPRESSION && !node.isStatic) {
|
||||
processExpression(node, context)
|
||||
if (node.type === NodeTypes.INTERPOLATION) {
|
||||
node.content = processExpression(
|
||||
node.content as SimpleExpressionNode,
|
||||
context
|
||||
)
|
||||
} else if (node.type === NodeTypes.ELEMENT) {
|
||||
// handle directives on element
|
||||
for (let i = 0; i < node.props.length; i++) {
|
||||
const prop = node.props[i]
|
||||
if (prop.type === NodeTypes.DIRECTIVE) {
|
||||
if (prop.exp) {
|
||||
processExpression(prop.exp, context)
|
||||
const exp = prop.exp as SimpleExpressionNode | undefined
|
||||
const arg = prop.arg as SimpleExpressionNode | undefined
|
||||
if (exp) {
|
||||
prop.exp = processExpression(exp, context)
|
||||
}
|
||||
if (prop.arg && !prop.arg.isStatic) {
|
||||
if (arg && !arg.isStatic) {
|
||||
if (prop.name === 'class') {
|
||||
// TODO special expression optimization for classes
|
||||
processExpression(prop.arg, context)
|
||||
prop.arg = processExpression(arg, context)
|
||||
} else {
|
||||
processExpression(prop.arg, context)
|
||||
prop.arg = processExpression(arg, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,32 +73,33 @@ interface PrefixMeta {
|
||||
// always be used with a leading !__BROWSER__ check so that it can be
|
||||
// tree-shaken from the browser build.
|
||||
export function processExpression(
|
||||
node: ExpressionNode,
|
||||
node: SimpleExpressionNode,
|
||||
context: TransformContext
|
||||
) {
|
||||
): ExpressionNode {
|
||||
if (!context.prefixIdentifiers) {
|
||||
return
|
||||
return node
|
||||
}
|
||||
// lazy require dependencies so that they don't end up in rollup's dep graph
|
||||
// and thus can be tree-shaken in browser builds.
|
||||
const parseScript =
|
||||
_parseScript || (_parseScript = require('meriyah').parseScript)
|
||||
const walk = _walk || (_walk = require('estree-walker').walk)
|
||||
|
||||
// fast path if expression is a simple identifier.
|
||||
if (simpleIdRE.test(node.content)) {
|
||||
if (!context.identifiers[node.content]) {
|
||||
node.content = `_ctx.${node.content}`
|
||||
}
|
||||
return
|
||||
return node
|
||||
}
|
||||
|
||||
// lazy require dependencies so that they don't end up in rollup's dep graph
|
||||
// and thus can be tree-shaken in browser builds.
|
||||
const parseScript =
|
||||
_parseScript || (_parseScript = require('meriyah').parseScript)
|
||||
const walk = _walk || (_walk = require('estree-walker').walk)
|
||||
|
||||
let ast
|
||||
try {
|
||||
ast = parseScript(`(${node.content})`, { ranges: true }) as any
|
||||
} catch (e) {
|
||||
context.onError(e)
|
||||
return
|
||||
return node
|
||||
}
|
||||
|
||||
const ids: (Identifier & PrefixMeta)[] = []
|
||||
@@ -145,7 +159,7 @@ export function processExpression(
|
||||
// an ExpressionNode has the `.children` property, it will be used instead of
|
||||
// `.content`.
|
||||
const full = node.content
|
||||
const children: ExpressionNode['children'] = []
|
||||
const children: CompoundExpressionNode['children'] = []
|
||||
ids.sort((a, b) => a.start - b.start)
|
||||
ids.forEach((id, i) => {
|
||||
const last = ids[i - 1] as any
|
||||
@@ -155,7 +169,7 @@ export function processExpression(
|
||||
}
|
||||
const source = full.slice(id.start - 1, id.end - 1)
|
||||
children.push(
|
||||
createExpression(id.name, false, {
|
||||
createSimpleExpression(id.name, false, {
|
||||
source,
|
||||
start: advancePositionWithClone(node.loc.start, source, id.start - 1),
|
||||
end: advancePositionWithClone(node.loc.start, source, id.end - 1)
|
||||
@@ -167,10 +181,9 @@ export function processExpression(
|
||||
})
|
||||
|
||||
if (children.length) {
|
||||
// mark it empty so that it's more noticeable in case another transform or
|
||||
// codegen forget to handle `.children` first.
|
||||
node.content = __DEV__ ? `[[REMOVED]]` : ``
|
||||
node.children = children
|
||||
return createCompoundExpression(children, node.loc)
|
||||
} else {
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NodeTransform } from '../transform'
|
||||
import { NodeTypes, createExpression } from '../ast'
|
||||
import { NodeTypes, createSimpleExpression } from '../ast'
|
||||
|
||||
// Parse inline CSS strings for static style attributes into an object.
|
||||
// This is a NodeTransform since it works on the static `style` attribute and
|
||||
@@ -13,11 +13,11 @@ export const transformStyle: NodeTransform = (node, context) => {
|
||||
if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) {
|
||||
// replace p with an expression node
|
||||
const parsed = JSON.stringify(parseInlineCSS(p.value.content))
|
||||
const exp = context.hoist(createExpression(parsed, false, p.loc))
|
||||
const exp = context.hoist(createSimpleExpression(parsed, false, p.loc))
|
||||
node.props[i] = {
|
||||
type: NodeTypes.DIRECTIVE,
|
||||
name: `bind`,
|
||||
arg: createExpression(`style`, true, p.loc),
|
||||
arg: createSimpleExpression(`style`, true, p.loc),
|
||||
exp,
|
||||
modifiers: [],
|
||||
loc: p.loc
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
import { DirectiveTransform } from '../transform'
|
||||
import { createObjectProperty, createExpression } from '../ast'
|
||||
import { createObjectProperty, createSimpleExpression, NodeTypes } from '../ast'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import { camelize } from '@vue/shared'
|
||||
import { CAMELIZE } from '../runtimeConstants'
|
||||
|
||||
// v-bind without arg is handled directly in ./element.ts due to it affecting
|
||||
// codegen for the entire props object. This transform here is only for v-bind
|
||||
// *with* args.
|
||||
export const transformBind: DirectiveTransform = (
|
||||
{ exp, arg, modifiers, loc },
|
||||
context
|
||||
) => {
|
||||
export const transformBind: DirectiveTransform = (dir, context) => {
|
||||
const { exp, modifiers, loc } = dir
|
||||
const arg = dir.arg!
|
||||
if (!exp) {
|
||||
context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc))
|
||||
}
|
||||
// .prop is no longer necessary due to new patch behavior
|
||||
// .sync is replced by v-model:arg
|
||||
if (modifiers.includes('camel')) {
|
||||
arg!.content = camelize(arg!.content)
|
||||
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
if (arg.isStatic) {
|
||||
arg.content = camelize(arg.content)
|
||||
} else {
|
||||
arg.content = `${context.helper(CAMELIZE)}(${arg.content})`
|
||||
}
|
||||
} else {
|
||||
arg.children.unshift(`${context.helper(CAMELIZE)}(`)
|
||||
arg.children.push(`)`)
|
||||
}
|
||||
}
|
||||
return {
|
||||
props: createObjectProperty(
|
||||
arg!,
|
||||
exp || createExpression('', true, loc),
|
||||
exp || createSimpleExpression('', true, loc),
|
||||
loc
|
||||
),
|
||||
needRuntime: false
|
||||
|
||||
@@ -5,8 +5,9 @@ import {
|
||||
import {
|
||||
NodeTypes,
|
||||
ExpressionNode,
|
||||
createExpression,
|
||||
SourceLocation
|
||||
createSimpleExpression,
|
||||
SourceLocation,
|
||||
SimpleExpressionNode
|
||||
} from '../ast'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import { getInnerRange } from '../utils'
|
||||
@@ -17,7 +18,12 @@ export const transformFor = createStructuralDirectiveTransform(
|
||||
'for',
|
||||
(node, dir, context) => {
|
||||
if (dir.exp) {
|
||||
const parseResult = parseForExpression(dir.exp, context)
|
||||
const parseResult = parseForExpression(
|
||||
// can only be simple expression because vFor transform is applied
|
||||
// before expression transform.
|
||||
dir.exp as SimpleExpressionNode,
|
||||
context
|
||||
)
|
||||
|
||||
if (parseResult) {
|
||||
context.helper(RENDER_LIST)
|
||||
@@ -66,29 +72,33 @@ const stripParensRE = /^\(|\)$/g
|
||||
|
||||
interface ForParseResult {
|
||||
source: ExpressionNode
|
||||
value: ExpressionNode | undefined
|
||||
key: ExpressionNode | undefined
|
||||
index: ExpressionNode | undefined
|
||||
value: SimpleExpressionNode | undefined
|
||||
key: SimpleExpressionNode | undefined
|
||||
index: SimpleExpressionNode | undefined
|
||||
}
|
||||
|
||||
function parseForExpression(
|
||||
input: ExpressionNode,
|
||||
input: SimpleExpressionNode,
|
||||
context: TransformContext
|
||||
): ForParseResult | null {
|
||||
const loc = input.loc
|
||||
const source = input.content
|
||||
const inMatch = source.match(forAliasRE)
|
||||
const exp = input.content
|
||||
const inMatch = exp.match(forAliasRE)
|
||||
if (!inMatch) return null
|
||||
|
||||
const [, LHS, RHS] = inMatch
|
||||
|
||||
let source: ExpressionNode = createAliasExpression(
|
||||
loc,
|
||||
RHS.trim(),
|
||||
exp.indexOf(RHS, LHS.length)
|
||||
)
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
source = processExpression(source, context)
|
||||
}
|
||||
|
||||
const result: ForParseResult = {
|
||||
source: createAliasExpression(
|
||||
loc,
|
||||
RHS.trim(),
|
||||
source.indexOf(RHS, LHS.length),
|
||||
context,
|
||||
context.prefixIdentifiers
|
||||
),
|
||||
source,
|
||||
value: undefined,
|
||||
key: undefined,
|
||||
index: undefined
|
||||
@@ -106,11 +116,8 @@ function parseForExpression(
|
||||
const keyContent = iteratorMatch[1].trim()
|
||||
let keyOffset: number | undefined
|
||||
if (keyContent) {
|
||||
keyOffset = source.indexOf(
|
||||
keyContent,
|
||||
trimmedOffset + valueContent.length
|
||||
)
|
||||
result.key = createAliasExpression(loc, keyContent, keyOffset, context)
|
||||
keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
|
||||
result.key = createAliasExpression(loc, keyContent, keyOffset)
|
||||
}
|
||||
|
||||
if (iteratorMatch[2]) {
|
||||
@@ -120,25 +127,19 @@ function parseForExpression(
|
||||
result.index = createAliasExpression(
|
||||
loc,
|
||||
indexContent,
|
||||
source.indexOf(
|
||||
exp.indexOf(
|
||||
indexContent,
|
||||
result.key
|
||||
? keyOffset! + keyContent.length
|
||||
: trimmedOffset + valueContent.length
|
||||
),
|
||||
context
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valueContent) {
|
||||
result.value = createAliasExpression(
|
||||
loc,
|
||||
valueContent,
|
||||
trimmedOffset,
|
||||
context
|
||||
)
|
||||
result.value = createAliasExpression(loc, valueContent, trimmedOffset)
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -147,17 +148,11 @@ function parseForExpression(
|
||||
function createAliasExpression(
|
||||
range: SourceLocation,
|
||||
content: string,
|
||||
offset: number,
|
||||
context: TransformContext,
|
||||
process: boolean = false
|
||||
): ExpressionNode {
|
||||
const exp = createExpression(
|
||||
offset: number
|
||||
): SimpleExpressionNode {
|
||||
return createSimpleExpression(
|
||||
content,
|
||||
false,
|
||||
getInnerRange(range, offset, content.length)
|
||||
)
|
||||
if (!__BROWSER__ && process) {
|
||||
processExpression(exp, context)
|
||||
}
|
||||
return exp
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
ElementTypes,
|
||||
ElementNode,
|
||||
DirectiveNode,
|
||||
IfBranchNode
|
||||
IfBranchNode,
|
||||
SimpleExpressionNode
|
||||
} from '../ast'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import { processExpression } from './transformExpression'
|
||||
@@ -16,7 +17,9 @@ export const transformIf = createStructuralDirectiveTransform(
|
||||
/^(if|else|else-if)$/,
|
||||
(node, dir, context) => {
|
||||
if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
|
||||
processExpression(dir.exp, context)
|
||||
// dir.exp can only be simple expression because vIf transform is applied
|
||||
// before expression transform.
|
||||
processExpression(dir.exp as SimpleExpressionNode, context)
|
||||
}
|
||||
if (dir.name === 'if') {
|
||||
context.replaceNode({
|
||||
|
||||
@@ -1,37 +1,46 @@
|
||||
import { DirectiveTransform } from '../transform'
|
||||
import { createObjectProperty, createExpression, ExpressionNode } from '../ast'
|
||||
import {
|
||||
createObjectProperty,
|
||||
createSimpleExpression,
|
||||
ExpressionNode,
|
||||
NodeTypes,
|
||||
createCompoundExpression
|
||||
} from '../ast'
|
||||
import { capitalize } from '@vue/shared'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
|
||||
// v-on without arg is handled directly in ./element.ts due to it affecting
|
||||
// codegen for the entire props object. This transform here is only for v-on
|
||||
// *with* args.
|
||||
export const transformOn: DirectiveTransform = (
|
||||
{ arg, exp, loc, modifiers },
|
||||
context
|
||||
) => {
|
||||
export const transformOn: DirectiveTransform = (dir, context) => {
|
||||
const { exp, loc, modifiers } = dir
|
||||
const arg = dir.arg!
|
||||
if (!exp && !modifiers.length) {
|
||||
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
|
||||
}
|
||||
const { content, isStatic, loc: argLoc } = arg!
|
||||
let eventName: ExpressionNode
|
||||
if (isStatic) {
|
||||
// static arg
|
||||
eventName = createExpression(`on${capitalize(content)}`, true, argLoc)
|
||||
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
if (arg.isStatic) {
|
||||
eventName = createSimpleExpression(
|
||||
`on${capitalize(arg.content)}`,
|
||||
true,
|
||||
arg.loc
|
||||
)
|
||||
} else {
|
||||
eventName = createCompoundExpression([`"on" + (`, arg, `)`], arg.loc)
|
||||
}
|
||||
} else {
|
||||
// dynamic arg. turn it into a compound expression.
|
||||
eventName = arg!
|
||||
;(
|
||||
eventName.children ||
|
||||
(eventName.children = [{ ...eventName, children: undefined }])
|
||||
).unshift(`"on" + `)
|
||||
// already a compound epxression.
|
||||
eventName = arg
|
||||
eventName.children.unshift(`"on" + (`)
|
||||
eventName.children.push(`)`)
|
||||
}
|
||||
// TODO .once modifier handling since it is platform agnostic
|
||||
// other modifiers are handled in compiler-dom
|
||||
return {
|
||||
props: createObjectProperty(
|
||||
eventName,
|
||||
exp || createExpression(`() => {}`, false, loc),
|
||||
exp || createSimpleExpression(`() => {}`, false, loc),
|
||||
loc
|
||||
),
|
||||
needRuntime: false
|
||||
|
||||
Reference in New Issue
Block a user