Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Writing Packages

Peter Hallam edited this page May 5, 2016 · 6 revisions

Code in Nuclide is organized as a collection of Node/Atom packages.

Creating a new Package

Code in Nuclide is organized as a collection of Node and Atom packages. These are realized as a set of module folders within pkg/nuclide and pkg/fb in the Nuclide project (for ope-source, and Facebook-specific functionality respectively). Each has an entry point (probably main.js), and a package.json file to describe its purpose and APIs.

We have a handy script to create a new Node/Atom package:

# Creates an Atom or Node Package depending on your answers to the prompts.
~/Nuclide/scripts/dev/create_package <package-name>

This will create ~/Nuclide/pkg/nuclide/<package-name> and the associated files.

Developing a Package

In general, once you have run Nuclide/scripts/dev/setup, you can edit .js files in place and use ctrl-alt-cmd-l to reload Atom and test your changes.

Differences between Atom and Node Packages

(See [[Tools/Nuclide/pkg/README.md | https://github.com/facebook/nuclide/blob/master/pkg/README.md]] for a detailed explanation on this topic.)

Most of our packages should be treated as Node packages. Such packages can be require()'d directly. By comparison, Atom package activation order is unpredictable, so they do not make for good dependencies.

If you have some logic that needs to be import'able in an Atom package, just split it into two packages.

Basically, you should only create an Atom package if you are using an API that is unique to Atom packages, which includes:

  • One of the special Atom directories like keymaps, menus, styles, etc. (Though the nuclide-atom-npm often eliminates the need for an Atom package in this case.)
  • A "providedServices" or "consumedServices" entry in your package.json.

Note that what we call a Node package is not traditionally what you would see on npm. Specifically, our "Node" packages can assume the global variable, atom, is present, and can leverage things like "use babel".

The overarching reason we are doing things this way is that the npm community is obsessed with semver and having tons of repos whereas we operate under the "one repo to rule them all" approach. If you're wondering why we don't operate the npm way, take a look at the fraction of commits that are dedicated to bumping version numbers of Atom. It's absurd. Instead, when we upgrade something, we fix all the call sites and everything moves forward together. This is why we symlink our folders rather than copy them.

Importing packages

Use the import statement at the top of your file. Do NOT use require. More info TBD.

Pattern for Main Files

It's common for an activate() function to initialize a number of variables local to the file and for deactivate() to set them to null. This doesn't play well with Flow because a bunch of fields that are non-null for the lifetime of the package have to be declared as nullable for Flow. A good way to work around this to create a class that contains all of the state for the lifetime of the package's activation, which makes it easier to create/tear down:

'use babel';
/* @flow */

import {CompositeDisposable} from 'atom';

class Activation {
  _disposables: CompositeDisposable;

  constructor(state: ?Object) {
    // Assign all fields here so they are non-nullable for
    // the lifetime of Activation.
    this._disposables = new CompositeDisposable();
  }

  activate() {
    // Do activation work here!
  }

  dispose() {
    this._disposables.dispose();
  }
}

var activation: ?Activation = null;

export function activate(state: ?Object) {
  if (!activation) {
    activation = new Activation(state);
    activation.activate();
  }
}

export function deactivate() {
  if (activation) {
    activation.dispose();
    activation = null;
  }
}

Package Do-s and Don't-s

activate

Do not raise events in your activate method. Listeners will not have had a chance to subscribe to your events until after your activate completes. If you are tempted to raise an event in your activate method, then you probably want to defer the work to PackageManager.onDidActivateInitialPackages.

In general, you should use the Services API to communicate across Atom packages. Or move logic out of an Atom package and into a Node package, making it easier to share.

serialize

Do not call atom.config.set from a package's serialize method. atom.config.set does not flush the write to disk, it only schedules the write for sometime in the next 100ms. During app shutdown your chance of getting your config to disk before the process terminates is small.

Checklist

package.json

  • Do begin name with 'nuclide-'
  • Do include a helpful description
Clone this wiki locally