Meteor-RSPack Integration: A Modern Bundler Meets Meteor 3.4

Meteor 3.4 will let you optionally use the Meteor-Rspack integration mentioned in this post by adding the new rspack package. Using it unlocks many bundler features, including support for the exports field in package.json.

For projects that choose to keep the default Meteor bundler, we still need a contribution to add this feature to Meteor itself:

We’re open to assisting with its inclusion once it’s ready by the author, but the modern path to support it is via Rspack.

4 Likes

After successfully automating Rspack in Meteor and focusing on production deployment, it’s clear that build time isn’t the only improvement coming in Meteor 3.4.

Client bundles are now thinner, meaning less code delivered and faster initial load times for your apps.

By integrating the work-in-progress Rspack package in fredmaiaarantes/simpletasks and deploying it to a private instance on Galaxy Cloud, the client bundle size dropped by about 50% compared to the 3.3.1-rc.2 release.

Gains vary by app and dependencies, could be less or much more. This comes from long-awaited tree shaking support and other optimizations. Large libraries like ChakraUI benefit the most when properly tree-shaken, and with a modern bundler, your app can now take full advantage of this optimization phase.

Besides, Rspack comes with tooling to diagnose and analyze your bundles. I’ll demo that soon once it’s properly integrated with Meteor’s bundle visualizer. After wrapping up a few things, we aim to hit the first beta sometime in August.

12 Likes

Any ETA on when the RSPack beta is going to be released? Are we still on track in the next couple of weeks? Super looking forward to it, and setting up a bunch of tools/projects in anticipation for it.

2 Likes

No ETA yet, but we’re committed to delivering the beta as soon as possible, still aiming for August. We’re also continuing the 3.3.x series with 3.3.1, the upcoming 3.3.2, and a 2.16.2 with Cordova 14 support.

Rspack integration has automation in place. We’re building a modern test suite to ensure quality and stability over time, along with documentation outlining the basic adoption steps. We’re also reviewing other apps and project configurations to make sure the integration can handle any customized project from the Rspack ecosystem. This flexibility work will continue through the betas to ensure compatibility for everyone. Once we have the basic examples, test coverage, and docs in place, we’ll be ready for first 3.4-beta. We’ll communicate here.

3 Likes

For any project that wants to adopt Rspack, start by addressing the prerequisites needed for this to work in any Meteor app.

Today I’ll mention the first one: clearly defining your Meteor app’s entry points.

Meteor apps with a modular structure can adopt the Rspack integration. Modular here means declaring the entry files that start your client and server apps, along with the tests. Your app should specify at package.json:

{
  "meteor": {
    "mainModule": {
      "client": "client/main.js",
      "server": "server/main.js"
    },
    "testModule": "tests.js"
  }
}

Learn more on “Modular application structure” in Meteor.

Before using this modular config with entrypoints (before Meteor 1.7), the Meteor bundler eagerly loaded every JS file it encountered in your project directory and constructed the booter itself. That was less efficient and didn’t match how modern bundlers work. During the Meteor 3.3 beta, some projects that sent me profiles didn’t describe their entrypoints, and I’m not sure they know how.

Describing entrypoints isn’t about creating blank files and listing them in package.json. Those entrypoints must import the rest of your app’s modules in order. An entrypoint is the first module that builds the module tree for your app, so it’s the starting point for booting it. You need to migrate your app to use this approach to use the opt-in Rspack integration.


Please answer the next question so we can understand your app’s state and decide whether to add more automation, documentation or guide to ensure a modular structure with clear entry points.

Do you use entry points in your Meteor app?

  • Yes, my project already uses entry points for the app and tests
  • No, my project doesn’t use entry points, but I know how to migrate
  • No, my project doesn’t use entry points, and I don’t know how to migrate
0 voters
3 Likes

An entry point file for an app makes total sense but for tests it would be a big deterioration to not just include all *.tests.* files. Would it be possible to keep that mode for tests only?

1 Like

In my Meteor‑Rspack app, I first compiled each *.test.js separately by finding them and adding each as a bundler entry. That added overhead and hurt performance. I switched to a single entry point that I maintain manually with “meteor.testModule”.

I’m open to adding an automatic “virtual” entry point, limited to tests, as it only seems to require listing .tests.js files that are likely independent, and ensure watcher keeps active filling that entry point. This approach appears efficient to keep. I’ll consider it for the first beta if time allows, otherwise later.

