Skip to content

feat(experimental-extractor): pluggable bundler interface + rolldown support#2572

Open
timofei-iatsenko wants to merge 22 commits into
mainfrom
experimental-extractor-plugable-bundler
Open

feat(experimental-extractor): pluggable bundler interface + rolldown support#2572
timofei-iatsenko wants to merge 22 commits into
mainfrom
experimental-extractor-plugable-bundler

Conversation

@timofei-iatsenko

@timofei-iatsenko timofei-iatsenko commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Description

This one is made on top of #2571

Summary

  • Makes the bundler for extract-experimental configurable via the bundler field in experimental.extractor config
  • Adds createEsbuildBundler() and createRolldownBundler() factory functions as separate entrypoints (@lingui/cli/bundlers/esbuild, @lingui/cli/bundlers/rolldown)
  • enables codesplitting for esbuild bundlers, and implement a traversal of emitted common chunks in the extractor (should improve speed a lot!)
  • Deprecates includeDeps, excludeExtensions, and resolveEsbuildOption on the extractor config in favor of passing them to the bundler factory
  • makes rolldown and esbuild optional dependencies

Motivation

The experimental extractor was hardcoded to esbuild. Users who prefer rolldown (or another bundler) had no way to swap it. The new ExperimentalExtractorBundler interface is minimal — bundle(entryPoints, outDir, linguiConfig) => BundleResult — so custom implementations are straightforward.

Usage

  // lingui.config.ts
  import { createRolldownBundler } from "@lingui/cli/bundlers/rolldown"

  export default defineConfig({
    experimental: {
      extractor: {
        entries: ["src/**/*.page.tsx"],
        output: "src/locales/{entryName}.{locale}",
        bundler: createRolldownBundler(),
      },
    },
  })

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Examples update

Fixes # (issue)

Checklist

  • I have read the CONTRIBUTING and CODE_OF_CONDUCT docs
  • I have added tests that prove my fix is effective or that my feature works
  • I have added the necessary documentation (if appropriate)

@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
js-lingui Ready Ready Preview Jun 17, 2026 9:24am

Request Review

@timofei-iatsenko timofei-iatsenko changed the title feat(experimental-extractor): make a bundler configurable, add rolldown feat(experimental-extractor): pluggable bundler interface + rolldown support Jun 11, 2026
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
packages/core/dist/index.mjs 1.69 KB (0%)
packages/detect-locale/dist/index.mjs 633 B (0%)
packages/react/dist/index.mjs 1.22 KB (0%)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR makes the extract-experimental pipeline bundler-pluggable by introducing a minimal bundler interface and shipping built-in bundlers for esbuild (default) and rolldown, plus updates the macro extraction mark to a JSDoc-style comment so it survives rolldown output.

Changes:

  • Add ExperimentalExtractorBundler / BundleResult types and a new experimental.extractor.bundler config field (with deprecations for esbuild-specific options on the extractor config).
  • Replace the hardcoded esbuild bundling with a bundler abstraction and add createEsbuildBundler() and createRolldownBundler() implementations.
  • Update macro extraction marks and extraction logic to support both /*i18n*/ and /** i18n */, plus add an E2E rolldown extractor test.

Reviewed changes

Copilot reviewed 31 out of 32 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
yarn.lock Adds/updates lock entries for rolldown + plugin-babel and bumps esbuild version.
packages/conf/src/types.ts Introduces BundleResult + ExperimentalExtractorBundler types; adds bundler option and deprecations on extractor config.
packages/conf/src/makeConfig.ts Extends example/validation shape to allow experimental.extractor.bundler.
packages/cli/test/index.test.ts Adds E2E test coverage for experimental extractor using rolldown.
packages/cli/test/extractor-experimental/lingui.config.rolldown.ts Adds test config exercising createRolldownBundler().
packages/cli/src/lingui-extract-experimental.ts Switches extractor to use configured bundler, defaulting to esbuild bundler factory.
packages/cli/src/extract-experimental/linguiEsbuildPlugin.ts Uses a config-aware content filter regex for macro detection.
packages/cli/src/extract-experimental/constants.ts Centralizes the default excluded extensions list.
packages/cli/src/extract-experimental/bundleSource.ts Removes old esbuild-only bundling implementation (superseded by bundlers).
packages/cli/src/extract-experimental/bundlers/rolldown.ts Adds rolldown bundler implementation.
packages/cli/src/extract-experimental/bundlers/esbuild.ts Adds esbuild bundler implementation replacing the old bundling code.
packages/cli/src/extract-experimental/buildContentFilter.ts Adds helper to build macro import detection regex from linguiConfig.macro.
packages/cli/package.json Exposes new bundler entrypoints and changes bundler-related dependency declarations.
packages/babel-plugin-lingui-macro/test/fixtures/jsx-plural-select-nested.expected.js Updates expected output to JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/fixtures/jsx-keep-forced-newlines.expected.js Updates expected output to JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/fixtures/js-t-var/js-t-var.expected.js Updates expected output to JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/fixtures/js-t-continuation-character.expected.js Updates expected output to JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/snapshots/lingui-directive.test.ts.snap Snapshot updates for JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/snapshots/jsx-trans.test.ts.snap Snapshot updates for JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/snapshots/jsx-selectOrdinal.test.ts.snap Snapshot updates for JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/snapshots/jsx-select.test.ts.snap Snapshot updates for JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/snapshots/jsx-plural.test.ts.snap Snapshot updates for JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/snapshots/jsx-placeholder.test.ts.snap Snapshot updates for JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/snapshots/js-useLingui.test.ts.snap Snapshot updates for JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/snapshots/js-t.test.ts.snap Snapshot updates for JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/snapshots/js-selectOrdinal.test.ts.snap Snapshot updates for JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/snapshots/js-select.test.ts.snap Snapshot updates for JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/snapshots/js-plural.test.ts.snap Snapshot updates for JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/test/snapshots/js-defineMessage.test.ts.snap Snapshot updates for JSDoc-style extraction mark.
packages/babel-plugin-lingui-macro/src/constants.ts Changes the extraction mark value to produce /** i18n */ output.
packages/babel-plugin-extract-messages/test/extract-messages.test.ts Adds test ensuring both old and new extraction mark formats are supported.
packages/babel-plugin-extract-messages/src/index.ts Updates detection logic to accept both i18n and * i18n comment bodies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/cli/src/extract-experimental/bundlers/esbuild.ts Outdated
Comment thread packages/cli/src/extract-experimental/bundlers/rolldown.ts Outdated
Comment thread packages/cli/package.json
@timofei-iatsenko

