22

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.

pomo
  • 2,251
  • 1
  • 21
  • 34

5 Answers5

26

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 html-webpack-plugin@4.0.0-beta.4
yarn add html-webpack-inline-source-plugin@1.0.0-beta.2

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.

pomo
  • 2,251
  • 1
  • 21
  • 34
13

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.

Jansky
  • 1,455
  • 1
  • 17
  • 33
  • 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. – not an alien Mar 16 '22 at 17:17
  • 2
    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. – Stanley Thijssen Mar 17 '22 at 01:26
  • 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 ``, it loads as expected. Big kudos! – not an alien Mar 17 '22 at 09:58
  • @notanalien replace inject: true, with inject: "body", – Trentfrompunchbowl1 Aug 31 '22 at 09:31
  • I followed these steps and also got the blank screen with this error: `createRoot(...): Target container is not a DOM element.` Adding `inject: 'body'` to the `HtmlWebpackPlugin` fixed this. However I still had issues with broken links to static assets. I fixed this by setting the PUBLIC_URL env var to `.`, e.g.: `"build": "PUBLIC_URL=. node scripts/build.js",` – Andrew Aarestad Jan 12 '23 at 19:15
  • 1
    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. – Janis Veinbergs Feb 25 '23 at 14:25
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()
]
Kevin Hoerr
  • 2,319
  • 1
  • 10
  • 21
  • 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. – pomo Jan 03 '19 at 17:24
  • 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? – Kevin Hoerr Jan 04 '19 at 16:21
  • An update in 2021, we can replace `new HtmlWebpackInlineSourcePlugin()` to `new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin)` to get it work. – keikai May 23 '21 at 15:32
  • This is no longer valid/working solution for webpack5/react17. There is an issue there that shows what errors you will get: https://github.com/DustinJackson/html-webpack-inline-source-plugin/issues/79#issuecomment-1222361171 You can no longer pass arguments to HtmlWebpackInlineSourcePlugin – Janis Veinbergs Feb 25 '23 at 14:27
4

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/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>

Hope this helps

J.K.
  • 236
  • 3
  • 5
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;
  }
}
Janis Veinbergs
  • 6,907
  • 5
  • 48
  • 78