记一次create-react-app脚手架 env环境变量设置逻辑的踩坑

@CoderMing 2018-08-29 14:14:33发表于 CoderMing/blog React

我最近在开发 blogsue,一个基于GitHub issue的博客系统。技术栈是React全家桶,用create-react-app脚手架。
现在有一个需求:我希望能同时支持hashbrowser两种路由模式。而且我希望只在命令行输入不同指令就能进行对应的操作。
很明显,最好的方式就是使用process.argv这个node自带的参数接收器。这样我们就可以很轻松地实现将命令行参数对应进Node环境里。那现在问题就来了: 我们在命令行运行的是build进程,而不是用户打开页面的进程。 那么我们就需要有某种方法,将我们在build过程中传入的参数保留至打包后的bundle中。
幸运的是,我们的create-react-app脚手架就已经做了这个工作。那么接下来我们就先分析下这个功能的代码逻辑:

代码分析

如果你想跟着学习的话,请先eject出配置文件。
首先我们来看一下 npm run build命令的内容,是运行 scripts/build.js这个文件。那我们就先看下这个文件中的逻辑。
可以发现,该文件最开始设置了本次构建的环境变量:

// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production'
process.env.NODE_ENV = 'production'

接下来,你会发现很多构建的工具。比如检测yarn,去除logger,引入webpack等。这些还需要深挖很多。那我们发现与今天探索的主题一致的是require('../config/env')这一句。那我们就去看一下这个文件。(链接在这里
首先看一下exports,发现是一个名为getClientEnvironment的函数。看名字,大概的意思是输出一个环境变量的对象。那我们查看一下引用链,发现webpack.config.dev/prod.js引用了它。去看代码,发现有这么几行:

...
    // Get environment variables to inject into our app.
    const env = getClientEnvironment(publicUrl)
...
    // Makes some environment variables available to the JS code, for example:
    // if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
    new webpack.DefinePlugin(env.stringified)

那我们去查webpack.DefinePlugin,发现这个插件的功能就是往bundle里注入参数。
那这样,我们就知道create-react-app脚手架是如何将build时传入的参数留到bundle里了。总结如下:

  • scripts/build.js中定义process.env这个node环境变量,并引用config/env.js文件。
  • config/env.js中进行一系列核实与校验,最终输出处理后的env环境变量的获取方法。
  • config/webpack.config.prod.js中获取成型的env变量,通过webpack.DefinePlugin这个插件注入到bundle。
    知道这些后,我也得到了这个需求的解决办法:

解决问题

首先我修改scripts/build.js,加入了这一行:

process.env.ROUTE_MODE = process.argv[2] === 'hash' ? 'hash' : 'browser'

可以看到,如果我第二个参数传入的是hash的话,就采用hashRouter。
其次修改config/env.js

function getClientEnvironment(publicUrl) {
  const raw = Object.keys(process.env)
    .filter(key => REACT_APP.test(key))
    .reduce(
      (env, key) => {
        env[key] = process.env[key]
        return env
      },
      {
        ......
        ROUTE_MODE: process.env.ROUTE_MODE || 'hash' // 添加此配置项
      }
    )
  ......

此项功能是让config/env.js在验证配置项时通过ROUTE_MODE属性。
通过这两步,已经可以将数据注入到bundle里了。我只需要在合适的地方使用。对于本项目:

import { BrowserRouter, HashRouter, Route } from 'react-router-dom'
const Router = process.env.ROUTE_MODE !== 'hash' ? BrowserRouter : HashRouter

最后测试可以顺利使用。
额外地,可以在package.json中添加此script字段:

    "build:hash": "npm run build -- hash"

完美解决问题~