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