In 2018, are you still just NPM Install?

Nodejs community and even Web front-end engineering field develop to today, as the package management tool of Node, NPM has become a necessary tool for every front-end developer. But the reality is that many of us are still using and understanding the nodeJS infrastructure: we use NPM install here (haven’t you ever removed the entire node_modules directory and re-installed it?).

Of course, NPM is the largest package management system in the world today, in large part because it is user-friendly enough that I don’t have to worry too much about things going wrong even if I only do install. But there’s more to NPM than just an install. This article will help you unpack some NPM principles, features, tips, and (in my opinion) best practices that you may not know.

You are too lazy to read the NPM document, I helped you to translate and then try to organize over 🐢🐢🐢

1. npm init

Json file is used to define a package, and NPM init is used to initialize a simple package.json file. After this command is executed, the terminal queries the name, version, and description fields in sequence.

1.1 NPM init Performs the default behavior

If you want to skip pressing Enter all the time, just append –yes to the command, which has the same effect as the next step.

npm init --yes

1.2 Customizing NPM Init behavior

The principle of the NPM init command is not complicated. Just call the script and output an initialized package.json file. In the Home directory, create a.npm-init.js file with module.exports as package.json configuration content. Use the prompt() method.

For example, write ~/.npm-init.js like this

const desc = prompt('description? '.'A new package... ')
const bar = prompt('bar? '.' ')
const count = prompt('count? '.The '42')

module.exports = {
  key: 'value'.foo: {
    bar: bar,
    count: count
  },
  name: prompt('name? ', process.cwd().split('/').pop()),
  version: prompt('version? '.'0.1.0 from'),
  description: desc,
  main: 'index.js',}Copy the code

Executing NPM init in the ~/hello directory will give you package.json like this:

{
  "key": "value"."foo": {
    "bar": ""."count": "42"
  },
  "name": "hello"."version": "0.1.0 from"."description": "A new package..."."main": "index.js"
}
Copy the code

In addition to generating package.json, because.npm-init.js is a regular module, it means we can perform any task that any node script can do. For example, fs to create README,.eslintrc and other project required files, to achieve the role of project scaffolding.

2. Install dependency packages

Dependencies management is the core function of NPM, the principle is to execute NPM install from package.json in dependencies, devDependencies into the current directory./node_modules folder.

2.1 package definition

We all know that to install a package manually, run the NPM install command. The third parameter, package, is usually the name of the package we want to install. By default, NPM will look up the package address corresponding to the package name from the default source (Registry) and download and install it. But in the world of NPM, instead of simply specifying a package name, a package can also be an HTTP URL /git URL/folder path to a valid package name.

Reading the NPM documentation, we will find that a package is defined as a package as long as one of the following conditions (a) to g) is met:

# instructions example
a) A package. Json file that contains the program and a package describing the programfolder ./local-module/
b) One that contains (a)Gzip compressed file ./module.tar.gz
c) One that can be downloaded to (b) resourcesurl(Usually an HTTP (s) URL) https://registry.npmjs.org/webpack/-/webpack-4.1.0.tgz
d) One format is<name>@<version>A string that points to a published and accessible URL on an NPM source (usually the official source npmjs.org) that satisfies the criteria (c) [email protected]
e) One format is<name>@<tag>On the NPM source<tag>Pointing to a<version>get<name>@<version>The latter satisfies the condition (D). webpack@latest
f) One format is<name>Is a string added by defaultlatestTag<name>@latestSatisfies the condition (e) webpack
g) agit url, the url points to a code base that satisfies the condition (a) [email protected]:webpack/webpack.git

2.2 Installing the local/remote Git Repository package

The definitions in the table above mean that when we share dependencies, we do not have to publish them to an NPM source to make them available to consumers for installation. This is useful for scenarios where it is not easy to publish to a remote source (even if it is private), or where you need to make changes to an official source but still need to share the package.

Scenario 1: Local module reference

Inter-module invocation is inevitable in nodeJS application development. For example, in practice, the configuration modules that need to be frequently referenced are often placed in the root directory of the application. After creating multiple levels of directories and files, you’re likely to encounter code like this:

const config = require('.. /.. /.. /.. /config.js');
Copy the code

In addition to looking ugly, such path references are bad for code refactoring. And as programmer self-cultivation tells us, the amount of repetitive code means it’s time to separate the module and share it with other modules within the app. For example, config.js in this example is perfectly suited to be packaged in a node_modules directory and shared with other modules in the same application.

Instead of copying files manually or creating soft links to node_modules, NPM has a more elegant solution.

