Skip to content

Commit

Permalink
feat(taroize): 支持使用 template::name 声明组件
Browse files Browse the repository at this point in the history
  • Loading branch information
yuche authored and luckyadam committed Nov 19, 2018
1 parent 4cbe987 commit 0167dd3
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 80 deletions.
29 changes: 29 additions & 0 deletions packages/taroize/src/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const specialEvents = new Map<string, string>()
specialEvents.set('bindtimeupdate', 'onTimeUpdate')
specialEvents.set('bindgetphoneNumber', 'onGetPhoneNumber')
specialEvents.set('bindgetrealnameauthinfo', 'onGetRealnameAuthInfo')
specialEvents.set('bindopensetting', 'onOpenSetting')
specialEvents.set('bindscancode', 'onScanCode')
specialEvents.set('bindstatechange', 'onStateChange')
specialEvents.set('bindhtouchmove', 'onHTouchMove')
specialEvents.set('bindvtouchmove', 'onVTouchMove')
specialEvents.set('bindcolumnchange', 'onColumnChange')
specialEvents.set('bindscrolltoupper', 'onScrollToUpper')
specialEvents.set('bindscrolltolower', 'onScrollToLower')
specialEvents.set('bindanimationfinish', 'onAnimationFinish')
specialEvents.set('bindfullscreenchange', 'onFullscreenChange')
specialEvents.set('bindtouchstart', 'onTouchStart')
specialEvents.set('bindtouchmove', 'onTouchMove')
specialEvents.set('bindtouchcancel', 'onTouchCancel')
specialEvents.set('bindtouchend', 'onTouchEnd')
specialEvents.set('bindlongpress', 'onLongPress')
specialEvents.set('bindlongclick', 'onLongClick')
specialEvents.set('bindtransitionend', 'onTransitionEnd')
specialEvents.set('bindanimationstart', 'onAnimationStart')
specialEvents.set('bindanimationtteration', 'onAnimationIteration')
specialEvents.set('bindanimationend', 'onAnimationEnd')
specialEvents.set('bindtouchforcechange', 'onTouchForceChange')
specialEvents.set('bindtap', 'onTouchForceChange')
specialEvents.forEach((value, key) => {
specialEvents.set(key.replace(/^bind/, 'catch'), value)
})
39 changes: 1 addition & 38 deletions packages/taroize/src/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as t from 'babel-types'
import traverse, { NodePath } from 'babel-traverse'
import { transform } from 'babel-core'
import * as template from 'babel-template'
import { buildImportStatement, codeFrameError } from './utils'
import { buildImportStatement, codeFrameError, buildRender } from './utils'
import { usedComponents, WXS } from './wxml'
import { PageLifecycle, Lifecycle } from './lifecycle'

Expand Down Expand Up @@ -97,43 +97,6 @@ export function parseScript (
return ast
}

function buildRender (
returned: t.Expression,
stateKeys: string[],
propsKeys: string[]
) {
const returnStatement: t.Statement[] = [t.returnStatement(returned)]
if (stateKeys.length) {
const stateDecl = t.variableDeclaration('const', [
t.variableDeclarator(
t.objectPattern(stateKeys.map(s =>
t.objectProperty(t.identifier(s), t.identifier(s))
) as any),
t.memberExpression(t.thisExpression(), t.identifier('state'))
)
])
returnStatement.unshift(stateDecl)
}

if (propsKeys.length) {
const stateDecl = t.variableDeclaration('const', [
t.variableDeclarator(
t.objectPattern(propsKeys.map(s =>
t.objectProperty(t.identifier(s), t.identifier(s))
) as any),
t.memberExpression(t.thisExpression(), t.identifier('props'))
)
])
returnStatement.unshift(stateDecl)
}
return t.classMethod(
'method',
t.identifier('render'),
[],
t.blockStatement(returnStatement)
)
}

const defaultClassName = '_C'

