首页 > 解决方案 > 在 ReactJS 应用程序中,如何将全局环境中的环境变量替换为我的 index.html 文件?

问题描述

我有一个 ReactJS 17 应用程序,我想将全局环境中的环境变量替换到我的src/index.html文件中。.env但是,正如这个答案所暗示的那样,我们没有使用文件 - Create React App: using environment variables in index.html。这是src/index.html文件...

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
    
    ...
    <link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="512x512">
    <script src="%REACT_APP_THIRD_PARTY_URL%"></script>
  </head>
  <body>

在我的 shell 中,我可以在我的全局环境中看到 env var

$ echo $REACT_APP_THIRD_PARTY_URL
http://thirdpartyurl.com

我在开发模式下运行我的应用程序,使用安装npm i和运行npm run start。但是,上面的替换从未进行过,并且页面正在呈现

<script src="%REACT_APP_THIRD_PARTY_URL%"></script>

编辑:

这是一个开发环境,所以热重新加载等等。这是我在 package.json 中“开始”的方式

"start": "webpack-dev-server --open --config webpack.development.js"

编辑 2:不确定这是否重要,但这是在我的 webpack 文件中......

我的 package.json ...

{
  "name": "my_app",
  "version": "1.0.0",
  "description": "My desc",
  "main": "index.js",
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watchAll",
    "test:coverage": "jest --coverage",
    "build": "webpack --config webpack.production.js",
    "start": "webpack-dev-server --open --config webpack.development.js",
...

这是 webpack.development.js 文件...

require('dotenv').config();
const { merge } = require('webpack-merge');
const common = require('./webpack.common');

const WEBPACK_WATCH_POLLING = process.env.WEBPACK_WATCH_POLLING === '1';

module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './dist',
    publicPath: '/',
    historyApiFallback: true,
    disableHostCheck: true,
    hot: true,
    host: '0.0.0.0', 
    port: 8080,
  },
  output: {
    publicPath: '/',
  },
  // This is needed for docker-on-windows.
  watchOptions: !WEBPACK_WATCH_POLLING
    ? undefined
    : {
        poll: 1000,
      },
});

这是我的 webpack.common 文件...

module.exports = {
  entry: './src/index.tsx',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.jsx', '.js', '.json', '.css', '.scss'],
    alias: {
      '@': path.resolve('src'),
      '@sb': path.resolve('.storybook'),
      '@static': path.resolve('static'),
      common: path.resolve('src/components/common'),
    },
  },
  module: {
    rules: [
      {
        test: /\.(js|ts)x?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            babelrc: true,
          },
        },
      },
      {
        test: /\.html$/,
        use: [
          {
            loader: 'html-loader',
          },
        ],
      },
      {
        test: /\.(scss|css)$/i,
        use: ['style-loader', 'css-loader', 'sass-loader'],
      },
      {
        test: /\.(jpe?g|gif|png)$/,
        use: {
          loader: 'url-loader',
        },
      },
      {
        test: /\.svg$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: 'icons/',
          },
        },
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: 'fonts/',
          },
        },
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      REACT_APP_THIRD_PARTY_URL: "http://google.com"
      //filename: './index.html',
    }),
    new CopyWebpackPlugin({
      patterns: [
        { from: 'src/manifest.json' },
        { from: 'static/**/*.{png,svg,ico}', flatten: true },
      ],
    }),
    new WorkboxWebpackPlugin.InjectManifest({
      swSrc: './src/service-worker.js',
    }),
    new EnvironmentPlugin([
      'NODE_ENV',
      'REACT_APP_THIRD_PARTY_URL',
    ]),
  ],
};

最后,我的 src/index.tsx 文件...

import './monitor';
import * as serviceWorker from './sw-registration';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/app/App';
import './i18n.js';

ReactDOM.render(
  <React.StrictMode>
    <React.Suspense fallback={<div>Loading...</div>}>
      <App />
    </React.Suspense>
  </React.StrictMode>,
  document.getElementById('root')
);

serviceWorker.register();

标签: reactjsnpmenvironment-variables

解决方案


根据您的环境和构建工具,有几种可能的方法。

我想对您来说最方便的方法是在构建时将它们提供给您的项目。

  1. 您可以index.html动态生成文件(通过使用模板引擎或为您执行作业的简单脚本)并通过从环境中读取所需的变量来手动附加所需的变量。
  2. 或者更优雅的解决方案 - 将此步骤集成到您的构建工具中。如果您正在使用webpack构建您的包,您可以使用为您HtmlWebpackPlugin构建您的index.html文件,并为您提供所需的所有环境数据。

为此,请创建一个您将用作模板的简单文件.ejs或文件:.html

<!DOCTYPE html>
<html>
   <head>
      ...
      <script src="<%= htmlWebpackPlugin.options.thirdPartyData.url %>"></script>
   </head>
   <body></body>
</html>

然后,在您的webpack.config.js设置中,HtmlWebpackPlugin如下所示:

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    ...
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: '<PATH_TO_YOUR_TEMPLATE>',
            thirdPartyData: {
                url: process.env.<YOUR_ENV_VARIABLE>
            }
        })
    ]
};

Webpack将从环境中获取变量(通过node),并将index.html根据您的模板文件为您构建。您可以向插件提供任意数据,然后您可以从htmlWebpackPlugin.options属性中读取模板。您可以在此处了解有关HtmlWebpackPlugin 的更多信息。

请注意,为了更改代码本身中的环境变量,您必须通过 进行重建webpack,这是可以理解的。

此解决方案不需要您使用create-react-app.env文件,也不需要服务器/服务器渲染来为您完成这项工作。请注意,它也完全在反应应用程序的上下文之外工作(您不在反应应用程序本身,您直接在.html标记上工作)。


npm scripts另一种可能的方法是通过使用标志来提供环境变量,--env但这对您来说不方便集成,因为它需要一些额外的工作。


评论后编辑

这是一个最小的示例沙箱。由于它执行脚本的方式,提供的代码将无法在代码和框本身内部工作,但如果您在您的机器上复制提供的示例,它会做它应该做的事情。为了测试它,你只需要index.html、 the webpack.config.json、 thepackage.json和一个 dummy index.js。为简洁起见,我删除了与反应相关的代码。

只需下载代码和框示例,运行npm install,然后npm start.

然后,您可以将该方法迁移到您的特定代码库。


编辑 2

好的,我发现了问题。您html-loader会干扰HtmlWebpackPlugin并且不会替换模板中的变量。如果您html-loader在 webpack.config 中注释掉转换工作,您会注意到。因此,最简单的解决方案将是:

  1. 创建一个新index.html.ejs文件并将其放在您的index.html. 这是一个真正合适的ejs模板。把你的当前内容放在index.html那里:

index.html.ejs

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
    <link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="512x512">
    <script src="<%= htmlWebpackPlugin.options.REACT_APP_THIRD_PARTY_URL %>"></script>
    ...
</head>

<body>
    ...
</body>

</html>
  1. 在您的webpack.common.js观点中,配置中的template属性HtmlWebpackPlugin会导致该模板:
new HtmlWebpackPlugin({
    template: './src/index.html.ejs',
    REACT_APP_THIRD_PARTY_URL: "http://google.com"
})

此时您甚至可以完全删除index.html它,因为它将由HtmlWebpackPlugin插件生成。

通过这种方式,您可以保留您的html-loader转换(如果您确实需要它)并获得插值的 html 文件。


推荐阅读