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

wip(mp): vif

上级 57d61c3d
......@@ -20,19 +20,14 @@ function assert(template: string, templateCode: string, renderCode: string) {
}
describe('compiler', () => {
test(`generate v-for with v-if`, () => {
test(`basic v-if`, () => {
assert(
`<view v-for="item in items"><view v-if="item.show">{{item.title}}</view></view>`,
`<view wx:for="{{a}}" wx:for-item="item"><view wx:if="{{item.b}}">{{item.a}}</view></view>`,
`<view v-if="ok"/>`,
`<view wx:if="{{a}}"/>`,
`(_ctx, _cache) => {
return {
a: vFor(_ctx.items, item => {
return {
a: item.title,
b: item.show,
...(item.show ? {} : {})
};
})
a: _ctx.ok,
...(_ctx.ok ? {} : {})
}
}`
)
......
import { compile } from '../src/index'
import { CompilerOptions } from '../src/options'
export function inspect(obj: any) {
console.log(require('util').inspect(obj, { colors: true, depth: null }))
}
export function assert(
template: string,
templateCode: string,
renderCode: string,
options: CompilerOptions = {}
) {
const res = compile(template, {
filename: 'foo.vue',
prefixIdentifiers: true,
inline: true,
emitFile({ source }) {
// console.log(source)
if (!options.onError) {
expect(source).toBe(templateCode)
}
return ''
},
...options,
})
// expect(res.template).toBe(templateCode)
// expect(res.code).toBe(renderCode)
// console.log(require('util').inspect(res.code, { colors: true, depth: null }))
// console.log(require('util').inspect(res, { colors: true, depth: null }))
if (!options.onError) {
expect(res.code).toBe(renderCode)
}
}
import { ErrorCodes, IfNode, NodeTypes } from '@vue/compiler-core'
import { compile } from '../src'
import { CompilerOptions } from '../src/options'
import { assert } from './testUtils'
function compileWithIfTransform(
template: string,
options: CompilerOptions = {},
returnIndex: number = 0,
childrenLen: number = 1
) {
const { ast } = compile(template, options)
if (!options.onError) {
expect(ast.children.length).toBe(childrenLen)
for (let i = 0; i < childrenLen; i++) {
expect(ast.children[i].type).toBe(NodeTypes.IF)
}
}
return {
root: ast,
node: ast.children[returnIndex] as IfNode,
}
}
describe(`compiler: v-if`, () => {
describe(`codegen`, () => {
test(`basic v-if`, () => {
assert(
`<view v-if="ok"/>`,
`<view wx:if="{{a}}"/>`,
`(_ctx, _cache) => {
return {
a: _ctx.ok,
...(_ctx.ok ? {} : {})
}
}`
)
})
test(`template v-if`, () => {
assert(
`<template v-if="ok"><view/>hello<view/></template>`,
`<block wx:if="{{a}}"><view/>hello<view/></block>`,
`(_ctx, _cache) => {
return {
a: _ctx.ok,
...(_ctx.ok ? {} : {})
}
}`
)
})
test(`component v-if`, () => {
assert(
`<Component v-if="ok"></Component>`,
`<Component wx:if="{{a}}"></Component>`,
`(_ctx, _cache) => {
return {
a: _ctx.ok,
...(_ctx.ok ? {} : {})
}
}`
)
})
test(`v-if + v-else`, () => {
assert(
`<view v-if="ok"/><view v-else/>`,
`<view wx:if="{{a}}"/><view wx:else/>`,
`(_ctx, _cache) => {
return {
a: _ctx.ok,
...(_ctx.ok ? {} : {})
}
}`
)
})
test(`v-if + v-else-if`, () => {
assert(
`<view v-if="ok"/><view v-else-if="orNot"/>`,
`<view wx:if="{{a}}"/><view wx:elif="{{b}}"/>`,
`(_ctx, _cache) => {
return {
a: _ctx.ok,
...(_ctx.ok ? {} : _ctx.orNot ? {} : {}),
b: _ctx.orNot
}
}`
)
})
test(`v-if + v-else-if + v-else`, () => {
assert(
`<view v-if="ok"/><view v-else-if="orNot"/><template v-else>fine</template>`,
`<view wx:if="{{a}}"/><view wx:elif="{{b}}"/><block wx:else>fine</block>`,
`(_ctx, _cache) => {
return {
a: _ctx.ok,
...(_ctx.ok ? {} : _ctx.orNot ? {} : {}),
b: _ctx.orNot
}
}`
)
})
test(`v-if + v-else-if + v-else-if + v-else`, () => {
assert(
`<view v-if="ok"/><view v-else-if="orNot"/><view v-else-if="3"/><template v-else>fine</template>`,
`<view wx:if="{{a}}"/><view wx:elif="{{b}}"/><view wx:elif="{{3}}"/><block wx:else>fine</block>`,
`(_ctx, _cache) => {
return {
a: _ctx.ok,
...(_ctx.ok ? {} : _ctx.orNot ? {} : 3 ? {} : {}),
b: _ctx.orNot
}
}`
)
})
test(`comment between branches`, () => {
assert(
`
<view v-if="ok"/>
<!--foo-->
<view v-else-if="orNot"/>
<!--bar-->
<template v-else>fine</template>
`,
`<view wx:if="{{a}}"/><view wx:elif="{{b}}"/><block wx:else>fine</block>`,
`(_ctx, _cache) => {
return {
a: _ctx.ok,
...(_ctx.ok ? {} : _ctx.orNot ? {} : {}),
b: _ctx.orNot
}
}`
)
})
})
describe('errors', () => {
test('error on v-else missing adjacent v-if', () => {
const onError = jest.fn()
const { node: node1 } = compileWithIfTransform(`<view v-else/>`, {
onError,
})
expect(onError.mock.calls[0]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node1.loc,
},
])
const { node: node2 } = compileWithIfTransform(
`<view/><view v-else/>`,
{ onError },
1
)
expect(onError.mock.calls[1]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node2.loc,
},
])
const { node: node3 } = compileWithIfTransform(
`<view/>foo<view v-else/>`,
{ onError },
2
)
expect(onError.mock.calls[2]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node3.loc,
},
])
})
test('error on v-else-if missing adjacent v-if or v-else-if', () => {
const onError = jest.fn()
const { node: node1 } = compileWithIfTransform(
`<view v-else-if="foo"/>`,
{
onError,
}
)
expect(onError.mock.calls[0]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node1.loc,
},
])
const { node: node2 } = compileWithIfTransform(
`<view/><view v-else-if="foo"/>`,
{ onError },
1
)
expect(onError.mock.calls[1]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node2.loc,
},
])
const { node: node3 } = compileWithIfTransform(
`<view/>foo<view v-else-if="foo"/>`,
{ onError },
2
)
expect(onError.mock.calls[2]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node3.loc,
},
])
const {
node: { branches },
} = compileWithIfTransform(
`<view v-if="notOk"/><view v-else/><view v-else-if="ok"/>`,
{ onError },
0
)
expect(onError.mock.calls[3]).toMatchObject([
{
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: branches[branches.length - 1].loc,
},
])
})
// test('error on user key', () => {
// const onError = jest.fn()
// // dynamic
// compileWithIfTransform(
// `<view v-if="ok" :key="a + 1" /><view v-else :key="a + 1" />`,
// { onError }
// )
// expect(onError.mock.calls[0]).toMatchObject([
// {
// code: ErrorCodes.X_V_IF_SAME_KEY,
// },
// ])
// // static
// compileWithIfTransform(
// `<view v-if="ok" key="1" /><view v-else key="1" />`,
// {
// onError,
// }
// )
// expect(onError.mock.calls[1]).toMatchObject([
// {
// code: ErrorCodes.X_V_IF_SAME_KEY,
// },
// ])
// })
})
})
......@@ -2,6 +2,8 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.genElementProps = exports.genElement = exports.genNode = exports.generate = void 0;
const codegen_1 = require("../codegen");
const vFor_1 = require("../transforms/vFor");
const vIf_1 = require("../transforms/vIf");
function generate({ children }, { emitFile, filename }) {
const context = {
code: '',
......@@ -17,6 +19,10 @@ function generate({ children }, { emitFile, filename }) {
exports.generate = generate;
function genNode(node, context) {
switch (node.type) {
case 9 /* IF */:
return node.branches.forEach((node) => {
genElement(node, context);
});
case 2 /* TEXT */:
return genText(node, context);
case 5 /* INTERPOLATION */:
......@@ -41,13 +47,13 @@ function genVElseIf(exp, { push }) {
function genVElse({ push }) {
push(` wx:else`);
}
function genVFor(node, props, { push }) {
push(` wx:for="{{${node.source}}}"`);
if (node.value) {
push(` wx:for-item="${node.value}"`);
function genVFor(opts, props, { push }) {
push(` wx:for="{{${opts.source}}}"`);
if (opts.value) {
push(` wx:for-item="${opts.value}"`);
}
if (node.key) {
push(` wx:for-index="${node.key}"`);
if (opts.key) {
push(` wx:for-index="${opts.key}"`);
}
const keyIndex = props.findIndex((prop) => prop.type === 7 /* DIRECTIVE */ &&
prop.name === 'bind' &&
......@@ -61,25 +67,28 @@ function genVFor(node, props, { push }) {
props.splice(keyIndex, 1);
}
}
const tagMap = {
template: 'block',
};
function genElement(node, context) {
const { tag, children, isSelfClosing, props } = node;
const { children, isSelfClosing, props } = node;
const tag = tagMap[node.tag] || node.tag;
const { push } = context;
push(`<${tag}`);
const ifNode = node.ifNode;
if (ifNode) {
if (ifNode.name === 'if') {
genVIf(ifNode.condition, context);
if ((0, vIf_1.isIfElementNode)(node)) {
const { name, condition } = node.vIf;
if (name === 'if') {
genVIf(condition, context);
}
else if (ifNode.name === 'else-if') {
genVElseIf(ifNode.condition, context);
else if (name === 'else-if') {
genVElseIf(condition, context);
}
else if (ifNode.name === 'else') {
else if (name === 'else') {
genVElse(context);
}
}
const forNode = node.forNode;
if (forNode) {
genVFor(forNode, props, context);
if ((0, vFor_1.isForElementNode)(node)) {
genVFor(node.vFor, props, context);
}
if (props.length) {
genElementProps(props, context);
......
......@@ -24,6 +24,7 @@ export interface TransformContext extends Required<Omit<TransformOptions, 'filen
removeHelper<T extends symbol>(name: T): void;
helperString(name: symbol): string;
replaceNode(node: TemplateChildNode): void;
removeNode(node?: TemplateChildNode): void;
onNodeRemoved(): void;
addIdentifiers(exp: ExpressionNode | string): void;
removeIdentifiers(exp: ExpressionNode | string): void;
......
......@@ -176,6 +176,34 @@ function createTransformContext(root, { isTS = false, inline = false, bindingMet
replaceNode(node) {
context.parent.children[context.childIndex] = context.currentNode = node;
},
removeNode(node) {
if (!context.parent) {
throw new Error(`Cannot remove root node.`);
}
const list = context.parent.children;
const removalIndex = node
? list.indexOf(node)
: context.currentNode
? context.childIndex
: -1;
/* istanbul ignore if */
if (removalIndex < 0) {
throw new Error(`node being removed is not a child of current parent`);
}
if (!node || node === context.currentNode) {
// current node removed
context.currentNode = null;
context.onNodeRemoved();
}
else {
// sibling node removed
if (context.childIndex > removalIndex) {
context.childIndex--;
context.onNodeRemoved();
}
}
context.parent.children.splice(removalIndex, 1);
},
onNodeRemoved: () => { },
addIdentifiers(exp) {
if ((0, shared_1.isString)(exp)) {
......
import { ExpressionNode, SimpleExpressionNode, ElementTypes } from '@vue/compiler-core';
import { ExpressionNode, SimpleExpressionNode, ElementTypes, ElementNode } from '@vue/compiler-core';
import { NodeTransform, TransformContext } from '../transform';
export interface ForNode {
export interface VForOptions {
source: string;
value: string;
key: string;
index: string;
}
export declare type ForElementNode = ElementNode & {
vFor: VForOptions;
};
export declare function isForElementNode(node: unknown): node is ForElementNode;
export declare const transformFor: NodeTransform;
export interface ForParseResult {
source: ExpressionNode;
......
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createForLoopParams = exports.parseForExpression = exports.transformFor = void 0;
exports.createForLoopParams = exports.parseForExpression = exports.transformFor = exports.isForElementNode = void 0;
const compiler_core_1 = require("@vue/compiler-core");
const ast_1 = require("../ast");
const transformExpression_1 = require("./transformExpression");
function isForElementNode(node) {
return !!node.vFor;
}
exports.isForElementNode = isForElementNode;
exports.transformFor = (0, compiler_core_1.createStructuralDirectiveTransform)('for', (node, dir, _context) => {
const context = _context;
if (!dir.exp) {
......@@ -18,7 +22,6 @@ exports.transformFor = (0, compiler_core_1.createStructuralDirectiveTransform)('
parseResult.tagType = node.tagType;
const { addIdentifiers, removeIdentifiers } = context;
const { source, value, key, index } = parseResult;
// scopes.index++
if (context.prefixIdentifiers) {
value && addIdentifiers(value);
key && addIdentifiers(key);
......@@ -29,24 +32,24 @@ exports.transformFor = (0, compiler_core_1.createStructuralDirectiveTransform)('
key: key ? key.content : '',
index: index ? index.content : '',
};
const { currentScope: parentScope, popScope } = context;
const vForScope = context.addVForScope({
source: source.content,
...vForData,
});
return () => {
// scopes.index--
if (context.prefixIdentifiers) {
value && removeIdentifiers(value);
key && removeIdentifiers(key);
index && removeIdentifiers(index);
}
const { currentScope } = context;
const id = currentScope.id.next();
node.forNode = {
const id = parentScope.id.next();
node.vFor = {
source: id,
...vForData,
};
currentScope.properties.push((0, ast_1.createObjectProperty)(id, (0, ast_1.createVForCallExpression)(vForScope)));
parentScope.properties.push((0, ast_1.createObjectProperty)(id, (0, ast_1.createVForCallExpression)(vForScope)));
popScope();
};
});
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
......
import { NodeTransform } from '../transform';
export interface IfNode {
import { DirectiveNode, ElementNode, IfNode } from '@vue/compiler-core';
import { NodeTransform, TransformContext } from '../transform';
interface IfOptions {
name: string;
condition: string;
condition?: string;
}
export declare type IfElementNode = ElementNode & {
vIf: IfOptions;
};
export declare function isIfElementNode(node: unknown): node is IfElementNode;
export declare const transformIf: NodeTransform;
export declare function processIf(node: ElementNode, dir: DirectiveNode, context: TransformContext, processCodegen?: (node: IfNode, branch: IfElementNode, isRoot: boolean) => (() => void) | undefined): (() => void) | undefined;
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.transformIf = void 0;
exports.processIf = exports.transformIf = exports.isIfElementNode = void 0;
const parser_1 = require("@babel/parser");
const types_1 = require("@babel/types");
const compiler_core_1 = require("@vue/compiler-core");
const ast_1 = require("../ast");
const codegen_1 = require("../codegen");
const transform_1 = require("../transform");
const transformExpression_1 = require("./transformExpression");
const transformIdentifier_1 = require("./transformIdentifier");
function isIfElementNode(node) {
return !!node.vIf;
}
exports.isIfElementNode = isIfElementNode;
exports.transformIf = (0, compiler_core_1.createStructuralDirectiveTransform)(/^(if|else|else-if)$/, (node, dir, _context) => {
const context = _context;
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
const { currentScope: parentScope, popScope } = context;
const ifOptions = {
name: dir.name,
};
branch.vIf = ifOptions;
const condition = dir.exp
? (0, parser_1.parseExpression)((0, codegen_1.genNode)(dir.exp).code)
: undefined;
const vIfScope = context.addVIfScope({
name: dir.name,
condition,
});
return () => {
if (condition) {
if (!(0, types_1.isLiteral)(condition)) {
ifOptions.condition = (0, transformIdentifier_1.rewriteExpression)(dir.exp, parentScope, condition).content;
}
else {
ifOptions.condition = dir.exp.content;
}
}
if (isRoot) {
parentScope.properties.push((0, ast_1.createVIfSpreadElement)(vIfScope));
}
else {
const vIfSpreadElement = findVIfSpreadElement(parentScope);
if (!vIfSpreadElement) {
popScope();
return context.onError((0, compiler_core_1.createCompilerError)(30 /* X_V_ELSE_NO_ADJACENT_IF */, node.loc));
}
let alternate = (0, ast_1.createObjectExpression)([]);
if (dir.name === 'else-if') {
alternate = (0, ast_1.createVIfConditionalExpression)(vIfScope);
}
else if (dir.name === 'else') {
alternate = (0, ast_1.createObjectExpression)(vIfScope.properties);
}
findVIfConditionalExpression(vIfSpreadElement.argument).alternate = alternate;
}
popScope();
};
});
});
function processIf(node, dir, context, processCodegen) {
if (dir.name !== 'else' &&
(!dir.exp || !dir.exp.content.trim())) {
const loc = dir.exp ? dir.exp.loc : node.loc;
......@@ -17,52 +67,66 @@ exports.transformIf = (0, compiler_core_1.createStructuralDirectiveTransform)(/^
dir.exp = (0, compiler_core_1.createSimpleExpression)(`true`, false, loc);
}
if (context.prefixIdentifiers && dir.exp) {
// dir.exp can only be simple expression because vIf transform is applied
// before expression transform.
dir.exp = (0, transformExpression_1.processExpression)(dir.exp, context);
}
const condition = dir.exp
? (0, parser_1.parseExpression)((0, codegen_1.genNode)(dir.exp).code)
: undefined;
const { currentScope: parentScope, popScope } = context;
const vIfScope = context.addVIfScope({
name: dir.name,
condition,
});
return () => {
if (dir.name === 'if') {
const ifNode = {
name: dir.name,
condition: '',
type: 9 /* IF */,
loc: node.loc,
branches: [node],
};
if (condition) {
if (!(0, types_1.isLiteral)(condition)) {
ifNode.condition = (0, transformIdentifier_1.rewriteExpression)(dir.exp, parentScope, condition).content;
}
else {
ifNode.condition = dir.exp.content;
}
}
;
node.ifNode = ifNode;
if (dir.name === 'if') {
parentScope.properties.push((0, ast_1.createVIfSpreadElement)(vIfScope));
context.replaceNode(ifNode);
if (processCodegen) {
return processCodegen(ifNode, node, true);
}
else {
const vIfSpreadElement = findVIfSpreadElement(parentScope);
if (!vIfSpreadElement) {
popScope();
return context.onError((0, compiler_core_1.createCompilerError)(30 /* X_V_ELSE_NO_ADJACENT_IF */, dir.loc));
}
else {
// locate the adjacent v-if
const siblings = context.parent.children;
let i = siblings.indexOf(node);
while (i-- >= -1) {
const sibling = siblings[i];
if (sibling && sibling.type === 3 /* COMMENT */) {
context.removeNode(sibling);
continue;
}
let alternate = (0, ast_1.createObjectExpression)([]);
if (dir.name === 'else-if') {
alternate = (0, ast_1.createVIfConditionalExpression)(vIfScope);
if (sibling &&
sibling.type === 2 /* TEXT */ &&
!sibling.content.trim().length) {
context.removeNode(sibling);
continue;
}
else if (dir.name === 'else') {
alternate = (0, ast_1.createObjectExpression)(vIfScope.properties);
if (sibling && sibling.type === 9 /* IF */) {
// Check if v-else was followed by v-else-if
if (dir.name === 'else-if' &&
sibling.branches[sibling.branches.length - 1].vIf.condition === undefined) {
context.onError((0, compiler_core_1.createCompilerError)(30 /* X_V_ELSE_NO_ADJACENT_IF */, node.loc));
}
// move the node to the if node's branches
context.removeNode();
sibling.branches.push(node);
const onExit = processCodegen &&
processCodegen(sibling, node, false);
// since the branch was removed, it will not be traversed.
// make sure to traverse here.
(0, transform_1.traverseNode)(node, context);
// call on exit
if (onExit)
onExit();
// make sure to reset currentNode after traversal to indicate this
// node has been removed.
context.currentNode = null;
}
findVIfConditionalExpression(vIfSpreadElement.argument).alternate = alternate;
else {
context.onError((0, compiler_core_1.createCompilerError)(30 /* X_V_ELSE_NO_ADJACENT_IF */, node.loc));
}
break;
}
popScope();
};
});
}
}
exports.processIf = processIf;
function findVIfSpreadElement({ properties }) {
const len = properties.length;
for (let i = len - 1; i >= 0; i--) {
......
......@@ -11,8 +11,8 @@ import {
} from '@vue/compiler-core'
import { TemplateCodegenOptions } from '../options'
import { genNode as genCodeNode } from '../codegen'
import { ForNode } from '../transforms/vFor'
import { IfNode } from '../transforms/vIf'
import { isForElementNode, VForOptions } from '../transforms/vFor'
import { IfElementNode, isIfElementNode } from '../transforms/vIf'
interface TemplateCodegenContext {
code: string
push(code: string): void
......@@ -39,6 +39,10 @@ export function genNode(
context: TemplateCodegenContext
) {
switch (node.type) {
case NodeTypes.IF:
return node.branches.forEach((node) => {
genElement(node as unknown as IfElementNode, context)
})
case NodeTypes.TEXT:
return genText(node, context)
case NodeTypes.INTERPOLATION:
......@@ -67,16 +71,16 @@ function genVElse({ push }: TemplateCodegenContext) {
}
function genVFor(
node: ForNode,
opts: VForOptions,
props: (AttributeNode | DirectiveNode)[],
{ push }: TemplateCodegenContext
) {
push(` wx:for="{{${node.source}}}"`)
if (node.value) {
push(` wx:for-item="${node.value}"`)
push(` wx:for="{{${opts.source}}}"`)
if (opts.value) {
push(` wx:for-item="${opts.value}"`)
}
if (node.key) {
push(` wx:for-index="${node.key}"`)
if (opts.key) {
push(` wx:for-index="${opts.key}"`)
}
const keyIndex = props.findIndex(
(prop) =>
......@@ -93,24 +97,26 @@ function genVFor(
props.splice(keyIndex, 1)
}
}
const tagMap: Record<string, string> = {
template: 'block',
}
export function genElement(node: ElementNode, context: TemplateCodegenContext) {
const { tag, children, isSelfClosing, props } = node
const { children, isSelfClosing, props } = node
const tag = tagMap[node.tag] || node.tag
const { push } = context
push(`<${tag}`)
const ifNode = (node as any).ifNode as IfNode
if (ifNode) {
if (ifNode.name === 'if') {
genVIf(ifNode.condition, context)
} else if (ifNode.name === 'else-if') {
genVElseIf(ifNode.condition, context)
} else if (ifNode.name === 'else') {
if (isIfElementNode(node)) {
const { name, condition } = node.vIf
if (name === 'if') {
genVIf(condition!, context)
} else if (name === 'else-if') {
genVElseIf(condition!, context)
} else if (name === 'else') {
genVElse(context)
}
}
const forNode = (node as any).forNode as ForNode
if (forNode) {
genVFor(forNode, props, context)
if (isForElementNode(node)) {
genVFor(node.vFor, props, context)
}
if (props.length) {
genElementProps(props, context)
......
......@@ -62,6 +62,7 @@ export interface TransformContext
removeHelper<T extends symbol>(name: T): void
helperString(name: symbol): string
replaceNode(node: TemplateChildNode): void
removeNode(node?: TemplateChildNode): void
onNodeRemoved(): void
addIdentifiers(exp: ExpressionNode | string): void
removeIdentifiers(exp: ExpressionNode | string): void
......@@ -272,6 +273,33 @@ export function createTransformContext(
replaceNode(node) {
context.parent!.children[context.childIndex] = context.currentNode = node
},
removeNode(node) {
if (!context.parent) {
throw new Error(`Cannot remove root node.`)
}
const list = context.parent!.children
const removalIndex = node
? list.indexOf(node)
: context.currentNode
? context.childIndex
: -1
/* istanbul ignore if */
if (removalIndex < 0) {
throw new Error(`node being removed is not a child of current parent`)
}
if (!node || node === context.currentNode) {
// current node removed
context.currentNode = null
context.onNodeRemoved()
} else {
// sibling node removed
if (context.childIndex > removalIndex) {
context.childIndex--
context.onNodeRemoved()
}
}
context.parent!.children.splice(removalIndex, 1)
},
onNodeRemoved: () => {},
addIdentifiers(exp) {
if (isString(exp)) {
......
......@@ -8,18 +8,24 @@ import {
SourceLocation,
createStructuralDirectiveTransform,
ElementTypes,
ElementNode,
} from '@vue/compiler-core'
import { createObjectProperty, createVForCallExpression } from '../ast'
import { NodeTransform, TransformContext } from '../transform'
import { processExpression } from './transformExpression'
export interface ForNode {
export interface VForOptions {
source: string
value: string
key: string
index: string
}
export type ForElementNode = ElementNode & {
vFor: VForOptions
}
export function isForElementNode(node: unknown): node is ForElementNode {
return !!(node as ForElementNode).vFor
}
export const transformFor = createStructuralDirectiveTransform(
'for',
(node, dir, _context) => {
......@@ -44,7 +50,6 @@ export const transformFor = createStructuralDirectiveTransform(
parseResult.tagType = node.tagType
const { addIdentifiers, removeIdentifiers } = context
const { source, value, key, index } = parseResult
// scopes.index++
if (context.prefixIdentifiers) {
value && addIdentifiers(value)
key && addIdentifiers(key)
......@@ -55,26 +60,26 @@ export const transformFor = createStructuralDirectiveTransform(
key: key ? (key as SimpleExpressionNode).content : '',
index: index ? (index as SimpleExpressionNode).content : '',
}
const { currentScope: parentScope, popScope } = context
const vForScope = context.addVForScope({
source: (source as SimpleExpressionNode).content,
...vForData,
})
return () => {
// scopes.index--
if (context.prefixIdentifiers) {
value && removeIdentifiers(value)
key && removeIdentifiers(key)
index && removeIdentifiers(index)
}
const { currentScope } = context
const id = currentScope.id.next()
;(node as any).forNode = {
const id = parentScope.id.next()
;(node as ForElementNode).vFor = {
source: id,
...vForData,
}
currentScope.properties.push(
parentScope.properties.push(
createObjectProperty(id, createVForCallExpression(vForScope))
)
popScope()
}
}
) as unknown as NodeTransform
......
......@@ -10,7 +10,12 @@ import {
createCompilerError,
createSimpleExpression,
createStructuralDirectiveTransform,
DirectiveNode,
ElementNode,
ErrorCodes,
IfBranchNode,
IfNode,
NodeTypes,
SimpleExpressionNode,
} from '@vue/compiler-core'
import {
......@@ -20,83 +25,174 @@ import {
} from '../ast'
import { genNode } from '../codegen'
import { CodegenScope } from '../options'
import { NodeTransform, TransformContext } from '../transform'
import { NodeTransform, TransformContext, traverseNode } from '../transform'
import { processExpression } from './transformExpression'
import { rewriteExpression } from './transformIdentifier'
export interface IfNode {
interface IfOptions {
name: string
condition: string
condition?: string
}
export type IfElementNode = ElementNode & {
vIf: IfOptions
}
export function isIfElementNode(node: unknown): node is IfElementNode {
return !!(node as IfElementNode).vIf
}
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, _context) => {
const context = _context as unknown as TransformContext
if (
dir.name !== 'else' &&
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
) {
const loc = dir.exp ? dir.exp.loc : node.loc
context.onError(
createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
)
dir.exp = createSimpleExpression(`true`, false, loc)
}
if (context.prefixIdentifiers && dir.exp) {
dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
}
const condition = dir.exp
? parseExpression(genNode(dir.exp).code)
: undefined
const { currentScope: parentScope, popScope } = context
const vIfScope = context.addVIfScope({
name: dir.name,
condition,
})
return () => {
const ifNode: IfNode = {
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
const { currentScope: parentScope, popScope } = context
const ifOptions: IfOptions = {
name: dir.name,
condition: '',
}
if (condition) {
if (!isLiteral(condition)) {
ifNode.condition = rewriteExpression(
dir.exp!,
parentScope,
condition
).content
branch.vIf = ifOptions
const condition = dir.exp
? parseExpression(genNode(dir.exp).code)
: undefined
const vIfScope = context.addVIfScope({
name: dir.name,
condition,
})
return () => {
if (condition) {
if (!isLiteral(condition)) {
ifOptions.condition = rewriteExpression(
dir.exp!,
parentScope,
condition
).content
} else {
ifOptions.condition = (dir.exp as SimpleExpressionNode).content
}
}
if (isRoot) {
parentScope.properties.push(createVIfSpreadElement(vIfScope))
} else {
ifNode.condition = (dir.exp as SimpleExpressionNode).content
const vIfSpreadElement = findVIfSpreadElement(parentScope)
if (!vIfSpreadElement) {
popScope()
return context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
)
}
let alternate: ConditionalExpression | ObjectExpression =
createObjectExpression([])
if (dir.name === 'else-if') {
alternate = createVIfConditionalExpression(vIfScope)
} else if (dir.name === 'else') {
alternate = createObjectExpression(vIfScope.properties)
}
findVIfConditionalExpression(
vIfSpreadElement.argument as ConditionalExpression
).alternate = alternate
}
popScope()
}
;(node as any).ifNode = ifNode
if (dir.name === 'if') {
parentScope.properties.push(createVIfSpreadElement(vIfScope))
} else {
const vIfSpreadElement = findVIfSpreadElement(parentScope)
if (!vIfSpreadElement) {
popScope()
return context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, dir.loc)
})
}
) as unknown as NodeTransform
export function processIf(
node: ElementNode,
dir: DirectiveNode,
context: TransformContext,
processCodegen?: (
node: IfNode,
branch: IfElementNode,
isRoot: boolean
) => (() => void) | undefined
) {
if (
dir.name !== 'else' &&
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
) {
const loc = dir.exp ? dir.exp.loc : node.loc
context.onError(
createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
)
dir.exp = createSimpleExpression(`true`, false, loc)
}
if (context.prefixIdentifiers && dir.exp) {
// dir.exp can only be simple expression because vIf transform is applied
// before expression transform.
dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
}
if (dir.name === 'if') {
const ifNode: IfNode = {
type: NodeTypes.IF,
loc: node.loc,
branches: [node as unknown as IfBranchNode],
}
context.replaceNode(ifNode)
if (processCodegen) {
return processCodegen(ifNode, node as IfElementNode, true)
}
} else {
// locate the adjacent v-if
const siblings = context.parent!.children
let i = siblings.indexOf(node)
while (i-- >= -1) {
const sibling = siblings[i]
if (sibling && sibling.type === NodeTypes.COMMENT) {
context.removeNode(sibling)
continue
}
if (
sibling &&
sibling.type === NodeTypes.TEXT &&
!sibling.content.trim().length
) {
context.removeNode(sibling)
continue
}
if (sibling && sibling.type === NodeTypes.IF) {
// Check if v-else was followed by v-else-if
if (
dir.name === 'else-if' &&
(
sibling.branches[
sibling.branches.length - 1
] as unknown as IfElementNode
).vIf.condition === undefined
) {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
)
}
let alternate: ConditionalExpression | ObjectExpression =
createObjectExpression([])
if (dir.name === 'else-if') {
alternate = createVIfConditionalExpression(vIfScope)
} else if (dir.name === 'else') {
alternate = createObjectExpression(vIfScope.properties)
}
findVIfConditionalExpression(
vIfSpreadElement.argument as ConditionalExpression
).alternate = alternate
// move the node to the if node's branches
context.removeNode()
sibling.branches.push(node as unknown as IfBranchNode)
const onExit =
processCodegen &&
processCodegen(sibling, node as IfElementNode, false)
// since the branch was removed, it will not be traversed.
// make sure to traverse here.
traverseNode(node, context)
// call on exit
if (onExit) onExit()
// make sure to reset currentNode after traversal to indicate this
// node has been removed.
context.currentNode = null
} else {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
)
}
popScope()
break
}
}
) as unknown as NodeTransform
}
function findVIfSpreadElement({ properties }: CodegenScope) {
const len = properties.length
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册