This comment was marked as resolved.

@andrii-bodnar

This comment was marked as resolved.

@timofei-iatsenko

This comment was marked as outdated.

@andrii-bodnar andrii-bodnar linked an issue Jun 12, 2026 that may be closed by this pull request
@timofei-iatsenko timofei-iatsenko marked this pull request as draft June 15, 2026 07:56
@timofei-iatsenko

Copy link
Copy Markdown
Collaborator Author

I explored one uncovered case which was ok on esbuild and not ok with rolldown.

Both esbuild and rolldown support code splitting. For example if you have shared.ts included in both a.ts and b.ts both of them should create a common chunk for this shared module.

However esbuild allow to switch this codesplitting even when multiple entrypoints are passed. In this case it creates 1 bundle for 1 entrypoint. Previous implementation was based on this, and extracted from bundles with 1-1 mapping to catalogs.

Rolldown doesn't allow to switch off code splitting when you pass multiple entry points. It will always produce 3 chunks. So the extractor code should be able to also scan this common chunk and put extracted messages into corresponding catalog.

I added an implementation for this and actually enabled code-splitting for esbuild as well.

This actually should also give a performance speed up. Previously both bundles a.ts and b.ts would contain a duplicated common code. So extractor would parse the same code again and again.

Now common code extracted in the separate file and would be scanned only once, so the amount of work would grew linearly with the size of the project, not the amount of entrypoints and reusability multiplier.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 40 changed files in this pull request and generated 5 comments.

Files not reviewed (8)
  • packages/cli/test/extractor-experimental/expected-rolldown/about.page.en.js: Generated file
  • packages/cli/test/extractor-experimental/expected-rolldown/about.page.pl.js: Generated file
  • packages/cli/test/extractor-experimental/expected-rolldown/index.page.en.js: Generated file
  • packages/cli/test/extractor-experimental/expected-rolldown/index.page.pl.js: Generated file
  • packages/cli/test/extractor-experimental/expected/about.page.en.js: Generated file
  • packages/cli/test/extractor-experimental/expected/about.page.pl.js: Generated file
  • packages/cli/test/extractor-experimental/expected/index.page.en.js: Generated file
  • packages/cli/test/extractor-experimental/expected/index.page.pl.js: Generated file
Comments suppressed due to low confidence (1)

packages/cli/src/extract-experimental/linguiEsbuildPlugin.ts:27

  • buildContentFilterRe(options.linguiConfig) is recomputed for every loaded file. Since it only depends on the config, it can be computed once in setup and reused, reducing per-file overhead during bundling.
  setup(build) {
    build.onLoad({ filter: babelRe, namespace: "" }, async (args) => {
      const filename = path.relative(process.cwd(), args.path)

      const contents = await fs.promises.readFile(args.path, "utf8")

      const hasMacroRe = buildContentFilterRe(options.linguiConfig)

      if (!hasMacroRe.test(contents)) {
        // let esbuild process file as usual
        return undefined
      }

Comment thread website/docs/guides/message-extraction.md
Comment thread website/docs/guides/message-extraction.md
Comment thread packages/cli/src/extract-experimental/bundlers/rolldown.ts
Comment thread packages/conf/src/types.ts
Comment thread packages/cli/src/extract-experimental/buildChunkGraph.ts
@timofei-iatsenko timofei-iatsenko marked this pull request as ready for review June 16, 2026 10:04
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.

Remove required dependency on esbuild

3 participants