From

Atmosphere

to

npm

Namir Sayed-Ahmad Baraza

@namirsab [github|twitter]

namirsab@gmail.com

I'm a software engineer at

We use meteor to develop our tool

What we are going to see...

Introduction

  • What is atmosphere?
  • What is npm?
  • Meteor Roadmap

Migrating a Package

  • From 1.2 to 1.3 with ES6 Modules
  • From 1.3 to npm

Conclusions

  • Pros
  • Cons

Introduction

As we know, javascript development is crazy

It's changing  every day

But

We

Use Meteor!

  • Blaze for the Frontend / Templating System
  • MongoDB as the Database
  • Atmosphere as our package manager
  • Meteor magics for the rest!

Now is different

  • Blaze / React  / Angular[1.x|2.x]

  • MongoDB ( but Apollo in the Roadmap)

  • Atmosphere and npm as packages managers

  • Meteor for the rest ( but less than before)

What Is Atmosphere?

  • Package manager for Meteor

  • It is the best friend of Meteor developer

$> meteor add username:packagename

Pros:

Cons:

  • Simple to use
  • All for Meteor
  • Simple to write and publish packages
  • Outside of the general ecosystem
  • Version managing is really strict

What Is npm?

  • Originally the Node Package Manager

  • Nowadays, is just the Javascript Package Manager

  • Just like pip  for Python and gem for Ruby

$> npm install packagename [options]

Pros:

Cons:

  • Very configurable
  • +250 000 packages
  • Snippet catalog
  • Simple to write and to publish a package
  • Used among all the community
  • Allows private npm
  • More complex to use than  Atmosphere
  • It's not only  for Meteor

What is in the Roadmap?

"1.3 introduced npm install support along with ES2015 modules. With 1.4, we would like to transition the Meteor package ecosystem over entirely from Atmosphere to npm"

MDG in the Meteor Roadmap

Migrating a Package

From 1.2 to 1.3 with ES6 modules

const height = new ReactiveVar(window.outerHeight);
const width = new ReactiveVar(window.outerWidth);
const $window = $(window);

Meteor.startup(() => {
    $window.resize(function() {
        height.set(window.outerHeight);
        width.set(window.outerWidth);
    });
});

// Global Symbol necessary to export it
WindowState = {
    isMaximized() {
            const currentHeight = height.get();
            const currentWidth = width.get();
            const availHeight = screen.availHeight * 0.9;
            const availWidth = screen.availWidth * 0.9;

            return !(currentHeight < availHeight || currentWidth < availWidth);
        },

        height: () => height.get(),
        width: () => width.get()
};

Original code

Original package.js

Package.describe({
    name: 'qlp:qlp-window-state',
    version: '0.0.1',
    // Brief, one-line summary of the package.
    summary: '',
    // URL to the Git repository containing the source code for this package.
    git: '',
    // By default, Meteor will default to using README.md for documentation.
    // To avoid submitting documentation, set this field to null.
    documentation: 'README.md'
});

Package.onUse(function (api) {
    api.versionsFrom('1.2.0.2');

    api.use([
        'ecmascript'
    ]);
    // Packages for Client
    api.use([
        'jquery',
        'reactive-var',
    ], 'client');

    api.addFiles('client/WindowState.js', 'client');
    // Export the Global Symbol we've defined in client/WindowState.js
    api.export('WindowState');
});
import { ReactivVar} from 'meteor/reactive-var';
import { jQuery as $} from 'meteor/jquery'; // Note we import everything now

const height = new ReactiveVar(window.outerHeight);
const width = new ReactiveVar(window.outerWidth);
const $window = $(window);

Meteor.startup(() => {
    $window.resize(function () {
        height.set(window.outerHeight);
        width.set(window.outerWidth);
    });
});

// The symbol is not global anymore
const WindowState = {
    isMaximized() {
        const currentHeight = height.get();
        const currentWidth = width.get();
        const availHeight = screen.availHeight * 0.9;
        const availWidth = screen.availWidth * 0.9;

        return !(currentHeight < availHeight || currentWidth < availWidth);
    },

    height: () => height.get(),
    width: () => width.get()
};

// We export it using ES6 exports
export {
    WindowState
};

New Code

New package.js

Package.describe({
    name: 'qlp:qlp-window-state',
    version: '1.0.0',
    // Brief, one-line summary of the package.
    summary: '',
    // URL to the Git repository containing the source code for this package.
    git: '',
    // By default, Meteor will default to using README.md for documentation.
    // To avoid submitting documentation, set this field to null.
    documentation: 'README.md'
});

