Using npm Packages
Searching for packages
The best way to find npm packages is by searching on npmjs.com. There are also some websites that have special search features specifically for certain kinds of packages, like the aptly named react-components.com.
npm on the client
Tools like browserify and webpack are designed to provide a Node-like environment on the client so that many npm packages, even ones originally intended for the server, can run unmodified. Meteor’s ES2015 module system does this for you out of the box with no additional configuration necessary. In most cases, you can simply import npm dependencies from a client file, just as you would on the server.
When creating a new application Meteor installs the
meteor-node-stubs
npm package to help provide this client browser compatibility. If you are upgrading an application to Meteor 1.3 you may have to runmeteor npm install --save meteor-node-stubs
manually.
Installing npm Packages
npm packages are configured in a package.json
file at the root of your project. If you create a new Meteor project, you will have such a file created for you. If not you can run meteor npm init
to create one.
To install a package into your app you run the npm install
command with the --save
flag:
meteor npm install --save moment
This will both update your package.json
with information about the dependency and download the package into your app’s local node_modules/
directory. Typically, you don’t check the node_modules/
directory into source control and your teammates run meteor npm install
to get up to date when dependencies change:
meteor npm install
If the package is just a development dependency (i.e. it’s used for testing, linting or the like) then you should use --save-dev
. That way if you have some kind of build script, it can do npm install --production
and avoid installing packages it doesn’t need.
For more information about npm install
, check out the official documentation.
Meteor comes with npm bundled so that you can type
meteor npm
without worrying about installing it yourself. If you like, you can also use a globally installed npm to manage your packages.
Using npm Packages
To use an npm package from a file in your application you simply import
the name of the package:
import moment from 'moment'; // this is equivalent to the standard node require: const moment = require('moment');
This imports the default export from the package into the symbol moment
.
You can also import specific functions from a package using the destructuring syntax:
import { isArray } from 'lodash';
You can also import other files or JS entry points from a package:
import { parse } from 'graphql/language';
Importing styles from npm
Using any of Meteor’s supported CSS pre-processors you can import other style files from both relative and absolute paths from an npm package.
Importing styles from an npm package with an absolute path using the {}
syntax, for instance with Less:
@import '{}/node_modules/npm-package-name/button.less';
Importing styles from an npm package with a relative path:
@import '../../node_modules/npm-package-name/colors.less';
You can also import CSS directly from a JavaScript file to control load order if you have the ecmascript
package installed:
import 'npm-package-name/stylesheets/styles.css';
When importing CSS from a JavaScript file, that CSS is not bundled with the rest of the CSS processed with the Meteor Build tool, but instead is put in your app’s
<head>
tag inside<style>...</style>
after the main concatenated CSS file.
npm Shrinkwrap
package.json
typically encodes a version range, and so each npm install
command can sometimes lead to a different result if new versions have been published in the meantime. In order to ensure that you and the rest of your team are using the same exact same version of each package, it’s a good idea to use npm shrinkwrap
after making any dependency changes to package.json
:
# after installing meteor npm install --save moment meteor npm shrinkwrap
This will create an npm-shrinkwrap.json
file containing the exact versions of each dependency, and you should check this file into source control. For even more precision (the contents of a given version of a package can change), and to avoid a reliance on the npm server during deployment, you should consider using npm shrinkpack
.
Asyncronous Callbacks
Many npm packages rely on an asynchronous, callback or promise-based coding style. For several reasons, Meteor is currently built around a synchronous-looking but still non-blocking style using Fibers.
The global Meteor server context and every method and publication initialize a new fiber so that they can run concurrently. Many Meteor APIs, for example collections, rely on running inside a fiber. They also rely on an internal Meteor mechanism that tracks server “environment” state, like the currently executing method. This means you need to initialize your own fiber and environment to use asynchronous Node code inside a Meteor app. Let’s look at an example of some code that won’t work, using the code example from the node-github repository:
// Inside a Meteor method definition updateGitHubFollowers() { github.user.getFollowingFromUser({ user: 'stubailo' }, (err, res) => { // Using a collection here will throw an error // because the asynchronous code is not in a fiber Followers.insert(res); }); }
Let’s look at a few ways to resolve this issue.
Meteor.bindEnvironment
In most cases, simply wrapping the callback in Meteor.bindEnvironment
will do the trick. This function both wraps the callback in a fiber, and does some work to maintain Meteor’s server-side environment tracking. Here’s the same code with Meteor.bindEnvironment
:
// Inside a Meteor method definition updateGitHubFollowers() { github.user.getFollowingFromUser({ user: 'stubailo' }, Meteor.bindEnvironment((err, res) => { // Everything is good now Followers.insert(res); })); }
However, this won’t work in all cases - since the code runs asynchronously, we can’t use anything we got from an API in the method return value. We need a different approach that will convert the async API to a synchronous-looking one that will allow us to return a value.
Meteor.wrapAsync
Many npm packages adopt the convention of taking a callback that accepts (err, res)
arguments. If your asynchronous function fits this description, like the one above, you can use Meteor.wrapAsync
to convert to a fiberized API that uses return values and exceptions instead of callbacks, like so:
// Setup sync API const getFollowingFromUserFiber = Meteor.wrapAsync(github.user.getFollowingFromUser, github.user); // Inside a Meteor method definition updateGitHubFollowers() { const res = getFollowingFromUserFiber({ user: 'stubailo' }); Followers.insert(res); // Return how many followers we have return res.length; }
If you wanted to refactor this and create a completely fiber-wrapper GitHub client, you could write some logic to loop over all of the methods available and call Meteor.wrapAsync
on them, creating a new object with the same shape but with a more Meteor-compatible API.
Promises
Recently, a lot of npm packages have been moving to Promises instead of callbacks for their API. This means you actually get a return value from the asynchronous function, but it’s just an empty shell where the real value is filled in later.
The good news is that Promises can be used with the new ES2015 async/await
syntax (available in the ecmascript
package since Meteor 1.3) in a natural and synchronous-looking style on both the client and the server.
If you declare your function async
(which ends up meaning it returns a Promise itself), then you can use the await
keyword to wait on other promise inside. This makes it very easy to serially call Promise-based libraries:
async function sendTextMessage(user) { const toNumber = await phoneLookup.findFromEmail(user.emails[0].address); return await client.sendMessage({ to: toNumber, from: '+14506667788', body: 'Hello world!' }); }
Shrinkpack
Shrinkpack is a tool that gives you more bulletproof and repeatable builds than you get by using npm shrinkwrap
alone.
Essentially it copies a tarball of the contents of each of your npm dependencies into your application source repository. This is essentially a more robust version of the npm-shrinkwrap.json
file that shrinkwrap creates, because it means your application’s npm dependencies can be assembled without the need or reliance on the npm servers being available or reliable. This is good for repeatable builds especially when deploying.
To use shrinkpack, first globally install it:
npm install -g shrinkpack
Then use it directly after you shrinkwrap
meteor npm install moment meteor npm shrinkwrap shrinkpack
You should then check the generated node_shrinkwrap/
directory into source control, but ensure it is ignored by your text editor.
NOTE: Although this is a good idea for projects with a lot of npm dependencies, it will not affect Atmosphere dependencies, even if they themselves have direct npm dependencies.
© 2011–2017 Meteor Development Group, Inc.
Licensed under the MIT License.
https://guide.meteor.com/v1.3/using-npm-packages.html