react native中metro的一些经验

393 阅读1分钟

前言

RN代码正在如火如荼的重构,在此之前,发现RN代码有很多不合理的地方。

不合理

工程里有很多自己的组件库,一一对应着不同的功能,例如登录,支付等等。在这之前,为了实现组件注册,使用标签的方式来对组件进行注册,这样的做法导致,自己的组件需要依赖RN自己的component的生命周期,而且写法上也不是很舒服。

生成代码的网址:ts-ast-viewer.com/

重构

现在代码重构,使用最新版本的ts,配合metro-config来进行组件注册和事件分发。
以下代码只是一个demo,整体逻辑就是将所有组件的相关初始化注入到service.tsx的init方法中

// decorator.tsx
import EvenStore from './eventStore';

export const component = (title: string): ClassDecorator => {
    return (target: any) => {
        target.prototype.title = title
    }
}

export const event = (event: string) => {
    return (target: any, key: string, descriptor: PropertyDescriptor) => {
        EvenStore.addEvent(event, descriptor.value);
    }
}

// testa.tsx
import { component, event } from "./decorator";
@component('hello')
export class Testa {

    @event('aaa')
    inita(params: string) {
        console.log(`bbbb ${params}`);
    }

    a() {
    }
}

// service.tsx
export class Service {
    init() {
        
    }

}

// metro.config.js
const fs = require('fs-extra');
const ts = require('typescript')

const moduleDir = "./node_modules";
// 组件装饰器
const componentDecoratorName = "component";

module.exports = {
  reporter: {
    update: (event) => {
      // 打印日志(后续可以优化打印格式)
      if (event.type === 'client_log') {
        event.data.forEach(data => {
          console.log(data);
        });
        return;
      }
      if (event.type === 'dep_graph_loading') {
        // 初始化组件服务文件
        const path = "service.tsx";
        const sourceFile = ts.createSourceFile(path, fs.readFileSync(path).toString(), ts.ScriptTarget.ES2015, true);
        // 清理缓存语法
        // 删除import语句
        sourceFile.statements = sourceFile.statements.filter(item => {
          return item.kind !== ts.SyntaxKind.ImportDeclaration
        });
        // 删除init方法中的语句
        sourceFile.statements[sourceFile.statements.length - 1].members[0].body.statements.length = 0;
        // 组件格式 ./node_modules/ts-rn-xxxx/xxxxComponent.tsx(暂定)
        // 遍历模块文件夹,找出自己相关组件
        const moduleNames = fs.readdirSync(moduleDir).filter(item => {
          return item.startsWith("ts-rn-");
        });
        moduleNames.forEach(moduleName => {
          // 找出模块下所有组件
          const componentDir = `${moduleDir}/${moduleName}`;
          const componentFileNames = fs.readdirSync(componentDir).filter(item => {
            return item.endsWith("Component.tsx");
          });
          componentFileNames.forEach(componentFileName => {
            // 生成语句
            const { importNode, componentObjNode, componentInitNode } = generateStatement(componentFileName, moduleName);
            sourceFile.statements = [importNode, ...sourceFile.statements];
            sourceFile.statements[sourceFile.statements.length - 1].members[0].body.statements.push(componentObjNode);
            sourceFile.statements[sourceFile.statements.length - 1].members[0].body.statements.push(componentInitNode);
          });
        });
        const result = ts.createPrinter().printFile(sourceFile);
        // 写入文件
        fs.writeFileSync(path, result);
      }
    }
  },
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  }
};

/**
 * 生成语句
 */
generateStatement = (componentFileName, moduleName) => {
  // 解析组件文件
  const result = parseComponentFile(`${moduleName}/${componentFileName}.tsx`);
  // 不是组件跳过
  if (!result) {
    return;
  }
  // 组件名规则:首字母大写,和文件名保持一致
  const componentName = componentFileName[0].toUpperCase() + componentFileName.substr(1);
  // 导入组件
  const importNode = ts.factory.createImportDeclaration(
    undefined,
    ts.factory.createImportClause(false, undefined,
      ts.factory.createNamedImports([ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(componentName))])
    ),
    ts.factory.createStringLiteral(`${moduleName}/${componentFileName}`),
    undefined
  );
  // 创建组件对象
  const componentObjNode = ts.factory.createVariableStatement(
    undefined,
    ts.factory.createVariableDeclarationList(
      [ts.factory.createVariableDeclaration(
        ts.factory.createIdentifier(componentFileName),
        undefined,
        undefined,
        ts.factory.createNewExpression(ts.factory.createIdentifier(componentName), undefined, [])
      )],
      ts.NodeFlags.Let
    )
  );
  // 组件初始化代码
  // 存放组件对象
  const componentInitNode = ts.factory.createExpressionStatement(ts.factory.createCallExpression(
    ts.factory.createPropertyAccessExpression(
      ts.factory.createIdentifier("console"),
      ts.factory.createIdentifier("log")
    ),
    undefined,
    [ts.factory.createPropertyAccessExpression(
      ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(componentName), ts.factory.createIdentifier("prototype")),
      ts.factory.createIdentifier("title")
    )]
  ));
  return { importNode: importNode, componentObjNode: componentObjNode, componentInitNode: componentInitNode };
}

/**
 * 解析组件文件
 */
parseComponentFile = (componentFile) => {
  const sourceFile = ts.createSourceFile(componentFile, fs.readFileSync(componentFile).toString(), ts.ScriptTarget.ES2015, true);
  // 过滤类
  const classStatement = sourceFile.statements.filter(item => {
    return item.kind === ts.SyntaxKind.ClassDeclaration;
  });
  // 组件文件规定只能有一个类
  const classNode = classStatement[0];
  // 查找类的装饰器是否是组件装饰器
  const classDecorator = ts.getDecorators(classNode);
  if (!classDecorator) {
    return false;
  }
  const classDecoratorName = classDecorator[0].expression.expression.escapedText;
  if (componentDecoratorName !== classDecoratorName) {
    return false;
  }
  return true;
}