UNPKG

i18-fe-automator

Version:

前端代码提取中文并替换成$t函数

499 lines (498 loc) 25.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const uuid_1 = require("uuid"); const traverse_1 = __importDefault(require("@babel/traverse")); const generator_1 = __importDefault(require("@babel/generator")); const template_1 = __importDefault(require("@babel/template")); const isEmpty_1 = __importDefault(require("lodash/isEmpty")); const collector_1 = __importDefault(require("./collector")); const includeChinese_1 = require("./utils/includeChinese"); const assertType_1 = require("./utils/assertType"); const escapeQuotes_1 = require("./utils/escapeQuotes"); const constants_1 = require("./utils/constants"); const stateManager_1 = __importDefault(require("./utils/stateManager")); const removeLineBreaksInTag_1 = require("./utils/removeLineBreaksInTag"); const log_1 = __importDefault(require("./utils/log")); const formatKey_1 = __importDefault(require("./utils/formatKey")); const t = require('@babel/types'); let currCollector = collector_1.default.getInstance(); function getObjectExpression(obj) { const ObjectPropertyArr = []; Object.keys(obj).forEach((k) => { const tempValue = obj[k]; let newValue; if ((0, assertType_1.isObject)(tempValue)) { newValue = tempValue.value; } else { newValue = t.identifier(tempValue); } ObjectPropertyArr.push(t.objectProperty(t.identifier(k), newValue)); }); const ast = t.objectExpression(ObjectPropertyArr); return ast; } function getDeepObjVal(myObj, val) { // 如果不是个对象,return随机数 if (!myObj) return (0, formatKey_1.default)(undefined, 'debug71'); if (typeof myObj === 'object') { const newVal = myObj[val] || myObj['']; return typeof newVal === 'object' ? getDeepObjVal(newVal, val) : newVal; } return val; } // 判断节点是否是props属性的默认值 function isPropNode(path) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; const objWithProps = (_d = (_c = (_b = (_a = path.parentPath) === null || _a === void 0 ? void 0 : _a.parentPath) === null || _b === void 0 ? void 0 : _b.parentPath) === null || _c === void 0 ? void 0 : _c.parentPath) === null || _d === void 0 ? void 0 : _d.parent; const rootNode = (_k = (_j = (_h = (_g = (_f = (_e = path.parentPath) === null || _e === void 0 ? void 0 : _e.parentPath) === null || _f === void 0 ? void 0 : _f.parentPath) === null || _g === void 0 ? void 0 : _g.parentPath) === null || _h === void 0 ? void 0 : _h.parentPath) === null || _j === void 0 ? void 0 : _j.parentPath) === null || _k === void 0 ? void 0 : _k.parent; let isMeetProp = false; let isMeetKey = false; let isMeetContainer = false; // 属性是否包含在props结构里 if (objWithProps && objWithProps.type === 'ObjectProperty' && objWithProps.key.type === 'Identifier' && objWithProps.key.name === 'props') { isMeetProp = true; } // 对应key是否是default if (path.parent && path.parent.type === 'ObjectProperty' && path.parent.key.type === 'Identifier' && path.parent.key.name === 'default') { isMeetKey = true; } // 遍历到指定层数后是否是导出声明 if (rootNode && rootNode.type === 'ExportDefaultDeclaration') { isMeetContainer = true; } return isMeetProp && isMeetKey && isMeetContainer; } function getStringLiteral(value) { return Object.assign(t.StringLiteral(value), { extra: { raw: `'${value}'`, rawValue: value, }, }); } function nodeToCode(node) { return (0, generator_1.default)(node).code; } // 允许往react函数组件中加入自定义代码 function insertSnippets(node, snippets) { if (node.body.type === 'BlockStatement' && snippets) { const returnStatement = node.body.body.find((node) => node.type === 'ReturnStatement'); if (returnStatement) { const arg = returnStatement.argument; const argType = arg === null || arg === void 0 ? void 0 : arg.type; const code = nodeToCode(node); // 函数是否是react函数组件 // 情况1: 返回的三元表达式包含JSXElement // 情况2: 直接返回了JSXElement if (argType === 'ConditionalExpression' && (arg.consequent.type === 'JSXElement' || arg.alternate.type === 'JSXElement')) { if ((0, includeChinese_1.includeChinese)(code)) { const statements = template_1.default.statements(snippets)(); node.body.body.unshift(...statements); } } else if (argType === 'JSXElement') { const statements = template_1.default.statements(snippets)(); node.body.body.unshift(...statements); } } } } function transformJs(code, options) { const { rule, sourceContent, collector } = options; const isTransformKey = !!sourceContent; const { caller, functionName, customizeKey, importDeclaration, functionSnippets, forceImport } = rule; currCollector = collector || currCollector || collector_1.default.getInstance(); let hasImportI18n = false; // 文件是否导入过i18n let hasTransformed = false; // 文件里是否存在中文转换,有的话才有必要导入i18n function getCallExpression(identifier, quote = "'") { // if (isTransformKey) { // log.debug('getCallExpression:identifier', identifier) // } const expression = `${functionName}({key: '', desc: ${quote}${identifier}${quote}})`; // log.debug('expression--end', expression) return expression; } function getReplaceValue(value, params) { let targetKeyLiteral = t.stringLiteral(''); // log.debug('172::::targetKeyLiteral') // 需要过滤处理引号和换行 value = (0, removeLineBreaksInTag_1.removeLineBreaksInTag)((0, escapeQuotes_1.escapeQuotes)(value)); if (isTransformKey && value) { // log.debug('转换uuidEnKey') const uuidEnKey = (0, formatKey_1.default)(sourceContent[value] || getDeepObjVal(sourceContent, value) || sourceContent[(0, removeLineBreaksInTag_1.removeLineBreaksInTag)((0, escapeQuotes_1.escapeQuotes)(value))] || getDeepObjVal(sourceContent, (0, removeLineBreaksInTag_1.removeLineBreaksInTag)((0, escapeQuotes_1.escapeQuotes)(value))), 'debug186'); const uuidEnKeyStr = `${(0, uuid_1.v5)(currCollector.getCurrentCollectorPath(), uuid_1.v5.URL).slice(0, 6)}-${uuidEnKey}`; log_1.default.debug('转换uuidEnKey', uuidEnKeyStr); targetKeyLiteral = t.stringLiteral(uuidEnKeyStr); currCollector.add(uuidEnKeyStr, customizeKey, value); } // @TODO: 后面改成可配置 // 表达式结构 obj.fn('xx',{xx:xx}) let expression; // i18n标记有参数的情况 if (params) { // log.debug('keyLiteral209', value) const keyLiteral = getStringLiteral(customizeKey(value, currCollector.getCurrentCollectorPath())); // log.debug('keyLiteral210', keyLiteral) if (caller) { // @TODO: 后面改成可配置 // return t.callExpression( // t.memberExpression(t.identifier(caller), t.identifier(functionName)), // [keyLiteral, getObjectExpression(params)] // ) // return t.callExpression( // // @TODO: 待改善 // t.memberExpression(t.identifier(caller), t.identifier(functionName)), // [ // t.objectExpression([ // t.objectProperty(t.identifier('key'), targetKeyLiteral), // t.objectProperty(t.identifier('desc'), keyLiteral), // ]), // getObjectExpression(params), // ] // ) return t.callExpression( // @TODO: 待改善 t.identifier(functionName), [ t.objectExpression([ t.objectProperty(t.identifier('key'), targetKeyLiteral), t.objectProperty(t.identifier('desc'), keyLiteral), ]), getObjectExpression(params), ]); } else { // return t.callExpression(t.identifier(functionName), [ // keyLiteral, // getObjectExpression(params), // ]) return t.callExpression(t.identifier(functionName), [ t.objectExpression([ t.objectProperty(t.identifier('key'), targetKeyLiteral), t.objectProperty(t.identifier('desc'), keyLiteral), ]), getObjectExpression(params), ]); } } else { // i18n标记没参数的情况 expression = getCallExpression(customizeKey(value, currCollector.getCurrentCollectorPath())); return template_1.default.expression(expression)(); } } function transformAST(code, options) { function getTraverseOptions() { return { // 第二次遍历,判断是否需要把key进行转换, isTransformKey enter(path) { const leadingComments = path.node.leadingComments; if (leadingComments) { // 是否跳过翻译 let isSkipTransform = false; // 如果包含i18-ignore,就跳过翻译 leadingComments.every((comment) => { if (comment.value.includes(constants_1.IGNORE_REMARK)) { isSkipTransform = true; return false; } return true; }); if (isSkipTransform) { path.skip(); } } }, StringLiteral(path) { const value = path.node.value; log_1.default.debug('StringLiteral----', value); // if (isTransformKey) { // path.skip() // return // } if (isTransformKey) { // log.debug('StringLiteral:val--a', value) } // 处理vue props里的中文 log_1.default.debug('300' + value); log_1.default.debug('301' + options.isJsInVue); log_1.default.debug('302' + isPropNode(path)); log_1.default.debug('303' + isTransformKey); if ((0, includeChinese_1.includeChinese)(value) && options.isJsInVue && isPropNode(path) && !isTransformKey) { // if (includeChinese(value) && options.isJsInVue && isPropNode(path)) { log_1.default.debug('这里应该是第一次'); const expression = `function() { return ${getCallExpression(value)} }`; currCollector.add(value, customizeKey); path.replaceWith(template_1.default.expression(expression)()); path.skip(); return; } if (isTransformKey) { log_1.default.debug('第二次:StringLiteral,存在isTransformKey,:val'); } if ((0, includeChinese_1.includeChinese)(value) && !isTransformKey) { log_1.default.debug('非isJSInVue或者非isPropNode, StringLiteral:val--b' + value); // if (includeChinese(value)) { hasTransformed = true; // 第一次执行的逻辑,其实无用 currCollector.add(value, customizeKey); try { path.replaceWith(getReplaceValue(value)); } catch (_) { log_1.default.error(`jsx-string替换出错,检查${currCollector.getCurrentCollectorPath()}是否存在有中文当key的情况`); } } path.skip(); }, TemplateLiteral(path) { // log.debug('TemplateLiteral----', path) // if (isTransformKey) { // path.skip() // return // } const { node } = path; const templateMembers = [...node.quasis, ...node.expressions]; templateMembers.sort((a, b) => a.start - b.start); const shouldReplace = node.quasis.some((node) => (0, includeChinese_1.includeChinese)(node.value.raw)); // if (shouldReplace && !isTransformKey) { if (shouldReplace) { let value = ''; let slotIndex = 1; const params = {}; templateMembers.forEach(function (node) { if (node.type === 'Identifier') { value += `{${node.name}}`; params[node.name] = node.name; } else if (node.type === 'TemplateElement') { value += node.value.raw.replace(/[\r\n]/g, ''); // 用raw防止字符串中出现 /n } else if (node.type === 'MemberExpression') { const key = `slot${slotIndex++}`; value += `{${key}}`; params[key] = { isAstNode: true, value: node, }; } else { // 处理${}内容为表达式的情况。例如`测试${a + b}`,把 a+b 这个语法树作为params的值, 并自定义params的键为slot加数字的形式 const key = `slot${slotIndex++}`; value += `{${key}}`; const expression = (0, generator_1.default)(node).code; const tempAst = transformAST(expression, options); const expressionAst = tempAst.program.body[0].expression; params[key] = { isAstNode: true, value: expressionAst, }; } }); hasTransformed = true; // log.debug('currCollector:value', value) if (!isTransformKey) { currCollector.add(value, customizeKey); const slotParams = (0, isEmpty_1.default)(params) ? undefined : params; path.replaceWith(getReplaceValue(value, slotParams)); } else { // currCollector.add(value, customizeKey) const slotParams = (0, isEmpty_1.default)(params) ? undefined : params; // 内部会add path.replaceWith(getReplaceValue(value, slotParams)); } } }, JSXText(path) { const value = path.node.value; if (isTransformKey) { log_1.default.debug('JSX skip'); path.skip(); return; } if ((0, includeChinese_1.includeChinese)(value) && !isTransformKey) { hasTransformed = true; currCollector.add(value.trim(), customizeKey); path.replaceWith(t.JSXExpressionContainer(getReplaceValue(value.trim()))); } path.skip(); }, JSXAttribute(path) { var _a; const node = path.node; const valueType = (_a = node.value) === null || _a === void 0 ? void 0 : _a.type; if (valueType === 'StringLiteral' && node.value && (0, includeChinese_1.includeChinese)(node.value.value) && !isTransformKey) { const value = node.value.value; const jsxIdentifier = t.jsxIdentifier(node.name.name); const jsxContainer = t.jSXExpressionContainer(getReplaceValue(value)); hasTransformed = true; currCollector.add(value, customizeKey); path.replaceWith(t.jsxAttribute(jsxIdentifier, jsxContainer)); path.skip(); } if (isTransformKey) { log_1.default.debug('JSXAttribute skip'); } }, // 函数调用表达式 CallExpression(path) { // log.debug('CallExpression----', path) const { node } = path; const callee = node.callee; // 根据全局配置,跳过不需要提取的函数 const globalRule = stateManager_1.default.getToolConfig().globalRule; // log.debug('是否转换key:CallExpression', isTransformKey) // log.debug(callee) if (isTransformKey && callee.type === 'Identifier' && callee.name === functionName) { // 第一个参数 const firstArgument = node.arguments[0]; // 判断是否一个对象 if (firstArgument && firstArgument.type === 'ObjectExpression') { const properties = firstArgument.properties; // log.debug('整个对象', properties) const keyNode = properties.find((currNode) => currNode.key.name === 'key') || {}; const descNode = properties.find((currNode) => currNode.key.name === 'desc') || {}; if (keyNode.type === 'ObjectProperty' && keyNode.value.value === '' && descNode.value && (0, includeChinese_1.includeChinese)(descNode.value.value)) { log_1.default.debug(descNode.value.value); // log.debug('descNode.value.value--', descNode.value.value) let newValue = (0, formatKey_1.default)(sourceContent[descNode.value.value] || getDeepObjVal(sourceContent, descNode.value.value) || sourceContent[(0, removeLineBreaksInTag_1.removeLineBreaksInTag)((0, escapeQuotes_1.escapeQuotes)(descNode.value.value))] || getDeepObjVal(sourceContent, (0, removeLineBreaksInTag_1.removeLineBreaksInTag)((0, escapeQuotes_1.escapeQuotes)(descNode.value.value))), 'debug468'); newValue = `${(0, uuid_1.v5)(currCollector.getCurrentCollectorPath(), uuid_1.v5.URL).slice(0, 6)}-${newValue}`; // path.replaceWith(getReplaceValue(value)) keyNode.value = getStringLiteral(newValue); hasTransformed = true; currCollector.add(newValue, customizeKey, descNode.value.value); // path.replaceWith(node) // keyNode.value.value = newValue // keyNode.value.extra = { // raw: `'${newValue}'`, // rawValue: newValue, // } } } path.skip(); return; } const code = nodeToCode(node); globalRule.ignoreMethods.forEach((ignoreRule) => { if (code.startsWith(ignoreRule)) { path.skip(); return; } }); // 跳过console.log的提取 if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'console') { path.skip(); return; } // 无调用对象的情况,例如$hxt('xx') 并且没有源内容 if (callee.type === 'Identifier' && callee.name === functionName && !sourceContent) { path.skip(); return; } // 有调用对象的情况,例如this.$hxt('xx')、i18n.$hxt('xx) if (callee.type === 'MemberExpression' && !sourceContent) { if (callee.property && callee.property.type === 'Identifier') { if (callee.property.name === functionName) { // 处理形如i18n.$hxt('xx)的情况 if (callee.object.type === 'Identifier' && callee.object.name === caller) { path.skip(); return; } // 处理形如this.$hxt('xx')的情况 if (callee.object.type === 'ThisExpression' && caller === 'this') { path.skip(); return; } } } } }, ImportDeclaration(path) { // log.debug('ImportDeclaration----', path) const res = importDeclaration.match(/from ["'](.*)["']/); const packageName = res ? res[1] : ''; if (path.node.source.value === packageName) { hasImportI18n = true; } if (!hasImportI18n && hasTransformed) { const importAst = template_1.default.statements(importDeclaration)(); const program = path.parent; importAst.forEach((statement) => { program.body.unshift(statement); }); hasImportI18n = true; } }, ArrowFunctionExpression(path) { // log.debug('ArrowFunctionExpression----', path) const { node } = path; // 函数组件必须在代码最外层 if (path.parentPath.scope.block.type !== 'Program') { return; } // 允许往react函数组件中加入自定义代码 insertSnippets(node, functionSnippets); }, FunctionExpression(path) { // log.debug('FunctionExpression----', path) const { node } = path; // 函数组件必须在代码最外层 if (path.parentPath.scope.block.type !== 'Program') { return; } // 允许往react函数组件中加入自定义代码 insertSnippets(node, functionSnippets); }, }; } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const ast = options.parse(code); (0, traverse_1.default)(ast, getTraverseOptions()); return ast; } // 转换成AST const ast = transformAST(code, options); // 再把AST转换成代码 const result = (0, generator_1.default)(ast, { compact: false, retainLines: true, }); // 文件里没有出现任何导入语句的情况 if (!hasImportI18n && hasTransformed) { result.code = `${importDeclaration}\n${result.code}`; } // 有forceImport时,即使没发生中文提取,也要在文件里加入i18n导入语句 if (!hasImportI18n && !hasTransformed && forceImport) { result.code = `${importDeclaration}\n${result.code}`; } return result; } exports.default = transformJs;