关于前端:如何阅读嵌套深引用关系复杂的reactts项目-要不咱写个loader缓解一下

如何浏览’嵌套深’&’援用关系简单’的react+ts我的项目? 要不咱写个loader缓解一下~

介绍

     本文讲述了我为做出这个性能所经验的全过程, 一直的掉进坑里又一直地爬出来, 相比于后果过程更乏味, 所以才想把它分享进去。

一. 我的项目太’简单’, 找个组件都发愁

     随着我的项目越做越大(cha), 会多出不少很深的代码模块, 比方你看到页面上显示的一个’名片框’, 但你可能须要找好几分钟能力找到这个’名片框’的代码写在了哪个文件里, 如果这个我的项目你只是接管过去, 前几年不是你在保护, 那么寻找代码这个过程会很苦楚, React Developer Tools也并没有很好的解决这个问题。

     要明确一点所谓的’简单’可能只是大家代码写的’差’, 代码结构设计的不合理, 比方过分形象, 很多人认为只有一直的抽出组件代码, 并且正文越少越好, 这样写的就是好代码, 其实这只是处于’比拟高级的程度’, 代码是写给人看的将代码写的逻辑清晰, 并且容易读懂容易找到外围的性能节点才是好代码, 往往过分的抽离出小组件会使性能降落, 毕竟不免要生成新的作用域, 很多人写react比写vue更容易过分形象。

     这里我想到的解决方案之一是这样的, 为每个元素增加一个’地址’属性: (本次以react + Ts 我的项目为例)

  • 比方某个导出的 button组件, 代码所在位置'object/src/page/home/index.tsx'
  • 则咱们就能够这样写 <button tipx='object/src/page/home/index.tsx'>按钮</button>
  • 咱们能够悬停展现门路, 也能够通过控制台查看门路信息
  • 比方img、input这种无奈应用伪元素的标签须要关上控制台查看

二. 计划抉择

谷歌浏览器插件

     这个尽管很容易为标签插入属性, 然而无奈读取到插件所在的开发门路, 这个计划能够排除了。

vscode 插件

     能够很好的读取到开发文件所在的文件夹, 然而增加门路属性的话会毁坏整体的代码构造, 并且不好解决用户被动删掉某些属性以及辨别开发环境与生产环境, 毕竟生产环境咱们可不会做解决。

loader

     针对特定类型的文件, 管制只在’开发环境下’为元素标签注入’门路属性’, 并且它自身就很不便取得以后文件所属门路。

     本篇也只是做了个小性能插件, 尽管没解决大问题, 然而思考过程还挺有意思的。

效果图

当鼠标选停放在元素上, 则展现出该元素的文件夹门路

三. 款式计划

     赋予标签属性之后咱们就要思考如何获取它了, 不言而喻咱们这次要用属性选择器, 把所有标签属性有tipx的标签全副检索进去, 而后咱们通过伪元素befour或者after来展现这个文件地址。

attr你还记得不?

     这个属性是css代码用来获取dom标签属性的, 而咱们就能够有如下的写法:

[tipx]:hover[tipx]::after{
  content: attr(tipx);
  color: white;
  display: flex;
  position: relative;
  align-items: center;
  background-color: red;
  justify-content: center;
  opacity: .6;
  font-size: 16px;
  padding: 3px 7px;
  border-radius: 4px;
}

