提交 e3ac1ea3 编写于 作者: fxy060608's avatar fxy060608

wip(mp): scope

上级 cf29e835
......@@ -4,11 +4,11 @@ describe('compiler: codegen', () => {
test('module mode preamble', () => {
assert(
`<view v-for="item in items" @click="onClick"></view>`,
`<view wx:for="{{a}}" wx:for-item="item" bindtap="{{a}}"></view>`,
`<view wx:for="{{a}}" wx:for-item="item" bindtap="{{b}}"></view>`,
`import { vOn as _vOn, vFor as _vFor } from "vue"
export function render(_ctx, _cache) {
return { a: _vFor(_ctx.items, item => { return { a: _vOn(_ctx.onClick) }; }) }
return { a: _vFor(_ctx.items, item => { return {}; }), b: _vOn(_ctx.onClick) }
}`,
{ inline: false, mode: 'module', prefixIdentifiers: false }
)
......@@ -17,11 +17,11 @@ export function render(_ctx, _cache) {
test('module mode preamble w/ optimizeImports: true', () => {
assert(
`<view v-for="item in items" @click="onClick"></view>`,
`<view wx:for="{{a}}" wx:for-item="item" bindtap="{{a}}"></view>`,
`<view wx:for="{{a}}" wx:for-item="item" bindtap="{{b}}"></view>`,
`import { vOn as _vOn, vFor as _vFor } from "vue"
export function render(_ctx, _cache) {
return { a: _vFor(_ctx.items, item => { return { a: _vOn(_ctx.onClick) }; }) }
return { a: _vFor(_ctx.items, item => { return {}; }), b: _vOn(_ctx.onClick) }
}`,
{ inline: false, mode: 'module' }
)
......@@ -30,14 +30,14 @@ export function render(_ctx, _cache) {
test('function mode preamble', () => {
assert(
`<view v-for="item in items" @click="onClick"></view>`,
`<view wx:for="{{a}}" wx:for-item="item" bindtap="{{a}}"></view>`,
`<view wx:for="{{a}}" wx:for-item="item" bindtap="{{b}}"></view>`,
`const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { vOn: _vOn, vFor: _vFor } = _Vue
return { a: _vFor(items, item => { return { a: _vOn(onClick) }; }) }
return { a: _vFor(items, item => { return {}; }), b: _vOn(onClick) }
}
}`,
{ inline: false, mode: 'function', prefixIdentifiers: false }
......@@ -46,11 +46,11 @@ return function render(_ctx, _cache) {
test('function mode preamble w/ prefixIdentifiers: true', () => {
assert(
`<view v-for="item in items" @click="onClick"></view>`,
`<view wx:for="{{a}}" wx:for-item="item" bindtap="{{a}}"></view>`,
`<view wx:for="{{a}}" wx:for-item="item" bindtap="{{b}}"></view>`,
`const { vOn: _vOn, vFor: _vFor } = Vue
return function render(_ctx, _cache) {
return { a: _vFor(_ctx.items, item => { return { a: _vOn(_ctx.onClick) }; }) }
return { a: _vFor(_ctx.items, item => { return {}; }), b: _vOn(_ctx.onClick) }
}`,
{ inline: false, mode: 'function' }
)
......
import { assert } from './testUtils'
describe('compiler: scope', () => {
test('v-for', () => {
assert(
`<view v-for="item in items" :key="item.id" :class="{red: item.isRed}" @longpress="longpress" @click="onClick(item)">{{item.title}}</view>`,
`<view wx:for="{{a}}" wx:for-item="item" wx:key="b" class="{{[item.c && 'red']}}" bindlongpress="{{b}}" bindtap="{{item.d}}">{{item.a}}</view>`,
`(_ctx, _cache) => {
return { a: _vFor(_ctx.items, item => { return { a: _toDisplayString(item.title), b: item.id, c: item.isRed, d: _vOn($event => _ctx.onClick(item)) }; }), b: _vOn(_ctx.longpress) }
}`
)
})
test('v-for + v-for', () => {
assert(
`<view v-for="item in items" :key="item.id">{{item.title}}{{handle(foo)}}<view v-for="item1 in item.list" :key="item1.id" @click="onClick(item)" @longpress="longpress(item1)">{{item.id}}{{item1.title}}</view></view>`,
`<view wx:for="{{a}}" wx:for-item="item" wx:key="e">{{item.a}}{{b}}<view wx:for="{{item.b}}" wx:for-item="item1" wx:key="b" bindtap="{{item.d}}" bindlongpress="{{item1.c}}">{{item.c}}{{item1.a}}</view></view>`,
`(_ctx, _cache) => {
return { a: _vFor(_ctx.items, item => { return { a: _toDisplayString(item.title), b: _vFor(item.list, item1 => { return { a: _toDisplayString(item1.title), b: item1.id, c: _vOn($event => _ctx.longpress(item1)) }; }), c: _toDisplayString(item.id), d: _vOn($event => _ctx.onClick(item)), e: item.id }; }), b: _toDisplayString(_ctx.handle(_ctx.foo)) }
}`
)
assert(
`<view v-for="item in items"><view v-for="item1 in item1" :data-id="item.id" :data-title="item1.title"/></view>`,
`<view wx:for="{{a}}" wx:for-item="item"><view wx:for="{{b}}" wx:for-item="item1" data-id="{{item.a}}" data-title="{{item1.a}}"/></view>`,
`(_ctx, _cache) => {
return { a: _vFor(_ctx.items, item => { return { a: item.id }; }), b: _vFor(_ctx.item1, item1 => { return { a: item1.title }; }) }
}`
)
})
test('v-for + v-if', () => {
assert(
`<view v-for="item in items"><view v-if="true" :data-id="id"></view></view>`,
`<view wx:for="{{a}}" wx:for-item="item"><view wx:if="{{true}}" data-id="{{item.a}}"></view></view>`,
`(_ctx, _cache) => {
return { a: _vFor(_ctx.items, item => { return { ...(true ? { a: _ctx.id } : {}) }; }) }
}`
)
})
test('v-if', () => {
assert(
`<view v-if="ok">{{ok}}</view><view v-else-if="ok1">{{ok1}}</view><view v-else-if="ok2">{{ok2}}</view><view v-else>{{ok3}}</view>`,
`<view wx:if="{{a}}">{{b}}</view><view wx:elif="{{c}}">{{d}}</view><view wx:elif="{{e}}">{{f}}</view><view wx:else>{{g}}</view>`,
`(_ctx, _cache) => {
return { a: _ctx.ok, ...(_ctx.ok ? { b: _toDisplayString(_ctx.ok) } : _ctx.ok1 ? { d: _toDisplayString(_ctx.ok1) } : _ctx.ok2 ? { f: _toDisplayString(_ctx.ok2) } : { g: _toDisplayString(_ctx.ok3) }), c: _ctx.ok1, e: _ctx.ok2 }
}`
)
})
test('v-if + v-for', () => {
assert(
`<view v-if="ok"><view v-for="item in items" :key="item.id" :data-title="item.title" :data-foo="foo" @click="onClick"/></view><view v-else-if="ok1"><view v-for="item in items" :key="item.id" :data-title="item.title" :data-foo="foo" @click="onClick"/></view><view v-else><view v-for="item in items" :key="item.id" :data-title="item.title" :data-foo="foo" @click="onClick"/></view>`,
`<view wx:if="{{a}}"><view wx:for="{{b}}" wx:for-item="item" wx:key="a" data-title="{{item.b}}" data-foo="{{c}}" bindtap="{{d}}"/></view><view wx:elif="{{e}}"><view wx:for="{{f}}" wx:for-item="item" wx:key="a" data-title="{{item.b}}" data-foo="{{g}}" bindtap="{{h}}"/></view><view wx:else><view wx:for="{{i}}" wx:for-item="item" wx:key="a" data-title="{{item.b}}" data-foo="{{j}}" bindtap="{{k}}"/></view>`,
`(_ctx, _cache) => {
return { a: _ctx.ok, ...(_ctx.ok ? { b: _vFor(_ctx.items, item => { return { a: item.id, b: item.title }; }), c: _ctx.foo, d: _vOn(_ctx.onClick) } : _ctx.ok1 ? { f: _vFor(_ctx.items, item => { return { a: item.id, b: item.title }; }), g: _ctx.foo, h: _vOn(_ctx.onClick) } : { i: _vFor(_ctx.items, item => { return { a: item.id, b: item.title }; }), j: _ctx.foo, k: _vOn(_ctx.onClick) }), e: _ctx.ok1 }
}`
)
})
})
......@@ -7,7 +7,7 @@ function assert(
template: string,
templateCode: string,
renderCode: string,
options: CompilerOptions
options: CompilerOptions = {}
) {
const res = compile(template, {
filename: 'foo.vue',
......@@ -31,14 +31,13 @@ function assert(
}
describe('compiler', () => {
test('should wrap as function if expression is inline statement', () => {
test('scope', () => {
assert(
`<view :class="{ ...{red:red} }"/>`,
`<view class="{{[ a ]}}"/>`,
`<view v-for="item in items"><view v-for="item1 in item1" :data-id="item.id" :data-title="item1.title"/></view>`,
`<view wx:for="{{a}}" wx:for-item="item"><view wx:for="{{b}}" wx:for-item="item1" data-id="{{item.a}}" data-title="{{item1.a}}"/></view>`,
`(_ctx, _cache) => {
return { a: _normalizeClass({ red: _ctx.red }) }
}`,
{}
return { a: _vFor(_ctx.items, item => { return { a: item.id }; }), b: _vFor(_ctx.item1, item1 => { return { a: item1.title }; }) }
}`
)
})
})
......@@ -199,9 +199,9 @@ describe(`compiler: v-for`, () => {
test(`v-if + v-for`, () => {
assert(
`<view v-if="ok" v-for="i in list"/>`,
`<view wx:if="{{b}}" wx:for="{{a}}" wx:for-item="i"/>`,
`<view wx:if="{{a}}" wx:for="{{b}}" wx:for-item="i"/>`,
`(_ctx, _cache) => {
return { b: _ctx.ok, ...(_ctx.ok ? { a: _vFor(_ctx.list, i => { return {}; }) } : {}) }
return { a: _ctx.ok, ...(_ctx.ok ? { b: _vFor(_ctx.list, i => { return {}; }) } : {}) }
}`
)
})
......@@ -209,9 +209,9 @@ describe(`compiler: v-for`, () => {
test(`v-if + v-for on <template>`, () => {
assert(
`<template v-if="ok" v-for="i in list"/>`,
`<block wx:if="{{b}}" wx:for="{{a}}" wx:for-item="i"/>`,
`<block wx:if="{{a}}" wx:for="{{b}}" wx:for-item="i"/>`,
`(_ctx, _cache) => {
return { b: _ctx.ok, ...(_ctx.ok ? { a: _vFor(_ctx.list, i => { return {}; }) } : {}) }
return { a: _ctx.ok, ...(_ctx.ok ? { b: _vFor(_ctx.list, i => { return {}; }) } : {}) }
}`
)
})
......
......@@ -161,9 +161,9 @@ describe(`compiler: v-if`, () => {
<view/>
</template>
`,
`<block wx:if="{{b}}"><view wx:if="{{a}}"></view><view wx:else/><view/></block>`,
`<block wx:if="{{a}}"><view wx:if="{{b}}"></view><view wx:else/><view/></block>`,
`(_ctx, _cache) => {
return { b: _ctx.ok, ...(_ctx.ok ? { a: _ctx.ok2, ...(_ctx.ok2 ? {} : {}) } : {}) }
return { a: _ctx.ok, ...(_ctx.ok ? { b: _ctx.ok2, ...(_ctx.ok2 ? {} : {}) } : {}) }
}`
)
})
......
......@@ -34,15 +34,16 @@ export interface CodegenRootScope {
id: IdentifierGenerator
identifiers: string[]
properties: (ObjectProperty | SpreadElement)[]
parent: CodegenScope | null
}
export interface CodegenVIfScopeInit {
name: 'if' | 'else-if' | 'else' | string
condition?: Expression
}
export type CodegenVForScopeInit = VForOptions & { locals: string[] }
export interface CodegenVIfScope
extends CodegenRootScope,
CodegenVIfScopeInit {}
export interface CodegenVIfScope extends CodegenRootScope, CodegenVIfScopeInit {
parentScope: CodegenRootScope | CodegenVForScope
}
export interface CodegenVForScope
extends CodegenRootScope,
CodegenVForScopeInit {}
......
......@@ -211,10 +211,21 @@ export function createTransformContext(
onWarn = defaultOnWarn,
}: TransformOptions
): TransformContext {
const scope: CodegenRootScope = {
const rootScope: CodegenRootScope = {
id: new IdentifierGenerator(),
identifiers: [],
properties: [],
parent: null,
}
function findVIfParentScope(): CodegenVForScope | CodegenRootScope {
for (let i = scopes.length - 1; i >= 0; i--) {
const scope = scopes[i]
if (isVForScope(scope) || isRootScope(scope)) {
return scope
}
}
return rootScope
}
function createScope(
......@@ -225,6 +236,7 @@ export function createTransformContext(
{
id,
properties: [],
parent: scopes[scopes.length - 1],
get identifiers() {
return Object.keys(identifiers)
},
......@@ -232,8 +244,9 @@ export function createTransformContext(
initScope
)
}
const identifiers = Object.create(null)
const scopes: CodegenScope[] = [scope]
const scopes: CodegenScope[] = [rootScope]
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
const context: TransformContext = {
// options
......@@ -258,7 +271,7 @@ export function createTransformContext(
components: new Set(),
cached: 0,
identifiers,
scope,
scope: rootScope,
scopes: {
vFor: 0,
},
......@@ -274,8 +287,8 @@ export function createTransformContext(
addVIfScope(initScope) {
const vIfScope = createScope(
scopes[scopes.length - 1].id,
initScope
) as CodegenVIfScope
extend(initScope, { parentScope: findVIfParentScope() })
) as unknown as CodegenVIfScope
scopes.push(vIfScope)
return vIfScope
},
......
......@@ -12,19 +12,13 @@ import {
createSimpleExpression,
ExpressionNode,
NodeTypes,
SimpleExpressionNode,
SourceLocation,
} from '@vue/compiler-core'
import { walk, BaseNode } from 'estree-walker'
import { isUndefined, parseExpr } from '../ast'
import { genBabelExpr, genExpr } from '../codegen'
import { CodegenScope, CodegenVForScope } from '../options'
import {
isRootScope,
isVForScope,
isVIfScope,
TransformContext,
} from '../transform'
import { isVForScope, isVIfScope, TransformContext } from '../transform'
export function rewriteSpreadElement(
name: symbol,
......@@ -80,50 +74,34 @@ export function rewriteExpression(
return createSimpleExpression('undefined', false, node.loc)
}
scope = findScope(babelNode, scope)!
scope = findReferencedScope(babelNode, scope)
const id = scope.id.next()
scope.properties.push(objectProperty(identifier(id), babelNode!))
if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
const firstChild = node.children[0]
if (isSimpleExpression(firstChild)) {
const content = firstChild.content.trim()
if (scope.identifiers.includes(content)) {
return createSimpleExpression(content + '.' + id)
}
// 在v-for中包含的v-if块,所有变量需要补充当前v-for value前缀
if (isVIfScope(scope)) {
if (isVForScope(scope.parentScope)) {
return createSimpleExpression(scope.parentScope.valueAlias + '.' + id)
}
return createSimpleExpression(id)
} else if (isVForScope(scope)) {
return createSimpleExpression(scope.valueAlias + '.' + id)
}
return createSimpleExpression(id)
}
// function findReferencedScope(
// node: Expression,
// scope: CodegenScope
// ): CodegenRootScope | CodegenVForScope {
// if (isRootScope(scope)) {
// return scope
// }
// }
function findScope(node: Expression, scope: CodegenScope) {
if (isRootScope(scope) || isVIfScope(scope)) {
return scope
}
return findVForScope(node, scope) || scope
}
function findVForScope(
function findReferencedScope(
node: Expression,
scope: CodegenScope
): CodegenVForScope | undefined {
if (isVForScope(scope)) {
): CodegenScope {
if (isVIfScope(scope)) {
return scope
} else if (isVForScope(scope)) {
if (isReferencedScope(node, scope)) {
return scope
}
return findReferencedScope(node, scope.parent!)
}
// if (scope.parent) {
// return findVForScope(node, scope.parent)
// }
return scope
}
function isReferencedScope(node: Expression, scope: CodegenVForScope) {
......@@ -149,7 +127,3 @@ function isReferencedScope(node: Expression, scope: CodegenVForScope) {
})
return referenced
}
function isSimpleExpression(val: any): val is SimpleExpressionNode {
return val.type && val.type === NodeTypes.SIMPLE_EXPRESSION
}
import { isString } from '@vue/shared'
import { extend, isString } from '@vue/shared'
import {
createCompilerError,
createSimpleExpression,
......@@ -14,22 +14,19 @@ import {
isTemplateNode,
findProp,
} from '@vue/compiler-core'
import {
createObjectProperty,
createVForCallExpression,
parseExpr,
parseParam,
} from '../ast'
import { createVForCallExpression, parseExpr, parseParam } from '../ast'
import { NodeTransform, TransformContext } from '../transform'
import { processExpression } from './transformExpression'
import { genExpr } from '../codegen'
import {
cloneNode,
Expression,
Identifier,
isIdentifier,
Pattern,
RestElement,
} from '@babel/types'
import { rewriteExpression } from './utils'
export type VForOptions = Omit<ForParseResult, 'tagType'> & {
sourceExpr?: Expression
......@@ -119,11 +116,20 @@ export const transformFor = createStructuralDirectiveTransform(
...vForData,
locals: findVForLocals(parseResult),
})
// 先占位vFor,后续更新 cloneSourceExpr 为 CallExpression
const cloneSourceExpr = cloneNode(sourceExpr!, false)
const vFor = {
...vForData,
sourceAlias: rewriteExpression(
source,
context,
cloneSourceExpr,
parentScope
).content,
}
;(node as ForElementNode).vFor = vFor
scopes.vFor++
return () => {
if (isTemplateNode(node)) {
node.children.some((c) => {
......@@ -146,16 +152,22 @@ export const transformFor = createStructuralDirectiveTransform(
key && removeIdentifiers(key)
index && removeIdentifiers(index)
}
const id = parentScope.id.next()
vFor.sourceAlias = id
parentScope.properties.push(
createObjectProperty(id, createVForCallExpression(vForScope, context))
extend(
clearExpr(cloneSourceExpr),
createVForCallExpression(vForScope, context)
)
popScope()
}
}
) as unknown as NodeTransform
function clearExpr(expr: Expression) {
Object.keys(expr).forEach((key) => {
delete (expr as any)[key]
})
return expr
}
function parseAlias(
babelExpr: Identifier | Pattern | RestElement,
exprCode: string,
......
......@@ -54,19 +54,19 @@ export const transformIf = createStructuralDirectiveTransform(
name: dir.name,
condition,
})
return () => {
if (condition) {
if (!isLiteral(condition)) {
ifOptions.condition = rewriteExpression(
dir.exp!,
context,
condition,
parentScope
).content
} else {
ifOptions.condition = (dir.exp as SimpleExpressionNode).content
}
if (condition) {
if (!isLiteral(condition)) {
ifOptions.condition = rewriteExpression(
dir.exp!,
context,
condition,
parentScope
).content
} else {
ifOptions.condition = (dir.exp as SimpleExpressionNode).content
}
}
return () => {
if (isRoot) {
parentScope.properties.push(createVIfSpreadElement(vIfScope))
} else {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册