24

I want to build a react app and have the static css and js files effectively embedded into the index.html that gets produced when I call yarn build.

I can obviously hack the output and replace the <script src"... and <link href="/static/css/... tags with inline versions but was hoping for a more elegant solution.

1
  • 3
    Why do you want one single html file? Commented Aug 21, 2018 at 13:30

5 Answers 5

27

I got it working. For future reference (using react 16.7.0 & yarn 1.10.1)...

Create a react app:

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

All good? Cool, run eject so that you have access to the webpack config:

yarn eject
rm -rf build node_modules
yarn install
yarn build

Add this project and update webpack:

yarn add [email protected]
yarn add [email protected]

Edit 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/]),
    ...

Run rm -rf build && yarn build && serve -s build - the index.html you load should now contain all the js and css stuff inline. Note that the static files are still created in the build dir, but they aren't used by the page.

Sign up to request clarification or add additional context in comments.

Comments

16

Previous answers don't work because html-webpack-inline-source-plugin is no longer supported by the author, replaced by official plugins html-inline-script-webpack-plugin and html-inline-css-webpack-plugin. The following method works with the latest React 17 as of October 2021. Steps:

Create a React app

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

Make sure it's working, you're happy with the output. Then, eject your config (I think this can be done without ejecting config, but I can't be bothered to look up how to configure CRA)

yarn eject
rm -rf build node_modules
yarn
yarn build

Then, add the Webpack plugins (Assuming you want both CSS and scripts embedded here, just remove one if you only want the other)

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

Finally, add these to the Webpack config config/webpack.config.js. Firstly declare them at the top of the file:

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

Then add them to the plugins: [...] collection. Search for new InlineChunkHtmlPlugin and add them just after that:

      isEnvProduction &&
        shouldInlineRuntimeChunk &&
        new HTMLInlineCSSWebpackPlugin(),
      isEnvProduction &&
        shouldInlineRuntimeChunk &&
        new HtmlInlineScriptPlugin(),

Note: It is important to include the isEnvProduction / shouldInlineRuntimeChunk checks! If you skip these the hot refresh won't work when you run yarn start. You only want to embed the scripts in production mode, not during development.

Now, if you run yarn build you'll find all the CSS and JS embedded in build/index.html. If you run yarn start it'll start a hot-reloading dev environment you can develop in.

6 Comments

After performing these steps it looks to have inlined everything into my index.html file correctly, however, the release index.html file in the build directory doesn't load properly now and just shows a blank white screen. Any idea what could be the issue here? I am very much a newbie to the this world.
You probably have to add this to your HtmlWebpackPlugin configuration inside your webpack.config.js. Look for the inject property and change it's value to body. This makes sure that your JavaScript is loaded after your HTML is done loading. The issue your probably having is that your script is loaded before your HTML is ready.
Thanks @StanleyThijssen, I wasn't able to change the inject property's value to body (it looks like a boolean in my project), but you were spot on the mark with your reasoning - after moving my inline script to the last line of the HTML <body>, it loads as expected. Big kudos!
@notanalien replace inject: true, with inject: "body",
Hooray, thank you very much @Jansky, it works. I provided also an answer that extends on your solution, basically achieving this via react-app-rewired so not to eject or mess with react-scripts config directly.
|
9

2023 Update

The plugin originally linked below is no longer maintained. There have been multiple forks to update but none of them seem to be actively maintained either. The most recent option supporting Webpack 5 is the following:

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

Per its README, it works the same way as the original plugin. Specify:

var HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin-next');

Then you can pass in the plugins as before.


There is a Webpack plugin that has been created for this purpose:

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

As the README specifies, this must be used with html-webpack-plugin, and you have to specify the inlineSource that is passed in:

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

4 Comments

Thanks but I can't get this working. Have added html-webpack-inline-source-plugin and html-webpack-plugin to my devDependencies, and have created a webpack.config.js file (though not sure exactly what needs to be in there). But the build ignores it.
Ah, it was my assumption you were using webpack already. Do you know what's compiling it under the hood? Are you using something like create-react-app?
An update in 2021, we can replace new HtmlWebpackInlineSourcePlugin() to new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin) to get it work.
This is no longer valid/working solution for webpack5/react17. There is an issue there that shows what errors you will get: github.com/DustinJackson/html-webpack-inline-source-plugin/… You can no longer pass arguments to HtmlWebpackInlineSourcePlugin
7

I couldn't get it working with all of the supplied answers. However, I want to help anyone trying to find the solution by providing some links that I found corresponding to this issue:

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

Generate single physical javascript file using create-react-app

Inline CSS with Webpack, without HtmlWebpackInlineSourcePlugin?

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

For my very simple use case minification etc. is not as important so I will just use this modified version of the react website example (example download here).

<!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/[email protected]/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>

Hope this helps

Comments

3

@Jansky answer works for me, hooray! However, I want to follow up as there is a way to do these modifications without ejecting app or messing with react-scripts/config/webpack.config.js file. And that includes Overriding HtmlWebpackPlugin to inline script within body.

Others can't get this to work probably because creating webpack.config.js within own project which is not used when using create-react-app.

This works for me with webpack5, react17

  1. Install react-app-rewired that allows overriding webpack config
npm install react-app-rewired --save-dev
  1. Read readme.md to see that you have to update scripts to invoke react-app-rewired instead of 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. Create config-overrides.js with this content
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;
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.