Migrating from Blaze to Vue 3, with Meteor 3

We’re currently looking at migrating a Meteor 3 + Blaze app to Vue 3. I’ve found the Meteor 3 + Vue 3 tutorial but it has this warning at the top:

This tutorial uses the vue-meteor-tracker package, which is currently in beta and does not support async calls yet. However, it is still a valuable package, and we hope it will be updated soon. We are also working on a new tutorial that will use Meteor core packages instead.

I’m wondering what the current best practices are for Vue 3 in Meteor – are folks continuing to use vue-meteor-tracker in its beta form like the tutorial does, or something different? (I’m also confused about what the reference to async calls means above – how are async calls not supported?)

Has anyone found a good approach to migrating a Blaze app to Vue 3 piecemeal so that they can coexist for a period of time in production?

1 Like

vue-meteor-tracker seems abandoned, I could not get it to work with Vue 3 and I do not see much reason to use it anyway. I just use Meteor tracker directly in Vue 3. I described it here: Subscriptions and live queries for Vue 3 projects without vue-meteor-tracker

A bit more verbose, but one less entry in your dependencies that can cause trouble down the line.

1 Like

Just on the subject of vue-meteor-tracker, I remembered before Akryum released the updated vue-meteor-tracker for Vue 3, I already had a proof of concept for the Vue 2 version of the composition API (back in the RFC days) using Tracker, ReactiveVar and ref/reactive from Vue.

(It is gross, mixed with (deprecated) business code, and causes some errors with recent updates of Vue, so I won’t share it here)

Since then I’ve mostly used Tanstack Query, but I experimented a bit more later on with some useSubscription/useTracker composables/hooks at some point, and I’ve just had a review of @vooteles’ code and tried to think about how it could be turned into a hook, so I’ve quickly regurgitated all of that information and come up with this (untested) draft idea, where I’m trying to have the lightest-touch approach possible while keeping the two reactivity systems synced up:

import { Meteor } from 'meteor/meteor'
import { Tracker } from 'meteor/tracker'
import { ReactiveVar } from 'meteor/reactive-var'
import { ref, watch, reactive, set, computed, type Ref, onUnmounted, WatchSource } from 'vue'

export function useTracker<T>(source: WatchSource, query: () => Promise<T> | T) {
    const data = ref<T | undefined>()
    const error = ref<any>()
    const hasError = ref<boolean>(false)

    const tracker = Tracker.autorun(() => {
        try {
            // Ideally some Mongo.Collection query goes here
            Promise.resolve(query())
                .then(result => {
                    data.value = result
                    hasError.value = false
                    error.value = undefined
                })
                .catch(e => {
                    console.error(e)
                    hasError.value = true
                    error.value = e
                })
        } catch (e) {
            console.error(e)
            hasError.value = true
            error.value = e
        }
    })

    watch(source, () => {
        tracker.invalidate()
    })

    onUnmounted(() => {
        tracker.stop()
    })

    return {
        data,
        hasError,
        error,
    }
}

export function useSubscription(key: string, params?: Ref<any[]>) {
    return useTracker(params ?? ref(), () => {
        Meteor.subscribe(key, ...params?.value ?? [])
    })
}

So then you’d use it like:

<script setup lang="ts">
import { useTracker, useSubscription } from '/imports/hooks/meteor'

useSubscription('admin.users.all')

const {
  data: users,
  error: usersError,
  hasError: usersHasError
} = useTracker(() => Meteor.users.find({ ... }).fetch())

//...
</setup>

<template>
  <WarningMessage v-if="usersHasError">
    {{ String(usersError) }}
  </WarningMessage>
  ...
</template>

I noticed there’s still a bit of a difference between our approaches @vooteles, so I wanted to know if I was missing anything critical that you found when testing on your end.

Particularly worried with potential conflicts with multiple subscriptions supplying the same collection with data.

The other thing I noticed reviewing the source code for Akryum’s vue-meteor-tracker is that it seems much more in-depth than what I’ve got above. I’m not sure what I’m missing out on if that’s the case.

(I need to start a new Meteor 3 Vue project to try this out, my two Meteor 3 projects are an in-progress update from Meteor 2 to 3 with lots of Tanstack Query, and a React project :sweat_smile:)

The current best practices for Vue 3 in Meteor 3 are still evolving. The vue-meteor-tracker beta package is popular but has limitations around async. Some use the core Meteor packages directly. For migrating Blaze to Vue 3, a gradual approach like using {{> Template.dynamic}} can work.

1 Like

@ceigey I do not see anything in your code that immediately stands out as a potential issue. But then again, I have not really looked much into Tracker past the specific need I had myself when doing the Vue 3 update.

One difference we have is that you are using tracker.invalidate() when data changes, while I am just stopping one tracker instance and creating another. Probably not the most efficient approach on my end. But likely with no actual effect on performance.

Generally the negative side of using tracker directly like this is that it is so verbose. And much of that is simply to stop tracker instances that are not needed any more. If that code can be separated into its own file, then that is very nice. But for me, it is OK to have those pieces live inside actual component code as well. DRY has never been my absolute top priority :slight_smile:

1 Like