写一个vite插件处理console

写一个vite插件去除代码中的console

使用babel做处理。简单处理,复杂情况未考虑。
学习babel写的demo。

项目根目录新建文件
babel-plugin-remove-console.js
rollup-plugin-remove-console.js

在这里插入图片描述

babel-plugin-remove-console.js

import { declare } from '@babel/helper-plugin-utils';

const removeConsolePlugin = declare((api, options, dirname) => {
  api.assertVersion(7);

  return {
    visitor: {
      CallExpression(path) {
        // 检查是否是console.method()调用
        const { callee } = path.node;
        if (
          callee.type === 'MemberExpression' &&
          callee.object.type === 'Identifier' &&
          callee.object.name === 'console' &&
          callee.property.type === 'Identifier'
        ) {
          // 如果是独立语句 (ExpressionStatement),直接移除整个语句
          if (path.parent.type === 'ExpressionStatement') {
            path.parentPath.remove();
          }
          // 否则,替换为undefined (避免语法错误)
          else {
            path.replaceWith(api.types.identifier('undefined'));
          }
          return;
        }
      },
    },
  };
});

export default removeConsolePlugin;

为什么要处理这些呢

 callee.type === 'MemberExpression' &&
          callee.object.type === 'Identifier' &&
          callee.object.name === 'console' &&
          callee.property.type === 'Identifier'

https://astexplorer.net/ 可以尝试下
在这里插入图片描述

当然也可以写成这样

 visitor: {
      MemberExpression(path){
      if(path.node.object.name=='console'){
        console.log(path.parentPath.node)
      		path.parentPath.remove();
      	}
      }
    },

rollup-plugin-remove-console.js

import { createFilter } from '@rollup/pluginutils';
import { transformFromAstSync } from '@babel/core';
import parser from '@babel/parser';
import removeConsolePlugin from './babel-plugin-remove-console';
export default function myPlugin(pluginOptions = {}) {
  const defaultExclude = /node_modules/;

  // 如果用户提供了exclude选项,合并默认排除
  const excludePattern = pluginOptions.exclude
    ? [defaultExclude, pluginOptions.exclude]
    : defaultExclude;
  const filter = createFilter(
    pluginOptions.include || /\.(js|ts|jsx|tsx|vue)$/,
    excludePattern
  );

  return {
    name: 'rollup-plugin-remove-console',

    transform(src, id) {
      if (!filter(id)) {
        return null;
      }
      const ast = parser.parse(src, {
        sourceType: 'unambiguous',
      });

      const { code, map } = transformFromAstSync(ast, src, {
        plugins: [[removeConsolePlugin]],
      });
      return {
        code,
        map, // 或者提供一个 sourcemap 对象
      };
    },
  };
}

vite.config.js引入使用

在这里插入图片描述
如果你使用了多个插件,需要把自己定义的这个去除插件放到最后面,等其他代码都转换完毕后,只需要处理js语法即可。
比如我们这里引入了vuejsx,支持vuejsx语法。
在这里插入图片描述

他是怎么处理的呢。
一般vue编译的时候,会把vue文件中的,样式,模版,脚本拆分。
在这里插入图片描述

我这里的jsx写法,所以是lang.jsx
在这里插入图片描述
经过vue,vuejsx插件的加工后,成了这样
在这里插入图片描述
所以我们只需要考虑console本身即可。

扩展

忽略某些

增加配置来处理,如,我们可以配置console的哪些方法不移除或者哪些方法移除。
在这里插入图片描述
在vite插件中,将pluginOptions传递给babel插件,这里文件名起的是rollup-plugin-remove-console.js因为没用到vite的特性hooks所以也支持rollup(按理来说,不过没有测试)。

在这里插入图片描述
在这里插入图片描述
可以看到传递过来的参数。
在这里插入图片描述

怎么获取是log还是warn还是error呢。
在这里插入图片描述

在这里插入图片描述
所以要获取下 const name = path.node.property.name;

import { declare } from '@babel/helper-plugin-utils';
const removeConsolePlugin = declare((api, options, dirname) => {
  api.assertVersion(7);
  const ignores = options.ignore || [];
  return {
    visitor: {
      MemberExpression(path) {
        if (path.node.object.name == 'console') {
          const name = path.node.property.name;
          const isIgnore = ignores.includes(name);
          if (!isIgnore) {
            path.parentPath.remove();
          }
        }
      },
    },
  };
});

