3.4-rc.1 Release Candidate, Faster Builds, Smaller Bundles and Modern Setups with the Rspack integration ⚡

Definitely, it shouldn’t. It has never been my case. The only recompilation I can think of is when moving to a new Meteor version and a Rspack bump at package.json or node_modules reinstallation happens as part of meteor run, it could trigger it. But definitely not all time.

Does this recompilation happen every time for you, or only under specific circumstances?

It’s a surreal experience coming back to a Webpack-style interface after being in the Vite world for so long, I don’t know if I’m doing the right thing but I somehow managed to get this working (in local dev).

Issue with public - resolved?

Originally I had a big issue with all the public/**/*.{svg,ttf,etc} files referred to in CSS files by their absolute path (e.g. /my/fancy.svg) not being found by Rspack, I forgot about externals in the last 6 years but I’m not sure if this is intended? I expected public to just work the same way as before with Meteor but I guess this is an ambiguous situation.

In any case I got ChatGPT to draft me up an ExternalsPlugin callback which seems to work. I feel like I’m missing something obvious that would have saved me this confusion and extra config though.

Current Rspack config

const { defineConfig } = require('@meteorjs/rspack')
const { VueLoaderPlugin } = require('vue-loader')
const { VuetifyPlugin } = require('webpack-plugin-vuetify')
const rspack = require('@rspack/core')
const { RsdoctorRspackPlugin } = require('@rsdoctor/rspack-plugin')

const CSS_EXT_REGEX = /\.(less|sass|scss|css)$/i

/**
 * Rspack configuration for Meteor projects.
 *
 * Provides typed flags on the `Meteor` object, such as:
 * - `Meteor.isClient` / `Meteor.isServer`
 * - `Meteor.isDevelopment` / `Meteor.isProduction`
 * - …and other flags available
 *
 * Use these flags to adjust your build settings based on environment.
 */
module.exports = defineConfig((Meteor) => {
	return {
		experiments: {
			css: true,
		},
		plugins: [
			new RsdoctorRspackPlugin(),
			// Courtesy of ChatGPT
			new rspack.ExternalsPlugin('module', (data, callback) => {
				const req = data.request

				// Webpack provides dependencyType === "url" for CSS url() deps.
				// Rspack is webpack-compatible here, but keep this tolerant.
				const isUrlDep = data.dependencyType === 'url'
				const issuer = data.contextInfo?.issuer ?? ''
				const fromCss = CSS_EXT_REGEX.test(issuer)

				if (req && req.startsWith('/') && (isUrlDep || fromCss)) {
					// Keep the URL as-is (served by your server from /public)
					return callback(undefined, `asset ${req}`)
				}

				callback()
			}),
			new VueLoaderPlugin(),
			new VuetifyPlugin({ autoImport: true }),
			// https://forums.meteor.com/t/3-4-rc-1-release-candidate-faster-builds-smaller-bundles-and-modern-setups-with-the-rspack-integration/64124/132?u=ceigey
		].filter(Boolean), // use this filter to conditionally exclude things
		resolve: {
			alias: {
				'#shared': '/shared',
				'#client': '/client',
				'#server': '/server',
				'#imports': '/imports',
				'#tests': '/tests',
				'~~': '/',
				'@': '/app',
			},
		},
		module: {
			rules: [
				{
					test: /\.vue$/,
					loader: 'vue-loader',
					options: {
						experimentalInlineMatchResource: true,
					},
				},
			],
		},
		externals: {},
	}
})

Font & CSS workarounds

I had to give up on using unplugin-fonts because it seemed to cause a chain reaction with Vue files’ associated scoped CSS fragments being misidentified as JS imports or something weird, and instead I’m using fontsource directly as a replacement.

I also found for some reason that a unovis graph refused to have the correct width, I just had to add a fix for that in CSS:

.unovis-xy-container {
    width: 100%;
}

I don’t know why that appeared, maybe I’ve accidentally uncovered a CSS issue that was hidden while using Vite.

Galaxy deployment

Also, deploying to Galaxy, I need to comment out the new RsdoctorRspackPlugin() line (and un-comment it back in when I want to simulate a production build locally, I guess), otherwise rspack seems to keep working indefinitely without swapping to Preparing to upload your app....

(I’m still testing the deployment, will report back…)

