有没有一种方法可以在单个HTML文件中构建React应用程序?

23

我希望构建一个React应用程序,并将静态的CSS和JS文件有效地嵌入到当我调用yarn build时生成的index.html中。

显然,我可以通过替换<script src"...<link href="/static/css/...标签为内联版本来完成,但我希望有一种更优雅的解决方案。


3
为什么你想要一个单独的HTML文件? - Tall Paul
5个回答

26

我搞定了。供日后参考(使用react 16.7.0和yarn 1.10.1)...

创建一个React应用:

npx create-react-app my-app
cd my-app
yarn build

一切都好吗?太好了,运行eject以便您可以访问webpack配置:

yarn eject
rm -rf build node_modules
yarn install
yarn build

添加该项目并更新webpack:

yarn add html-webpack-plugin@4.0.0-beta.4
yarn add html-webpack-inline-source-plugin@1.0.0-beta.2

编辑config/webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin'); <--- add this
...
plugins: [
  new HtmlWebpackPlugin(
    Object.assign(
      ...
      isEnvProduction
        ? {
            inlineSource: '.(js|css)$', <-- add this
            minify: {
              ...
  ),
  ...
  isEnvProduction &&
    shouldInlineRuntimeChunk &&
    new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin), <--- add this
    new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]),
    ...

运行命令rm -rf build && yarn build && serve -s build - 你加载的index.html现在应包含所有js和css内容。请注意,静态文件仍然创建在build目录中,但页面不会使用它们。


13

之前的答案都不适用了,因为 html-webpack-inline-source-plugin 已经不再被支持,作者已经用官方插件 html-inline-script-webpack-pluginhtml-inline-css-webpack-plugin 进行了替换。以下方法可以在最新的React 17中(截至2021年10月)使用。具体步骤如下:

创建一个React应用

create-react-app my-app
cd my-app
yarn
yarn start

确保它能工作,并且你对输出结果感到满意。然后,弹出你的配置文件(我认为这可以在不弹出配置文件的情况下完成,但我懒得查找如何配置CRA)

yarn eject
rm -rf build node_modules
yarn
yarn build

然后,添加Webpack插件(假设你想在这里嵌入CSS和脚本,请删除其中一个如果你只想要另外一个)

yarn add html-inline-css-webpack-plugin -D
yarn add html-inline-script-webpack-plugin -D

最后,在 Webpack 配置文件 config/webpack.config.js 中添加这些内容。首先在文件顶部声明它们:

const HTMLInlineCSSWebpackPlugin = require('html-inline-css-webpack-plugin').default;
const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin');

然后将它们添加到plugins: [...]集合中。搜索new InlineChunkHtmlPlugin,并将它们添加在其后面:

      isEnvProduction &&
        shouldInlineRuntimeChunk &&
        new HTMLInlineCSSWebpackPlugin(),
      isEnvProduction &&
        shouldInlineRuntimeChunk &&
        new HtmlInlineScriptPlugin(),
注意: 包括 isEnvProduction / shouldInlineRuntimeChunk 检查非常重要!如果您跳过这些检查,当您运行 yarn start 时,热更新将无法正常工作。您只想在生产模式下嵌入脚本,而不是在开发期间嵌入。

现在,如果您运行 yarn build,您会发现所有的 CSS 和 JS 都被嵌入到 build/index.html 中。如果您运行 yarn start,它将启动一个热重载开发环境,您可以在其中进行开发。


执行这些步骤后,它似乎已经正确地将所有内容内联到我的index.html文件中,但是构建目录中的发布index.html文件现在无法正确加载,只显示一个空白白屏。有什么想法吗?我对这个世界非常陌生。 - not an alien
2
你可能需要在webpack.config.js文件中的HtmlWebpackPlugin配置中添加以下内容。查找“inject”属性并将其值更改为“body”。这样可以确保在HTML加载完成后再加载JavaScript。你遇到的问题可能是脚本在HTML准备好之前就已经加载了。 - Stanley Thijssen
感谢@StanleyThijssen,我无法将“inject”属性的值更改为“body”(在我的项目中它看起来像一个布尔值),但是您的推理非常准确 - 在将内联脚本移动到HTML <body>的最后一行之后,它按预期加载。非常感谢! - not an alien
@notanalien 将 inject: true, 替换为 inject: "body", - Trentfrompunchbowl1
我按照这些步骤操作,但是出现了空白屏幕和错误提示:createRoot(...): Target container is not a DOM element.HtmlWebpackPlugin 中添加 inject: 'body' 解决了这个问题。然而,我仍然遇到了静态资源链接损坏的问题。通过将 PUBLIC_URL 环境变量设置为“.”,例如:"build": "PUBLIC_URL=. node scripts/build.js",我解决了这个问题。 - Andrew Aarestad
1
太好了,非常感谢@Jansky,它起作用了。我还提供了一个扩展您解决方案的答案,基本上通过react-app-rewired实现这一点,因此不需要弹出或直接干扰react-scripts配置。 - Janis Veinbergs

9

2023 更新

下面原本链接的插件已经不再维护。虽然有多个分支进行了更新,但似乎没有一个是活跃维护的。目前支持 Webpack 5 的最新选项如下:

https://www.npmjs.com/package/html-webpack-inline-source-plugin-next

根据它的README,它的工作方式与原始插件相同。请指定:
var HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin-next');

然后您可以像以前一样传递插件。


为此目的创建了一个Webpack插件:

https://www.npmjs.com/package/html-webpack-inline-source-plugin

如README所指定,这必须与html-webpack-plugin一起使用,并且您必须指定传递的inlineSource

plugins: [
  new HtmlWebpackPlugin({
        inlineSource: '.(js|css)$' // embed all javascript and css inline
    }),
  new HtmlWebpackInlineSourcePlugin()
]

谢谢,但我无法让它工作。我已经将html-webpack-inline-source-plugin和html-webpack-plugin添加到我的devDependencies中,并创建了一个webpack.config.js文件(尽管不确定需要放什么)。但是构建忽略了它。 - pomo
啊,我以为你已经在使用webpack了。你知道它是如何编译的吗?你是在使用类似create-react-app这样的工具吗? - Kevin Hoerr
2021年的更新,我们可以将 new HtmlWebpackInlineSourcePlugin() 替换为 new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin),以使其正常工作。 - keikai
这不再是webpack5/react17的有效/可行解决方案。有一个问题显示了你会得到什么错误:https://github.com/DustinJackson/html-webpack-inline-source-plugin/issues/79#issuecomment-1222361171您不能再将参数传递给HtmlWebpackInlineSourcePlugin。 - Janis Veinbergs