Definitely, app entrypoints are needed, as you noted. With the app we can’t guarantee the proper load order. We could try to mimic what Meteor internals do, but that’s likely time consuming. I don’t have experience with it yet.

I also considered automatically adding entry points when a project lacks them, as @radekmie suggested here. I could automate it later, and it would help others replicate the process and migrate their apps to proper entry points.

1 Like

Yes I +1 the wanting the test files. note that there are two version .app-test and .test that are the difference between unit test files and functional test files.

It would be possible to do it manually, but its a nice piece of meteor architecture that would be annoying to lose

1 Like

I tried this:

  "meteor": {
    "mainModule": {
      "client": "client/main.js",
      "server": "server/init.js"
    },
    "modern": {
      "transpiler": {
        "verbose": false
      }
    }
  },

However, my server has this folder structure, and the files in the synced-cron folder weren’t being launched.

My file server/init.js is contains:

import "../imports/startup/server";

Should I just update it to this?

import "../imports/startup/server";
import "server/synced-cron/cron.js";
import "server/synced-cron/main.js;

To speed up test and make use of all the cores and RAM on the build server, we partition tests with METEOR_IGNORE to only include certain *.tests.* files and then run test processes in parallel on the same circleci “machine”. Each instance gets a unique port and results file.

    "test:xunit-first-half": "parallel --xapply --tagstring '{= $_=3000+3*seq() =}' --linebuffer --header : 'INCLUDE_RANGE={RANGE} PORT={= $_=3000+3*seq() =} MOCHA_OUTPUT_FILE=result{#} yarn test:xunit' ::: RANGE a-c d-j k-n",
"test:xunit": "unset MONGO_URL && bash -c 'TEST_CONSOLE_LOGGING=true TYPESCRIPT_FAIL_ON_COMPILATION_ERRORS=1 METEOR_IGNORE=$(server/buildmeteorignore $INCLUDE_RANGE) TEST_CLIENT=0 MOCHA_REPORTER=xunit MOCHA_TIMEOUT=60000 SERVER_MOCHA_OUTPUT=$PWD/mocha/${MOCHA_OUTPUT_FILE:-results}.xml meteor test --once --driver-package meteortesting:mocha --exclude-archs web.browser.legacy --port \"[::]:${PORT:-3000}\"'",    

with buildmeteorignore being:

#!/usr/bin/env bash
# builds contents for METEOR_IGNORE to include only test files that start with
# the character range provided as arg1
# OR with invert as arg2 to EXCLUDE that character range
# TEST_PREFIX is set to "app-" when running ui tests since then the test files are named XX.XX.app-tests.ts
echo "# Generated METEOR_IGNORE contents"

if [[ -z $1 ]]; then
    # no argument => no contents
    exit
fi

RANGE=${1:-a-z}
if [[ $2 == invert ]]; then
    PREFIX=""
else
    echo "*.${TEST_PREFIX}tests.ts"
    PREFIX="!"
fi

# for debugging
# echo >&2 RANGE=$RANGE PREFIX=$PREFIX

echo $PREFIX[$RANGE]*.${TEST_PREFIX}tests.ts

/me checking the Meteor Forums every day, to see if the new 3.4 RSPack release has shipped yet.

4 Likes

Same! :joy: It’s in my muscle memory now first thing in the morning I type “for” and this thread on the forums autocompletes in my chrome address bar.

Definitely anxious for an RC to try with my projects!

2 Likes

Thanks to everyone who voted. Most projects already use the expected package.json entrypoints in Meteor’s config.

For those who don’t but know how to adapt them, that’s great. For those who don’t and need help, please reach me privately via DM in the forums. Over the coming weeks, while the beta is running, I’ll assist on adapting projects. I’ll also work to understand how to add entrypoints to projects without them by the approach mentioned above. @vikr00001 let’s move this to private messages, I’ll DM you when available.

For tests, projects still use both eager runs and full-app mode. I’ve updated the Rspack integration to support all test setups, and results look fine. Whether you use testModule with separate configs for client and server, a single config, no testModule at all with the eager approach, or run --full-app, everything pass as expected. :white_check_mark:


