wip: expression rewrite
This commit is contained in:
parent
70656690e2
commit
bb8524e199
@ -0,0 +1,19 @@
|
|||||||
|
import { SourceMapConsumer } from 'source-map'
|
||||||
|
import { compile } from '../../src'
|
||||||
|
|
||||||
|
test(`should work`, async () => {
|
||||||
|
const { code, map } = compile(
|
||||||
|
`<div v-if="hello">{{ ({ a }, b) => a + b + c }}</div>`,
|
||||||
|
{
|
||||||
|
useWith: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
console.log(code)
|
||||||
|
console.log(map)
|
||||||
|
const consumer = await new SourceMapConsumer(map!)
|
||||||
|
const pos = consumer.originalPositionFor({
|
||||||
|
line: 4,
|
||||||
|
column: 31
|
||||||
|
})
|
||||||
|
console.log(pos)
|
||||||
|
})
|
@ -10,7 +10,9 @@
|
|||||||
],
|
],
|
||||||
"types": "dist/compiler-core.d.ts",
|
"types": "dist/compiler-core.d.ts",
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
"formats": ["cjs"]
|
"formats": [
|
||||||
|
"cjs"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -26,6 +28,8 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-core#readme",
|
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-core#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"estree-walker": "^0.8.1",
|
||||||
|
"meriyah": "^1.7.2",
|
||||||
"source-map": "^0.7.3"
|
"source-map": "^0.7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ export const enum NodeTypes {
|
|||||||
ATTRIBUTE,
|
ATTRIBUTE,
|
||||||
DIRECTIVE,
|
DIRECTIVE,
|
||||||
// containers
|
// containers
|
||||||
|
COMPOUND_EXPRESSION,
|
||||||
IF,
|
IF,
|
||||||
IF_BRANCH,
|
IF_BRANCH,
|
||||||
FOR,
|
FOR,
|
||||||
@ -109,6 +110,7 @@ export interface ExpressionNode extends Node {
|
|||||||
type: NodeTypes.EXPRESSION
|
type: NodeTypes.EXPRESSION
|
||||||
content: string
|
content: string
|
||||||
isStatic: boolean
|
isStatic: boolean
|
||||||
|
children?: (ExpressionNode | string)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IfNode extends Node {
|
export interface IfNode extends Node {
|
||||||
|
@ -119,7 +119,7 @@ export function generate(
|
|||||||
options: CodegenOptions = {}
|
options: CodegenOptions = {}
|
||||||
): CodegenResult {
|
): CodegenResult {
|
||||||
const context = createCodegenContext(ast, options)
|
const context = createCodegenContext(ast, options)
|
||||||
const { mode, push, useWith, indent, deindent } = context
|
const { mode, push, useWith, indent, deindent, newline } = context
|
||||||
const imports = ast.imports.join(', ')
|
const imports = ast.imports.join(', ')
|
||||||
if (mode === 'function') {
|
if (mode === 'function') {
|
||||||
// generate const declarations for helpers
|
// generate const declarations for helpers
|
||||||
@ -135,13 +135,19 @@ export function generate(
|
|||||||
push(`export default `)
|
push(`export default `)
|
||||||
}
|
}
|
||||||
push(`function render() {`)
|
push(`function render() {`)
|
||||||
// generate asset resolution statements
|
|
||||||
ast.statements.forEach(s => push(s + `\n`))
|
|
||||||
if (useWith) {
|
|
||||||
indent()
|
|
||||||
push(`with (this) {`)
|
|
||||||
}
|
|
||||||
indent()
|
indent()
|
||||||
|
// generate asset resolution statements
|
||||||
|
if (ast.statements.length) {
|
||||||
|
ast.statements.forEach(s => {
|
||||||
|
push(s)
|
||||||
|
newline()
|
||||||
|
})
|
||||||
|
newline()
|
||||||
|
}
|
||||||
|
if (useWith) {
|
||||||
|
push(`with (this) {`)
|
||||||
|
indent()
|
||||||
|
}
|
||||||
push(`return `)
|
push(`return `)
|
||||||
genChildren(ast.children, context)
|
genChildren(ast.children, context)
|
||||||
if (useWith) {
|
if (useWith) {
|
||||||
@ -236,6 +242,10 @@ function genNode(node: CodegenNode, context: CodegenContext) {
|
|||||||
break
|
break
|
||||||
case NodeTypes.JS_ARRAY_EXPRESSION:
|
case NodeTypes.JS_ARRAY_EXPRESSION:
|
||||||
genArrayExpression(node, context)
|
genArrayExpression(node, context)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
__DEV__ &&
|
||||||
|
assert(false, `unhandled codegen node type: ${(node as any).type}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,9 +264,9 @@ function genText(node: TextNode | ExpressionNode, context: CodegenContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function genExpression(node: ExpressionNode, context: CodegenContext) {
|
function genExpression(node: ExpressionNode, context: CodegenContext) {
|
||||||
// if (node.codegenNode) {
|
if (node.children) {
|
||||||
// TODO handle transformed expression
|
return genCompoundExpression(node, context)
|
||||||
// }
|
}
|
||||||
const text = node.isStatic ? JSON.stringify(node.content) : node.content
|
const text = node.isStatic ? JSON.stringify(node.content) : node.content
|
||||||
context.push(text, node)
|
context.push(text, node)
|
||||||
}
|
}
|
||||||
@ -265,9 +275,9 @@ function genExpressionAsPropertyKey(
|
|||||||
node: ExpressionNode,
|
node: ExpressionNode,
|
||||||
context: CodegenContext
|
context: CodegenContext
|
||||||
) {
|
) {
|
||||||
// if (node.codegenNode) {
|
if (node.children) {
|
||||||
// TODO handle transformed expression
|
return genCompoundExpression(node, context)
|
||||||
// }
|
}
|
||||||
if (node.isStatic) {
|
if (node.isStatic) {
|
||||||
// only quote keys if necessary
|
// only quote keys if necessary
|
||||||
const text = /^\d|[^\w]/.test(node.content)
|
const text = /^\d|[^\w]/.test(node.content)
|
||||||
@ -279,6 +289,17 @@ function genExpressionAsPropertyKey(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function genComment(node: CommentNode, context: CodegenContext) {
|
function genComment(node: CommentNode, context: CodegenContext) {
|
||||||
context.push(`<!--${node.content}-->`, node)
|
context.push(`<!--${node.content}-->`, node)
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import { transformFor } from './transforms/vFor'
|
|||||||
import { prepareElementForCodegen } from './transforms/element'
|
import { prepareElementForCodegen } from './transforms/element'
|
||||||
import { transformOn } from './transforms/vOn'
|
import { transformOn } from './transforms/vOn'
|
||||||
import { transformBind } from './transforms/vBind'
|
import { transformBind } from './transforms/vBind'
|
||||||
|
import { rewriteExpression } from './transforms/expression'
|
||||||
|
|
||||||
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ export function compile(
|
|||||||
nodeTransforms: [
|
nodeTransforms: [
|
||||||
transformIf,
|
transformIf,
|
||||||
transformFor,
|
transformFor,
|
||||||
|
...(!__BROWSER__ && options.useWith === false ? [rewriteExpression] : []),
|
||||||
prepareElementForCodegen,
|
prepareElementForCodegen,
|
||||||
...(options.nodeTransforms || []) // user transforms
|
...(options.nodeTransforms || []) // user transforms
|
||||||
],
|
],
|
||||||
@ -31,7 +33,6 @@ export function compile(
|
|||||||
...(options.directiveTransforms || {}) // user transforms
|
...(options.directiveTransforms || {}) // user transforms
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return generate(ast, options)
|
return generate(ast, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ function createTransformContext(
|
|||||||
removeNode(node) {
|
removeNode(node) {
|
||||||
const list = context.parent.children
|
const list = context.parent.children
|
||||||
const removalIndex = node
|
const removalIndex = node
|
||||||
? list.indexOf(node)
|
? list.indexOf(node as any)
|
||||||
: context.currentNode
|
: context.currentNode
|
||||||
? context.childIndex
|
? context.childIndex
|
||||||
: -1
|
: -1
|
||||||
@ -124,12 +124,15 @@ export function traverseChildren(
|
|||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
for (; i < parent.children.length; i++) {
|
for (; i < parent.children.length; i++) {
|
||||||
|
const child = parent.children[i]
|
||||||
|
if (isString(child)) continue
|
||||||
|
context.currentNode = child
|
||||||
context.parent = parent
|
context.parent = parent
|
||||||
context.ancestors = ancestors
|
context.ancestors = ancestors
|
||||||
context.childIndex = i
|
context.childIndex = i
|
||||||
context.onNodeRemoved = nodeRemoved
|
context.onNodeRemoved = nodeRemoved
|
||||||
context.identifiers = identifiers
|
context.identifiers = identifiers
|
||||||
traverseNode((context.currentNode = parent.children[i]), context)
|
traverseNode(child, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,9 +192,7 @@ function createDirectiveArgs(
|
|||||||
// inject statement for resolving directive
|
// inject statement for resolving directive
|
||||||
const dirIdentifier = `_directive_${toValidId(dir.name)}`
|
const dirIdentifier = `_directive_${toValidId(dir.name)}`
|
||||||
context.statements.push(
|
context.statements.push(
|
||||||
`const ${dirIdentifier} = _${RESOLVE_DIRECTIVE}(${JSON.stringify(
|
`const ${dirIdentifier} = ${RESOLVE_DIRECTIVE}(${JSON.stringify(dir.name)})`
|
||||||
dir.name
|
|
||||||
)})`
|
|
||||||
)
|
)
|
||||||
const dirArgs: ArrayExpression['elements'] = [dirIdentifier]
|
const dirArgs: ArrayExpression['elements'] = [dirIdentifier]
|
||||||
const { loc } = dir
|
const { loc } = dir
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// - Parse expressions in templates into more detailed JavaScript ASTs so that
|
// - Parse expressions in templates into compound expressions so that each
|
||||||
// source-maps are more accurate
|
// identifier gets more accurate source-map locations.
|
||||||
//
|
//
|
||||||
// - Prefix identifiers with `_ctx.` so that they are accessed from the render
|
// - Prefix identifiers with `_ctx.` so that they are accessed from the render
|
||||||
// context
|
// context
|
||||||
@ -7,3 +7,146 @@
|
|||||||
// - This transform is only applied in non-browser builds because it relies on
|
// - 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
|
// an additional JavaScript parser. In the browser, there is no source-map
|
||||||
// support and the code is wrapped in `with (this) { ... }`.
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,7 +24,7 @@ export const transformIf = createStructuralDirectiveTransform(
|
|||||||
// locate the adjacent v-if
|
// locate the adjacent v-if
|
||||||
const siblings = context.parent.children
|
const siblings = context.parent.children
|
||||||
const comments = []
|
const comments = []
|
||||||
let i = siblings.indexOf(node)
|
let i = siblings.indexOf(node as any)
|
||||||
while (i-- >= -1) {
|
while (i-- >= -1) {
|
||||||
const sibling = siblings[i]
|
const sibling = siblings[i]
|
||||||
if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
|
if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -2627,6 +2627,11 @@ estree-walker@^0.6.1:
|
|||||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
|
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
|
||||||
integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==
|
integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==
|
||||||
|
|
||||||
|
estree-walker@^0.8.1:
|
||||||
|
version "0.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.8.1.tgz#6230ce2ec9a5cb03888afcaf295f97d90aa52b79"
|
||||||
|
integrity sha512-H6cJORkqvrNziu0KX2hqOMAlA2CiuAxHeGJXSIoKA/KLv229Dw806J3II6mKTm5xiDX1At1EXCfsOQPB+tMB+g==
|
||||||
|
|
||||||
esutils@^2.0.2:
|
esutils@^2.0.2:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
|
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
|
||||||
@ -4785,6 +4790,11 @@ merge2@^1.2.3:
|
|||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.4.tgz#c9269589e6885a60cf80605d9522d4b67ca646e3"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.4.tgz#c9269589e6885a60cf80605d9522d4b67ca646e3"
|
||||||
integrity sha512-FYE8xI+6pjFOhokZu0We3S5NKCirLbCzSh2Usf3qEyr4X8U+0jNg9P8RZ4qz+V2UoECLVwSyzU3LxXBaLGtD3A==
|
integrity sha512-FYE8xI+6pjFOhokZu0We3S5NKCirLbCzSh2Usf3qEyr4X8U+0jNg9P8RZ4qz+V2UoECLVwSyzU3LxXBaLGtD3A==
|
||||||
|
|
||||||
|
meriyah@^1.7.2:
|
||||||
|
version "1.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/meriyah/-/meriyah-1.7.2.tgz#c47d07d8f1284577658827cd134b180e80ae4bef"
|
||||||
|
integrity sha512-bBXN6hJ9RHA0mEae5O2Ocr6giK0S87nsz/W7tnBRm4kpW04LEEpXSOfwaID9GZgPRVcn3rAHzHHDDD68DLQgWw==
|
||||||
|
|
||||||
micromatch@^3.1.10, micromatch@^3.1.4:
|
micromatch@^3.1.10, micromatch@^3.1.4:
|
||||||
version "3.1.10"
|
version "3.1.10"
|
||||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
|
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
|
||||||
|
Loading…
Reference in New Issue
Block a user