# rollup-plugin-consolelogplus

> 自动改造 console.log 打印出该 console 所处的文件和代码在原文件（未经过任何编译）中所在行数，如果是变量的话还会加上变量名。兼容rollup和vite。

Latest version **1.0.0** (published 2023-07-11) · ISC license · 0 weekly downloads

## Install

```sh
npm install rollup-plugin-consolelogplus
pnpm add rollup-plugin-consolelogplus
yarn add rollup-plugin-consolelogplus
bun add rollup-plugin-consolelogplus
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.0.0 |
| Published | 2023-07-11 |
| First published | 2023-07-11 |
| Weekly downloads | 0 |
| License | ISC |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 9 |
| Unpacked size | 21.1 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Maintainers | yonecdeng |
| Keywords | rollup-plugin, vite-plugin, console.log |

## Links

- npm: https://www.npmjs.com/package/rollup-plugin-consolelogplus
- npm.io page: https://npm.io/package/rollup-plugin-consolelogplus

## Dependencies (9)

- [typescript](https://npm.io/package/typescript.md) ^5.0.4
- [@types/node](https://npm.io/package/@types/node.md) ^20.4.1
- [@babel/types](https://npm.io/package/@babel/types.md) ^7.22.5
- [@babel/parser](https://npm.io/package/@babel/parser.md) ^7.22.7
- [@babel/traverse](https://npm.io/package/@babel/traverse.md) ^7.22.8
- [@babel/generator](https://npm.io/package/@babel/generator.md) ^7.22.7
- [@vue/compiler-sfc](https://npm.io/package/@vue/compiler-sfc.md) ^3.3.4
- [@types/babel__traverse](https://npm.io/package/@types/babel__traverse.md) ^7.20.1
- [@types/babel__generator](https://npm.io/package/@types/babel__generator.md) ^7.6.4

## Alternatives

- [raw-loader](https://npm.io/package/raw-loader.md) — 4.3M weekly downloads
- [plop](https://npm.io/package/plop.md) — 1.4M weekly downloads
- [webpack-deadcode-plugin](https://npm.io/package/webpack-deadcode-plugin.md) — 80.3K weekly downloads
- [@storybook/preact-vite](https://npm.io/package/@storybook/preact-vite.md) — 54.2K weekly downloads
- [vite-plugin-transform](https://npm.io/package/vite-plugin-transform.md) — 2.4K weekly downloads

## Recent versions

- 1.0.0 (latest) — 2023-07-11

## README

# 使用方式

## 注册插件

注册插件之后就会自动改造 console.log 打印出该 console 所处的文件和代码在原文件（未经过任何编译）中所在行数，如果是变量的话还会加上变量名。

`vite.config.ts`

```tsx
export default defineConfig({
  plugins: [vitePluginConsolelogplus()]
});
```

## 配置

```tsx
interface VitePluginConsolelogplusOptions {
  preTip: string; //打印的前缀提示，这样方便快速找到log
  splitBy: string; // 每个参数之间加个分隔符
  endTip: boolean; //是否打印后缀提示，这样方便快速找到log
}
```

## 跳过

在`console.log();`<strong style="color: red">分号前</strong>加上注释：`/*@vite-plugin-consolelogplus-skip*/`

```tsx
console.log() /*@vite-plugin-consolelogplus-skip*/;
```

# 用途

## 介绍

注册插件之后就会自动改造 console.log 打印出该 console 所处的文件和代码在原文件（未经过任何编译）中所在行数，如果是变量的话还会加上变量名。

## 场景

真机调试和生产环境下看代码在编辑器的位置（未经任何编译之前）。

比如天天领现金的 console 就比较难看 😅，而且这是个两年的项目且我是接手的角色，里面有不少 console，我不可能一个一个去处理这些 console，但是不处理确实影响了我用 console 去看一些问题，所以就用插件去处理清晰每个 console.log。![image-20230608151658345](https://picbed-1306720359.cos.ap-guangzhou.myqcloud.com/upic/2023-06-08-15-17-image-20230608151658345.png)

# 思路

## 获取精简版文件地址

```tsx
const filePath = id.replace(process.cwd(), ''); //获取精简版的文件路径
```

## 如果是 vue 文件

先用`@vue/compiler-sfc` 去解析，然后提取出里面的 script 部分给 babel 去解析（这会导致一个问题：如果解析的 script 标签 前写了其他东西，那么行数就会不正确），如果 setup 语法和 script 同时存在，那我只解析 setup 语法里面的。

```tsx
let toAstCode = ''; // 要解析成ast的代码
let vueSource = ''; // 记录vue文件里的源码，最后要返回出去给@vitejs/plugin-vue插件使用
const isVue = id.endsWith('.vue');
if (isVue) {
  const { descriptor } = vueParse(code, {
    filename: id,
    sourceMap: false
  });
  if (descriptor) {
    // 有setup语法的时候就只解析setup语法
    toAstCode = descriptor.scriptSetup?.content || descriptor.script?.content;
    vueSource = descriptor.source;
  }
} else {
  toAstCode = code;
}
```

遍历处理完代码之后，应该将 vue 的模版返回出去而不只是 script 里的代码（因为后面还要给`@vitejs/plugin-vue`插件处理），所以要将改过的 script 代码在 vue 模版里替换然后返回替换后的 vue 模版

```tsx
const { code: generatedCode, map } = generate(ast);
let resultCode = generatedCode;
if (isVue) {
  resultCode = vueSource.replace(toAstCode, generatedCode);
}
return { code: resultCode, map };
```

## 遍历 ast 阶段

1. 判断到 console.log 的 ast

```tsx
          if (
            calleeCode.type === 'MemberExpression' &&
            calleeCode.object.name === 'console' &&
            calleeCode.property.name === 'log'
          )