@permb your approach to partitioning tests with Meteor ignore is solid. Since Meteor was the bundler in charge, that worked well. With the new Rspack integration, METEOR_IGNORE now applies broadly to source code (including tests), and delegation goes to Rspack. To achieve a similar setup you’ll need to use rspack.config.js at your project root with the Rspack IgnorePlugin, for example:

import path from "path";
import { IgnorePlugin } from "@rspack/core";
import { defineConfig } from "@meteorjs/rspack";

export default defineConfig(Meteor => {
  const range = process.env.TEST_RANGE || "a-z";         // e.g. "a-m"
  const invert = process.env.TEST_INVERT === "true";     // "true" to exclude range
  const testPrefix = Meteor.isTestApp ? "app-" : "";     // matches ".app-tests.ts" vs ".tests.ts"
  const testSuffix = `.${testPrefix}tests.ts`;
  const rangeRe = new RegExp(`^[${range}]`);

  return {
    plugins: [
      ...(Meteor.isTest || Meteor.isTestApp
        ? [
            new IgnorePlugin({
              checkResource(resource) {
                const filename = path.basename(resource);
                // Only act on test files with the expected suffix
                if (!filename.endsWith(testSuffix)) return false;
                const inRange = rangeRe.test(filename);
                return invert ? inRange : !inRange;
              },
            }),
          ]
        : []),
    ],
  };
});

This is just an example to show how rspack.config.js is introduced in Meteor projects. The code snippet above is not tested, but should work similarly in your env once you adopt Rspack.

The rspack.config.js file lets you customize your Rspack setup with plugins and configurations, acting directly on the modern bundler process. Meteor now only handles Atmosphere packages and linking app code with the Meteor core.

Keep in mind Rspack is optional, but if you opt in, you’ll need to adjust parts of your current setup, at least for more complex projects.

3 Likes

I’m really excited about all the attention this integration is getting. Like many of you, I can’t wait to see it evolve. This will go through several betas before a stable RC, but it’s worth it. The benefits are huge for Meteor users: better performance, smaller bundles, and new features. What’s even better is how much ground a single integration already covers, solving long-standing bundler issues that would have been nearly impossible to address one by one with a small team.

A quick update on the state: our initial goal was to ship the first beta in August. But after the releases of 3.3.1, 3.3.2, and 2.16.2, plus some uncovered Rspack integration details (like full support for Meteor test options), the timeline has slipped a bit.

The good news is we now support all official Meteor skeleton projects: React, TypeScript, Blaze, CoffeeScript, Solid, Svelte, and Vue. Angular experiments may follow after the first beta. Some setups need small migration steps, but they’re straightforward and already covered in the Rspack docs with clear rspack.config.js examples.

To ensure stability and avoid regressions, we’ve built a new modern test suite. It runs across all skeletons and checks key things: server responses, page rendering, dev/prod builds, and multiple configurations. Every skeleton now passes these tests as shown in the picture.

The suite also goes beyond skeleton coverage. It validates critical features and past Meteor limitations: HMR, dynamic imports, custom aliases with file and node_module redirects, SWC/Babel configs, React compiler, ESM imports (like react-router v7), style compilers (Less, SCSS, Tailwind), and more. This gives us a strong base to add tests/features without breaking existing ones. Before, skeletons had no tests at all, some even broken over time.

What’s left now is documentation and final cleanup before the PR. I expect to try an internal beta next week. Public beta will take longer, since I want to ensure published version works as from checkout tests, deployment on Galaxy and docs readiness. Realistically, that means at least one more week if everything goes well. In the meantime, I’ll share more details in this post about migration steps and integration benefits so you can start preparing.

12 Likes

Today I will introduce the last major prerequisite to adopt Rspack in your Meteor app: transforming nested imports into a standard style.

Nested imports are a Meteor-specific bundler feature not supported by standard tools. They come from the reify package and allow you to place import statements inside nested blocks (ifs, function bodies, etc.), deferring evaluation of the imported code (why nested imports?). In standard JavaScript you should instead use require or dynamic import (which can also split your bundle).

Example of a nested import:

// import { a as b } from "./c"; // root import

if (condition) {
  import { a as b } from "./c"; // nested import
  console.log(b);
}

Don’t confuse nested imports with dynamic imports (await import(...)). Dynamic imports are a modern standard for bundlers, letting you split bundles and defer loading safely.

