From 061311cad9989864410068d4609d537e219f8015 Mon Sep 17 00:00:00 2001 From: yuche Date: Wed, 24 Oct 2018 18:45:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(taroize):=20=E6=94=AF=E6=8C=81=20wxs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/loop.spec.ts | 2 +- packages/taroize/.eslintignore | 2 + packages/taroize/src/index.ts | 4 +- packages/taroize/src/script.ts | 16 +- packages/taroize/src/wxml.ts | 251 ++++++++++++------ 5 files changed, 192 insertions(+), 83 deletions(-) create mode 100644 packages/taroize/.eslintignore diff --git a/packages/taro-transformer-wx/__tests__/loop.spec.ts b/packages/taro-transformer-wx/__tests__/loop.spec.ts index 783cae272e74..b87b1a8dc2c1 100644 --- a/packages/taro-transformer-wx/__tests__/loop.spec.ts +++ b/packages/taro-transformer-wx/__tests__/loop.spec.ts @@ -2747,7 +2747,7 @@ describe('loop', () => { ]) expect( instance.state.loopArray0.map(i => - i.$anonymousCallee__7.map(a => a.$original) + i.$anonymousCallee__1.map(a => a.$original) ) ).toEqual(Object.keys(keys).map(key => Object.keys(keys[key]).map(i => i))) expect(template).toMatch( diff --git a/packages/taroize/.eslintignore b/packages/taroize/.eslintignore new file mode 100644 index 000000000000..ec0251730283 --- /dev/null +++ b/packages/taroize/.eslintignore @@ -0,0 +1,2 @@ +/lib +/index.js diff --git a/packages/taroize/src/index.ts b/packages/taroize/src/index.ts index 4c12832ae096..297aeaa6e902 100644 --- a/packages/taroize/src/index.ts +++ b/packages/taroize/src/index.ts @@ -10,7 +10,7 @@ interface Option { } export function parse (option: Option): t.File { - const wxml = parseWXML(option.wxml) + const { wxml, wxses } = parseWXML(option.wxml) const json = parseJSON(option.json) - return parseScript(option.script, wxml as t.Expression, json) + return parseScript(option.script, wxml as t.Expression, json, wxses) } diff --git a/packages/taroize/src/script.ts b/packages/taroize/src/script.ts index c6ef3bf69580..65be71603b0c 100644 --- a/packages/taroize/src/script.ts +++ b/packages/taroize/src/script.ts @@ -2,7 +2,7 @@ import * as t from 'babel-types' import traverse, { NodePath } from 'babel-traverse' import { transform } from 'babel-core' import { buildImportStatement, codeFrameError } from './utils' -import { usedComponents } from './wxml' +import { usedComponents, WXS } from './wxml' import { PageLifecycle } from './lifecycle' const buildDecorator = (type: string) => t.decorator(t.callExpression( @@ -10,7 +10,12 @@ const buildDecorator = (type: string) => t.decorator(t.callExpression( [t.stringLiteral(type)] )) -export function parseScript (script?: string, returned?: t.Expression, json?: t.ObjectExpression) { +export function parseScript ( + script?: string, + returned?: t.Expression, + json?: t.ObjectExpression, + wxses: WXS[] = [] +) { script = script || 'Page({})' const { ast } = transform(script, { parserOpts: { @@ -82,7 +87,12 @@ export function parseScript (script?: string, returned?: t.Expression, json?: t. [], 'withWeapp' ) - ast.program.body.unshift(taroComponentsImport, taroImport, withWeappImport) + ast.program.body.unshift( + taroComponentsImport, + taroImport, + withWeappImport, + ...wxses.map(wxs => buildImportStatement(wxs.src, [], wxs.module)) + ) return ast } diff --git a/packages/taroize/src/wxml.ts b/packages/taroize/src/wxml.ts index 8d1f3e1f0813..622eb9e64b15 100644 --- a/packages/taroize/src/wxml.ts +++ b/packages/taroize/src/wxml.ts @@ -4,7 +4,8 @@ import { camelCase, cloneDeep } from 'lodash' import traverse, { NodePath } from 'babel-traverse' import { buildTemplate, DEFAULT_Component_SET } from './utils' -const allCamelCase = (str: string) => str.charAt(0).toUpperCase() + camelCase(str.substr(1)) +const allCamelCase = (str: string) => + str.charAt(0).toUpperCase() + camelCase(str.substr(1)) enum NodeType { Element = 'element', @@ -34,15 +35,24 @@ interface Text { content: string } +export interface WXS { + module: string + src: string +} + type AllKindNode = Element | Comment | Text type Node = Element | Text interface Condition { - condition: string, - path: NodePath, + condition: string + path: NodePath tester: t.JSXExpressionContainer } -type AttrValue = t.StringLiteral | t.JSXElement | t.JSXExpressionContainer | null +type AttrValue = + | t.StringLiteral + | t.JSXElement + | t.JSXExpressionContainer + | null const WX_IF = 'wx:if' const WX_ELSE_IF = 'wx:elif' @@ -53,7 +63,7 @@ const WX_KEY = 'wx:key' function buildElement ( name: string, - children: Node[] = [] , + children: Node[] = [], attributes: Attribute[] = [] ): Element { return { @@ -66,24 +76,97 @@ function buildElement ( export const usedComponents = new Set() -export function parseWXML (wxml?: string) { +export function parseWXML (wxml?: string): { + wxses: WXS[], + wxml?: t.Node +} { + let wxses: WXS[] = [] if (!wxml) { - return t.nullLiteral() + return { + wxses, + wxml: t.nullLiteral() + } } const nodes = removEmptyTextAndComment(parse(wxml.trim())) - const ast = t.file(t.program([t.expressionStatement(parseNode(buildElement('block', nodes)) as t.Expression)], [])) + const ast = t.file( + t.program( + [ + t.expressionStatement(parseNode( + buildElement('block', nodes) + ) as t.Expression) + ], + [] + ) + ) traverse(ast, { JSXAttribute (path) { const name = path.node.name as t.JSXIdentifier - const jsx = path.findParent(p => p.isJSXElement()) as NodePath + const jsx = path.findParent(p => p.isJSXElement()) as NodePath< + t.JSXElement + > const valueCopy = cloneDeep(path.get('value').node) transformIf(name.name, path, jsx, valueCopy) transformLoop(name.name, path, jsx, valueCopy) + }, + JSXElement (path) { + const openingElement = path.get('openingElement') + const jsxName = openingElement.get('name') + const attrs = openingElement.get('attributes') + if (!jsxName.isJSXIdentifier()) { + return + } + if (jsxName.node.name === 'wxs') { + wxses.push(getWXS(attrs.map(a => a.node), path)) + } } }) - return hydrate(ast) + return { + wxses, + wxml: hydrate(ast) + } +} + +function getWXS (attrs: t.JSXAttribute[], path: NodePath): WXS { + let moduleName: string | null = null + let src: string | null = null + + for (const attr of attrs) { + if (t.isJSXIdentifier(attr.name)) { + const attrName = attr.name.name + const attrValue = attr.value + let value: string | null = null + if (attrValue === null) { + throw new Error('WXS 标签的属性值不得为空') + } + if (t.isStringLiteral(attrValue)) { + value = attrValue.value + } else if ( + t.isJSXExpressionContainer(attrValue) && + t.isStringLiteral(attrValue.expression) + ) { + value = attrValue.expression.value + } + if (attrName === 'module') { + moduleName = value + } + if (attrName === 'src') { + src = value + } + } + } + + if (!moduleName || !src) { + throw new Error('一个 WXS 需要同时存在两个属性:`wxs`, `src`') + } + + path.remove() + + return { + module: moduleName, + src + } } function hydrate (file: t.File) { @@ -92,7 +175,9 @@ function hydrate (file: t.File) { const jsx = ast.expression if (jsx.children.length === 1) { const children = jsx.children[0] - return t.isJSXExpressionContainer(children) ? children.expression : children + return t.isJSXExpressionContainer(children) + ? children.expression + : children } } } @@ -112,38 +197,38 @@ function transformLoop ( attr.remove() let item = t.stringLiteral('item') let index = t.stringLiteral('index') - jsx.get('openingElement').get('attributes').forEach(p => { - const node = p.node - if (node.name.name === WX_FOR_ITEM) { - if (!node.value || !t.isStringLiteral(node.value)) { - throw new Error(WX_FOR_ITEM + ' 的值必须是一个字符串') + jsx + .get('openingElement') + .get('attributes') + .forEach(p => { + const node = p.node + if (node.name.name === WX_FOR_ITEM) { + if (!node.value || !t.isStringLiteral(node.value)) { + throw new Error(WX_FOR_ITEM + ' 的值必须是一个字符串') + } + item = node.value + p.remove() } - item = node.value - p.remove() - } - if (node.name.name === WX_FOR_INDEX) { - if (!node.value || !t.isStringLiteral(node.value)) { - throw new Error(WX_FOR_INDEX + ' 的值必须是一个字符串') + if (node.name.name === WX_FOR_INDEX) { + if (!node.value || !t.isStringLiteral(node.value)) { + throw new Error(WX_FOR_INDEX + ' 的值必须是一个字符串') + } + index = node.value + p.remove() } - index = node.value - p.remove() - } - if (node.name.name === WX_KEY) { - p.get('name').replaceWith(t.jSXIdentifier('key')) - } - }) + if (node.name.name === WX_KEY) { + p.get('name').replaceWith(t.jSXIdentifier('key')) + } + }) jsx.replaceWith( t.jSXExpressionContainer( - t.callExpression( - value.expression, - [t.arrowFunctionExpression( + t.callExpression(value.expression, [ + t.arrowFunctionExpression( [t.identifier(item.value), t.identifier(index.value)], - t.blockStatement([ - t.returnStatement(jsx.node) - ]) - )] - ) + t.blockStatement([t.returnStatement(jsx.node)]) + ) + ]) ) ) } @@ -165,7 +250,7 @@ function transformIf ( tester: value as t.JSXExpressionContainer }) attr.remove() - for (const [ index, sibling ] of siblings.entries()) { + for (const [index, sibling] of siblings.entries()) { const next = siblings[index + 1] const currMatches = findWXIfProps(sibling) const nextMatches = findWXIfProps(next) @@ -198,40 +283,54 @@ function handleConditions (conditions: Condition[]) { const lastCon = conditions[lastLength] let lastAlternate: t.Expression = cloneDeep(lastCon.path.node) if (lastCon.condition === WX_ELSE_IF) { - lastAlternate = t.logicalExpression('&&', lastCon.tester.expression, lastAlternate) + lastAlternate = t.logicalExpression( + '&&', + lastCon.tester.expression, + lastAlternate + ) } - const node = conditions.slice(0, lastLength).reduceRight((acc: t.Expression, condition) => { - return t.conditionalExpression(condition.tester.expression, cloneDeep(condition.path.node), acc) - }, lastAlternate) + const node = conditions + .slice(0, lastLength) + .reduceRight((acc: t.Expression, condition) => { + return t.conditionalExpression( + condition.tester.expression, + cloneDeep(condition.path.node), + acc + ) + }, lastAlternate) conditions[0].path.replaceWith(t.jSXExpressionContainer(node)) conditions.slice(1).forEach(c => c.path.remove()) } } -function findWXIfProps (jsx: NodePath): { reg: RegExpMatchArray, tester: AttrValue } | null { - let matches: { reg: RegExpMatchArray, tester: AttrValue } | null = null - jsx && jsx.isJSXElement() && jsx - .get('openingElement') - .get('attributes') - .some(path => { - const attr = path.node - if (t.isJSXIdentifier(attr.name)) { - const name = attr.name.name - if (name === WX_IF) { - return true - } - const match = name.match(/wx:else|wx:elif/) - if (match) { - path.remove() - matches = { - reg: match, - tester: attr.value +function findWXIfProps ( + jsx: NodePath +): { reg: RegExpMatchArray; tester: AttrValue } | null { + let matches: { reg: RegExpMatchArray; tester: AttrValue } | null = null + jsx && + jsx.isJSXElement() && + jsx + .get('openingElement') + .get('attributes') + .some(path => { + const attr = path.node + if (t.isJSXIdentifier(attr.name)) { + const name = attr.name.name + if (name === WX_IF) { + return true + } + const match = name.match(/wx:else|wx:elif/) + if (match) { + path.remove() + matches = { + reg: match, + tester: attr.value + } + return true } - return true } - } - return false - }) + return false + }) return matches } @@ -244,9 +343,7 @@ function parseNode (node: Node) { } function parseElement (element: Element): t.JSXElement { - const tagName = t.jSXIdentifier( - allCamelCase(element.tagName) - ) + const tagName = t.jSXIdentifier(allCamelCase(element.tagName)) if (DEFAULT_Component_SET.has(tagName.name)) { usedComponents.add(tagName.name) } @@ -262,9 +359,8 @@ function removEmptyTextAndComment (nodes: AllKindNode[]) { for (let index = 0; index < nodes.length; index++) { const node = nodes[index] if ( - (node.type === NodeType.Text && node.content.trim().length === 0) - || - (node.type === NodeType.Comment) + (node.type === NodeType.Text && node.content.trim().length === 0) || + node.type === NodeType.Comment ) { nodes.splice(index, 1) } @@ -294,7 +390,7 @@ function parseContent (content: string) { } } const tokens: string[] = [] - let lastIndex = handlebarsRE.lastIndex = 0 + let lastIndex = (handlebarsRE.lastIndex = 0) let match let index let tokenValue @@ -329,16 +425,16 @@ function parseAttribute (attr: Attribute) { if (value) { const { type, content } = parseContent(value) - jsxValue = type === 'raw' ? t.stringLiteral(content) : t.jSXExpressionContainer(buildTemplate(content)) + jsxValue = + type === 'raw' + ? t.stringLiteral(content) + : t.jSXExpressionContainer(buildTemplate(content)) } const jsxKey = handleAttrKey(key) if (/^on[A-Z]/.test(jsxKey) && jsxValue && t.isStringLiteral(jsxValue)) { jsxValue = t.jSXExpressionContainer( - t.memberExpression( - t.thisExpression(), - t.identifier(jsxValue.value) - ) + t.memberExpression(t.thisExpression(), t.identifier(jsxValue.value)) ) } return t.jSXAttribute(t.jSXIdentifier(jsxKey), jsxValue) @@ -361,7 +457,8 @@ function handleAttrKey (key: string) { default: jsxKey = jsxKey.replace(/^(bind|catch)/, 'on') if (jsxKey.startsWith('on') && jsxKey.length > 2) { - jsxKey = jsxKey.substr(0, 2) + jsxKey[2].toUpperCase() + jsxKey.substr(3) + jsxKey = + jsxKey.substr(0, 2) + jsxKey[2].toUpperCase() + jsxKey.substr(3) } break }