1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const t = require("babel-types");
|
4 | const path_1 = require("path");
|
5 | const utils_1 = require("./utils");
|
6 | const constant_1 = require("./constant");
|
7 | const lodash_1 = require("lodash");
|
8 | const render_1 = require("./render");
|
9 | const jsx_1 = require("./jsx");
|
10 | const adapter_1 = require("./adapter");
|
11 | const babel_generator_1 = require("babel-generator");
|
12 | const env_1 = require("./env");
|
13 | const functional_1 = require("./functional");
|
14 | const render_props_1 = require("./render-props");
|
15 | const stopPropagationExpr = require('babel-template')(`typeof e === 'object' && e.stopPropagation && e.stopPropagation()`);
|
16 | function buildConstructor() {
|
17 | const ctor = t.classMethod('constructor', t.identifier('constructor'), [t.identifier('props')], t.blockStatement([
|
18 | t.expressionStatement(t.callExpression(t.identifier('super'), [
|
19 | t.identifier('props')
|
20 | ]))
|
21 | ]));
|
22 | return ctor;
|
23 | }
|
24 | function processThisPropsFnMemberProperties(member, path, args) {
|
25 | const propertyArray = [];
|
26 | function traverseMember(member) {
|
27 | const object = member.object;
|
28 | const property = member.property;
|
29 | if (t.isIdentifier(property)) {
|
30 | propertyArray.push(property.name);
|
31 | }
|
32 | if (t.isMemberExpression(object)) {
|
33 | if (t.isThisExpression(object.object) &&
|
34 | t.isIdentifier(object.property) &&
|
35 | object.property.name === 'props') {
|
36 | if (!adapter_1.isNewPropsSystem()) {
|
37 | 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)])]));
|
38 | }
|
39 | }
|
40 | traverseMember(object);
|
41 | }
|
42 | }
|
43 | traverseMember(member);
|
44 | }
|
45 | class Transformer {
|
46 | constructor(path, sourcePath, componentProperies, sourceDir, methods) {
|
47 | this.result = {
|
48 | template: '',
|
49 | components: [],
|
50 | componentProperies: []
|
51 | };
|
52 | this.renderJSX = new Map();
|
53 | this.refIdMap = new Map();
|
54 | this.initState = new Set();
|
55 | this.customComponents = new Map();
|
56 | this.anonymousMethod = new Map();
|
57 | this.customComponentNames = new Set();
|
58 | this.usedState = new Set();
|
59 | this.refs = [];
|
60 | this.loopRefs = new Map();
|
61 | this.anonymousFuncCounter = utils_1.incrementId();
|
62 | this.importJSXs = new Set();
|
63 | this.refObjExpr = [];
|
64 | this.buildAnonyMousFunc = (jsxExpr, attr, expr) => {
|
65 | const exprPath = attr.get('value.expression');
|
66 | const stemParent = jsxExpr.getStatementParent();
|
67 | const counter = this.anonymousFuncCounter();
|
68 | const anonymousFuncName = `${constant_1.ANONYMOUS_FUNC}${counter}`;
|
69 | const isCatch = utils_1.isContainStopPropagation(exprPath);
|
70 | const classBody = this.classPath.node.body.body;
|
71 | const loopCallExpr = jsxExpr.findParent(p => utils_1.isArrayMapCallExpression(p));
|
72 | let index;
|
73 | const self = this;
|
74 | if (loopCallExpr) {
|
75 | index = lodash_1.get(loopCallExpr, 'node.arguments[0].params[1]');
|
76 | if (!t.isIdentifier(index)) {
|
77 | index = t.identifier('__index' + counter);
|
78 | lodash_1.set(loopCallExpr, 'node.arguments[0].params[1]', index);
|
79 | }
|
80 | classBody.push(t.classProperty(t.identifier(anonymousFuncName + 'Map'), t.objectExpression([])));
|
81 | const indexKey = stemParent.scope.generateUid('$indexKey');
|
82 |
|
83 | function findParentLoopCallExprIndices(callExpr) {
|
84 | const indices = new Set([]);
|
85 |
|
86 | while (callExpr = callExpr.findParent(p => utils_1.isArrayMapCallExpression(p) && p !== callExpr)) {
|
87 | let index = lodash_1.get(callExpr, 'node.arguments[0].params[1]');
|
88 | if (!t.isIdentifier(index)) {
|
89 | index = t.identifier('__index' + self.anonymousFuncCounter());
|
90 | lodash_1.set(callExpr, 'node.arguments[0].params[1]', index);
|
91 | }
|
92 | indices.add(index);
|
93 | }
|
94 | return indices;
|
95 | }
|
96 | const indices = [...findParentLoopCallExprIndices(loopCallExpr)].reverse();
|
97 | const indexKeyDecl = t.variableDeclaration('const', [t.variableDeclarator(t.identifier(indexKey), indices.length === 0
|
98 | ? t.binaryExpression('+', t.stringLiteral(utils_1.createRandomLetters(5)), index)
|
99 | : t.templateLiteral([
|
100 | t.templateElement({ raw: utils_1.createRandomLetters(5) }),
|
101 | ...indices.map(() => t.templateElement({ raw: '-' })),
|
102 | t.templateElement({ raw: '' })
|
103 | ], [
|
104 | ...indices.map(i => t.identifier(i.name)),
|
105 | index
|
106 | ]))]);
|
107 | const func = loopCallExpr.node.arguments[0];
|
108 | if (t.isArrowFunctionExpression(func)) {
|
109 | const body = loopCallExpr.get('arguments')[0].get('body.body');
|
110 | if (!t.isBlockStatement(func.body)) {
|
111 | func.body = t.blockStatement([
|
112 | indexKeyDecl,
|
113 | t.returnStatement(func.body)
|
114 | ]);
|
115 | }
|
116 | else {
|
117 |
|
118 |
|
119 | body[body.length - 1].insertBefore(indexKeyDecl);
|
120 | }
|
121 | const arrayFunc = t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(anonymousFuncName + 'Map')), t.identifier(indexKey), true);
|
122 | classBody.push(t.classMethod('method', t.identifier(anonymousFuncName), [t.identifier(indexKey), t.restElement(t.identifier('e'))], t.blockStatement([
|
123 | isCatch ? stopPropagationExpr() : t.emptyStatement(),
|
124 | t.returnStatement(t.logicalExpression('&&', arrayFunc, t.callExpression(arrayFunc, [t.spreadElement(t.identifier('e'))])))
|
125 | ])));
|
126 | exprPath.replaceWith(t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(anonymousFuncName)), t.identifier('bind')), [t.thisExpression(), t.identifier(indexKey)]));
|
127 | body[body.length - 1].insertBefore(t.expressionStatement(t.assignmentExpression('=', arrayFunc, expr)));
|
128 | }
|
129 | else {
|
130 | throw utils_1.codeFrameError(func, '返回 JSX 的循环语句必须使用箭头函数');
|
131 | }
|
132 | }
|
133 | else {
|
134 | classBody.push(t.classMethod('method', t.identifier(anonymousFuncName), [t.identifier('e')], t.blockStatement([
|
135 | isCatch ? t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('e'), t.identifier('stopPropagation')), [])) : t.emptyStatement()
|
136 | ])));
|
137 | exprPath.replaceWith(t.memberExpression(t.thisExpression(), t.identifier(anonymousFuncName)));
|
138 | stemParent.insertBefore(t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), t.identifier(anonymousFuncName)), expr)));
|
139 | }
|
140 | };
|
141 | this.jsxClosureFuncDecl = new Set();
|
142 | this.renameJSXClassFunc = (propName, methodName, callPath, args, isClosure = false) => {
|
143 | const parentPath = callPath.parentPath;
|
144 | if (parentPath.isCallExpression()) {
|
145 | return;
|
146 | }
|
147 | const callee = !isClosure
|
148 | ? t.memberExpression(t.thisExpression(), t.identifier(`_create${propName.slice(6)}Data`))
|
149 | : t.identifier(propName);
|
150 | const templateAttr = [
|
151 | t.jSXAttribute(t.jSXIdentifier('is'), t.stringLiteral(propName)),
|
152 | t.jSXAttribute(t.jSXIdentifier('data'), t.jSXExpressionContainer(t.callExpression(t.callExpression(callee, [t.binaryExpression('+', methodName === 'render'
|
153 | ? t.identifier('__prefix')
|
154 | : t.identifier(constant_1.CLASS_COMPONENT_UID), t.stringLiteral(utils_1.createRandomLetters(10)))]), args)))
|
155 | ];
|
156 | this.jsxClosureFuncDecl.add(parentPath);
|
157 | callPath.replaceWith(t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('Template'), templateAttr), t.jSXClosingElement(t.jSXIdentifier('Template')), [], false));
|
158 | };
|
159 | this.buildPropsAnonymousFunc = (attr, expr, isBind = false, path) => {
|
160 | const { code } = babel_generator_1.default(expr);
|
161 | const id = t.isMemberExpression(expr.callee) ? utils_1.findFirstIdentifierFromMemberExpression(expr.callee) : null;
|
162 | if (code.startsWith('this.props') ||
|
163 | (id && utils_1.isDerivedFromProps(attr.scope, id.name))) {
|
164 | const methodName = utils_1.findMethodName(expr);
|
165 | const uniqueMethodName = `${methodName}${String(isBind)}`;
|
166 | const hasMethodName = this.anonymousMethod.has(uniqueMethodName) || !methodName;
|
167 | const funcName = hasMethodName
|
168 | ? this.anonymousMethod.get(uniqueMethodName)
|
169 |
|
170 | : env_1.isTestEnv ? lodash_1.uniqueId('funPrivate') : `funPrivate${utils_1.createRandomLetters(5)}`;
|
171 | this.anonymousMethod.set(uniqueMethodName, funcName);
|
172 | const newVal = isBind
|
173 | ? t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(funcName)), t.identifier('bind')), expr.arguments || [])
|
174 | : t.memberExpression(t.thisExpression(), t.identifier(funcName));
|
175 | attr.get('value.expression').replaceWith(newVal);
|
176 | this.methods.set(funcName, null);
|
177 | this.componentProperies.add(methodName);
|
178 | if (hasMethodName) {
|
179 | return;
|
180 | }
|
181 | const attrName = attr.node.name;
|
182 | if (t.isJSXIdentifier(attrName) && attrName.name.startsWith('on')) {
|
183 | this.componentProperies.add(`${constant_1.FN_PREFIX}${attrName.name}`);
|
184 | }
|
185 | if (methodName.startsWith('on')) {
|
186 | this.componentProperies.add(`${constant_1.FN_PREFIX}${methodName}`);
|
187 | }
|
188 | const method = !adapter_1.isNewPropsSystem() ?
|
189 | t.classMethod('method', t.identifier(funcName), [], t.blockStatement([
|
190 | t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__triggerPropsFn')), [t.stringLiteral(methodName), t.arrayExpression([t.spreadElement(t.identifier('arguments'))])]))
|
191 | ])) :
|
192 | t.classMethod('method', t.identifier(funcName), [], t.blockStatement([
|
193 | t.returnStatement(t.callExpression(t.memberExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier('props')), t.identifier(methodName)), t.identifier('apply')), [
|
194 | isBind ? t.identifier('this') : t.identifier('undefined'),
|
195 | t.callExpression(t.memberExpression(t.memberExpression(t.memberExpression(t.identifier('Array'), t.identifier('prototype')), t.identifier('slice')), t.identifier('call')), [t.identifier('arguments'), t.numericLiteral(1)])
|
196 | ]))
|
197 | ]));
|
198 | this.classPath.node.body.body = this.classPath.node.body.body.concat(method);
|
199 | }
|
200 | else if (t.isMemberExpression(expr) && !t.isThisExpression(expr.object)) {
|
201 |
|
202 | this.buildAnonyMousFunc(path, attr, expr);
|
203 | }
|
204 | };
|
205 | this.classPath = path;
|
206 | this.sourcePath = sourcePath;
|
207 | this.sourceDir = sourceDir;
|
208 | this.moduleNames = Object.keys(path.scope.getAllBindings('module'));
|
209 | this.componentProperies = new Set(componentProperies);
|
210 | this.methods = methods;
|
211 | this.compile();
|
212 | }
|
213 | setMultipleSlots() {
|
214 | const body = this.classPath.node.body.body;
|
215 | if (body.some(c => t.isClassProperty(c) && c.key.name === 'multipleSlots')) {
|
216 | return;
|
217 | }
|
218 | const multipleSlots = t.classProperty(t.identifier('multipleSlots'), t.booleanLiteral(true));
|
219 | multipleSlots.static = true;
|
220 | body.push(multipleSlots);
|
221 | }
|
222 | createStringRef(componentName, id, refName) {
|
223 | this.refs.push({
|
224 | type: constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component',
|
225 | id,
|
226 | refName
|
227 | });
|
228 | }
|
229 | createFunctionRef(componentName, id, fn) {
|
230 | this.refs.push({
|
231 | type: constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component',
|
232 | id,
|
233 | fn
|
234 | });
|
235 | }
|
236 | handleRefs() {
|
237 | this.refObjExpr = this.refs.map(ref => {
|
238 | return t.objectExpression([
|
239 | t.objectProperty(t.identifier('type'), t.stringLiteral(ref.type)),
|
240 | t.objectProperty(t.identifier('id'), t.stringLiteral(ref.id)),
|
241 | t.objectProperty(t.identifier('refName'), t.stringLiteral(ref.refName || '')),
|
242 | t.objectProperty(t.identifier('fn'), ref.fn ? ref.fn : t.nullLiteral())
|
243 | ]);
|
244 | });
|
245 | const _constructor = this.classPath.node.body.body.find(item => {
|
246 | const constructorName = env_1.isTestEnv ? 'constructor' : '_constructor';
|
247 | if (t.isClassMethod(item) && t.isIdentifier(item.key) && item.key.name === constructorName) {
|
248 | return true;
|
249 | }
|
250 | return false;
|
251 | });
|
252 | if (_constructor && t.isClassMethod(_constructor) && adapter_1.Adapter.type !== "quickapp" ) {
|
253 | _constructor.body.body.push(t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), t.identifier('$$refs')), t.newExpression(t.memberExpression(t.identifier('Taro'), t.identifier('RefsArray')), []))));
|
254 | }
|
255 | }
|
256 | setComponentPath() {
|
257 | let componentPath = this.sourcePath.replace(this.sourceDir, '');
|
258 | componentPath = componentPath.replace(path_1.extname(componentPath), '');
|
259 | componentPath = componentPath.split(path_1.sep).join('/');
|
260 | if (componentPath.startsWith('/')) {
|
261 | componentPath = componentPath.slice(1);
|
262 | }
|
263 | const $$componentPath = t.classProperty(t.identifier('$$componentPath'), t.stringLiteral(componentPath));
|
264 | $$componentPath.static = true;
|
265 | this.classPath.node.body.body.push($$componentPath);
|
266 | }
|
267 | traverse() {
|
268 | const self = this;
|
269 | let hasRender = false;
|
270 | self.classPath.traverse({
|
271 | JSXOpeningElement: (path) => {
|
272 | const jsx = path.node;
|
273 | const attrs = jsx.attributes;
|
274 | if (!t.isJSXIdentifier(jsx.name)) {
|
275 | return;
|
276 | }
|
277 | const loopCallExpr = path.findParent(p => utils_1.isArrayMapCallExpression(p));
|
278 | const componentName = jsx.name.name;
|
279 | const refAttr = jsx_1.findJSXAttrByName(attrs, 'ref');
|
280 | if (!refAttr) {
|
281 | return;
|
282 | }
|
283 | const idAttr = jsx_1.findJSXAttrByName(attrs, 'id');
|
284 | let id = utils_1.createRandomLetters(5);
|
285 | let idExpr;
|
286 | if (!idAttr) {
|
287 | if (loopCallExpr && loopCallExpr.isCallExpression()) {
|
288 | const [func] = loopCallExpr.node.arguments;
|
289 | let indexId = null;
|
290 | if (t.isFunctionExpression(func) || t.isArrowFunctionExpression(func)) {
|
291 | const params = func.params;
|
292 | indexId = params[1];
|
293 | }
|
294 | if (indexId === null || !t.isIdentifier(indexId)) {
|
295 | throw utils_1.codeFrameError(path.node, '在循环中使用 ref 必须暴露循环的第二个参数 `index`');
|
296 | }
|
297 | attrs.push(t.jSXAttribute(t.jSXIdentifier('id'), t.jSXExpressionContainer(t.binaryExpression('+', t.stringLiteral(id), indexId))));
|
298 | }
|
299 | else {
|
300 | attrs.push(t.jSXAttribute(t.jSXIdentifier('id'), t.stringLiteral(id)));
|
301 | }
|
302 | }
|
303 | else {
|
304 | const idValue = idAttr.value;
|
305 | if (t.isStringLiteral(idValue)) {
|
306 | id = idValue.value;
|
307 | }
|
308 | else if (t.isJSXExpressionContainer(idValue)) {
|
309 | if (t.isStringLiteral(idValue.expression)) {
|
310 | id = idValue.expression.value;
|
311 | }
|
312 | else {
|
313 | idExpr = idValue.expression;
|
314 | }
|
315 | }
|
316 | }
|
317 | if (t.isStringLiteral(refAttr.value)) {
|
318 | if (loopCallExpr) {
|
319 | throw utils_1.codeFrameError(refAttr, '循环中的 ref 只能使用函数。');
|
320 | }
|
321 | this.createStringRef(componentName, id, refAttr.value.value);
|
322 | }
|
323 | if (t.isJSXExpressionContainer(refAttr.value)) {
|
324 | const expr = refAttr.value.expression;
|
325 | if (t.isStringLiteral(expr)) {
|
326 | if (loopCallExpr) {
|
327 | throw utils_1.codeFrameError(refAttr, '循环中的 ref 只能使用函数。');
|
328 | }
|
329 | this.createStringRef(componentName, id, expr.value);
|
330 | }
|
331 | else if (t.isArrowFunctionExpression(expr) || t.isMemberExpression(expr)) {
|
332 | const type = constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component';
|
333 | if (loopCallExpr) {
|
334 | this.loopRefs.set(path.parentPath.node, {
|
335 | id: idExpr || id,
|
336 | fn: expr,
|
337 | type,
|
338 | component: path.parentPath
|
339 | });
|
340 | }
|
341 | else {
|
342 | this.refs.push({
|
343 | type,
|
344 | id,
|
345 | fn: expr
|
346 | });
|
347 | }
|
348 | }
|
349 | else if (t.isIdentifier(expr)) {
|
350 | const type = constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component';
|
351 | const binding = path.scope.getBinding(expr.name);
|
352 | const decl = t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), expr), expr));
|
353 | if (binding) {
|
354 | binding.path.parentPath.insertAfter(decl);
|
355 | }
|
356 | else {
|
357 | path.getStatementParent().insertBefore(decl);
|
358 | }
|
359 | this.refs.push({
|
360 | type,
|
361 | id,
|
362 | fn: t.memberExpression(t.thisExpression(), expr)
|
363 | });
|
364 | }
|
365 | else {
|
366 | throw utils_1.codeFrameError(refAttr, 'ref 仅支持传入字符串、匿名箭头函数和 class 中已声明的函数');
|
367 | }
|
368 | }
|
369 | if ("alipay" === adapter_1.Adapter.type) {
|
370 | attrs.push(t.jSXAttribute(t.jSXIdentifier('onTaroCollectChilds'), t.jSXExpressionContainer(t.memberExpression(t.thisExpression(), t.identifier('$collectChilds')))));
|
371 | }
|
372 | for (const [index, attr] of attrs.entries()) {
|
373 | if (attr === refAttr) {
|
374 | attrs.splice(index, 1);
|
375 | }
|
376 | }
|
377 | },
|
378 | ClassMethod(classMethodPath) {
|
379 | const node = classMethodPath.node;
|
380 | if (t.isIdentifier(node.key)) {
|
381 | const methodName = node.key.name;
|
382 | self.methods.set(methodName, classMethodPath);
|
383 | if (methodName.startsWith('render')) {
|
384 | if (!utils_1.isContainJSXElement(classMethodPath)) {
|
385 | throw utils_1.codeFrameError(classMethodPath.node, '以 render 开头的类函数必须返回 JSX,否则会导致渲染失败。如果是为了渲染字符串,建议更名。\n' +
|
386 | '以 VSCode 为例:右键点击选择方法名,点击 rename symbol(重命名符号),输入新方法名。');
|
387 | }
|
388 | hasRender = true;
|
389 | self.renderJSX.set(methodName, classMethodPath);
|
390 | self.refIdMap.set(classMethodPath, new Set([]));
|
391 | classMethodPath.traverse({
|
392 | ReturnStatement(returnPath) {
|
393 | const arg = returnPath.node.argument;
|
394 | const ifStem = returnPath.findParent(p => p.isIfStatement());
|
395 |
|
396 | if (ifStem && classMethodPath.node.body.body.some(s => s === ifStem.node) && ifStem.isIfStatement() && arg === null) {
|
397 | const consequent = ifStem.get('consequent');
|
398 | if (consequent.isBlockStatement() && consequent.node.body.includes(returnPath.node)) {
|
399 | returnPath.get('argument').replaceWith(t.nullLiteral());
|
400 | }
|
401 | }
|
402 | },
|
403 | CallExpression: {
|
404 | enter(callPath) {
|
405 | const callee = callPath.get('callee');
|
406 | const args = callPath.node.arguments;
|
407 | if (callee.isMemberExpression()) {
|
408 | const { object, property } = callee.node;
|
409 | if (t.isThisExpression(object) && t.isIdentifier(property) && property.name.startsWith('render')) {
|
410 | const propName = property.name;
|
411 | if (!self.methods.has(propName)) {
|
412 | const o = utils_1.getSuperClassPath(self.classPath);
|
413 | if (o) {
|
414 | const p = o.resolvePath.endsWith('.js') ? o.resolvePath.slice(0, o.resolvePath.length - 3) : o.resolvePath;
|
415 | self.importJSXs.add(`<import src="${p + '.wxml'}"/>`);
|
416 | }
|
417 | }
|
418 | self.renameJSXClassFunc(propName, methodName, callPath, args);
|
419 | }
|
420 | }
|
421 | if (callee.isIdentifier()) {
|
422 | const nodeName = callee.node.name;
|
423 | if (nodeName.startsWith('renderClosure')) {
|
424 | self.renameJSXClassFunc(nodeName, methodName, callPath, args, true);
|
425 | }
|
426 | }
|
427 | },
|
428 | exit(callPath) {
|
429 | const jsxExpr = callPath.parentPath;
|
430 | if (!jsxExpr.isJSXExpressionContainer()) {
|
431 | return;
|
432 | }
|
433 | const jsxAttr = jsxExpr.parentPath;
|
434 | if (!jsxAttr.isJSXAttribute()) {
|
435 | return;
|
436 | }
|
437 | const { name: attrName } = jsxAttr.node;
|
438 | if (!t.isJSXIdentifier(attrName, { name: 'data' })) {
|
439 | return;
|
440 | }
|
441 | utils_1.generateAnonymousState(callPath.scope, callPath, self.refIdMap.get(classMethodPath));
|
442 | }
|
443 | }
|
444 | });
|
445 | }
|
446 | if (methodName.startsWith('render')) {
|
447 | self.renderJSX.set(methodName, classMethodPath);
|
448 | self.refIdMap.set(classMethodPath, new Set([]));
|
449 | }
|
450 | if (methodName === 'constructor') {
|
451 | classMethodPath.traverse({
|
452 | AssignmentExpression(p) {
|
453 | if (t.isMemberExpression(p.node.left) &&
|
454 | t.isThisExpression(p.node.left.object) &&
|
455 | t.isIdentifier(p.node.left.property) &&
|
456 | p.node.left.property.name === 'state' &&
|
457 | t.isObjectExpression(p.node.right)) {
|
458 | const properties = p.node.right.properties;
|
459 | properties.forEach(p => {
|
460 | if (t.isObjectProperty(p) && t.isIdentifier(p.key)) {
|
461 | self.initState.add(p.key.name);
|
462 | }
|
463 | });
|
464 | }
|
465 | }
|
466 | });
|
467 | }
|
468 | }
|
469 | },
|
470 | ClassBody: {
|
471 | exit(path) {
|
472 | const node = path.node;
|
473 | if (!hasRender) {
|
474 | node.body.push(t.classMethod('method', t.identifier('_createData'), [], t.blockStatement([])));
|
475 | }
|
476 | }
|
477 | },
|
478 | IfStatement: (path) => {
|
479 | const test = path.get('test');
|
480 | const consequent = path.get('consequent');
|
481 | if (utils_1.isContainJSXElement(consequent) && utils_1.hasComplexExpression(test)) {
|
482 | this.renderJSX.forEach(method => {
|
483 | const renderMethod = path.findParent(p => method === p);
|
484 | if (renderMethod && renderMethod.isClassMethod()) {
|
485 | const scope = renderMethod && renderMethod.scope || path.scope;
|
486 | utils_1.generateAnonymousState(scope, test, this.refIdMap.get(renderMethod), true);
|
487 | }
|
488 | });
|
489 | }
|
490 | },
|
491 | ClassProperty(path) {
|
492 | const { key: { name }, value } = path.node;
|
493 | if (t.isArrowFunctionExpression(value) || t.isFunctionExpression(value)) {
|
494 | self.methods.set(name, path);
|
495 | if (name.startsWith('render')) {
|
496 | path.replaceWith(t.classMethod('method', t.identifier(name), value.params, t.isBlockStatement(value.body) ? value.body : t.blockStatement([
|
497 | t.returnStatement(value.body)
|
498 | ])));
|
499 | }
|
500 | }
|
501 | if (name === 'state' && t.isObjectExpression(value)) {
|
502 | value.properties.forEach(p => {
|
503 | if (t.isObjectProperty(p)) {
|
504 | if (t.isIdentifier(p.key)) {
|
505 | self.initState.add(p.key.name);
|
506 | }
|
507 | }
|
508 | });
|
509 | }
|
510 | },
|
511 | JSXExpressionContainer(path) {
|
512 | const attr = path.findParent(p => p.isJSXAttribute());
|
513 | if (!attr) {
|
514 | const expr = path.get('expression');
|
515 | if (expr.isBooleanLiteral() || expr.isNullLiteral()) {
|
516 | path.remove();
|
517 | return;
|
518 | }
|
519 | }
|
520 | const isFunctionProp = attr && typeof attr.node.name.name === 'string' && attr.node.name.name.startsWith('on');
|
521 | let renderMethod;
|
522 | self.renderJSX.forEach(method => {
|
523 | renderMethod = path.findParent(p => method === p);
|
524 | });
|
525 | const jsxReferencedIdentifiers = self.refIdMap.get(renderMethod);
|
526 | path.traverse({
|
527 | MemberExpression(path) {
|
528 | const sibling = path.getSibling('property');
|
529 | if (path.get('object').isThisExpression() &&
|
530 | (path.get('property').isIdentifier({ name: 'props' }) || path.get('property').isIdentifier({ name: 'state' })) &&
|
531 | sibling.isIdentifier()) {
|
532 | if (!isFunctionProp) {
|
533 | self.usedState.add(sibling.node.name);
|
534 | }
|
535 | }
|
536 | }
|
537 | });
|
538 | const expression = path.get('expression');
|
539 | const scope = renderMethod && renderMethod.scope || path.scope;
|
540 | const calleeExpr = expression.get('callee');
|
541 | const parentPath = path.parentPath;
|
542 | if (utils_1.hasComplexExpression(expression) &&
|
543 | !isFunctionProp &&
|
544 | !(calleeExpr &&
|
545 | calleeExpr.isMemberExpression() &&
|
546 | calleeExpr.get('object').isMemberExpression() &&
|
547 | calleeExpr.get('property').isIdentifier({ name: 'bind' }))
|
548 | ) {
|
549 | const calleeName = calleeExpr.isIdentifier() && calleeExpr.node.name;
|
550 | if (typeof calleeName === 'string' && calleeName.startsWith('render') && utils_1.isDerivedFromProps(calleeExpr.scope, calleeName)) {
|
551 | return;
|
552 | }
|
553 | if (calleeExpr.isMemberExpression() && utils_1.isDerivedFromProps(calleeExpr.scope, utils_1.findFirstIdentifierFromMemberExpression(calleeExpr.node).name)) {
|
554 | const idName = utils_1.findFirstIdentifierFromMemberExpression(calleeExpr.node).name;
|
555 | if (utils_1.isDerivedFromProps(calleeExpr.scope, idName) && t.isIdentifier(calleeExpr.node.property) && calleeExpr.node.property.name.startsWith('render')) {
|
556 | return;
|
557 | }
|
558 | }
|
559 | utils_1.generateAnonymousState(scope, expression, jsxReferencedIdentifiers);
|
560 | }
|
561 | else {
|
562 | if (parentPath.isJSXAttribute()) {
|
563 | if (!(expression.isMemberExpression() || expression.isIdentifier()) && parentPath.node.name.name === 'key') {
|
564 | utils_1.generateAnonymousState(scope, expression, jsxReferencedIdentifiers);
|
565 | }
|
566 | }
|
567 | }
|
568 | if (!attr)
|
569 | return;
|
570 | const key = attr.node.name;
|
571 | const value = attr.node.value;
|
572 | if (!t.isJSXIdentifier(key)) {
|
573 | return;
|
574 | }
|
575 | const jsx = path.findParent(p => p.isJSXOpeningElement());
|
576 | if (t.isJSXIdentifier(key) && key.name.startsWith('on') && t.isJSXExpressionContainer(value)) {
|
577 | const expr = value.expression;
|
578 | if (t.isCallExpression(expr) &&
|
579 | t.isMemberExpression(expr.callee) &&
|
580 | t.isIdentifier(expr.callee.property, { name: 'bind' }) &&
|
581 | !functional_1.Status.isSFC) {
|
582 | if ((!adapter_1.isNewPropsSystem()) ||
|
583 | (t.isJSXIdentifier(jsx.node.name) && constant_1.DEFAULT_Component_SET.has(jsx.node.name.name))) {
|
584 | self.buildPropsAnonymousFunc(attr, expr, true, path);
|
585 | }
|
586 | }
|
587 | else if (t.isMemberExpression(expr)) {
|
588 | if ((!adapter_1.isNewPropsSystem()) ||
|
589 | (t.isJSXIdentifier(jsx.node.name) && constant_1.DEFAULT_Component_SET.has(jsx.node.name.name))) {
|
590 | self.buildPropsAnonymousFunc(attr, expr, false, path);
|
591 | }
|
592 | }
|
593 | else if (!t.isLiteral(expr)) {
|
594 | self.buildAnonyMousFunc(path, attr, expr);
|
595 | }
|
596 | else {
|
597 | throw utils_1.codeFrameError(path.node, '组件事件传参不能传入基本类型');
|
598 | }
|
599 | }
|
600 | if (!jsx)
|
601 | return;
|
602 | const jsxName = jsx.node.name;
|
603 | if (!t.isJSXIdentifier(jsxName))
|
604 | return;
|
605 | if (expression.isJSXElement())
|
606 | return;
|
607 | if (constant_1.DEFAULT_Component_SET.has(jsxName.name) || expression.isIdentifier() || expression.isMemberExpression() || expression.isLiteral() || expression.isLogicalExpression() || expression.isConditionalExpression() || key.name.startsWith('on') || expression.isCallExpression())
|
608 | return;
|
609 | if (utils_1.isContainJSXElement(path))
|
610 | return;
|
611 | utils_1.generateAnonymousState(scope, expression, jsxReferencedIdentifiers);
|
612 | },
|
613 | Identifier(path) {
|
614 | const isStartWithRender = /^render[A-Z]/.test(path.node.name);
|
615 | const isInJSXExprContainer = !!path.findParent(p => p.isJSXExpressionContainer());
|
616 | if (!isInJSXExprContainer) {
|
617 | return;
|
618 | }
|
619 | if (path.node.name === 'children' || isStartWithRender) {
|
620 | const parentPath = path.parentPath;
|
621 | const slot = t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('slot'), [], true), t.jSXClosingElement(t.jSXIdentifier('slot')), [], true);
|
622 | if (isStartWithRender) {
|
623 | slot.openingElement.attributes.push(t.jSXAttribute(t.jSXIdentifier('name'), t.stringLiteral(utils_1.getSlotName(path.node.name))));
|
624 | self.setMultipleSlots();
|
625 | }
|
626 | if (parentPath.isCallExpression() && parentPath.parentPath.isJSXExpressionContainer()) {
|
627 | if (utils_1.isDerivedFromProps(path.scope, path.node.name)) {
|
628 | render_props_1.injectRenderPropsEmiter(parentPath, path.node.name);
|
629 | parentPath.replaceWith(slot);
|
630 | }
|
631 | }
|
632 | if (parentPath.isMemberExpression() && parentPath.parentPath.isCallExpression()) {
|
633 | if (utils_1.isDerivedFromProps(path.scope, utils_1.findFirstIdentifierFromMemberExpression(parentPath.node).name)) {
|
634 | render_props_1.injectRenderPropsEmiter(parentPath.parentPath, path.node.name);
|
635 | parentPath.parentPath.replaceWith(slot);
|
636 | }
|
637 | }
|
638 | if (parentPath.isMemberExpression() &&
|
639 | parentPath.isReferenced() &&
|
640 | (parentPath.parentPath.isJSXExpressionContainer() ||
|
641 | parentPath.parentPath.isLogicalExpression() ||
|
642 | parentPath.parentPath.isConditionalExpression())) {
|
643 | const object = parentPath.get('object');
|
644 | if (object.isIdentifier()) {
|
645 | const objectName = object.node.name;
|
646 | if (utils_1.isDerivedFromProps(path.scope, objectName)) {
|
647 | parentPath.replaceWith(slot);
|
648 | }
|
649 | }
|
650 | }
|
651 | else if (path.isReferencedIdentifier()) {
|
652 | if (utils_1.isDerivedFromProps(path.scope, 'children')) {
|
653 | parentPath.replaceWith(slot);
|
654 | }
|
655 | }
|
656 | }
|
657 | },
|
658 | JSXElement(path) {
|
659 | const id = path.node.openingElement.name;
|
660 | if (t.isJSXIdentifier(id) &&
|
661 | !constant_1.DEFAULT_Component_SET.has(id.name)) {
|
662 | if (self.moduleNames.indexOf(id.name) !== -1) {
|
663 | const name = id.name;
|
664 | const binding = self.classPath.scope.getBinding(name);
|
665 | if (binding && t.isImportDeclaration(binding.path.parent)) {
|
666 | const sourcePath = binding.path.parent.source.value;
|
667 | const specs = binding.path.parent.specifiers.filter(s => t.isImportSpecifier(s));
|
668 | if (binding.path.isImportDefaultSpecifier()) {
|
669 | self.customComponents.set(name, {
|
670 | sourcePath,
|
671 | type: 'default'
|
672 | });
|
673 | }
|
674 | else {
|
675 | const spec = specs.find(s => s.local.name === name && s.imported.name !== name);
|
676 | if (spec) {
|
677 | self.customComponents.set(name, {
|
678 | sourcePath,
|
679 | type: 'pattern',
|
680 | imported: spec.imported.name
|
681 | });
|
682 | }
|
683 | else {
|
684 | self.customComponents.set(name, {
|
685 | sourcePath,
|
686 | type: 'pattern'
|
687 | });
|
688 | }
|
689 | }
|
690 | }
|
691 | }
|
692 | if (id.name.endsWith(constant_1.CONTEXT_PROVIDER)) {
|
693 | const valueAttr = path.node.openingElement.attributes.find(a => t.isJSXIdentifier(a.name) && a.name.name === 'value');
|
694 | const contextName = id.name.slice(0, id.name.length - constant_1.CONTEXT_PROVIDER.length);
|
695 | if (valueAttr) {
|
696 | if (t.isJSXElement(valueAttr.value)) {
|
697 | throw utils_1.codeFrameError(valueAttr.value, 'Provider 的 value 只能传入一个字符串或普通表达式,不能传入 JSX');
|
698 | }
|
699 | else {
|
700 | const value = t.isStringLiteral(valueAttr.value) ? valueAttr.value : valueAttr.value.expression;
|
701 | const expr = t.expressionStatement(t.callExpression(t.memberExpression(t.identifier(contextName), t.identifier('Provider')), [value]));
|
702 | path.getStatementParent().insertBefore(expr);
|
703 | path.replaceWith(t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('Block'), []), t.jSXClosingElement(t.jSXIdentifier('Block')), path.node.children));
|
704 | }
|
705 | }
|
706 | }
|
707 | }
|
708 | },
|
709 | MemberExpression: (path) => {
|
710 | const object = path.get('object');
|
711 | const property = path.get('property');
|
712 | if (!(object.isThisExpression() && property.isIdentifier({ name: 'props' }))) {
|
713 | return;
|
714 | }
|
715 | const parentPath = path.parentPath;
|
716 | if (parentPath.isMemberExpression()) {
|
717 | const siblingProp = parentPath.get('property');
|
718 | if (siblingProp.isIdentifier()) {
|
719 | const name = siblingProp.node.name;
|
720 | if (name === 'children') {
|
721 | parentPath.replaceWith(t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('slot'), [], true), t.jSXClosingElement(t.jSXIdentifier('slot')), [], true));
|
722 | }
|
723 | else if (/^render[A-Z]/.test(name)) {
|
724 | const slotName = utils_1.getSlotName(name);
|
725 | if (parentPath.parentPath.isCallExpression()) {
|
726 | render_props_1.injectRenderPropsEmiter(parentPath.parentPath, name);
|
727 | parentPath.parentPath.replaceWith(t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('slot'), [
|
728 | t.jSXAttribute(t.jSXIdentifier('name'), t.stringLiteral(slotName))
|
729 | ], true), t.jSXClosingElement(t.jSXIdentifier('slot')), []));
|
730 | }
|
731 | else {
|
732 | parentPath.replaceWith(t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('slot'), [
|
733 | t.jSXAttribute(t.jSXIdentifier('name'), t.stringLiteral(slotName))
|
734 | ], true), t.jSXClosingElement(t.jSXIdentifier('slot')), []));
|
735 | }
|
736 | this.setMultipleSlots();
|
737 | }
|
738 | else {
|
739 | self.componentProperies.add(siblingProp.node.name);
|
740 | }
|
741 | }
|
742 | }
|
743 | else if (parentPath.isVariableDeclarator()) {
|
744 | const siblingId = parentPath.get('id');
|
745 | if (siblingId.isObjectPattern()) {
|
746 | const properties = siblingId.node.properties;
|
747 | for (const prop of properties) {
|
748 | if (t.isRestProperty(prop)) {
|
749 | throw utils_1.codeFrameError(prop.loc, 'this.props 不支持使用 rest property 语法,请把每一个 prop 都单独列出来');
|
750 | }
|
751 | else if (t.isIdentifier(prop.key)) {
|
752 | self.componentProperies.add(prop.key.name);
|
753 | }
|
754 | }
|
755 | }
|
756 | }
|
757 | },
|
758 | CallExpression(path) {
|
759 | const node = path.node;
|
760 | const callee = node.callee;
|
761 | if (t.isMemberExpression(callee) && t.isMemberExpression(callee.object)) {
|
762 | const property = callee.property;
|
763 | if (t.isIdentifier(property)) {
|
764 | if (property.name.startsWith('on')) {
|
765 | self.componentProperies.add(`${constant_1.FN_PREFIX}${property.name}`);
|
766 | processThisPropsFnMemberProperties(callee, path, node.arguments);
|
767 | }
|
768 | else if (property.name === 'call' || property.name === 'apply') {
|
769 | self.componentProperies.add(`${constant_1.FN_PREFIX}${property.name}`);
|
770 | processThisPropsFnMemberProperties(callee.object, path, node.arguments);
|
771 | }
|
772 | }
|
773 | }
|
774 | }
|
775 | });
|
776 | }
|
777 | setComponents() {
|
778 | const components = [];
|
779 | this.customComponents.forEach((component, name) => {
|
780 | if (name.startsWith('Taro') && component.sourcePath === constant_1.COMPONENTS_PACKAGE_NAME) {
|
781 | return;
|
782 | }
|
783 | if (adapter_1.Adapter.type === "quickapp" && constant_1.DEFAULT_Component_SET_COPY.has(name)) {
|
784 | return;
|
785 | }
|
786 | components.push(name);
|
787 | this.result.components.push({
|
788 | path: utils_1.pathResolver(component.sourcePath, this.sourcePath),
|
789 | name: component.imported ? lodash_1.kebabCase(name) + '|' + lodash_1.kebabCase(component.imported) : lodash_1.kebabCase(name),
|
790 | type: component.type
|
791 | });
|
792 | });
|
793 | this.classPath.node.body.body.push(t.classProperty(t.identifier('customComponents'), t.arrayExpression(components.map(c => t.stringLiteral(c)))));
|
794 | }
|
795 | setMethods() {
|
796 | const methods = this.classPath.get('body').get('body');
|
797 | for (const method of methods) {
|
798 | if (method.isClassMethod()) {
|
799 | const key = method.get('key');
|
800 | if (key.isIdentifier()) {
|
801 | this.methods.set(key.node.name, method);
|
802 | }
|
803 | }
|
804 | }
|
805 | }
|
806 | resetConstructor() {
|
807 | const body = this.classPath.node.body.body;
|
808 | if (!this.methods.has('constructor')) {
|
809 | const ctor = buildConstructor();
|
810 | body.unshift(ctor);
|
811 | }
|
812 | if (env_1.isTestEnv) {
|
813 | return;
|
814 | }
|
815 | for (const method of body) {
|
816 | if (t.isClassMethod(method) && method.kind === 'constructor') {
|
817 | method.kind = 'method';
|
818 | method.key = t.identifier('_constructor');
|
819 | if (t.isBlockStatement(method.body)) {
|
820 | for (const statement of method.body.body) {
|
821 | if (t.isExpressionStatement(statement)) {
|
822 | const expr = statement.expression;
|
823 | if (t.isCallExpression(expr) && (t.isIdentifier(expr.callee, { name: 'super' }) || t.isSuper(expr.callee))) {
|
824 | expr.callee = t.memberExpression(t.identifier('super'), t.identifier('_constructor'));
|
825 | }
|
826 | }
|
827 | }
|
828 | }
|
829 | }
|
830 | }
|
831 | }
|
832 | handleLifecyclePropParam(propParam, properties) {
|
833 | let propsName = null;
|
834 | if (!propParam) {
|
835 | return null;
|
836 | }
|
837 | if (t.isIdentifier(propParam)) {
|
838 | propsName = propParam.name;
|
839 | }
|
840 | else if (t.isObjectPattern(propParam)) {
|
841 | for (const prop of propParam.properties) {
|
842 | if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
|
843 | properties.add(prop.key.name);
|
844 | }
|
845 | else if (t.isRestProperty(prop) && t.isIdentifier(prop.argument)) {
|
846 | propsName = prop.argument.name;
|
847 | }
|
848 | }
|
849 | }
|
850 | else {
|
851 | throw utils_1.codeFrameError(propParam.loc, '此生命周期的第一个参数只支持写标识符或对象解构');
|
852 | }
|
853 | return propsName;
|
854 | }
|
855 | findMoreProps() {
|
856 |
|
857 | const lifeCycles = new Set([
|
858 |
|
859 | 'componentDidUpdate',
|
860 | 'shouldComponentUpdate',
|
861 | 'getDerivedStateFromProps',
|
862 | 'getSnapshotBeforeUpdate',
|
863 | 'componentWillReceiveProps',
|
864 | 'componentWillUpdate'
|
865 | ]);
|
866 | const properties = new Set();
|
867 | this.methods.forEach((method, name) => {
|
868 | if (!lifeCycles.has(name)) {
|
869 | return;
|
870 | }
|
871 | const node = method.node;
|
872 | let propsName = null;
|
873 | if (t.isClassMethod(node)) {
|
874 | propsName = this.handleLifecyclePropParam(node.params[0], properties);
|
875 | }
|
876 | else if (t.isArrowFunctionExpression(node.value) || t.isFunctionExpression(node.value)) {
|
877 | propsName = this.handleLifecyclePropParam(node.value.params[0], properties);
|
878 | }
|
879 | if (propsName === null) {
|
880 | return;
|
881 | }
|
882 | method.traverse({
|
883 | MemberExpression(path) {
|
884 | if (!path.isReferencedMemberExpression()) {
|
885 | return;
|
886 | }
|
887 | const { object, property } = path.node;
|
888 | if (t.isIdentifier(object, { name: propsName }) && t.isIdentifier(property)) {
|
889 | properties.add(property.name);
|
890 | }
|
891 | },
|
892 | VariableDeclarator(path) {
|
893 | const { id, init } = path.node;
|
894 | if (t.isObjectPattern(id) && t.isIdentifier(init, { name: propsName })) {
|
895 | for (const prop of id.properties) {
|
896 | if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
|
897 | properties.add(prop.key.name);
|
898 | }
|
899 | }
|
900 | }
|
901 | }
|
902 | });
|
903 | properties.forEach((value) => {
|
904 | this.componentProperies.add(value);
|
905 | });
|
906 | });
|
907 | }
|
908 | parseRender() {
|
909 | if (this.importJSXs.size) {
|
910 | this.importJSXs.forEach(s => {
|
911 | this.result.template += s + '\n';
|
912 | });
|
913 | }
|
914 | if (this.renderJSX.size) {
|
915 | this.renderJSX.forEach((method, methodName) => {
|
916 | this.result.template = this.result.template
|
917 | + new render_1.RenderParser(method, this.methods, this.initState, this.refIdMap.get(method), this.usedState, this.customComponentNames, this.componentProperies, this.loopRefs, this.refObjExpr, methodName).outputTemplate + '\n';
|
918 | });
|
919 | }
|
920 | else {
|
921 | throw utils_1.codeFrameError(this.classPath.node.loc, '没有定义 render 方法');
|
922 | }
|
923 | }
|
924 | clearClosureMethods() {
|
925 | this.classPath.node.body.body = this.classPath.node.body.body.filter(m => {
|
926 | if (m && t.isClassMethod(m) && t.isIdentifier(m.key) && m.key.name.startsWith('_createClosure')) {
|
927 | return false;
|
928 | }
|
929 | return true;
|
930 | });
|
931 | }
|
932 | compile() {
|
933 | this.traverse();
|
934 | this.setMethods();
|
935 | this.setComponents();
|
936 | this.resetConstructor();
|
937 | this.findMoreProps();
|
938 | this.handleRefs();
|
939 | this.parseRender();
|
940 | this.setComponentPath();
|
941 | this.clearClosureMethods();
|
942 | this.result.componentProperies = [...this.componentProperies];
|
943 | }
|
944 | }
|
945 | exports.Transformer = Transformer;
|
946 |
|
\ | No newline at end of file |