feat(compiler): expression prefixing + v-for scope analysis

This commit is contained in:
Evan You
2019-09-23 13:25:18 -04:00
parent b04be6a561
commit e57cb51066
8 changed files with 209 additions and 97 deletions

View File

@@ -15,30 +15,56 @@ import { NodeTypes, createExpression, ExpressionNode } from '../ast'
import { Node, Function, Identifier } from 'estree'
import { advancePositionWithClone } from '../utils'
export const rewriteExpression: NodeTransform = (node, context) => {
export const expressionTransform: NodeTransform = (node, context) => {
if (node.type === NodeTypes.EXPRESSION && !node.isStatic) {
context.replaceNode(convertExpression(node, context))
processExpression(node, 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) {
prop.exp = convertExpression(prop.exp, context)
processExpression(prop.exp, context)
}
if (prop.arg && !prop.arg.isStatic) {
prop.arg = convertExpression(prop.arg, context)
processExpression(prop.arg, context)
}
}
}
}
}
function convertExpression(
const simpleIdRE = /^[a-zA-Z$_][\w$]*$/
// cache node requires
let _parseScript: typeof parseScript
let _walk: typeof walk
export function processExpression(
node: ExpressionNode,
context: TransformContext
): ExpressionNode {
const ast = parseScript(`(${node.content})`, { ranges: true }) as any
) {
// 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
}
let ast
try {
ast = parseScript(`(${node.content})`, { ranges: true }) as any
} catch (e) {
context.onError(e)
return
}
const ids: Node[] = []
const knownIds = Object.create(context.identifiers)
@@ -98,10 +124,7 @@ function convertExpression(
}
})
return {
...node,
children
}
node.children = children
}
const globals = new Set(

View File

@@ -1,8 +1,17 @@
import { createStructuralDirectiveTransform } from '../transform'
import { NodeTypes, ExpressionNode, createExpression } from '../ast'
import {
createStructuralDirectiveTransform,
TransformContext
} from '../transform'
import {
NodeTypes,
ExpressionNode,
createExpression,
SourceLocation
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { getInnerRange } from '../utils'
import { RENDER_LIST } from '../runtimeConstants'
import { processExpression } from './expression'
const forAliasRE = /([\s\S]*?)(?:(?<=\))|\s+)(?:in|of)\s+([\s\S]*)/
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
@@ -13,23 +22,35 @@ export const transformFor = createStructuralDirectiveTransform(
(node, dir, context) => {
if (dir.exp) {
context.imports.add(RENDER_LIST)
const aliases = parseAliasExpressions(dir.exp.content)
const parseResult = parseForExpression(dir.exp, context)
if (parseResult) {
const { source, value, key, index } = parseResult
if (aliases) {
// TODO inject identifiers to context
// and remove on exit
context.replaceNode({
type: NodeTypes.FOR,
loc: node.loc,
source: maybeCreateExpression(
aliases.source,
dir.exp
) as ExpressionNode,
valueAlias: maybeCreateExpression(aliases.value, dir.exp),
keyAlias: maybeCreateExpression(aliases.key, dir.exp),
objectIndexAlias: maybeCreateExpression(aliases.index, dir.exp),
source,
valueAlias: value,
keyAlias: key,
objectIndexAlias: index,
children: [node]
})
// scope management
const { addIdentifier, removeIdentifier } = context
// inject identifiers to context
value && addIdentifier(value)
key && addIdentifier(key)
index && addIdentifier(index)
return () => {
// remove injected identifiers on exit
value && removeIdentifier(value)
key && removeIdentifier(key)
index && removeIdentifier(index)
}
} else {
context.onError(
createCompilerError(ErrorCodes.X_FOR_MALFORMED_EXPRESSION, dir.loc)
@@ -43,28 +64,31 @@ export const transformFor = createStructuralDirectiveTransform(
}
)
interface AliasExpression {
offset: number
content: string
interface ForParseResult {
source: ExpressionNode
value: ExpressionNode | undefined
key: ExpressionNode | undefined
index: ExpressionNode | undefined
}
interface AliasExpressions {
source: AliasExpression
value: AliasExpression | undefined
key: AliasExpression | undefined
index: AliasExpression | undefined
}
function parseAliasExpressions(source: string): AliasExpressions | null {
function parseForExpression(
input: ExpressionNode,
context: TransformContext
): ForParseResult | null {
const loc = input.loc
const source = input.content
const inMatch = source.match(forAliasRE)
if (!inMatch) return null
const [, LHS, RHS] = inMatch
const result: AliasExpressions = {
source: {
offset: source.indexOf(RHS, LHS.length),
content: RHS.trim()
},
const result: ForParseResult = {
source: createAliasExpression(
loc,
RHS.trim(),
source.indexOf(RHS, LHS.length),
context,
!context.useWith
),
value: undefined,
key: undefined,
index: undefined
@@ -80,49 +104,60 @@ function parseAliasExpressions(source: string): AliasExpressions | null {
valueContent = valueContent.replace(forIteratorRE, '').trim()
const keyContent = iteratorMatch[1].trim()
let keyOffset: number | undefined
if (keyContent) {
result.key = {
offset: source.indexOf(keyContent, trimmedOffset + valueContent.length),
content: keyContent
}
keyOffset = source.indexOf(
keyContent,
trimmedOffset + valueContent.length
)
result.key = createAliasExpression(loc, keyContent, keyOffset, context)
}
if (iteratorMatch[2]) {
const indexContent = iteratorMatch[2].trim()
if (indexContent) {
result.index = {
offset: source.indexOf(
result.index = createAliasExpression(
loc,
indexContent,
source.indexOf(
indexContent,
result.key
? result.key.offset + result.key.content.length
? keyOffset! + keyContent.length
: trimmedOffset + valueContent.length
),
content: indexContent
}
context
)
}
}
}
if (valueContent) {
result.value = {
offset: trimmedOffset,
content: valueContent
}
result.value = createAliasExpression(
loc,
valueContent,
trimmedOffset,
context
)
}
return result
}
function maybeCreateExpression(
alias: AliasExpression | undefined,
node: ExpressionNode
): ExpressionNode | undefined {
if (alias) {
return createExpression(
alias.content,
false,
getInnerRange(node.loc, alias.offset, alias.content.length)
)
function createAliasExpression(
range: SourceLocation,
content: string,
offset: number,
context: TransformContext,
process: boolean = false
): ExpressionNode {
const exp = createExpression(
content,
false,
getInnerRange(range, offset, content.length)
)
if (!__BROWSER__ && process) {
processExpression(exp, context)
}
return exp
}

View File

@@ -10,10 +10,14 @@ import {
IfBranchNode
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './expression'
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, context) => {
if (!__BROWSER__ && !context.useWith && dir.exp) {
processExpression(dir.exp, context)
}
if (dir.name === 'if') {
context.replaceNode({
type: NodeTypes.IF,