前端进阶 - npm基本操作

834 阅读6分钟

初始化

在使用npm之前,需要先对当前的目录进行初始化:

npm init

可以先运行npm init -h对init的参数做更详细的了解:

% npm init -h
npm init

Usage:
npm init [--force|-f|--yes|-y|--scope]
npm init <@scope> (same as `npx <@scope>/create`)
npm init [<@scope>/]<name> (same as `npx [<@scope>/]create-<name>`)

aliases: create, innit

Run "npm help init" for more info

这里面提示npm help init可以看到更多的操作指南,就不赘述了。

直接运行npm init的时候,会有一步一步的提示,让开发者输入相应的信息。之后在当前目录就会产生我们熟悉的package.json文件了。

如果不想这么麻烦,可以直接npm init -y来跳过输入步骤。

建议在进行npm初始化之前,先对目录做git初始化,这样的好处是,在npm init时,repo项会自动填充为gitorigin源。

// 初始化git
git init
git remote add origin git://xxxxx

总结一下:

  • 在使用npm之前,应当使用npm init对目录进行初始化,创建package.json文件。
  • 使用npm init -y来跳过初始化交互过程。
  • 建议先进行git init,并配置好origin源。

包管理

源配置

查看当前源设置

npm config get registry

配置新的源

npm config set registry https://registry.npm.taobao.org

指定源安装

npm --registry https://registry.npm.taobao.org install <package-name>

另,发布源默认和安装源一致,我有印象发布源可以单独配置,但用处不大,先不说了。

安装npm包

npm install <package-name>

也可以简写为

npm i <package-name>

同时安装多个包

npm i <package1> <package2> <package3> ...

依赖层级

包有两个依赖层级:项目依赖开发依赖

项目依赖的意思是,当前项目发布运行需要该包支持,那么在使用webpack之类进行打包时,该包应当被打入到最后的package中。比如react redux antd之类。此类包会标记在package.json中的dependencies字段。

开发依赖中的包,当前项目对其不应有运行依赖,一般都是围绕项目编译、打包、集成等做辅助工作的周边工具。比如webpack以及各种babel插件。此类包会标记在package.json中的devDependencies字段。

当然,在项目打包期间,打包工具会做treeshake动作来晃掉未使用的包,但维护一个严谨的package依赖关系,应当是前端在管理项目时要重点关注的事情,更何况treeshake也是有局限的。

# 安装项目依赖
npm install <package-name>
npm install <package-name> --save
npm install <package-name> -S
# 安装开发依赖
npm install <package-name> --save-dev
npm install <package-name> -D

一个真实的package.json代码:

{
  ...
  "dependencies": {
    "@babel/core": "^7.14.6",
    "@babel/plugin-transform-react-jsx": "^7.14.5",
    "chalk": "^2.4.2",
    "commander": "^7.2.0",
    "md5": "^2.3.0",
    "watch": "^1.0.2"
  },
  "devDependencies": {
    "@types/babel__core": "^7.1.14",
    "@types/md5": "^2.3.0",
    "@types/watch": "^1.0.1",
    "ts-node": "^10.0.0",
    "typescript": "^4.3.4"
  }
}

卸载包

# 卸载项目依赖
npm uninstall -S <package-name>
# 卸载开发依赖
npm uninstall -D <package-name>
# 卸载全局包
npm uninstall -g <package-name>

全局包

就像webpack-cli或者create-react-app之类的工具,当npm安装其为全局时,这些包可以被当成一个工具,在bash命令中直接运行。

# 安装全局包
npm install -g <package-name>
# 卸载全局包
npm uninstall -g <package-name>

安装全局包有几个地方需要注意:

  1. 安装位置是否在环境变量中做了配置?否则会无法执行。

  2. 一些类似nvm的工具,会对-g的包按node版本进行安装,切换node版本之后可能会找不到该包,或者出现问题1的情况。

  3. Mac系统中,如果是从官网下载dmg包安装,在安装-g时需要sudo,同样uninstall也需要sudo。建议使用Homebrew安装node

  4. webpack ts-node tsc 这些工具,可安装为项目的开发依赖,通过npm运行,不必安装为-g。这样做的好处是,项目与开发环境解耦。下面是在开发依赖中配置ts编译器、在scripts.build中对ts文件进行编译的例子:

    {
      "scripts": {
        "build": "tsc ./src/*.ts --esModuleInterop -d -t ES5 --lib es5 --outDir dist"
      },
      "devDependencies": {
        "typescript": "^4.3.4"
      }
    }
    

package-lock.json

用来锁定安装包的版本号。

这个文件有可能造成一些问题,比如锁定的低版本已经从npm源下线了。

所以当对版本没有强要求,或者是对包作者足够信赖时,我一般会把这个文件放入.gitignore。而需要锁定的版本,直接在package.json中写死。

关于版本号

版本号中^字符,经测试是不会跳大版本的。

比如npm i webpack@^3,只会安装webpack3的最新版本,并不会安装webpack4甚至webpack5。

如果想安装最新版本,使用@latest版本号

npm i <package-name>@latest

编写指令脚本

指令 + bash

package.jsonscripts中,配置npm指令和对应的运行脚本。

默认有个test指令:

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
}

我们可以试一下:

% npm run test

> testnpm@1.0.0 test
> echo "Error: no test specified" && exit 1

Error: no test specified
npm ERR! code 1
npm ERR! path /Users/kema/testnpm
npm ERR! command failed
npm ERR! command sh -c echo "Error: no test specified" && exit 1

实际上,是在bash中执行了

echo "Error: no test specified" && exit 1

其中&&是用顺序执行的方式来连接两个bash命令echoexit

注意最后的exit 1,也正是这个1,引发了后面的npm ERR! code 1一系列报错堆栈。

我们改一下:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 0"
}

改成exit 0之后,npm报错就木有了:

% npm run test    

> testnpm@1.0.0 test
> echo "Error: no test specified" && exit 0

Error: no test specified

所以npm run就是在运行bash命令。

指令 + node

我们写一个build.js文件:

console.log('build')

scripts中添加build指令:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 0",
  "build": "node build.js"
}
% npm run build   

> testnpm@1.0.0 build
> node build.js

build

这样,我们在build.js中随心所欲写代码,然后用npm来执行。无论是build.js的业务逻辑更改,还是在scripts.build中把js文件直接更换,我们的npm run build这个命令是保持不变的。这对集成、运维等等都有很重要的意义。

前置和后置指令:pre | post

build指令为例,

  • prebuild运行于build之前
  • postbuild运行于build之后

我们对scripts做以下更改:

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 0",
    "build": "node build.js",
    "prebuild": "echo \"Starting...\"",
    "postbuild": "echo \"Done!\""
}
% npm run build   

> testnpm@1.0.0 prebuild
> echo "Starting..."

Starting...

> testnpm@1.0.0 build
> node build.js

build

> testnpm@1.0.0 postbuild
> echo "Done!"

Done!

可以看到,prebuild build postbuild 按序先后执行。

npm工作流

我们可以通过pre/post指令特性,来构造工作流:

"scripts": {
    "pretest": "echo \"preparing for testing...\"",
    "test": "echo \"testing...\" && exit 0",
    "posttest": "test done!",
    "build": "node build.js",
    "prebuild": "npm run test",
    "postbuild": "echo \"Done!\""
}
% npm run build   

> testnpm@1.0.0 prebuild
> npm run test


> testnpm@1.0.0 pretest
> echo "preparing for testing..."

preparing for testing...

> testnpm@1.0.0 test
> echo "testing..." && exit 0

testing...

> testnpm@1.0.0 posttest
> test done!


> testnpm@1.0.0 build
> node build.js

build

> testnpm@1.0.0 postbuild
> echo "Done!"

Done!

上面的流程是:

  1. npm run build
  2. prebuild指令运行npm run test
  3. pretest指令运行
  4. test指令运行
  5. posttest指令运行,此时prebuild指令运行完毕。
  6. build指令运行
  7. postbuild指令运行

我们制造一点指令错误,看下工作流运行情况:

"test": "echo \"testing...\" && exit 1"

我们让test指令exit 1,引发test指令报错,模拟单元测试没通过则终止构建的工作流。

% npm run build   

> testnpm@1.0.0 prebuild
> npm run test


> testnpm@1.0.0 pretest
> echo "preparing for testing..."

preparing for testing...

> testnpm@1.0.0 test
> echo "testing..." && exit 1

testing...
npm ERR! code 1

可以看到,在运行到test指令时,工作流中断退出,符合预期。