四. 计划1: loader配正则

     简略粗犷的形式那必定非 正则 莫属, 匹配出所有的开始标签, 比方<div替换成<div tipx='xxxxxx' , 这里要留神咱们不必向自定义的组件上放属性, 要把属性放在原生标签上。

  // 大略就是这个意思, 列举出所有的原生标签名
  context = context.replace(
    /\<(div|span|p|ul|li|i|a")/g,
    `<$1 tipx='${this.resourcePath}'`
  );

     咱们从头创立react我的项目并设置loader:

  1. npx create-react-app show_path --template typescript, ts在前面有坑缓缓观赏。
  2. yarn eject 裸露配置。
  3. config文件夹下建设loaders/loader.js

    module.exports = function (context) {
      // .... 稍后在此大(lang)展(bei)身(bu)手(kan)
      context = context.replace(
     /\<(div|span|p|ul|li|i|a")/g,
     `<$1 tipx='${this.resourcePath}'`
      );
      return context
    };
    
  4. 关上show_path/config/webpack.config.js文件, 大略第557行, 增加如下代码:

         {
           test: /\.(tsx)$/,
           use: [
             require.resolve("./loaders/loader.js")
         },

五. 正则’难以招架’的几种状况

1:div字符串
const str = "<div>是现代程序员, 常常应用的标签"

上述情况会被正则误判成实在标签, 但其实不应该批改这个字符串。

2:名称反复
<divss>自定义标签名<divss>

此类标签几率小, 然而有几率呈现重名的状况

3:单引号双引号
const str = "<div>标签外层曾经有双引号</div>"

// 替换后报错
const str = "<div tipx="xxx/xx/xx/x.index">标签外层曾经有双引号</div>"

咱们不好判断外层是单引号还是双引号

4:styled-components

这个技术的书写形式使咱们没法拆分进去, 比方上面的写法:

import styled from "styled-components";

export default function Home() {
  const MyDiv = styled.div`
    border: 1px solid red;
  `;
  return <MyDiv>123</MyDiv>
}

六. 计划2: AST树 & 获取以后文件门路

     终于达到主线工作了, 将代码解析成树结构就能够更难受的剖析了, 比拟好用的转换AST树的插件有esprimarecast, 咱们能够把步骤差分成三局部, code转树结构循环遍历树结构树结构转code

     以后文件门路webpack曾经注入了loader外面, this.resourcePath就能够取到, 但它会是一个全局门路, 也就是从根目录始终到当前目录的电脑残缺门路, 有需要的话咱们能够进行一下拆分展现。

     咱们为loader.js写入代码,进行 “第一步” 解析的时候报错了, 起因是它不意识jsx语法。

const esprima = require("esprima");

module.exports = function (context, map, meta) {
  let astTree = esprima.parseModule(context);
  console.log(astTree);
  this.callback(null, context, map, meta);
};

七. 如何生成与解析react代码

     这时咱们能够为其传入一个参数jsx:true:

  let astTree = esprima.parseModule(context, { jsx: true });
遍历这颗树

     因为树结构可能会十分深, 咱们能够用工具函数estraverse来做遍历:

    estraverse.traverse(astTree, {
      enter(node) {
        console.log(node);
      },
    });

此时报错了, 一起观赏下吧:

解决遍历问题

     我在网上找到了解决办法, 就是用专门解决jsxElement的循环插件yarn add estraverse-fb:

// 替换前
const estraverse = require("estraverse");

// 替换后
const estraverse = require("estraverse-fb");

能够失常循环:

生成代码

     我平时罕用的解析纯js代码的工具函数退场了escodegen:

const esprima = require("esprima");
const estraverse = require("estraverse-fb");
const escodegen = require("escodegen");

module.exports = function (context, map, meta) {
  let astTree = esprima.parseModule(context, { jsx: true });
  estraverse.traverse(astTree, {
    enter(node) {}
  });
  // 此处将AST树转成js代码
  context = escodegen.generate(astTree);
  this.callback(null, context, map, meta);
};

而后就又报错了:

但此时问题必定是出在AST树还原成jscode这一步了, 搜寻了escodegen的各种配置并没有找到能够解决以后问题的配置, 过后也只好去寻找其余插件了。

八. recast

     recast也是一款很好用的AST转换库, recast官网地址, 但他没有自带好用的遍历办法, 应用形式如下:

const recast = require("recast");

module.exports = function (context, map, meta) {
    // 1: 生成树
    const ast = recast.parse(context);
    // 2: 转换树
    const out = recast.print(ast).code;
    context = out;
  this.callback(null, context, map, meta);
};

那咱们忍痛割爱只取它的树转code性能:

// 替换前
 context = escodegen.generate(astTree);

// 替换后
 context = recast.print(astTree).code;

九. 找到指标 & 赋予属性

     前后流程都买通了当初须要对标签赋予属性了, 这里间接看我总结的写法吧:

    const path = this.resourcePath;
    estraverse.traverse(astTree, {
      enter(node) {
        if (node.type === "JSXOpeningElement") {
        node.attributes.push({
          type: "JSXAttribute",
          name: {
            type: "JSXIdentifier",
            name: "tipx",
          },
          value: {
            type: "Literal",
            value: path,
          },
        });
        }
      },
    });
  1. 筛选出JSXOpeningElement类型的元素
  2. node.attributes.push将要新增的属性放入元素的属性队列
  3. JSXIdentifier属性名类型
  4. Literal属性值类型

配合recast的确能够把代码还原的不错, 但这就真的完结了么?

十. ts有话说!

     当我把开发的loader投入到理论我的项目时, 那真是大写的傻眼, 假如开发的代码如下:

import React from "react";

export default function Home() {
  interface C {
    name: string;
  }
  const c: C = {
    name: "金毛",
  };
  return <div title="title">home 页面</div>;
}

则会产生如下报错信息:

     也好了解, interface不能随便应用, 因为这是ts的语法咱们js不意识, 我第一工夫想到的是ts-loader并且尝试了让ts-loader先编译, 而后咱们解析它编译过的代码, 然而果然行不通。

     esprima这边无奈间接读懂ts语法, ts-loader无奈很好的解析jsx并且解析后的代码无奈与咱们之前写的各种解析AST树的代码相配合, 我过后一度陷入’泥潭’, 这个时候万能的babel-loader怯懦的站了进去!

十一. babel扭转了切

     咱们把它放在最后面执行:

{
   test: /\.(tsx)$/,
   use: [
       require.resolve("./loaders/loader.js"),
       {
        loader: require.resolve("babel-loader"),
          options: {
            presets: [[require.resolve("babel-preset-react-app")]],
          },
        },
       ],
},

     过后给本人鼓了4.6s的掌, 终于通过了, 然而不能就这样完结了, 因为文件曾经被babel解决过了, 所以实践上咱们之前针对jsx的非凡解决都能够去掉了:

// 之前的
const estraverse = require("estraverse-fb");

// 当初的
const estraverse = require("estraverse");


// 之前的
let astTree = esprima.parseModule(context, { jsx: true });

// 当初的
let astTree = esprima.parseModule(context);
循环的曾经不是jsx了, 循环体外面也要大改
// 之前的
  estraverse.traverse(astTree, {
    enter(node) {
      if (node.type === "JSXOpeningElement") {
        node.attributes.push({
           type: "JSXAttribute",
           name: {
             type: "JSXIdentifier",
             name: "tipx",
           },
           value: {
             type: "Literal",
             value: path,
           },
         });
      }
    },
  });

// 当初的
  estraverse.traverse(astTree, {
    enter(node) {
      if (node.type === "ObjectExpression") {
        node.properties.push({
          type: "Property",
          key: { type: "Identifier", name: "tipx" },
          computed: false,
          value: {
            type: "Literal",
            value: path,
            raw: '""',
          },
          kind: "init",
          method: false,
          shorthand: false,
        });
      }
    },
  });

此时启动咱们的我的项目就曾经能够解析ts语言了, 然而…投入理论我的项目里又又又出问题了!

十二. 理论开发时的谬误

     依照我下面配置的形式一成不变的放入正式我的项目, 居然报错了, 我就间接说吧谬误起因是package.json外面须要为babel指定类型:

  "babel": {
    "presets": [
      "react-app"
    ]
  },

这里再附上我babel的版本:

    "@babel/core": "7.12.3",
    "babel-loader": "8.1.0",
    "babel-plugin-named-asset-import": "^0.3.7",
    "babel-preset-react-app": "^10.0.0",

你认为这就没bug了?

十三. 居然真须要try退场!

     真的是一些语法依然有问题, 可能须要联合每个我的项目的特点进行一个独特的配置, 然而进百页代码只有3页报了奇怪的错, 最初还是抉择应用try catch 包裹住了整个过程, 这样也是最谨严的做法, 毕竟只是个辅助插件不应影响主体流程的进行。

十四. 残缺代码

const esprima = require('esprima');
const estraverse = require('estraverse');
const recast = require('recast');
module.exports = function (context, map, meta) {
  const path = this.resourcePath;
  let astTree = '';
  try {
    astTree = esprima.parseModule(context);
    estraverse.traverse(astTree, {
      enter(node) {
        if (node.type === 'ObjectExpression') {
          node.properties.push({
            type: 'Property',
            key: { type: 'Identifier', name: 'tipx' },
            computed: false,
            value: {
              type: 'Literal',
              value: path,
              raw: '""',
            },
            kind: 'init',
            method: false,
            shorthand: false,
          });
        }
      },
    });
    context = recast.print(astTree).code;
  } catch (error) {
    console.log('>>>>>>>>谬误');
  }
  return context;
};

配置

        {
          test: /\.(tsx)$/,
          use: [
            require.resolve("./loaders/loader.js"),
            {
              loader: require.resolve("babel-loader"),
              options: {
                presets: [[require.resolve("babel-preset-react-app")]],
              },
            },
          ],
        },

十五. 我的播种?

     尽管最终的代码并不长, 然而过程真的是挺崎岖的, 一直的尝试各种库, 并且要想解决问题就要挖一挖这些库到底做了什么, 就这样一次就使我对编译方面有了更好的了解。

     整个组件只能标出组件代码所在的地位, 并不能很好的指出其父级所在的文件地位, 还须要关上控制台查看他父级标签的tipx属性, 但至多当某个小小的组件出问题, 恰好这个小组件的命名不标准,且套还有点深, 而且咱们还不相熟代码, 那就试试应用这个loader找出他吧。

end

     这次就是这样, 心愿与你一起提高。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理