create-react-app 里 .env 文件的应用

.env 文件在由 creact-react-app 生成的项目里是一个关于当环境的配置文件(先不去管这个文件的起源),我们可以在这个文件里写一些配置,然后通过某种手段在代码里读取、应用这些配置,比如我们会有开发、staging、生产等环境,每个环境的配置都不尽相同,最基本地,每个环境请求的后端服务器的 url 就不相同,自己 mock 的时候的本机地址、与后端联调时候的局域网的地址,staging 的域名和正式上线环境的域名,之前的做法是一般会在 api 请求相关的目录下有 config.js 这样一个文件,那些配置就当成一个常量写到这个文件里,然后导出供其他需要的模块引用,那么问题就来了,每次切换环境时都需要去更改这些配置变量为对应环境的值,这是一件很繁琐的事情,一般我是

const HOST = 'xxx.xxx.xxx'
// const HOST = 'yyy.yyy.yyy'

在用哪个环境的时候就把另一个环境的赋值语句给注释掉,然后后面如果在需要 build 的时候把多余的注释掉的删掉,当然,删除注释的工作可以由工具来做,但是开发时一堆无关代码的注释躺在那里看起来很恶心,然后我就想能不能不管怎么切换环境,需要放到浏览器上去跑的那段代码永远不用变,而只通过启动时输入不同的命令就可以应用对应的配置,比如 npm run start:mock 表示使用本地的 mock 环境,同时开启本地的 mock 服务;npm run start 表示使用默认的开发环境,使用后端提供的环境,然后 npm run build 表示正式上线环境的编译;npm run build:staging 表示 staging 环境的编译。然后我就找到了 .env 文件。

要实现上面说的那些,我们需要用到 dotenvdotenv-webpack 这两个 npm 包(前提是在通过 create-react-app 创建的项目里,同时还要 run eject,当然其他脚手架生成的项目肯定也可以做到这样,因为凭自己目前的功力还没有办法自己捣鼓出一个完成的项目骨架,所以就先把 create-react-app 研究透)。关于 .env 文件的用法,就不多说了 https://github.com/motdotla/dotenv 里有,然后我看到里面:

Should I have multiple .env files?
No. We strongly recommend against having a "main" .env file and an "environment" .env file like .env.test. Your config should vary between deploys, and you should not be sharing values between environments.

里面是不提倡用多个 .env.*.* 这样的文件的,可是 create-react-app 里则提倡用多个这样的文件,比如 .env.development.local.env.development.env.local……这样的,然后读取这些配置的时候有个优先级,这些代码都写在项目根目录下的 config/env.js 里的,然后在切换环境的时候都会执行一遍这个文件,读取对应的配置,我暂时还没弄懂它读取所有 .env.*.* 文件的用意。但是我目前也没想到如何单凭一个 .env 文件而能实现我想要的东西,所以我也用的多个 .env 文件,但是我稍微改写了env.jsstart.jswebpcakDevServer.jswebpack.config.dev.jswebpack.config.prod.js ,使得一个环境对应特定的 .env 文件,每个环境里的通用配置的字段都一样,然后正式代码里只用读取这些字段就可以了,这些字段会根据不同的环境提供不同的值。其起作用的原理 https://github.com/mrsteele/dotenv-webpack 里有写:

The plugin can be installed with little-to-no configuration needed. Once installed, you can access the variables within your code using process.env as you would with dotenv.

就是说,在编译时,将正式代码里引用了 .env 文件配置的变量直接替换成对应的值,比如 .env 里有 HOST = xxx.xxx.xxx,然后正式的 js 代码里有 const host = process.env.HOST,然后在编译过程中 HOST 这个变量会被替换成 xxx.xxx.xxx,所以编译后的代码就会是 const host = 'xxx.xxx.xxx',相当于对正式 js 代码提供了一个全局变量 process.env.HOST,但是这个变量只在编译时有效。dotenv-webpack 这个插件的用法:

const Dotenv = require('dotenv-webpack');

module.exports = {
  ...
  plugins: [
    new Dotenv()
  ]
  ...
}

用了这个插件后 new webpack.definePlugin({...}) 这条语句实现的功能可一并由 dotenv-webpack 来实现了,只需要在对应 .env 文件里写上 NODE_ENV = developmentNODE_ENV = production 就可以了。此外,new DotEnv() 接受一个配置对象,里面有个 path 字段,值是加载的 .env 文件的路径,这就允许我们根据不同的条件加载不同的 .env.*.* 文件,这是实现我想要的功能的关键(不过这也应该是一个插件必然会提供的)。

下面我以默认开发环境和 mock 环境为例,简单说下大致细节。这二者都算开发环境,但还是可能会在某些时候需要区分。现在我为 mock 环境配一个 .env.development.local 文件,然后当我执行 npm run start:mock 时让 new DotEnv(option) 加载这个文件,而在 npm run start 时加载 .env.develpment 文件。 npm run start 真正执行的是 node ./scripts/start.js,然后这个 start.js 文件里有对 webpcak.config.dev.js 这个文件的引用,需要用到它导出的 webpack 的配置,这是一个静态的对象,所以如果我们在 webpcak.config.dev.js 里将 new DotEnv(option) 里的 option 写死的话,那么我们对于多个 option 就需要导出多个 webpack 配置,所以可以将 webpcak.config.dev.js 的导出做成一个接受一个 option 的函数,函数的返回值是应用了这个 option 而生成的配置对象。然后我们需要一个变量来决定 option 的取值,这个变量需要在 start.js 里出现,然后这个变量显然不能是通过在 start.js 文件里通过 const arg = 'xxx' 来生成而只能通过外部传入,怎么传入呢?很自然想到在命令行里传入,然后再对应的脚本文件里读取 process.argv 来获取。于是:

// package.json

{
  ...
  "start:mock": "node ./scrips/start.js mock"
  ...
}

将 mock 这个字符串传到 start.js 里,然后 start.js 通过判断 process.argv[2] === ‘mock’ 来决定加载哪个 .env 文件。加载其他 .env 的原理一致。

此外,当我们切换到 mock 环境时候,还有一件事情要做,就是开启本地的 mock 服务,当然我们可以在输入 npm run start:mock 之后再又去手动开启 mock 服务,但步也可以放到 npm run start:mock 命令去完成,通过 npm-run-all 这个 npm 模块。
然后,通常 mock 服务是不是也会有个类似的 config.js 文件,但是这里我们既然已经有了 env.development.lcoal 那便不必再费事去多维护一些多余的数据了。通过

const config = require('dotenv').config({
    path: dotEnvFile
  }).parsed

就可以读取一个由 .env 里的配置转换而成的配置对象,然后这就保持正式 js 代码里的配置值和实际 mock 服务的配置永远一致了。

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

推荐阅读更多精彩内容