Demystifying Meteor

a.k.a. The Disclaimer

demystify

/diːˈmɪstɪfʌɪ/

(verb) Make (a difficult subject) clearer and easier to understand.

In other words: how Meteor does its magic.

And that it's not magic at all.

Goal of this presentation

Universal code

Universal code

All files in a client directory or with a client part in their name will be accessible only on the client.

(Of course the same works for server.)

Universal code

import { Meteor } from 'meteor/meteor';

// This code runs everywhere.

if (Meteor.isClient) {
  // This code runs only on client.
}

if (Meteor.isServer) {
  // This code runs only on server.
}

Remember!

Meteor will include all the code in the client bundle. It may change though: meteor/meteor#11164.

Tracker

What is Tracker?

Tracker is essentially a simple convention, or interface, that lets reactive data sources (like your database) talk to reactive data consumers (such as a live-updating HTML templating library) without the application code in between having to be involved.

~ Tracker docs

Tracker gives you much of the power of a full-blown Functional Reactive Programming (FRP) system without requiring you to rewrite your program as a FRP data flow graph.
Meteor Tracker is an incredibly tiny (~1k) but incredibly powerful library for transparent reactive programming in JavaScript.

API

import { Thermometer } from 'best-thermometer-library';

import { Tracker } from 'meteor/tracker';

const temperatureDependency = new Tracker.Dependency();

export function currentTemperature() {
  temperatureDependency.depend();
  return Thermometer.read();
}

Thermometer.onChange(() => {
  temperatureDependency.changed();
});
import { Tracker } from 'meteor/tracker';

import { currentTemperature } from './thermometer';

Tracker.autorun(() => {
  console.log(`Temperature: ${currentTemperature()}°C`);
});

DDP

What is DDP?

Distributed Data Protocol (or DDP) is a client-server protocol for querying and updating a server-side database and for synchronizing such updates among clients. It uses the publish-subscribe messaging pattern. It was created for use by the Meteor JavaScript framework. The DDP Specification is located on GitHub.

~ Wikipedia

DDP is simple

DDP supports only two operations*

  1. Remote Procedure Calls (RPC)

  2. Subscriptions (Pub-Sub)

(*) establishing connection and "heartbeats"
are technically operations as well

DDP is real-time

DDP may use either SockJS or WebSockets as a lower-level message transport.

At any time [...] the server can send data messages to the client. [...] These messages model a local set of data the client should keep track of.

~ DDP Specification

Methods

Data flow in Meteor

API

import { Meteor } from 'meteor/meteor';

// Call a method.
Meteor.call('createTask', 'Buy more tea.');
import { Tasks } from '../collections/tasks';

Tracker.autorun(() => {
  const tasks = Tasks.find().fetch();
  console.table(tasks);
});
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';

// Define a collection.
export const Tasks = new Mongo.Collection('Tasks');

// Define a method.
Meteor.methods({
  createTask(taskName) {
    // TODO: Validate your inputs!
    Tasks.insert({ name: taskName });
  }
});

Optimistic UI

Optimistic UI will work only if the method definition (Meteor.methods) is both on the client and server side. Server-only methods will not be simulated.

It is 100% correct to have a different client- and server-side definition to achieve optimistic UI and keep the code hidden.

Publications

API

import { Meteor } from 'meteor/meteor';

import { Tasks } from '../collections/tasks';

// Define a publication.
Meteor.publish('tasks', (...args) => {
  // TODO: Limit published documents!
  return Tasks.find();
});
import { Meteor } from 'meteor/meteor';

// Subscribe.
Meteor.subscribe('tasks', ...args);

Low-level API

import { SomePubSubLibrary } from 'some-pub-sub-lib';

import { Meteor } from 'meteor/meteor';

// Define a low-level publication.
Meteor.publish('external-data', function (topic) {
  // TODO: Validate inputs!
  const handler = SomePubSubLibrary.subscribe(topic);

  handler.on('data', data => {
    this.added('external-pub-sub', topic, data);
  });

  this.ready();
  this.onStop(() => {
    this.removed('external-pub-sub', topic);
  });
});

It's completely transparent for the client.

See vlasky:mysql for an example.

Merge Box & MiniMongo

import { Meteor } from 'meteor/meteor';

// Documents are sent only once.
Meteor.subscribe('tasks');
Meteor.subscribe('tasks');
Meteor.subscribe('tasks');

Fibers

What are Fibers?

Fibers, sometimes called coroutines, are a powerful tool which expose an API to jump between multiple call stacks from within a single thread. This can be useful to make code written for a synchronous library play nicely in an asynchronous environment.

~ node-fibers docs

What...?

What are Fibers?

Attempt #2

import Fiber from 'fibers';

function sleep(ms) {
  const fiber = Fiber.current;
  setTimeout(() => fiber.run(), ms);
  Fiber.yield();
}

const fiber = new Fiber(() => () {
  console.log(new Date());
  sleep(42 * 1000);
  console.log(new Date());
});

fiber.run();

console.log('Done.');

// $ node sleep.js
// Fri Oct 11 2019 10:30:00 GMT+0100 (CET)
// Done.
// Fri Oct 11 2019 10:30:42 GMT+0100 (CET)

What are Fibers?

Attempt #2

import Fiber from 'fibers';

function sleep(ms) {
  const fiber = Fiber.current;
  setTimeout(() => fiber.run(), ms);
  Fiber.yield();
}

const fiber = new Fiber(() => () {
  console.log(new Date());
  sleep(42 * 1000);
  console.log(new Date());
});

fiber.run();

console.log('Done.');

// $ node sleep.js
// Fri Oct 11 2019 10:30:00 GMT+0100 (CET)
// Done.
// Fri Oct 11 2019 10:30:42 GMT+0100 (CET)


async function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

async run() {
  console.log(new Date());
  await sleep(42 * 1000);
  console.log(new Date());
}

run();

console.log('Done.');

// $ node sleep.js
// Fri Oct 11 2019 10:30:00 GMT+0100 (CET)
// Done.
// Fri Oct 11 2019 10:30:42 GMT+0100 (CET)

What are Fibers?

Attempt #3

NOTE OF OBSOLESCENCE -- The author of this project recommends you avoid its use if possible. The original version of this module targeted nodejs v0.1.x in early 2011 when JavaScript on the server looked a lot different. Since then async/await, Promises, and Generators were standardized and the ecosystem as a whole has moved in that direction.

~ node-fibers docs

Why it's important?

async function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

async run() {
  console.log(new Date());
  await sleep(42 * 1000);
  console.log(new Date());
}

// Meteor only!
run().await();
// ...or...
Promise.await(run());

console.log('Done.');

// Fri Oct 11 2019 10:30:00 GMT+0100 (CET)
// Fri Oct 11 2019 10:30:42 GMT+0100 (CET)
// Done.

Tooling

Tooling

  1. MiniMongoExplorer

  2. Meteor DevTools

  3. Meteor DevTools Evolved

  4. METEOR_PROFILE=0

  5. ROOT_URL=http://192.168.21.37

  6. --extra-packages bundle-visualizer --production

  7. --inspect & --inspect-brk

FAQ

FAQ

  1. Can I disable Merge Box?

  2. Where can I read more?

Questions?

Demystifying Meteor

By Radosław Miernik

Demystifying Meteor

  • 740