Node 的生态NPM

  • npm i 【npm install】

安装

  • npm install npm -g

npm的三个部分

npm registry 【注册表】

  • npm i lodash@4.17.11
  • 淘宝 NPM 镜像
    例如:
    npm i lodash@4.17.11 --registry=https://registry.npm.taobao.org
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install lodash@4.17.11

团队搭建私有 registry

初始化

  • npm init
  • npm init --yes

安装

  • npm install

运行时依赖、本地开发依赖

  • --save、--save-dev 安装到 dependencies【运行时依赖的模块】 和 devDependencies【本地开发时依赖的模块】
  • 简写 npm i xx -Snpm i xx -D

npm help install

~ npm help install
# 项目中已有 package.json,可以直接 npm intall 安装所有依赖项
npm install (with no args, in package dir)
# scope 通常用于管理私有模块,以 @ 开头,没有 @ 则反之
# npm install some-pkg
# npm install @scott/some-pkg
npm install [<@scope>/]<name>
# 可以安装特定 tag 的模块,默认是 latest,如:
# npm install lodash@latest
npm install [<@scope>/]<name>@<tag>
# npm install lodash@4.17.11
# npm install @scott/some-pkg@1.0.0
npm install [<@scope>/]<name>@<version>
# 安装一个某个范围内的版本
# npm install lodash@">=2.0.0 <3.0.0"
# npm install @scott/som-pkg@">=2.0.0 <3.0.0"
npm install [<@scope>/]<name>@<version range>
# npm install git+ssh://git@github.com:tj/commander.js.git
npm install <git-host>:<git-user>/<repo-name>
# 以 git 仓库地址来安装
# npm install https://github.com/petkaantonov/bluebird.git
npm install <git repo url>
# 安装本地的 tar 包
# npm install /Users/black/Downloads/request-2.88.1.tar.gz
npm install <tarball file>
# 以 tar 包地址来安装
# npm install https://github.com/caolan/async/tarball/v2.3.0
# npm install https://github.com/koajs/koa/archive/2.5.3.tar.gz
npm install <tarball url>
# 从本地文件夹安装
# npm install ../scott/some-module
npm install <folder>
# 卸载也很简单
npm uninstall some-pkg -S
# 或者简写,加上 -S 是把卸载也同步到 package.json 中
npm un some-pkg -S

npm semver version

  • package.json
"dependencies": {
  "bluebird": "^3.5.2",
  "lodash": "^4.17.11"
}
  • node_modules
npm tree -L 2
.
├── node_modules
│   ├── bluebird
│   └── lodash
├── package-lock.json
└── package.json

版本管理

  • Semantic Versioning 2.0.0

  • semver.org
    版本号比如 v4.5.1,v 是 version 的缩写,4.5.1 被 . 分开成三端,这三端分别是:major minor patch,也就是 主版本号.次版本号.修订号

  • major: breaking changes (做了不兼容的 API 修改)

  • minor: feature add(向下兼容的功能性新增)

  • patch: bug fix, docs(向下兼容的问题修正)

node_modules

  • 目录是递归安装的,它是按照依赖关系进行文件夹的嵌套
~ tree -L 4
.
├── connect-mongo
│   ├── node_modules
│   │   └── mongodb
│   │       ├── node_modules
├── mongoose
│   ├── node_modules
│   │   ├── mongodb
│   │   │   └── node_modules
│   │   └── sliced
├── async
├── grunt
│   ├── node_modules
│   │   ├── async
│   │   └── which
└── underscore

npm3 时代里面策略改成了平铺结构

➜  node_modules tree -L 1
.
├── ajv
├── asn1
├── assert-plus
├── asynckit
├── aws-sign2
├── aws4
├── bcrypt-pbkdf
├── bluebird
├── caseless
├── co
├── combined-stream
├── delayed-stream
├── fast-deep-equal
├── fast-json-stable-stringify
├── forever-agent
├── form-data
├── uuid
└── ...省略剩下 20 个

npm shrinkwrap 锁包

  • package-lock文件 【npm5以后的新特性】
  • 在一个 package.json 里的 dependencies 里面,包的依赖版本可以这样写:
"lodash": "~3.9.0",
"lodash": "^3.9.0",
"lodash": ">3.9.0",
"lodash": ">=1.0.0-rc.2",
"lodash": "*"
// ... 更多写法不再列举
  • ~ 意思是,选择一个最近的小版本依赖包,比如 ~3.9.0 可以匹配到所有的 3.9.x 版本,但是不会匹配到 3.10.0
  • ^ 则是匹配最新的大版本,比如 ^3.9.0 可以匹配到所有的 3.x.x,但是不会匹配到 4.0.0
  • npm init --yes && npm i lodash async -S