export default removeConsolePlugin;

或者

import { declare } from '@babel/helper-plugin-utils';
const removeConsolePlugin = declare((api, options, dirname) => {
  api.assertVersion(7);
  const ignores = options.ignore || [];
  return {
    visitor: {
      CallExpression(path) {
        // 检查是否是console.method()调用
        const { callee } = path.node;
        if (
          callee.type === 'MemberExpression' &&
          callee.object.type === 'Identifier' &&
          callee.object.name === 'console' &&
          callee.property.type === 'Identifier'
        ) {
          const name = callee.property.name;
          const isIgnore = ignores.includes(name);
          if (!isIgnore) {
            // 如果是独立语句 (ExpressionStatement),直接移除整个语句
            if (path.parent.type === 'ExpressionStatement') {
              path.parentPath.remove();
            }
            // 否则,替换为undefined (避免语法错误)
            else {
              path.replaceWith(api.types.identifier('undefined'));
            }
          }
        }
      },
    },
  };
});

export default removeConsolePlugin;

在这里插入图片描述

替换

比如我们有这样两个个函数。上报数据,上报异常。
在这里插入图片描述
假设,在开发环境我们不需要上报,也就是开发环境不替换,一般也是开发环境不替换。
我们需要在插件运行前获取环境
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
传递给babel插件
在这里插入图片描述
当然其实这一步不用,你可以在babe插件里面直接获取。
在babel插件里面接收下。在这里插入图片描述

如果不是开发环境就执行插件。
在这里插入图片描述
或者我们简单点。

在vite插件中直接不往下走了,不执行babel插件了。
在这里插入图片描述
然后继续完善替换的逻辑。
假设我们的插件是这样传递参数的。
在这里插入图片描述
babel获取下
在这里插入图片描述

替换的逻辑为
当匹配上的时候,把原先的参数带进去,再额外携带一个文件的信息。

在这里插入图片描述

source为来源。
在这里插入图片描述
source大概这样
在这里插入图片描述
然后我们看下效果。
开发环境
在这里插入图片描述

build后
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

完整代码

rollup-plugin-remove-console.js

rollup-plugin-remove-console.js

import { createFilter } from '@rollup/pluginutils';
import { transformFromAstSync } from '@babel/core';
import parser from '@babel/parser';
import removeConsolePlugin from './babel-plugin-remove-console';
export default function myPlugin(pluginOptions = {}) {
  const defaultExclude = /node_modules/;
  let isDev = false;

  // 如果用户提供了exclude选项,合并默认排除
  const excludePattern = pluginOptions.exclude
    ? [defaultExclude, pluginOptions.exclude]
    : defaultExclude;
  const filter = createFilter(
    pluginOptions.include || /\.(js|ts|jsx|tsx|vue)$/,
    excludePattern
  );
  // console.log(pluginOptions);

  return {
    name: 'rollup-plugin-remove-console',
    options(inputOptions) {
      isDev = process.env.NODE_ENV === 'development';
      console.log('isDev', isDev);
      return inputOptions;
    },

    transform(src, id) {
      if (!filter(id) || isDev) {
        return null;
      }

      const ast = parser.parse(src, {
        sourceType: 'unambiguous',
      });
      const paths = id.split('/');
      const source = paths[paths.length - 1];
      console.log(source);
      const { code, map } = transformFromAstSync(ast, src, {
        plugins: [[removeConsolePlugin, { ...pluginOptions, source, isDev }]],
      });

      return {
        code,
        map, // 或者提供一个 sourcemap 对象
      };
    },
  };
}

babel-plugin-remove-console.js

babel-plugin-remove-console.js

import { declare } from '@babel/helper-plugin-utils';
import { types as t } from '@babel/core';

