preface

Json and package-lock.json were the first things I came into contact with when I was developing the project. However, due to various reasons, I did not explore it further, and the holes left will always be buried. Therefore, I will finish up here.

package.json

Specifics of npm’s package.json handling

All npm packages contain a file, usually in the project root, called package.json – this file holds various metadata relevant to the project. This file is used to give information to npm that allows it to identify the project as well as handle the project’s dependencies. It can also contain other metadata such as a project description, the version of the project in a particular distribution, license information, even configuration data – all of which can be vital to both npm and to the end users of the package. The package.json file is normally located at the root directory of a Node.js project.

The package.json file is usually located in the root directory of the project and contains various data related to the project. This file is typically used by NPM to identify project information and to handle project dependencies. It also contains other data such as project descriptions, project-specific releases, license information, and even configuration data important to NPM packages or end users. This file is usually located at the root of a nodeJs project.

Initialize the

The Node environment needs to be installed. If it is not installed, install it yourself

](nodejs.cn/download/)

npm init
Copy the code

The directory structure

A VUe-based package.json file might look like this

Note: the following items are referred to as items or packages unless otherwise specified

{
  "name": "test-project".// The name, usually the github repository name
  "author": "xxx".// Information about the author
  "contributors": ["xxx"."xxxx"].// Array of contributor information
  "bugs": "https://github.com/nodejscn/node-api-cn/issues".// Bug information, usually github issue page
  "homepage": "http://nodejs.cn".// The project home page when the project is published
  "version": "1.0.0".// The current version follows semver's semantic versioning specification, which will be explained later
  "license": "MIT".// License information
  "keywords": ["xxx"."xxxx"].// Key word count group
  "description": "A Vue.js project".// Description
  "repository": "git://github.com/xxxx.git".// Warehouse address
  "main": "src/main.js".// When the package is referenced, the application searches for module exports at that location
  "private": true.// Prevent packages from being accidentally published to NPM. If true, NPM will refuse to publish
  "scripts": {
    "serve": "vue-cli-service serve"."build": "vue-cli-service build"."lint": "vue-cli-service lint"
  }, // A runnable Node script, usually NPM run serve
  "dependencies": {
    "core-js": "^ 3.6.5." "."vue": "^ 3.0.0-0"."vue-router": "^ 4.0.0-0"."vuex": "^ 4.0.0-0"
  }, // The installation package that the production environment depends on
  "devDependencies": {
    "@vue/cli-plugin-babel": "~ 4.5.0." "."@vue/cli-plugin-eslint": "~ 4.5.0." "."@vue/cli-plugin-router": "~ 4.5.0." "."@vue/cli-plugin-vuex": "~ 4.5.0." "."@vue/cli-service": "~ 4.5.0." "."@vue/compiler-sfc": "^ 3.0.0-0"."babel-eslint": "^ 10.1.0"."eslint": "^ 6.7.2." "."eslint-plugin-vue": "^ 7.0.0-0"."less": "^ 3.0.4"."less-loader": "^ 5.0.0"
  } // The installation package that the development environment depends on
  "engines": {
    "node": "> = 6.0.0"."npm": "> = 3.0.0"
  }, / / to run the Node. Js version or other commands, but it seems no eggs with, may refer to https://github.com/nodejs/node/issues/29249
  "browserslist": ["1%" >."last 2 versions"."not ie <= 8"]  // Supported browsers and their versions, which polyfill will use
}

Copy the code

Development environment vs. production environment

Production environment

  • npm install xxxIs installed in the production environment by default
  • npm install xxx --sornpm install xxx -S

DevDependencies:

  • npm install xxx --save-devornpm install xxx -D

Tips: In production environments, you need to make sure that the packaged code is as small as possible, so when installing packages, you need to distinguish which environment you are installing them in.

The version number

Due to the use of Semver (Semantic versioning), all versions have 3 numbers, the main version. This is the patch version with the following rules:

  • ~: If yes is written~ 0.13.0, only the patch version is updated0.13.1Ok, but0.14.0Can’t.
  • ^: If yes is written^ 0.13.0, the patch version and minor version must be updated0.13.1,0.14.0And so on.
  • *: If yes is written*, all updates are accepted, including major version upgrades.
  • >: Accepts any version higher than the specified version.
  • > =: Accepts any version equal to or higher than the specified version.
  • < =: Accepts any version equal to or lower than the specified version.
  • <: Accepts any version lower than the specified version.

There are other rules:

  • Unsigned: Only the specified version is accepted.
  • latest: Use the latest version available.

Can also be within the scope of combination more than most of the content, such as: 1.0.0 | | > = 1.1.0 < 1.2.0, namely using 1.0.0 or but below 1.2.0 starting 1.1.0 version.