Migration

To identify and fix nested imports in your project, use verbose mode in Meteor 3.3’s modern transpiler. Enable it with:

"meteor": {
  "modern": {
    "transpiler": {
      "verbose": true
    }
  }
}

When you run your app, [Transpiler] logs will show each file. Focus on (app) files that fail with messages like:

Error: 'import' and 'export' cannot be used outside of module code

Fix nested imports by moving them to the top of the file, or by replacing them with require or dynamic import.

You can skip migrating (package) code with nested imports. Meteor packages are still handled by the Meteor bundler in Rspack integration, but your app code is fully delegated to Rspack and must use standard syntax.

Backwards compatibility

While backwards compatibility was straightforward for the SWC integration in Meteor 3.3, achieving the same for Rspack in Meteor 3.4 is harder. In Meteor 3.3, we had a fallback mechanism using Babel+Reify per file, which SWC couldn’t handle but still worked at a file level. With Rspack, the integration is at the bundle level: the entire app code is processed by a modern bundler that not only parses files individually but also links them into a single distributable. Since Reify has its own way of linking modules, ensuring compatibility with a modern bundler has been difficult.

In the future, we might explore solutions, but our focus is on moving Meteor forward with a modern-first approach. For nested imports, there’s little reason to keep them today since standard alternatives exist and bring clear benefits with modern bundlers.

Remember, Rspack integration is optional. You can continue using Meteor’s bundler and still benefit only from Meteor 3.3 optimizations if you prefer.


Please answer the following question so we can assess how prepared your project is for nested imports migrations.

When using verbose mode, do you have nested imports in your <app> code?

  • Yes, I have nested imports, but I will migrate them
  • My code is standardized and ready to integrate Rspack
  • Regardless of nested imports, I prefer to keep Meteor specifics and will skip Rspack
0 voters
5 Likes

So looking forward to this!

Can you share anything about the reimplementation of dynamic imports with rspack? Is this provided by rspack now?

Just gave this verbose transpiler a run, and it found a half dozen nested imports in our projects. We were able to get rid of any warnings by replacing the nested imports with the following syntax:

  if (Package['browser-policy-common']) {
    console.log('Configuring content-security-policy.');
    import('meteor/browser-policy-common').then(({ BrowserPolicy }) => {
      // Use BrowserPolicy here
      BrowserPolicy.content.allowSameOriginForAll();
      // etc, etc....
    });
  }

Everything updated. Ready to RSPack when it drops.

4 Likes

I tried the beta and ran into a few broken cases

import { S3Client } from "@aws-sdk/client-s3";
console.log("s3client", S3Client);

Ends up generating a error:

Error: Cannot find module '@swc/helpers/_/_await_async_generator'
W20250903-16:03:41.027(-7)? (STDERR)     at makeMissingError (packages/modules-runtime.js:221:12)
W20250903-16:03:41.027(-7)? (STDERR)     at Module.require (packages/modules-runtime.js:240:17)

If that gets resolved there is then an issue with aldeed:collection2

import "meteor/aldeed:collection2/static";
Error: Cannot find package "aldeed:collection2". Try "meteor add aldeed:collection2".
W20250903-16:12:21.624(-7)? (STDERR)     at makeInstallerOptions.fallback (packages/modules-runtime.js:704:13)
W20250903-16:12:21.624(-7)? (STDERR)     at Module.require (packages/modules-runtime.js:243:14)
W20250903-16:12:21.624(-7)? (STDERR)     at Module.mod.require 

Both of these are runtime errors, as the packager believes everything is working

I updated to v3.4 beta0 and ran my app on dev. I didn’t test every function, but what I ran seems to be working as usual

Yesterday I kicked off the publish CI job for 3.4-beta at the end of the day. This morning I already have feedback, which is great. I appreciate the time and testing. Thanks :pray:

I’m verifying everything matches the checkout version, since I found some inconsistencies. The immediate issue is CI errors when releasing to specific architectures. Then, I’ve received some reports on the integration itself. I’ll check whether they are critical or just missing scenarios I can fix later. I may fix the quick and critical ones and republish.

My plan was to test the beta to verify stability and release a few quick internal betas to ensure it. Then I’ll publicly communicate here that the beta is live for everyone along the rest information.

6 Likes