UNPKG

saven

Version:
486 lines 23 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const t = require("babel-types"); const utils_1 = require("./utils"); const constant_1 = require("./constant"); const lodash_1 = require("lodash"); const render_1 = require("./render"); const jsx_1 = require("./jsx"); const babel_generator_1 = require("babel-generator"); function buildConstructor() { const ctor = t.classMethod('constructor', t.identifier('constructor'), [t.identifier('props')], t.blockStatement([ t.expressionStatement(t.callExpression(t.identifier('super'), [ t.identifier('props') ])) ])); return ctor; } function processThisPropsFnMemberProperties(member, path, args) { const propertyArray = []; function traverseMember(member) { const object = member.object; const property = member.property; if (t.isIdentifier(property)) { propertyArray.push(property.name); } if (t.isMemberExpression(object)) { if (t.isThisExpression(object.object) && t.isIdentifier(object.property) && object.property.name === 'props') { path.replaceWith(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__triggerPropsFn')), [t.stringLiteral(propertyArray.reverse().join('.')), t.callExpression(t.memberExpression(t.arrayExpression([t.nullLiteral()]), t.identifier('concat')), [t.arrayExpression(args)])])); } traverseMember(object); } } traverseMember(member); } class Transformer { constructor(path, sourcePath, componentProperies) { this.result = { template: '', components: [], componentProperies: [] }; this.methods = new Map(); this.initState = new Set(); this.jsxReferencedIdentifiers = new Set(); this.customComponents = new Map(); this.anonymousMethod = new Map(); this.renderMethod = null; this.customComponentNames = new Set(); this.usedState = new Set(); this.loopStateName = new Map(); this.customComponentData = []; this.refs = []; this.buildAnonymousFunc = (attr, expr, isBind = false) => { const { code } = babel_generator_1.default(expr); if (code.startsWith('this.props')) { const methodName = utils_1.findMethodName(expr); const hasMethodName = this.anonymousMethod.has(methodName) || !methodName; const funcName = hasMethodName ? this.anonymousMethod.get(methodName) // 测试时使用1个稳定的 uniqueID 便于测试,实际使用5个英文字母,否则小程序不支持 : process.env.NODE_ENV === 'test' ? lodash_1.uniqueId('func__') : `func__${utils_1.createRandomLetters(5)}`; this.anonymousMethod.set(methodName, funcName); const newVal = isBind ? t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(funcName)), t.identifier('bind')), expr.arguments || []) : t.memberExpression(t.thisExpression(), t.identifier(funcName)); attr.get('value.expression').replaceWith(newVal); this.methods.set(funcName, null); this.componentProperies.add(methodName); if (hasMethodName) { return; } const attrName = attr.node.name; if (t.isJSXIdentifier(attrName) && attrName.name.startsWith('on')) { this.componentProperies.add(`__fn_${attrName.name}`); } if (methodName.startsWith('on')) { this.componentProperies.add(`__fn_${methodName}`); } const method = t.classMethod('method', t.identifier(funcName), [], t.blockStatement([ t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__triggerPropsFn')), [t.stringLiteral(methodName), t.arrayExpression([t.spreadElement(t.identifier('arguments'))])])) ])); this.classPath.node.body.body = this.classPath.node.body.body.concat(method); } }; this.classPath = path; this.sourcePath = sourcePath; this.moduleNames = Object.keys(path.scope.getAllBindings('module')); this.componentProperies = new Set(componentProperies); this.compile(); } createStringRef(componentName, id, refName) { this.refs.push({ type: constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component', id, refName }); } createFunctionRef(componentName, id, fn) { this.refs.push({ type: constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component', id, fn }); } handleRefs() { const objExpr = this.refs.map(ref => { return t.objectExpression([ t.objectProperty(t.identifier('type'), t.stringLiteral(ref.type)), t.objectProperty(t.identifier('id'), t.stringLiteral(ref.id)), t.objectProperty(t.identifier('refName'), t.stringLiteral(ref.refName || '')), t.objectProperty(t.identifier('fn'), ref.fn ? ref.fn : t.nullLiteral()) ]); }); this.classPath.node.body.body.push(t.classProperty(t.identifier('$$refs'), t.arrayExpression(objExpr))); } traverse() { const self = this; self.classPath.traverse({ JSXOpeningElement: (path) => { const jsx = path.node; const attrs = jsx.attributes; if (!t.isJSXIdentifier(jsx.name)) { return; } const componentName = jsx.name.name; const refAttr = jsx_1.findJSXAttrByName(attrs, 'ref'); if (!refAttr) { return; } const idAttr = jsx_1.findJSXAttrByName(attrs, 'id'); let id = utils_1.createRandomLetters(5); if (!idAttr) { attrs.push(t.jSXAttribute(t.jSXIdentifier('id'), t.stringLiteral(id))); } else { const idValue = idAttr.value; if (t.isStringLiteral(idValue)) { id = idValue.value; } else if (t.isJSXExpressionContainer(idValue) && t.isStringLiteral(idValue.expression)) { id = idValue.expression.value; } } if (t.isStringLiteral(refAttr.value)) { this.createStringRef(componentName, id, refAttr.value.value); } if (t.isJSXExpressionContainer(refAttr.value)) { const expr = refAttr.value.expression; if (t.isStringLiteral(expr)) { this.createStringRef(componentName, id, expr.value); } else if (t.isArrowFunctionExpression(expr) || t.isMemberExpression(expr)) { this.refs.push({ type: constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component', id, fn: expr }); } else { throw utils_1.codeFrameError(refAttr, 'ref 仅支持传入字符串、匿名箭头函数和 class 中已声明的函数'); } } for (const [index, attr] of attrs.entries()) { if (attr === refAttr) { attrs.splice(index, 1); } } }, ClassMethod(path) { const node = path.node; if (t.isIdentifier(node.key)) { const name = node.key.name; self.methods.set(name, path); if (name === 'render') { self.renderMethod = path; path.traverse({ ReturnStatement(returnPath) { const arg = returnPath.node.argument; const ifStem = returnPath.findParent(p => p.isIfStatement()); if (ifStem && ifStem.isIfStatement() && arg === null) { const consequent = ifStem.get('consequent'); if (consequent.isBlockStatement() && consequent.node.body.includes(returnPath.node)) { returnPath.get('argument').replaceWith(t.nullLiteral()); } } } }); } if (name === 'constructor') { path.traverse({ AssignmentExpression(p) { if (t.isMemberExpression(p.node.left) && t.isThisExpression(p.node.left.object) && t.isIdentifier(p.node.left.property) && p.node.left.property.name === 'state' && t.isObjectExpression(p.node.right)) { const properties = p.node.right.properties; properties.forEach(p => { if (t.isObjectProperty(p) && t.isIdentifier(p.key)) { self.initState.add(p.key.name); } }); } } }); } } }, IfStatement(path) { const test = path.get('test'); const consequent = path.get('consequent'); if (utils_1.isContainJSXElement(consequent) && utils_1.hasComplexExpression(test)) { const scope = self.renderMethod && self.renderMethod.scope || path.scope; utils_1.generateAnonymousState(scope, test, self.jsxReferencedIdentifiers, true); } }, ClassProperty(path) { const { key: { name }, value } = path.node; if (t.isArrowFunctionExpression(value) || t.isFunctionExpression(value)) { self.methods.set(name, path); } if (name === 'state' && t.isObjectExpression(value)) { value.properties.forEach(p => { if (t.isObjectProperty(p)) { if (t.isIdentifier(p.key)) { self.initState.add(p.key.name); } } }); } }, JSXExpressionContainer(path) { path.traverse({ MemberExpression(path) { const sibling = path.getSibling('property'); if (path.get('object').isThisExpression() && path.get('property').isIdentifier({ name: 'props' }) && sibling.isIdentifier()) { const attr = path.findParent(p => p.isJSXAttribute()); const isFunctionProp = attr && typeof attr.node.name.name === 'string' && attr.node.name.name.startsWith('on'); if (!isFunctionProp) { self.usedState.add(sibling.node.name); } } } }); const expression = path.get('expression'); const scope = self.renderMethod && self.renderMethod.scope || path.scope; const calleeExpr = expression.get('callee'); if (utils_1.hasComplexExpression(expression) && !(calleeExpr && calleeExpr.isMemberExpression() && calleeExpr.get('object').isMemberExpression() && calleeExpr.get('property').isIdentifier({ name: 'bind' })) // is not bind ) { utils_1.generateAnonymousState(scope, expression, self.jsxReferencedIdentifiers); } const attr = path.findParent(p => p.isJSXAttribute()); if (!attr) return; const key = attr.node.name; const value = attr.node.value; if (t.isJSXIdentifier(key) && key.name.startsWith('on') && t.isJSXExpressionContainer(value)) { const expr = value.expression; if (t.isCallExpression(expr) && t.isMemberExpression(expr.callee) && t.isIdentifier(expr.callee.property, { name: 'bind' })) { self.buildAnonymousFunc(attr, expr, true); } else if (t.isMemberExpression(expr)) { self.buildAnonymousFunc(attr, expr, false); } else { throw utils_1.codeFrameError(expr.loc, '组件事件传参只能在类作用域下的确切引用(this.handleXX || this.props.handleXX),或使用 bind。'); } } }, JSXElement(path) { const id = path.node.openingElement.name; if (t.isJSXIdentifier(id) && !constant_1.DEFAULT_Component_SET.has(id.name) && self.moduleNames.indexOf(id.name) !== -1) { const name = id.name; const binding = self.classPath.scope.getBinding(name); if (binding && t.isImportDeclaration(binding.path.parent)) { const sourcePath = binding.path.parent.source.value; if (binding.path.isImportDefaultSpecifier()) { self.customComponents.set(name, { sourcePath, type: 'default' }); } else { self.customComponents.set(name, { sourcePath, type: 'pattern' }); } } } }, MemberExpression(path) { const object = path.get('object'); const property = path.get('property'); if (!(object.isThisExpression() && property.isIdentifier({ name: 'props' }))) { return; } const parentPath = path.parentPath; if (parentPath.isMemberExpression()) { const siblingProp = parentPath.get('property'); if (siblingProp.isIdentifier()) { const name = siblingProp.node.name; if (name === 'children') { parentPath.replaceWith(t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('slot'), [], true), t.jSXClosingElement(t.jSXIdentifier('slot')), [], true)); } else { self.componentProperies.add(siblingProp.node.name); } } } else if (parentPath.isVariableDeclarator()) { const siblingId = parentPath.get('id'); if (siblingId.isObjectPattern()) { const properties = siblingId.node.properties; for (const prop of properties) { if (t.isRestProperty(prop)) { throw utils_1.codeFrameError(prop.loc, 'this.props 不支持使用 rest property 语法,请把每一个 prop 都单独列出来'); } else if (t.isIdentifier(prop.key)) { self.componentProperies.add(prop.key.name); } } } } }, CallExpression(path) { const node = path.node; const callee = node.callee; if (t.isMemberExpression(callee) && t.isMemberExpression(callee.object)) { const property = callee.property; if (t.isIdentifier(property)) { if (property.name.startsWith('on')) { self.componentProperies.add(`__fn_${property.name}`); processThisPropsFnMemberProperties(callee, path, node.arguments); } else if (property.name === 'call' || property.name === 'apply') { self.componentProperies.add(`__fn_${property.name}`); processThisPropsFnMemberProperties(callee.object, path, node.arguments); } } } } }); } setComponents() { this.customComponents.forEach((component, name) => { this.result.components.push({ path: utils_1.pathResolver(component.sourcePath, this.sourcePath), name: lodash_1.kebabCase(name), type: component.type }); }); } resetConstructor() { const body = this.classPath.node.body.body; if (!this.methods.has('constructor')) { const ctor = buildConstructor(); body.unshift(ctor); } if (process.env.NODE_ENV === 'test') { return; } for (const method of body) { if (t.isClassMethod(method) && method.kind === 'constructor') { method.kind = 'method'; method.key = t.identifier('_constructor'); if (t.isBlockStatement(method.body)) { for (const statement of method.body.body) { if (t.isExpressionStatement(statement)) { const expr = statement.expression; if (t.isCallExpression(expr) && (t.isIdentifier(expr.callee, { name: 'super' }) || t.isSuper(expr.callee))) { expr.callee = t.memberExpression(t.identifier('super'), t.identifier('_constructor')); } } } } } } } handleLifecyclePropParam(propParam, properties) { let propsName = null; if (!propParam) { return null; } if (t.isIdentifier(propParam)) { propsName = propParam.name; } else if (t.isObjectPattern(propParam)) { for (const prop of propParam.properties) { if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) { properties.add(prop.key.name); } else if (t.isRestProperty(prop) && t.isIdentifier(prop.argument)) { propsName = prop.argument.name; } } } else { throw utils_1.codeFrameError(propParam.loc, '此生命周期的第一个参数只支持写标识符或对象解构'); } return propsName; } findMoreProps() { // 第一个参数是 props 的生命周期 const lifeCycles = new Set([ // 'constructor', 'componentDidUpdate', 'shouldComponentUpdate', 'getDerivedStateFromProps', 'getSnapshotBeforeUpdate', 'componentWillReceiveProps', 'componentWillUpdate' ]); const properties = new Set(); this.methods.forEach((method, name) => { if (!lifeCycles.has(name)) { return; } const node = method.node; let propsName = null; if (t.isClassMethod(node)) { propsName = this.handleLifecyclePropParam(node.params[0], properties); } else if (t.isArrowFunctionExpression(node.value) || t.isFunctionExpression(node.value)) { propsName = this.handleLifecyclePropParam(node.value.params[0], properties); } if (propsName === null) { return; } method.traverse({ MemberExpression(path) { if (!path.isReferencedMemberExpression()) { return; } const { object, property } = path.node; if (t.isIdentifier(object, { name: propsName }) && t.isIdentifier(property)) { properties.add(property.name); } }, VariableDeclarator(path) { const { id, init } = path.node; if (t.isObjectPattern(id) && t.isIdentifier(init, { name: propsName })) { for (const prop of id.properties) { if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) { properties.add(prop.key.name); } } } } }); properties.forEach((value) => { this.componentProperies.add(value); }); }); } parseRender() { if (this.renderMethod) { this.result.template = this.result.template + new render_1.RenderParser(this.renderMethod, this.methods, this.initState, this.jsxReferencedIdentifiers, this.usedState, this.loopStateName, this.customComponentNames, this.customComponentData, this.componentProperies).outputTemplate; } else { throw utils_1.codeFrameError(this.classPath.node.loc, '没有定义 render 方法'); } } compile() { this.traverse(); this.setComponents(); this.resetConstructor(); this.findMoreProps(); this.handleRefs(); this.parseRender(); this.result.componentProperies = [...this.componentProperies]; } } exports.Transformer = Transformer; //# sourceMappingURL=class.js.map