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`, + `(_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`, + `(_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`, + `(_ctx, _cache) => { +return { + a: _ctx.ok, + ...(_ctx.ok ? {} : _ctx.orNot ? {} : 3 ? {} : {}), + b: _ctx.orNot +} +}` + ) + }) + test(`comment between branches`, () => { + assert( + ` + + + + + + `, + `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