diff --git a/packages/uni-mp-compiler/__tests__/compiler.spec.ts b/packages/uni-mp-compiler/__tests__/compiler.ts
similarity index 100%
rename from packages/uni-mp-compiler/__tests__/compiler.spec.ts
rename to packages/uni-mp-compiler/__tests__/compiler.ts
diff --git a/packages/uni-mp-compiler/__tests__/test.spec.ts b/packages/uni-mp-compiler/__tests__/test.spec.ts
index d70a7729483efdb93d0655eefa21005983d3fcb7..cfd21143f57b7e9ccb8e4812a757b93fa31bf955 100644
--- a/packages/uni-mp-compiler/__tests__/test.spec.ts
+++ b/packages/uni-mp-compiler/__tests__/test.spec.ts
@@ -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(
- `{{item.title}}`,
- `{{item.a}}`,
+ ``,
+ ``,
`(_ctx, _cache) => {
return {
- a: vFor(_ctx.items, item => {
- return {
- a: item.title,
- b: item.show,
- ...(item.show ? {} : {})
- };
- })
+ a: _ctx.ok,
+ ...(_ctx.ok ? {} : {})
}
}`
)
diff --git a/packages/uni-mp-compiler/__tests__/testUtils.ts b/packages/uni-mp-compiler/__tests__/testUtils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9f60b89dea407a1b72ed7b54b553d21195b53da3
--- /dev/null
+++ b/packages/uni-mp-compiler/__tests__/testUtils.ts
@@ -0,0 +1,32 @@
+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)
+ }
+}
diff --git a/packages/uni-mp-compiler/__tests__/vIf.spec.ts b/packages/uni-mp-compiler/__tests__/vIf.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..77aa725c1155870e4c15408208169287dec9de48
--- /dev/null
+++ b/packages/uni-mp-compiler/__tests__/vIf.spec.ts
@@ -0,0 +1,255 @@
+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(
+ ``,
+ ``,
+ `(_ctx, _cache) => {
+return {
+ a: _ctx.ok,
+ ...(_ctx.ok ? {} : {})
+}
+}`
+ )
+ })
+ test(`template v-if`, () => {
+ assert(
+ `hello`,
+ `hello`,
+ `(_ctx, _cache) => {
+return {
+ a: _ctx.ok,
+ ...(_ctx.ok ? {} : {})
+}
+}`
+ )
+ })
+ test(`component v-if`, () => {
+ assert(
+ ``,
+ ``,
+ `(_ctx, _cache) => {
+return {
+ a: _ctx.ok,
+ ...(_ctx.ok ? {} : {})
+}
+}`
+ )
+ })
+ test(`v-if + v-else`, () => {
+ assert(
+ ``,
+ ``,
+ `(_ctx, _cache) => {
+return {
+ a: _ctx.ok,
+ ...(_ctx.ok ? {} : {})
+}
+}`
+ )
+ })
+ test(`v-if + v-else-if`, () => {
+ assert(
+ ``,
+ ``,
+ `(_ctx, _cache) => {
+return {
+ a: _ctx.ok,
+ ...(_ctx.ok ? {} : _ctx.orNot ? {} : {}),
+ b: _ctx.orNot
+}
+}`
+ )
+ })
+ test(`v-if + v-else-if + v-else`, () => {
+ assert(
+ `fine`,
+ `fine`,
+ `(_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(
+ `fine`,
+ `fine`,
+ `(_ctx, _cache) => {
+return {
+ a: _ctx.ok,
+ ...(_ctx.ok ? {} : _ctx.orNot ? {} : 3 ? {} : {}),
+ b: _ctx.orNot
+}
+}`
+ )
+ })
+ test(`comment between branches`, () => {
+ assert(
+ `
+
+
+
+
+ fine
+ `,
+ `fine`,
+ `(_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(``, {
+ onError,
+ })
+ expect(onError.mock.calls[0]).toMatchObject([
+ {
+ code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
+ loc: node1.loc,
+ },
+ ])
+
+ const { node: node2 } = compileWithIfTransform(
+ ``,
+ { onError },
+ 1
+ )
+ expect(onError.mock.calls[1]).toMatchObject([
+ {
+ code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
+ loc: node2.loc,
+ },
+ ])
+
+ const { node: node3 } = compileWithIfTransform(
+ `foo`,
+ { 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(
+ ``,
+ {
+ onError,
+ }
+ )
+ expect(onError.mock.calls[0]).toMatchObject([
+ {
+ code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
+ loc: node1.loc,
+ },
+ ])
+
+ const { node: node2 } = compileWithIfTransform(
+ ``,
+ { onError },
+ 1
+ )
+ expect(onError.mock.calls[1]).toMatchObject([
+ {
+ code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
+ loc: node2.loc,
+ },
+ ])
+
+ const { node: node3 } = compileWithIfTransform(
+ `foo`,
+ { onError },
+ 2
+ )
+ expect(onError.mock.calls[2]).toMatchObject([
+ {
+ code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
+ loc: node3.loc,
+ },
+ ])
+
+ const {
+ node: { branches },
+ } = compileWithIfTransform(
+ ``,
+ { 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(
+ // ``,
+ // { onError }
+ // )
+ // expect(onError.mock.calls[0]).toMatchObject([
+ // {
+ // code: ErrorCodes.X_V_IF_SAME_KEY,
+ // },
+ // ])
+ // // static
+ // compileWithIfTransform(
+ // ``,
+ // {
+ // onError,
+ // }
+ // )
+ // expect(onError.mock.calls[1]).toMatchObject([
+ // {
+ // code: ErrorCodes.X_V_IF_SAME_KEY,
+ // },
+ // ])
+ // })
+ })
+})
diff --git a/packages/uni-mp-compiler/dist/template/codegen.js b/packages/uni-mp-compiler/dist/template/codegen.js
index 6275cfe4238ad2dfadc3f416a57b742e7f502a71..f2a505c016c4ecb29fbb9c7220410cb4264a31c1 100644
--- a/packages/uni-mp-compiler/dist/template/codegen.js
+++ b/packages/uni-mp-compiler/dist/template/codegen.js
@@ -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);
diff --git a/packages/uni-mp-compiler/dist/transform.d.ts b/packages/uni-mp-compiler/dist/transform.d.ts
index 65ff4fdb1acfeecd39b379c4e3f954acebd3797c..7f112a312574e89a0c31e33c02aac5c7fafe2ece 100644
--- a/packages/uni-mp-compiler/dist/transform.d.ts
+++ b/packages/uni-mp-compiler/dist/transform.d.ts
@@ -24,6 +24,7 @@ export interface TransformContext extends Required(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;
diff --git a/packages/uni-mp-compiler/dist/transform.js b/packages/uni-mp-compiler/dist/transform.js
index 0963ff8f9e57840134c654e431b8dc1c5d79aaab..e7c02cec6d01dc59beacfd9de40fa32a645d89c9 100644
--- a/packages/uni-mp-compiler/dist/transform.js
+++ b/packages/uni-mp-compiler/dist/transform.js
@@ -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)) {
diff --git a/packages/uni-mp-compiler/dist/transforms/vFor.d.ts b/packages/uni-mp-compiler/dist/transforms/vFor.d.ts
index 9709570845baaad33b368f244ee0e9fb3f49a4d4..4e1398a103a5675d28cedbd4019f2a8e015db8c1 100644
--- a/packages/uni-mp-compiler/dist/transforms/vFor.d.ts
+++ b/packages/uni-mp-compiler/dist/transforms/vFor.d.ts
@@ -1,11 +1,15 @@
-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;
diff --git a/packages/uni-mp-compiler/dist/transforms/vFor.js b/packages/uni-mp-compiler/dist/transforms/vFor.js
index c8031eb2bf6e4b17d6268e7322c4e8ca8d2ec2af..62c275c0bf404fa7bbb9958576caa338c1f6b2e9 100644
--- a/packages/uni-mp-compiler/dist/transforms/vFor.js
+++ b/packages/uni-mp-compiler/dist/transforms/vFor.js
@@ -1,9 +1,13 @@
"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]*)/;
diff --git a/packages/uni-mp-compiler/dist/transforms/vIf.d.ts b/packages/uni-mp-compiler/dist/transforms/vIf.d.ts
index 1c0eb9f682ce6cc67fd4165247d4fb2088778ba1..9dac5bf764e46002f7370063b629763c5c6553e5 100644
--- a/packages/uni-mp-compiler/dist/transforms/vIf.d.ts
+++ b/packages/uni-mp-compiler/dist/transforms/vIf.d.ts
@@ -1,6 +1,13 @@
-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 {};
diff --git a/packages/uni-mp-compiler/dist/transforms/vIf.js b/packages/uni-mp-compiler/dist/transforms/vIf.js
index 11ab1cba545822406939ee7efacb256e225de465..4b47d554d22cd421b1ed0ad4b23a3b61b62d5686 100644
--- a/packages/uni-mp-compiler/dist/transforms/vIf.js
+++ b/packages/uni-mp-compiler/dist/transforms/vIf.js
@@ -1,15 +1,65 @@
"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--) {
diff --git a/packages/uni-mp-compiler/src/template/codegen.ts b/packages/uni-mp-compiler/src/template/codegen.ts
index 4aab1dbb321cbbf7d11fcd3ea379d732b53e2788..e50080760e155cb076c8c2c688b48e97171ca108 100644
--- a/packages/uni-mp-compiler/src/template/codegen.ts
+++ b/packages/uni-mp-compiler/src/template/codegen.ts
@@ -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 = {
+ 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)
diff --git a/packages/uni-mp-compiler/src/transform.ts b/packages/uni-mp-compiler/src/transform.ts
index 7bf9015f8fbc90629d6a7b9dbcb50ad65f21f3b1..493f20ce12fd4d4c5188b8cc6a709e9dda253180 100644
--- a/packages/uni-mp-compiler/src/transform.ts
+++ b/packages/uni-mp-compiler/src/transform.ts
@@ -62,6 +62,7 @@ export interface TransformContext
removeHelper(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)) {
diff --git a/packages/uni-mp-compiler/src/transforms/vFor.ts b/packages/uni-mp-compiler/src/transforms/vFor.ts
index 4f6c19605d982fa717dfe95d46387f6015b04615..e437f91ae1ce5a5363ad483d5b3017f13a10e4ed 100644
--- a/packages/uni-mp-compiler/src/transforms/vFor.ts
+++ b/packages/uni-mp-compiler/src/transforms/vFor.ts
@@ -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
diff --git a/packages/uni-mp-compiler/src/transforms/vIf.ts b/packages/uni-mp-compiler/src/transforms/vIf.ts
index 27b0ac57e137fc7e0f8ef9937ef062a565c8a0d8..23c17a86e600fa80ccf62e9f8bf3f91f6e67f80e 100644
--- a/packages/uni-mp-compiler/src/transforms/vIf.ts
+++ b/packages/uni-mp-compiler/src/transforms/vIf.ts
@@ -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