Tips: It is recommended to use the specified version number NPM install [email protected] to avoid confusing problems caused by the version upgrade (fall into a pit over šŸ˜­).Copy the code

The problem

Now that we know what package.json is, the problem is that when we install a package such as Lodash, it has a version number of “Lodash “: “^ 4.17.20”, through the version number above we know that this means that as long as the big version remains the same, but there are updated, may be 4.18.20, then later version installed into the latest version. Under normal circumstances, we don’t allow people to collaborate with different package versions, so package-lock.json appears here.

package-lock.json

A manifestation of the manifest

package-lock.json is automatically generated for any operations where npm modifies either the node_modules tree, or package.json. It describes the exact tree that was generated, such that subsequent installs are able to generate identical trees, regardless of intermediate dependency updates.


This file is intended to be committed into source repositories, and serves various purposes:

  • Describe a single representation of a dependency tree such that teammates, deployments, and continuous integration are guaranteed to install exactly the same dependencies.
  • Provide a facility for users to ā€œtime-travelā€ to previous states of node_modules without having to commit the directory itself.
  • To facilitate greater visibility of tree changes through readable source control diffs.
  • And optimize the installation process by allowing npm to skip repeated metadata resolutions for previously-installed packages.

Package-lock. json is automatically generated when NPM makes any changes to node_modules tree or package.json. He describes the specific dependency tree to be generated, so that no matter how the intermediate dependency is updated, it ensures that subsequent installations will generate the same tree. This file is intended to be submitted to the source repository and has several uses:

  • Represents a dependency tree expressed separately, so make sure your teammates, deployments, and continuous integration can install exactly the same dependencies.
  • Provides a convenience for users to “time travel” to the state before node_modules without submitting their own directory.
  • Tree changes are better seen through readable source code control differences.
  • Optimize the installation process by allowing NPM to skip repeated data parsing of previously installed packages.

Take a quick look at what package-lock.json looks likeYou can obviously see that it includes the version number of all dependencies, the installation address, the sha-1 encrypted value, the environment in which the dependencies are installed, the dependencies that are needed internally… For other fields you can look at the official documentation,npm-package-lock.jsonI won’t repeat it here.

The problem

  • Question1: When we implementnpm installWhen, what happens?

Some friends may say, your question is too easy. If you have package-lock.json in your project, the dependencies to install will be resolved from there, not through package.json.

  • Question2: Do you know the directory structure for node_modules?

NPM will try its best to flatten dependencies when parsing node_modules, placing them under the top node_modules.

  • Question3: What about the dependencies required by the dependency itself when installing dependencies?

How does NPM handle dependencies

NPM init package.json in test and install [email protected]. Node_modules

The test node_modules | [email protected] | [email protected]Copy the code

Let’s install [email protected] again, and the dependency diagram is as follows:

The test node_modules | [email protected] | [email protected] node_modules | [email protected]Copy the code

We will find that

  1. Due to thenpmWill flatly install the dependency, so[email protected]Will install to the top levelnode_modulesIn the.
  2. Due to the topnode_modulesThere has been a[email protected], so react internally relies on[email protected]Will be installed inreactThe inside of thenode_modulesIn the.

If we install [email protected], it depends on [email protected] and [email protected], so node_modules looks like this:

The test node_modules | [email protected] | [email protected] node_modules | [email protected] | [email protected] node_modules | [email protected]Copy the code

Ok, we found

  1. NPM first installs [email protected] in top-level node_modules when installing dependencies
  2. Due to the[email protected]And topnode_modulesThe version of prop-types below is the same, so it is no longer installed separately
  3. The react version is not the same as the react version under the top-level node_modulesnode_modulesInternal separate installation[email protected].

If there are other installation packages, and so on…

* Tips: CNPM neither generates package-lock.json nor installs dependencies from package-lock.json *

Of course, we can also find the algorithm in npm-install:

Copy the code

load the existing node_modules tree from disk clone the tree fetch the package.json and assorted metadata and add it to the clone walk the clone and add any missing dependencies dependencies will be added as close to the top as is possible without breaking any other modules compare the original tree with the cloned tree and make a list of actions to take to convert one to the other execute all of the actions, deepest first kinds of actions are install, update, remove and move

Look a the diff source > - choose from https://github.com/npm/cli/blob/latest/lib/install/diff-trees.jsCopy the code

module.exports = function (oldTree, newTree, differences, log, next) { validate(‘OOAOF’, arguments) pushAll(differences, sortActions(diffTrees(oldTree, newTree))) log.finish() next() }

Without further ado, let's see what 'diffTrees' doesCopy the code

