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 }
},
}
}