4

虽然我无法使用之前提供的所有答案将其运行起来,但我想通过提供一些链接来帮助任何试图找到解决方案的人:

https://github.com/facebook/create-react-app/issues/3365#issuecomment-376546407

使用create-react-app生成单个物理JavaScript文件

在不使用HtmlWebpackInlineSourcePlugin的情况下使用Webpack内联CSS?

https://pangyiwei.medium.com/building-a-react-app-as-a-single-html-file-with-create-react-app-no-eject-283a141e7635

https://www.labnol.org/code/bundle-react-app-single-file-200514

对于我非常简单的用例,缩小等并不是那么重要,因此我将使用修改后的React网站示例(示例下载在这里)。

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <title>Add React in One Minute</title>
</head>

<body>

  <h2>Add React in One Minute</h2>
  <p>This page demonstrates using React with no build tooling.</p>
  <p>React is loaded as a script tag.</p>

  <!-- We will put our React component inside this div. -->
  <div id="root"></div>

  <!-- Load React. -->
  <!-- Note: when deploying, replace "development.js" with "production.min.js". -->
  <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>

  <script type="text/babel">
    'use strict';
    class LikeButton extends React.Component {
      constructor(props) {
        super(props);
        this.state = { liked: false };
      }

      render() {
        if (this.state.liked) {
          return 'You liked this.';
        }

        return <button onClick={() => this.setState({ liked: true })} > Like </button >;
      }
    }
    const root = ReactDOM.createRoot(document.getElementById('root'));

    root.render(<LikeButton />)
  </script>
</body>

</html>

希望这能帮到您。

3

@Jansky的回答对我很有帮助,太好了!不过,我想跟进一下,因为有一种方法可以在不弹出应用程序或搞乱react-scripts/config/webpack.config.js文件的情况下进行这些修改。这包括重写HtmlWebpackPlugin以内联脚本到body中。

其他人可能无法使此方法正常工作,因为创建了webpack.config.js并未在使用create-react-app时使用自己的项目。

这适用于我的webpack5,react17

  1. 安装react-app-rewired,允许覆盖webpack配置
npm install react-app-rewired --save-dev
  1. 阅读 readme.md 文件,了解需要更新脚本以调用 react-app-rewired 而不是 react-scripts
  /* package.json */

  "scripts": {
-   "start": "react-scripts start",
+   "start": "react-app-rewired start",
-   "build": "react-scripts build",
+   "build": "react-app-rewired build",
-   "test": "react-scripts test",
+   "test": "react-app-rewired test",
    "eject": "react-scripts eject"
}
  1. 创建config-overrides.js文件,内容如下:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlInlineCssWebpackPlugin = require('html-inline-css-webpack-plugin').default;
const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin');

module.exports = {
  webpack: function (config, env) {
    //inline css and scripts right after chunk plugin.
    //chunk plugin will not be present for development build and thats ok.
    const inlineChunkHtmlPlugin = config.plugins.find(element => element.constructor.name === "InlineChunkHtmlPlugin");
    if (inlineChunkHtmlPlugin) {
      config.plugins.splice(config.plugins.indexOf(inlineChunkHtmlPlugin), 0,
        new HtmlInlineCssWebpackPlugin(),
        new HtmlInlineScriptPlugin()
      );
    }
    
    //Override HtmlWebpack plugin with preserving all options and modifying what we want
    const htmlWebpackPlugin = config.plugins.find(element => element.constructor.name === "HtmlWebpackPlugin");
    config.plugins.splice(config.plugins.indexOf(htmlWebpackPlugin), 1,
      new HtmlWebpackPlugin(
        {
          ...htmlWebpackPlugin.userOptions,
          inject: 'body'
        }
      )
    );

    return config;
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接