EDIT:
Looks like deployment worked, but something I forgot to test was that accounts still worked (for this particularly deployment, they’re not really that necessary, so it’s easy to overlook them). Funnily enough they broke.

Turns out when I was running:

Accounts.validateLoginAttempt(verifyLoginAttempt)

verifyLoginAttempt was actually a promise from a dynamic import that I forgot to await. Somehow that never caused an issue before! Huh… I have no idea how that only just came up.

Vite config

If anyone’s curious, this is (roughly) my old Vite config (e.g. using jorgenvatle:vite):

/// <reference types="vitest" />

import { defineConfig, Plugin } from 'vite'
import { meteor } from 'meteor-vite/plugin'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import vuetify from 'vite-plugin-vuetify'
import UnpluginFonts from 'unplugin-fonts/vite'
import {} from 'lodash'
import { analyzer } from 'vite-bundle-analyzer'

export default defineConfig({
	test: {
		globals: true,
		root: './',
		include: [
			'server/modules/**/*.spec.ts',
			'shared/domains/**/*.spec.ts',
			'imports/features/**/*.spec.ts',
			'server/features/**/*.spec.ts',
		],
	},
	plugins: [
		analyzer({
			openAnalyzer: true,
			fileName: '.bundle-report.html',
			analyzerMode: 'static',
		}),
		meteorIsServerPlugin(),
		meteor({
			// assetsBaseUrl: '/',
			// assetsDir: 'public',
			clientEntry: 'client/main.js',
			// Optionally specify a server entrypoint to build the Meteor server with Vite.
			serverEntry: 'server/main.js',
			enableExperimentalFeatures: true, // Required to enable server bundling.
			externalizeNpmPackages: ['react', 'react-dom'],
			stubValidation: {
				warnOnly: true,
				ignorePackages: ['meteor/mongo', 'meteor/modules'],
				ignoreDuplicateExportsInPackages: ['react', 'react-dom'],
			},
			meteorStubs: {
				debug: false,
			},
		}),

		vue({
			template: {
				transformAssetUrls: false,
				compilerOptions: {
					whitespace: 'preserve',
				},
			},
		}),

		vuetify({
			// https://www.npmjs.com/package/vite-plugin-vuetify
			autoImport: true,
		}),

		UnpluginFonts({
			fontsource: {
				families: ['Inter Variable', 'IBM Plex Sans Variable'],
			},
		}),
	],
	resolve: {
		alias: {
			// Project internal
			'#shared': path.resolve(__dirname, './shared'),
			// '/app': path.resolve(__dirname, './app'),
			'#client': path.resolve(__dirname, './client'),
			'#server': path.resolve(__dirname, './server'),
			'#imports': path.resolve(__dirname, './imports'),
			'#tests': path.resolve(__dirname, './tests'),
			'~~': path.resolve(__dirname, '.'),
			'@': path.resolve(__dirname, './app'),
		},
	},
	css: {
		preprocessorMaxWorkers: true,
	},
	logLevel: 'silent',
	build: {
		minify: true,
	},
})

// https://github.com/JorgenVatle/meteor-vite/issues/282
function meteorIsServerPlugin(): Plugin {
	return {
		name: 'meteor-isserver-plugin',
		enforce: 'pre',
		transform(code, id) {
			if (code.includes('Meteor.isServer')) {
				console.log('transforming Meteor.isServer', id)

				return {
					code: code.replace(
						/\bMeteor\.isServer\b/g,
						'import.meta.env.SSR',
					),
					map: null,
				}
			}
			return { code, map: null }
		},
	}
}
1 Like

Hey, thanks for reporting your use case.

Issue with public - resolved?

Yes, this is an issue I just noticed with your report. Each project handles styles a bit differently, and so far I’ve adapted several cases where styles either remained compiled by Meteor (when placed close to the client entrypoint), or, when compiled by Rspack, they were referenced via relative paths or with a configured alias like @public.

But you’re right: we should have a default behavior closer to Meteor so projects can transition smoothly. Your report helped us realize that. I’ll add an automatic resolution by default for absolute asset paths (starting with /). This should be quick, and it’s important for many projects to transition directly.

Font & CSS workarounds

