5 Features in npm 10
Details on 5 major changes
Introduction
npm is the package manager for the node.js JavaScript platform. It puts modules in place (node_modules) so that the node can find them. It also manages dependency conflicts intelligently.
npm is configurable to support a variety of use cases to publish, discover, install, and develop node programs. It has a list of powerful commands.
npm 10 was released on August 31, 2023. The goal for this major release was to standardize appropriate defaults and clean up legacy configurations where possible.
Here is the command to install npm 10:
% npm install -g npm@10After the installation, npm is at version 10.2.0.
% npm --version
10.2.0This is the help manual:
% npm --help
npm <command>
Usage:
npm install install all the dependencies in your project
npm install <foo> add the <foo> dependency to your project
npm test run this project's tests
npm run <foo> run the script named <foo>
npm <command> -h quick help on <command>
npm -l display usage info for all commands
npm help <term> search for help on <term>
npm help npm more involved overview
All commands:
access, adduser, audit, bugs, cache, ci, completion,
config, dedupe, deprecate, diff, dist-tag, docs, doctor,
edit, exec, explain, explore, find-dupes, fund, get, help,
help-search, hook, init, install, install-ci-test,
install-test, link, ll, login, logout, ls, org, outdated,
owner, pack, ping, pkg, prefix, profile, prune, publish,
query, rebuild, repo, restart, root, run-script, sbom,
search, set, shrinkwrap, star, stars, start, stop, team,
test, token, uninstall, unpublish, unstar, update, version,
view, whoami
Specify configs in the ini-formatted file:
/Users/jenniferfu/.npmrc
or on the command line via: npm <command> --key=value
More configuration info: npm help config
Configuration fields: npm help 7 config
[email protected] /Users/jenniferfu/.nvm/versions/node/v21.1.0/lib/node_modules/npmnpm 10 upgrade can be performed for any supported node version, ^18.17.0 || >=20.5.0.
nvm is a simple way to manage versions for node and npm. We explore npm 10 features in the node.js 21 working environment.
% node --version
v21.1.0
% npm --version
10.2.0These are the new features in npm 10:
- No longer treats missing scripts as a special case in workspace mode
@npmcli/agentis now used as the agent for network requests- Removed strict mode from
npm-package-arg - Removed the deprecated or unused configs
- Don’t retry 409 errors during npm publish
No longer treats missing scripts as a special case in workspace mode
Workspace is a feature in the npm cli that manages multiple packages within a singular root package. This type of repository is also known as a monorepo.
Here is a repository structure of npm-workspace, which will be used throughout this article:
npm-workspace
├── package.json
└── packages
├── package-a
│ └── package.json
└── package-b
└── package.jsonnpm-workspace/package.json is defined as follows, where workspaces are placed under the packages folder:
{
"name": "npm-workspace",
"version": "1.0.0",
"description": "Show npm 10 changes",
"workspaces": ["./packages/*"]
}npm-workspace/packages/package-a.json is defined as follows, with a test script:
{
"name": "workspace-a",
"version": "1.0.0",
"scripts": {
"test": "echo \"Run test in package-b\" && exit 0"
}
}npm-workspace/packages/package-a.json is defined as follows, without a test script:
{
"name": "workspace-b",
"version": "1.0.0"
}Run the following command to have an exit code in the zsh shell:
% PROMPT_COMMAND='printf "Exit code: %s\n" $?'
% precmd() { eval "$PROMPT_COMMAND" }Execute the test script with npm 9 for all workspaces:
% npm --version
9.0.0
Exit code: 0
% npm --workspaces run test
> [email protected] test
> echo "Run test in package-b" && exit 0
Run test in package-b
npm ERR! Lifecycle script `test` failed with error:
npm ERR! Error: Missing script: "test"
To see a list of scripts, run:
npm run
npm ERR! in workspace: [email protected]
npm ERR! at location: /Users/jenniferfu/npm-workspace/packages/package-b
Exit code: 0 npm 9 ignores the missing scripts error, and exit with code 0. The special treatment is coded in runWorkspaces. The bold lines check whether it is scriptMissing, and only set process.exitCode = 1 when it is not scriptMissing.
async runWorkspaces (args, filters) {
const res = []
await this.setWorkspaces()
for (const workspacePath of this.workspacePaths) {
const { content: pkg } = await pkgJson.normalize(workspacePath)
const runResult = await this.run(args, {
path: workspacePath,
pkg,
}).catch(err => {
log.error(`Lifecycle script \`${args[0]}\` failed with error:`)
log.error(err)
log.error(` in workspace: ${pkg._id || pkg.name}`)
log.error(` at location: ${workspacePath}`)
const scriptMissing = err.message.startsWith('Missing script')
// avoids exiting with error code in case there's scripts missing
// in some workspaces since other scripts might have succeeded
if (!scriptMissing) {
process.exitCode = 1
}
return scriptMissing
})
res.push(runResult)
}
// in case **all** tests are missing, then it should exit with error code
if (res.every(Boolean)) {
throw new Error(`Missing script: ${args[0]}`)
}
}Starting from npm 10, the bold lines have been removed in lib/commands/run-script.js.
Execute the test script with npm 10 for all workspaces:
% npm --version
10.2.0
Exit code: 0
% npm --workspaces run test
> [email protected] test
> echo "Run test in package-b" && exit 0
Run test in package-b
npm ERR! Lifecycle script `test` failed with error:
npm ERR! Error: Missing script: "test"
To see a list of scripts, run:
npm run
npm ERR! in workspace: [email protected]
npm ERR! at location: /Users/jenniferfu/npm-workspace/packages/package-b
Exit code: 1 As expected, npm 10 has no special treatment for missing scripts in workspace, and exit with code 1.
If we want to ignore missing scripts in workspace, --if-present can be used to ignore missing scripts and exit with code 0. It works for both npm 9 and 10.
% npm --workspaces --if-present run test
> workspace-a@1.0.0 test
> echo "Run test in package-b" && exit 0
Run test in package-b
Exit code: 0 @npmcli/agent is now used as the agent for network requests
@npmcli/agent is a set of Node.js HTTP Agent classes used by the npm cli. Go to the repository npm-workspack, and cd packages/package-a.
Install the package @npmcli/agent:
% yarn add @npmcli/agentAfter the installation, @npmcli/agent becomes a member of dependencies in npm-workspace/packages/package-a/package.json:
{
"name": "workspace-a",
"version": "1.0.0",
"scripts": {
"test": "echo \"Run test in package-b\" && exit 0"
},
"dependencies": {
"@npmcli/agent": "^2.2.0"
}
}There are a number of agents in @npmcli/agent:
module.exports = {
getAgent,
Agent,
// these are exported for backwards compatability
HttpAgent: Agent,
HttpsAgent: Agent,
cache: {
proxy: proxyCache,
agent: agentCache,
dns: dns.cache,
clear: () => {
proxyCache.clear()
agentCache.clear()
dns.cache.clear()
},
},
}We pick up HttpAgent, where agentOptions can be defined with options of allowHalfOpen, highWaterMark, pauseOnConnect, noDelay, keepAlive, and keepAliveInitialDelay.
Create agent.js under npm-workspace/packages/package-a/:
// agent.js
import { HttpAgent } from '@npmcli/agent';
import fetch from 'minipass-fetch';
const getNpmVersion = async () => {
// define agent options as needed
const agentOptions = {};
const agent = new HttpAgent(agentOptions);
const data = await fetch('https://registry.npmjs.org/npm', { agent })
.then((response) => response.json())
.then((data) => {
return data;
})
.catch((error) => {
console.error(error);
});
console.log(data.versions['10.2.1'].version); // data is too large to be printed
};
getNpmVersion();Execute the code, and it fetches npm information from https://registry.npmjs.org/npm.
% node --experimental-default-type=module agent.js
10.2.1
Exit code: 0 Starting from npm 10, @npmcli/agent is used as the agent for network requests.
DEPENDENCIES.md includes the following addtion:
npmcli-agent-->http-proxy-agent; npmcli-agent-->https-proxy-agent; npmcli-agent-->lru-cache; npmcli-agent-->socks-proxy-agent;
Removed strict mode from npm-package-arg
npm-package-arg is a package to parse a value that can be an argument to npm install.
Go to the repository npm-workspack, and cd packages/package-a.
Install the package @npmcli/agent:
% yarn add npm-package-argAfter the installation, npm-package-arg becomes a member of dependencies in npm-workspace/packages/package-a/package.json:
{
"name": "workspace-a",
"version": "1.0.0",
"scripts": {
"test": "echo \"Run test in package-b\" && exit 0"
},
"dependencies": {
"@npmcli/agent": "^2.2.0",
"npm-package-arg": "^11.0.1"
}
}Create packageArg.js under npm-workspace/packages/package-a/:
// packageArg.js
import packageArg from 'npm-package-arg';
try {
console.log(packageArg('[email protected]'));
console.log(packageArg('git+https://github.com/user/john'));
} catch (error) {
console.error(error);
}Execute the code, and it parses the package names of [email protected] and git+https://github.com/user/john:
% node --experimental-default-type=module packageArg.js
Result {
type: 'version',
registry: true,
where: undefined,
raw: '[email protected]',
name: 'npm',
escapedName: 'npm',
scope: undefined,
rawSpec: '10.2.0',
saveSpec: null,
fetchSpec: '10.2.0',
gitRange: undefined,
gitCommittish: undefined,
gitSubdir: undefined,
hosted: undefined
}
Result {
type: 'git',
registry: undefined,
where: undefined,
raw: 'git+https://github.com/user/john',
name: undefined,
escapedName: undefined,
scope: undefined,
rawSpec: 'git+https://github.com/user/john',
saveSpec: 'git+https://github.com/user/john.git',
fetchSpec: 'https://github.com/user/john.git',
gitRange: undefined,
gitCommittish: null,
gitSubdir: undefined,
hosted: GitHost {
sshtemplate: [Function: sshtemplate],
sshurltemplate: [Function: sshurltemplate],
edittemplate: [Function: edittemplate],
browsetemplate: [Function: browsetemplate],
browsetreetemplate: [Function: browsetreetemplate],
browseblobtemplate: [Function: browseblobtemplate],
docstemplate: [Function: docstemplate],
httpstemplate: [Function: httpstemplate],
filetemplate: [Function: filetemplate],
shortcuttemplate: [Function: shortcuttemplate],
pathtemplate: [Function: pathtemplate],
bugstemplate: [Function: bugstemplate],
hashformat: [Function: formatHashFragment],
protocols: [ 'git:', 'http:', 'git+ssh:', 'git+https:', 'ssh:', 'https:' ],
domain: 'github.com',
treepath: 'tree',
blobpath: 'blob',
editpath: 'edit',
gittemplate: [Function: gittemplate],
tarballtemplate: [Function: tarballtemplate],
extract: [Function: extract],
type: 'github',
user: 'user',
auth: null,
project: 'john',
committish: '',
default: 'https',
opts: { noGitPlus: true, noCommittish: true }
}
}
Exit code: 0Before npm 10, npm-package-arg can be set to run the strict RFC 8909 mode, when the environ NPM_PACKAGE_ARG_8909_STRICT=1 was set. The strict RFC 8909 mode has been removed.
The if condition in lib/npa.js has been removed.
if (process.env.NPM_PACKAGE_ARG_8909_STRICT !== '1') {
// XXX backwards compatibility lack of compliance with 8909
// Remove when we want a breaking change to come into RFC compliance.
if (resolvedUrl.host && resolvedUrl.host !== 'localhost') {
const rawSpec = res.rawSpec.replace(/^file:\/\//, 'file:///')
resolvedUrl = new url.URL(rawSpec, `file://${path.resolve(where)}/`)
specUrl = new url.URL(rawSpec)
rawNoPrefix = rawSpec.replace(/^file:/, '')
}
// turn file:/../foo into file:../foo
// for 1, 2 or 3 leading slashes since we attempted
// in the previous step to make it a file protocol url with a leading slash
if (/^\/{1,3}\.\.?(\/|$)/.test(rawNoPrefix)) {
const rawSpec = res.rawSpec.replace(/^file:\/{1,3}/, 'file:')
resolvedUrl = new url.URL(rawSpec, `file://${path.resolve(where)}/`)
specUrl = new url.URL(rawSpec)
rawNoPrefix = rawSpec.replace(/^file:/, '')
}
// XXX end 8909 violation backwards compatibility section
}Removed the deprecated or unused configs
npm 10 removed the deprecated or unused configs: tmp, ci-name, hashAlgorithm, and metrics-registry.
tmp: Thetmplocation stored temporary files. This setting is no longer used, as npm stores temporary files in a special location in the cache to be managed bycacache.
The following code is removed in workspaces/config/lib/definitions/definitions.js.
define('tmp', {
default: tmpdir(),
defaultDescription: `
The value returned by the Node.js \`os.tmpdir()\` method
<https://nodejs.org/api/os.html#os_os_tmpdir>
`,
type: path,
deprecated: `
This setting is no longer used. npm stores temporary files in a special
location in the cache, and they are managed by
[\`cacache\`](http://npm.im/cacache).
`,
description: `
Historically, the location where temporary files were stored. No longer
relevant.
`,
})ci-name: It was the name of a continuous integration (CI) system. The CI portion of the defaultuser-agentwill now only be derived from the environment and cannot be manually overridden.
The following code is removed in workspaces/config/lib/definitions/definitions.js.
define('ci-name', {
default: ciInfo.name ? ciInfo.name.toLowerCase().split(' ').join('-') : null,
defaultDescription: `
The name of the current CI system, or \`null\` when not on a known CI
platform.
`,
type: [null, String],
deprecated: `
This config is deprecated and will not be changeable in future version of npm.
`,
description: `
The name of a continuous integration system. If not set explicitly, npm
will detect the current CI environment using the
[\`ci-info\`](http://npm.im/ci-info) module.
`,
flatten,
})hashAlgorithm: The hard-codedhashAlgorithmvalue is no longer being passed throughflatOptions.
The following code is removed in workspaces/config/lib/definitions/index.js:
flat.hashAlgorithm = 'sha1'metrics-registry: The hard-codedmetrics-registryconfig has not be used by any of the npm modules that consume config. Also, npm does not send data to any metrics server. Hence, it is removed.
The following code is removed in workspaces/config/lib/index.js.
// the metrics-registry defaults to the current resolved value of
// the registry, unless overridden somewhere else.
settableGetter(data, 'metrics-registry', () => this.#get('registry'))Don’t retry 409 errors during npm publish
Before npm 10, if the npm CLI gets a 409 from the registry during publish, it attempts a single retry by re-fetching the manifest and attempting to apply a new patch on what is being published. This is not the safest approach, especially as the code used to patch is not the same code that is used to build the original manifest. It is much safer to simply have the user re-run the publish command.
The following code shows the removed try-catch block (in bold) in workspaces/libnpmpublish/lib/publish.js:
try {
const res = await npmFetch(spec.escapedName, {
...opts,
method: 'PUT',
body: metadata,
ignoreBody: true,
})
if (transparencyLogUrl) {
res.transparencyLogUrl = transparencyLogUrl
}
return res
} catch (err) {
if (err.code !== 'E409') {
throw err
}
// if E409, we attempt exactly ONE retry, to protect us
// against malicious activity like trying to publish
// a bunch of new versions of a package at the same time
// and/or spamming the registry
const current = await npmFetch.json(spec.escapedName, {
...opts,
query: { write: true },
})
const newMetadata = patchMetadata(current, metadata)
const res = await npmFetch(spec.escapedName, {
...opts,
method: 'PUT',
body: newMetadata,
ignoreBody: true,
})
/* istanbul ignore next */
if (transparencyLogUrl) {
res.transparencyLogUrl = transparencyLogUrl
}
return res
}Conclusion
npm 10 has been released. It supports node versions ^18.17.0 || >=20.5.0.
npm 9 made changes to handle missing scripts in workspace mode, how agent is used for network requests, and whether to retry 409 errors during npm publish. It also removed strict mode from npm-package-arg, as well as the deprecated or unused configs.
If you want to check out features of previous releases, take a look at the following articles:
- Exploring New Features in npm 9
- A Quick Glance at npm 8 Features and Predictions for npm 9 — A Close Look at ES Modules (ESM)
- The Step-by-Step Guide to Understanding and Adopting npm 7
Here is a list of Node.js:
- 7 Major Features of Node.js 21
- 6 Major Features of Node.js 20
- 6 Major Features of Node.js 19
- 5 Major Features of Node.js 18
- 3 Major Features of Node.js 17
- A Quick Look at the Node.js 16 Features
- What’s New in Node.js 15
Thanks for reading.
Want to Connect?
If you are interested, check out my directory of web development articles.In Plain English
Thank you for being a part of our community! Before you go:
- Be sure to clap and follow the writer! 👏
- You can find even more content at PlainEnglish.io 🚀
- Sign up for our free weekly newsletter. 🗞️
- Follow us: Twitter(X), LinkedIn, YouTube, Discord.
- Check out our other platforms: Stackademic, CoFeed, Venture.






