rspack integration: multiple issues migrating a real-world Blaze app - asset URLs ignore ROOT_URL path prefix, native npm addons fail to bundle, nested templates silently dropped
Environment
- Meteor 3.5 (also applies to 3.4.x based on package source)
[email protected],[email protected](Atmosphere),@meteorjs/[email protected]- Blaze app,
ROOT_URL=http://localhost:3000/live/(i.e. a path prefix)
Summary
We migrated a large production Blaze app (multi-directory, eager-loading era, MySQL-backed, served under a ROOT_URL path prefix) to the Rspack bundler and hit three independent problems. Each is reproducible on its own; together they made the migration considerably harder than the documentation suggests. App-side workarounds for all three are included below.
- Every URL the Rspack integration constructs omits the ROOT_URL path prefix, so the client bundle 404s and the app never boots.
- npm dependencies that ship compiled native addons (
.nodebinaries) break the server bundle and must be externalized by hand. - Blaze
.htmltemplates and.cssfiles in nested directories are silently excluded from the build unless individually listed inmeteor.modules.
Issue 1: All Rspack asset URLs ignore the ROOT_URL path prefix
When an app is served under a ROOT_URL path prefix (e.g. https://example.com/live/), every URL the Rspack integration constructs omits the prefix. In meteor run (development) the injected client bundle script tag 404s, so the server boots normally and the page renders its boilerplate, but the application client bundle never executes. This makes the integration unusable for any sub-path deployment, and the failure mode is silent and confusing.
Three faces of the same root cause:
Dev client bundle script tag.
boilerplate-generatorprefixes every bundle script withrootUrlPathPrefix, but emits the Rspack custom script verbatim:// boilerplate-generator (web browser template) }) : template(' <script ... src="<%- src %>"></script>')({ src: rootUrlPathPrefix + pathname // <-- normal bundles get the prefix }); }), process.env.METEOR_APP_CUSTOM_SCRIPT_URL ? template(" <script ... src=\"<%- src %>\"></script>")({ src: process.env.METEOR_APP_CUSTOM_SCRIPT_URL // <-- Rspack script does not }) : ''The env var is set in the
rspackbuild plugin (os/lib/config.js) as/__rspack__/<file>with no prefix. Result: the page requests/__rspack__/client-rspack.js→ 404, while/live/__rspack__/client-rspack.js(the mounted handler) serves fine.Injected CSS link. The
<link href="/build-chunks/main.css">head tag produced via HtmlRspackPlugin is also unprefixed.The integration's own compatibility redirects.
rspack_server.jsredirects/build-chunks/*and/build-assets/*to/__rspack__/...targets that are also unprefixed, so following the redirect still 404s.
Reproduction
meteor create --blaze app && cd app && meteor add rspack(any app with a client mainModule and an imported stylesheet works)ROOT_URL=http://localhost:3000/live/ meteor run- Open
http://localhost:3000/live/: server-side boilerplate renders, but the browser console/network tab showsGET /__rspack__/client-rspack.js→ 404 and no app JS runs.curl -I http://localhost:3000/live/__rspack__/client-rspack.jsreturns 200, confirming only the generated URL is wrong.
Expected
All Rspack-generated asset URLs should be prefixed with
__meteor_runtime_config__.ROOT_URL_PATH_PREFIX, consistent with every other
URL Meteor emits.
Workaround (app side)
In server startup code, before webapp generates the boilerplate:
// Fix 1: prefix the custom script URL
const rspackScriptUrl = process.env.METEOR_APP_CUSTOM_SCRIPT_URL;
const pathPrefix = process.env.ROOT_URL
? new URL(process.env.ROOT_URL).pathname.replace(/\/$/, "") : "";
if (rspackScriptUrl && pathPrefix && !rspackScriptUrl.startsWith(pathPrefix + "/")) {
process.env.METEOR_APP_CUSTOM_SCRIPT_URL = pathPrefix + rspackScriptUrl;
}
// Fix 2: bounce unprefixed asset requests to their prefixed equivalents
if (pathPrefix) {
const rawHandlers = WebApp.rawConnectHandlers || WebApp.rawHandlers;
rawHandlers.use((req, res, next) => {
if (/^\/(__rspack__|build-chunks|build-assets)\//.test(req.url)) {
res.writeHead(307, { Location: pathPrefix + req.url });
res.end();
return;
}
next();
});
}
With these two workarounds the app runs correctly under the prefix in dev and production-simulation modes.
Issue 2: npm packages with native addons (.node binaries) fail to bundle
The server Rspack build attempts to bundle npm dependencies that ship compiled native addons, and fails on the binary itself:
ERROR in ./node_modules/@vlasky/quoted-printable/binding.node
× Module parse failed:
╰─▶ × JavaScript parse error: Unexpected character '�'
help: You may need an appropriate loader to handle this file type.
ERROR in ./node_modules/@vlasky/shacrypt/shacrypt.js 3:26-51
× Module not found: Can't resolve './build/Release/shacrypt'
(The second error is the extension-less require('./build/Release/shacrypt')
convention used by virtually every node-gyp package; the suggested
resolve.extensions fix would only lead back to the first error.)
Expected
Since node_modules is present at server runtime anyway, the integration
should auto-externalize any server dependency that contains a .node binary /
binding.gyp (or simply treat .node requires as commonjs externals by
default), instead of failing the build and requiring users to discover this
pattern themselves.
Workaround (app side)
Externalize each such package by hand in rspack.config.js:
externals: Meteor.isServer
? {
'@vlasky/shacrypt': 'commonjs @vlasky/shacrypt',
'@vlasky/quoted-printable': 'commonjs @vlasky/quoted-printable',
}
: undefined,
Issue 3: Nested Blaze templates and stylesheets are silently dropped
In a multi-directory Blaze app, nested client/**/*.html templates and
client/**/*.css stylesheets are silently excluded from the build unless each
file is individually listed in meteor.modules. The integration's ignore list
keeps only top-level client/*.html and client/*.css on the Meteor side;
everything deeper vanishes. The build succeeds without any warning and the app
runs with all templates missing (Template.x is undefined at runtime, route
controllers fail to register, etc.), which is very hard to diagnose because
nothing errors at build time.
Expected
Either nested .html/.css under the entrypoint directory should be handled
like their top-level counterparts, or the build should emit a warning when
Blaze is enabled and unimported/unlisted .html files exist under the
entrypoint directory. Documentation for migrating multi-directory Blaze apps
would also help: the required combination (list every nested asset in
meteor.modules AND import every .html from the client entry module so
RequireExternalsPlugin wires them back to the Meteor compiler) is currently
undocumented and was only discoverable by reading the integration source.
Workaround (app side)
- List every nested
.html/.css/.lessfile inmeteor.modulesinpackage.json. - Import every
.htmltemplate file from the client entry module (before the JS modules that reference the templates).
Source: meteor/meteor