Considerations for integrating Vue in Meteor

Keywords: Javascript Front-end Vue Vue.js

intro

Scenario 1:

If you find an old meteor project with version 1.8.1, it was created before May 2019

In October 2021, you want to create a 1.8.1 meteor project. You can create the project by specifying the version number, as follows:
meteor create vue-meteor-app --release 1.8.1

then:

But when you create a project and want to run it by typing meteor on the command line, you find an error.

Uncaught Error: jQuery not found

He said that you lack the package jquery, and you can't install it through npm i jquery

The jquery package must be added through the command of metal add jquery

meteor add jquery

OK, now the project can run normally.

Scenario 2:

However, when you confidently integrate the vue framework in meteor according to the documents on the official website, you find that you report an error again

While loading plugin vue-component from package akryum:vue-component: Cannot find module

You will think that the project version is the same as 1.8.1, and the step process is also based on the official website. Why can it be created in 2019, but not now in 2021? What has changed.

Later, it was found that the version was changed when akryum: vue component, a necessary plug-in for vue conversion, was installed.

By looking at old projects in 2019
Project root directory /. meteor/versions file
Found akryum:vue-component@0.14.3

the other one

By viewing newly created projects in 2021
Project root directory /. meteor/versions file
Found akryum:vue-component@0.15.3

Conclusion: the plug-in version has changed. 0.14.3 can be used with 1.8.1. If it is 0.15.3, an error will be reported. Therefore, you can install the plug-in by specifying the version.

meteor add akryum:vue-component@=0.14.3

More cmd commands can be viewed through meteor add --help

At this point, the integration of vue framework in meteor is successful.

Then follow the steps of the official website. A lot of emotion!

Integrating Vue With Meteor

To start a new project:

meteor create vue-meteor-app --release 1.8.1

To install Vue in Meteor, you should add it as an npm dependency:

meteor npm install --save vue

To support Vue's Single File Components with the .vue file extensions, install the following Meteor package created by Vue Core developer Akryum (Guillaume Chau).

meteor add akryum:vue-component

If the project doesn't work, the version of akryum: Vue component doesn't match the version of the project 1.8.1.

Therefore, specify the version of akryum: Vue component to install, as follows:

meteor add akryum:vue-component@=0.14.3

You will end up with at least 3 files:

  1. a /client/App.vue The root component of your app
  2. a /client/main.js Initializing the Vue app in Meteor startup
  3. a /client/main.html containing the body with the #app div

We need a base HTML document that has the app id. If you created a new project from meteor create ., put this in your /client/main.html.

<body>
  <div id="app"></div>
</body>

You can now start writing .vue files in your app with the following format. If you created a new project from meteor create ., put this in your /client/App.vue.

<template>
  <div>
    <p>This is a Vue component and below is the current date:<br />{{date}}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      date: new Date(),
    };
  }
}
</script>

<style scoped>
  p {
    font-size: 2em;
    text-align: center;
  }
</style>

You can render the Vue component hierarchy to the DOM by using the below snippet in you client startup file. If you created a new project from meteor create ., put this in your /client/main.js.

import Vue from 'guide/site/content/vue';
import App from './App.vue';
import './main.html';

Meteor.startup(() => {
  new Vue({
    el: '#app',
    ...App,
  });
});

Run your new Vue+Meteor app with this command: NO_HMR=1 meteor

Using Meteor's data system

One of the biggest advantages of Meteor is definitely it's realtime data layer. It allows for so called full-stack reactivity and optimistic UI functionality. To accomplish full-stack reactivity, Meteor uses Tracker. In this section we will explain how to integrate Meteor Tracker with Vue to leverage the best of both tools.

  1. Install the vue-meteor-tracker package from NPM:
meteor npm install --save vue-meteor-tracker

Next, the package needs to be plugged into Vue as a plugin. Add the following to your /client/main.js:

import Vue from 'vue';
import VueMeteorTracker from 'vue-meteor-tracker'; // import the integration package!
import App from './App.vue';
import './main.html';

Vue.use(VueMeteorTracker);                         // Add the plugin to Vue!

Meteor.startup(() => {
  new Vue({
    el: '#app',
    ...App,
  });
});

Example app

If you've followed the integration guide, then your Vue application shows the time it was loaded.

Let's add some functionality that makes this part dynamic. To flex Meteor's plumbing, we'll create:

  1. A Meteor Collection called Time with a currentTime doc.
  2. A Meteor Publication called Time that sends all documents
  3. A Meteor Method called UpdateTime to update the currentTime doc.
  4. A Meteor Subscription to Time
  5. Vue/Meteor Reactivity to update the Vue component

The first 3 steps are basic Meteor:

  1. In /imports/collections/Time.js
Time = new Mongo.Collection("time");
  1. In /imports/publications/Time.js
Meteor.publish('Time', function () {
  return Time.find({});
});
  1. In /imports/methods/UpdateTime.js
Meteor.methods({
  UpdateTime() {
    Time.upsert('currentTime', { $set: { time: new Date() } });
  },
});

Now, let's add these to our server. First remove autopublish so our publications matter:

meteor remove autopublish

For fun, let's make a settings.json file:

{ "public": { "hello": "world" } }

Now, let's update our /server/main.js to use our new stuff:

import { Meteor } from 'meteor/meteor';

