npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

rollup-plugin-consolelogplus

v1.0.0

Published

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

Downloads

3

Readme

使用方式

注册插件

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

vite.config.ts

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

配置

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

跳过

console.log();分号前加上注释:/*@vite-plugin-consolelogplus-skip*/

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

用途

介绍

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

场景

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

比如天天领现金的 console 就比较难看 😅,而且这是个两年的项目且我是接手的角色,里面有不少 console,我不可能一个一个去处理这些 console,但是不处理确实影响了我用 console 去看一些问题,所以就用插件去处理清晰每个 console.log。image-20230608151658345

思路

获取精简版文件地址

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

如果是 vue 文件

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

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 模版

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
          if (
            calleeCode.type === 'MemberExpression' &&
            calleeCode.object.name === 'console' &&
            calleeCode.property.name === 'log'
          )

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

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 的参数。

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 去解析)

源码

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 };
    }
  };
}