diff --git a/README.md b/README.md index 3561796..fcfbae3 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ $ npm install babel-plugin-react-intl The default message descriptors for the app's default language will be extracted from: `defineMessages()`, ``, and ``; all of which are named exports of the React Intl package. +If a message descriptor has a `description`, it'll be removed from the source after it's extracted to save bytes since it isn't used at runtime. + ### Via `.babelrc` (Recommended) **.babelrc** diff --git a/src/index.js b/src/index.js index 0a32f89..40b8cde 100644 --- a/src/index.js +++ b/src/index.js @@ -20,7 +20,9 @@ const FUNCTION_NAMES = [ const DESCRIPTOR_PROPS = new Set(['id', 'description', 'defaultMessage']); -export default function () { +const EXTRACTED_TAG = Symbol('ReactIntlExtracted'); + +export default function ({types: t}) { function getModuleSourceName(opts) { return opts.moduleSourceName || 'react-intl'; } @@ -153,6 +155,14 @@ export default function () { return importedNames.some((name) => path.referencesImport(mod, name)); } + function tagAsExtracted(path) { + path.node[EXTRACTED_TAG] = true; + } + + function wasExtracted(path) { + return !!path.node[EXTRACTED_TAG]; + } + return { visitor: { Program: { @@ -192,10 +202,13 @@ export default function () { }, JSXOpeningElement(path, state) { - const {file, opts} = state; - const moduleSourceName = getModuleSourceName(opts); + if (wasExtracted(path)) { + return; + } - let name = path.get('name'); + const {file, opts} = state; + const moduleSourceName = getModuleSourceName(opts); + const name = path.get('name'); if (name.referencesImport(moduleSourceName, 'FormattedPlural')) { file.log.warn( @@ -231,7 +244,20 @@ export default function () { descriptor = evaluateMessageDescriptor(descriptor, { isJSXSource: true, }); + storeMessage(descriptor, path, state); + + // Remove description since it's not used at runtime. + attributes.some((attr) => { + let ketPath = attr.get('name'); + if (getMessageDescriptorKey(ketPath) === 'description') { + attr.remove(); + return true; + } + }); + + // Tag the AST node so we don't try to extract it twice. + tagAsExtracted(path); } } }, @@ -254,6 +280,10 @@ export default function () { function processMessageObject(messageObj) { assertObjectExpression(messageObj); + if (wasExtracted(messageObj)) { + return; + } + let properties = messageObj.get('properties'); let descriptor = createMessageDescriptor( @@ -266,6 +296,21 @@ export default function () { // Evaluate the Message Descriptor values, then store it. descriptor = evaluateMessageDescriptor(descriptor); storeMessage(descriptor, messageObj, state); + + // Remove description since it's not used at runtime. + messageObj.replaceWith(t.objectExpression([ + t.objectProperty( + t.stringLiteral('id'), + t.stringLiteral(descriptor.id) + ), + t.objectProperty( + t.stringLiteral('defaultMessage'), + t.stringLiteral(descriptor.defaultMessage) + ), + ])); + + // Tag the AST node so we don't try to extract it twice. + tagAsExtracted(messageObj); } if (referencesImport(callee, moduleSourceName, FUNCTION_NAMES)) { diff --git a/test/fixtures/FormattedHTMLMessage/expected.js b/test/fixtures/FormattedHTMLMessage/expected.js index 01c30c6..b25f443 100644 --- a/test/fixtures/FormattedHTMLMessage/expected.js +++ b/test/fixtures/FormattedHTMLMessage/expected.js @@ -34,8 +34,7 @@ var Foo = function (_Component) { value: function render() { return _react2.default.createElement(_reactIntl.FormattedHTMLMessage, { id: 'foo.bar.baz', - defaultMessage: '

Hello World!

', - description: 'The default message.' + defaultMessage: '

Hello World!

