diff --git a/packages/compiler-core/__tests__/transforms/expression.spec.ts b/packages/compiler-core/__tests__/transforms/expression.spec.ts
new file mode 100644
index 00000000..2153fd08
--- /dev/null
+++ b/packages/compiler-core/__tests__/transforms/expression.spec.ts
@@ -0,0 +1,19 @@
+import { SourceMapConsumer } from 'source-map'
+import { compile } from '../../src'
+
+test(`should work`, async () => {
+ const { code, map } = compile(
+ `
{{ ({ a }, b) => a + b + c }}
`,
+ {
+ 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)
+})
diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json
index db71ca54..bfefbbae 100644
--- a/packages/compiler-core/package.json
+++ b/packages/compiler-core/package.json
@@ -10,7 +10,9 @@
],
"types": "dist/compiler-core.d.ts",
"buildOptions": {
- "formats": ["cjs"]
+ "formats": [
+ "cjs"
+ ]
},
"repository": {
"type": "git",
@@ -26,6 +28,8 @@
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-core#readme",
"dependencies": {
+ "estree-walker": "^0.8.1",
+ "meriyah": "^1.7.2",
"source-map": "^0.7.3"
}
}
diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts
index 897eaafa..1c5e87a5 100644
--- a/packages/compiler-core/src/ast.ts
+++ b/packages/compiler-core/src/ast.ts
@@ -16,6 +16,7 @@ export const enum NodeTypes {
ATTRIBUTE,
DIRECTIVE,
// containers
+ COMPOUND_EXPRESSION,
IF,
IF_BRANCH,
FOR,
@@ -109,6 +110,7 @@ export interface ExpressionNode extends Node {
type: NodeTypes.EXPRESSION
content: string
isStatic: boolean
+ children?: (ExpressionNode | string)[]
}
export interface IfNode extends Node {
diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index 3b2d1f32..820b1d97 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -119,7 +119,7 @@ export function generate(
options: CodegenOptions = {}
): CodegenResult {
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(', ')
if (mode === 'function') {
// generate const declarations for helpers
@@ -135,13 +135,19 @@ export function generate(
push(`export default `)
}
push(`function render() {`)
- // generate asset resolution statements
- ast.statements.forEach(s => push(s + `\n`))
- if (useWith) {
- indent()
- push(`with (this) {`)
- }
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 `)
genChildren(ast.children, context)
if (useWith) {
@@ -236,6 +242,10 @@ function genNode(node: CodegenNode, context: CodegenContext) {
break
case NodeTypes.JS_ARRAY_EXPRESSION:
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) {
- // if (node.codegenNode) {
- // TODO handle transformed expression
- // }
+ if (node.children) {
+ return genCompoundExpression(node, context)
+ }
const text = node.isStatic ? JSON.stringify(node.content) : node.content
context.push(text, node)
}
@@ -265,9 +275,9 @@ function genExpressionAsPropertyKey(
node: ExpressionNode,
context: CodegenContext
) {
- // if (node.codegenNode) {
- // TODO handle transformed expression
- // }
+ if (node.children) {
+ return genCompoundExpression(node, context)
+ }
if (node.isStatic) {
// only quote keys if necessary
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) {
context.push(``, node)
}
diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts
index 2409c920..5af9df19 100644
--- a/packages/compiler-core/src/index.ts
+++ b/packages/compiler-core/src/index.ts
@@ -8,6 +8,7 @@ import { transformFor } from './transforms/vFor'
import { prepareElementForCodegen } from './transforms/element'
import { transformOn } from './transforms/vOn'
import { transformBind } from './transforms/vBind'
+import { rewriteExpression } from './transforms/expression'
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
@@ -22,6 +23,7 @@ export function compile(
nodeTransforms: [
transformIf,
transformFor,
+ ...(!__BROWSER__ && options.useWith === false ? [rewriteExpression] : []),
prepareElementForCodegen,
...(options.nodeTransforms || []) // user transforms
],
@@ -31,7 +33,6 @@ export function compile(
...(options.directiveTransforms || {}) // user transforms
}
})
-
return generate(ast, options)
}
diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts
index 3cf26ecd..d3b442b3 100644
--- a/packages/compiler-core/src/transform.ts
+++ b/packages/compiler-core/src/transform.ts
@@ -79,7 +79,7 @@ function createTransformContext(
removeNode(node) {
const list = context.parent.children
const removalIndex = node
- ? list.indexOf(node)
+ ? list.indexOf(node as any)
: context.currentNode
? context.childIndex
: -1
@@ -124,12 +124,15 @@ export function traverseChildren(
i--
}
for (; i < parent.children.length; i++) {
+ const child = parent.children[i]
+ if (isString(child)) continue
+ context.currentNode = child
context.parent = parent
context.ancestors = ancestors
context.childIndex = i
context.onNodeRemoved = nodeRemoved
context.identifiers = identifiers
- traverseNode((context.currentNode = parent.children[i]), context)
+ traverseNode(child, context)
}
}
diff --git a/packages/compiler-core/src/transforms/element.ts b/packages/compiler-core/src/transforms/element.ts
index 3844f4e0..115d9a02 100644
--- a/packages/compiler-core/src/transforms/element.ts
+++ b/packages/compiler-core/src/transforms/element.ts
@@ -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
diff --git a/packages/compiler-core/src/transforms/expression.ts b/packages/compiler-core/src/transforms/expression.ts
index ba13f11f..943cb5ac 100644
--- a/packages/compiler-core/src/transforms/expression.ts
+++ b/packages/compiler-core/src/transforms/expression.ts
@@ -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
+ }
+}
diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts
index 69b5db1e..bcb0203d 100644
--- a/packages/compiler-core/src/transforms/vIf.ts
+++ b/packages/compiler-core/src/transforms/vIf.ts
@@ -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) {
diff --git a/yarn.lock b/yarn.lock
index 0cfeed06..6e3e1b3b 100644
--- a/yarn.lock
+++ b/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"
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:
version "2.0.2"
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"
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:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"