给源代码打标记

一、背景

为什么需要要给源代码打标记? 给源代码打标记是为了给源码项目提供可编辑能力。源码项目有了编辑能力可以做很多事情,比如可视化开发、可视化埋点、修改文案,代码定位等等;埋点和修改文案这种需求完全可以交给需求方自己来做。 如下图可以对源代码项目进行编辑

二、实现方案

两种方案

1.基于代码位置

先来个图 如图标记路径和位置可定位到具体的文件和行号就可以定位到具体的代码上,实现编辑能力。 实现思路: 实现无非是在打包构建时给jsx代码增加path属性,以vite为例给babel写个jsx插件,设置在开发环境起作用:

markdown 复制代码
react(
  {
    babel: {
      plugins: [
        [
          'firefly-babel-jsx/dist',
          {
            env: 'development',
          },
        ],
      ],
    },
  },
),

具体的代码实现

markdown 复制代码
export default function transformPathJsComponents(babel: Babel): {
	return {
    visitor: {
      Program: {
        enter(path, state) {
          path.traverse({
            FunctionDeclaration: {
              enter(path, state) {
                
              },
              exit(path, state) {
              
              },
              TaggedTemplateExpression(path) {
                
              },
              JSXElement(path) {
                  // 重点在这里
                  const id = addExpressionToStorage({
                    name,
                    loc: path.node.loc,
                    wrappingComponentId: currentWrappingComponentId,
                  });
                  const newAttr = t.jSXAttribute(
                    t.jSXIdentifier('data-path-id'),
                    t.jSXExpressionContainer(
                      t.stringLiteral(
                        createDataId(fileStorage, id),
                      ),
                    ),
                  );

                  const dataUnique = t.jSXAttribute(
                    t.jSXIdentifier('data-unique'),
                    t.jSXExpressionContainer(
                      t.stringLiteral(
                        `${path.node.start}::${path.node.end}`,
                      ),
                    ),
                  );
                  path.node.openingElement.attributes.push(newAttr, dataUnique);
              }
            }  
        }
      }
    }
  }  
}

上面代码只是提供思路的示例代码,重点是对ast进行操作,可以配合这个网站写代码 ts-ast-viewer.com/,具体的代码在这里:github.com/sparrow-js/... 使用位置标记方案的问题: 1.上下逻辑不清晰,实际在结合可视化编辑时比如我们新增了一个组件节点然后在格式化想要找到新增的节点是哪个就比较困难。 2.性能体验问题,改一下代码很可能导致所有标记都变了。

2.使用属性+文件路径

先来个图 如图标记变成了一个看似随机数的属性data-uid="424" 实现思路 1.生成ID

markdown 复制代码
import Hash from 'object-hash';
// 创建Hash值
const hash = Hash({
    fileName: sourceFile.fileName,
    name: parseJSXElementName((node as any).openingElement, sourceFile),
    props,
});

export const atoz = [
    'a',
    'b',
    'c',
    'd',
    'e',
    'f',
    'g',
    'h',
    'i',
    'j',
    'k',
    'l',
    'm',
    'n',
    'o',
    'p',
    'q',
    'r',
    's',
    't',
    'u',
    'v',
    'w',
    'x',
    'y',
    'z',
];

export function generateConsistentUID(
    possibleStartingValue: string,
    ...existingIDSets: Array<Set<string>>
): string {
    function alreadyExistingID(idToCheck: string): boolean {
        return existingIDSets.some((s) => s.has(idToCheck));
    }
    
    // 截取字符串的三个字符,判断是否存在存在往后移动三位
    if (possibleStartingValue.length >= 3) {
        const maxSteps = Math.floor(possibleStartingValue.length / 3);
        for (let step = 0; step < maxSteps; step++) {
          const possibleUID = possibleStartingValue.substring(step * 3, (step + 1) * 3);

          if (!alreadyExistingID(possibleUID)) {
            return possibleUID;
          }
        }
    }

  	// 循环取出uid
    for (let firstChar of atoz) {
        for (let secondChar of atoz) {
          for (let thirdChar of atoz) {
            const possibleUID = `${firstChar}${secondChar}${thirdChar}`;

            if (!alreadyExistingID(possibleUID)) {
              return possibleUID;
            }
          }
        }
    }
}

通过上面代码获取到uid,hash值是通过属性标签+属性+文件,属性变化会影响生成的uid,实际在可视化编辑时改变元素的属性需要让uid保持不变,避免当前元素包裹的其他元素路径结构变化,所以就需要做diff操作,存一份旧的ast树,生成的新的ast树跟旧的做对比如果只是属性变化就把旧的uid替换到新的上面去大致代码如下

markdown 复制代码
function fixArrayElements<T>(
    oldElements: any[],
    newElements: any[],
) {
    let oldElementIndexesUsed: Set<number> = new Set();
    let workingArray: T[] = [];
    newElements.forEach((newElement, newElementIndex) => {
        const { uid } = newElement;
        let possibleOldElement: T | undefined;

        if (!oldElements) {
            workingArray[newElementIndex] = newElement;
        } else {
            oldElements.forEach((oldElement, oldElementIndex) => {
                const oldUid = oldElement.uid;
                if (oldUid === uid) {
                    possibleOldElement = oldElement;
                    oldElementIndexesUsed.add(oldElementIndex);
                }
            });
        }

        if (possibleOldElement != null) {
            workingArray[newElementIndex] = fixJSXElementUIDs(possibleOldElement, newElement);
        }
    });

    return newElements.map((newElement, newElementIndex) => {
        if (newElementIndex in workingArray) {
            return workingArray[newElementIndex];
        } else if (!oldElementIndexesUsed.has(newElementIndex)) {
            const oldElement = oldElements?.[newElementIndex];
            return fixJSXElementUIDs(oldElement, newElement);
        } else {
            return fixJSXElementUIDs(newElement, newElement);
        }
    });
}

具体代码在这里:github.com/sparrow-js/... 可以看上面class从App改到Appuid,data-uid="c0d",uid没有变化。还可以使用原id对当前元素编辑。 通过第二种方式让源代码可视化编辑的基础就搞定了。实现细节可以看源代码

相关推荐
赖龙3 小时前
pnpm vs npm
前端·npm·node.js
To_OC4 小时前
LC 239 滑动窗口最大值:从暴力超时到单调队列一遍过
javascript·算法·leetcode
学如逆水,不进则退4 小时前
在线免费音频剪辑:NBTools 可视化波形裁剪 MP3/WAV,本地处理免上传
前端·音视频
Mh5 小时前
虚拟滚动真的比普通滚动性能更好吗?
前端·javascript·性能优化
pe7er5 小时前
React + Ant Design 中的 IME (输入法合成)安全输入组件
前端
Super 含5 小时前
Android 启动优化(二):TTID、TTFD 与 Macrobenchmark 启动性能测量
前端
Python私教6 小时前
别急着加 llms.txt:企业官网面向 AI 搜索的工程清单
前端·人工智能·seo
东方小月6 小时前
从零开发一个 Coding Agent(十三):实现安全的 read 文件读取工具
前端·人工智能·全栈
东方小月6 小时前
从零开发一个 Coding Agent(十二):实现版本化 JSONL 与真实 CLI 入口
前端·设计模式·前端框架
FL16238631297 小时前
室内易燃物识别易燃评估室内易燃程度识别分割数据集labelme格式1015张85类别
java·服务器·前端