' }); } }]); diff --git a/test/fixtures/FormattedMessage/expected.js b/test/fixtures/FormattedMessage/expected.js index 0b6f6ef..294f3d2 100644 --- a/test/fixtures/FormattedMessage/expected.js +++ b/test/fixtures/FormattedMessage/expected.js @@ -34,8 +34,7 @@ var Foo = function (_Component) { value: function render() { return _react2.default.createElement(_reactIntl.FormattedMessage, { id: 'foo.bar.baz', - defaultMessage: 'Hello World!', - description: 'The default message.' + defaultMessage: 'Hello World!' }); } }]); diff --git a/test/fixtures/defineMessages/expected.js b/test/fixtures/defineMessages/expected.js index 14d21dd..ea4fbe5 100644 --- a/test/fixtures/defineMessages/expected.js +++ b/test/fixtures/defineMessages/expected.js @@ -22,14 +22,12 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function" var msgs = (0, _reactIntl.defineMessages)({ header: { - id: 'foo.bar.baz', - defaultMessage: 'Hello World!', - description: 'The default message' + 'id': 'foo.bar.baz', + 'defaultMessage': 'Hello World!' }, content: { - id: 'foo.bar.biff', - defaultMessage: 'Hello Nurse!', - description: 'Another message' + 'id': 'foo.bar.biff', + 'defaultMessage': 'Hello Nurse!' } }); diff --git a/test/fixtures/moduleSourceName/expected.js b/test/fixtures/moduleSourceName/expected.js index 779ea42..6e21493 100644 --- a/test/fixtures/moduleSourceName/expected.js +++ b/test/fixtures/moduleSourceName/expected.js @@ -54,8 +54,7 @@ var Foo = function (_Component) { null, _react2.default.createElement(_reactI18n.FormattedMessage, { id: 'foo.bar.baz', - defaultMessage: 'Hello World!', - description: 'The default message.' + defaultMessage: 'Hello World!' }), msgs ); diff --git a/test/fixtures/removeDescriptions/actual.js b/test/fixtures/removeDescriptions/actual.js new file mode 100644 index 0000000..e2430ed --- /dev/null +++ b/test/fixtures/removeDescriptions/actual.js @@ -0,0 +1,22 @@ +import React, {Component} from 'react'; +import {defineMessages, FormattedMessage} from 'react-intl'; + +const messages = defineMessages({ + foo: { + id: 'greeting-user', + description: 'Greeting the user', + defaultMessage: 'Hello, {name}', + }, +}); + +export default class Foo extends Component { + render() { + return ( + + ); + } +} diff --git a/test/index.js b/test/index.js index 46c4312..41ddd8b 100644 --- a/test/index.js +++ b/test/index.js @@ -15,6 +15,7 @@ const skipTests = [ 'extractSourceLocation', 'moduleSourceName', 'icuSyntax', + 'removeDescriptions', ]; const fixturesDir = path.join(__dirname, 'fixtures'); @@ -74,6 +75,22 @@ describe('options', () => { } }); + it('removes descriptions when plugin is applied more than once', () => { + const fixtureDir = path.join(fixturesDir, 'removeDescriptions'); + + try { + transform(path.join(fixtureDir, 'actual.js'), { + enforceDescriptions: true, + }, { + multiplePasses: true, + }); + assert(true); + } catch (e) { + console.error(e); + assert(false); + } + }); + it('respects moduleSourceName', () => { const fixtureDir = path.join(fixturesDir, 'moduleSourceName'); @@ -133,13 +150,18 @@ const BASE_OPTIONS = { messagesDir: baseDir, }; -function transform(filePath, options = {}) { +function transform(filePath, options = {}, {multiplePasses = false} = {}) { + function getPluginConfig() { + return [plugin, { + ...BASE_OPTIONS, + ...options, + }]; + } + return babel.transformFileSync(filePath, { - plugins: [ - [plugin, { - ...BASE_OPTIONS, - ...options, - }], - ], + plugins: multiplePasses ? [ + getPluginConfig(), + getPluginConfig(), + ] : [getPluginConfig()], }).code; }