Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 编码专题

  • 深入浅出 Vite

  • 快速上手 API

  • 深入浅出Babel

    • 如何封装 @babel:code-frame ?
      • 0.前言
      • 1.最终效果?
      • 2. 分析实现方案?
      • 3. 数据流向视角
      • 4.函数设计如下:
      • 5. 使用 ASCII 码打印颜色
    • 03.babel-JS解析器梳理
  • 深入浅出 React

  • 百问掘金
  • 深入浅出Babel
wangjiasheng
2023-05-21
目录

如何封装 @babel:code-frame ?

# 0.前言

本篇博客快速上手 @babel/code-frame 的实现原理,示例仓库 (opens new window)

# 1.最终效果?

import { describe, expect, it } from "vitest";

const { codeFrameColumns } = require("@babel/code-frame");

const code = `
const a = 1;
const b = 2;
`;

describe("使用 code-frame 实现错误提示", () => {
  it("01.打印出标记相应位置代码的 code frame", () => {
    const res = codeFrameColumns(
      code,
      {
        start: { line: 2, column: 1 },
        end: { line: 3, column: 5 },
      },
      {
        /* 此部分为 options  */
        highlightCode: true,
        message: "这里出错了",
        /* forceColor: true, */
      },
    );
    expect(res).toMatchInlineSnapshot(`
      "  1 |
      > 2 | const a = 1;
          | ^^^^^^^^^^^^
      > 3 | const b = 2;
          | ^^^^^ 这里出错了
        4 |"
    `);
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

在 terminal 中显示:

# 2. 分析实现方案?

详细使用见官方说明文档:https://babel.dev/docs/babel-code-frame

需要实现的 API :codeFrameColumns

const res = codeFrameColumns(
  code,
  {
    start: { line: 2, column: 1 },
    end: { line: 3, column: 5 },
  },
  {
    /* 此部分为 options  */
    highlightCode: true,
    message: "这里出错了",
    forceColor: true,
    linesAbove: 2 /* 打印当前上2行 */,
    linesBelow: 3 /* 打印当前下2行 */,
  },
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

其中:

  1. 第一个参数:输入原始的 code。
  2. 第二个参数:提示的范围。
  3. 第三个参数:opts 配置选项。

# 3. 数据流向视角

这里并不想简单的把源码的实现给贴上来,而是思考如何如果让我来实现这个函数,数据是如何流转的?

下面以 数据流向 的视角来概述整个代码的功能。

输入 code 的代码是基于 ES6 的模板父子串实现的:

`
const a = 1;
const b = 2;
`;
1
2
3
4

实际获取到的是:

"\nconst a = 1;\nconst b = 2;\n";
1

通过正则的手段,将字符串处理如下:

["", "const a = 1;", "const b = 2;", ""];
1

需要构造一个临时的结构,将需要修改的行标识出来:

{
  "2": [1,12],
  "3": [0,5],
}
1
2
3
4

根据临时结构对数组进行如下修改:

  1. 添加序号(gutter) ,如 > rowNumber |。
  2. 添加一行 \n | ^^^^^^^^^^^^ 其中 ^ 波浪行的长度取决于 col 。
  3. 添加颜色。

示例结构:

[
  "  1 |",
  "> 2 | const a = 1;\n    | ^^^^^^^^^^^^",
  "> 3 | const b = 2;\n    | ^^^^^ 这里出错了",
  "  4 |",
].join("\n");
1
2
3
4
5
6

# 4.函数设计如下:

通过上述数据流基本上很清晰了,本节将从函数的实现步骤进行分析:

  1. 首先将 rawLines 转化为数组

    const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
    const lines = rawLines.split(NEWLINE);
    
    1
    2
  2. 读取配置信息:

    • 是否需要高亮?

      const highlighted = opts.highlightCode;
      
      1
    • 对 chalk 进行二次封装

      function getDefs(chalk) {
        return {
          gutter: chalk.grey,
          marker: chalk.red.bold,
          message: chalk.red.bold,
        };
      }
      const defs = getDefs(chalk); /* 将 chalk 更加语义化 */
      
      1
      2
      3
      4
      5
      6
      7
      8
  3. 现在开始思考如何处理下面的数组。

    [
      "",
      "const a = 1;" /* 整行需要标识 */,
      "const b = 2;" /* 前5个字符需要标识 */,
      "",
    ];
    
    1
    2
    3
    4
    5
    6

    此时我们需要三个信息:

    1. 打印的函数范围?起始行数以及终止行数。
    2. 记录标记的第 k 行的,第 i 列到第 j 列。

    此时,需要抽象出 getMarkerLines 函数,用于生成一个临时结构:

    const {
      start /* 需要打印的初始行数,与 aboveLines 与 belowLinse 这两个 opts 有关 */,
      end /* 需要打印的结束结束行数,与 aboveLines 与 belowLinse 这两个 opts 有关 */,
      markerLines /* key-当前行数,value-数组【startCol,len】 */,
    } = getMarkerLines(loc, lines, opts);
    
    1
    2
    3
    4
    5

    其中,markerLines 结构如下:

    {
      "2": [ 1,12 ],
      "3": [ 0,5,],
    }
    
    1
    2
    3
    4

    使用正则转为数组,只截取 start 到 end 即可,后续在 .map 中编写核心逻辑

    highlightedLines.split(NEWLINE, end).slice(start, end).map(()=>{....})
    
    1
  4. 循环体内:

    • 打印行数:number = start + 1 + index

    • 通过 markerLines[number] 可以判读当前行是否需要标记。

    • 为了保证打印的格式化,通过 paddingNumber 控制是否需要打印多余空格。

    • 计算需要需要的标记符重复次数:\n + gutter.replace(/\d/g, " ") + "^".repeat(numberOfMarkers)

    • 判断是否为最后一行,如果是最后一行,则打印 message:

      markerLine += " " + opts.message;
      
      1

    最终处理结果如下:

    [
      "  1 |",
      ["> 2 | const a = 1;", "\n    | ^^^^^^^^^^^^"].join(""),
      ["> 3 | const b = 2;", "\n    | ^^^^^ 这里出错了"].join(""),
      "  4 |",
    ];
    
    1
    2
    3
    4
    5
    6

循环体内的代码,完整见 @babel/code-frame 代码:

let frame = highlightedLines
  .split(NEWLINE, end)
  .slice(start, end)
  .map((line, index) => {
    const number = start + 1 + index;
    const paddedNumber = ` ${number}`.slice(-numberMaxWidth);
    const gutter = ` ${paddedNumber} |`;
    const hasMarker = markerLines[number];
    const lastMarkerLine = !markerLines[number + 1];
    if (hasMarker) {
      let markerLine = "";
      if (Array.isArray(hasMarker)) {
        const markerSpacing = line
          .slice(0, Math.max(hasMarker[0] - 1, 0))
          .replace(/[^\t]/g, " ");
        const numberOfMarkers = hasMarker[1] || 1;
        markerLine = [
          "\n ",
          maybeHighlight(defs.gutter, gutter.replace(/\d/g, " ")),
          " ",
          markerSpacing,
          maybeHighlight(defs.marker, "^").repeat(numberOfMarkers),
        ].join("");
        if (lastMarkerLine && opts.message) {
          markerLine += " " + maybeHighlight(defs.message, opts.message);
        }
      }
      return [
        maybeHighlight(defs.marker, ">"),
        maybeHighlight(defs.gutter, gutter),
        line.length > 0 ? ` ${line}` : "",
        markerLine,
      ].join("");
    } else {
      return ` ${maybeHighlight(defs.gutter, gutter)}${
        line.length > 0 ? ` ${line}` : ""
      }`;
    }
  })
  .join("\n");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 5. 使用 ASCII 码打印颜色

若想要在控制台内打印出颜色,可以使用 ESC 让 ASCII 码进入控制字符模式:

格式如下:

ESC(进入控制模式) + [(开始) + 前景色 + ; + 背景色 + ; + 加粗 + ; + 下划线 + m(结束)

在 shell 终端执行如下代码:

# 字符串格式
echo -e "\e[36;1;4mjiasheng"
# 16 进制
echo -e "\033[36;1;4mjiasheng"
# 8 进制
echo -e "\0x1b[36;4mjiasheng"
1
2
3
4
5
6

其中,36 - 青色 | 1 - 加粗 | 4 - 下划线

image-20230521223141050

同样使用: \e[0m 可以添加去除样式,如在 jiasheng 字符串后接上一个不带格式的 wang,可以

编辑 (opens new window)
上次更新: 2023/06/16, 10:06:00
perf_hooks
03.babel-JS解析器梳理

← perf_hooks 03.babel-JS解析器梳理→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式