Skip to content

Commit

Permalink
feat(transformer): 支持闭包函数组件
Browse files Browse the repository at this point in the history
  • Loading branch information
yuche committed May 9, 2019
1 parent b05ce3d commit a67f2f9
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 35 deletions.
90 changes: 59 additions & 31 deletions packages/taro-transformer-wx/src/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,43 @@ class Transformer {
}
}

private jsxClosureFuncDecl = new Set<NodePath<t.Node>>()

renameJSXClassFunc = (propName: string, methodName: string, callPath: NodePath<t.CallExpression>, args: any[], isClosure = false) => {
const parentPath = callPath.parentPath
if (parentPath.isCallExpression()) {
return
}
const callee = !isClosure
? t.memberExpression(
t.thisExpression(),
t.identifier(`_create${propName.slice(6)}Data`)
)
: t.identifier(propName)
const templateAttr = [
t.jSXAttribute(t.jSXIdentifier('is'), t.stringLiteral(propName)),
t.jSXAttribute(t.jSXIdentifier('data'), t.jSXExpressionContainer(
t.callExpression(
t.callExpression(callee, [t.binaryExpression(
'+',
methodName === 'render'
? t.memberExpression(t.thisExpression(), t.identifier('$prefix'))
: t.identifier(CLASS_COMPONENT_UID),
t.stringLiteral(createRandomLetters(10))
)]),
args
)
))
]
this.jsxClosureFuncDecl.add(parentPath)
callPath.replaceWith(t.jSXElement(
t.jSXOpeningElement(t.jSXIdentifier('Template'), templateAttr),
t.jSXClosingElement(t.jSXIdentifier('Template')),
[],
false
))
}

