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

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

image_1akdbc4t86e41r17s2518obtgb13.png-1421.1kB
image_1akdbc4t86e41r17s2518obtgb13.png-1421.1kB

广而告之时间:我的新书《Android 高级进阶》(https://item.jd.com/10821975932.html在京东开始预售了,欢迎订购!

TB2MnqlXH1J.eBjSszcXXbFzVXa_!!1020536390.png-39kB
TB2MnqlXH1J.eBjSszcXXbFzVXa_!!1020536390.png-39kB

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

image_1ak5a6lts1i7t1taee9t12djb599.png-313.4kB
image_1ak5a6lts1i7t1taee9t12djb599.png-313.4kB

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

image_1ak5eb3u0h5u1gandlpjle1e1v9.png-151.2kB
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 dan.abramov@me.com(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-logger[1]

{
  "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
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》上[3][4][5]
《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 前端移动开发领域高质量文章,主要包括业界最新动态,前沿技术趋势,开源函数库与工具等。


  1. https://github.com/evgenyrodionov/redux-logger

  2. https://docs.npmjs.com/

  3. http://www.infoq.com/cn/news/2016/02/gulp-grunt-npm-scripts-part1

  4. http://www.infoq.com/cn/news/2016/02/gulp-grunt-npm-scripts-part2

  5. http://www.infoq.com/cn/news/2016/02/gulp-grunt-npm-scripts-part3

  6. https://medium.com/@dabit3/introduction-to-using-npm-as-a-build-tool-b41076f488b0#.tylyovxdw

  7. http://blog.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/

  8. http://browsenpm.org/package.json#description

  9. http://www.alloyteam.com/2016/03/master-npm/

  10. https://github.com/icepy/_posts/issues/36

  11. http://www.ruanyifeng.com/blog/2016/01/npm-install.html

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

推荐阅读更多精彩内容