```

2.如果 用户通过注释的形式表示这个 console.log 不需要处理则跳过这个 console.log

```tsx
const { trailingComments } = path.node;
const shouldSkip = (trailingComments || []).some((item) => {
  return item.type === 'CommentBlock' && item.value === SKIP_KEY;
});
if (shouldSkip) return;
```

3.拿到 console.log 的 arguments，也就是 log 的参数。

```tsx
const nodeArguments = path.node.arguments;
```

4.遍历 path.node.arguments 每个参数

- 字面量的，则无须添加变量名
- 变量的，添加变量名前缀，如 `a =`
- 根据传入的分隔符插入到原始参数的后面

  5.拿到 console.log 的开始行数，创建一个包含行数的 StringLiteral，同时加上 preTip，比如上面的 🚀🚀🚀，然后 unshift，放在第一个参数的位置。

  6.拿到 console.log 的结束行数，过程跟第 5 点类似，通过 push 放到最后一个参数的位置

# 存在的问题&todo

- [ ] 如果解析的 script 标签 前写了其他东西，那么行数就会不正确（因为我是取出 vue 里面的 script 部分给 babel 去解析）

# 源码

```ts
import { parse as vueParse } from '@vue/compiler-sfc';
import { parse } from '@babel/parser';
import * as t from '@babel/types';
import _traverse from '@babel/traverse';
import _generate from '@babel/generator';
const traverse = _traverse.default;
const generate = _generate.default; //根据ast节点生成代码字符串
interface VitePluginConsolelogplusOptions {
  preTip: string; //打印的前缀提示，这样方便快速找到log
  splitBy: string; // 每个参数之间加个分隔符
  endTip: boolean; //是否打印后缀提示，这样方便快速找到log
}
const DEFAULT_PRE_TIP = '🚀🚀🚀';
const DEFAULT_SPLIT_BY = ';';
const SKIP_KEY = '@vite-plugin-consolelogplus-skip';