I’m not very familiar with unplugin-fonts, but I do want to experiment with it. As part of the 3.4.x series, I’ll expand beyond the setups we’ve reviewed so far.

Could you prepare a small app repository showing your attempts to use this plugin and where it fails? That would help me jump in quickly, understand the problem, and apply fixes in core if a behavior is confirmed to be wrong.


I’m willing to help expand the use cases and improve support for using Rspack with your preferred plugins and tooling. I also hope people keep reporting what they find so we get good coverage for the next official releases.

1 Like

I created a fresh Vue project and found the included Rspack config actually has something I’m missing, the postcss loader, and interestingly that doesn’t trigger the error that unplugin-fonts triggers with the “implicit” CSS config (but the fonts don’t seem to load, interestingly)

I’m starting to think it’s just an unplugin-fonts issue and the exported webpack plugin is not ready to cook. So hesitant to send you down a rabbit hole. Fontsource packages seem to be working in prod so happy enough with that.

Still, here’s a barebones reproduction in case it’s worth a quick glance (comes with postcss loader disabled to see the funky errors):

I would say it’s very consistently doing the double build. I thought it was only on the client but now I saw the server as well!

When I get a moment I’ll dig into the Rspack debug output.

Another troubling thing is I’m finding that rspack is a lot less reliable when it comes to picking up file changes (being deleted, new files installed) than the old system. Often I will need to fully restart to get it start working again.

Haven’t sat down to create a repro but curious if anyone else was seeing that.

Another issue on my end, this time with Push to Deploy. Granted I couldn’t get this working when I was using meteor-vite either, but I get this error now with Rspack:

Preparing to build your app…
Rspack plugin error: Could not find rspack.config.js, rspack.config.ts, rspack.config.mjs, or rspack.config.cjs.

This will ensure @meteorjs/rspack is installed correctly.

Try running npm install in your project directory and then re-run the build.

(this file has been committed and works with manual Meteor deploy, and default app directory is still ./ as per Push to Deploy defaults. @meteorjs/rspack is installed under devDepenendencies)

Earlier on I noticed the builder image is using Node 14 and a lot of dependencies when installed will say that they don’t support this version of Node, e.g.:

npm WARN notsup Not compatible with your version of node/npm: @rspack/core@1.6.7

As a result the builds keep running out of retries.

It’s step 6/6 that seems to have the issue:

RUN cd repo && cd ././ && DEPLOY_HOSTNAME=ap-southeast-2.galaxy.meteor.com TOOL_NODE_FLAGS=–max-old-space-size=6144 meteor •••••••••.au.meteorapp.com --owner •••••• --container-size tiny --plan essentials --settings settings.json --no-wait --allow-superuser --deploy-token *****

Even though that comes from a production app at work, you can recreate with the same project as before, just using the branch deployable-version. I’m in the ap-southeast-2 region (both at work and on my personal account).

Manual deployments work fine so far.

Even though you pointed out this could be an issue on the unplugin-fonts side, I want to confirm it is not something we are doing incorrectly on the Meteor side with the Rspack integration.

I am keeping a note of your message and repository so I can look into it later in the 3.4.x series as a lower priority, since it is not blocking you and may be an external issue. Thank you!

Does it happen on a new project too? For example, with meteor create --release 3.4-rc.1?

I haven’t seen the problem you’re hitting in my tests, or any other report, so I’m trying to rule out anything environment-related. Still, this likely comes from something in your rspack.config.js or something project-specific.

Could you share your config with me privately?, or details of your project (uses React, Blaze or something specific). If I can quickly look through it, I might spot something that could trigger this. But ideally we should find the means to reproduce it in a small repository.

Apparently this is a problem that has been reported and also spotted in Galaxy, happening mostly in automated environments, like Docker images.

As part of Docker images, we should retry automatically. The new mechanism that bumps Rspack or NPM dependencies as part of Meteor releases runs on meteor run/build/deploy, and in some environments it’s not guaranteed that node_modules is stable after the bump. In those cases you need to run npm install and then retry the original meteor run/build/deploy command.

We will fix this in the Galaxy Push to Deploy image, since we already identified a workaround, so it can reinstall and reattempt automatically. As part of manual meteor deploy it works as you stated.

Also, I’m planning a new RC including a new command meteor update --npm, so automated processes (like images) can bump NPM dependencies earlier, run npm install next, and then meteor run/build/deploy should succeed directly. For stability new images will use something like, once the next RC.

