Skip to content
This repository has been archived by the owner on Jun 8, 2019. It is now read-only.

Commit

Permalink
Hoist state to file level
Browse files Browse the repository at this point in the history
This fixes issues without writing the extracted messages out to the
filesystem when multiple instances of the plugin are being applied.

This also removes the CLI usage docs which say to use `--plugins`
option because this is not useful for this plugin since it needs to
be configured via `.babelrc` or use via Babel's API.

Fixes #92
  • Loading branch information
ericf committed Jan 25, 2017
1 parent ef4ef93 commit 48f3635
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 54 deletions.
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,6 @@ If a message descriptor has a `description`, it'll be removed from the source af

- **`moduleSourceName`**: The ES6 module source name of the React Intl package. Defaults to: `"react-intl"`, but can be changed to another name/path to React Intl.

### Via CLI

```sh
$ babel --plugins react-intl script.js
```

### Via Node API

The extract message descriptors are available via the `metadata` property on the object returned from Babel's `transform()` API:
Expand Down
97 changes: 49 additions & 48 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ const FUNCTION_NAMES = [

const DESCRIPTOR_PROPS = new Set(['id', 'description', 'defaultMessage']);

const EXTRACTED_TAG = Symbol('ReactIntlExtracted');
const EXTRACTED = Symbol('ReactIntlExtracted');
const MESSAGES = Symbol('ReactIntlMessages');

export default function ({types: t}) {
function getModuleSourceName(opts) {
return opts.moduleSourceName || 'react-intl';
}

function evaluatePath(path) {
let evaluated = path.evaluate();
const evaluated = path.evaluate();
if (evaluated.confident) {
return evaluated.value;
}
Expand Down Expand Up @@ -62,7 +63,7 @@ export default function ({types: t}) {
}

function getICUMessageValue(messagePath, {isJSXSource = false} = {}) {
let message = getMessageDescriptorValue(messagePath);
const message = getMessageDescriptorValue(messagePath);

try {
return printICUMessage(message);
Expand Down Expand Up @@ -90,7 +91,7 @@ export default function ({types: t}) {

function createMessageDescriptor(propPaths) {
return propPaths.reduce((hash, [keyPath, valuePath]) => {
let key = getMessageDescriptorKey(keyPath);
const key = getMessageDescriptorKey(keyPath);

if (DESCRIPTOR_PROPS.has(key)) {
hash[key] = valuePath;
Expand All @@ -102,7 +103,7 @@ export default function ({types: t}) {

function evaluateMessageDescriptor({...descriptor}, {isJSXSource = false} = {}) {
Object.keys(descriptor).forEach((key) => {
let valuePath = descriptor[key];
const valuePath = descriptor[key];

if (key === 'defaultMessage') {
descriptor[key] = getICUMessageValue(valuePath, {isJSXSource});
Expand All @@ -115,16 +116,17 @@ export default function ({types: t}) {
}

function storeMessage({id, description, defaultMessage}, path, state) {
const {file, opts, reactIntl} = state;
const {file, opts} = state;

if (!(id && defaultMessage)) {
throw path.buildCodeFrameError(
'[React Intl] Message Descriptors require an `id` and `defaultMessage`.'
);
}

if (reactIntl.messages.has(id)) {
let existing = reactIntl.messages.get(id);
const messages = file.get(MESSAGES);
if (messages.has(id)) {
const existing = messages.get(id);

if (description !== existing.description ||
defaultMessage !== existing.defaultMessage) {
Expand Down Expand Up @@ -155,7 +157,7 @@ export default function ({types: t}) {
};
}

reactIntl.messages.set(id, {id, description, defaultMessage, ...loc});
messages.set(id, {id, description, defaultMessage, ...loc});
}

function referencesImport(path, mod, importedNames) {
Expand All @@ -167,51 +169,50 @@ export default function ({types: t}) {
}

function tagAsExtracted(path) {
path.node[EXTRACTED_TAG] = true;
path.node[EXTRACTED] = true;
}

function wasExtracted(path) {
return !!path.node[EXTRACTED_TAG];
return !!path.node[EXTRACTED];
}

return {
visitor: {
Program: {
enter(path, state) {
state.reactIntl = {
messages: new Map(),
};
},

exit(path, state) {
const {file, opts, reactIntl} = state;
const {basename, filename} = file.opts;

let descriptors = [...reactIntl.messages.values()];
file.metadata['react-intl'] = {messages: descriptors};

if (opts.messagesDir && descriptors.length > 0) {
// Make sure the relative path is "absolute" before
// joining it with the `messagesDir`.
let relativePath = p.join(
p.sep,
p.relative(process.cwd(), filename)
);
pre(file) {
if (!file.has(MESSAGES)) {
file.set(MESSAGES, new Map());
}
},

let messagesFilename = p.join(
opts.messagesDir,
p.dirname(relativePath),
basename + '.json'
);
post(file) {
const {opts} = this;
const {basename, filename} = file.opts;

let messagesFile = JSON.stringify(descriptors, null, 2);
const messages = file.get(MESSAGES);
const descriptors = [...messages.values()];
file.metadata['react-intl'] = {messages: descriptors};

mkdirpSync(p.dirname(messagesFilename));
writeFileSync(messagesFilename, messagesFile);
}
},
},
if (opts.messagesDir && descriptors.length > 0) {
// Make sure the relative path is "absolute" before
// joining it with the `messagesDir`.
const relativePath = p.join(
p.sep,
p.relative(process.cwd(), filename)
);

const messagesFilename = p.join(
opts.messagesDir,
p.dirname(relativePath),
basename + '.json'
);

const messagesFile = JSON.stringify(descriptors, null, 2);

mkdirpSync(p.dirname(messagesFilename));
writeFileSync(messagesFilename, messagesFile);
}
},

visitor: {
JSXOpeningElement(path, state) {
if (wasExtracted(path)) {
return;
Expand All @@ -232,7 +233,7 @@ export default function ({types: t}) {
}

if (referencesImport(name, moduleSourceName, COMPONENT_NAMES)) {
let attributes = path.get('attributes')
const attributes = path.get('attributes')
.filter((attr) => attr.isJSXAttribute());

let descriptor = createMessageDescriptor(
Expand Down Expand Up @@ -260,7 +261,7 @@ export default function ({types: t}) {

// Remove description since it's not used at runtime.
attributes.some((attr) => {
let ketPath = attr.get('name');
const ketPath = attr.get('name');
if (getMessageDescriptorKey(ketPath) === 'description') {
attr.remove();
return true;
Expand Down Expand Up @@ -295,7 +296,7 @@ export default function ({types: t}) {
return;
}

let properties = messageObj.get('properties');
const properties = messageObj.get('properties');

let descriptor = createMessageDescriptor(
properties.map((prop) => [
Expand Down Expand Up @@ -325,7 +326,7 @@ export default function ({types: t}) {
}

if (referencesImport(callee, moduleSourceName, FUNCTION_NAMES)) {
let messagesObj = path.get('arguments')[0];
const messagesObj = path.get('arguments')[0];

assertObjectExpression(messagesObj);

Expand Down

0 comments on commit 48f3635

Please sign in to comment.