traverse () {
const self = this
let hasRender = false
Expand Down Expand Up @@ -430,38 +467,19 @@ class Transformer {
CallExpression: {
enter (callPath: NodePath<t.CallExpression>) {
const callee = callPath.get('callee')
if (!callee.isMemberExpression()) {
return
}
const args = callPath.node.arguments
const { object, property } = callee.node
if (t.isThisExpression(object) && t.isIdentifier(property) && property.name.startsWith('render')) {
const name = property.name
// @TODO 优化创建函数的机制,如果函数的 JSX 没有自定义组件或自定义组件里没有参数,不需要创建新函数
const templateAttr = [
t.jSXAttribute(t.jSXIdentifier('is'), t.stringLiteral(name)),
t.jSXAttribute(t.jSXIdentifier('data'), t.jSXExpressionContainer(
t.callExpression(
t.callExpression(t.memberExpression(
t.thisExpression(),
t.identifier(`_create${name.slice(6)}Data`)
), [t.binaryExpression(
'+',
methodName === 'render'
? t.memberExpression(t.thisExpression(), t.identifier('$prefix'))
: t.identifier(CLASS_COMPONENT_UID),
t.stringLiteral(createRandomLetters(10))
)]),
args
)
))
]
callPath.replaceWith(t.jSXElement(
t.jSXOpeningElement(t.jSXIdentifier('Template'), templateAttr),
t.jSXClosingElement(t.jSXIdentifier('Template')),
[],
false
))
if (callee.isMemberExpression()) {
const { object, property } = callee.node
if (t.isThisExpression(object) && t.isIdentifier(property) && property.name.startsWith('render')) {
const propName = property.name
self.renameJSXClassFunc(propName, methodName, callPath, args)
}
}
if (callee.isIdentifier()) {
const nodeName = callee.node.name
if (nodeName.startsWith('renderClosure')) {
self.renameJSXClassFunc(nodeName, methodName, callPath, args, true)
}
}
},
exit (callPath: NodePath<t.CallExpression>) {
Expand Down Expand Up @@ -970,6 +988,15 @@ class Transformer {
}
}

clearClosureMethods () {
this.classPath.node.body.body = this.classPath.node.body.body.filter(m => {
if (m && t.isClassMethod(m) && t.isIdentifier(m.key) && m.key.name.startsWith('_createClosure')) {
return false
}
return true
})
}

compile () {
this.traverse()
this.setMethods()
Expand All @@ -979,6 +1006,7 @@ class Transformer {
this.handleRefs()
this.parseRender()
this.setComponentPath()
this.clearClosureMethods()
this.result.componentProperies = [...this.componentProperies]
}
}
Expand Down
5 changes: 4 additions & 1 deletion packages/taro-transformer-wx/src/functional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ export const functionalComponent: () => {
throw codeFrameError(id, '函数式组件只能使用普通标识符定义')
}
if (!initialIsCapital(id.name)) {
return
throw codeFrameError(id, '组件命名规则请遵守帕斯卡命名法(Pascal Case)')
}
const hasClassDecl = arrowFuncExpr.findParent(p => p.isClassDeclaration())
if (hasClassDecl) {
// @TODO: 加上链接
throw codeFrameError(arrowFuncExpr.node, '在类中的函数式组件请写成类函数式组件:参考:')
return
// throw codeFrameError(arrowFuncExpr.node, '在类中的函数式组件请写成类函数式组件:参考:')
}
const { body } = arrowFuncExpr.node
if (t.isBlockStatement(body)) {
Expand All @@ -47,6 +49,7 @@ export const functionalComponent: () => {
const hasClassDecl = functionDecl.findParent(p => p.isClassDeclaration())
if (hasClassDecl) {
// @TODO: 加上链接
return
throw codeFrameError(functionDecl.node, '在类中的函数式组件请写成类函数式组件:参考:')
}
const { id, body, params } = functionDecl.node
Expand Down
29 changes: 28 additions & 1 deletion packages/taro-transformer-wx/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
} from './constant'
import { Adapters, setAdapter, Adapter } from './adapter'
import { Options, setTransformOptions, buildBabelTransformOptions } from './options'
import { get as safeGet } from 'lodash'
import { get as safeGet, cloneDeep } from 'lodash'
import { isTestEnv } from './env'

const template = require('babel-template')
Expand Down Expand Up @@ -99,6 +99,32 @@ function resetTSClassProperty (body: (t.ClassMethod | t.ClassProperty)[]) {
}
}

function handleClosureJSXFunc (jsx: NodePath<t.JSXElement>, mainClass: NodePath<t.ClassDeclaration>) {
// 在 ./functional.ts 会把 FunctionExpression 转化为 arrowFunctionExpr
// 所以我们这里只处理一种情况
const arrowFunc = jsx.findParent(p => p.isArrowFunctionExpression())
if (arrowFunc && arrowFunc.isArrowFunctionExpression()) {
const parentPath = arrowFunc.parentPath
if (parentPath.isVariableDeclarator()) {
const id = parentPath.node.id
if (t.isIdentifier(id) && id.name.startsWith('render')) {
const funcName = `renderClosure${id.name.slice(6, id.name.length)}`
mainClass.node.body.body.push(
t.classProperty(
t.identifier(funcName),
cloneDeep(arrowFunc.node)
)
)
parentPath.scope.rename(id.name, funcName)
arrowFunc.replaceWith(t.memberExpression(
t.thisExpression(),
t.identifier(funcName)
))
}
}
}
}

function findDeclarationScope (path: NodePath<t.Node>, id: t.Identifier) {
const scopePath = path.findParent(p => !!p.scope.getOwnBindingIdentifier(id.name))
if (scopePath) {
Expand Down Expand Up @@ -441,6 +467,7 @@ export default function transform (options: Options): TransformResult {
])
}
}
handleClosureJSXFunc(path, mainClass)
},
JSXOpeningElement (path) {
const { name } = path.node.name as t.JSXIdentifier
Expand Down
28 changes: 26 additions & 2 deletions packages/taro-transformer-wx/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export class RenderParser {
private isDefaultRender: boolean = false
// private renderArg: t.Identifier | t.ObjectPattern | null = null
private renderMethodName: string = ''
private deferedHandleClosureJSXFunc: Function[] = []

private renderPath: NodePath<t.ClassMethod>
private methods: ClassMethodsMap
Expand Down Expand Up @@ -1357,6 +1358,24 @@ export class RenderParser {
}

private visitors: Visitor = {
MemberExpression: (path) => {
const { object, property } = path.node
if (t.isThisExpression(object) && t.isIdentifier(property) && property.name.startsWith('renderClosure')) {
const parentPath = path.parentPath
if (parentPath.isVariableDeclarator()) {
const id = parentPath.node.id
if (t.isIdentifier(id) && id.name.startsWith('renderClosure')) {
this.deferedHandleClosureJSXFunc.push(() => {
const classMethod = this.methods.get(id.name)
if (classMethod && classMethod.isClassMethod()) {
path.replaceWith(t.arrowFunctionExpression(classMethod.node.params, classMethod.node.body))
// classMethod.node.body.body = []
}
})
}
}
}
},
VariableDeclarator: (path) => {
const init = path.get('init')
const id = path.get('id')
Expand Down Expand Up @@ -1506,6 +1525,11 @@ export class RenderParser {
this.setProperies()
}
this.setLoopRefFlag()
this.handleClosureComp()
}

private handleClosureComp () {
this.deferedHandleClosureJSXFunc.forEach(func => func())
}

private quickappVistor: Visitor = {
Expand Down Expand Up @@ -1870,8 +1894,8 @@ export class RenderParser {
},
MemberExpression (path) {
const { object, property } = path.node
if (t.isThisExpression(object) && t.isIdentifier(property, { name: 'state' })) {
if (path.parentPath.isMemberExpression() && path.parentPath.parentPath.isMemberExpression()) {
if (t.isThisExpression(object) && t.isIdentifier(property)) {
if (property.name === 'state' && path.parentPath.isMemberExpression() && path.parentPath.parentPath.isMemberExpression()) {
// tslint:disable-next-line
console.warn(
codeFrameError(path.parentPath.parentPath.node,
Expand Down

0 comments on commit a67f2f9

Please sign in to comment.