Babel 在参数全为字面量的纯函数调用处替换为其返回值
需求
对于参数全为字面量的纯函数,可以在其调用处使用其返回值计算,以简化代码
纯函数和字面量相关概念请搜索引擎走起
下面是代码样例(encode.js)
function test(x, y, z) {
x += 2;
return x + y * z;
}
var a = test(11, 20, 1);
console.log('a is ' + a);
处理后代码(decode.js)
function test(x, y, z) {
x += 2;
return x + y * z;
}
var a = 33;
console.log('a is ' + a);
思路
- 特征判断要准确,参数需全为字面量
- 将函数定义
eval
到本地,然后根据作用域寻找到函数调用处,然后eval
调用语句(需要使用try
包起来防止报错,如函数非纯函数)。
编写 babel 插件
完整插件代码如下
// decrypt.js
const fs = require('fs');
var util = require('util');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const types = require('@babel/types');
const generator = require('@babel/generator').default;
// 程序启动时间
var time_start = new Date().getTime()
// 读取文件
process.argv.length > 2 ? encode_file = process.argv[2] : encode_file = 'encode.js';
process.argv.length > 3 ? decode_file = process.argv[3] : decode_file = 'decode.js';
let jscode = fs.readFileSync(encode_file, { encoding: 'utf-8' });
console.log(util.format('Reading the file [%s] is complete.', encode_file))
// 转换为 ast 树
let ast = parser.parse(jscode);
const visitor =
{
FunctionDeclaration(path) {
let func_str = path.toString();
if (func_str.indexOf('random') != -1 || func_str.indexOf('Date') != -1 || (func_str.indexOf('try') != -1 && func_str.indexOf('catch') != -1)) {
// 处理的函数参数固定时,函数结果必须唯一
return;
}
// 将函数定义到本地
eval(func_str);
let { id } = path.node;
let binding = path.scope.getBinding(id.name);
for (let refer_path of binding.referencePaths) {
// let call_path = refer_path.findParent(p => p.isCallExpression());
let call_path = refer_path.parentPath;
if (!types.isCallExpression(call_path)) {
continue;
}
let { arguments } = call_path.node;
if (arguments.length === 0) {
break;
}
let break_flag = false;
for (let arg of arguments) {
if (!types.isLiteral(arg)) {
// 只处理参数全为字面量的函数
break_flag = true;
break;
}
}
if (break_flag) {
break;
}
try {
// 防止函数执行出错导致程序报错,如函数中使用了全局变量肯定会报错(因为没把全局变量定义到本地)
let func_retrun = eval(call_path.toString());
if (func_retrun !== undefined) {
// 函数一般不会返回 undefined ,故排除此种情况
call_path.replaceWith(types.valueToNode(func_retrun));
}
} catch (e) { }
break;
}
// 手动更新 scope ,防止影响下个插件使用
path.scope.crawl();
}
}
//调用插件,处理待处理 js ast 树
traverse(ast, visitor);
console.log('AST traverse completed.')
// 生成处理后的 js
let { code } = generator(ast);
console.log('AST generator completed.')
fs.writeFile(decode_file, code, (err) => { });
console.log(util.format('The javascript code in [%s] has been processed.', encode_file))
console.log(util.format('The processing result has been saved to [%s].', decode_file))
// 程序结束时间
var time_end = new Date().getTime()
console.log(util.format('The program runs to completion, time-consuming: %s s', (time_end - time_start) / 1000))