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:
- The Meteor Guide
- The
modules
readme in the 1.3 release - Transmission - Episode 6 with Ben Newman
- Meteor 1.3 and Beyond by Matt DeBergalis
- Building an Error Logger by TMC
- Building a Chat Application by TMC