Very bumpy upgrade to 3.4.1 and Rspack - Still can't build

We’re upgrading our Meteor 3.3.2 app to 3.4.1 with Rspack. The journey has been a bit bumpy and we’re currently crashing in production (along with a few other weird things). It’s been quite a bit more work from the historical Meteor upgrade experience. I wanted to outline our issues and get support for our production build problems. I’m wondering if any of our below work-arounds is breaking the app when it actually builds in production.

While Rspack is much faster, has a smaller build, and refreshes faster, it is so much pickier than the traditional Meteor/Babel bundler. We’ve had to make the following changes:

CSS Issues

We keep a few different app-wide CSS files in ui/stylesheets. These were no longer automatically loaded. They now have to be statically imported at the end of startup/client/index.js to preserve CSS ordering alongside per-component React CSS files that live inside ui/react that do get manually imported.

Paths to images and fonts within our CSS files broke. We had to add the following to rspack.config.js:

module: {
  parser: {
    // Don't resolve url() references in CSS — pass them through as-is so the browser
    // resolves them at runtime. Old Meteor's CSS processor did the same; fonts and other
    // assets are served as Meteor public/ static files at their natural paths.
    css: { url: false },
    'css/auto': { url: false },
    'css/module': { url: false },
  },
  },

Symlink Handing

This is a big one. This is where the old Meteor bundler with Babel was very forgiving.

Our production app is broken into multiple micro-services that share a lot of code, mostly collection/schema/API code, but some client code/templates (e.g. image uploaders, etc.) as well. By default with Rspack, any symlink file’s import paths are resolved where the symlink file lives, not where it’s loaded. Meteor/Babel used the standard handling, which is the symlink file is treated exactly as if it were really there, so its relative paths load from where the symlink is located, not where the original file is located. This broke all over for us as our current codebase is architected around the standard symlink handling. We had to use the work-around from this bug report which adds the following to rspack.config.js:

module.exports = defineConfig(Meteor => {
  const swcOptions = JSON.parse(JSON.stringify(Meteor.swcConfigOptions || {}));
  if (swcOptions.jsc) {
    delete swcOptions.jsc.baseUrl;
    delete swcOptions.jsc.paths;
  }

  return {
    ...Meteor.replaceSwcConfig(swcOptions),
    resolve: {
      symlinks: false
    },
    module: {...}
   };
});

I’m not totally sure what this does, but it does make the symlinks work in the standard way. But I’m wondering if this could be what is causing us issues in our production build (see below).

HTML File Duplication

The above symlink work-around however doesn’t work on HTML Blaze files so it doesn’t resolve relative imports in symlink client files that import HTML files. Unfortunately we had to actually duplicate a few utilities and Blaze templates instead of being able to simply symlink. We’ll definitely be tracking that, as we’ve already had to make a few changes in the duplicated files. :face_with_diagonal_mouth:

Dynamic Imports

We got spoiled on the Meteor/Babel bundler dynamic import pattern:

if(Meteor.settings.public.SERVICE_ID === "ABC") {
  //  Selectively dynamically import if this is service ABC
  import('../../widget/server/utils.js').then((_ServerUtils) => {ServerUtils = _ServerUtils;});
}

//  Lower in code
if(Meteor.settings.public.SERVICE_ID === "ABC") {
  const widget = await ServerUtils._getCachedWidget(...);
  ...
}

This was nice because it lets us symlink the above file on different services, and the above pattern would dynamically load and run on the right service. Not in Rspack. It very much dislikes the above. I tried quite a few work-arounds with require and /* webpackIgnore: true */ but it will always try to resolve ../../widget/server/utils.js everywhere, which will cause an error on the services that are not "ABC" because they don’t have the file. The only solution without refactoring was suppressing the errors in rspack.config.js using the full path:

plugins: [
  new IgnorePlugin({ resourceRegExp: /^\.\/utils\.js$/, contextRegExp: /api\/widget\/server/ }),
  //  Additional IgnorePlugin entries
]

This successfully suppresses the errors when building locally and also when building in Docker. We only have a handful of these, and yes, the files should be re-architected to prevent this. But, it’s a historical pattern we’ve had that always worked in Meteor/Babel.

Symlink Import Warnings

Similar to the above Dynamic Imports, many shared symlink api files (e.g. api/widgets/server/utils.js) will have imports to functions from files that exist in all services, but are unique per service (e.g. startup/server/configurations.js). That file on service "ABC" may not export {s3Client} because that service doesn’t use AWS S3, but the api file has an import for it, but the function that uses it never runs on service "ABC". In Rspack, it throws a warning. In Meteor/Babel it allowed it. Again, this should probably be shored up with better file splitting, but it’s a pattern we used often. So rspack.config.js needed warning suppressions added:

ignoreWarnings: [
  /export 's3Client'.*was not found/,  
  //  Additional warning entries
]

I didn’t want to blanket suppress all warnings, so we added specific entries for each so we can see new ones if they occur.

Package Import Tweaks

We had a few package imports that needed their import syntax tweaked. And we had to add some aliases for a few packages’ CSS files.

Building Issues

After all of the above, we have an error- and warning-free build locally in development and our app runs totally fine. However, I can’t seem to get a Docker build working.

If I build using meteor build --directory /tmp/meteor-test-build --server-only the app successfully builds using Rspack just like it does when running the development version.

However, I have a local Docker build setup using disney:meteor-base that we use to test CI Docker builds. It builds for linux/amd64 on Apple Silicon and worked great with Meteor 3.3.2. I’ve updated to Meteor 3.4.1 (and according to the issues resolved disney:meteor-base works with Meteor 3.4.1 and Rspack), but no matter what I try, when the build gets to RUN bash $SCRIPTS_FOLDER/build-meteor-bundle.sh which just meteor build --directory $APP_BUNDLE_FOLDER --server-only, it hangs on that step and tries to run a Rspack development server with the logs:

#   "testClient": "_build/test/client-meteor.js",                                                                                                                                                
#   "testServer": "_build/test/server-meteor.js"                                                                                                                                                 
# }                                                                                                                                                                                              
# [i] Rspack DevServer Port: 8080                                                                                                                                                                
# [i] Rspack default config: /opt/src/node_modules/@meteorjs/rspack/rspack.config.js                                                                                                             
# [i] Rspack custom config: /opt/src/rspack.config.js 

Clause says its mistakenly thinking its in the development environment. I’ve thrown a bunch of Claude suggestions at it and nothing is working.

For production, we deploy to AWS ECS using Bitbucket Docker Pipeline and build on linux/amd64. The build process was successful in a test for staging, but the actual deployed builds crashed out hard with the browser console filling up with dozens of errors:

Uncaught ReferenceError: Package is not defined
Uncaught ReferenceError: Package is not defined
Uncaught ReferenceError: Package is not defined

So weren’t currently unable to get a local Docker build test working or get a working build for our staging CI workflow.

And we’re only testing one micro-service to begin with and it actually doesn’t use any symlinks and has nothing too weird in the rspack.config.js, so it’s nothing related to that, so far as I can tell.