npm Scripts: Complete Guide to Package.json Scripts
In the previous tutorial we showed you how to work with scopes in npm. In this tutorial we will take a look at how npm handles the "script" field.
Description
The script property of your package.json is supported by npm, for the scripts below:
- prepublish: this is run BEFORE the package is packed and published, it will also need to run on local npm install without any arguments.
- prepare: this is to run both BEFORE the package is packed and published, as well as on local npm install without any arguments. This is going to run AFTER prepublish, but will run BEFORE prepublishOnly.
- prepublishOnly: this will run BEFORE the package is prepared and packed, and ONLY on npm publish.
- prepack: this will run BEFORE a tarball is packed (on npm pack, npm publish, as well as when installing git dependencies)
- postpack: this is run AFTER the tarball has been generated and moved into its final destination.
- publish, postpublish: this is run AFTER the package is published.
- preinstall: this is run BEFORE the package is installed
- install, postinstall: This will run AFTER the package is installed.
- preuninstall, uninstall: this is run BEFORE the package is uninstalled.
- postuninstall: this will run AFTER the package is uninstalled.
- preversion: this will run BEFORE bumping the package version.
- version: this will run AFTER bumping the package version, but BEFORE commit.
- postversion: this will run AFTER bumping the package version, and AFTER commit.
- pretest, test, posttest: this is run by the npm test command.
- prestop, stop, poststop: this is run by the npm stop command.
- prestart, start, poststart: this is run by the npm start command.
- prerestart, restart, postrestart: this is run by the npm restart command. It should be noted that: npm restart runs the start and stop scripts if no restart script is provided.
- preshrinkwrap, shrinkwrap, postshrinkwrap: this is run by the npm shrinkwrap command.
In addition, you can execute arbitrary scripts by running npm run-script
DEPRECATION NOTE
As from [email protected], the npm CLI runs the prepublish script for both npm publish and npm install, because it is a convenient way to prepare a package for use (some common use cases will be described in the next section). It has also turned out to be very confusing in practice. Since [email protected], there is an introduction of a new event, prepare, which preserves this existing behavior. A new event, prepublishOnly has been added as a transitional strategy to enable users avoid the confusing behavior of existing npm versions and it only runs on npm publish (for instance, when running the tests one last time to ensure they're in good shape).
USE CASES
In the case where you need to perform operations on your package before it is used, in a way that will not be dependent on the architecture or operating system of the target system, you should use a prepublish script. This includes tasks like:
- Compiling a CoffeeScript source code into JavaScript.
- Creating a minified version of a JavaScript source code.
- Fetching the remote resources that your package will use.
The advantage of doing these things at prepublish time, is that they can be done once, in a single place, this reduces complexity and variability. Additionally, what this means is that:
- You will be able to depend on coffee-script as a devDependency, and hence your users do not need to have it installed.
- You don't have to include minifiers in your package, and this reduces the size for your users.
- You do not need to rely on your users having curl or wget or any other system tools on the target machines.
DEFAULT VALUES
npm defaults some scripts based on the package contents.
"start": "node server.js":
Whenever there is a server.js in the root of you package, then npm defaults the start command to node server.js.
"install": "node-gyp rebuild":
Whenever there is a binding.gyp file in the root of your package and you have not defined your own install or preinstall scripts, npm defaults the install command to compile using node-gyp.
USER
Whenever npm is invoked with root privileges, then it changes the uid to the user account or the uid that is specified by the user config, this defaults to nobody. you should set the unsafe-perm flag to run scripts with root privileges.
ENVIRONMENT
A package script runs in an environment where many pieces of information are made available regarding the setup of npm and the current state of the process.
path
If your project depends on modules that define executable scripts, such as test suites, then those executables are added to the PATH for executing the scripts. So, you have a package.json that is like this:
{ "name" : "eyster"
, "dependencies" : { "eyster" : "0.1.x" }
, "scripts": { "start" : "eyster ./test" }
}
Then you could run the npm start to execute the eyster script, which will be exported into the node_modules/.bin directory when you run npm install.
package.json vars
All package.json fields are tacked onto the npm_package_ prefix. So, if you had {"name":"bar", "version":"1.2.4"} in your package.json file, then your scripts would have the npm_package_name environment variable set to "bar", and the npm_package_version set to "1.2.4". These variables can be accessed in your code using process.env.npm_package_name and process.env.npm_package_version, and this applies for other fields.
Configuration
The configuration parameters are added to the environment using the npm_config_ prefix. For example, you will be able to view the effective root config if you check the npm_config_root the environment variable.
special:package.json "config" object
The package.json "config" keys will be overwritten in the environment if there is a config param of <name> [@<version>]:<key>. For instance, if your package.json is like this:
{ "name" : "vikta"
, "config" : { "port" : "8080" }
, "scripts" : { "start" : "node server.js" }
}
And the server.js will be like this:
http.createServer(...).listen(process.env.npm_package_config_port)
then the user can change the behavior by writing this:
npm config set vikta:port 80
current lifecycle event
Lastly, the npm_lifecycle_event environment variable is set to whichever stage of the cycle that is being executed. So, you might have a single script that is used for different parts of the process which switches based on what's currently happening.
Objects will be flattened following this format, so, if you had {"scripts":{"install":"bar.js"}} in your package.json, then you would see this in the script:
process.env.npm_package_scripts_install === "bar.js"
EXAMPLES
For instance, if your package.json has this:
{ "scripts" :
{ "install" : "scripts/install.js"
, "postinstall" : "scripts/install.js"
, "uninstall" : "scripts/uninstall.js"
}
}
then scripts/install.js is called for the install and the post-install stages of the lifecycle, and scripts/uninstall.js is called when the package is uninstalled. Because scripts/install.js runs for two different phases, it would be wise to look at the npm_lifecycle_event environment variable in this case.
If you wish to run a make command, you can do that. This will work just fine:
{ "scripts" :
{ "preinstall" : "./configure"
, "install" : "make && make install"
, "test" : "make test"
}
}
EXITING
Npm runs scripts by passing the line as a script argument to sh.
If the script exits with a code that is not 0, then it will abort the process.
Note that these script files do not have to be a nodejs or even a javascript program. They just need to be some kind of executable file.
HOOK SCRIPTS
If you need to run a specific script at a specific lifecycle event for ALL packages, then you might use a hook script.
You can place an executable file at node_modules/.hooks/{eventname}, and it will be run for all packages when they are going through that point in the package lifecycle for any packages installed in that root.
Hook scripts will be run exactly the same way as package.json scripts. That is, they will be in a separate child process, with the env file described above.
BEST PRACTICES
- Do not exit with a non-zero error code except you really mean it. Except in the case of uninstall scripts, this causes the npm action to fail, and might be rolled back. Where the failure is minor or only prevents some optional features, then it is better to just print a warning and exit successfully.
- You should try not to use scripts to do what npm can do for you. Read our tutorial on package.json to see all the things that you can specify and enable simply by describing your package appropriately. Generally, this leads to a more robust and consistent state.
- You should inspect the env to determine where to put things. For instance, if you set the npm_config_binroot environment variable to /home/user/bin, then you should not try to install executables into /usr/local/bin. Probably, the user set it up that way for a reason.
- You should not prefix your script commands with "sudo". When the root permissions are required for some reason, then it will fail with that error, and then the user will sudo the npm command in question.
- You should not use install. Instead, you should use a .gyp file for compilation, and prepublish for any other thing. Often, it is advisable not to ever set a preinstall or install script explicitly. However, if you do this, you should consider if there is another option. The only valid use of preinstall or install scripts is for compilation which must be done on the target architecture.
Previous:
Scoped Packages in npm: Complete Guide and Usage.
Next:
Understanding SemVer: Versioning in npm with Semantic Versioning.
- Weekly Trends and Language Statistics
- Weekly Trends and Language Statistics