Skip to content
On this page

对js编码实现函数打桩实现国际化

js编码实现函数打桩,实现自动国际化;

js
const a = '哈哈';

目标转成:

js
const a = window.$tx('哈哈');

第一步初始化

准备基础代码、以及依赖如下:

js
import * as parser from '@babel/parser';
import traverse from '@babel/traverse';
import generate from '@babel/generator';
import template from '@babel/template';
import * as types from '@babel/types';

const code = `
const a = window.$tx('国际化');
const obj = {
  a: 'test',
  b: '哈哈',
  d: 'a哈',
}
const a1 = a ? '哈哈1' : a;
const k = '我' + '是babel';
function fn(params) {
  let results  = ''
  switch (params) {
    case '国际化':
      results = '国际化'
      break;
    case 'aa':
      results = 'aa'
      break;
    default:
      break;
  }
  return results;
}
`
const ast = parser.parse(code, {
  sourceType: 'unambiguous',
});

console.log(generate(ast).code);

第二步 利用traverseast 进行改造

traverse ast=>修改过的ast

可以使用template帮咱们快速生成window.$tx 的 ast如下:

js
import template from '@babel/template';
const txTemp = template(`window.$tx(NODE_TEMPLATE)`);

也可以使用types创建window.$tx ast如下:

js
import * as types from '@babel/types';

const txCallExpression = types.callExpression(
  types.identifier('window.$tx'),  // 被调用的函数名
  [types.stringLiteral('哈哈')] // 入参数  这个是需要打桩的语言文本
)

编写转化部分程序:
使用template

js
export function isCN(value) { // 判断是不是中文
  return /[\u4E00-\u9FFF]+/g.test(value);
}

traverse(ast, {
    StringLiteral(path) { // 使用字符串字面量
      if (isCN(path.node.value)) { // path.node.value 就是文本,判断是中文就做处理
        const tem = txTemp({
          NODE_TEMPLATE: path.node,
        });
        path.replaceWith(tem); // 使用 path.replaceWith 做程序转换
        path.skip(); // 转换完成跳过本次循环
      }
    },
  });

使用types:

js
function isCN(value) { // 判断是不是中文
  return /[\u4E00-\u9FFF]+/g.test(value);
}

traverse(ast, {
    StringLiteral(path) { // 使用字符串字面量
      if (isCN(path.node.value)) { // path.node.value 就是文本,判断是中文就做处理
        const txCallExpression = types.callExpression(
          types.identifier('window.$tx'),  // 被调用的函数名
          [types.stringLiteral(path.node.value)] // 入参数  这个是需要打桩的语言文本
        ) 
        path.replaceWith(txCallExpression);
        path.skip();
      }
    },
  });

这个时候运行会发现

js
const a = window.$tx('国际化');

被转成了:

js
const a = window.$tx(window.$tx('国际化'));

这明显不是咱们想要的,添加判断如下:

js
function isCN(value) { // 判断是不是中文
  return /[\u4E00-\u9FFF]+/g.test(value);
}

traverse(ast, {
    CallExpression(path) {
      const calleeCode = generate(path.node.callee).code;
      if (calleeCode.includes('$tx')) {
        path.skip();
      }
    },

    StringLiteral(path) { // 使用字符串字面量
      if (isCN(path.node.value)) { // path.node.value 就是文本,判断是中文就做处理
        const txCallExpression = types.callExpression(
          types.identifier('window.$tx'),  // 被调用的函数名
          [types.stringLiteral(path.node.value)] // 入参数  这个是需要打桩的语言文本
        ) 
        path.replaceWith(txCallExpression);
        path.skip();
      }
    },
  });

完整代码

js
import * as parser from '@babel/parser';
import traverse from '@babel/traverse';
import generate from '@babel/generator';
import template from '@babel/template';
import * as types from '@babel/types';

const code = `
const a = window.$tx('国际化');
const obj = {
  a: 'test',
  b: '哈哈',
  d: 'a哈',
}
const a1 = a ? '哈哈1' : a;
const k = '我' + '是babel';
function fn(params) {
  let results  = ''
  switch (params) {
    case '国际化':
      results = '国际化'
      break;
    case 'aa':
      results = 'aa'
      break;
    default:
      break;
  }
  return results;
}
`
const ast = parser.parse(code, {
  sourceType: 'unambiguous',
});


function isCN(value) { // 判断是不是中文
  return /[\u4E00-\u9FFF]+/g.test(value);
}

const txTemp = template(`window.$tx(NODE_TEMPLATE)`);

traverse(ast, {
    CallExpression(path) {
      const calleeCode = generate(path.node.callee).code;
      if (calleeCode.includes('$tx')) {
        path.skip();
      }
    },

    StringLiteral(path) { // 使用字符串字面量
      if (isCN(path.node.value)) { // path.node.value 就是文本,判断是中文就做处理
        const txCallExpression = types.callExpression(
          types.identifier('window.$tx'),  // 被调用的函数名
          [types.stringLiteral(path.node.value)] // 入参数  这个是需要打桩的语言文本
        ) 
        path.replaceWith(txCallExpression);
        path.skip();

        //  const tem = txTemp({
        //   NODE_TEMPLATE: path.node,
        // });
        // path.replaceWith(tem); // 使用 path.replaceWith 做程序转换
        // path.skip(); // 转换完成跳过本次循环
      }
    },
  });

console.log(generate(ast, {
  jsescOption: {
    minimal: true, // issues https://github.com/babel/babel/issues/4909#issuecomment-397715926
  }
}).code);

输出:

bash
$ node ./dist/js.js 
const a = window.$tx('国际化');
const obj = {
  a: 'test',
  b: window.$tx("哈哈"),       
  d: window.$tx("a哈")
};
const a1 = a ? window.$tx("哈哈1") : a;
const k = window.$tx("我") + window.$tx("是babel");
function fn(params) {
  let results = '';
  switch (params) {
    case window.$tx("国际化"):
      results = window.$tx("国际化");
      break;
    case 'aa':
      results = 'aa';
      break;
    default:
      break;
  }
  return results;
}

总结

注意 generatejsescOption.minimal 设置为true, 不然就会乱码了 issues地址。 经过咱们上面的编码就完成了自动化函数打桩的过程。

Released under the MIT License.