包管理器
看了一下NPM,写点口水话。
如果使用过Gradle
,一定对下面的配置很容易理解:
//用于构建项目的插件
apply plugin: 'java'
apply plugin: 'spring-boot'
apply plugin: 'idea'
apply plugin: 'war'
buildscript {
repositories {
mavenCentral()
maven { url "https://repo.spring.io/plugins-release" }
}
//指定gradle插件的版本
dependencies {
classpath('io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE')
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.2.RELEASE")
}
}
//构建脚本中所依赖库在jcenter仓库下载
repositories {
jcenter()
}
//指定当前项目的依赖
dependencies {
compile("org.springframework.boot:spring-boot-starter")
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile('mysql:mysql-connector-java:5.1.38')
compile('commons-fileupload:commons-fileupload:1.3.1')
compile('com.alibaba:fastjson:1.2.7')
compile("org.springframework.boot:spring-boot-starter-velocity")
}
tasks.withType(JavaCompile) {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
如果使用过Maven
,一定对下面的配置很容易理解:
4.0.0
FruitShop
FruitShop
war
1.0-SNAPSHOT
FruitShop Maven Webapp
http://maven.apache.org
1.2.5
4.2.5.RELEASE
org.slf4j
slf4j-api
1.7.21
org.mybatis
mybatis
3.4.0
mysql
mysql-connector-java
5.1.38
FruitShop
maven-compiler-plugin
1.6
src/main/java
**/*.xml
Gradle和Maven都是Java Web项目的构建工具,当然还有Ant,Gradle还作为Android项目的官方构建工具。
所以如果熟悉Gradle和Maven,那么对NPM就非常容易理解。
NPM前世今生
NPM(Node Package Manager)
- 作为NodeJs的包管理器,伴随着Node的诞生而诞生,内置于Node中,当安装好Node之后,NPM也就对应安装好了。 但是具体在哪个版本中内置的我没有找到(请大佬告诉我)。下面是NPM的最初版本:
Yarn与CNPM
-
Yarn可以理解为NPM的远房表亲,同样是包管理器(但我就是不用你)。
-
CNPM是淘宝对NPM做的国内镜像,主要为了解决国内开发者使用NPM下载依赖中超时等问题。但是使用CNPM下载安装各种依赖包时有可能会出现莫名其妙的错误。
NPM项目
初始化项目,命令行输入:
npm init
,然后一顿回车
D:\dev>cd npm-test
D:\dev\npm-test>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install ` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (npm-test)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to D:\dev\npm-test\package.json:
{
"name": "npm-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this ok? (yes)
D:\dev\npm-test>
此时已经初始化好了项目,并自动生成了package.json文件
{
"name": "npm-test", //项目名
"version": "1.0.0", //版本号
"description": "", //项目描述
"main": "index.js", //入口文件
"scripts": { //定义脚本命令
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "", //作者
"license": "ISC" //开放源代码许可证 ISC
}
其中
scripts
的配置里面有一个test字段,当我们在命令行输入npm run test
会输出echo "Error: no test specified" && exit 1
D:\dev\npm-test>npm run test
> [email protected] test D:\dev\npm-test
> echo "Error: no test specified" && exit 1
"Error: no test specified"
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] test: `echo "Error: no test specified" && exit 1`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] test script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm WARN Local package.json exists, but node_modules missing, did you mean to install?
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\wxqdoit\AppData\Roaming\npm-cache\_logs\2019-02-26T06_46_24_718Z-debug.log
D:\dev\npm-test>
将
scripts
下改成:"build": "node index.js"
,再在项目目录下新建index.js,并编写如下代码
function closure(){
var a = 1;
return function(){
console.log(a)
}
}
//内部函数引用外部函数的变量,并返回自身,なに、大名鼎鼎的闭包?
closure()()
在命令行输入
npm run build
,记住是npm run +自定义命令
D:\dev\npm-test>npm run build
> [email protected] build D:\dev\npm-test
> node index.js
1
D:\dev\npm-test>
姿势摆好,准备进入正题了
安装依赖
- 安装express:
npm install express
很快就安装好了,打开package.json文件发现里面多了如下依赖,同时也自动生成了node_modules文件夹。 很多资料说
npm install xxx
只是安装到node_modules目录中不会添加package.json到中, 而npm install xxx --save
会添加到package.json中,但是我每次执行npm install xxx
都修改了package.json
"dependencies": {
"express": "^4.16.4"
}
下面我们执行
npm install express --save-dev
,安装完成后查看package.json, 发现将express转移到了devDependencies里面
"dependencies": {},
"devDependencies": {
"express": "^4.16.4"
}
所以总结如下
-
npm install xxx
在我的环境下等同于npm install xxx --save
会安装到dependencies里面 -
npm install xxx --save
项目实际上线后需要依赖的包请使用这条命令安装 -
npm install xxx --save-dev
项目实际上线后【不】需要依赖的包请使用这条命令安装 -
dependencies代表生成环境
-
devDependencies代表开发环境
现在将express转到dependencies里面:
npm install express --save
试了几次之后发现
npm install express --save
根本没有起作用, 所以我npm uninstall express
再npm install express --save
NPM模块安装机制
- 发出
npm install
命令 - 检测package.json依赖
- 查询node_modules目录之中是否已经存在指定模块
- 若存在,不再重新安装
- 若不存在
- npm 向 registry 查询模块压缩包的网址
- 下载压缩包,存放在根目录下的
.npm
目录里 - 解压压缩包到当前项目的
node_modules
目录
NPM有哪些命令呢,命令行输入
npm
,简写命令在Node安装目录\node_modules\npm\lib\config\cmd-list.js
可以看到
D:\dev\npm-test>npm
Usage: npm
where is one of:
access, adduser, bin, bugs, c, cache, completion, config,
ddp, dedupe, deprecate, dist-tag, docs, doctor, edit,
explore, get, help, help-search, i, init, install,
install-test, it, link, list, ln, login, logout, ls,
outdated, owner, pack, ping, prefix, profile, prune,
publish, rb, rebuild, repo, restart, root, run, run-script,
s, se, search, set, shrinkwrap, star, stars, start, stop, t,
team, test, token, tst, un, uninstall, unpublish, unstar,
up, update, v, version, view, whoami
打开你的Node安装目录,仔细分析发现NPM其实就是一个Node应用。 那我们的
npm install
究竟干了什么呢,当你安装完Node,就在系统中注册了npm命令。 打开Node安装目录\node_modules\npm\bin
,有npm.cmd文件与npm-cli.js文件 npm.cmd入口
:: Created by npm, please don't edit manually.
//关闭回显
@ECHO OFF
//本地化
SETLOCAL
//%~dp0代表当前位置
SET "NODE_EXE=%~dp0\node.exe"
IF NOT EXIST "%NODE_EXE%" (
SET "NODE_EXE=node"
)
//'CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g'这一行在npm.cmd起的作用是
//如果能运行这两个命令并且得到结果的话将NPM_PREFIX_NPM_CLI_JS的值设置
//为"\node_modules\npm\bin\npm-cli.js"
SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js"
FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g') DO (
SET "NPM_PREFIX_NPM_CLI_JS=%%F\node_modules\npm\bin\npm-cli.js"
)
IF EXIST "%NPM_PREFIX_NPM_CLI_JS%" (
SET "NPM_CLI_JS=%NPM_PREFIX_NPM_CLI_JS%"
)
"%NODE_EXE%" "%NPM_CLI_JS%" %*
//现在实际启动npm并运行命令
//这是如何以编程方式使用npm:
conf._exit = true
npm.load(conf, function (er) {
if (er) return errorHandler(er)
npm.commands[npm.command](npm.argv, function (err) {
// https://www.youtube.com/watch?v=7nfPu8qTiQU
if (!err && npm.config.get('ham-it-up') && !npm.config.get('json') && !npm.config.get('parseable') && npm.command !== 'completion') {
output('\n ? I Have the Honour to Be Your Obedient Servant,? ~ npm ??\n')
}
errorHandler.apply(this, arguments)
})
})
打开
Node安装目录\node_modules\npm\lib
,找到install.js文件 (细心的你可能已经发现了基本上每一个js文件对应了上述的一个命令),用编辑器打开install.js
'use strict'
// npm install
//
// See doc/cli/npm-install.md for more description
//
// Managing contexts...
// there's a lot of state associated with an "install" operation, including
// packages that are already installed, parent packages, current shrinkwrap, and
// so on. We maintain this state in a "context" object that gets passed around.
// every time we dive into a deeper node_modules folder, the "family" list that
// gets passed along uses the previous "family" list as its __proto__. Any
// "resolved precise dependency" things that aren't already on this object get
// added, and then that's passed to the next generation of installation.
module.exports = install
module.exports.Installer = Installer
var usage = require('./utils/usage')
install.usage = usage(
'install',
'\nnpm install (with no args, in package dir)' +
'\nnpm install [<@scope>/]' +
'\nnpm install [<@scope>/]@' +
'\nnpm install [<@scope>/]@' +
'\nnpm install [<@scope>/]@' +
'\nnpm install ' +
'\nnpm install ' +
'\nnpm install ' +
'\nnpm install ' +
'\nnpm install /',
'[--save-prod|--save-dev|--save-optional] [--save-exact] [--no-save]'
)
//175行定义install方法,这个方法进入核心的install逻辑
function install (where, args, cb) {
if (!cb) {
cb = args
args = where
where = null
}
var globalTop = path.resolve(npm.globalDir, '..')
if (!where) {
where = npm.config.get('global')
? globalTop
: npm.prefix
}
validate('SAF', [where, args, cb])
// the /path/to/node_modules/..
var dryrun = !!npm.config.get('dry-run')
if (npm.config.get('dev')) {
log.warn('install', 'Usage of the `--dev` option is deprecated. Use `--only=dev` instead.')
}
if (where === globalTop && !args.length) {
args = ['.']
}
args = args.filter(function (a) {
return path.resolve(a) !== npm.prefix
})
new Installer(where, dryrun, args).run(cb)
}
//205行定义了Installer类
function Installer (where, dryrun, args, opts) {}
//358行 跟踪器创建
Installer.prototype.newTracker = function (tracker, name, size) {
validate('OS', [tracker, name])
if (size) validate('N', [size])
this.progress[name] = tracker.newGroup(name, size)
return function (next) {
process.emit('time', 'stage:' + name)
next()
}
}
Installer.prototype.finishTracker = function (name, cb) {
validate('SF', arguments)
process.emit('timeEnd', 'stage:' + name)
cb()
}
Installer.prototype.loadCurrentTree = function (cb) {
validate('F', arguments)
log.silly('install', 'loadCurrentTree')
var todo = []
if (this.global) {
todo.push([this, this.readGlobalPackageData])
} else {
todo.push([this, this.readLocalPackageData])
}
todo.push([this, this.normalizeCurrentTree])
chain(todo, cb)
}
//387行 创建node
var createNode = require('./install/node.js').create
下面是官方介绍
This command installs a package, and any packages that it depends on. If the package has a package-lock or shrinkwrap file, the installation of dependencies will be driven by that, with an npm-shrinkwrap.json
taking precedence if both files exist. See package-lock.json(5) and npm-shrinkwrap(1).
A package
is:
- a) a folder containing a program described by a
package.json(5)
file - b) a gzipped tarball containing (a)
- c) a url that resolves to (b)
- d) a
that is published on the registry (see@ npm-registry(7)
) with (c) - e) a
(see@ npm-dist-tag(1)
) that points to (d) - f) a
that has a "latest" tag satisfying (e) - g) a
that resolves to (a)
总结下来就是:
- 执行工程自身
- 确定首层依赖模块
- 根据模块信息递归网络获取模块
- 模块扁平化,解决重复冗余问题
- 安装模块,更新node_modules
- 自身生命周期,生成或更新package.json
参考文章
Maven和Gradle对比
第20题:介绍下 npm 模块安装机制
Node.js npm 详解;