refactor(compiler): separate Interpolation, SimpleExpression & CompoundExpression types

This commit is contained in:
Evan You
2019-09-27 11:42:02 -04:00
parent d491a022a7
commit d900c13efb
25 changed files with 947 additions and 650 deletions

View File

@@ -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
)
),

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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({

View File

@@ -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