export default function vitePluginConsolelogplus(
  opts: VitePluginConsolelogplusOptions = {
    preTip: DEFAULT_PRE_TIP,
    splitBy: DEFAULT_SPLIT_BY,
    endTip: false
  }
) {
  const splitNode = t.stringLiteral(opts.splitBy);
  return {
    name: 'vite-plugin-consolelogplus',
    enforce: 'pre' as const, //在esbuild执行之前
    transform(code, id) {
      if (/(node_modules)/.test(id)) return null; //过滤node_modules
      const filePath = id.replace(process.cwd(), ''); //获取精简版的文件路径
      let toAstCode = ''; // 要解析成ast的代码
      let vueSource = ''; // 记录vue文件里的源码（@vue/compiler-sfc的parse之后能拿到），最后要返回出去给@vitejs/plugin-vue插件使用
      const isVue = id.endsWith('.vue');
      if (isVue) {
        const { descriptor } = vueParse(code, {
          filename: id,
          sourceMap: false
        });
        if (descriptor) {
          // 有setup语法的时候就只解析setup语法
          toAstCode =
            descriptor.scriptSetup?.content || descriptor.script?.content;
          vueSource = descriptor.source;
        }
      } else {
        toAstCode = code;
      }

      const ast = parse(toAstCode, {
        sourceType: 'module',
        plugins: ['typescript', 'jsx'] // 若要处理 TypeScript或jsx 代码，请启用插件
      });
      traverse(ast, {
        CallExpression(path) {
          //   const calleeCode = generate(path.node.callee).code;
          const calleeCode = path.node.callee;
          if (
            calleeCode.type === 'MemberExpression' &&
            calleeCode.object.name === 'console' &&
            calleeCode.property.name === 'log'
          ) {
            // add comment to skip if enter next time
            const { trailingComments } = path.node;
            const shouldSkip = (trailingComments || []).some((item) => {
              return item.type === 'CommentBlock' && item.value === SKIP_KEY;
            });
            if (shouldSkip) return;

            // t.addComment(path.node, 'trailing', SKIP_KEY);

            const nodeArguments = path.node.arguments;
            const newNodeArguments = [...nodeArguments];
            for (let i = 0, j = 0; i < nodeArguments.length; i++, j++) {
              //i 遍历原数组，j遍历新数组
              const argument = nodeArguments[i];
              if (!t.isLiteral(argument)) {
                if (t.isIdentifier(argument) && argument.name === 'undefined') {
                  //特殊case：console.log(undefined)的时候也不添加变量名（变量名会是‘undefined’）
                  newNodeArguments.splice(j + 1, 0, splitNode);
                  j++;
                  continue;
                }
                const node = t.stringLiteral(`${generate(argument).code} =`);

                newNodeArguments.splice(j, 0, node);
                j++;
                newNodeArguments.splice(j + 1, 0, splitNode);
                j++;
              } else {
                newNodeArguments.splice(j + 1, 0, splitNode);
                j++;
              }
            }
            // the last needn't split
            // if (newNodeArguments[newNodeArguments.length - 1] === splitNode)
            //   newNodeArguments.pop();
            const { loc } = path.node;
            if (loc) {
              const startLine = loc.start.line;
              const startLineTipNode = t.stringLiteral(
                `${opts.preTip}${filePath}${opts.preTip}line of ${startLine} :\n`
              );
              newNodeArguments.unshift(startLineTipNode);
              if (opts.endTip) {
                const endLine = loc.end.line;
                const endLineTipNode = t.stringLiteral(
                  `\n${opts.preTip}line of ${endLine}:\n`
                );
                newNodeArguments.push(endLineTipNode);
              }
            }
            path.node.arguments = newNodeArguments;
          }
        }
      });
      const { code: generatedCode, map } = generate(ast);
      let resultCode = generatedCode;
      if (isVue) {
        resultCode = vueSource.replace(toAstCode, generatedCode);
      }
      return { code: resultCode, map };
    }
  };
}
```

---
_Source: https://npm.io/package/rollup-plugin-consolelogplus · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
