Meteor 1.3 from a 20,000 Foot View

Note: This blog post is a republish from my original post over at The Meteor Chef.

Meteor 1.3 is a game changer. There are huge changes coming down the pipe, so here’s a quick and dirty TL;DR summary of the new features so you can get back to building great software.

Matt DeBergalis, founder of the Meteor Development Group, has said that the themes surrounding the Meteor 1.3 release will make Meteor “about professional apps” and help get it “closer to the other ecosystems” of the JavaScript world. MDG is doing this by supporting ES2015 modules, NPM modules, and creating a Meteor testing story (among other improvements).

As a reminder, all of the following topics are still in beta as of writing, which means the final implementations could change.

Alright, let’s jump in!

ES2015 modules with import and export

In Meteor 1.2 we saw the addition of the ecmascript package, which allowed developers to start using ES2015 features like const, let, and destructuring. When 1.2 was released, a big piece of ES2015 was missing: modules. What are modules? As our applications grow in complexity, we need more control over things like load order, dependency management, and performance optimization. To accomplish this, we can break our apps into smaller, discrete bits of code that we can import and export as needed called modules.

Previously, modules were only possible through emulation with tools like CommonJS (modules.export) or RequireJS (require()). But now, with the new ES2015 modules syntax, alternatives like these are no longer required. Now, by using the new ES2015 modules, we no longer need to wory about things like load order of files, circular dependencies, or global namespace pollution. That means we have more time to build well-structured, reusable, and more maintainable applications!

Imports and Exports with ES2015 Modules

ES2015 modules allow us to export and import our code on the client and the server. A module consists of a single file with two kinds of exports: named and default. A module can have several named exports, but only one default export. We’re rewarded for using default exports when importing their contents because the exported value doesn’t need to be referenced by name. However, when we import a named export, we must know the name of that piece of code exported to import it.

/client/modules/cat-stuff.js

export const CATURDAY = "Saturday";
export function catLoaf() {}

let toys = ["yarn", "feather", "catnip"];

export { toys as catToys };

