const fs = require('fs'); const { relative, resolve, join, dirname } = require('path'); const t = require('@babel/types'); const assert = require('assert'); const { fork } = require('child_process'); const Regex = /([\\/]*[a-zA-Z0-9_-\w\W]|[\\/]*[a-zA-Z0-9_-\w\W]:)*[\\/](lib|src|es)([\\/]*[a-zA-Z0-9_-])*[\\/](pages|components)+[\\/]/; const ModuleDict = {}; const ReactNativeProjectDict = {}; const ProcessRecord = {}; function parseFileModuleAndNs(cwd, filename) { let cwd2 = cwd; if (cwd.endsWith('native')) { // react-native环境,需要重新定位一下项目根目录 if (ReactNativeProjectDict.hasOwnProperty(cwd)) { if (ReactNativeProjectDict[cwd]) { cwd2 = join(cwd, '..'); } } else { const nodeModulePath = join(cwd, '..', 'node_modules'); const packageJsonPath = join(cwd, '..', 'package.json'); if (fs.existsSync(packageJsonPath) && fs.existsSync(nodeModulePath)) { ReactNativeProjectDict[cwd] = true; cwd2 = join(cwd, '..'); } else { ReactNativeProjectDict[cwd] = false; } } } const relativePath = relative(cwd2, filename); if (relativePath.startsWith('node_modules') || relativePath.startsWith('..')) { // 在测试环境下是相对路径 // 寻找离filename最近层的package.json,看作项目名称 // 用relativePath来递进查找在操作系统层面更安全 const fileDirPaths = relativePath.replace(/\\/g, '/').split('/'); let iter = fileDirPaths.length; let filePrjRootPath = ''; let moduleName = ''; do { filePrjRootPath = join(cwd2, ...fileDirPaths.slice(0, iter)); const pkgJsonPath = join(filePrjRootPath, 'package.json'); if (fs.existsSync(pkgJsonPath)) { moduleName = ModuleDict[filePrjRootPath]; if (!moduleName) { const { name } = require(pkgJsonPath); moduleName = name; ModuleDict[filePrjRootPath] = name; } break; } else { assert(iter > 0); iter--; } } while (true); // 引用的第三方项目目前合法的只有components下的组件,pages是容一些较早的遗留代码 const fileRelPathInPrj = relative(filePrjRootPath, filename).replace(/\\/g, '/').split('/');; let ns; switch (fileRelPathInPrj[1]) { case 'pages': { ns = `${moduleName}-p-${fileRelPathInPrj.slice(2, fileRelPathInPrj.length - 1).join('-')}`; break; } default: { if (fileRelPathInPrj[1] !== 'components') { assert(false); } assert(fileRelPathInPrj[1] === 'components', fileRelPathInPrj.join('//')); ns = `${moduleName}-c-${fileRelPathInPrj.slice(2, fileRelPathInPrj.length - 1).join('-')}`; break; } } return { moduleName, ns, }; } else { let moduleName = ModuleDict['./']; if (!moduleName) { const { name } = require(join(cwd2, 'package.json')); ModuleDict['./'] = name; moduleName = name; } const rel2paths = relative(cwd2, filename) .replace(/\\/g, '/') .split('/'); let ns; switch (rel2paths[1]) { case 'pages': { ns = `${moduleName}-p-${rel2paths.slice(2, rel2paths.length - 1).join('-')}`; break; } case 'components': { ns = `${moduleName}-c-${rel2paths.slice(2, rel2paths.length - 1).join('-')}`; break; } default: { // 处理web/wechatMp中的数据 assert(rel2paths[1] === 'src'); const p1 = rel2paths[0]; if (p1 === 'web') { ns = `${moduleName}-w-${rel2paths.slice(2, rel2paths.length - 1).join('-')}`; } else if (p1 === 'wechatMp') { ns = `${moduleName}-wmp-${rel2paths.slice(2, rel2paths.length - 1).join('-')}`; } else { assert(p1.startsWith('wechatMp')); const iter = parseInt(p1.slice(8), 10); ns = `${moduleName}-wmp${iter}-${rel2paths.slice(2, rel2paths.length - 1).join('-')}`; } break; } } return { moduleName, ns, }; } } const oakNsPropName = '#oakNamespace'; const oakModulePropName = '#oakModule'; module.exports = (babel) => { return { visitor: { CallExpression(path, state) { const { cwd, filename } = state; const res = resolve(cwd, filename).replace(/\\/g, '/'); // this.props.t/this.t/t // 处理策略为给第二个参数中加上'#oakNameSpace, #oakModule两个参数,告知t模块此文件相应的位置,再加以处理寻找 if ( /(pages|components|namespaces)[\w|\W]+(.tsx|.ts|.jsx|.js)$/.test(res) ) { const { node } = path; if ( node && node.callee && ((t.isIdentifier(node.callee) && node.callee.name === 't') || (t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.property) && node.callee.property.name === 't')) ) { const { moduleName, ns } = parseFileModuleAndNs(cwd, filename); const arguments = node.arguments; // react-native会调用两次,这里要保护一下 // 代码看上去可以避免被调用两次,rn待测试 by Xc 20240108 const [arg0, arg1] = arguments; assert(arg0); if (arg1) { // 一般是对象,也可能是变量,表达式不予考虑 // 加上Object.assign({ '#oakNamespace': xxx, '#oakModule': xxx }, arg1); if (t.isCallExpression(arg1) && t.isMemberExpression(arg1.name) && t.isIdentifier(arg1.name.object) && arg1.name.object.name === 'Object' && t.isIdentifier(arg1.name.property) && arg1.name.property.name === 'assign') { // 不处理,这里似乎会被反复调用,不知道为什么 } else { arguments.splice(1, 1, t.callExpression( t.memberExpression( t.identifier('Object'), t.identifier('assign') ), [ t.objectExpression( [ t.objectProperty(t.stringLiteral(oakNsPropName), t.stringLiteral(ns)), t.objectProperty(t.stringLiteral(oakModulePropName), t.stringLiteral(moduleName)) ] ), arg1 ] )); } /* if (t.isObjectExpression(arg1)) { const { properties } = arg1; const oakNsProp = properties.find( ele => t.isStringLiteral(ele.key) && ele.key.value === oakNsPropName ); if (!oakNsProp) { properties.push( t.objectProperty(t.stringLiteral(oakNsPropName), t.stringLiteral(ns)), t.objectProperty(t.stringLiteral(oakModulePropName), t.stringLiteral(moduleName)) ); } } else if (t.isIdentifier(arg1)) { arguments.splice(1, 1, t.callExpression( t.memberExpression( t.identifier('Object'), t.identifier('assign') ), [ arg1, t.objectExpression( [ t.objectProperty(t.stringLiteral(oakNsPropName), t.stringLiteral(ns)), t.objectProperty(t.stringLiteral(oakModulePropName), t.stringLiteral(moduleName)) ] ) ] )); } else { // 不处理,这里似乎会反复调用,不知道为什么 } */ } else { // 如果无参数就构造一个对象传入 arguments.push( t.objectExpression( [ t.objectProperty(t.stringLiteral(oakNsPropName), t.stringLiteral(ns)), t.objectProperty(t.stringLiteral(oakModulePropName), t.stringLiteral(moduleName)) ] ) ); } } } }, }, }; };