npm scripts

  • 在 package.json 里的 scripts 里配置的各种任务,都可以这样直接调用:
npm start
npm run dev
npm run egg:prod
"scripts": {
  "build": "npm run build:prod",
  "clean:dist": "rimraf ./dist",
  "build:prod": "cross-env NODE_ENV=production webpack"
}

# 如下命令行均可执行
➜  npm run clean:dist
➜  npm run build:prod
➜  npm run build
"scripts": {
  // 通过 && 分隔,如果 clean:dist 任务失败,则不会执行后面的构建任务
  "build:task1": "npm run clean:dist && npm run build:prod"
  // 通过 ; 分隔,无论 clean:dist 是否成功,运行后都继续执行后面的构建任务
  "build:task2": "npm run clean:dist;npm run build:prod"
  // 通过 || 分隔,只有当 clean:dist 失败,才会继续执行后面的构建任务
  "build:task3": "npm run clean:dist;npm run build:prod"
  "clean:dist": "rimraf ./dist",
  "build:prod": "cross-env NODE_ENV=production webpack",
  // 对一个命令传配置参数,可以通过 -- --prod
  // 比如 npm run compile:prod 相当于执行 node ./r.js --prod
  "compile:prod": "npm run compile -- --prod",
  "compile": "node ./r.js",
}

npx

  • npx 是 npm 自带的非常酷炫的功能,直接执行依赖包里的二进制文件
# 先安装一个 cowsay
➜  npm install cowsay -D
# 包里的二进制文件会被放到 node_modules/.bin 目录下

➜  ll node_modules/.bin/
total 0
lrwxr-xr-x 1 16:34 cowsay -> ../cowsay/cli.js
lrwxr-xr-x 1 16:34 cowthink -> ../cowsay/cli.js

# 直接通过 npx 来调用 cowsay 里的二进制文件
➜  npx cowthink Node 好玩么
 _____________
( Node 好玩么 )
 -------------
        o   ^__^
         o  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

➜  npx cowsay 爽爆了
 _____________
< Node 爽爆了 >
 -------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

实现一个 Node LTS 查看工具

项目初始化

➜  cd ltsn
# 通过 touch 新建一个 markdown 的文件,用来描述包功能
➜  touch README.md
# 通过 touch 新建一个 git 忽略文件
➜  touch .gitignore

.gitignore

.DS_Store
npm-debug.log
node_modules
yarn-error.log
.vscode
.eslintrc.json

npm init 生成 package.json

  • npm init:
➜  ltsn npm init
Press ^C at any time to quit.
# 回车确认或者输入另外一个名字作为包名
package name: (ltsn)
# 版本就从 1.0.0 开始
version: (1.0.0)
# 简单的描述
description: CommandLine Tool for Node LTS
# 包的入口文件地址,通过 index.js 暴露内部函数
entry point: (index.js) index.js
# 测试脚本,可以先留空,大家根据实际情况取舍
test command:
# 包的 github 仓库地址
git repository: git@github.com:4liang/ltsn.git
# 一些功能关键词描述
keywords: Node LTS
# 作者自己
author: 4liang
# 开源的协议,默认是 ISC,我个人喜欢 MIT
license: (ISC) MIT
# 检查信息无误,输入 yes 回车即可
About to write to /Users/4liang/juejin/ltsn/package.json:
{
  "name": "ltsn",
  "version": "1.0.0",
  "description": "CommandLine Tool for Node LTS",
  "main": "lib/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git@github.com:4liang/ltsn.git"
  },
  "keywords": [
    "Node", "LTS"
  ],
  "author": "4liang",
  "license": "MIT",
  "bugs": { "url": "https://github.com/4liang/ltsn/issues" },
  "homepage": "https://github.com/4liang/ltsn#readme"
}

Is this OK? (yes) yes

# 输入 ls 查看当前包内文件
➜  ls
package.json README.md

脚手架

npm i yo generator-nm -g

目录 /lib

exports.query = require('./lib/query')
exports.update = require('./lib/update')

/lib/update.js 可以用来放数据源的获取和更新,而 /lib/query.js 里面可以放对数据的二次加工格式化之类,首先是 /lib/update.js 获取 Node LTS 数据:

const axios = require('axios')
const color = require('cli-color')
const terminalLink = require('terminal-link')
const compareVersions = require('compare-versions')

module.exports = async (v) => {
  // 拿到所有的 Node 版本
  const { data } = await axios
    .get('https://nodejs.org/dist/index.json')
  
  // 把目标版本的 LTS 都挑选出来
  return data.filter(node => {
    const cp = v
      ? (compareVersions(node.version, 'v' + v + '.0.0') >= 0)
      : true
    return node.lts && cp
  }).map(it => {
    // 踢出去 file 这个字段,其他的全部返回
    const { files, ...rest } = it
    return { ...rest }
  })
}