import '/imports/collections/Time';
import '/imports/publications/Time';
import '/imports/methods/UpdateTime';

Meteor.startup(() => {
  // Update the current time
  Meteor.call('UpdateTime');
  // Add a new doc on each start.
  Time.insert({ time: new Date() });
  // Print the current time from the database
  console.log(`The time is now ${Time.findOne().time}`);
});

Start your Meteor app, your should see a message pulling data from Mongo. We haven't made any changes to the client, so you should just see some startup messages.

meteor
  1. and 5) Great, let's integrate this with Vue using Vue Meteor Tracker and update our /client/App.vue file:
<template>
  <div>
    <div v-if="!$subReady.Time">Loading...</div>
    <div v-else>
      <p>Hello {{hello}},
        <br>The time is now: {{currentTime}}
      </p>
      <button @click="updateTime">Update Time</button>
      <p>Startup times:</p>
      <ul>
        <li v-for="t in TimeCursor">
          {{t.time}}  -  {{t._id}}
        </li>
      </ul>
      <p>Meteor settings</p>
      <pre><code>
        {{settings}}
      </code></pre>
    </div>
  </div>
</template>

<script>
import '/imports/collections/Time';

export default {
  data() {
    console.log('Sending non-Meteor data to Vue component');
    return {
      hello: 'World',
      settings: Meteor.settings.public,   // not Meteor reactive
    }
  },
  // Vue Methods
  methods: {  
    updateTime() {
      console.log('Calling Meteor Method UpdateTime');
      Meteor.call('UpdateTime');          // not Meteor reactive
    }
  },
  // Meteor reactivity
  meteor: {
    // Subscriptions - Errors not reported spelling and capitalization.
    $subscribe: {
      'Time': []
    },
    // A helper function to get the current time
    currentTime () {
      console.log('Calculating currentTime');
      var t = Time.findOne('currentTime') || {};
      return t.time;
    },
    // A Minimongo cursor on the Time collection is added to the Vue instance
    TimeCursor () {
      // Here you can use Meteor reactive sources like cursors or reactive vars
      // as you would in a Blaze template helper
      return Time.find({}, {
        sort: {time: -1}
      })
    },
  }
}
</script>

<style scoped>
  p {
    font-size: 2em;
  }
</style>

Restart your server to use the settings.json file.

meteor --settings=settings.json 

Then refresh your browser to reload the client.

You should see:

  • the current time
  • a button to Update the current time
  • startup times for the server (added to the Time collection on startup)
  • The Meteor settings from your settings file

Excellent! That's a tour of some of Meteor's features, and how to integrate with Vue. Have a better approach? Please send a PR.

Style Guide and File Structure

Like code linting and style guides are tools for making code easier and more fun to work with.

These are practical means to practical ends.

  1. Leverage existing tools
  2. Leverage existing configurations

Meteor's style guide and Vue's style guide can be overlapped like this:

  1. Configure your Editor
  2. Configure eslint for Meteor
  3. Review the Vue Style Guide
  4. Open up the ESLint rules as needed.

Application Structure is documented here:

  1. Meteor's Application Structure is the default start.
  2. Vuex's Application Structure may be interesting.

SSR and Code Splitting

Vue has an excellent guide on how to render your Vue application on the server. It includes code splitting, async data fetching and many other practices that are used in most apps that require this.

Basic Example

Making Vue SSR to work with Meteor is not more complex then for example with Express. However instead of defining a wildcard route, Meteor uses its own server-render package that exposes an onPageLoad function. Every time a call is made to the server side, this function is triggered. This is where we should put our code like how its described on the VueJS SSR Guide.

To add the packages, run:

meteor add server-render
meteor npm install --save vue-server-renderer

then connect to Vue in /server/main.js:

import { Meteor } from 'meteor/meteor';
import Vue from 'vue';
import { onPageLoad } from 'meteor/server-render';
import { createRenderer } from 'vue-server-renderer';

const renderer = createRenderer();

onPageLoad(sink => {
  console.log('onPageLoad');
  
  const url = sink.request.url.path;
  
  const app = new Vue({
    data: {
      url
    },
    template: `<div>The visited URL is: {{ url }}</div>`
  });

  renderer.renderToString(app, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error');
      return
    }
    console.log('html', html);
    
    sink.renderIntoElementById('app', html);
  })
})

Luckily Akryum has us covered and provided us with a Meteor package for this: akryum:vue-ssr allows us to write our server-side code like below:

import { VueSSR } from 'meteor/akryum:vue-ssr';
import createApp from './app';

VueSSR.createApp = function () {
  // Initialize the Vue app instance and return the app instance
  const { app } = createApp(); 
  return { app };
}

Server-side Routing

Sweet, but most apps have some sort of routing functionality. We can use the VueSSR context parameter for this. It simply passes the Meteor server-render request url which we need to push into our router instance:

import { VueSSR } from 'meteor/akryum:vue-ssr';
import createApp from './app';

VueSSR.createApp = function (context) {
  // Initialize the Vue app instance and return the app + router instance
  const { app, router } = createApp(); 
  
  // Set router's location from the context
  router.push(context.url);
  
  return { app };
}

For more information, please see the official website of meteor https://guide.meteor.com/vue.html

Posted by fazbob on Tue, 26 Oct 2021 21:49:06 -0700