const removeConsolePlugin = declare((api, options, dirname) => {
  api.assertVersion(7);
  const ignores = options.ignore || [];
  const replaceList = options.replaceList || [];
  const source = options.source;
  let isDev = process.env.NODE_ENV == 'development';
  if (typeof options.isDev != 'undefined') {
    isDev = options.isDev;
  }
  return {
    visitor: {
      MemberExpression(path) {
        if (path.node.object.name == 'console' && !isDev) {
          const name = path.node.property.name;
          const replaceItem = replaceList.find((item) => item[0] === name);
          if (replaceItem) {
            const replaceName = replaceItem[1];
            if (!replaceName) {
              console.warn('请配置替换的函数');
            }
            if (replaceList.length > 0) {
              const args = path.parentPath.node.arguments;
              const loggerCall = t.callExpression(t.identifier(replaceName), [
                ...args,
                t.stringLiteral(source),
              ]);
              loggerCall.isDone = true;
              path.parentPath.replaceWith(loggerCall);
            }
          }

          const isIgnore = ignores.includes(name);
          if (!isIgnore) {
            path.parentPath.remove();
          }
        }
      },
    },
  };
});

export default removeConsolePlugin;

vite.config.js

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import rollupPluginRemoveConsole from './rollup-plugin-remove-console.js';
import vueJsx from '@vitejs/plugin-vue-jsx';

// https://vite.dev/config/
export default defineConfig({
  //
  plugins: [
    vue(),
    vueJsx(),
    rollupPluginRemoveConsole({
      ignore: ['log', 'error'],
      replaceList: [
        ['log', 'uploadLog'],
        ['error', 'uploadError'],
      ],
    }),
  ],
  base: './',
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000/',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
});

main.js

import { createApp } from 'vue';
import './style.css';
import App from './App.vue';

const upData = (type, args) => {
  fetch(`/api/${type}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(args),
  });
};

window.uploadLog = (...args) => {
  upData('log', args);
};
window.uploadError = (...args) => {
  upData('error', args);
  // fetch
};

createApp(App).mount('#app');

### 如何在 Vite 构建时保留 `console` 日志 Vite 默认会在生产构建过程中移除所有的 `console` 语句,这是因为它使用了 Rollup 的 `terser` 插件来进行代码压缩和优化。如果需要在打包后的文件中保留这些 `console` 语句,可以通过调整配置实现。 #### 方法一:禁用 Terser 压缩器的日志删除功能 可以在 `vite.config.js` 中设置 `build.terserOptions.compress.drop_console` 属性为 `false` 来阻止移除 `console` 语句: ```javascript import { defineConfig } from 'vite'; export default defineConfig({ build: { terserOptions: { compress: { drop_console: false, // 不移除 console 语句 }, }, }, }); ``` 此方法适用于使用默认的 Terser 进行代码压缩的情况[^1]。 #### 方法二:自定义 Rollup 插件行为 如果你正在使用其他工具链或者想要更灵活地控制日志的行为,也可以通过引入第三方插件来完成这一需求。例如,`rollup-plugin-terser` 提供了类似的选项。 安装依赖: ```bash npm install rollup-plugin-terser --save-dev ``` 然后在 `vite.config.js` 文件中进行如下配置: ```javascript import { defineConfig } from 'vite'; import terser from 'rollup-plugin-terser'; const { terser: terserPlugin } = terser; export default defineConfig({ build: { rollupOptions: { plugins: [ terserPlugin({ // 自定义 terser 行为 compress: { drop_console: false, }, }), ], }, }, }); ``` 这种方法提供了更大的灵活性,并允许开发者进一步定制构建过程中的具体细节[^2]。 #### 方法三:利用环境变量区分开发与生产模式下的日志记录方式 另一种推荐的方式是基于运行环境动态决定是否打印日志信息。比如,在开发环境中保持完整的日志输出;而在生产环境下则有条件地过滤掉部分敏感或冗余的信息。 示例代码片段展示如何根据 NODE_ENV 判断当前所处状态并相应处理: ```javascript if (process.env.NODE_ENV !== 'production') { console.log('This message will only appear during development.'); } ``` 这样既满足了调试期间对于详尽反馈的需求,又能在正式上线前有效减少无意义的数据暴露风险[^3]。 --- ### 总结 以上介绍了三种不同的策略帮助你在采用 Vite 工具链的情况下维持必要的控制台消息可见度。无论是简单修改官方内置参数还是深入探索外部扩展可能性都各有优劣,请依据实际项目情况做出最佳抉择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码哈士奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值