然后是 /lib/query.js:

const Table = require('cli-table')

function query(dists) {
  const keys = Object.keys(dists[0])
  // 建立表头
  const table = new Table({
    head: keys
  })
  
  // 拼接出表格的每一行
  return dists
    .reduce((res, item) => {
      table.push(
        Object.values(item)
      )
      return res
    }, table)
    .toString()
}

module.exports = query

最后,再增加一个 bin 文件夹,在它里面增加一个 ltsn 脚本文件,在里面写入:

#!/usr/bin/env node

const pkg = require('../package')
// 从顶层 index.js 里面拿到 lib 下面模块暴露的方法
const query = require('..').query
const update = require('..').update

// 输出结果到命令行窗口
function printResult(v) {
  update(v).then(dists => {
    const results = query(dists, v)
    console.log(results)
    process.exit()
  })
}

function printVersion() {
  console.log('ltsn ' + pkg.version)
  process.exit()
}

// 一些命令的帮助提示
function printHelp(code) {
  const lines = [
    '',
    '  Usage:',
    '    ltsn [8]',
    '',
    '  Options:',
    '    -v, --version             print the version of vc',
    '    -h, --help                display this message',
    '',
    '  Examples:',
    '    $ ltsn 8',
    ''
  ]

  console.log(lines.join('\n'))
  process.exit(code || 0)
}

// 包的入口函数,里面对参数做剪裁处理,拿到入参并给予
// 不同入参的处理逻辑
function main(argv) {
  if (!argv) {
    printHelp(1)
  }

  const getArg = function() {
    let args = argv.shift()

    args = args.split('=')
    if (args.length > 1) {
      argv.unshift(args.slice(1).join('='))
    }
    return args[0]
  }

  let arg

  while (argv.length) {
    arg = getArg()
    switch(arg) {
      case '-v':
      case '-V':
      case '--version':
        printVersion()

        break
      case '-h':
      case '-H':
      case '--help':
        printHelp()

        break
      default:
        printResult(arg)

        break
    }
  }
}

// 启动程序就开始执行主函数
main(process.argv.slice(2))

module.exports = main

#!/usr/bin/env node 加上 #! 这里是定义当前脚本的执行环境是用 Node 执行,安装包以后我们希望它可以像一个二进制一样来执行,那么可以到 package.json 来配置下执行路径,在 package.json 里面增加一个配置属性:

"bin": {
  "ltsn": "bin/ltsn"
},

然后对于用到的模块,我们在包目录下,执行:

npm i axios cli-color cli-table compare-versions -S

这样安装后,package-lock.json 也自动创建了, 整个的目录结果如下:

~ tree -L 2
.
├── README.md
├── bin
│   └── ltsn
├── index.js
├── lib
│   ├── query.js
│   └── update.js
├── node_modules
├── package-lock.json
└── package.json

再把 README.md 文档内容完善一下,我们的代码就准备好了。

npm install 本地包进行测试

等到代码写完,就可以本地测试了,本地测试最简单的办法,就是通过 npm link 安装下:

~ npm link
npm WARN ltsn@1.0.0 No repository field.

audited 145 packages in 2.103s
found 0 vulnerabilities

/Users/4liang/.nvm/versions/node/v10.11.0/bin/ltsn -> /Users/4liang/.nvm/versions/node/v10.11.0/lib/node_modules/ltsn/bin/ltsn
/Users/4liang/.nvm/versions/node/v10.11.0/lib/node_modules/ltsn -> /Users/4liang/juejin/ltsn

然后边调试代码边测试,测试完毕后,可以直接在本地指定目录来全局安装,首先卸载掉之前可能测试安装过的全局包:

npm uninstall ltsn -g

然后可以在命令行窗口用绝对路径,或者直接进入到包目录下,执行全局安装动作:

npm i ./ -g
# npm i /Users/4liang/juejin/ltsn -g
image.png

npm publish 发布包

  • npmjs.com 注册好一个账号且邮箱验证完

  • npm login

  • npm publish

npm install 线上包进行验证

~ npm uninstall ltsn -g
removed 1 package in 0.262s

~ npm i ltsn -g
/Users/black/.nvm/versions/node/v10.13.0/bin/ltsn -> /Users/black/.nvm/versions/node/v10.13.0/lib/node_modules/ltsn/bin/ltsn
+ ltsn@1.0.0
added 28 packages from 17 contributors in 5.33s
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容