Solution:

  1. Create config package: add config folder; Rename config.js to the config/index.js file; Create package.json to define the config package

    {
        "name": "config"."main": "index.js"."version": "0.1.0 from"
    }
    Copy the code
  2. Add dependencies to the application layer package.json file and run NPM install. Or go straight to Step 3

    {
        "dependencies": {
            "config": "file:./config"}}Copy the code
  3. (Equivalent to Step 2) Run NPM install file:./config directly in the application directory

    At this point, if we look at the node_modules directory, we will see an extra soft link named config to the upper config/ folder. This is because NPM recognizes the URL of the file: protocol and, knowing that the package needs to be retrieved directly from the file system, automatically creates a soft link to node_modules to complete the “install” process.

    In contrast to manual soft linking, you don’t need to worry about the Differences between Windows and Linux commands, and you can explicitly fix dependencies into the Dependencies field, which the rest of the development team can use directly after executing NPM install.

Scenario 2: Private Git shared Package

Sometimes we have code/common libraries that need to be shared between different projects within the team, but may contain sensitive content, or the code is too bad to release to the source, etc.

In this case, you can simply host the package in a private Git repository and save the Git URL to dependencies, and NPM will call the system git command to pull the package from the repository to node_modules.

Git URL format supported by NPM:

<protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>[#<commit-ish> | #semver:<semver>]
Copy the code

You can use # to specify a specific Git branch/commit/tag, or #semver: to specify a specific Semver range.

Such as:

git+ssh://[email protected]:npm/npm.git# v1.0.27
git+ssh://[email protected]:npm/npm# semver: ^ 5.0
git+https://[email protected]/npm/npm.git
git://github.com/npm/npm.git# v1.0.27
Copy the code

Scenario 3: Open source Package problem fix

We used an NPM package and discovered that it had a serious bug, but maybe the original author no longer maintained the code, or maybe we were working in a hurry and didn’t have enough time to bring the issue to the author and wait for the author to release a new fix to the NPM source.

At this point we can manually go to node_modules and change the contents of the corresponding package, perhaps changing a line of code to fix the problem. But this is very unwise!

First, node_modules itself should not be included in version control, and changes to the contents of node_modules will not be recorded in the Git commit record. Second, even if we want to anti-pattern and put node_modules into version control, your changes can easily be overwritten the next time a team member performs an NPM install or NPM update, and such a commit may include dozens or hundreds of package updates. It’s easy to get lost in the vast list of diff files that you’ve made yourself.

Solution:

Your best bet would be to fork the original git library, fix the problem in your own Repo, and change the corresponding dependency in Dependencies to the Git URL of your version of the fix. After forking the code base, it is also easy to submit PR fixes to the original author. After the upstream codebase fixes the problem, it’s not too late to update our dependency configuration again.

3. How does NPM install work — node_modules directory structure

After NPM install completes, we can see all dependent packages in node_modules. While the user doesn’t need to pay attention to the details of the folder structure in this directory and can refer to the dependency packages in the business code, understanding the contents of node_modules will help us understand how NPM works and what changes and improvements have been made from NPM 2 to NPM 5.

For simplicity, let’s assume the application directory is app and use two popular packages, Webpack and nconf, as dependency packages. In order to install it properly, the “old” NPM 2 version [email protected] and [email protected] were used.

3.1 npm 2

NPM 2 uses a simple recursive installation method when installing dependency packages. After NPM install, NPM 2 recursively installs webpack and nconf into node_modules. When we’re done, we’ll see that the./node_modules layer contains only these two subdirectories.

Node_modules / β”œβ”€β”€ nconf/ β”œβ”€ webpack/Copy the code

Go deeper into the nconf or Webpack directories and you’ll see that NPM has recursively installed its own dependencies in their respective node_modules. Include./node_modules/webpack/node_modules/webpack-core,./node_modules/conf/node_modules/async and so on. Each package has its own dependencies, and each package has its own dependencies installed in its own node_modules. The hierarchy of dependencies forms a dependency tree, which corresponds to the hierarchy of the file structure tree in the file system.

The most convenient way to view the dependency tree is to directly run the NPM ls command in the app directory.

[email protected] β”œ ─ ┬ [email protected] β”‚ β”œ ─ ─ [email protected] β”‚ β”œ ─ ─ [email protected] β”‚ β”œ ─ ─ [email protected] β”‚ β”” ─ ─ [email protected] β”” ─ ┬ [email protected] β”œ ─ ─ [email protected] β”œ ─ ─ [email protected] β”œ ─ ─clone@ 1.0.3 β”œ ─ ─... β”œ ─ ─ [email protected] β”œ ─ ─ [email protected] β”œ ─ ─ [email protected] β”œ ─ ─ [email protected] β”œ ─ ─ [email protected] β”” ─ ┬ [email protected] β”œ ─ ─source- [email protected] β”” ─ ─source- [email protected]Copy the code

The advantage of such a directory structure is that the hierarchy is obvious and easy to fool management:

  1. For example, when you install a dependency package, you immediately see the subdirectories in the first layer node_modules
  2. If you know the required package name and version number, you can even manually copy the required package from another folder into the node_modules folder, and then manually modify the dependency configuration in package.json
  3. To remove the package, you can simply manually delete the subdirectory of the package and remove the corresponding line from the package.json file

In fact, many people did this in the NPM 2 era, and indeed it can be installed and removed successfully without any errors.

But there are obvious problems with this file structure:

  1. For complex projects, the directory structure in node_modules may be too deep, causing deep file paths to be too long and triggering a Windows file system error where file paths cannot be longer than 260 characters
  2. Some packages that are dependent on more than one package are likely to be installed repeatedly in many places in the application node_modules directory. As the project gets larger and the dependency tree becomes more complex, there will be more of these packages, resulting in a lot of redundancy.

In our example, webpack and nconf both rely on the async package, so in the file system, both webpack and nconf have the same async package installed in the node_modules subdirectory, and the same version.

+-------------------------------------------+ | app/ | +----------+------------------------+-------+ | | | | + -- -- -- -- -- -- -- -- -- -- + + v -- -- -- -- -- - -- -- -- -- -- -- -- -- -- v -- -- -- -- -- -- -- + | | | | | [email protected] | | [email protected] | | | | | + -- -- -- -- -- -- -- - + -- -- -- -- -- -- -- -- + + + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + | | + -- -- -- -- -- v + + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- v + | [email protected] | | [email protected] | + -- -- -- -- -- -- -- -- -- -- - + + -- -- -- -- -- -- -- -- -- -- - +Copy the code

3.2 NPM 3 – Flat structure

To solve this problem, the node_modules directory in NPM 3 has been changed to a more flat hierarchy. The webpack, nconf, and async hierarchies in the file system are now flat, in the same directory.

+-------------------------------------------+ | app/ | +-+---------------------------------------+-+ | | | | + -- -- -- -- -- -- -- -- -- -- + + v -- -- -- -- -- - -- -- -- -- -- -- -- -- -- -- -- -- -- + + -- -- -- -- -- -- -- -- -- v -- -- -- -- -- -- -- + | | | | | | | [email protected] | | [email protected] | | [email protected] | |  | | | | | +-----------------+ +-------------+ +-----------------+Copy the code

Webpack /node_modules and nconf/node_modules no longer have an async folder, but thanks to Node’s module loading mechanism, they can both find the async library in the node_modules directory above. So the require(‘async’) statement in the webpack and nconf library code will execute without any problems.

This is just the most simple example, the actual engineering projects, the dependence tree inevitably there will be many layers, many depend on the package, there are a lot of the same name but different versions of packages exist in different level of dependence, in these complex case, the NPM 3 will traverse the entire dependence tree when installation, calculate the most reasonable installation folder, Enable all packages that are repeatedly dependent to be re-installed.

The NPM documentation provides a more intuitive example to explain this situation:

A{B,C}, B{C}, C{D},C {D}, A{B,C}, C{D}

A
+-- B
+-- C
+-- D
Copy the code

The reason D is installed at the same level as B and C is that NPM will install packages as high as possible by default without conflict.

A{B,C}, B{C,D@1}, C{D@2}

A
+-- B
+-- C
   `-- D@2
+-- D@1
Copy the code

This is because, for NPM, packages with the same name but different versions are two separate packages, and the same layer cannot have two subdirectories of the same name, so the D@2 is placed in the C subdirectory and the D@1 is placed in the next layer above.

It is clear that after NPM 3 the dependency tree structure of NPM no longer corresponds to the folder hierarchy. To view the app’s immediate dependencies, specify –depth using the NPM ls command:

npm ls --depth 1
Copy the code

PS: Unlike local dependency packages, if we use NPM install –global to install the global directory, we still get the “traditional” directory structure. If you want to get the “traditional” form of the local node_modules directory using NPM 3, use the NPM install –global-style command.

3.3 NPM 5-package-lock files

NPM 5 was released in 2017 and is the latest version of NPM. This version still adopts the flat dependency installation method of NPM 3, and the biggest change is the addition of package-lock-. json file.

Package-lock-json locks the dependency installation structure. If you look at the structure of the JSON, you’ll see that it’s one to one with the file hierarchy in the node_modules directory.

For example, the package-lock file of an ‘app’ project whose dependencies are: app{webpack} contains such fragments.

{" name ":" app ", "version" : "0.1.0 from", "lockfileVersion" : 1, "requires" : true, "dependencies" : {/ /... Other dependencies "webPack ": {"version": "1.8.11"," Resolved ": "Https://registry.npmjs.org/webpack/-/webpack-1.8.11.tgz", "integrity" : "Sha1 - Yu0hnstBy/qcKuanu6laSYtgkcI =", "the requires" : {" async ":" 0.9.2 ", "clone" : "0.1.19", "enhanced - resolve" : "" esprima 0.8.6", ":" 1.2.5 "and" interpret ":" 0.5.2 ", "the memory - fs" : "0.2.0," "mkdirp" : "0.5.1", "node - libs - browser" : "0.4.3", "optimist" : "0.6.1", "supports - color" : "1.3.1", "tapable" : "0.1.10", "uglify - js" : "2.4.24", "watchpack" : "0.2.9 webpack -", "core" : "0.6.9}}", "webpack - core" : {" version ":" 0.6.9 ", "resolved" : "Https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", "integrity" : "/FcViMhVjad76e+23r3Fo7FyvcI=", "requires": {"source-list ": "0.1.8", "source-map": "0.4.4"}, "requires": {" source - map ": {" version" : "0.4.4," "resolved" : "Https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "integrity" : "Sha1-66 t12pwnyzneaamti092fzzsa2s =", "the requires" : {" amdefine ":" 1.0.1 "}}}}, / /... Other dependencies}}Copy the code

Package-lock files are easy to read. They are structured as a nest of fields of the same type: Version, Resolved, integrity, requires, and dependencies.

  • version.resolved.integrityThe exact “identity” information used to record the exact version number, content hash, and installation source of the package determines the exact “identity” information of the package to be installed
  • Suppose you cover the other fields and only focus on the ones in the filedependencies: {}You’ll notice that the dependencies hierarchy in the JSON configuration for the entire file is exactly the same as the folder hierarchy in the node_modules file system
  • Focus on therequires: {}Fields will be found again, except for the outermostrequiresWith the exception of the “true” attribute, the “requires” attribute in the other layers corresponds to the dependencies recorded in the package’s package.json

Because this file records the structure, hierarchy, version number and even installation source of all packages in node_modules, it actually provides the ability to “save” the state of node_modules. As long as there is such a lock file, NPM install will get exactly the same node_modules result regardless of which machine you run it on.

This is the scenario in which package-lock files aim to optimize: previously only using package.json to record dependencies, due to the semver Range mechanism; A package.json file generated by A A month ago, and B A month later according to the node_modules result of executing NPM install on it, it is likely that many packages have different differences. Although the semver mechanism prevents the same package.json from having different dependencies in large versions, installing different dependencies from the same code in different environments is still a potential cause of surprise.

NPM 5 generates package-lock files after NPM install. NPM shrinkwrap generates package-lock files after NPM install. It is also recommended that you commit to the Git/SVN code base.

The package-lock-json file also caused considerable controversy when it was originally introduced by default in NPM 5.0. In NPM 5.0, if a package.json file already exists and you manually add a dependency to the package.json file, After NPM install, the new dependencies are not installed into node_modules, and package-lock.json is not updated accordingly. Such performance is not consistent with the user’s natural expectation of performance. This was fixed in the first Release of NPM 5.1. This tells us to upgrade, not use 5.0.

However, there are still objections that package-lock is too complex, for which NPM also provides a disabled configuration:

npm config set package-lock false
Copy the code

4. Dependency package version management

Just because dependency packages are installed is not the end of the story. Version maintenance and updates are also important. This chapter covers dependency package upgrade management. If you are not looking at the version, skip to 4.3 Best Practices.

4.1 semver

An important feature of NPM dependency management is the adoption of the Semver specification as a dependency versioning solution.

Semver convention A package version number must contain three digits in the format of major.minor. PATCH, meaning the MAJOR version number. Minor version number. Revision version number.

  • MAJOR corresponds to a large version iteration. Update the MAJOR version when a change is made that is not compatible with the previous version
  • MINOR indicates MINOR version iteration. The MINOR version is updated when an API change or function update is made that is compatible with the previous version
  • PATCH Indicates the version number of the BUG

For package authors (publishers), NPM requires that version numbers be updated before publish. NPM provides NPM version tools, the implementation of NPM version major | minor | patch can easily be set to the version number of the corresponding number + 1.

If the package is a Git repository, NPM Version also automatically creates a Git Commit with the updated version number and a tag named that version number

For package referencers, we need to specify the version number or version range of the required dependency package in Dependencies using semver Range of the semver convention. NPM provides the website https://semver.npmjs.com to easily calculate the matching range of the entered expression. The following table provides an example of common rules:

range meaning case
^ 2.2.1 All updated versions under the specified MAJOR version number matching2.2.3.2.3.0; Don’t match1.0.3.3.0.1
~ 2.2.1 MINOR specifies all updated versions under the major. MINOR version number matching2.2.3.2.2.9; Don’t match2.3.0.2.4.5
> = 2.1 The version number is greater than or equal to2.1.0 matching2.1.2.3.1
< = 2.2 The version number must be less than or equal to2.2 matching1.0.0.2.2.1.2.2.11
1.0.0-2.0.0 Version numbers from 1.0.0 to 2.0.0 matching1.0.0.1.3.4.2.0.0

Any two rules, joined by Spaces, represent the “and” logic, i.e. the intersection of the two rules:

For example, >=2.3.1 <=2.8.0 can be interpreted as: >=2.3.1 and <=2.8.0

  • Can match2.3.1.2.4.5.2.8.0
  • But don’t match1.0.0.2.3.0.2.8.1.3.0.0

Any two rules, through the | | connected, said “or” logic, namely two rules and set:

As > ^ 2 = 2.3.1 | | ^ 3 > 3.2

  • Can match2.3.1.2,8.1.3.3.1
  • But don’t match1.0.0.2.2.0.3.1.0.4.0.0

PS: In addition to these, there are more intuitive ways to express the range of version numbers:

  • * ζˆ– xMatches all major versions
  • 1 ζˆ– 1.xMatches all versions whose major version number is 1
  • 1.2 ζˆ– X 1.2.Matches all versions whose version numbers start with 1.2

PPS: In addition to the usual digital-only version numbers, Semver allows you to append – followed by a dough-delimited label to major.minor. PATCH as a Prerelese tag – a version that is generally considered unstable and not recommended for production use. Such as:

  • 1.0.0 - alpha
  • 1.0.0 - beta. 1
  • 1.0.0 - rc. 3

The most common format we use in the table above is ^1.8.11, because when we use NPM install to install packages, NPM installs the latest version by default. For example, 1.8.11, then add a ^ before the installed version number and write ^1.8.11 to the package.json dependency configuration, which means you can match all versions from 1.8.11 up to 2.0.0.

4.2 Dependent Version Upgrade

The problem is, after installing a dependency package and a new version is released, how to use NPM to upgrade the version? — The answer is simple NPM install or NPM Update, but the performance of the installation/upgrade varies between versions of NPM and different package.json, package-lock-. json files.

Let’s also use webPack as an example and make the following assumptions:

  • Our engineering projectsappRely on webpack
  • When the project was initially initialized, the current package [email protected] was installed and the dependencies in package.json were configured as follows:"Webpack" : "^ 1.8.0 comes with"
  • The current (March 2018) version of WebPack is4.2.0The latest subversion of Webpack 1.x is1.15.0

If we are using NPM 3 and the project does not contain package-lock-json, then depending on whether node_modules is empty, The result of the install/update command is as follows (node 6.13.1, NPM 3.10.10):

# package.json (BEFORE) node_modules (BEFORE) command (npm 3) package.json (AFTER) node_modules (AFTER)
a) Webpack: ^ 1.8.0 comes with [email protected] install Webpack: ^ 1.8.0 comes with [email protected]
b) Webpack: ^ 1.8.0 comes with empty install Webpack: ^ 1.8.0 comes with [email protected]
c) Webpack: ^ 1.8.0 comes with [email protected] update Webpack: ^ 1.8.0 comes with [email protected]
d) Webpack: ^ 1.8.0 comes with empty update Webpack: ^ 1.8.0 comes with [email protected]

According to this table, we can draw the following conclusions about NPM 3:

  • If node_modules is installed locally, install again will not update the package version, update will update; If node_modules is null, install/update will install the update package directly.
  • NPM Update will always update the package to the semver specified in package.jsonThe latestVersion number — Yes in this case^ 1.8.0 comes withThe latest version of1.15.0
  • Once given package.json, the Webpack version in package.json stubbornly remains the one it started with, regardless of whether NPM install or update is later executed^ 1.8.0 comes withkui

What makes sense here is that if the first person on the team originally installed [email protected], a new member of the project who checked out the project code and executed NPM install would get a different version of 1.15.0. Although semver has a convention that small versions should be backward compatible (smaller versions under the same larger version number), if a package publisher who is unfamiliar with this convention releases incompatible packages, there may be bugs due to different dependency environments.

This is what NPM 5 looks like when you execute install/update on node 9.8.0 and NPM 5.7.1.

The following table is simple, omitting the package name webpack, abbreviated install I, and abbreviated update UP

# package.json (BEFORE) node_modules (BEFORE) package-lock (BEFORE) command package.json (AFTER) node_modules (AFTER)
a) ^ 1.8.0 comes with @ 1.8.0 comes with @ 1.8.0 comes with i ^ 1.8.0 comes with @ 1.8.0 comes with
b) ^ 1.8.0 comes with empty @ 1.8.0 comes with i ^ 1.8.0 comes with @ 1.8.0 comes with
c) ^ 1.8.0 comes with @ 1.8.0 comes with @ 1.8.0 comes with up ^ 1.15.0 @ 1.15.0
d) ^ 1.8.0 comes with empty @ 1.8.0 comes with up ^ 1.8.0 comes with @ 1.15.0
e) ^ 1.15.0 @ 1.8.0 comes with (old) @ 1.15.0 i ^ 1.15.0 @ 1.15.0
f) ^ 1.15.0 @ 1.8.0 comes with (old) @ 1.15.0 up ^ 1.15.0 @ 1.15.0

Compared to NPM 3, the main differences in installing and updating dependent versions are:

  • Whenever install is executed, NPM preferentially installs WebPack with the version specified in package-lock; Avoid scenario b) in table NPM 3;
  • Whenever the installation/update is complete, the package-lock file is always updated with node_modules — (so think of the package-lock file as a JSON representation of node_modules)
  • If you execute NPM update after node_modules is installed, the version number in package.json will be changed to^ 1.15.0

Json and package-lock-. json are stored in a more uniform version, which solves many of the previous problems of NPM. As long as good practices are followed, team members can easily maintain an environment where both application code and node_modules dependencies are consistent.

All is well.

4.3 Best Practices

To sum up, in the 2018 (Node 9.8.0, NPM 5.7.1) era, I think dependency versioning should be:

  • Use NPM: >=5.1 and leave package-lock-. json files enabled by default

  • Initialization: when the first author initializes the project, use NPM install to install the dependency packages, and by default saves the ^X.Y.Z dependency range in package.json; Submit package.json, package-lock-json, do not submit node_modules

  • Initialization: After project members checkout/clone the project code for the first time, execute NPM install to install the dependency packages

  • Do not manually modify package-lock-json

  • Upgrade dependency packages:

    • Upgrade a minor version: Perform it locallynpm updateUpgrade to a new, smaller version
    • Upgrade a major version: Perform the upgrade locallynpm install <package-name>@<version>Upgrade to a new, larger version
    • You can also manually change the version number in package.json toupgradeVersion (greater than the existing version number) and specify the required semver, and then executenpm install
    • After verifying that the new version is correct,submitThe newpackage.json.package-lock.jsonfile
  • Demote dependency packages:

    • correct: npm install <package-name>@<old-version>After verifying that there is no problem,submitPackage. json and package-lock-. json files
    • error: Manual modificationpackage.jsonIs an earlier version of semver, and the change will not take effect because it will be executed againnpm installWill still be installedpackage-lock.jsonLock version in
  • To delete dependency packages:

    • Plan A: npm uninstall <package>And submitpackage.json ε’Œ package-lock.json
    • Plan B: Delete the package to be uninstalled from the Dependencies field in package.json, and then executenpm installAnd submitpackage.json ε’Œ package-lock.json
  • Any time someone commits a package.json, package-lock-json update, the rest of the team should execute the NPM install script after SVN update/ Git pull to install the updated dependency packages

Congratulations on finally getting to talk torm -rf node_modules && npm installThis wave says goodbye (it doesn’t)

5. npm scripts

5.1 Basic Usage

NPM Scripts is another important feature of NPM. Define a script by using the scripts field in package.json, for example:

{
    "scripts": {
        "echo": "echo HELLO WORLD"}}Copy the code

We can run the script by NPM run echo, just as if we run the command echo HELLO WORLD in the shell, and see the terminal output HELLO WORLD.

The basic use of NPM scripts is that simple. It provides a simple interface to invoke project-related scripts. For more detailed information, please refer to Teacher Ruan yifeng’s article NPM Script Guide (October 2016).

Briefly summarize the contents of Teacher Ruan’s article:

  1. npm runWhen the command is executed, the./node_modules/.bin/Directory added to the execution environmentPATHVariable, so if someOrdered my stuffNot installed globally, but installed only in node_modules of the current project, passednpm runThe command can also be invoked.
  2. When executing the NPM script, you need to pass in the parameter, which needs to be added after the command--Clearly, such asnpm run test -- --grep="pattern"Can be--grep="pattern"Parameters totestThe command
  3. NPM provides two hook mechanisms, pre and POST, to define the execution script before and after a script
  4. Runtime variable: innpm runIn the script execution environment, you can obtain a lot of runtime information by means of environment variablesprocess.envObject access to obtain:
    • npm_lifecycle_event– The name of the running script
    • npm_package_<key>– Obtain the configuration value of a field in the current package.json package: for examplenpm_package_nameTo get the package name
    • npm_package_<key>_<sub-key>– Nested field properties in package.json: for examplenpm_pacakge_dependencies_webpackYou can get it in package.jsondependencies.webpackField, which is the webPack version number

5.2 node_modules/bin directory

The node_modules/.bin directory mentioned above holds the command-line packages installed in the dependency directory that can be invoked.

What is a command line package? Webpack, for example, belongs to a command-line package. If we add the –global parameter when we install WebPack, we can invoke it by typing Webpack directly at the terminal. If we do not use the –global parameter, we will see a file named webpack in the node_modules/.bin directory. If we enter./node_modules/.bin/webpack on the terminal, we can execute it as well.

This is because Webpack defines the bin field in the package.json file as:

{
    "bin": {
        "webpack": "./bin/webpack.js"}}Copy the code

The bin field is configured in the following format: :

, that is, the command name: When the executable. NPM executes install, it analyzes the bin field in package.json of each dependency package and installs its contained entries into the./node_modules/.bin directory named
. For global installation, a soft link named
is created in the bin directory of the NPM global installation path. Thus, the./node_modules/.bin/webpack file, when invoked from the command line, is actually executing the node. /node_modules/.bin/webpack.js command.

As mentioned in the previous section, the NPM run command adds./node_modules/.bin to the PATH when executed, allowing us to directly call all dependencies that provide a command-line invocation interface. So here’s a best practice:

Install the project dependent command-line tool into the project dependency folder and invoke it via NPM scripts. Not a global installation

For example, WebPack is a standard build tool for front-end engineering. Although we are used to installing globally and calling directly from the command line, different projects may depend on different versions of WebPack. The corresponding webpack.config.js configuration file may also only be compatible with a specific version of WebPack. If we had only installed the latest Webpack 4.x globally and called it with the Webpack command, we would have failed to successfully execute the build in a project that relied on Webpack 3.x.

However, if such tools are always installed locally, we have to call a command and manually add the long prefix./node_modules/.bin, which would be too much trouble. We nodejs developers are lazy. NPM came with a new tool, NPX, starting in 5.2.

5.3 NPX

Using NPX is as simple as executing NPX , where defaults to the name of the executable script installed in./node_modules. For example, we can use NPX webpack to execute the webpack package installed locally.

In addition to this simplest scenario, NPM CLI team developer Kat Marchan introduces several other magical uses of NPX in this post: Introducing NPX: An NPM Package Runner, a domestic developer Robin. Law translated the original text into Chinese. What is NPX and why do you need NPX? .

Interested can poke the link to understand, lazy to click the link, see the summary:

Scenario a) One-click execution of binary packets from a remote NPM source

In addition to executing the installed commands in the package./node_modules/.bin, you can also directly specify the uninstalled binary package name to execute. For example, in a directory with no package.json and no node_modules, we execute:

npx cowsay hello
Copy the code

NPX will download cowsay from the NPM source (but not install it) and execute:

 _______ 
< hello >
 ------- 
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
Copy the code

This purpose is well suited to 1. Simply test or debug the functionality of these binaries locally on the NPM source; 2. Use scaffolding tools like create-React-app or Yeoman, which are often only used once per project

PS: There is an Easter egg here. Try this command:

npx workin-hard
Copy the code

Scenario B) One-click Execution of GitHub Gist

Remember from [2.1 Package definition], NPM install can be a Git URL that contains valid package.json.

As it happens, GitHub Gist is also a git repository, and the collection NPX makes it easy to share simple scripts with others. The person who owns the link can execute the script without having to install it in a local working directory. Json and the binary script you need to execute are uploaded to gist, and you can easily execute the commands defined by this GIST by running NPX

.

Kat Marchan, author of the original article, provides this example gist, implemented:

npx https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32
Copy the code

Get a Hello world greeting from GitHubGist.

Scenario c) Different versions of node are used to run commands

Will create the node package NPX and Aria Stewart (https://www.npmjs.com/package/node), can be achieved using the specified on the command line version of the node.

For example, sequential execution:

npx node@4 -e "console.log(process.version)"
npx node@6 -e "console.log(process.version)"
Copy the code

V4.8.7 and V6.13.0 will be output, respectively.

This is normally done by node versioning tools such as NVM, but NPX node@4 is much simpler and eliminates the need for NVM to manually switch configurations.

6. NPM configuration

6.1 npm config

The NPM CLI provides the NPM config command to configure NPM. You can run the NPM config ls -l command to view all the NPM configurations, including the default configurations. NPM documentation page for each configuration item provides detailed instructions at https://docs.npmjs.com/misc/config.

The command to modify the configuration is NPM config set

. We use the common important configuration:

  • proxy.https-proxy: Specifies the agent used by NPM
  • registrySpecifies the source from which NPM downloads the installation package. The default ishttps://registry.npmjs.org/Can be specified as a private Registry source
  • package-lockSpecifies whether package-lock files are generated by default. It is recommended to keep the default true
  • saveTrue /false Specifies whether to save the package as dependencies after NPM install. The default value is true since NPM 5

To delete a configuration item, run the NPM config delete

command.

6.2 NPMRC file

In addition to using the CLI NPM config command to display and change the NPM configuration, you can also modify the configuration directly through the NPMRC file.

Such NPMRC files, in descending order of priority, include:

  • Project configuration file:/path/to/my/project/.npmrc
  • User-level profiles:~/.npmrc
  • Global configuration file:$PREFIX/etc/npmrc(i.e.npm config get globalconfigOutput path)
  • NPM built-in configuration file:/path/to/npm/npmrc

With this mechanism, we can easily create a.npmrc file in the project and directory to share the configuration of the NPM run that needs to be shared across the team. For example, if we need to access the registry.npmjs.org source through an agent in an Intranet environment, or access the Intranet Registry, we can add a.npmrc file under the work item and submit the code base.

proxy = http://proxy.example.com/
https-proxy = http://proxy.example.com/
registry = http://registry.example.com/
Copy the code

Because the scope of the project-level.npmrc file is only under this project, these configurations do not take effect in directories other than this. For developers working on laptops, it is a good way to separate the two different environments: work projects at the company and study projects at home.

When combined with the ~/.npm-init.js configuration, you can add a configured.npmrc to the NPM init scaffold along with files such as.gitignore and README, further reducing manual configuration.

6.3 Node Version Constraints

Although teams on a project share the same code, each person’s development machine may have a different version of Node installed, and the server side may not be consistent with the local development machine.

This is another possible source of inconsistencies — but it’s not too hard to work around, declarative constraints + scripting constraints.

Declaration: Declare the version runtime requirements required for the application to run through the Engines property of package.json. For example, in our project, we use async, await features, and the compatibility table shows that the minimum supported version is 7.6.0, so we specify engines to be configured as:

{
    "engines": { "node": "> = 7.6.0"}}Copy the code

Strong constraints (optional) : In NPM, the above fields are only used as suggested fields. To add strong constraints to a private project, you need to write your own script hooks, read and parse the Semver range of the Engines field, and compare it with the runtime environment.

7. Summarize NPM best practices

  • Initialize the new project with npm-init
  • Unified project configuration: The NPM config configuration items that need to be shared by the team are solidified into the.npmrc file
  • Unified running environment, unified package.json, unified package-lock file
  • Use multiple sources to install dependencies:npm install <git url>|<local file>
  • Use NPM: >= version 5.2
  • Use NPM scripts and NPX (NPM: >=5.2) scripts to manage application-related scripts

8. More information

reference

  • NPM Team member Ashley Williams’ Talk on Node.js Live 2016:You Don’t Know npmThere was no NPM 5
    • YouTube video link: Node.js Live (Paris) – Ashley Williams, You Don’t Know NPM
    • Slides: the ag_deck
  • This 2015 article explains how to pack local modules into node_modules dependencies using Build Modular Application with NPM Local Modules
  • Everything you wanted to know about package-lock.json
  • Ruan Yifeng NPM Scripts use guide
  • Kat Marchan on NPX:
    • Introducing NPX: An NPM package Runner
    • What is NPX in Chinese and why do you need IT?

The document

  • NPM official document, no Chinese translation
    • Package. The json file
    • NPM config configuration
    • NPM Semver calculator
    • The node_modules directory is flattened
  • Yarn Chinese document, although NPM competitor, but compatible with package.json and node_modules directory, so these two parts are also available for reference:
    • Package. The json – Chinese
    • Dependencies and versions – Chinese

read

  • Sam Boyer, “So You Want to Write a Package Management System,” introduces all aspects of a package management system from a language-specific perspective: So You Want to Write a Package Manager