-
Notifications
You must be signed in to change notification settings - Fork 681
Writing Packages
Code in Nuclide is organized as a collection of Node/Atom packages.
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.
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.
(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 thenuclide-atom-npmoften eliminates the need for an Atom package in this case.) - A
"providedServices"or"consumedServices"entry in yourpackage.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.
Use the import statement at the top of your file. Do NOT use require. More info TBD.
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;
}
}
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.
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.
package.json
- Do begin
namewith 'nuclide-' - Do include a helpful
description