function parsePage (
Expand Down
56 changes: 56 additions & 0 deletions packages/taroize/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,62 @@ import { codeFrameColumns } from '@babel/code-frame'

export const buildTemplate = (str: string) => template(str)().expression as t.Expression

export function buildBlockElement () {
return t.jSXElement(
t.jSXOpeningElement(t.jSXIdentifier('Block'), []),
t.jSXClosingElement(t.jSXIdentifier('Block')),
[]
)
}

export function buildRender (
returned: t.Expression,
stateKeys: string[],
propsKeys: string[],
templateType?: string | never[]
) {
const returnStatement: t.Statement[] = [t.returnStatement(returned)]
if (stateKeys.length) {
const stateDecl = t.variableDeclaration('const', [
t.variableDeclarator(
t.objectPattern(stateKeys.map(s =>
t.objectProperty(t.identifier(s), t.identifier(s))
) as any),
t.memberExpression(t.thisExpression(), t.identifier('state'))
)
])
returnStatement.unshift(stateDecl)
}

if (propsKeys.length) {
let patterns = t.objectPattern(propsKeys.map(s =>
t.objectProperty(t.identifier(s), t.identifier(s))
) as any)
if (typeof templateType === 'string') {
patterns = t.objectPattern([
t.objectProperty(t.identifier('data'), t.identifier(templateType)) as any
])
} else if (Array.isArray(templateType)) {
patterns = t.objectPattern([
t.objectProperty(t.identifier('data'), patterns as any) as any
])
}
const stateDecl = t.variableDeclaration('const', [
t.variableDeclarator(
patterns,
t.memberExpression(t.thisExpression(), t.identifier('props'))
)
])
returnStatement.unshift(stateDecl)
}
return t.classMethod(
'method',
t.identifier('render'),
[],
t.blockStatement(returnStatement)
)
}

export function buildImportStatement (source: string, specifiers: string[] = [], defaultSpec?: string) {
return t.importDeclaration(
defaultSpec ? [defaultSpec, ...specifiers].map((spec, index) => {
Expand Down
131 changes: 89 additions & 42 deletions packages/taroize/src/wxml.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { parse } from 'himalaya'
import * as t from 'babel-types'
import { camelCase, cloneDeep } from 'lodash'
import { camelCase, cloneDeep, capitalize } from 'lodash'
import traverse, { NodePath } from 'babel-traverse'
import { buildTemplate, DEFAULT_Component_SET } from './utils'
import { buildTemplate, DEFAULT_Component_SET, buildRender, buildBlockElement } from './utils'
import { specialEvents } from './events'
const generate = require('babel-generator').default

const allCamelCase = (str: string) =>
str.charAt(0).toUpperCase() + camelCase(str.substr(1))
Expand Down Expand Up @@ -76,9 +78,7 @@ function buildElement (

export const usedComponents = new Set<string>()

export function parseWXML (
wxml?: string
): {
export function parseWXML (wxml?: string): {
wxses: WXS[]
wxml?: t.Node
} {
Expand Down Expand Up @@ -119,9 +119,13 @@ export function parseWXML (
if (!jsxName.isJSXIdentifier()) {
return
}
if (jsxName.node.name === 'Wxs') {
const tagName = jsxName.node.name
if (tagName === 'Wxs') {
wxses.push(getWXS(attrs.map(a => a.node), path))
}
if (tagName === 'Template') {
parseTemplate(path)
}
}
})

Expand All @@ -131,6 +135,58 @@ export function parseWXML (
}
}

function parseTemplate (path: NodePath<t.JSXElement>) {
const openingElement = path.get('openingElement')
const attrs = openingElement.get('attributes')
const is = attrs.find(attr => attr.get('name').isJSXIdentifier({ name: 'is' }))
const data = attrs.find(attr => attr.get('name').isJSXIdentifier({ name: 'data' }))
const spread = attrs.find(attr => attr.get('name').isJSXIdentifier({ name: 'spread' }))
const name = attrs.find(attr => attr.get('name').isJSXIdentifier({ name: 'name' }))
const refIds = new Set<string>()
if (name) {
const value = name.node.value
if (value === null || !t.isStringLiteral(value)) {
throw new Error('template 的 `name` 属性只能是字符串')
}
const className = capitalize(camelCase(value.value))
path.traverse({
Identifier (p) {
if (!p.isReferencedIdentifier()) {
return
}
const jsxExprContainer = p.findParent(p => p.isJSXExpressionContainer())
if (!jsxExprContainer || !jsxExprContainer.isJSXExpressionContainer()) {
return
}
refIds.add(p.node.name)
}
})

const block = buildBlockElement()
block.children = path.node.children
let render: t.ClassMethod
if (refIds.size === 0) {
// 无状态组件
render = buildRender(block, [], [])
} else if (refIds.size === 1) {
// 只有一个数据源
render = buildRender(block, [], Array.from(refIds), Array.from(refIds)[0])
} else {
// 使用 ...spread
render = buildRender(block, [], Array.from(refIds), [])
}

const classDecl = t.classDeclaration(
t.identifier(className),
t.memberExpression(t.identifier('Taro'), t.identifier('Component')),
t.classBody([render!]),
[]
)
path.remove()
return classDecl
}
}

function getWXS (attrs: t.JSXAttribute[], path: NodePath<t.JSXElement>): WXS {
let moduleName: string | null = null
let src: string | null = null
Expand Down Expand Up @@ -350,13 +406,37 @@ function parseNode (node: Node) {
return parseElement(node)
}

const expressionRE = /(?<=\(\.\.\.).+(?=(\)))/g

function parseElement (element: Element): t.JSXElement {
const tagName = t.jSXIdentifier(allCamelCase(element.tagName))
if (DEFAULT_Component_SET.has(tagName.name)) {
usedComponents.add(tagName.name)
}
let attributes = element.attributes
if (tagName.name === 'Template') {
let isSpread = false
attributes = attributes.map(attr => {
if (attr.key === 'data') {
const value = attr.value || ''
const content = parseContent(value)
if (content.type === 'expression') {
isSpread = true
const expression = expressionRE.exec(value)
attr.value = expression ? '{{expression[0]}}' : '{{item}}'
}
}
return attr
})
if (isSpread) {
attributes.push({
key: 'spread',
value: null
})
}
}
return t.jSXElement(
t.jSXOpeningElement(tagName, element.attributes.map(parseAttribute)),
t.jSXOpeningElement(tagName, attributes.map(parseAttribute)),
t.jSXClosingElement(tagName),
removEmptyTextAndComment(element.children).map(parseNode),
false
Expand Down Expand Up @@ -425,7 +505,6 @@ function parseContent (content: string) {
}
}


function parseAttribute (attr: Attribute) {
const { key, value } = attr

Expand All @@ -448,38 +527,6 @@ function parseAttribute (attr: Attribute) {
return t.jSXAttribute(t.jSXIdentifier(jsxKey), jsxValue)
}

const specialComponentProps = new Map<string, string>()

specialComponentProps.set('bindtimeupdate', 'onTimeUpdate')
specialComponentProps.set('bindgetphoneNumber', 'onGetPhoneNumber')
specialComponentProps.set('bindgetrealnameauthinfo', 'onGetRealnameAuthInfo')
specialComponentProps.set('bindopensetting', 'onOpenSetting')
specialComponentProps.set('bindscancode', 'onScanCode')
specialComponentProps.set('bindstatechange', 'onStateChange')
specialComponentProps.set('bindhtouchmove', 'onHTouchMove')
specialComponentProps.set('bindvtouchmove', 'onVTouchMove')
specialComponentProps.set('bindcolumnchange', 'onColumnChange')
specialComponentProps.set('bindscrolltoupper', 'onScrollToUpper')
specialComponentProps.set('bindscrolltolower', 'onScrollToLower')
specialComponentProps.set('bindanimationfinish', 'onAnimationFinish')
specialComponentProps.set('bindfullscreenchange', 'onFullscreenChange')
specialComponentProps.set('bindtouchstart', 'onTouchStart')
specialComponentProps.set('bindtouchmove', 'onTouchMove')
specialComponentProps.set('bindtouchcancel', 'onTouchCancel')
specialComponentProps.set('bindtouchend', 'onTouchEnd')
specialComponentProps.set('bindlongpress', 'onLongPress')
specialComponentProps.set('bindlongclick', 'onLongClick')
specialComponentProps.set('bindtransitionend', 'onTransitionEnd')
specialComponentProps.set('bindanimationstart', 'onAnimationStart')
specialComponentProps.set('bindanimationtteration', 'onAnimationIteration')
specialComponentProps.set('bindanimationend', 'onAnimationEnd')
specialComponentProps.set('bindtouchforcechange', 'onTouchForceChange')
specialComponentProps.set('bindtap', 'onTouchForceChange')

specialComponentProps.forEach((value, key) => {
specialComponentProps.set(key.replace(/^bind/, 'catch'), value)
})

function handleAttrKey (key: string) {
if (
key.startsWith('wx:') ||
Expand All @@ -490,8 +537,8 @@ function handleAttrKey (key: string) {
} else if (key === 'class') {
return 'className'
} else if (/^(bind|catch)[a-z]/.test(key)) {
if (specialComponentProps.has(key)) {
return specialComponentProps.get(key)!
if (specialEvents.has(key)) {
return specialEvents.get(key)!
} else {
key = key.replace(/^(bind|catch)[a-z]/, 'on')
return key.substr(0, 2) + key[2].toUpperCase() + key.substr(3)
Expand Down

0 comments on commit 0167dd3

Please sign in to comment.