Package.onUse(function (api) {
    api.versionsFrom('1.3.2.4');

    api.use([
        'ecmascript'
    ]);
    // Packages for Client
    api.use([
        'jquery',
        'reactive-var',
    ], 'client');
    // Now we export it with api.mainModule
    api.mainModule('client/WindowState.js', 'client');
});

Just a few changes

  • Remove global object

  • Remove api.export

  • Use api.mainModule

Warning:

Right now our package is not compatible with 1.2 and below

Migrating a Package

From 1.3 with ES6 modules to npm package

Steps

  • Create /node_modules

  • Create package folder

  • Initialize npm package

$> mkdir node_modules;
$> cd node_modules; 
$> mkdir qlp-window-state
$> cd qlp-window-state && npm init
  • Now fill everything npm asks you
  • Move your package code into the new folder

You should have something like this

my-app/
    node_modules/
        package-name/
            package.json
            myCode.js

And now we can install dependencies directly with npm

$> meteor npm install jquery --save

Back to our example:  package.json

{
  "name": "qlp-window-state",
  "version": "1.0.0",
  "description": "Reactive Window State",
  "main": "client/WindowState.js",
  "author": "quantilope",
  "license": "MIT",
  "dependencies": {
    "jquery": "^2.2.4"
  }
}

Take a look to the dependencies...

Don't you miss something?

reactive-var

First Problem with npm

Packages hosted only on Atmosphere cannot be linked as dependencies  of an  npm package

But we can do a trick...

function importFromMeteorPackage(packageName, symbolName) {
    if(!Package) {
        throw new Error(`This package is only working inside Meteor apps`);
    }
    else if (!Package['reactive-var']) {
        throw new Error(`${symbolName} is required, add it with "meteor add ${packageName}"`);
    }
    else {
        return Package[packageName][symbolName];
    }
}

Let's take a look to the code

import $ from 'jquery'; //jquery now comes from npm

const ReactiveVar = importFromMeteorPackage('reactive-var', 'ReactiveVar');
const height = new ReactiveVar(window.outerHeight);
const width = new ReactiveVar(window.outerWidth);
const $window = $(window);
// No more Meteor.startup. We don't want more Meteor dependecies.
$(document).ready(function () {
    $window.resize(function () {
        height.set(window.outerHeight);
        width.set(window.outerWidth);
    });
});

const WindowState = {
    isMaximized() {
        const currentHeight = height.get();
        const currentWidth = width.get();
        const availHeight = screen.availHeight * 0.9;
        const availWidth = screen.availWidth * 0.9;

        return !(currentHeight < availHeight || currentWidth < availWidth);
    },

    height: () => height.get(),
    width: () => width.get()
};
// npm module.exports instead of export from js
module.exports = {
    WindowState
}

New Code

And then you try to run the app

But it doesn't  work

Uncaught SyntaxError: Unexpected token import

Any  ideas?

Transpile npm packages

In your package folder:

  1. npm install babel-cli babel-preset-es2015 --save-dev
  2. Create build script
  3. Change package entry point to point to the transpiled code
{
  "name": "qlp-window-state",
  "version": "1.0.0",
  "description": "Reactive Window State",
  "main": "./distribution/WindowState.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
     "build": "babel client --presets babel-preset-es2015 --out-dir distribution"
  },
  "author": "quantilope",
  "license": "MIT",
  "dependencies": {
    "jquery": "^2.2.4"
  },
  "devDependencies": {
    "babel-cli": "^6.9.0",
    "babel-preset-es2015": "^6.9.0"
  }
}

New package.json

Now run npm run build

and everything  works!

Conclusions

1.2 Packages

Pros

Cons

  • Compatible with <= 1.3
  • We are used to write them
  • One day they will be deprecated
  • Force us to write global symbols inside packages

1.3 Packages with Modules

Pros

Cons

  • Use of ES6 modules
  • Better syntax. No global symbols.
  • It's more futureproof
  • They don't work < 1.3
  • Force us to write global symbols inside packages
  • Still will be deprecated sooner than later

1.3 npm packages

Pros

Cons

  • npm packages, with all npm features.
  • Embrace the ecosystem. Learning to write npm packages makes us more valuable as developers.
  • It's totally futureproof. Meteor wants to move everything to npm.
  • They don't work < 1.3
  • They are a little bit more complex to handle.
  • You cannot depend on atmosphere packages.
  • No automatic transpiling

Bibliography

Access to the code

  • meteor-lte-1.2
  • meteor-gte-1.3-modules
  • meteor-gte-1.3-npm

Checkout this branches

FromAtmospheretonpm

By Namir Sayed-Ahmad Baraza