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

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

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

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

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" },"url": "git+https://github.com/PaicHyperionDev/MobileDevWeekly.git" },"keywords": [ "mobile","dev" ],"author": "asce1885","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,会得到如下结果:

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom