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
JavaScript
;
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);
}
}
};