var diffTrees = module.exports._diffTrees = function (oldTree, newTree) { validate(‘OO’, arguments) var differences = [] var flatOldTree = flattenTree(oldTree) var flatNewTree = flattenTree(newTree) var toRemove = {} var toRemoveByName = {}

// Build our tentative remove list. We don’t add remove actions yet // because we might resuse them as part of a move. Object.keys(flatOldTree).forEach(function (flatname) { if (flatname === ‘/’) return if (flatNewTree[flatname]) return var pkg = flatOldTree[flatname] if (pkg.isInLink && /^[.][.][/\]/.test(path.relative(newTree.realpath, pkg.realpath))) return

toRemove[flatname] = pkg var name = moduleName(pkg) if (! toRemoveByName[name]) toRemoveByName[name] = [] toRemoveByName[name].push({flatname: flatname, pkg: pkg})Copy the code

})

// generate our add/update/move actions Object.keys(flatNewTree).forEach(function (flatname) { if (flatname === ‘/’) return var pkg = flatNewTree[flatname] var oldPkg = pkg.oldPkg = flatOldTree[flatname] if (oldPkg) { // if the versions Are equivalent then we don’t need to update… unless // the user explicitly asked us to. if (! pkg.userRequired && pkgAreEquiv(oldPkg, pkg)) return setAction(differences, ‘update’, pkg) } else { var name = moduleName(pkg) // find any packages we’re removing that share the same name and are equivalent var removing = (toRemoveByName[name] || []).filter((rm) => pkgAreEquiv(rm.pkg, pkg)) var bundlesOrFromBundle = pkg.fromBundle || pkg.package.bundleDependencies // if we have any removes that match AND we’re not working with a bundle then upgrade to a move if (removing.length && ! bundlesOrFromBundle) { var toMv = removing.shift() toRemoveByName[name] = toRemoveByName[name].filter((rm) => rm ! == toMv) pkg.fromPath = toMv.pkg.path setAction(differences, ‘move’, pkg) delete toRemove[toMv.flatname] // we don’t generate add actions for things found in links (which already exist on disk) } else if (! pkg.isInLink || ! (pkg.fromBundle && pkg.fromBundle.isLink)) { setAction(differences, ‘add’, pkg) } } })

// finally generate our remove actions from any not consumed by moves Object .keys(toRemove) .map((flatname) => toRemove[flatname]) .forEach((pkg) => setAction(differences, ‘remove’, pkg))

return filterActions(differences) }

First of all, we know that diff is nothing more than add, delete, or modify operations, with a billion more details added, so this code is easy to understand. -delete: finds the information from oldTree that newTree does not have and adds it to toRemove. <br /> is placed in toRemove because of the flattening of node_modules and the interdependence of modules, and subsequent operations may reuse packages here. Can't help but think of the classic recursive optimization...Copy the code

// Build our tentative remove list. We don’t add remove actions yet // because we might resuse them as part of a move. Object.keys(flatOldTree).forEach(function (flatname) { if (flatname === ‘/’) return if (flatNewTree[flatname]) return var pkg = flatOldTree[flatname] if (pkg.isInLink && /^[.][.][/\]/.test(path.relative(newTree.realpath, pkg.realpath))) return

toRemove[flatname] = pkg var name = moduleName(pkg) if (! toRemoveByName[name]) toRemoveByName[name] = [] toRemoveByName[name].push({flatname: flatname, pkg: pkg}) })

- update: traverses newTree. If there are oldTree packages in newTree, the current package status is updated. Of course, NPM does not automatically update these packages unless the user updates themCopy the code

var oldPkg = pkg.oldPkg = flatOldTree[flatname] if (oldPkg) { // if the versions are equivalent then we don’t need to The update… unless // the user explicitly asked us to. if (! pkg.userRequired && pkgAreEquiv(oldPkg, pkg)) return setAction(differences, ‘update’, pkg) }

- move: If the package information found from toRemove is the same as that of the new package and the package has no binding operation, move the package to move. Then delete the package from toRemove.Copy the code

// find any packages we’re removing that share the same name and are equivalent var removing = (toRemoveByName[name] || []).filter((rm) => pkgAreEquiv(rm.pkg, pkg)) var bundlesOrFromBundle = pkg.fromBundle || pkg.package.bundleDependencies // if we have any removes that match AND we’re not working with a bundle then upgrade to a move if (removing.length && ! bundlesOrFromBundle) { var toMv = removing.shift() toRemoveByName[name] = toRemoveByName[name].filter((rm) => rm ! == toMv) pkg.fromPath = toMv.pkg.path setAction(differences, ‘move’, pkg) delete toRemove[toMv.flatname] // we don’t generate add actions for things found in links (which already exist on disk) }“`

  • Add: If no information is found in oldTree or in the Move list, the state is Add
  • Remove: The last remaining toRemove state is remove

end

Refer to the link

  • What is the file ‘package.json’ ?
  • npm semver calculator
  • npm-package-lock.json
  • Understand front-end dependencies
  • npm-install