Skip to content

fix(@formatjs/unplugin): add flatten option to match babel-plugin-formatjs ID generation#6178

Merged
longlho merged 3 commits intomainfrom
claude/issue-6177-20260323-1535
Mar 23, 2026
Merged

fix(@formatjs/unplugin): add flatten option to match babel-plugin-formatjs ID generation#6178
longlho merged 3 commits intomainfrom
claude/issue-6177-20260323-1535

Conversation

@longlho
Copy link
Member

@longlho longlho commented Mar 23, 2026

When flatten=true, apply printAST(hoistSelectors(parse(message))) before generating the ID, matching the behavior of babel-plugin-formatjs and the Rust formatjs_cli. Previously the Vite plugin only applied simple whitespace normalization, producing a different hash input and therefore different IDs.

Fixes #6177

Generated with Claude Code

…matjs ID generation

When flatten=true, apply printAST(hoistSelectors(parse(message))) before
generating the ID, matching the behavior of babel-plugin-formatjs and the
Rust formatjs_cli. Previously the Vite plugin only applied simple whitespace
normalization, producing a different hash input and therefore different IDs.

Fixes #6177

Co-authored-by: Long Ho <longlho@users.noreply.github.com>
Copy link
Member Author

@longlho longlho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude address those comments

additionalFunctionNames = [],
ast: preParseAst = false,
preserveWhitespace = false,
flatten = false,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default to true

const input = `<FormattedMessage defaultMessage="Are you sure you want to delete {count,plural,one {this report template} other {these # report templates}}?" />`
const result = t(input, {flatten: true})
// Hoisted form: plural wraps the full sentence
expect(result).toContain(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compare the whole output, not just toContain

@claude
Copy link

claude bot commented Mar 23, 2026

Claude finished @longlho's task in 8m 11s —— View job


Addressed Review Comments

  • Read review comments
  • Change flatten default to true in packages/unplugin/transform.ts:54
  • Update flatten tests to compare whole output with toBe instead of toContain
  • Commit and push changes

Changes Made

packages/unplugin/transform.ts

  • Changed flatten = falseflatten = true so flattening is enabled by default, matching babel-plugin-formatjs and Rust CLI behavior out of the box

packages/unplugin/tests/transform.test.ts

  • Added import {interpolateName} from '@formatjs/ts-transformer'
  • Test 1 (normalizes ICU format...): replaced two toContain checks with a single toBe comparing the full output string (<FormattedMessage id="14X+wqft" defaultMessage="..." />)
  • Test 2 (hoists select/plural patterns...): replaced toContain with toBe comparing the full output; uses interpolateName to dynamically compute the expected id from the known flattened message so the assertion remains self-consistent without a magic constant
    |

…assertions in tests

- Change `flatten` option default from `false` to `true` to match
  babel-plugin-formatjs and Rust CLI behavior out of the box
- Update flatten tests to use `toBe` for whole-output assertions
  instead of multiple `toContain` checks

Co-authored-by: Long Ho <longlho@users.noreply.github.com>
@longlho
Copy link
Member Author

longlho commented Mar 23, 2026

@claude add conformance integration tests to check between formatjs_cli and our vite plugin to make sure default set of options matches. Make sure to bazel it

@claude
Copy link

claude bot commented Mar 23, 2026

Claude finished @longlho's task in 7m 55s —— View job


Adding Conformance Integration Tests

  • Explore existing conformance test structure
  • Explore unplugin and formatjs_cli codebases
  • Design conformance test approach
  • Create packages/unplugin/conformance-tests/rust-binary-utils.ts
  • Create packages/unplugin/conformance-tests/cli-unplugin-conformance.test.ts
  • Update packages/unplugin/BUILD.bazel (copy_file + new vitest target)
  • CI running bazel test //packages/unplugin:conformance_test

What was added

packages/unplugin/conformance-tests/cli-unplugin-conformance.test.ts

7 test cases that each:

  1. Write source code to a temp .tsx file
  2. Run formatjs_cli extract --flatten → collect the JSON-keyed IDs
  3. Run transform(code, tempFile, {}) with default options (flatten=true) → extract id:"..." / id="..." from the output
  4. Assert both ID sets are identical

Test cases:

  • Plain formatMessage without description
  • Plain formatMessage with description
  • Plural message — flatten hoists selectors
  • Plural embedded in sentence — flatten produces full phrases
  • JSX <FormattedMessage> with description
  • defineMessages with multiple messages
  • Whitespace normalization before ID generation

packages/unplugin/BUILD.bazel

  • Added copy_file rule to copy the Rust CLI binary into the package (same pattern as packages/cli/integration-tests)
  • Added conformance_test vitest target with data = ["//crates/formatjs_cli"] and @bazel/runfiles dep
    |

Verify that formatjs_cli extract --flatten and the unplugin transform
(default options: flatten=true) generate identical IDs for the same source,
ensuring extraction and bundling pipelines stay in sync.

Co-authored-by: Long Ho <longlho@users.noreply.github.com>
@longlho longlho merged commit 9cc3087 into main Mar 23, 2026
6 checks passed
@longlho longlho deleted the claude/issue-6177-20260323-1535 branch March 23, 2026 17:11
@lensbart
Copy link

Thanks for the lightning-fast fix & release! ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

defaultMessage differs between formatjs extract and @formatjs/unplugin/vite

2 participants