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

wip(mp): vif

上级 57d61c3d
...@@ -20,19 +20,14 @@ function assert(template: string, templateCode: string, renderCode: string) { ...@@ -20,19 +20,14 @@ function assert(template: string, templateCode: string, renderCode: string) {
} }
describe('compiler', () => { describe('compiler', () => {
test(`generate v-for with v-if`, () => { test(`basic v-if`, () => {
assert( assert(
`<view v-for="item in items"><view v-if="item.show">{{item.title}}</view></view>`, `<view v-if="ok"/>`,
`<view wx:for="{{a}}" wx:for-item="item"><view wx:if="{{item.b}}">{{item.a}}</view></view>`, `<view wx:if="{{a}}"/>`,
`(_ctx, _cache) => { `(_ctx, _cache) => {
return { return {
a: vFor(_ctx.items, item => { a: _ctx.ok,
return { ...(_ctx.ok ? {} : {})
a: item.title,
b: item.show,
...(item.show ? {} : {})
};
})
} }
}` }`
) )
......
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 @@ ...@@ -2,6 +2,8 @@
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.genElementProps = exports.genElement = exports.genNode = exports.generate = void 0; exports.genElementProps = exports.genElement = exports.genNode = exports.generate = void 0;
const codegen_1 = require("../codegen"); const codegen_1 = require("../codegen");
const vFor_1 = require("../transforms/vFor");
const vIf_1 = require("../transforms/vIf");
function generate({ children }, { emitFile, filename }) { function generate({ children }, { emitFile, filename }) {
const context = { const context = {
code: '', code: '',
...@@ -17,6 +19,10 @@ function generate({ children }, { emitFile, filename }) { ...@@ -17,6 +19,10 @@ function generate({ children }, { emitFile, filename }) {
exports.generate = generate; exports.generate = generate;
function genNode(node, context) { function genNode(node, context) {
switch (node.type) { switch (node.type) {
case 9 /* IF */:
return node.branches.forEach((node) => {
genElement(node, context);
});
case 2 /* TEXT */: case 2 /* TEXT */:
return genText(node, context); return genText(node, context);
case 5 /* INTERPOLATION */: case 5 /* INTERPOLATION */:
...@@ -41,13 +47,13 @@ function genVElseIf(exp, { push }) { ...@@ -41,13 +47,13 @@ function genVElseIf(exp, { push }) {
function genVElse({ push }) { function genVElse({ push }) {
push(` wx:else`); push(` wx:else`);
} }
function genVFor(node, props, { push }) { function genVFor(opts, props, { push }) {
push(` wx:for="{{${node.source}}}"`); push(` wx:for="{{${opts.source}}}"`);
if (node.value) { if (opts.value) {
push(` wx:for-item="${node.value}"`); push(` wx:for-item="${opts.value}"`);
} }
if (node.key) { if (opts.key) {
push(` wx:for-index="${node.key}"`); push(` wx:for-index="${opts.key}"`);
} }
const keyIndex = props.findIndex((prop) => prop.type === 7 /* DIRECTIVE */ && const keyIndex = props.findIndex((prop) => prop.type === 7 /* DIRECTIVE */ &&
prop.name === 'bind' && prop.name === 'bind' &&
...@@ -61,25 +67,28 @@ function genVFor(node, props, { push }) { ...@@ -61,25 +67,28 @@ function genVFor(node, props, { push }) {
props.splice(keyIndex, 1); props.splice(keyIndex, 1);
} }
} }
const tagMap = {
template: 'block',
};
function genElement(node, context) { 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; const { push } = context;
push(`<${tag}`); push(`<${tag}`);
const ifNode = node.ifNode; if ((0, vIf_1.isIfElementNode)(node)) {
if (ifNode) { const { name, condition } = node.vIf;
if (ifNode.name === 'if') { if (name === 'if') {
genVIf(ifNode.condition, context); genVIf(condition, context);
} }
else if (ifNode.name === 'else-if') { else if (name === 'else-if') {
genVElseIf(ifNode.condition, context); genVElseIf(condition, context);
} }
else if (ifNode.name === 'else') { else if (name === 'else') {
genVElse(context); genVElse(context);
} }
} }
const forNode = node.forNode; if ((0, vFor_1.isForElementNode)(node)) {
if (forNode) { genVFor(node.vFor, props, context);
genVFor(forNode, props, context);
} }
if (props.length) { if (props.length) {
genElementProps(props, context); genElementProps(props, context);
......
...@@ -24,6 +24,7 @@ export interface TransformContext extends Required<Omit<TransformOptions, 'filen ...@@ -24,6 +24,7 @@ export interface TransformContext extends Required<Omit<TransformOptions, 'filen
removeHelper<T extends symbol>(name: T): void; removeHelper<T extends symbol>(name: T): void;
helperString(name: symbol): string; helperString(name: symbol): string;
replaceNode(node: TemplateChildNode): void; replaceNode(node: TemplateChildNode): void;
removeNode(node?: TemplateChildNode): void;
onNodeRemoved(): void; onNodeRemoved(): void;
addIdentifiers(exp: ExpressionNode | string): void; addIdentifiers(exp: ExpressionNode | string): void;
removeIdentifiers(exp: ExpressionNode | string): void; removeIdentifiers(exp: ExpressionNode | string): void;
......
...@@ -176,6 +176,34 @@ function createTransformContext(root, { isTS = false, inline = false, bindingMet ...@@ -176,6 +176,34 @@ function createTransformContext(root, { isTS = false, inline = false, bindingMet
replaceNode(node) { replaceNode(node) {
context.parent.children[context.childIndex] = context.currentNode = 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: () => { }, onNodeRemoved: () => { },
addIdentifiers(exp) { addIdentifiers(exp) {
if ((0, shared_1.isString)(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'; import { NodeTransform, TransformContext } from '../transform';
export interface ForNode { export interface VForOptions {
source: string; source: string;
value: string; value: string;
key: string; key: string;
index: 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 declare const transformFor: NodeTransform;
export interface ForParseResult { export interface ForParseResult {
source: ExpressionNode; source: ExpressionNode;
......
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); 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 compiler_core_1 = require("@vue/compiler-core");
const ast_1 = require("../ast"); const ast_1 = require("../ast");
const transformExpression_1 = require("./transformExpression"); 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) => { exports.transformFor = (0, compiler_core_1.createStructuralDirectiveTransform)('for', (node, dir, _context) => {
const context = _context; const context = _context;
if (!dir.exp) { if (!dir.exp) {
...@@ -18,7 +22,6 @@ exports.transformFor = (0, compiler_core_1.createStructuralDirectiveTransform)(' ...@@ -18,7 +22,6 @@ exports.transformFor = (0, compiler_core_1.createStructuralDirectiveTransform)('
parseResult.tagType = node.tagType; parseResult.tagType = node.tagType;
const { addIdentifiers, removeIdentifiers } = context; const { addIdentifiers, removeIdentifiers } = context;
const { source, value, key, index } = parseResult; const { source, value, key, index } = parseResult;
// scopes.index++
if (context.prefixIdentifiers) { if (context.prefixIdentifiers) {
value && addIdentifiers(value); value && addIdentifiers(value);
key && addIdentifiers(key); key && addIdentifiers(key);
...@@ -29,24 +32,24 @@ exports.transformFor = (0, compiler_core_1.createStructuralDirectiveTransform)(' ...@@ -29,24 +32,24 @@ exports.transformFor = (0, compiler_core_1.createStructuralDirectiveTransform)('
key: key ? key.content : '', key: key ? key.content : '',
index: index ? index.content : '', index: index ? index.content : '',
}; };
const { currentScope: parentScope, popScope } = context;
const vForScope = context.addVForScope({ const vForScope = context.addVForScope({
source: source.content, source: source.content,
...vForData, ...vForData,
}); });
return () => { return () => {
// scopes.index--
if (context.prefixIdentifiers) { if (context.prefixIdentifiers) {
value && removeIdentifiers(value); value && removeIdentifiers(value);
key && removeIdentifiers(key); key && removeIdentifiers(key);
index && removeIdentifiers(index); index && removeIdentifiers(index);
} }
const { currentScope } = context; const id = parentScope.id.next();
const id = currentScope.id.next(); node.vFor = {
node.forNode = {
source: id, source: id,
...vForData, ...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]*)/; const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
......
import { NodeTransform } from '../transform'; import { DirectiveNode, ElementNode, IfNode } from '@vue/compiler-core';
export interface IfNode { import { NodeTransform, TransformContext } from '../transform';
interface IfOptions {
name: string; 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 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"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); 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 parser_1 = require("@babel/parser");
const types_1 = require("@babel/types"); const types_1 = require("@babel/types");
const compiler_core_1 = require("@vue/compiler-core"); const compiler_core_1 = require("@vue/compiler-core");
const ast_1 = require("../ast"); const ast_1 = require("../ast");
const codegen_1 = require("../codegen"); const codegen_1 = require("../codegen");
const transform_1 = require("../transform");
const transformExpression_1 = require("./transformExpression"); const transformExpression_1 = require("./transformExpression");
const transformIdentifier_1 = require("./transformIdentifier"); 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) => { exports.transformIf = (0, compiler_core_1.createStructuralDirectiveTransform)(/^(if|else|else-if)$/, (node, dir, _context) => {
const context = _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' && if (dir.name !== 'else' &&
(!dir.exp || !dir.exp.content.trim())) { (!dir.exp || !dir.exp.content.trim())) {
const loc = dir.exp ? dir.exp.loc : node.loc; const loc = dir.exp ? dir.exp.loc : node.loc;
...@@ -17,52 +67,66 @@ exports.transformIf = (0, compiler_core_1.createStructuralDirectiveTransform)(/^ ...@@ -17,52 +67,66 @@ exports.transformIf = (0, compiler_core_1.createStructuralDirectiveTransform)(/^
dir.exp = (0, compiler_core_1.createSimpleExpression)(`true`, false, loc); dir.exp = (0, compiler_core_1.createSimpleExpression)(`true`, false, loc);
} }
if (context.prefixIdentifiers && dir.exp) { 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); dir.exp = (0, transformExpression_1.processExpression)(dir.exp, context);
} }
const condition = dir.exp if (dir.name === 'if') {
? (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 () => {
const ifNode = { const ifNode = {
name: dir.name, type: 9 /* IF */,
condition: '', loc: node.loc,
branches: [node],
}; };
if (condition) { context.replaceNode(ifNode);
if (!(0, types_1.isLiteral)(condition)) { if (processCodegen) {
ifNode.condition = (0, transformIdentifier_1.rewriteExpression)(dir.exp, parentScope, condition).content; return processCodegen(ifNode, node, true);
}
else {
ifNode.condition = dir.exp.content;
}
}
;
node.ifNode = ifNode;
if (dir.name === 'if') {
parentScope.properties.push((0, ast_1.createVIfSpreadElement)(vIfScope));
} }
else { }
const vIfSpreadElement = findVIfSpreadElement(parentScope); else {
if (!vIfSpreadElement) { // locate the adjacent v-if
popScope(); const siblings = context.parent.children;
return context.onError((0, compiler_core_1.createCompilerError)(30 /* X_V_ELSE_NO_ADJACENT_IF */, dir.loc)); 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 (sibling &&
if (dir.name === 'else-if') { sibling.type === 2 /* TEXT */ &&
alternate = (0, ast_1.createVIfConditionalExpression)(vIfScope); !sibling.content.trim().length) {
context.removeNode(sibling);
continue;
} }
else if (dir.name === 'else') { if (sibling && sibling.type === 9 /* IF */) {
alternate = (0, ast_1.createObjectExpression)(vIfScope.properties); // 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 }) { function findVIfSpreadElement({ properties }) {
const len = properties.length; const len = properties.length;
for (let i = len - 1; i >= 0; i--) { for (let i = len - 1; i >= 0; i--) {
......
...@@ -11,8 +11,8 @@ import { ...@@ -11,8 +11,8 @@ import {
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { TemplateCodegenOptions } from '../options' import { TemplateCodegenOptions } from '../options'
import { genNode as genCodeNode } from '../codegen' import { genNode as genCodeNode } from '../codegen'
import { ForNode } from '../transforms/vFor' import { isForElementNode, VForOptions } from '../transforms/vFor'
import { IfNode } from '../transforms/vIf' import { IfElementNode, isIfElementNode } from '../transforms/vIf'
interface TemplateCodegenContext { interface TemplateCodegenContext {
code: string code: string
push(code: string): void push(code: string): void
...@@ -39,6 +39,10 @@ export function genNode( ...@@ -39,6 +39,10 @@ export function genNode(
context: TemplateCodegenContext context: TemplateCodegenContext
) { ) {
switch (node.type) { switch (node.type) {
case NodeTypes.IF:
return node.branches.forEach((node) => {
genElement(node as unknown as IfElementNode, context)
})
case NodeTypes.TEXT: case NodeTypes.TEXT:
return genText(node, context) return genText(node, context)
case NodeTypes.INTERPOLATION: case NodeTypes.INTERPOLATION:
...@@ -67,16 +71,16 @@ function genVElse({ push }: TemplateCodegenContext) { ...@@ -67,16 +71,16 @@ function genVElse({ push }: TemplateCodegenContext) {
} }
function genVFor( function genVFor(
node: ForNode, opts: VForOptions,
props: (AttributeNode | DirectiveNode)[], props: (AttributeNode | DirectiveNode)[],
{ push }: TemplateCodegenContext { push }: TemplateCodegenContext
) { ) {
push(` wx:for="{{${node.source}}}"`) push(` wx:for="{{${opts.source}}}"`)
if (node.value) { if (opts.value) {
push(` wx:for-item="${node.value}"`) push(` wx:for-item="${opts.value}"`)
} }
if (node.key) { if (opts.key) {
push(` wx:for-index="${node.key}"`) push(` wx:for-index="${opts.key}"`)
} }
const keyIndex = props.findIndex( const keyIndex = props.findIndex(
(prop) => (prop) =>
...@@ -93,24 +97,26 @@ function genVFor( ...@@ -93,24 +97,26 @@ function genVFor(
props.splice(keyIndex, 1) props.splice(keyIndex, 1)
} }
} }
const tagMap: Record<string, string> = {
template: 'block',
}
export function genElement(node: ElementNode, context: TemplateCodegenContext) { 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 const { push } = context
push(`<${tag}`) push(`<${tag}`)
const ifNode = (node as any).ifNode as IfNode if (isIfElementNode(node)) {
if (ifNode) { const { name, condition } = node.vIf
if (ifNode.name === 'if') { if (name === 'if') {
genVIf(ifNode.condition, context) genVIf(condition!, context)
} else if (ifNode.name === 'else-if') { } else if (name === 'else-if') {
genVElseIf(ifNode.condition, context) genVElseIf(condition!, context)
} else if (ifNode.name === 'else') { } else if (name === 'else') {
genVElse(context) genVElse(context)
} }
} }
const forNode = (node as any).forNode as ForNode if (isForElementNode(node)) {
if (forNode) { genVFor(node.vFor, props, context)
genVFor(forNode, props, context)
} }
if (props.length) { if (props.length) {
genElementProps(props, context) genElementProps(props, context)
......
...@@ -62,6 +62,7 @@ export interface TransformContext ...@@ -62,6 +62,7 @@ export interface TransformContext
removeHelper<T extends symbol>(name: T): void removeHelper<T extends symbol>(name: T): void
helperString(name: symbol): string helperString(name: symbol): string
replaceNode(node: TemplateChildNode): void replaceNode(node: TemplateChildNode): void
removeNode(node?: TemplateChildNode): void
onNodeRemoved(): void onNodeRemoved(): void
addIdentifiers(exp: ExpressionNode | string): void addIdentifiers(exp: ExpressionNode | string): void
removeIdentifiers(exp: ExpressionNode | string): void removeIdentifiers(exp: ExpressionNode | string): void
...@@ -272,6 +273,33 @@ export function createTransformContext( ...@@ -272,6 +273,33 @@ export function createTransformContext(
replaceNode(node) { replaceNode(node) {
context.parent!.children[context.childIndex] = context.currentNode = 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: () => {}, onNodeRemoved: () => {},
addIdentifiers(exp) { addIdentifiers(exp) {
if (isString(exp)) { if (isString(exp)) {
......
...@@ -8,18 +8,24 @@ import { ...@@ -8,18 +8,24 @@ import {
SourceLocation, SourceLocation,
createStructuralDirectiveTransform, createStructuralDirectiveTransform,
ElementTypes, ElementTypes,
ElementNode,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { createObjectProperty, createVForCallExpression } from '../ast' import { createObjectProperty, createVForCallExpression } from '../ast'
import { NodeTransform, TransformContext } from '../transform' import { NodeTransform, TransformContext } from '../transform'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
export interface ForNode { export interface VForOptions {
source: string source: string
value: string value: string
key: string key: string
index: 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( export const transformFor = createStructuralDirectiveTransform(
'for', 'for',
(node, dir, _context) => { (node, dir, _context) => {
...@@ -44,7 +50,6 @@ export const transformFor = createStructuralDirectiveTransform( ...@@ -44,7 +50,6 @@ export const transformFor = createStructuralDirectiveTransform(
parseResult.tagType = node.tagType parseResult.tagType = node.tagType
const { addIdentifiers, removeIdentifiers } = context const { addIdentifiers, removeIdentifiers } = context
const { source, value, key, index } = parseResult const { source, value, key, index } = parseResult
// scopes.index++
if (context.prefixIdentifiers) { if (context.prefixIdentifiers) {
value && addIdentifiers(value) value && addIdentifiers(value)
key && addIdentifiers(key) key && addIdentifiers(key)
...@@ -55,26 +60,26 @@ export const transformFor = createStructuralDirectiveTransform( ...@@ -55,26 +60,26 @@ export const transformFor = createStructuralDirectiveTransform(
key: key ? (key as SimpleExpressionNode).content : '', key: key ? (key as SimpleExpressionNode).content : '',
index: index ? (index as SimpleExpressionNode).content : '', index: index ? (index as SimpleExpressionNode).content : '',
} }
const { currentScope: parentScope, popScope } = context
const vForScope = context.addVForScope({ const vForScope = context.addVForScope({
source: (source as SimpleExpressionNode).content, source: (source as SimpleExpressionNode).content,
...vForData, ...vForData,
}) })
return () => { return () => {
// scopes.index--
if (context.prefixIdentifiers) { if (context.prefixIdentifiers) {
value && removeIdentifiers(value) value && removeIdentifiers(value)
key && removeIdentifiers(key) key && removeIdentifiers(key)
index && removeIdentifiers(index) index && removeIdentifiers(index)
} }
const { currentScope } = context const id = parentScope.id.next()
const id = currentScope.id.next() ;(node as ForElementNode).vFor = {
;(node as any).forNode = {
source: id, source: id,
...vForData, ...vForData,
} }
currentScope.properties.push( parentScope.properties.push(
createObjectProperty(id, createVForCallExpression(vForScope)) createObjectProperty(id, createVForCallExpression(vForScope))
) )
popScope()
} }
} }
) as unknown as NodeTransform ) as unknown as NodeTransform
......
...@@ -10,7 +10,12 @@ import { ...@@ -10,7 +10,12 @@ import {
createCompilerError, createCompilerError,
createSimpleExpression, createSimpleExpression,
createStructuralDirectiveTransform, createStructuralDirectiveTransform,
DirectiveNode,
ElementNode,
ErrorCodes, ErrorCodes,
IfBranchNode,
IfNode,
NodeTypes,
SimpleExpressionNode, SimpleExpressionNode,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { import {
...@@ -20,83 +25,174 @@ import { ...@@ -20,83 +25,174 @@ import {
} from '../ast' } from '../ast'
import { genNode } from '../codegen' import { genNode } from '../codegen'
import { CodegenScope } from '../options' import { CodegenScope } from '../options'
import { NodeTransform, TransformContext } from '../transform' import { NodeTransform, TransformContext, traverseNode } from '../transform'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
import { rewriteExpression } from './transformIdentifier' import { rewriteExpression } from './transformIdentifier'
export interface IfNode { interface IfOptions {
name: string 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( export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/, /^(if|else|else-if)$/,
(node, dir, _context) => { (node, dir, _context) => {
const context = _context as unknown as TransformContext const context = _context as unknown as TransformContext
if ( return processIf(node, dir, context, (ifNode, branch, isRoot) => {
dir.name !== 'else' && const { currentScope: parentScope, popScope } = context
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim()) const ifOptions: IfOptions = {
) {
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 = {
name: dir.name, name: dir.name,
condition: '',
} }
if (condition) { branch.vIf = ifOptions
if (!isLiteral(condition)) { const condition = dir.exp
ifNode.condition = rewriteExpression( ? parseExpression(genNode(dir.exp).code)
dir.exp!, : undefined
parentScope, const vIfScope = context.addVIfScope({
condition name: dir.name,
).content 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 { } 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)) ) as unknown as NodeTransform
} else {
const vIfSpreadElement = findVIfSpreadElement(parentScope) export function processIf(
if (!vIfSpreadElement) { node: ElementNode,
popScope() dir: DirectiveNode,
return context.onError( context: TransformContext,
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, dir.loc) 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([]) // move the node to the if node's branches
if (dir.name === 'else-if') { context.removeNode()
alternate = createVIfConditionalExpression(vIfScope)
} else if (dir.name === 'else') { sibling.branches.push(node as unknown as IfBranchNode)
alternate = createObjectExpression(vIfScope.properties) const onExit =
} processCodegen &&
findVIfConditionalExpression( processCodegen(sibling, node as IfElementNode, false)
vIfSpreadElement.argument as ConditionalExpression // since the branch was removed, it will not be traversed.
).alternate = alternate // 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) { function findVIfSpreadElement({ properties }: CodegenScope) {
const len = properties.length const len = properties.length
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册