从源码看React环境变量那些事儿

// 文件目录结构
|--config
        |--jest
        |--env.js
        ...
        |--paths.js
|--node_modules
|--public
|--scripts
        |--build.js
        |--start.js
        |--test.js
|--src
|--package.json
使用第三方库 dotenv、dotenv-expand
  • dotenv解析文件赋值到process.env,默认解析.env文件。也可添加配置去解析其他文件

    const path = require("path");
    const envFile = path.join(__dirname, ".env.local")
    dotenv({path: envFile});
    
  • Dotenv-expand

    默认dotenv不能解析文件里的变量,必须得是字符串才行,dotenv-expand 扩展了dotenv的功能

    可在目标文件里通过{} 引用变量

    // .env.local
    NODE_ENV = "production"
    VUE_APP_NAME = "vue app"
    
    SUNSET = $REAC_APP_NAME
    
使用规则

React中规定,要使用process.env上的变量,可以在本地新建.env、.env.development、.env.production、.env.local 等文件,在文件里定义以REACT_APP
开头的变量。然后就可以在项目中使用 process.env.xxx等环境变量。

从源码中看环境变量的定义过程
  • 当我们执行npm start 时,在scripts --> start.js文件头部一开始就挂载了两个变量
    BABEL_ENVNODE_ENV

    // Do this as the first thing so that any code reading it knows the right env.
    process.env.BABEL_ENV = 'development';
    process.env.NODE_ENV = 'development';
    
  • 然后进入到config --> env.js文件

    首先这里定义 dotenvFiles ,是一个包含几个文件名的数组,这几个文件名就是上面提到的react规定的几个定义变量的文件(.env 、.env.local 、.env.development ...)
    其中paths.dotenv可以在config --> paths.js`中找到,就是项目根目录下的.env文件,
    其余几种都是基于.env的名字出来的。

    const NODE_ENV = process.env.NODE_ENV;
    if (!NODE_ENV) {
      throw new Error(
        'The NODE_ENV environment variable is required but was not specified.'
      );
    }
    // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
    const dotenvFiles = [
      `${paths.dotenv}.${NODE_ENV}.local`,
      `${paths.dotenv}.${NODE_ENV}`,
      // Don't include `.env.local` for `test` environment
      // since normally you expect tests to produce the same
      // results for everyone
      NODE_ENV !== 'test' && `${paths.dotenv}.local`,
      paths.dotenv,
    ].filter(Boolean);
    

    这里遍历上面的dotenvFiles,并用dotenv,dotenv-expand 分别绑定变量到 process.env上。

    执行完下面这段代码后就可以在process.env上看到自己在.env等文件中定义的变量。
    到目前为止这些变量都只是在node环境中可见,在浏览器中是找不到的

    接下来通过webpack.DefinePlugin把变量注入到浏览器环境。

    // Load environment variables from .env* files. Suppress warnings using silent
    // if this file is missing. dotenv will never modify any environment variables
    // that have already been set.  Variable expansion is supported in .env files.
    // https://github.com/motdotla/dotenv
    // https://github.com/motdotla/dotenv-expand
    dotenvFiles.forEach(dotenvFile => {
      if (fs.existsSync(dotenvFile)) {
        require('dotenv-expand')(
          require('dotenv').config({
            path: dotenvFile,
          })
        );
      }
    });
    
  • 在把变量注入到浏览器环境之前,react还做了一层过滤,在config --> env.js里定义了一个/^REACT_APP_/i正则,这个函数就是只抽取 变量名是 REACT_APP_开头的变量作为客户端环境变量

    const REACT_APP = /^REACT_APP_/i;
    
    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;
          },
          {
            // Useful for determining whether we’re running in production mode.
            // Most importantly, it switches React into the correct mode.
            NODE_ENV: process.env.NODE_ENV || 'development',
            // Useful for resolving the correct path to static assets in `public`.
            // For example, .
            // This should only be used as an escape hatch. Normally you would put
            // images into the `src` and `import` them in code to get their paths.
            PUBLIC_URL: publicUrl,
            // We support configuring the sockjs pathname during development.
            // These settings let a developer run multiple simultaneous projects.
            // They are used as the connection `hostname`, `pathname` and `port`
            // in webpackHotDevClient. They are used as the `sockHost`, `sockPath`
            // and `sockPort` options in webpack-dev-server.
            WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST,
            WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
            WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
          }
        );
      // Stringify all values so we can feed into webpack DefinePlugin
      const stringified = {
        'process.env': Object.keys(raw).reduce((env, key) => {
          env[key] = JSON.stringify(raw[key]);
          return env;
        }, {}),
      };
    
      return { raw, stringified };
    }
    
    module.exports = getClientEnvironment;
    
  • 接下来打开config --> webpack.config.js,在 plugins 配置里

    new webpack.DefinePlugin(env.stringified),
    

    这一步就是把最终过滤出来的变量,注入到客户端环境了。

    最后放上一段webpack的部分文件介绍。

    DefinePlugin

    DefinePlugin 允许创建一个在编译时可以配置的全局常量。这可能会对开发模式和发布模式的构建允许不同的行为非常有用。如果在开发构建中,而不在发布构建中执行日志记录,则可以使用全局常量来决定是否记录日志。这就是 DefinePlugin 的用处,设置它,就可以忘记开发和发布构建的规则。

    new webpack.DefinePlugin({
      // Definitions...
    })
    

你可能感兴趣的:(从源码看React环境变量那些事儿)