File structure conflict with upgrading to rspack

Hey guys, I hope everyone’s doing well!

I am upgrading a Meteor project to 3.4 and ran into an issue with getting rspack to work. After upgrading to Meteor 3.4 and setting everything up, rspack is unable to find my project’s main.html file and due to this Vue 3 cannot mount when I start the project. The problem seems to stem from my project’s file structure. Instead of having a /client directory directly at the project’s root, I have the following structure:

/src/client/startup/main.js
/src/client/startup/main.html

And in package.json I have the following:

  "meteor": {
    "modern": true,
    "mainModule": {
      "client": "src/client/startup/main.js",
      "server": "src/server/startup/main.js"
    },
    "testModule": "tests/main.js"
  },

This worked fine with previous Meteor versions that used the jorgenvatle package with vite. But now with rspack it seems that having the /client directory under /src causes an issue whereby rspack just ignores the main.html inside src/client/startup. Even though in package.json I have marked the client side mainModule to “src/client/startup/main.js”.

If I add the following line in package.json to the meteor object, everything starts working fine again:

"modules": ["src/client/startup/main.html"],

However this seems like an undocumented option that I would prefer not to rely on. Can anyone suggest a better way to achieve this? Or am I being too skeptical about this line and perhaps it is exactly the right tool for this job without any negative side effects?

If at all possible I would like to avoid having to move everything inside the /src directory to root. Having a /src directory for code is a common pattern.

If anyone’s interested, this is what Claude had to say about the issue:

Root cause: The rspack Meteor package’s configureMeteorForRspack() function builds a list of directories to ignore. It takes all top-level
project directories and excludes only public/, private/, .meteor/, packages/, and the build context. Every other top-level directory —
including src/ — ends up in extraFoldersToIgnore as src/**.

This ignore list is passed to setMeteorAppIgnore(), which tells Meteor’s build pipeline to skip those directories. As a result, static-html
never sees src/client/startup/main.html, so body.html in the compiled output is empty, and

never gets injected into the served
page.

Why myapp (Meteor’s Vue 3 skeleton for 3.4) works: Its HTML is at client/main.html. The root-level client/ directory is similarly placed in the ignore list for rspack — but
that only tells rspack not to bundle those files. The difference is that client/ being at the root level seems to be handled correctly by this
mechanism (or the ignore only applies to rspack bundling, not to the Meteor static-html plugin processing pipeline).

Claude’s second long-winded explanation:

  1. The rspack Meteor package calls configureMeteorForRspack() during startup. It collects all top-level project directories, then adds every non-whitelisted one to METEOR_IGNORE as
    dirname/**.
  2. In myapp (Meteor’s Vue 3 skeleton for 3.4): entrypoint is client/main.js → ignores client/**, but adds the counter-pattern !client/.html → gitignore semantics allow this because client/ directory itself is not directly
    excluded, only its contents — so !client/
    .html successfully un-ignores client/main.html.
  3. In tmk4 (my app): entrypoint is src/client/startup/main.js → ignores src/** (the entire source tree), and adds !src/client/startup/.html as the counter-pattern. This fails — gitignore’s rule
    is: “It is not possible to re-include a file if a parent directory of that file is excluded.” src/** causes src/client/startup/ to be treated as excluded, so the
    !src/client/startup/
    .html exception has no effect.
  4. METEOR_IGNORE is read by optimisticReadMeteorIgnore() in Meteor’s file scanner. With src/** active and no working counter-pattern, the scanner never delivers
    src/client/startup/main.html to the static-html build plugin.
  5. body.html stays empty → no
    → Vue can’t mount.

The fix: The structure src/client/startup/main.html is fundamentally incompatible with how the rspack package generates its ignore rules.

Rspack already documents the modules option, although I might create a dedicated section for it so it’s easier to find and reference.

By default, the Meteor-Rspack integration in Meteor 3.4 makes Rspack handle all app source code in the repository, except some files (*.html, *.css, and other style-like files) inside the Meteor entry folders. These folders are defined by the entrypoints in package.json, usually client/* and server/*.

So the .html file you have into ./src is outside the Meteor entry folders. Meteor ignores it and Rspack handles it instead. However, Rspack does not process HTML files by default unless you define proper pipeline rules in rspack.config, but Rspack doesn’t handle it the way Meteor does.

This is where the meteor.modules field in package.json helps. The paths listed there tell Meteor not to ignore those files and to process them as usual using the Meteor build plugin compilers. For .html, this means merging all .html files into the final HTML served by the app. For style files, it means building the merged stylesheets through Meteor compiler plugins.

1 Like

@nachocodoner, thanks a bunch for the explanation! It seems that this means that it is safe to use the meteor.modules option in package.json. I’ll just reference the main.html file there. Claude had warned me that it was an undocumented feature, so did not realize to look for it in the rspack documentation.

Regarding CSS files, when migrating from version 3.3 to 3.4 my CSS (in src/client/startup/main.css) did not load initially. One fix is to just add “import ./main.css” in my client side main.js file as rspack requires implicit imorting of CSS as well. However, based on your explanation, another option is to add a reference to main.css in package.json under the meteor.modules. Is this the more correct way to do it in Meteor?