UNPKG

babel-plugin-react-intl-auto-ext

Version:

i18n for the component age. Auto management react-intl ID. Extended with custom module name enabled

325 lines (251 loc) 10.5 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = function () { return { name: 'react-intl-auto', visitor: { JSXElement: visitJSXElement, CallExpression(path, state) { if (!isDefineMessagesCall(path, state)) { return; } var properties = getProperties(path.get('arguments.0')); if (properties) { var exportName = getExportName(path, state.opts.includeExportName || false); replaceProperties(properties, state, exportName); } } } }; }; var _path = require('path'); var _path2 = _interopRequireDefault(_path); var _babelTypes = require('babel-types'); var t = _interopRequireWildcard(_babelTypes); var _murmurhash3js = require('murmurhash3js'); var _murmurhash3js2 = _interopRequireDefault(_murmurhash3js); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } // import blog from 'babel-log' var isImportLocalName = function isImportLocalName(name, allowedNames, _ref) { var file = _ref.file, _ref$opts$moduleSourc = _ref.opts.moduleSourceName, moduleSourceName = _ref$opts$moduleSourc === undefined ? 'react-intl' : _ref$opts$moduleSourc; var isSearchedImportSpecifier = function isSearchedImportSpecifier(specifier) { return specifier.isImportSpecifier() && allowedNames.includes(specifier.node.imported.name) && specifier.node.local.name === name; }; var isImported = false; file.path.traverse({ ImportDeclaration: { exit(path) { isImported = path.node.source.value.indexOf(moduleSourceName) > -1 && path.get('specifiers').some(isSearchedImportSpecifier); if (isImported) { path.stop(); } } } }); return isImported; }; var REG = new RegExp(`\\${_path2.default.sep}`, 'g'); var dotPath = function dotPath(str) { return str.replace(REG, '.'); }; var getPrefix = function getPrefix(_ref2, exportName) { var filename = _ref2.file.opts.filename, _ref2$opts = _ref2.opts, _ref2$opts$removePref = _ref2$opts.removePrefix, removePrefix = _ref2$opts$removePref === undefined ? '' : _ref2$opts$removePref, _ref2$opts$filebase = _ref2$opts.filebase, filebase = _ref2$opts$filebase === undefined ? false : _ref2$opts$filebase; if (removePrefix === true) { return exportName === null ? '' : exportName; } var file = _path2.default.relative(process.cwd(), filename); var fomatted = filebase ? file.replace(/\..+$/, '') : _path2.default.dirname(file); removePrefix = removePrefix === false ? '' : removePrefix; var fixed = removePrefix instanceof RegExp ? dotPath(fomatted.replace(removePrefix, '')) : dotPath(fomatted).replace(new RegExp(`^${removePrefix.replace(/\//g, '')}\\${dotPath(_path2.default.sep)}?`), ''); var result = exportName === null ? fixed : `${fixed}.${exportName}`; return result; }; var getId = function getId(path, prefix) { var name = void 0; if (path.isStringLiteral()) { name = path.node.value; } else if (path.isIdentifier()) { name = path.node.name; } if (!name) { throw new Error(`requires Object key or string literal`); } return dotPath(_path2.default.join(prefix, name)); }; var isLiteral = function isLiteral(node) { return t.isStringLiteral(node) || t.isTemplateLiteral(node); }; var isDefineMessagesCall = function isDefineMessagesCall(path, state) { var callee = path.get('callee'); return callee.isIdentifier() && isImportLocalName(callee.node.name, ['defineMessages'], state) && Boolean(path.get('arguments.0')); }; var getLeadingComment = function getLeadingComment(prop) { var commentNodes = prop.node.leadingComments; return commentNodes ? commentNodes.map(function (node) { return node.value.trim(); }).join('\n') : null; }; var objectProperty = function objectProperty(key, value) { var valueNode = typeof value === 'string' ? t.stringLiteral(value) : value; return t.objectProperty(t.stringLiteral(key), valueNode); }; var replaceProperties = function replaceProperties(properties, state, exportName) { var prefix = getPrefix(state, exportName); var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = properties[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var prop = _step.value; var propValue = prop.get('value'); var messageDescriptorProperties = []; // { defaultMessage: 'hello', description: 'this is hello' } if (propValue.isObjectExpression()) { var objProps = propValue.get('properties'); // { id: 'already has id', defaultMessage: 'hello' } var isNotHaveId = objProps.every(function (v) { return v.get('key').node.name !== 'id'; }); if (isNotHaveId) { var id = getId(prop.get('key'), prefix); messageDescriptorProperties.push(objectProperty('id', id)); } messageDescriptorProperties.push.apply(messageDescriptorProperties, _toConsumableArray(objProps.map(function (v) { return v.node; }))); } else if (isLiteral(propValue)) { // 'hello' or `hello ${user}` var _id = getId(prop.get('key'), prefix); messageDescriptorProperties.push(objectProperty('id', _id), objectProperty('defaultMessage', propValue.node)); } else { var evaluated = prop.get('value').evaluate(); if (evaluated.confident && typeof evaluated.value === 'string') { var _id2 = dotPath(_path2.default.join(prefix, evaluated.value)); messageDescriptorProperties.push(objectProperty('id', _id2), objectProperty('defaultMessage', propValue.node)); } } var _state$opts$extractCo = state.opts.extractComments, extractComments = _state$opts$extractCo === undefined ? true : _state$opts$extractCo; if (extractComments) { var hasDescription = messageDescriptorProperties.find(function (v) { return v.key.name === 'description'; }); if (!hasDescription) { var description = getLeadingComment(prop); if (description) { messageDescriptorProperties.push(objectProperty('description', description)); } } } propValue.replaceWith(t.objectExpression(messageDescriptorProperties)); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } }; var getExportName = function getExportName(path, includeExportName) { var namedExport = path.findParent(function (v) { return v.isExportNamedDeclaration(); }); var defaultExport = path.findParent(function (v) { return v.isExportDefaultDeclaration(); }); if (includeExportName && namedExport) { return namedExport.get('declaration.declarations.0.id.name').node; } if (includeExportName === 'all' && defaultExport) { return 'default'; } return null; }; function getProperties(path) { if (path.isObjectExpression()) { return path.get('properties'); } else if (path.isIdentifier()) { var name = path.node.name; var obj = path.scope.getBinding(name); if (!obj) { return null; } return obj.path.get('init.properties'); } return null; } // Process react-intl components var REACT_COMPONENTS = ['FormattedMessage', 'FormattedHTMLMessage']; var getElementAttributePaths = function getElementAttributePaths(elementPath) { if (!elementPath) { return {}; } var attributesPath = elementPath.get('attributes'); var defaultMessagePath = attributesPath.find(function (attrPath) { return attrPath.node.name && attrPath.node.name.name === 'defaultMessage'; }); var idPath = attributesPath.find(function (attrPath) { return attrPath.node.name && attrPath.node.name.name === 'id'; }); return { id: idPath, defaultMessage: defaultMessagePath }; }; var createHash = function createHash(message) { return `${_murmurhash3js2.default.x86.hash32(message)}`; }; var generateId = function generateId(defaultMessage, state) { var messageValuePath = defaultMessage.get('value'); var message = void 0; // Use the message as is if it's a string if (messageValuePath.isStringLiteral()) { message = defaultMessage.node.value.value; } else { // Evaluate the message expression to see if it yields a string var evaluated = messageValuePath.get('expression').evaluate(); if (evaluated.confident && typeof evaluated.value === 'string') { message = evaluated.value; } else { throw messageValuePath.buildCodeFrameError('[React Intl Auto] Messages must be statically evaluate-able for extraction.'); } } // ID is comprised of the path to the file and a hash // of the defaultMessage var hash = createHash(message); var prefix = getPrefix(state, hash); // Insert an id attribute before the defaultMessage attribute defaultMessage.insertBefore(t.jSXAttribute(t.jSXIdentifier('id'), t.stringLiteral(prefix))); }; var visitJSXElement = function visitJSXElement(path, state) { var element = path.get('openingElement'); // Is this a react-intl component? Handles both: // import { FormattedMessage as T } from 'react-intl' // import { FormattedMessage } from 'react-intl' if (isImportLocalName(element.node.name.name, REACT_COMPONENTS, state)) { // Get the attributes for the component var _getElementAttributeP = getElementAttributePaths(element), id = _getElementAttributeP.id, defaultMessage = _getElementAttributeP.defaultMessage; // If valid message but missing ID, generate one if (!id && defaultMessage) { generateId(defaultMessage, state); } } };