写一个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');