In the example above, we are exporting CATURDAY and catLoaf as named exports. We are also exporting our toys array as catToys. Because these are named exports, if we want to access their contents we must import them specifically as CATURDAY, catLoaf, and catToys (e.g., import CATURDAY from '/server/modules/catStuff.js). toys must be accessed as catToys because we renamed the variable before exporting it (i.e., import catToys from '/server/modules/catStuff.js').

/client/modules/laser-pointer.js

export default function laserPointer() {
  return { color: "#DA5347;", position: Math.random() };
}

A default export is similar to a named export in terms of behavior, however, the default keyword suggests that any variable can be used to reference the passed value when importing. For example, we could import laserPointer into another file like import laZerPointer from '...' or import LASERPOINTER from '...' and get the same result. This only works because of the default keyword used in our export statement.

Alernative Default Export Syntax

const function eatSleepPlayRepeat () {};

export { eatSleepPlayRepeat as default };

Alternatively, we can export a default value using a syntax similar to the renaming concept we saw above with named exports export { something as default }.

Importing our exports

Below is an example of how to import the modules we exported above:

/client/cat-distraction-machine.js

// Importing a default export. .js is implied on filenames so we
// can omit it while importing.
import laserPointer from "./modules/laser-pointer";

// Importing named exports.
import { CATURDAY, catLoaf, catToys } from "./modules/cat-stuff";

When importing default exports, we can name the import whatever we want. However, when importing named exports, we must use the name exported from the module (i.e., CATURDAY is explicitly exported from the module above and needs to be imported with the same name). If you’d like, you can also import and rename that import at the same time to match the context of the current file:

/client/cat-distraction-machine.js

import { catToys as toysCatsLove } from "./modules/cat-stuff";

ES2015 modules in packages

Good news: package developers can also take advantage of the new modules! In addition to adding api.use( 'modules' ) and api.use( 'ecmascript' ), 1.3 introduces a new api.mainModule method to describe where the package’s main entry point is (i.e., where we are exporting all of our code from).

/cat-distractor/package.js

Package.describe({
  name: 'cat-distractor'
  [...]
});

Package.onUse( function ( api ) {
  api.use( 'ecmascript' );  // Implies new modules package too.
  api.mainModule( 'modules/cat-stuff.js', 'client' );
  api.export( CATURDAY );
});

Once a package is installed in an application, we can import it like this:

/client/hello.js

import { CATURDAY, catToys } from "meteor/cat-distractor";

console.log(CATURDAY); // Auto-imported because of api.export.

console.log(catToys); // Exported by modules/cat-stuff.js, but not auto-imported.

Notice the prefixed meteor/cat-distractor; Meteor (Atmosphere) packages are identified with the meteor/ prefix to differentiate them from NPM packages.

How does Meteor handle modules?

The 1.3 module syntax comes to us via a new modules package. It’s installed by default for all new Meteor apps, but must be added to existing apps. Developers can install the modules package or the ecmascript package, which implies the modules package. Then, developers will have access to the export and import features. If they install the modules package without the ecmascript package, they will only have access to the CommonJS require and exports primitives.

To learn more about modules check out Axel Rauschmayer’s chapter on modules from Exploring ES6.

Native NPM modules

In versions of Meteor prior to 1.3, using an NPM package required the creation of a Meteor “wrapper package.”. In 1.3 this is no longer required because the node_modules directory is finally usable in Meteor projects! To use an NPM package, it’s as simple as installing the package with npm install from the root of your project, then importing that into the application.

npm install --save lodash

Importing an NPM Package

import * as _ from "lodash";

Using a Traditional Require

let _ = require("lodash");

NPM in packages is just as easy. In package.js, developers can now use Npm.depends() and any modules within the package can be imported on both the client and the server. The ability to use NPM packages can reduce our workload and is crucial to opening Meteor up to the rest of the Node world.

Official testing support

All of the new modules and NPM support added to Meteor in 1.3 will help developers build better applications, but that does not matter if we cannot ensure our applications work as intended. We need to develop new features and refactor code with confidence, which is why automated testing is crucial for success. Meteor 1.3 delivers an official answer to testing through modules. Because developers can break their code into smaller, maintainable pieces, they can now test their applications more efficiently.

Previously, Velocity—created by Xolv.io—was recognized as the official solution for testing Meteor apps. However, Xolv.io stopped developing Velocity and gave MDG the responsibility to build an official testing framework for Meteor. It looks like they have created a fantastic solution for testing Meteor apps.

How do tests work?

Tests in Meteor 1.3 will have a special .tests.js file extension and can be stored in any directory. Here’s a quick example unit test based on our cat-stuff.js module from earlier:

/client/modules/cat-stuff.tests.js

import { mocha } from "meteor/avital:mocha";
import { chai, assert } from "meteor/practicalmeteor:chai";
import laserPointer from "./cat-stuff.js";

describe("Cat Stuff", () => {
  it("draws a laser pointer's position as a number", () => {
    let laser = laserPointer();
    assert.typeOf(laser.position, "number");
  });
});

Though not required, here, we import the practicalmeteor:chai package into our test file to give us access to the Chai assertion library. Mocha has its own assertion library, but some developers prefer to use Chai. Either will work! Here, we expect our laserPointer() method to return an object with a property position set to a random number value. Using the Chai assert.typeOf() method, we can confirm this.

As of writing, tests are currently run as a two step process. First, by running your application like normal:

meteor

and then by running your test reporter on a separate port (assuming the app is running on port 3000)…

meteor test --driver-package avital:mocha --port 3100

Opening your browser to http://localhost:3100, you should see a report of how your tests are performing. The good news: testing is reactive. When you make a change to your tests—or your application code—the tests will be re-run. This is fairly similar to the approach Velocity took, so if you have experience there the move to the official solution shouldn’t be too jarring.

The Meteor Guide’s (draft) article on testing talks about support for many different test types including unit tests, integration tests, acceptance/end-to-end tests, and load tests (our example above showcases a unit test). Tests can only be written in Mocha right now (using the avital:mocha package), but in the future that will not be a restriction.

Testing is one of the final parts of the 1.3 release to take shape, so expect a lot of changes before the official release!

Conclusion

There are other updates in 1.3 we did not cover, including the hard work to update Cordova, which includes the new WKWebView class and more performant and reliable hot code push and file serving. Meteor 1.3 is currently about 70 percent complete, but that does not mean you cannot work with it right now! To create a new Meteor 1.3 project, just run this command to create it:

meteor create --release 1.3-beta.16

Or update an existing project to 1.3 by running:

meteor update --release 1.3-beta.16

Alright, now get back to work, and build great applications!

If you want to learn more about 1.3, modules, NPM support, testing, or any of the other changes coming, check out these resources: