1. 包管理器npm介绍

1.1 什么是npm

NPM的全称是Node Package Manager,是一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准。

2020年3月17日,Github宣布收购npm,GitHub现在已经保证npm将永远免费。

小贴士:

简单的讲,npm就是现代工程化的JavaScript项目中的依赖管理工具,工程化项目中的JavaScript依赖包全部通过npm工具进行安装和管理,开发者也可以通过npm工具发布个人开发的依赖包项目提供给世界范围内的程序员使用。

1.2 如何使用npm

本地安装了NodeJS环境后,在系统的命令行工具中除node命令外,还包含npm命令。npm命令可以帮助开发者快速的安装和管理项目以来。

查看npm版本的方式

npm -v

控制台上会展示当前npm以来的版本信息

zhangyunpeng@zhangyunpengdeMacBook-Pro ~ % npm -v
7.21.0

查看npm可用功能

开发者成功安装了npm依赖管理工具后,可以通过命令行的方式查看npm所包含的所有功能,利用npm -h指令,查看下面的注释了解npm主要功能介绍。

zhangyunpeng@zhangyunpengdeMacBook-Pro ~ % npm -h
npm <command>Usage:
#npm install 会自动安装你的项目package.json文件中所包含的所有依赖到本地
npm install        install all the dependencies in your project
#npm install <包名> 将指定依赖包下载并安装到你的项目中
npm install <foo>  add the <foo> dependency to your project
#npm test 运行当前项目的测试用例
npm test           run this project's tests
#npm run <命令名称> 会自动运行当前项目scripts中所包含的同名指令
npm run <foo>      run the script named <foo>
#npm <命令> -h 快速查看当前命令的帮助文档
npm <command> -h   quick help on <command>
#npm -l 列出所有命令的使用说明
npm -l             display usage info for all commands
#以下命令不常用
npm help <term>    search for help on <term>
npm help npm       more involved overview
# 所有可用命令列表
All commands:access, adduser, audit, bin, bugs, cache, ci, completion,config, dedupe, deprecate, diff, dist-tag, docs, doctor,edit, exec, explain, explore, find-dupes, fund, get, help,hook, init, install, install-ci-test, install-test, link,ll, login, logout, ls, org, outdated, owner, pack, ping,pkg, prefix, profile, prune, publish, rebuild, repo,restart, root, run-script, search, set, set-script,shrinkwrap, star, stars, start, stop, team, test, token,uninstall, unpublish, unstar, update, version, view, whoamiSpecify configs in the ini-formatted file:/Users/zhangyunpeng/.npmrc
or on the command line via: npm <command> --key=valueMore configuration info: npm help config
Configuration fields: npm help 7 confignpm@7.21.0 /usr/local/lib/node_modules/npm
zhangyunpeng@zhangyunpengdeMacBook-Pro ~ %

1.3 镜像地址管理

关于npm依赖中心

自从有了npm依赖管理工具后,所有互联网中存在的公共依赖都存在于https://www.npmjs.com/ 网站中。

所以通过使用npm包管理器安装的依赖都可以在该网站中查询到依赖包的安装方式和使用文档,如图所示。

接下来可以通过浏览NPM网站进行简单的依赖包学习。

npm镜像管理配置

由于日常的大量的npm依赖都通过世界的npm依赖中心提供,所以使用npm工具安装JavaScript依赖的时候需要开发者必须连接互联网,此时就涉及到npm的镜像地址配置工作了。

由于不同国家的开发者所存在的网络环境不同,而npm的依赖对于国内开发者来说都是保存在国外,所以使用npm下载依赖包时连接默认地址会出现访问慢的问题,所以在刚刚安装npm依赖管理工具时大多数人都会先使用配置工具将镜像地址进行修改。

查看当前的npm镜像地址

npm config get registry

默认的情况下npm返回的的镜像地址为https://registry.npmjs.org/

设置国内的npm镜像地址:

为了保证npm依赖的访问速度提升,各国都提供了很多的镜像地址,设置npm镜像地址的方式为

npm config set registry "镜像地址"

常用的npm镜像地址

npm ---------- https://registry.npmjs.org/
yarn --------- https://registry.yarnpkg.com/
tencent ------ https://mirrors.cloud.tencent.com/npm/
cnpm --------- https://r.cnpmjs.org/
taobao ------- https://registry.npmmirror.com/
npmMirror ---- https://skimdb.npmjs.com/registry/

镜像管理工具

频繁使用npm config 命令来切换镜像是非常麻烦的事情,所以重复的事情当然要交给工具去做,这时我们可以使用nrm镜像管理工具来实现快速的npm镜像地址切换。

镜像工具安装指令

npm i nrm -g #-g和--global代表全局安装的意思

安装完成后命令行工具中便可以使用nrm指令来进行镜像的管理和切换。

列出所有可用地址

nrm ls

添加新的地址

nrm add <key> <address>

删除已有的地址

nrm del <key>

切换现有的镜像地址

nrm use <key>

1.4 npm config的介绍

npm的命令行工具不仅仅可以切换镜像地址,还可以对npm所有的属性进行设置,在上面的章节中展示了设置或获取镜像地址的完整指令为

npm config get/set registry [<address>]

实际上npm config部分就代表操作了npm的配置文件的某个属性,所以set和get对应的就是获取或设置指定属性的结果。

查询详细的config数据

npm在电脑上的默认配置已经足以满足日常开发需求,但是如果涉及到npm的一些特殊参数查看时,还需要通过指令进行操作。

列出npm默认配置信息

npm config list #获取精简的npm配置信息

查看下面的案例

zhangyunpeng@zhangyunpengdeMacBook-Pro ~ % npm config list
#这行代表当前的npm配置信息保存在/Users/zhangyunpeng/.npmrc文件中
; "user" config from /Users/zhangyunpeng/.npmrc//localhost:4873/:_authToken = "vY61v52JVw2iMlRQm8yS7g=="
//registry.npmjs.org/:_authToken = (protected)
home = "https://npm.taobao.org"
http://ssss = ""
ignore-scripts = false
registry = "https://registry.npmmirror.com/"
ssss = ""
# node命令所存放的目录
; node bin location = /usr/local/bin/node
#运行命令所在目录
; cwd = /Users/zhangyunpeng
; HOME = /Users/zhangyunpeng
; Run `npm config ls -l` to show all defaults.

根据日志中给出的地址找到文件中的.npmrc文件

接下来执行npm config list -l,这里列出了本地的所有npm配置属性。

zhangyunpeng@zhangyunpengdeMacBook-Pro ~ % npm config list -l
; "default" config from default values_auth = (protected)
access = null
all = false
allow-same-version = false
also = null
audit = true
audit-level = null
auth-type = "legacy"
before = null
bin-links = true
browser = null
ca = null
cache = "/Users/zhangyunpeng/.npm"
cache-max = null
cache-min = 0
cafile = null
call = ""
cert = null
ci-name = null
cidr = null
color = true
commit-hooks = true
depth = null
description = true
dev = false
diff = []
diff-dst-prefix = "b/"
diff-ignore-all-space = false
diff-name-only = false
diff-no-prefix = false
diff-src-prefix = "a/"
diff-text = false
diff-unified = 3
dry-run = false
editor = "vi"
engine-strict = false
fetch-retries = 2
fetch-retry-factor = 10
fetch-retry-maxtimeout = 60000
fetch-retry-mintimeout = 10000
fetch-timeout = 300000
force = false
foreground-scripts = false
format-package-lock = true
fund = true
git = "git"
git-tag-version = true
global = false
global-style = false
globalconfig = "/usr/local/etc/npmrc"
heading = "npm"
https-proxy = null
if-present = false
; ignore-scripts = false ; overridden by user
include = []
include-staged = false
init-author-email = ""
init-author-name = ""
init-author-url = ""
init-license = "ISC"
init-module = "/Users/zhangyunpeng/.npm-init.js"
init-version = "1.0.0"
init.author.email = ""
init.author.name = ""
init.author.url = ""
init.license = "ISC"
init.module = "/Users/zhangyunpeng/.npm-init.js"
init.version = "1.0.0"
json = false
key = null
legacy-bundling = false
legacy-peer-deps = false
link = false
local-address = null
location = "user"
loglevel = "notice"
logs-max = 10
; long = false ; overridden by cli
maxsockets = 15
message = "%s"
metrics-registry = "https://registry.npmmirror.com/"
node-options = null
node-version = "v16.5.0"
noproxy = [""]
npm-version = "7.21.0"
offline = false
omit = []
only = null
optional = null
otp = null
pack-destination = "."
package = []
package-lock = true
package-lock-only = false
parseable = false
prefer-offline = false
prefer-online = false
prefix = "/usr/local"
preid = ""
production = null
progress = true
proxy = null
read-only = false
rebuild-bundle = true
; registry = "https://registry.npmjs.org/" ; overridden by user
save = true
save-bundle = false
save-dev = false
save-exact = false
save-optional = false
save-peer = false
save-prefix = "^"
save-prod = false
scope = ""
script-shell = null
searchexclude = ""
searchlimit = 20
searchopts = ""
searchstaleness = 900
shell = "/bin/zsh"
shrinkwrap = true
sign-git-commit = false
sign-git-tag = false
sso-poll-frequency = 500
sso-type = "oauth"
strict-peer-deps = false
strict-ssl = true
tag = "latest"
tag-version-prefix = "v"
timing = false
tmp = "/var/folders/36/2z_w159s5yx56w2rd37z2bcr0000gn/T"
umask = 0
unicode = true
update-notifier = true
usage = false
user-agent = "npm/7.21.0 node/v16.5.0 darwin x64 workspaces/false"
userconfig = "/Users/zhangyunpeng/.npmrc"
version = false
versions = false
viewer = "man"
which = null
workspace = []
workspaces = false
yes = null ; "user" config from /Users/zhangyunpeng/.npmrc//localhost:4873/:_authToken = "vY61v52JVw2iMlRQm8yS7g=="
//registry.npmjs.org/:_authToken = (protected)
home = "https://npm.taobao.org"
http://ssss = ""
ignore-scripts = false
registry = "https://registry.npmmirror.com/"
ssss = "" ; "cli" config from command line optionslong = true
zhangyunpeng@zhangyunpengdeMacBook-Pro ~ % 

如果想要更加方便的查看全局属性可以使用下列指令

npm config list --json

如果需要编辑文件可以通过下列指令

npm config edit

2. 企业级npm包管理器实用攻略

对于npm的应用其实大多数开发者涉及到的场景都是以下集中情况:

  • 安装项目的所有依赖包npm install

  • 安装执行依赖包到项目npm install <packageName>

  • 删除置顶依赖包npm uninstall <packageName>

  • 执行项目的功能脚本

    npm run build #构建项目
    npm run dev/serve/start #运行项目
    npm run eslint/... #其他项目功能
    

若仅掌握以上类型的npm指令正常在开发过程中能解决日常的大部分问题,但是对于想要晋升的开发者来说,如果只对npm了解的很片面代表所从事的项目级别和研发级别不是很高,所以接下来围绕Node项目进一步的了解npm。

2.1 初始化工程化项目

对于工程化项目来说,仅仅创建一个文件夹是不够的,我们需要在创建文件夹的基础上为该项目创建一个描述文件,也即是在前端项目中特别常见的package.json文件。那么这个文件究竟包含多少属性,每个属性有什么样的规则呢?

npm init 介绍

可以通过npm init -h查看该指令究竟有多少种使用方式:

zhangyunpeng@zhangyunpengdeMacBook-Pro test1 % npm init -h
npm initCreate a package.json fileUsage:
#npm init -f | -y 代表初始化当前项目的package.json文件
npm init [--force|-f|--yes|-y|--scope]
#npm init 名称 相当于以某个线上的模版初始化项目,比如npm init react-app 项目名称
npm init <@scope> (same as `npx <@scope>/create`)
#同上
npm init [<@scope>/]<name> (same as `npx [<@scope>/]create-<name>`)Options:
[-y|--yes] [-f|--force]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces]aliases: create, innitRun "npm help init" for more info

该指令最常用的部分就是npm init -y

初始化一个项目

  1. 在编辑器中创建一个文件夹test

  2. 在命令行工具中打开test文件夹并且输入指令

    npm init -y
    
  3. 命令行工具中会弹出如下日志,并且文件夹中会自动生成package.json配置文件

    zhangyunpeng@zhangyunpengdeMacBook-Pro test % npm init -y
    Wrote to /Users/zhangyunpeng/Desktop/JavaScript/test/package.json:{"name": "test","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"keywords": [],"author": "","license": "ISC"
    }
    
  4. 打开package.json文件查看默认属性

    {"name": "test",//项目名称"version": "1.0.0",//项目当前的版本号"description": "",//项目的描述内容"main": "index.js",//项目作为依赖包被别人引用时所执行的文件"scripts": { //项目的调试命令"test": "echo \"Error: no test specified\" && exit 1"},"keywords": [],//项目作为依赖发布后的搜索关键字"author": "", //项目作者"license": "ISC"//软件许可证
    }
    

2.2 依赖管理介绍

项目开发阶段,使用npm管理项目依赖是开发者做的最多的工作,所以我们接下来学习一下如何通过npm管理项目依赖。

安装依赖

安装项目依赖通过npm install指令完成,不过npm安装依赖的方式多种多样,并且存在很多的指令组合,可以使用npm install -h查看可用的npm安装方式

zhangyunpeng@zhangyunpengdeMacBook-Pro test % npm install -h
npm installInstall a packageUsage:
npm install [<@scope>/]<pkg>
npm install [<@scope>/]<pkg>@<tag>
npm install [<@scope>/]<pkg>@<version>
npm install [<@scope>/]<pkg>@<version range>
npm install <alias>@npm:<name>
npm install <folder>
npm install <tarball file>
npm install <tarball url>
npm install <git:// url>
npm install <github username>/<github project>Options:
#这里为install后面的可选指令,不同指令代表不同的安装模式
[-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer]
[-E|--save-exact] [-g|--global] [--global-style] [--legacy-bundling]
[--strict-peer-deps] [--no-package-lock]
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]] [--ignore-scripts]
[--no-audit] [--no-bin-links] [--no-fund] [--dry-run]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces]
#这里代表别名所以npm i npm in npm install 等指令均代表npm install
aliases: i, in, ins, inst, insta, instal, isnt, isnta, isntal, addRun "npm help install" for more info
zhangyunpeng@zhangyunpengdeMacBook-Pro test %

查看安装指令后很多人才会发现,原来npm安装一个依赖包都有这么多的可选项,那么这里实际上开发者使用更多的是如下几个指令:

npm install xxx -S|--save|-s #代表本地安装一个依赖包在项目中使用
npm install xxx -D|--save-dev #代表安装一个依赖包到项目,该依赖包为运行项目所需的依赖
npm install xxx -g|--global #代表全局安装一个依赖包到npm本地电脑的全局依赖文件夹中

全局安装的依赖可以通过如下指令查看:

npm ls -g

运行效果如下:

zhangyunpeng@zhangyunpengdeMacBook-Pro test % npm ls -g
/usr/local/lib
├── @nestjs/cli@8.2.0
├── @vue/cli@5.0.1
├── cnpm@6.1.1
├── npm@7.21.0
├── nrm@1.2.5
├── nvm@0.0.4
├── p-nrm@1.0.7
├── typescript@4.5.5
├── verdaccio@5.8.0
└── yarn@1.22.11

全局安装的依赖包会自导保存到日志输出的目录下,访问该目录可以查看所有的全局依赖会统一存放在/usr/local/lib目录下的node_module文件夹中

除以上几个指令外,实际上npm安装依赖时还可以选择如下指令:

npm install xxx --save-optional #代表可选依赖
npm install xxx --save-peer #代表当前项目作为依赖包提供给其他项目使用时,项目需要自行安装当前依赖

依赖安装实战

  1. 在项目中执行如下命令安装webpack

    npm i webpack -D
    
  2. 在项目中执行如下命令安装vue

    npm i vue -S
    
  3. 在项目中执行如下命令安装react

    npm i react --save-peer
    
  4. 在项目中执行如下命令安装webpack-cli

    npm i webpack-cli --save-optional
    

全部执行完毕之后该项目的package.json会变成如下内容:

{"name": "test","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"keywords": [],"author": "","license": "ISC",//该属性下的依赖代表当前项目在实际运行时所需要依赖的包,并不会与当前项目核心代码组合到一起,项目打包构建成静态资源或发布成依赖包供给其他开发者使用时不会进入构建后的代码中"devDependencies": {"webpack": "^5.72.0"},//该属性下的依赖代表当前项目的核心代码依赖,项目打包构建成静态资源或发布成依赖包供给其他开发者使用时所必要的依赖包"dependencies": {"vue": "^3.2.33"},//该属性代表同级依赖,指的是当前的项目在构建后的运行时所需要的依赖,并构建后的项目中不包含此依赖,当其他开发者安装安装本依赖包时需要额外下载当前依赖才能保证本依赖包正常工作"peerDependencies": {"react": "^18.0.0"},//此选项代表可选依赖"optionalDependencies": {"webpack-cli": "^4.9.2"}
}

关于npm install 和 npm ci

简而言之,使用npm install和npm ci之间的主要区别是:

  • 该项目必须具有现有的package-lock.json或npm-shrinkwrap.json。
  • 如果程序包锁中的依赖项与package.json中的依赖项不匹配,则npm ci将退出并显示错误,而不是更新程序包锁。
  • npm ci一次只能安装整个项目:此命令不能添加单个依赖项。
  • 如果已经存在node_modules,它将在npm ci开始安装之前自动删除。
  • 它永远不会写入package.json或任何包锁:安装实际上是冻结的。

本质上, **npm install**读取package.json会创建依赖项列表,并用于package-lock.json告知要安装这些依赖项的版本。如果不存在依赖项package-lock.json,则将添加npm install

npm ci(以C intinuous I ntegration 命名)直接从中安装依赖项,package-lock.json并且package.json仅用于验证没有不匹配的版本。如果缺少任何依赖项或版本不兼容,它将抛出错误

使用npm install到一个项目中添加新的依赖,并更新相关的更新。通常,在拉动更新依赖项列表的更改之后,您将在开发期间使用它,但是npm ci在这种情况下使用它可能是一个好主意。

使用npm ci如果你需要一个确定的,可重复的构建。例如,在持续集成,自动化作业等过程中,以及在首次安装依赖项时,而不是npm install

npm install

  • 安装软件包及其所有依赖项。

  • 依赖关系由npm-shrinkwrap.jsonpackage-lock.json(按此顺序)驱动。

  • 不带参数:安装本地模块的依赖项。

  • 可以安装全局软件包。

  • 将在中安装所有缺少的依赖项node_modules

  • 它可能会写入

    package.json
    

    package-lock.json
    

    • 与参数(npm i packagename)结合使用时,它可能会写入package.json以添加或更新依赖项。
    • 当不带参数使用时,(npm i)可能会写入以package-lock.json锁定某些依赖项的版本(如果它们尚未在此文件中)。

npm ci

  • 至少需要npm v5.7.1。
  • 需要package-lock.jsonnpm-shrinkwrap.json存在。
  • 如果这两个文件的依赖项不匹配,则会引发错误package.json
  • 一次删除node_modules并安装所有依赖项
  • 它从不写入package.jsonpackage-lock.json
npm ci的算法

npm ci从生成整个依赖树package-lock.jsonnpm-shrinkwrap.jsonnpm install *更新的内容node_modules*使用以下算法(源):

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 dependenciesdependencies will be added as close to the top as is possiblewithout 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 firstkinds of actions are install, update, remove and move

2.3 npm依赖加载规则

依赖包安装位置

使用npm所安装的依赖,都会被保存到当前项目目录下的node_modules文件夹中,项目中的依赖加载规则是自底向上寻找node_modules文件夹中的依赖包。

为验证npm的依赖包加载规则,在test项目中创建名为test-vue的文件夹,在文件夹内部创建node_modules文件夹,在其内部创建vue文件夹,如图所示

此文件结构代表N个嵌套的工程化项目,用此项目识别npm加载依赖是按照什么规则加载的,所以接下来使用命令行工具打开test-vue/node_modules/vue文件夹,在其中执行npm init -y将该项目初始化为一个npm项目。

Wrote to /Users/zhangyunpeng/Desktop/JavaScript/test/test-vue/node_modules/vue/package.json:{"name": "vue","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"keywords": [],"author": "","license": "ISC"
}zhangyunpeng@zhangyunpengdeMacBook-Pro vue %

初始化项目后,在vue文件夹中创建src文件,并在其中创建index.js文件,在文件内部编写代码

const { version, name } = require('../package.json')
module.exports = { version,name }

此时项目的文件结构为

test-vue
└── node_modules└── vue├── package.json└── src└── index.js

最后在test-vue/node_modules/vue中创建的package.json中找到main属性并做如下改造

{"name": "vue","version": "1.0.0","description": "",//main代表当前开发者使用import xx from 'vue'或require('vue')时加载的是src下的index.js文件"main": "./src/index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"keywords": [],"author": "","license": "ISC"
}

接下来在test-vue根目录下创建index.js文件并初始化如下代码

const vue = require('vue')
const react = require('react')
console.log(vue)
console.log(react)

接下来在test-vue中打开命令行工具并执行运行命令

zhangyunpeng@zhangyunpengdeMacBook-Pro test-vue % node index
{ version: '1.0.0', name: 'vue' }
{Children: {map: [Function: mapChildren],forEach: [Function: forEachChildren],count: [Function: countChildren],toArray: [Function: toArray],only: [Function: onlyChild]},Component: [Function: Component],Fragment: Symbol(react.fragment),Profiler: Symbol(react.profiler),PureComponent: [Function: PureComponent],StrictMode: Symbol(react.strict_mode),Suspense: Symbol(react.suspense),__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {ReactCurrentDispatcher: { current: null },ReactCurrentBatchConfig: { transition: null },ReactCurrentOwner: { current: null },ReactDebugCurrentFrame: {setExtraStackFrame: [Function (anonymous)],getCurrentStack: null,getStackAddendum: [Function (anonymous)]},ReactCurrentActQueue: {current: null,isBatchingLegacy: false,didScheduleLegacyUpdate: false}},cloneElement: [Function: cloneElementWithValidation],createContext: [Function: createContext],createElement: [Function: createElementWithValidation],createFactory: [Function: createFactoryWithValidation],createRef: [Function: createRef],forwardRef: [Function: forwardRef],isValidElement: [Function: isValidElement],lazy: [Function: lazy],memo: [Function: memo],startTransition: [Function: startTransition],unstable_act: [Function: act],useCallback: [Function: useCallback],useContext: [Function: useContext],useDebugValue: [Function: useDebugValue],useDeferredValue: [Function: useDeferredValue],useEffect: [Function: useEffect],useId: [Function: useId],useImperativeHandle: [Function: useImperativeHandle],useInsertionEffect: [Function: useInsertionEffect],useLayoutEffect: [Function: useLayoutEffect],useMemo: [Function: useMemo],useReducer: [Function: useReducer],useRef: [Function: useRef],useState: [Function: useState],useSyncExternalStore: [Function: useSyncExternalStore],useTransition: [Function: useTransition],version: '18.0.0-fc46dba67-20220329'
}
zhangyunpeng@zhangyunpengdeMacBook-Pro test-vue % 

执行命令后会发现在test-vue中的node_modules的vue会作为本项目的依赖优先加载,而项目外部的react对象仍然可以作为依赖被加载到本项目中,这个就是node_modules的依赖加载顺序。

所以为了进一步验证我们的猜想,接下来在test项目的根目录中的index.js文件中编写代码并运行

const vue = require('vue')
console.log(vue)
zhangyunpeng@zhangyunpengdeMacBook-Pro test % node index
{EffectScope: [class EffectScope],ReactiveEffect: [class ReactiveEffect],customRef: [Function: customRef],effect: [Function: effect],effectScope: [Function: effectScope],getCurrentScope: [Function: getCurrentScope],isProxy: [Function: isProxy],isReactive: [Function: isReactive],isReadonly: [Function: isReadonly],isRef: [Function: isRef],isShallow: [Function: isShallow],markRaw: [Function: markRaw],onScopeDispose: [Function: onScopeDispose],proxyRefs: [Function: proxyRefs],reactive: [Function: reactive],readonly: [Function: readonly],ref: [Function: ref],shallowReactive: [Function: shallowReactive],shallowReadonly: [Function: shallowReadonly],shallowRef: [Function: shallowRef],stop: [Function: stop],toRaw: [Function: toRaw],toRef: [Function: toRef],toRefs: [Function: toRefs],triggerRef: [Function: triggerRef],unref: [Function: unref],camelize: [Function (anonymous)],capitalize: [Function (anonymous)],normalizeClass: [Function: normalizeClass],normalizeProps: [Function: normalizeProps],normalizeStyle: [Function: normalizeStyle],toDisplayString: [Function: toDisplayString],toHandlerKey: [Function (anonymous)],BaseTransition: {name: 'BaseTransition',props: {mode: [Function: String],appear: [Function: Boolean],persisted: [Function: Boolean],onBeforeEnter: [Array],onEnter: [Array],onAfterEnter: [Array],onEnterCancelled: [Array],onBeforeLeave: [Array],onLeave: [Array],onAfterLeave: [Array],onLeaveCancelled: [Array],onBeforeAppear: [Array],onAppear: [Array],onAfterAppear: [Array],onAppearCancelled: [Array]},setup: [Function: setup]},Comment: Symbol(Comment),Fragment: Symbol(Fragment),KeepAlive: {name: 'KeepAlive',__isKeepAlive: true,props: { include: [Array], exclude: [Array], max: [Array] },setup: [Function: setup]},Static: Symbol(Static),Suspense: {name: 'Suspense',__isSuspense: true,process: [Function: process],hydrate: [Function: hydrateSuspense],create: [Function: createSuspenseBoundary],normalize: [Function: normalizeSuspenseChildren]},Teleport: {__isTeleport: true,process: [Function: process],remove: [Function: remove],move: [Function: moveTeleport],hydrate: [Function: hydrateTeleport]},Text: Symbol(Text),callWithAsyncErrorHandling: [Function: callWithAsyncErrorHandling],callWithErrorHandling: [Function: callWithErrorHandling],cloneVNode: [Function: cloneVNode],compatUtils: null,computed: [Function: computed],createBlock: [Function: createBlock],createCommentVNode: [Function: createCommentVNode],createElementBlock: [Function: createElementBlock],createElementVNode: [Function: createBaseVNode],createHydrationRenderer: [Function: createHydrationRenderer],createPropsRestProxy: [Function: createPropsRestProxy],createRenderer: [Function: createRenderer],createSlots: [Function: createSlots],createStaticVNode: [Function: createStaticVNode],createTextVNode: [Function: createTextVNode],createVNode: [Function: createVNodeWithArgsTransform],defineAsyncComponent: [Function: defineAsyncComponent],defineComponent: [Function: defineComponent],defineEmits: [Function: defineEmits],defineExpose: [Function: defineExpose],defineProps: [Function: defineProps],getCurrentInstance: [Function: getCurrentInstance],getTransitionRawChildren: [Function: getTransitionRawChildren],guardReactiveProps: [Function: guardReactiveProps],h: [Function: h],handleError: [Function: handleError],initCustomFormatter: [Function: initCustomFormatter],inject: [Function: inject],isMemoSame: [Function: isMemoSame],isRuntimeOnly: [Function: isRuntimeOnly],isVNode: [Function: isVNode],mergeDefaults: [Function: mergeDefaults],mergeProps: [Function: mergeProps],nextTick: [Function: nextTick],onActivated: [Function: onActivated],onBeforeMount: [Function (anonymous)],onBeforeUnmount: [Function (anonymous)],onBeforeUpdate: [Function (anonymous)],onDeactivated: [Function: onDeactivated],onErrorCaptured: [Function: onErrorCaptured],onMounted: [Function (anonymous)],onRenderTracked: [Function (anonymous)],onRenderTriggered: [Function (anonymous)],onServerPrefetch: [Function (anonymous)],onUnmounted: [Function (anonymous)],onUpdated: [Function (anonymous)],openBlock: [Function: openBlock],popScopeId: [Function: popScopeId],provide: [Function: provide],pushScopeId: [Function: pushScopeId],queuePostFlushCb: [Function: queuePostFlushCb],registerRuntimeCompiler: [Function: registerRuntimeCompiler],renderList: [Function: renderList],renderSlot: [Function: renderSlot],resolveComponent: [Function: resolveComponent],resolveDirective: [Function: resolveDirective],resolveDynamicComponent: [Function: resolveDynamicComponent],resolveFilter: null,resolveTransitionHooks: [Function: resolveTransitionHooks],setBlockTracking: [Function: setBlockTracking],setDevtoolsHook: [Function: setDevtoolsHook],setTransitionHooks: [Function: setTransitionHooks],ssrContextKey: Symbol(ssrContext),ssrUtils: {createComponentInstance: [Function: createComponentInstance],setupComponent: [Function: setupComponent],renderComponentRoot: [Function: renderComponentRoot],setCurrentRenderingInstance: [Function: setCurrentRenderingInstance],isVNode: [Function: isVNode],normalizeVNode: [Function: normalizeVNode]},toHandlers: [Function: toHandlers],transformVNodeArgs: [Function: transformVNodeArgs],useAttrs: [Function: useAttrs],useSSRContext: [Function: useSSRContext],useSlots: [Function: useSlots],useTransitionState: [Function: useTransitionState],version: '3.2.33',warn: [Function: warn],watch: [Function: watch],watchEffect: [Function: watchEffect],watchPostEffect: [Function: watchPostEffect],watchSyncEffect: [Function: watchSyncEffect],withAsyncContext: [Function: withAsyncContext],withCtx: [Function: withCtx],withDefaults: [Function: withDefaults],withDirectives: [Function: withDirectives],withMemo: [Function: withMemo],withScopeId: [Function: withScopeId],Transition: [Function: Transition] {displayName: 'Transition',props: {mode: [Function: String],appear: [Function: Boolean],persisted: [Function: Boolean],onBeforeEnter: [Array],onEnter: [Array],onAfterEnter: [Array],onEnterCancelled: [Array],onBeforeLeave: [Array],onLeave: [Array],onAfterLeave: [Array],onLeaveCancelled: [Array],onBeforeAppear: [Array],onAppear: [Array],onAfterAppear: [Array],onAppearCancelled: [Array],name: [Function: String],type: [Function: String],css: [Object],duration: [Array],enterFromClass: [Function: String],enterActiveClass: [Function: String],enterToClass: [Function: String],appearFromClass: [Function: String],appearActiveClass: [Function: String],appearToClass: [Function: String],leaveFromClass: [Function: String],leaveActiveClass: [Function: String],leaveToClass: [Function: String]}},TransitionGroup: {name: 'TransitionGroup',props: {mode: [Function: String],appear: [Function: Boolean],persisted: [Function: Boolean],onBeforeEnter: [Array],onEnter: [Array],onAfterEnter: [Array],onEnterCancelled: [Array],onBeforeLeave: [Array],onLeave: [Array],onAfterLeave: [Array],onLeaveCancelled: [Array],onBeforeAppear: [Array],onAppear: [Array],onAfterAppear: [Array],onAppearCancelled: [Array],name: [Function: String],type: [Function: String],css: [Object],duration: [Array],enterFromClass: [Function: String],enterActiveClass: [Function: String],enterToClass: [Function: String],appearFromClass: [Function: String],appearActiveClass: [Function: String],appearToClass: [Function: String],leaveFromClass: [Function: String],leaveActiveClass: [Function: String],leaveToClass: [Function: String],tag: [Function: String],moveClass: [Function: String]},setup: [Function: setup]},VueElement: [class VueElement],createApp: [Function: createApp],createSSRApp: [Function: createSSRApp],defineCustomElement: [Function: defineCustomElement],defineSSRCustomElement: [Function: defineSSRCustomElement],hydrate: [Function: hydrate],initDirectivesForSSR: [Function: initDirectivesForSSR],render: [Function: render],useCssModule: [Function: useCssModule],useCssVars: [Function: useCssVars],vModelCheckbox: {deep: true,created: [Function: created],mounted: [Function: setChecked],beforeUpdate: [Function: beforeUpdate]},vModelDynamic: {created: [Function: created],mounted: [Function: mounted],beforeUpdate: [Function: beforeUpdate],updated: [Function: updated]},vModelRadio: {created: [Function: created],beforeUpdate: [Function: beforeUpdate]},vModelSelect: {deep: true,created: [Function: created],mounted: [Function: mounted],beforeUpdate: [Function: beforeUpdate],updated: [Function: updated]},vModelText: {created: [Function: created],mounted: [Function: mounted],beforeUpdate: [Function: beforeUpdate]},vShow: {beforeMount: [Function: beforeMount],mounted: [Function: mounted],updated: [Function: updated],beforeUnmount: [Function: beforeUnmount]},withKeys: [Function: withKeys],withModifiers: [Function: withModifiers],compile: [Function: compileToFunction]
}

通过本案例的开发,我们完全理解了npm依赖包的加载机制以及import 或require时项目到底如何执行的。

2.4 bin属性的介绍

我们发现在使用npm安装依赖时,并不是所有依赖都是需要import到我们的项目中才能运行的依赖,比如nrm包就是当项目安装后,会在全局命令行工具中默认可以使用nrm指令来操作镜像地址。这个能力取决于package.json的bin属性。

接下来继续通过实践的方式学习如何创建带有命令行工具的项目。

  1. 在编辑器中创建名为demo的项目

  2. 在项目的根目录中创建bin文件夹并在其中创建index.js文件

  3. 在index.js文件中编写如下命令

    //#所声明的部分代表通过node命令行工具执行该文件
    #!/usr/bin/env node
    console.log('hello demo')
    
  4. 执行npm init -y命令初始化package.json

  5. 在demo根目录下创建一个空的index.js文件(此步骤为支持打包构建)

  6. 检查package.json文件会发现内部多出一个属性,将该属性的值改为bin/index.js

    {"name": "demo","version": "1.0.0","description": "","main": "index.js","bin": { //bin代表可执行的命令行指令所调用的js文件映射规则,安装此项目后便可以直接通过命令行工具中使用demo指令"demo": "bin/index.js"},"scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"keywords": [],"author": "","license": "ISC"
    }
  7. 接下来要思考的就是如何让命令行工具中可以直接运行demo指令,这里不需要通过node命令在本地测试index.js因为这种测试毫无意义

  8. 所以下一步需要做的就是将我们当前的项目变成一个真正的依赖包,这里便需要一个指令npm pack

  9. 在命令行工具中执行该指令

    zhangyunpeng@zhangyunpengdeMacBook-Pro demo % npm pack
    npm notice
    npm notice 												

    如果当面试官问你懂不懂npm,你知道怎么说吗?相关推荐

    1. 面试官问你斐波那契数列的时候不要高兴得太早 搞懂C语言函数指针 搜索引擎还可以这么玩? 那些相见恨晚的搜索技巧...

      面试官问你斐波那契数列的时候不要高兴得太早 前言 假如面试官让你编写求斐波那契数列的代码时,是不是心中暗喜?不就是递归么,早就会了.如果真这么想,那就危险了. 递归求斐波那契数列 递归,在数学与计算机 ...

    2. 面试官问:对我们公司,你还有想了解的吗?99%的面试人都不懂这样面试

      有一句话说:面试就像谈恋爱,只有彼此对上眼,情感产生共鸣时,方可成交! 今天就来和大家聊一聊面试中我们回答完面试官的问题后,突然来一句:你对我们公司还有什么想了解的吗?大部分面试者是不是经常被面试官提 ...

    3. 大叔手记(10):别再让面试官问你单例

      大叔手记(10):别再让面试官问你单例(暨6种实现方式让你堵住面试官的嘴) ... 2012-2-19 09:03| 发布者: benben| 查看: 283| 评论: 0 摘要: 引子经常从Recr ...

    4. 被面试官问懵:TCP 四次挥手收到乱序的 FIN 包会如何处理?

      摘要:收到个读者的问题,他在面试的时候,被搞懵了,因为面试官问了他这么一个网络问题. 本文分享自华为云社区<TCP 四次挥手收到乱序的 FIN 包会如何处理?>,作者:小林coding . ...

    5. 面试官问了四个问题,总结了4个经验

      目录 前言 面试过程 面试总结 面试重点总结和套路 结语 前言 个人是去年年底零基础转行,两三千培训费学出来,学完后也是稀里糊涂,仅是知道功能测试就是找问题,其他接口,性能,数据库,python基础, ...

    6. 面试官问“为什么应聘这个岗位”,应该如何回答?

      面试的时候,我们经常会被问到一个问题:为什么要来应聘这个职位? 这是很多同学,不管是在校招还是社招里面都会碰到的一个场景.出现这种问题,一般来说有两种情况. 一种是大学专业,或之前的实习经验.工作经验 ...

    7. 面试官问:Integer 如何实现节约内存和提升性能的?

      点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:Byte_Liu 来源:https://urlify.cn/ ...

    8. 面试官问我:一个 TCP 连接可以发多少个 HTTP 请求?我竟然回答不上来...

      点击上方"方志朋",选择"设为星标" 做积极的人,而不是积极废人 作者 | 松若章 来源 | https://zhuanlan.zhihu.com/p/6142 ...

    9. 面试官问:JS的继承

      原文作者若川,掘金链接:https://juejin.im/post/5c433e216fb9a049c15f841b 写于2019年2月20日,现在发到公众号声明原创,之前被<前端大全> ...

    最新文章

    1. 相对熵/KL散度(Kullback–Leibler divergence,KLD)
    2. RealSync异构热容灾解决方案
    3. python3多线程第三方库_Python之多线程爬虫抓取网页图片的示例代码
    4. 对typedef void (*sighandler_t)(int)的理解(声明了一种类型:sighandler_t)
    5. 智慧城轨信息技术架构及信息安全规范_在深圳,我们打造智慧地铁的“最强大脑”...
    6. Spring Cloud Alibaba 简介
    7. deeplearning4j – 分布式DL开源项目
    8. python pygame鼠标点击_Python中pygame的mouse鼠标事件用法实例
    9. sql server锁异常_SQL Server中异常处理的背景
    10. 自然数从1到n之间,有多少个数字含有1
    11. 怎么捡自己空投_绝地求生:如何用纸皮自制空投?只需2个道具即可完成,附带教程...
    12. 常见单片机芯片分析简介
    13. AutoCAD .NET 二次开发实例(2) 批量统计指定图层线段长度
    14. 廊坊金彩教育:怎么优化标题
    15. UVC之MJPEG流
    16. 我要你觉得,我不要我觉得--根据企业现状实施DevOps
    17. RAC的并发操作与分布式锁DLM
    18. 基础算法练习:杨辉三角形
    19. 如何将电子签名透明化处理
    20. python对excel添加新的一行_python 实现在Excel末尾增加新行

    热门文章

    1. Charles 配置 https
    2. 微信小程序添加开发者、赋予权限、添加体验者
    3. vue将毫秒为单位的时间转化成分钟和秒
    4. 【Java进阶营】Java是什么?Java的特点有哪些?
    5. if,while选择结构和while,dowhile,for循环的使用
    6. docker安装wnameless/oracle-xe-11g并运行(手写超详细)
    7. 蓝牙耳机啥牌子好?口碑好、音质好的蓝牙耳机推荐
    8. winU盘装Linux win32,用win32diskimager制作ubuntu U盘安装盘
    9. Scala自定义MEID效验工具类
    10. 火狐浏览器打不开淘宝首页的解决办法