(meteor update --npm 2>/dev/null || true) && npm install && meteor deploy [...]
1 Like

Has anyone experienced issues with the SWC using Blaze or require on the client?

Do you refer to the Rspack integration for Blaze projects in Meteor 3.4 betas or RCs, or to SWC in Meteor 3.3 with modern config?

Blaze is supported and covered by tests in Rspack, and it was also tested starting from Meteor 3.3. Do you have any reproduction steps so we can look into the issue further?

I am migrating a Blaze app to 3.0 and want to switch to biome for lint/format but it will not support non-top-level imports which I highly use in Meteor and Blaze so I was wondering how swc will behave especially in case of switching hundreds of inline imports to require. Moving them to top level is no option and neither is dynamic import.

Would be great to have some checks for DX so that if a user hasn’t yet set a meteor.mainModule in package.json it fails with a nicer message.

Rspack plugin error: The "path" argument must be of type string. Received undefined
packages/core-runtime.js:189
            throw error;
            ^

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined
    at Object.dirname (node:path:1457:5)
    at packages/rspack/lib/config.js:157:28
    at Array.map (<anonymous>)
    at configureMeteorForRspack (packages/rspack/lib/config.js:157:5)
    at module.wrapAsync.self (packages/rspack/rspack_plugin.js:152:5) {
  code: 'ERR_INVALID_ARG_TYPE'
}
2 Likes

I am still trying to run my final validation and this repo

Still fails “npm run test-app” even though the same test runs on Meteor 3.3 just fine.

This appears to be something to do with how puppeteer is finding items on the page.

I am going to dive a bit into it today to figure out exactly why its failing, but this as is this is a pretty bad regression for us.

This branch shows it working on 3.3:

Edit:
First thing I noticed was that “this” existed in the testing call in 3.3, but not in RS pack. I added that to the test.

Also in that branch if I do test watch, if I edit a test about 10 times. I end up with a hard test crash. So the problem is definitely not having too many files. It might be architecture? I have an M1 mac, with plenty of ram

I do see this error in my browser console:

pptr:evaluateHandle;step (meteor://💻app/app/app.js:1696840:23):8 Uncaught (in promise) ReferenceError: _to_consumable_array is not defined

So its likely a swc/rspack bundling weirdness.
It might be related to the fact that puppeteer needs to inject code to the base page to add hooks it can play around with. This might have been working on earlier versions of 3.4, I’m not sure.

Could you add this issue and explanation in the Meteor GitHub repository, so we can track and manage it properly?

Also, there’s an interesting report that I’m not sure will solve the RAM issues you mentioned before, but it may be possible to reduce the number of processes running:

I’ll start working on these new reports after the holidays. They will likely land in the 3.4.x series, since the official 3.4 release should come before and will prioritize stable, low-risk changes. In any case, it’s good to have these issues documented and ready for us to tackle with renewed energy in 2026. :grin:

I am migrating a Blaze app to 3.0 and want to switch to biome for lint/format but it will not support non-top-level imports
so I was wondering how swc will behave especially in case of switching hundreds of inline imports to require

I believe you’re referring to nested imports (the “does not support non-top-level imports” point). A nested import and a require() are similar in when they load, but not identical in what they return or which semantics are preserved. require() follows the standard JS mechanism. A nested import uses Reify’s semantics for binding modules, etc. But both load evaluate/cache when line where placed gets run.

So, I don’t see any issue in the migration itself. I haven’t had any problems on my end with how it loads.

I recommend trying small migration steps and seeing how it goes. Share results, and any problem if you have.

Ok damn, these are blocking for us, but I get that it involves specific packages that not everyone uses. Hopefully they get resolved quickly

I late read it but this news is sooo :heart_eyes:

2 Likes

Please consider what I mentioned in this message regarding the bump to the latest released Rspack 1.7.0. In the Meteor-Rspack integration, lazyCompilation should be disabled by setting lazyCompilation: false in your rspack.config.js.

I will disable it by default in the next 3.4 release (with Rspack 1.7.0 bumped as the minimum required) and work on understanding whether it can be supported later in the 3.4.x series.

2 Likes

this worked. Thank you!

2 Likes