我的 React Native 技能树点亮计划 の Javascript 模块管理器 npm

Posted ACE1985

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的 React Native 技能树点亮计划 の Javascript 模块管理器 npm相关的知识,希望对你有一定的参考价值。

@author ASCE1885的 Github 简书 微博 CSDN 知乎
本文由于潜在的商业目的,不开放全文转载许可,谢谢!

image_1akdbc4t86e41r17s2518obtgb13.png-1421.1kB

npm,全称是 node package manager,顾名思义最开始是作为 Node 的包管理器存在的。不过经过不断的发展和壮大,现在的 npm 早就不再局限于 Node 的范畴,已经成为 javascript 的包管理器,看看下面的 npm 官网首页介绍就知道了:

image_1ak5a6lts1i7t1taee9t12djb599.png-313.4kB

本系列教程假设你已经按照 React Native 官方入门指南安装好相关的环境(尤其是 Node.js),并且执行 react-native init AwesomeProject 命令生成了 Demo 工程 AwesomeProject,工程目录结构如下图所示:

image_1ak5eb3u0h5u1gandlpjle1e1v9.png-151.2kB

package.json 的文件结构

从上图中可以看到,AwesomeProject 工程的根目录有一个名为 package.json 的文件,它是工程的元数据集,主要作用有:

  • 管理项目中依赖的第三方包,可以很方便的和团队中其他开发者共享工程的依赖配置,这样不需要每个人都手动 npm install 相应的依赖包
  • 定义 npm 中可以执行的脚本

一个合格的 package.json 文件需要至少包含 nameversion 两个字段,这两个字段组成的二元组可以唯一标识一个包,如下所示:

{
  "name": "AwesomeProject",
  "version": "0.0.1"
}

当然一般情况下,工程的 package.json 不可能这么简单,常用的字段和解释如下表所示,开发者需要根据具体的业务需求进行选择:

字段名含义示例
name包名需要具备唯一性,而且字母必须全部小写,如果一个包缺少这个字段,使用 npm install 将会失败“name”: “redux”
version包的版本号,遵循语义化版本(http://semver.org/lang/zh-CN/)格式,也就是版本号包含三位:MAJOR.MINOR.PATCHMAJOR 表示版本发生大的变化,例如 API 不兼容旧版本;MINOR 表示版本增加新功能,但是兼容旧版本的;PATCH 表示兼容旧版本的一些 bug 修复“version”: “3.5.2”
description项目的描述,尽量保持言简意赅“description”: “Predictable state container for JavaScript apps”
author项目的作者名字和邮件地址,如果有多个,以 JSON 数组形式表示“authors”: [“Dan Abramov (https://github.com/gaearon)”,”Andrew Clark acdlite@me.com(https://github.com/acdlite)”]
contributors项目的贡献者名单,以 JSON 数组的形式表示“contributors”: [{“name”: “asce1885”,”email”: “asce1885@gmail.com”}]
bin项目对外暴露的 CLI 接口,提供给其他项目使用的脚本“bin”: {“module-name”:”./bin/module-name”}
scripts定义 npm 脚本命令,key 值表示命令名,value 值表示命令对应的脚本或者脚本的路径,通过 npm run 或者 npm run-script 可以执行对应的命令“scripts”: { “clean”: “rimraf lib dist es coverage”, “lint”: “eslint src test examples build”, “start”: “node node_modules/react-native/local-cli/cli.js start”
main工程生成的 Package 的主入口点,当在 node 中调用 require('{module name}') 时会 require 到这个文件“main”: “lib/index.js”
repostitory如果我们这个工程是开源的,这个字段用来指明工程的仓库 URL 地址以及版本控制系统的类型,这可以方便其他开发者贡献代码“repository”: { “type”: “git”, “url”: “https://github.com/reactjs/redux.git” }
bugs使用者可以提交bugs的 URL 或者邮件地址“bugs”: {“url”: “https://github.com/reactjs/redux/issues“}
keywords描述这个 Package 的关键字信息,方便用户通过关键字搜索到这个 Package“keywords”: [“redux”,”reducer”,”state”,”predictable”,”functional”,”immutable”,”hot”,”live”,”replay”,”flux”,”elm”]
dependencies这个 Package 的生产依赖,当用户安装你的 Package 时会自动安装这些依赖“dependencies”: { “react”: “^15.1.0”, “react-native”: “^0.27.0-rc2” }
devDependencies这个 Package 在开发或者测试阶段的依赖,不会打包到最终的生产包中“devDependencies”: { “babel-eslint”: “^5.0.0”, “eslint”: “^2.1.0”, “eslint-plugin-react”: “^3.16.1” }
preferGlobal表明这个 Package 希望通过 npm install -g {module-name} 全局安装,这个字段是给包含了 CLI 的 Package 使用, 其他情况下不要使用这个字段“preferGlobal”: true
private设置为 true 时,npm 将不会发布这个 Package,这个标记主要用来防止不小心发布某个内部使用的私有 Package 到公共的 npm registry“private”: true
publishConfig发布这个 Package 时用到的一些配置信息,这些配置信息会覆盖默认的 npm 配置“publishConfig”: { “registry”: “https://your-private-hosted-npm.registry.nodejitsu.com” }
subdomain指明应用的 subdomain,说明应该只包含 subdomain,而不是 root domain
analyze如果你的 Package 托管在 Nodejitsu(https://www.nodejitsu.com/) 上面,同时将这个字段设置为 true,Nodejitsu 将会自动尝试扫描你的 Package,可以及时发现缺少的依赖,可能存在的 bugs 以及语法错误等“analyze”: true
license如果这个 Package 是开源的,此处指定它遵循的许可协议“license”: “MIT”

一个真实项目的 Package.json 文件内容如下所示(取自 redux-logger1

{
  "name": "redux-logger",
  "version": "2.6.1",
  "description": "Logger for Redux",
  "main": "lib/index.js",
  "scripts": {
    "lint": "$(npm bin)/eslint src",
    "test": "npm run lint",
    "clean": "$(npm bin)/rimraf dist lib",
    "build:lib": "$(npm bin)/babel src --out-dir lib",
    "build:umd": "LIBRARY_NAME=reduxLogger NODE_ENV=development $(npm bin)/webpack src/index.js dist/index.js --config webpack.build.js",
    "build:umd:min": "LIBRARY_NAME=reduxLogger NODE_ENV=production $(npm bin)/webpack -p src/index.js dist/index.min.js --config webpack.build.js",
    "build": "npm run build:lib && npm run build:umd && npm run build:umd:min",
    "prepublish": "npm run clean && npm run test && npm run build"
  },
  "files": [
    "dist",
    "lib",
    "src"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/theaqua/redux-logger.git"
  },
  "keywords": [
    "redux",
    "logger",
    "redux-logger",
    "redux",
    "middleware"
  ],
  "author": "Eugene Rodionov (https://github.com/theaqua)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/theaqua/redux-logger/issues"
  },
  "homepage": "https://github.com/theaqua/redux-logger#readme",
  "devDependencies": {
    "@dtrussia/eslint-config-dtrussia": "2.2.1",
    "babel-cli": "6.3.13",
    "babel-core": "6.3.13",
    "babel-eslint": "6.0.4",
    "babel-loader": "6.2.0",
    "babel-plugin-add-module-exports": "0.1.1",
    "babel-preset-es2015": "6.3.13",
    "babel-preset-react": "6.3.13",
    "babel-preset-stage-0": "6.3.13",
    "eslint": "2.10.2",
    "eslint-plugin-react": "5.1.1",
    "rimraf": "2.4.4",
    "webpack": "1.12.9"
  },
  "dependencies": {
    "deep-diff": "0.3.4"
  }
}

npm 的模块管理

熟悉 npm 的常用命令,往往能够使得你的工作事半功倍。首先我们来介绍最常用的 npm install 命令,它是用来将依赖的模块安装到 node_modules 目录中,依赖分为两种:生产环境的依赖和开发环境的依赖,这个在前面一节已经介绍过了,对应的命令分别如下所示:

npm install redux // 生产环境的依赖
npm install redux -dev // 开发环境的依赖

在安装之前,npm 会先检查 node_modules 目录中是否已经存在指定的模块,如果存在,则不会重新安装,即使这个模块已经有新版本。当然,我们可以通过增加 -f 或者 --force 参数来强制重新安装最新版本。上面的命令安装完成后,我们可以在 node_modules 目录中找到 redux 的包,但这时 package.json 文件内容并没有发生变化,为了在发布我们这个包给其他开发者使用时,他们可以自动安装这些依赖,我们需要将依赖写入 package.json 文件中,当然你可以选择手动写入,但更方便的方法是在 npm install 时增加 --save 参数,如下所示:

npm install redux --save // 生产环境的依赖
npm install redux --save-dev // 开发环境的依赖

这时 npm 会自动帮我们写入 package.json 文件,如下所示:

{
  ...
  "dependencies": {
    ...
    "redux": "^3.5.2"
  },
  "devDependencies": {
    "eslint": "^2.11.1"
  }
}

有了依赖的安装,当然也有卸载的命令,很简单就是 npm uninstall,后面参数是需要卸载的包名,例如 npm uninstall redux

前面使用 React Native 提供的 react-native init AwesomeProject 命令生成的 Demo 工程已经自动帮我们生成了 package.json 文件,如果我们自己手动建立一个 React Native 的工程,那么可以选择从其他工程拷贝现成的 package.json 并进行修改,当正确的做法是使用 npm init 命令来生成它。在 Terminal 中输入 npm init,npm 将会一步一步引导我们输入一些关键的字段,最终生成的文件内容如下所示,从中看到的字段几乎是每个工程必备的:

{
  "name": "asce1885",
  "version": "1.0.0",
  "description": "One Piece",
  "main": "index.js",
  "scripts": {
    "test": "op"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/PaicHyperionDev/MobileDevWeekly.git"
  },
  "keywords": [
    "mobile",
    "dev"
  ],
  "author": "asce1885",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/PaicHyperionDev/MobileDevWeekly/issues"
  },
  "homepage": "https://github.com/PaicHyperionDev/MobileDevWeekly#readme"
}

当某个第三方依赖库需要发布新版本,我们项目也需要跟着升级时,可以通过 npm update 命令对指定的 Package 进行升级,例如 npm update redux。同时,我们可以通过执行命令 npm outdated 来查询当前安装的所有 npm 包中是否有存在新版本的。

一般来说,掌握上面几个命令,对开发 React Native 来说就已经足够了,对于不熟悉的命令,我们可以通过 npm help 来查询对应命令的用法,例如输入 npm help registry,会得到如下结果:

image_1akd8q4b8nr8124l1jli1q3sj4im.png-221.9kB

npm scripts

上一节我们介绍了 npm 的模块管理功能,事实上,npm 另外一个高频使用的功能就是用来执行脚本。我们在 package.json 文件的 scripts 字段中定义的脚本可以通过 npm run 或者 npm run-script 执行,例如在 React Native 工程中,我们可以定义如下脚本,分别用来创建,启动 android 模拟器,启动 node 服务和打包等:

"scripts": {
    "avd:create": "android create avd -t 1 -n MuchVote -d 9 -b x86_64 -s 1440x2560",
    "avd:start": "emulator -avd MuchVote -gpu on -dpi-device 560 -scale ${SCALE:-0.25}",
    "adb:reverse": "adb reverse tcp:8081 tcp:8081",
    "android": "npm run adb:reverse && node node_modules/react-native/local-cli/cli.js run-android",
    "start": "npm run adb:reverse && node_modules/react-native/packager/packager.sh",
  }

定义完成之后,就可以在 React Native 目录中像下面这样执行对应的命令:

npm run adb:reverse
npm run android
...

为了方便脚本的执行,npm 默认定义了一些命令的快捷键,例如 npm testnpm startnpm stop 等等,也就是说执行这些命令时,我们省去了 run 的输入。之所以定义这些快捷键,除了可以节省执行命令的时间,更重要的一点是这些命令的命名是通用的约定,很多持续构建平台例如 Travis 会默认为 Node.js 工程添加 npm test 命令;同时,这些通用的命令定义也方便其他开发者使用你的包。

npm 为每一条命令都提供了 pre--post 这两个钩子,分别表示在命令执行前和执行后会执行对应的钩子命令。例如下面的脚本,当用户执行 npm run test 命令时,事实上 npm 会先执行 pretest 命令,然后才执行 test 命令。

"scripts": {
    "eslint": "eslint --rulesdir **",
    "test": "mocha test/",

    "pretest": "npm run eslint"
  }

当一个命令比较复杂时,我们还可以将这个命令定义在一个单独的 js 文件中,然后通过 node 来执行,如下所示:

"scripts": {
    "build": "node build.js"
}

本系列关于 npm 的介绍就到这里,基本上对开发 React Native 已经足够用了,如果你不满足于这些基础知识,可以查阅拓展阅读部分的文档,进一步学习。

拓展阅读

《npm 官方文档》2
《我为何放弃 Gulp 与 Grunt,转投 npm scripts》上345
《Introduction to Using NPM as a Build Tool》6
《How to Use npm as a Build Tool》7
《package.json》8
《玩转 npm》9
《玩转 npm》10
《npm 模块安装机制简介》11

欢迎关注我的微信公众号,专注与原创或者分享 Android,ios,ReactNative,Web 前端移动开发领域高质量文章,主要包括业界最新动态,前沿技术趋势,开源函数库与工具等。