I’m using Meteor with the Rspack integration and I’m trying to understand how the build pipeline is split between the modern and legacy web architectures.
What I tested:
I set Rspack jsc.target to esNext.
Then I ran meteor build and inspected the output bundle under bundle/programs/.
What I observed:
In programs/web.browser (modern), the generated JS still contains lots of modern syntax such as ?? (and other ES2020+ features), which matches the esNext expectation.
In programs/web.browser.legacy (legacy), those modern syntaxes (e.g. ??) are completely absent. It looks like everything is transpiled down to ES5.
Questions:
Is it expected that Rspack config (like jsc.target / env) only affects the modern client build (web.browser), while the legacy build (web.browser.legacy) is still handled by Meteor’s legacy toolchain?
If yes, could this be documented more explicitly? I suspect many users assume Rspack config applies to both modern and legacy outputs.
Looking forward: is there a plan to let Rspack also take over web.browser.legacy? For example, introducing something like Meteor.modern() (or a similar API) so developers can switch Rspack targets/jsc.target/env between modern and legacy builds explicitly.
When you enable Rspack in your Meteor project, Rspack only builds for the modern web.browser architecture. The legacy web.browser.legacy build is still handled by Meteor’s traditional bundler using Babel for transpilation.
It’s a good feedback, today we have rspack only inside modern session, but maybe it isnt clear.
Are you open to push a PR to release 3.4?
I dont think so since legacy shouldn’t receive considerable updates, but @nacho can confirm it better then me.
Is it expected that Rspack config (like jsc.target / env) only affects the modern client build (web.browser), while the legacy build (web.browser.legacy) is still handled by Meteor’s legacy toolchain?
What happens behind the scenes is that Rspack compiles your app into intermediate client and server files. Meteor detects those Rspack transpiled outputs and uses them as new entrypoints for your app. For the modern web arch, they stay as-is (Rspack-compiled).
For the legacy web arch, the Rspack outputs also go through Meteor’s normal transpilation step. That step mainly uses SWC with old-browser targets, and falls back to Babel only when SWC can’t compile a file. This keeps the final code compatible with legacy browsers, with the same guarantees Meteor already provides.
is there a plan to let Rspack also take over web.browser.legacy ? For example, introducing something like Meteor.modern() (or a similar API) so developers can switch Rspack targets/jsc.target/env between modern and legacy builds explicitly.
I tried to do that during the implementation, but it was hard and time-consuming on my first attempt. Sadly, Meteor does not give us a clean, first-class “legacy-only entrypoint” switch that could make this feature easy to achieve. It is something we could revisit in a second round of work. But like with the Meteor-Rspack integration, we are trying to stay agile and think modern-first. So we should be clear about what we actually gain from allowing a specific legacy configuration.
What would this configuration distinction help you with? Are you not able to get your app fully in legacy browsers when working using Meteor’s normal procedures for legacy builds? Is it any clear regression from what you have accomplished on the past?
Legacy builds are still meant for a specific and low user base in Meteor apps. We continue to support them. I’d like to open a discussion to understand the real gain or limitation (compared to the bundler capabilities Meteor has had for years) so we can decide whether revisiting this is worth it, given the risk and time cost of handling legacy implementations and issues that are non-critical, hard to debug, and hard to verify through CI pipelines for QA on releases.
What we can surely do is to address any regression we identify as we did recently with fixing legacy compilations on SWC and Rspack.
In the past, my application’s web.browser.legacy build worked perfectly. However, as more NPM packages adopt modern syntax and Web APIs, maintaining full ES5 compatibility has become increasingly challenging. For example, the latest version of the immer library only supports modern browsers and cannot be made compatible through transpilation and polyfilling alone.
While one approach could be to raise the minimum browser version requirement, currently the only viable path seems to be disabling web.browser.legacy entirely, setting a minimum browser version (e.g., Chrome >= 49) in rspack, and injecting polyfills via corejs.
However, I find this approach somewhat regrettable. I believe Meteor’s dual-build feature is an elegant solution. What I hope to achieve is a setup where:
The modern build targets the latest browsers (e.g., Chrome >= 120)
The legacy build targets Chrome >= 49
Using ModernBrowsers.setMinimumBrowserVersions to differentiate between these build versions
Regarding the benefits, I believe this approach would deliver:
Better performance and smaller bundle sizes (though I haven’t conducted rigorous testing)
Long-term sustainability - ensuring Meteor’s dual-build system remains effective for the next decade or more. The user base requiring ES5 compatibility will only continue to shrink, eventually reaching a point where it becomes negligible.
This way, Meteor’s dual-build system could continue to shine as an excellent feature for years to come.
As said above, the “2perfect” scenario would be having a way in rspack.config.js to distinguish between modern vs legacy builds. That will be hard because of how the Meteor bundler works and the current Meteor-Rspack integration. It’s doable, but probably more time-consuming for a case that doesn’t seem very frequent, and there are ways to work around it, even if that means targeting less strict browser versions.
Still, before going for the harder solution above, I’d like to understand if there’s another path worth considering. I’ll keep it in mind while exploring the core code in case there’s a quick win. Among the alternatives, do you think a way to customize this would help?
That piece of code is the SWC transpiler (meteor.modern=true) used to compile Atmosphere packages and legacy builds. If we enable a .swcrc.legacy file as a way to override the default config, would that work for you?
My thinking is: you wouldn’t need to disable web.browser.legacy. You could manage the modern web.browser build independently, and you could adjust the legacy-only transpiler config via .swcrc.legacy. You could use ModernBrowsers.setMinimumBrowserVersions to distinguish between the build versions.
Did I get anything wrong? If so, a better next step might be a minimal repo with immer (or a small example with your ideal setup) that shows the need to tweak the minimum browser versions, so we can explore options to cover the use case you mention.