Node.js Packaging Guidelines
The upstream Node.js stance on global library packages is that they are ".. best avoided if not needed." In Fedora, we take the same stance with our nodejs packages. You can provide a package that uses nodejs, but you should bundle all the nodejs libraries that are needed.
What to Package
-
The interpreter, development headers/libraries, and the assorted tools to manage project-level installations.
-
Examples: nodejs, npm, yarn
-
-
Packages that provide applications that users would want to use in their shell.
-
Examples: uglify
-
Naming Guidelines
-
Application packages that mainly provide tools (as opposed to libraries) that happen to be written for Node.js must follow the general Naming Guidelines instead.
-
The name of a Node.js extension/library package must start with nodejs- then the upstream name or name used in the npm registry. For example: nodejs-foomodule. While it is uncommon for a package’s name to contain node, if it does, you should still add the nodejs prefix. For instance, the npm registry contains a uuid and a node-uuid module, which would need to be named nodejs-uuid and nodejs-node-uuid, repsectively.
BuildRequires
To build a package that is a nodejs module, bundles or uses nodejs modules, or needs nodejs for building or testing, you need to have:
BuildRequires: nodejs-devel
Macros
The following macros are defined for you:
Macro | Normal Definition | Notes |
---|---|---|
__nodejs |
%{_bindir}/node |
The Node.js interpreter |
nodejs_version |
e.g. 0.9.5 |
The currently installed version of Node.js. |
nodejs_sitelib |
%{_prefix}/lib/node_modules |
Where Node.js modules written purely in JavaScript are installed |
nodejs_sitearch |
%{_prefix}/lib/node_modules |
Where native C++ Node.js modules are installed |
nodejs_symlink_deps |
%{_prefix}/lib/rpm/nodejs-symlink-deps |
See Symlinking Dependencies below. |
nodejs_fixdep |
%{_prefix}/lib/rpm/nodejs-fixdep |
See Correcting Dependencies below. |
nodejs_arches |
%{ix86} x86_64 %{arm} |
See #ExclusiveArch. This macro is provided by |
nodejs_default_filter |
%global __provides_exclude_from ^%{nodejs_sitearch}/.*\\.node$ |
Filters unwanted provides from native modules. See Filtering Unwanted Provides below. |
These macros are provided by the nodejs-packaging package.
During %install
or when listing %files
you can use the
%nodejs_sitelib
or %nodejs_sitearch
macro to specify where the
installed modules are to be found. For instance:
%files # A pure JavaScript node module %{nodejs_sitelib}/foomodule/ # A native node module %{nodejs_sitearch}/barmodule/
Using this macro instead of hardcoding the directory in the specfile ensures
your spec remains compatible with the installed Node.js version even if the
directory structure changes radically (for instance, if %nodejs_sitelib
moves into %{_datadir}
).
ExclusiveArch
The V8 JavaScript runtime used by Node.js uses a Just-in-Time (JIT) compiler that is specially tuned to individual architectures, and must be manually ported to any new architectures that wish to support it. Node.js packages must include an ExclusiveArch line that restricts them to only these architectures.
The %{nodejs_arches}
macro is provided to make this easy, so pure
JavaScript packages must use:
ExclusiveArch: %{nodejs_arches} noarch
Native (binary) packages must omit noarch
and list only
%{nodejs_arches}
or the list of architectures as appropriate.
Bundled Licenses
The licenses of the bundled Node.js modules need to be in the spec file. If you are using our bundling script they will be listed in <package>-<version>-bundled-licenses.txt. It is recommended that you include <package>-<version>-bundled-licenses.txt in your rpm
Each time you update your package, you need to verify the bundled licenses against Fedoras Software License List.
List all unique licenses on the License: line of your spec file. Separate each license with the word "and"
License: <license1> and <license2> and <license3> ... Source3: %{npm_name}-%{version}-bundled-licenses.txt ... %prep ... cp %{SOURCE3} . ... %files %license LICENSE %{npm_name}-%{version}-bundled-licenses.txt
If you have further questions refer to the Fedora Licensing Guidelines
Using tarballs from the npm registry
The canonical method for shipping most node modules is tarballs from the npm
registry. The Source0
for such modules should be of the form
http://registry.npmjs.org//-/
.
For instance:-
.tgz
Source0: http://registry.npmjs.org/npm/-/npm-1.1.70.tgz
This method should be preferred to using checkouts from git or automatically generated tarballs from GitHub.
These tarballs store the contents of the module inside a package
directory, so every package using these tarballs should use the following in
%prep
:
%prep %setup -q -n package
Using tarballs for bundling
It is prefered to use tarballs for bundling node module dependencies. These tarballs should be independent from the main package source. There should be two tarballs. One for the binary, runtime package. One for testing while the package builds. This creates a smaller installed package.
These tarballs store the bundled modules in a directory called node_modules_prod, and node_modules_dev.
If your packages does not need one of the tarballs, then change these
instructions accordingly. If it does not need the prod tarball, remove
Source1
plus the %build
and %install
sections. If it does not
need the dev tarball, remove Source2
and the %check
section.
Note1: The setup of the prod and dev tarballs will soon become a macro. At the time of this writting, they are not.
Note2: The tarball with the dev dependencies needs to be unpacked in %check and not in %prep to avoid accidentally bundling the unpackaged dependencies that are only needed for testing.
... Source1: %{npm_name}-%{version}-nm-prod.tgz Source2: %{npm_name}-%{version}-nm-dev.tgz ... %prep ... # Setup bundled runtime(prod) node modules tar xfz %{SOURCE1} mkdir -p node_modules pushd node_modules ln -s ../node_modules_prod/* . ln -s ../node_modules_prod/.bin . popd ... %install ... # Copy over bundled nodejs modules cp -pr node_modules node_modules_prod %{buildroot}%{nodejs_sitelib}/%{npm_name} ... %check %{__nodejs} -e 'require("./")' # Setup bundled dev node_modules for testing # Note: this cannot be in %%prep or the dev node_modules # can get pulled into the regular rpm tar xfz %{SOURCE2} pushd node_modules ln -s ../node_modules_dev/* . popd pushd node_modules/.bin ln -s ../../node_modules_dev/.bin/* . popd # Example test run using the binary in ./node_modules/.bin/ ./node_modules/.bin/vows --spec --isolate ...
Installing Modules
Most node modules do not contain their own install mechanism. Instead they
rely on npm
to do it for them. npm
must not be used to install
modules in Fedora packages, since it usually requires network access.
Instead, install files in their proper place manually using install
or
cp
. Most files should be installed in %{nodejs_sitelib}/
but
documentation should be installed via %doc
. In the event that the module
ships arch independent content other than JavaScript code, that content
should be installed in %{_datadir}
and the module should be patched to
cope with that.
Client-side JavaScript
Many node modules contain JavaScript that can be used both client-side and
server-side and it is sometimes hard to identify code intended only for use
in the browser. Since there are no current packaging guidelines for
client-side JavaScript and bundling of such code is currently permitted in
Fedora, it is currently permissible for client-side JavaScript to be bundled
with nodejs modules in %{nodejs_sitelib}
.
Automatic Requires and Provides
The nodejs package includes an automatic Requires and Provides generator that automatically adds versioned dependencies based on the information provided in a module’s and bundled dependencies package.json file. Additional Requires are added to native (binary) modules to protect against ABI breaks in Node or the V8 JavaScript runtime. Additional Provides: bundled() line is added for e
Provides npm
It also adds virtual provides in the form npm(`
)` to identify modules
listed in the npm registry (the module is listed at npmjs.org) . If a module
is not listed in the npm registry, it must not provide this. Modules that
aren’t listed in the npm registry should set private
to true
in
their package.json
file. If not, you must patch package.json
to
include that.
Provides bundled
It also automatically adds bundled provides in the form
bundled(<bundled_module_name>) = <bundled_module_version>
to identify
bundled modules. Bundled modules must be in either the node_modules or
node_modules_prod directories to be automatically added.
Correcting Dependencies
For non-bundled modules only.
Occasionally the dependencies listed in package.json are inaccurate. For
instance, the module may work with a newer version of a dependency than the
one explictly listed in the package.json file. To correct this, use the
%nodejs_fixdep
RPM macro. This macro should be used in %prep
and
will patch package.json to contain the correct dependency information.
To convert any dependency to not list a specific version, just call
%nodejs_fixdep
with the npm module name of the dependency. This changes
the version in package.json to *
. (Or adds one if it wasn’t already
listed.) For example:
%prep %setup -q -n package %nodejs_fixdep foomodule
You can also specify a version:
%prep %setup -q -n package %nodejs_fixdep foomodule '>2.0'
The second argument to %nodejs_fixdep
must be a valid package.json
version specifier as explained in
+\+`man npm json\
++`.
You can also remove a dependency:
%prep %setup -q -n package %nodejs_fixdep -r foomodule
Symlinking Dependencies
For non-bundled modules only.
Node.js and npm require that dependencies explicitly be included or linked
into a node_modules directory inside the module directory. To make this
easier, a %nodejs_symlink_deps
macro is provided and will automatically
create a node_modules tree with symlinks for each dependency listed in
package.json. This macro should be called in the %install
section of
every Node.js module package.
Building native modules with node-gyp
Most native modules use the node-gyp
tool to build themselves, which
configures and uses the gyp
build framework to build addon modules that
can interact with Node.js and the V8 JavaScript interpreter used by it.
The WAF build framework has been abandoned by upstream and is not supported in Fedora.
BuildRequires
To build a native module designed to be built with node-gyp, add
BuildRequires: node-gyp
, along with BuildRequires: nodejs-devel
and
-devel packages for any shared libraries needed by the module.
%build
Some native modules have Makefiles or other build processes that handle any special needs that module has, such as linking to system versions of dependencies. If present, these should be used. Check the module’s package.json file for information about what command npm will run to build these modules.
Most modules use vanilla node-gyp, and may not have build instructions in package.json. To build these, simply use the following:
%build export CXXFLAGS="%{optflags}" node-gyp rebuild
Note that some modules may specify something like node-gyp configure &&
node-gyp build
. This is equivalent to node-gyp rebuild
.
%install
node-gyp
creates a shared object file with the extension .node
as
part of its build process, which should be located in
build/Release
. This file may be used as the main entry point for the
library, or is utilized by JavaScript wrapper code included with the module.
If the shared object is used as the main entry point, it should be installed
at %{nodejs_sitelib}/`
/index.node. The `require()
function will
automatically load this if there is no corresponding index.js
or entry
point defined in package.json to override it. For example:
%install mkdir -p %{buildroot}%{nodejs_sitelib}/foomodule cp -p build/Release/index.node package.json %{buildroot}%{nodejs_sitelib}/foomodule/
If the shared object is called by JavaScript wrapper code, the situation is slightly more complicated.
If the module uses the npm bindings
module, the shared object file should be installed in
%{nodejs_sitelib}/
, which is at the top of
bindings search path and where
/build/
.nodenode-gyp
usually creates a symlink to wherever the real shared object
file exists. For example:
%install mkdir -p %{buildroot}%{nodejs_sitelib}/foomodule/build cp -p package.json wrapper.js %{buildroot}%{nodejs_sitelib}/foomodule/ cp -p build/Release/foomodule.node %{buildroot}%{nodejs_sitelib}/foomodule/build/
If the module hardcodes build/Release/
instead, and upstream should be advised
that they should use the bindings
module, because their module could break when users use debug builds of
node..node
, the module should be
patched to use build/
.node
If the module uses it’s own Makefiles to locate the shared object file(s) to a specific location, then those files should installed in that location.
Filtering Unwanted Provides
RPM automatically adds some unwanted virtual provides to the shared object
files included with native modules. To remove them, add
%{?nodejs_default_filter}
to the top of the package’s spec file. For
more information, see
Packaging:AutoProvidesAndRequiresFiltering.
Build testing in %check
All Node.js module spec files must include a %check
section that
contains (at minimum) the line:
%{__nodejs} -e 'require("./")'
This test ensures that the module is actually loadable, which will help avoid situations where a new upstream release has added a new dependency without the packager noticing.
Any other tests made available by upstream should also be run wherever possible.
For convienence, %nodejs_symlink_deps also accepts a --check
argument,
which will make it operate in the current working directory instead of the
buildroot. You can use this in the %check
section to make dependencies
available for running tests. When this argument is used, development
dependencies as listed in the "devDependencies" key in package.json are
also linked.
Bundling Script
It is recommended to use the nodejs-packaging-bundler script found in the Fedora nodejs-packaging-bundler package. More documentation for it can be found at the Fedora nodejs-packaging repo .
Example Spec
%global npm_name tape Name: nodejs-%{npm_name} Version: 5.1.1 Release: 1%{?dist} Summary: Tap-producing test harness for Node.js and browsers License: MIT and ICS URL: https://github.com/substack/tape Source0: http://registry.npmjs.org/%{npm_name}/-/%{npm_name}-%{version}.tgz Source1: %{npm_name}-%{version}-nm-prod.tgz Source2: %{npm_name}-%{version}-nm-dev.tgz Source3: %{npm_name}-%{version}-bundled-licenses.txt BuildArch: noarch ExclusiveArch: %{nodejs_arches} noarch Requires: nodejs BuildRequires: nodejs-devel %description %{summary}. %prep %setup -q -n package cp %{SOURCE3} . # Setup bundled runtime(prod) node modules tar xfz %{SOURCE1} mkdir -p node_modules pushd node_modules ln -s ../node_modules_prod/* . ln -s ../node_modules_prod/.bin . popd %build #nothing to do %install mkdir -p %{buildroot}%{nodejs_sitelib}/%{npm_name} cp -pr package.json index.js lib/ \ %{buildroot}%{nodejs_sitelib}/%{npm_name} # Copy over bundled nodejs modules cp -pr node_modules node_modules_prod \ %{buildroot}%{nodejs_sitelib}/%{npm_name} mkdir -p %{buildroot}%{nodejs_sitelib}/tape/bin install -p -D -m0755 bin/tape %{buildroot}%{nodejs_sitelib}/tape/bin/tape mkdir -p %{buildroot}%{_bindir} ln -sf %{nodejs_sitelib}/tape/bin/tape %{buildroot}%{_bindir}/tape %check %{__nodejs} -e 'require("./")' # Setup bundled dev node_modules for testing # Note: this cannot be in %%prep or the dev node_modules # can get pulled into the regular rpm tar xfz %{SOURCE2} pushd node_modules ln -s ../node_modules_dev/* . popd pushd node_modules/.bin ln -s ../../node_modules_dev/.bin/* . popd # Run tests ./node_modules/.bin/tap test/*.js %files %doc readme.markdown %license LICENSE %{npm_name}-%{version}-bundled-licenses.txt %{nodejs_sitelib}/tape %{_bindir}/tape %changelog