wip: expression rewrite

This commit is contained in:
Evan You
2019-09-23 02:52:54 -04:00
parent 70656690e2
commit bb8524e199
10 changed files with 224 additions and 23 deletions

View File

@@ -192,9 +192,7 @@ function createDirectiveArgs(
// inject statement for resolving directive
const dirIdentifier = `_directive_${toValidId(dir.name)}`
context.statements.push(
`const ${dirIdentifier} = _${RESOLVE_DIRECTIVE}(${JSON.stringify(
dir.name
)})`
`const ${dirIdentifier} = ${RESOLVE_DIRECTIVE}(${JSON.stringify(dir.name)})`
)
const dirArgs: ArrayExpression['elements'] = [dirIdentifier]
const { loc } = dir

View File

@@ -1,5 +1,5 @@
// - Parse expressions in templates into more detailed JavaScript ASTs so that
// source-maps are more accurate
// - Parse expressions in templates into compound expressions so that each
// identifier gets more accurate source-map locations.
//
// - Prefix identifiers with `_ctx.` so that they are accessed from the render
// context
@@ -7,3 +7,146 @@
// - This transform is only applied in non-browser builds because it relies on
// an additional JavaScript parser. In the browser, there is no source-map
// support and the code is wrapped in `with (this) { ... }`.
import { parseScript } from 'meriyah'
import { walk } from 'estree-walker'
import { NodeTransform, TransformContext } from '../transform'
import { NodeTypes, createExpression, ExpressionNode } from '../ast'
import { Node, Function, Identifier } from 'estree'
import { advancePositionWithClone } from '../utils'
export const rewriteExpression: NodeTransform = (node, context) => {
if (node.type === NodeTypes.EXPRESSION && !node.isStatic) {
context.replaceNode(convertExpression(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)
}
if (prop.arg && !prop.arg.isStatic) {
prop.arg = convertExpression(prop.arg, context)
}
}
}
} else if (node.type === NodeTypes.IF) {
for (let i = 0; i < node.branches.length; i++) {}
} else if (node.type === NodeTypes.FOR) {
}
}
function convertExpression(
node: ExpressionNode,
context: TransformContext
): ExpressionNode {
const ast = parseScript(`(${node.content})`, { ranges: true }) as any
const ids: Node[] = []
const knownIds = Object.create(context.identifiers)
walk(ast, {
enter(node, parent) {
if (node.type === 'Identifier') {
if (ids.indexOf(node) === -1) {
ids.push(node)
if (!knownIds[node.name] && shouldPrependContext(node, parent)) {
node.name = `_ctx.${node.name}`
}
}
} else if (isFunction(node)) {
node.params.forEach(p =>
walk(p, {
enter(child) {
if (child.type === 'Identifier') {
knownIds[child.name] = true
;(
(node as any)._scopeIds ||
((node as any)._scopeIds = new Set())
).add(child.name)
}
}
})
)
}
},
leave(node: any) {
if (node._scopeIds) {
node._scopeIds.forEach((id: string) => {
delete knownIds[id]
})
}
}
})
const full = node.content
const children: ExpressionNode['children'] = []
ids.sort((a: any, b: any) => a.start - b.start)
ids.forEach((id: any, i) => {
const last = ids[i - 1] as any
const text = full.slice(last ? last.end - 1 : 0, id.start - 1)
if (text.length) {
children.push(text)
}
const source = full.slice(id.start, id.end)
children.push(
createExpression(id.name, false, {
source,
start: advancePositionWithClone(node.loc.start, source, id.start),
end: advancePositionWithClone(node.loc.start, source, id.end)
})
)
if (i === ids.length - 1 && id.end < full.length - 1) {
children.push(full.slice(id.end))
}
})
return {
type: NodeTypes.EXPRESSION,
content: '',
isStatic: false,
loc: node.loc,
children
}
}
const globals = new Set(
(
'Infinity,undefined,NaN,isFinite,isNaN,' +
'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
'require,' + // for webpack
'arguments,'
) // parsed as identifier but is a special keyword...
.split(',')
)
const isFunction = (node: Node): node is Function =>
/Function(Expression|Declaration)$/.test(node.type)
function shouldPrependContext(identifier: Identifier, parent: Node) {
if (
// not id of a FunctionDeclaration
!(parent.type === 'FunctionDeclaration' && parent.id === identifier) &&
// not a params of a function
!(isFunction(parent) && parent.params.indexOf(identifier) > -1) &&
// not a key of Property
!(
parent.type === 'Property' &&
parent.key === identifier &&
!parent.computed
) &&
// not a property of a MemberExpression
!(
parent.type === 'MemberExpression' &&
parent.property === identifier &&
!parent.computed
) &&
// not in an Array destructure pattern
!(parent.type === 'ArrayPattern') &&
// skip globals + commonly used shorthands
!globals.has(identifier.name)
) {
return true
}
}

View File

@@ -24,7 +24,7 @@ export const transformIf = createStructuralDirectiveTransform(
// locate the adjacent v-if
const siblings = context.parent.children
const comments = []
let i = siblings.indexOf(node)
let i = siblings.indexOf(node as any)
while (i-- >= -1) {
const sibling = siblings[i]
if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {