前言
- 在每个项目的根目录下面,一般都会有一个
package.json
文件,其定义了运行项目所需要的各种依赖
和项目的配置信息
(如名称、版本、许可证等元数据)。 大多数人对package.json
文件的了解,仅停留在: - 项目名称、项目构建版本、许可证的定义;
依赖定义(包括
dependencies
字段,devDependencies
字段); 使用scripts
字段指定运行脚本命令的npm
命令行缩写。 - 其实,
package.json
的作用远不止于此,我们可以通过新增配置项实现更强大的功能,下面将带你重新认识package.json
。
由简入繁,丰富项目的 package.json
简单版的 package.json
- 当我们新建一个名称为
my-test
的项目时,使用yarn init -y
或npm init -y
命令后,在项目目录下会新增一个package.json
文件,内容如下:
{
"name": "my-test", # 项目名称
"version": "1.0.0", # 项目版本(格式:大版本.次要版本.小版本)
"description": "", # 项目描述
"main": "index.js", # 入口文件
"scripts": { # 指定运行脚本命令的 npm 命令行缩写
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [], # 关键词
"author": "", # 作者
"license": "ISC" # 许可证
}
- 可以看到,
package.json
文件的内容是一个JSON
对象,对象的每一个成员就是当前项目的一项配置。
安装项目依赖(dependencies & devDependencies)
dependencies
字段指定了项目运行所依赖的模块(生产环境使用),如antd
、react
、moment
等插件库: 它们是我们生产环境所需要的依赖项,在把项目作为一个npm
包的时候,用户安装npm
包时只会安装dependencies
里面的依赖。devDependencies
字段指定了项目开发所需要的模块(开发环境使用),如webpack
、typescript
、babel
等: 在代码打包提交线上时,我们并不需要这些工具,所以我们将它放入devDependencies
中。- 如果一个模块不在
package.json
文件之中,我们可以单独安装这个模块,并使用相应的参数,将其写入dependencies
字段/devDependencies
字段中:
# 使用 npm
npm install <package...> --save # 写入 dependencies 属性
npm install <package...> --save-dev # 写入 devDependencies 属性
# 使用 yarn
yarn add <package...> # 写入 dependencies 属性
yarn add <package...> --dev # 写入 devDependencies 属性
- 有了
package.json
文件,开发直接使用npm install
/yarn install
命令,就会在当前目录中自动安装所需要的模块,安装完成项目所需的运行和开发环境就配置好了。
定义项目入口(main)
main
字段是package.json
中的另一种元数据功能,它可以用来指定加载的入口文件。假如你的项目是一个npm
包,当用户安装你的包后,require('my-module')
返回的是main
字段中所列出文件的module.exports
属性。- 当不指定
main
字段时,默认值是模块根目录下面的index.js
文件。
指定项目 node 版本(engines)
- 有时候,新拉一个项目的时候,由于和其他开发使用的
node
版本不同,导致会出现很多奇奇怪怪的问题(如某些依赖安装报错、依赖安装完项目跑步起来等)。 - 为了实现项目开箱即用的伟大理想,这时候可以使用
package.json
的engines
字段来指定项目 node 版本:
"engines": {
"node": ">= 8.16.0"
},
- 该字段也可以指定适用的
npm
版本:
"engines": {
"npm": ">= 6.9.0"
},
- 需要注意的是,engines属性仅起到一个说明的作用,当用户版本不符合指定值时也不影响依赖的安装。
自定义命令(bin)
- 用过
vue-cli
,create-react-app
等脚手架的朋友们,不知道你们有没有好奇过,为什么安装这些脚手架后,就可以使用类似vue create
/create-react-app
之类的命令,其实这和package.json
中的bin
字段有关。 bin
字段用来指定各个内部命令对应的可执行文件的位置。当package.json
提供了bin
字段后,即相当于做了一个命令名和本地文件名的映射。- 当用户安装带有
bin
字段的包时, 如果是全局安装,npm
将会使用符号链接把这些文件链接到/usr/local/node_modules/.bin/
; 如果是本地安装,会链接到./node_modules/.bin/
。 - 举个 🌰,如果要使用
my-app-cli
作为命令时,可以配置以下bin
字段:
"bin": {
"my-app-cli": "./bin/cli.js"
}
- 上面代码指定,
my-app-cli
命令对应的可执行文件为bin
子目录下的cli.js
,因此在安装了my-app-cli
包的项目中,就可以很方便地利用npm
执行脚本:
"scripts": {
start: 'node node_modules/.bin/my-app-cli'
}
- 咦,怎么看起来和
vue create
/create-react-app
之类的命令不太像?原因: 当需要node
环境时就需要加上node
前缀 如果加上node
前缀,就需要指定my-app-cli
的路径 ->node_modules/.bin
,否则node my-app-cli
会去查找当前路径下的my-app-cli.js
,这样肯定是不对。 - 若要实现像
vue create
/create-react-app
之类的命令一样简便的方式,则可以在上文提到的bin
子目录下可执行文件cli.js
中的第一行写入以下命令:
#!/usr/bin/env node
- 这行命令的作用是告诉系统用
node
解析,这样命令就可以简写成my-app-cli
了。
React 项目相关
设置应用根路径(homepage)
-
当我们使用
create-react-app
脚手架搭建的React
项目,默认是使用内置的webpack
配置,当package.json
中不配置homepage
属性时,build 打包之后的文件资源应用路径默认是 /,如下图: -
一般来说,我们打包的静态资源会部署在
CDN
上,为了让我们的应用知道去哪里加载资源,则需要我们设置一个根路径,这时可以通过package.json
中的homepage
字段设置应用的根路径。 当我们设置了homepage
属性后:
{
"homepage": "https://xxxx.cdn/my-project",
}
- 打包后的资源路径就会加上
homepage
的地址:
开发环境解决跨域问题(proxy)
- 在做前后端分离的项目的时候,调用接口时则会遇到跨域的问题,当在开发环境中时,可以通过配置
package.json
中的proxy
来解决跨域问题,配置如下:
{
"proxy": "http://localhost:4000" // 配置你要请求的服务器地址
}
- 注意,当
create-react-app
的版本高于 2.0 版本的时候在package.json
中只能配置string
类型,这意味着如果要使用package.json
来解决跨域问题,则只能代理一个服务器地址。 - 如果要代理多个服务器地址时,则需要安装
http-proxy-middleware
,在src
目录下新建setupProxy.js
:
const proxy = require("http-proxy-middleware");
module.exports = function(app) {
app.use(
proxy("/base", {
target: "http://localhost:4000",
changeOrigin: true
})
);
app.use(
proxy("/fans", {
target: "http://localhost:5000",
changeOrigin: true
})
);
};
根据开发环境采用不同的全局变量值(自定义字段)
- 假设有这么一个组件,当组件被点击时,在开发环境时是跳转测试环境的
sentry
地址,在正式环境时则跳转正式环境的sentry
地址。 - 首先,通过配置前面提到的
scripts
字段,实现环境变量(NODE_ENV)的设置:
"scripts": {
"start": "NODE_ENV=development node scripts/start.js",
"build": "NODE_ENV=production node scripts/build.js",
},
- 项目启动起来后,在代码中我们可以通过
process.env.NODE_ENV
访问到NODE_ENV
的值。
方案一
- 我们可以在组件中写类似以下的判断代码,根据不同环境给
sentryUrl
设置不同的值:
let sentryUrl;
if (process.env.NODE_ENV === 'development') {
sentryUrl = 'test-sentry.xxx.com';
} else {
sentryUrl = 'sentry.xxx.com';
}
- 这么做好像没毛病,但是深入一想,如果有多个组件,要根据不同的环境使用不同的服务(多种服务)地址,如果按照上面的写法,项目中将存在许多重复的判断代码,且当服务地址发生变化时,包含这些服务地址的组件都需要相应的做改动,这样明显是不合理的。
方案二
-
解决方案:相关服务的地址配置在
package.json
中,同时修改项目的webpack
配置。 -
注:修改项目的
webpack
配置需要 eject 项目的webpack
配置(更多细节可阅读 👉:react + typescript 项目的定制化过程)。 -
在项目根目录下使用
yarn eject
成功 eject 出配置后,可以发现项目目录的变化如下: -
如果需要定制化项目,一般就是在
config
目录下对默认的webpack
配置进行修改,在这里我们需要关注config/path.js
和config/env.js
两个文件:env.js
的主要目的在于读取env
配置文件并将env
的配置信息给到全局变量process.env
;path.js
的主要目的在于为项目提供各种路径,包括构建路径、public
路径等。 -
由于本文的重点不是学习
webpack
配置,这里仅介绍如何实现【根据开发环境采用不同的全局变量值】的功能。 -
首先,需要在
package.json
中配置以下内容:
"scripts": {
"start": "NODE_ENV=development node scripts/start.js",
"build": "NODE_ENV=production node scripts/build.js",
},
"sentryPath": {
"dev": "https://test-sentry.xxx.com",
"prod": "https://sentry.xxx.com"
}
- 然后,修改
path.js
文件,内容如下:
// 重写 getPublicUrl 方法
const getPublicUrl = (appPackageJson, pathName) => {
let path;
switch (process.env.DEPLOY_ENV) {
case 'development':
path = require(appPackageJson)[pathName].dev;
break;
case 'production':
path = require(appPackageJson)[pathName].prod;
break;
default:
path = envPublicUrl || require(appPackageJson).homepage;
}
return path;
}
// 新增 getSentryPath 方法
const getSentryPath = (appPackageJson) => {
return getPublicUrl(appPackageJson, 'sentryPath');
}
// config after eject: we're in ./config/
module.exports = {
...,
sentryUrl: getSentryPath(resolveApp('package.json')), // 新增
};
- 最后,修改 env.js 文件,内容如下:
// 修改 getClientEnvironment 方法
function getClientEnvironment(publicUrl) {
const raw = Object.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce(
(env, key) => {
...
},
{
NODE_ENV: process.env.NODE_ENV || 'development',
PUBLIC_URL: publicUrl,
SENTRY_URL: paths.sentryUrl // 新增
}
);
const stringified = {
...
};
return { raw, stringified };
}
- 通过上面的配置,我们就可以在组件中通过
process.env.SENTRY_URL
获取到sentry
服务的地址了,虽然看起来比方案一繁琐,但是这种收益是长期的,如要新增一个sonarqube
服务,同理实现即可,通过使用package.json
也可以清楚的看到当前服务在不同环境下使用的地址。