Ajit Goel
Ajit Goel

Reputation: 4418

Generate single physical javascript file using create-react-app

This is perhaps a newbie question. I have created a small reactjs app using create-react-app and I see that the bundle.js file is being served from http://localhost:3000/static/js/bundle.js. However, I do not see a physical "bundled" javascript file on my machine. How can I generate a physical bundled javascript file so I can "register" it in my wordpress php code? I'm building a small wordpress plugin that will use reactjs on the client side. Am I missing something obvious?

Upvotes: 32

Views: 43738

Answers (7)

Rob Willis
Rob Willis

Reputation: 4844

This is a 2023 update of the answer from @phdesign. The original answer works perfectly for the .js file but there was an error for .css.

Create a new file scripts/build-overrides.js

const rewire = require("rewire");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const defaults = rewire("react-scripts/scripts/build.js");
let config = defaults.__get__("config");

//disable splitting JS files into chunks in the production build
config.optimization.splitChunks = {
  cacheGroups: {
    default: false,
  },
};
config.optimization.runtimeChunk = false;

// JS
config.output.filename = "static/js/[name].js";

// CSS remove MiniCssPlugin
config.plugins = config.plugins.map((plugin) => {
  if (plugin instanceof MiniCssExtractPlugin) {
    return new MiniCssExtractPlugin({
      filename: "static/css/[name].css",
    });
  }
  return plugin;
});

Edit package.json

{
  "scripts": {
    ...
    "build": "npx ./scripts/build-overrides.js",
    ...
  }
}

Upvotes: 1

Richie
Richie

Reputation: 190

None of the given solutions worked for me while using create react app 5. After some research I have found that you can either eject your project and then modify your config/webpack.config.js or you can combine react-app-rewired with customize-cra-5; previous customize-cra won't work.

So the thing goes like this:

  • yarn add customize-cra-5 react-app-rewired --dev
  • Create a file named config-overrides.js in the same folder as package.json
  • Edit your scripts in package.json

The simplest config-overrides.js may look like:

const {
  override,
  disableChunk,
} = require("customize-cra-5");

module.exports = override(
  disableChunk()
);

And your scripts:

  "scripts": {
    "format": "prettier --write",
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  },

Some additional information:

Upvotes: 1

Drazen Bjelovuk
Drazen Bjelovuk

Reputation: 5492

Using react-app-rewired alongside customize-cra to produce a single JS bundle with included styles:

config-overrides.js

const { override, adjustStyleLoaders } = require('customize-cra');

module.exports = override(
  (config) => {
    config.optimization.splitChunks = {
      cacheGroups: { default: false }
    };
    config.optimization.runtimeChunk = false;

    return config;
  },
  adjustStyleLoaders(({ use }) => {
    use.forEach((loader) => {
      if (/mini-css-extract-plugin/.test(loader.loader)) {
        loader.loader = require.resolve('style-loader');
        loader.options = {};
      }
    });
  })
);

Upvotes: 6

adamduncan
adamduncan

Reputation: 168

After realising @Niraj's soluition didn't work with CRA v4 (as confirmed in this packaged implementation of the same approach), I found was possible without ejecting, with only re-implementing the bare bones of an adjacent webpack setup (assuming react v17.x and react-scripts v4.x):

1. Create webpack.config.js file at project root:

const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  entry: path.join(__dirname, 'src/index.js'),
  output: {
    path: path.join(__dirname, 'build/static/js'),
    filename: `bundle.min.js`,
  },
  module: {
    rules: [
      {
        test: /\.js/,
        exclude: /node_modules/,
        options: {
          cacheDirectory: true,
          presets: [
            '@babel/preset-env',
            ['@babel/preset-react', { runtime: 'automatic' }],
          ],
        },
        loader: 'babel-loader',
      },
    ],
  },
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
  },
};

2. Install dependencies to parse/compile React app

From the terminal, run:

npm install --save-dev @babel/preset-env @babel/preset-react babel-loader terser-webpack-plugin webpack-cli

Note use of terser-webpack-plugin as successor to UglifyJsPlugin

3. Add build scripts to package.json

    "build:app": "react-scripts build",
    "build:bundle": "webpack --mode production",
    "build": "npm run build:app && npm run build:bundle",

4. Run build

From the terminal, run npm run build, which should produce the single JS bundle comprising React library code and your application code, ready to drop in as a single <script> reference as needed (as well as chunked build output from standard react-scripts build).

Upvotes: 2

Niraj
Niraj

Reputation: 793

Yes it can be possible by below webpack configuration:

create file webpack.config.js in your root directory

const path = require("path")
const UglifyJsPlugin = require("uglifyjs-webpack-plugin")
const glob = require("glob")

    module.exports = {
      mode: "production",
      entry: {
        "bundle.js": glob.sync("build/static/?(js|css)/main.*.?(js|css)").map(f => path.resolve(__dirname, f)),
      },
      output: {
        path: path.resolve(__dirname, "build"),
        filename: "static/js/bundle.min.js",
      },
      module: {
        rules: [
          {
            test: /\.css$/,
            use: ["style-loader", "css-loader"],
          },
        ],
      },
      plugins: [new UglifyJsPlugin()],
    }

In package.json

...
    "build": "npm run build:react && npm run build:bundle", 
    "build:react": "react-scripts build", 
    "build:bundle": "webpack --config webpack.config.js", 
...

Reference from here

Upvotes: 9

phdesign
phdesign

Reputation: 2167

An alternative solution, as suggested here, is to use the rewire package to manipulate Create React App's webpack config, e.g.

Create a new file scripts/build.js

// npm install rewire
const rewire = require('rewire');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const defaults = rewire('react-scripts/scripts/build.js');
const config = defaults.__get__('config');

// Consolidate chunk files instead
config.optimization.splitChunks = {
  cacheGroups: {
    default: false,
  },
};
// Move runtime into bundle instead of separate file
config.optimization.runtimeChunk = false;

// JS
config.output.filename = 'static/js/[name].js';
// CSS remove MiniCssPlugin
config.plugins = config.plugins.filter(plugin =>
    !(plugin instanceof MiniCssExtractPlugin));
// CSS replaces all MiniCssExtractPlugin.loader with style-loader
config.module.rules[2].oneOf = config.module.rules[2].oneOf.map(rule => {
    if (!rule.hasOwnProperty('use')) return rule;
    return Object.assign({}, rule, {
        use: rule.use.map(options => /mini-css-extract-plugin/.test(options.loader)
            ? {loader: require.resolve('style-loader'), options: {}}
            : options)
    });
});

Edit package.json

{
  "scripts": {
    ...
    "build": "npx ./scripts/build.js",
    ...
  }
}

Upvotes: 14

aimass
aimass

Reputation: 418

I had the same exact problem and the short answer is NO. At least with the standard "npm run build" that is currently shipped up to the date of this writing.

I will however show you how to accomplish your goals by helping you understand what is going on. First, you need to realize that create-react-app is based on the webpack module builder:

https://webpack.github.io

So the canonical way to do this would probably be learning webpack and contributing to create-react-app so that instead of generating a static index.html version of your app, it could generate one or more javascript files that you could drop into a Wordpress page. I imagine something like "npm run build -js-only" or something like that. But AFAICT that is not currently possible.

The good news is that you can still accomplish your goal of "registering" your react app into WP but first you need to understand some concepts:

1. About create-react-app

a. When you scaffold your app with create-react-app, it's using webpack under the hood to do this. It's doing this in a pre-defined way defined by create-react-app, so from the start, your app is already structured to be served as an index.html file.

b. When you are developing with "npm start" it is actually webpack serving your app from RAM.

c. When you build you app, it is actually webpack (amongst other things) that is used to create that build directory.

2. Disect your Generated App

If you view source while you are testing your app with the embedded Webserver (npm start) you will notice that it's a very short html file with the typical structure:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="/manifest.json">
    <!--
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
    integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
    crossorigin="anonymous">
    -->
    <link rel="stylesheet" href="./bootstrap.min.css">

    <title>React App</title>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
  <script src="/static/js/bundle.js"></script><script src="/static/js/0.chunk.js"></script><script src="/static/js/main.chunk.js"></script></body>
</html>

So basically, you have some header code that loads manifests and stylesheets. Then you have a single container with id "root" and THEN you have the Javascript files you're looking for.

3. Create a Proof of Concept HTML File on the Worpress Side

So here is the actual answer to your question. As I said above, this is probably far from ideal but it does solve the problem you are having. I do suspect however that there is an easier and more formal way of accomplishing this, but as you, I'm also a newbie in React and related technologies, so hopefully some expert will eventually weigh in to this thread eventually and propose a more canonical way.

First you need to serve you React app under some domain, suppose it's being served at myreactapp.local.

Test that you can access your app by going to http://myreactapp.local/index.html and your app should work just as it did with "npm start". If something breaks, it's probably related to your stylesheets.

Once you get the static version of your React app to work, just do this:

  1. Look into the build/index.html file and you will notice 3 script tags. One of them has actual code in there, so just copy that code and create a new script on the ROOT of of your react app called loadme.js (at the same level as index.html).

  2. Copy the complete index.html file and create a static HTML file in the root of your wordpress called myreactappskel.html (just for testing the POC). This file will will serve as the basis for your templates and registering of the CSS and JS files into Wordpress ;-)

  3. Edit the file and format it neatly, replacing all relative paths with the URL of the server fo the react app (e.g. myreactapp.local).

You should end up with a file similar to this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="http://myreactapp.local/favicon.ico">
    <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="http://myreactapp.local/manifest.json">
    <link rel="stylesheet" href="http://myreactapp.local/bootstrap.min.css">
    <title>My React App POC Static Page in Wordpress</title>
    <link href="http://myreactapp.local/static/css/1.7fbfdb86.chunk.css" rel="stylesheet">
    <link href="http://myreactapp.local/static/css/main.ebeb5bdc.chunk.css" rel="stylesheet">
  </head>

    <title>React App</title>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="someotherid"></div>
    <script src="http://myreactapp.local/loadme.js"></script>
    <script src="http://myreactapp.local/static/js/1.b633dc93.chunk.js"></script>
    <script src="http://myreactapp.local/static/js/main.8958b7bb.chunk.js"></script>

</html>

And that's it! It sounds complicated but it's really not. Notice some important points and caveats:

  1. Rename the "root" to some other ID because "root" will likely collide with something in the HTML where this is embedded. You need to change this in your React source code in both index.html and index.js sources.

  2. Notice how the scripts are AFTER the container div. This is exactly how the packed HTML looks like.

  3. Notice the loadme.js file which you created in a step above by taking the contents of the first tag.

  4. The rest of the file is almost identical to the generated file.

The rest of the integration to Wordpress should be pretty obvious to you.

Hope this helps.

References

https://facebook.github.io/create-react-app/docs/deployment

https://webpack.github.io

Upvotes: 11

Related Questions