欢迎点击「算法与编程之美」↑关注我们!
本文首发于微信公众号:"算法与编程之美",欢迎关注,及时了解更多此系列文章。
作者|王小强
来源|https://my.oschina.net/wxqdoit
包管理器看了一下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.0FruitShopFruitShopwar1.0-SNAPSHOTFruitShop Maven Webapphttp://maven.apache.org1.2.5 4.2.5.RELEASEorg.slf4jslf4j-api1.7.21org.mybatismybatis3.4.0mysqlmysql-connector-java5.1.38FruitShopmaven-compiler-plugin1.61.6src/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 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 > npm-test@1.0.0 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! npm-test@1.0.0 test: `echo "Error: no test specified" && exit 1` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the npm-test@1.0.0 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 > npm-test@1.0.0 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: npmwhereis 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 详解;
更多精彩文章:
where2go 团队
微信号:算法与编程之美

长按识别二维码关注我们!
温馨提示:点击页面右下角“写留言”发表评论,期待您的参与!期待您的转发!