Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disabling code splitting in CRA2 #5306

Open
ghost opened this issue Oct 5, 2018 · 78 comments
Open

Disabling code splitting in CRA2 #5306

ghost opened this issue Oct 5, 2018 · 78 comments

Comments

@ghost
Copy link

@ghost ghost commented Oct 5, 2018

How can I disable the code splitting in CRA 2?

@gaearon
Copy link
Member

@gaearon gaearon commented Oct 5, 2018

Can you provide more details about what you’re trying to do and why?

@esturcke
Copy link
Contributor

@esturcke esturcke commented Oct 8, 2018

Hi @gaearon, I hope I'm not hijacking this thread, but we've run into a similar question. We are mixing CRA with legacy JS. We have certain code that we have to run synchronously before the legacy JS because we have to make it available to our legacy application. In CRA1 would add code like that to src/index.js, but this seems to no longer work. I'm not sure yet if it's because it's being split out or if it's that the main bundle runs async now.

@gaearon
Copy link
Member

@gaearon gaearon commented Oct 8, 2018

Sorry, it’s still not quite clear to me what you mean. An example reproducing the problem would be valuable.

@esturcke
Copy link
Contributor

@esturcke esturcke commented Oct 8, 2018

I can work on a minimal reproduction case.

@esturcke
Copy link
Contributor

@esturcke esturcke commented Oct 8, 2018

Actually, I missed the part about the runtime. I'm looking at the generated index.html and I see the inlined runtime followed by

      <script src="/ui/static/js/53.4ef0ec74.chunk.js"></script>
      <script src="/ui/static/js/main.384085f8.chunk.js"></script>

But right now in our product we've only added the main.*.js script. I can work on inlining the runtime, but how would I know to include 53.*.js?

@gaearon
Copy link
Member

@gaearon gaearon commented Oct 8, 2018

I don't understand what "added" means. If you have a separate HTML file you create manually (instead of using our HTML output), you can construct it with information in build/asset-manifest.json which gives you a list of all scripts on the page.

@esturcke
Copy link
Contributor

@esturcke esturcke commented Oct 8, 2018

Sorry, yeah, by added I just mean that we have custom HTML with a script tag for main.*.js where we get the full name from build/asset-manifest.json. I'm not sure how to tell that I also need to include 53.*.js.

@gaearon
Copy link
Member

@gaearon gaearon commented Oct 8, 2018

It seems like we should put more info in asset-manifest. It should provide enough information for people to build their own HTML regardless of how many chunks there are.

@BrendanBeltzNIH
Copy link

@BrendanBeltzNIH BrendanBeltzNIH commented Oct 9, 2018

Is there any way to do this? I work on cancer.gov and we have an embedded widget made in CRA that needs to be a single file for the current build and deployment system to work (I've already been having to overrride the automated hashing with react-app-rewired).

@BrendanBeltzNIH
Copy link

@BrendanBeltzNIH BrendanBeltzNIH commented Oct 9, 2018

I should add that the issue is caused in part by a peculiarity of our setup. The CRA based app is hosted in a separate repo from cancer.gov as source code (to allow for it to be used on other sites as well) which requires it as a dependency directly from github (so a post-install script has to run to build the CRA app inside of cancer.gov). When we updated webpack to version 4 on cgov, the CRA v1 app stopped working (assumedly because it was being built inside of cgov's node_modules causing some issues with the conflicting webpack versions), so in trying to update to CRA2, I've managed to work around almost every issue except the code splitting. At the end of the day, on cgov, we are requiring the CRA based widget as a library, so I want to be able to output a single file or at least require the package with static filenames.

@patope
Copy link

@patope patope commented Oct 10, 2018

asset-manifest entries like:"static/js/1.6105a37c.chunk.js": "/static/js/1.6105a37c.chunk.js" are not very useful.
Could you generate something like:
"main.js": ["/static/js/1.6105a37c.chunk.js","/static/js/main.bd9ad21f.chunk.js"] instead?

@RussCoder
Copy link

@RussCoder RussCoder commented Oct 10, 2018

Yes, it would be nice if you add an environment variable to opt out of code splitting.

I'm developing an app, which works like an embeddable widget - it doesn't render itself by default, it just adds a global object with some API, which should be used by an eventual user to render the widget to any tag he wishes to embed it. Consequently, I'm not interested in the default html output at all, I need only the final bundles of js and css files.

Formerly, CRA 1.x produced only 1 js and 1 css files and it was convenient. But now, with CRA 2.x, there are 3 js files, and in order to use the app, an eventual user should include 3 js files instead of one. Or I have to copy them all into one file (I haven't tried it yet).

Thus, I would like to have an opportunity to return to the previous behavior, namely, to have only 1 js and 1 css file as the output.

@kylehotchkiss
Copy link

@kylehotchkiss kylehotchkiss commented Oct 10, 2018

+1 on the environment variable to opt out of splitting. I'm just trying to build a JS bundle for a Wordpress site, while leaving my CRA bundle in a CDN. It's not possible to update wordpress for every CRA JS update, and i ended up having to do this to load my bundle from my wordpress site:

jQuery.getJSON(cdn + '/static/asset-manifest.json', ( data ) => {
    for ( var i in Object.keys( data ) ) {
        const key = Object.keys( data )[i];

        if ( key.indexOf('.js') !== -1 && key.indexOf('.js.map') === -1 ) {
            var path = host + data[key];
            console.log('getting script', path);
            jQuery.getScript(path);
        }
    }
});

@yordis
Copy link

@yordis yordis commented Oct 10, 2018

Hey @gaearon, we have this issue as well. We need to disable the code splitting because we live inside a Rails app and for now we need to ship a single file.

I read your suggestion about using asset-manifest.json which could be viable and is what we are trying to do right now.

But I have two concerns about it.

  1. When I run the webpack-dev-server all the files are created in memory if I am not mistaking, so I can't find a way to query the assets-manifest.json file in the file system.

  2. How do I know the order in which I could include the CSS and JS. I know some of the chunks have some numbers and maybe I should add the main file at the end but it would be helpful to have documented this strategy so maybe other people could learn how to include those files.

Since this is already done with the Webpack build step when you create the final HTML file, I am assuming that there is some code somewhere in the dependencies that I could give an asnwer my second question, would be helpful if you share a link to it if you know where it lives. I am trying to find it as I am typing this.

@ghost
Copy link
Author

@ghost ghost commented Oct 19, 2018

+1 for me too.

Same issue, currently phasing in React into a legacy app and the multiple JS files mess with the legacy setup. I understand wanting to educate developers as to best practices, but it would be nice if the option to opt out of code splitting was there, perhaps buried deep somewhere but there nevertheless. Thanks for the great work as always!

@timclifford
Copy link

@timclifford timclifford commented Oct 19, 2018

For those of you who want to disable default chunking/code splitting in cra-2 so it bundled and ran from a single file, then you can disable caching within webpack.config by adding the following:

splitChunks: {
   // chunks: 'all',
   // name: false,
   cacheGroups: {
      default: false
   }
}

You can also disable the long term caching just below by commenting out, or setting the runtimeChunk to false.

runtimeChunk: false,

@RussCoder
Copy link

@RussCoder RussCoder commented Oct 19, 2018

@timclifford Did I understand correctly that you suggest us to do npm run eject first? Or create a fork of CRA? Since there is no webpack.config by default, or at least I see none.

If you suggest us to eject, it's not an option. Or, speaking differently, it's an option, of course, but not the one we want.

@timclifford
Copy link

@timclifford timclifford commented Oct 19, 2018

@RussCoder I was yes, providing an option only for those who have ejected the project already.
However you can always create your own custom script to hook into react-scripts before it executes to make a few custom tweaks. For example, see this post and example https://daveceddia.com/customize-create-react-app-webpack-without-ejecting/

@rvirani1
Copy link

@rvirani1 rvirani1 commented Oct 25, 2018

+1 This is also a problem for us. We have to embed our output onto a WP site, so having a single output makes a ton of sense. This sounds like a great option to add as a toggle based on an environment variable during build.

@vonkanehoffen
Copy link

@vonkanehoffen vonkanehoffen commented Oct 26, 2018

See #4632 for this also. I think the way CRA2 does things out of the box, chunking included, is great. The problem comes when we have to deploy to third party systems we don't have control over. The system I'm deploying to knows how to read the main.js entry from manifest.json and render that filename as a script tag on the bottom of a page it generates. I have no way to change that unfortunately. ...so having some way to turn chunking off (without ejecting) for these unfortunate situations would be great.

@vonkanehoffen
Copy link

@vonkanehoffen vonkanehoffen commented Oct 26, 2018

Just thought I'd post my temporary solution to this in case it helps anyone. A bit hacky, but at least one doesn't have to eject:

const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
let config = defaults.__get__('config');

config.optimization.splitChunks = {
    cacheGroups: {
        default: false,
    },
};

config.optimization.runtimeChunk = false;

Save that as build-non-split.js or whatever and amend your package.json like:

"build": "./scripts/build-non-split.js",

@jeremyckahn
Copy link

@jeremyckahn jeremyckahn commented Oct 26, 2018

@vonkanehoffen This workaround is a lifesaver for me. THANK YOU!

@jd327
Copy link

@jd327 jd327 commented Oct 30, 2018

Also, if you're using react-app-rewired, in your config-overrides.js:

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

And if you happen to require the bundle js directly for testing (usually from another project), like this:

<script src="http://localhost:3000/static/js/bundle.js"></script>

You have to include a bit more now:

<script src="http://localhost:3000/static/js/bundle.js"></script>
<script src="http://localhost:3000/static/js/0.chunk.js"></script>
<script src="http://localhost:3000/static/js/main.chunk.js"></script>

(edit: also just fyi, as of this writing, react-app-rewired doesn't play nicely with cra 2)

@sespinosa
Copy link

@sespinosa sespinosa commented Nov 4, 2018

@vonkanehoffen THANKS

@joelvoss
Copy link

@joelvoss joelvoss commented Nov 6, 2018

@vonkanehoffen TIL you can do that...brilliant. This seems like the right amount of tinkering with the default react-scripts sources. The same can be done for the start.js as well.

@iansu iansu removed this from the 3.4 milestone Feb 14, 2020
@iansu iansu added this to the 3.5 milestone Feb 14, 2020
@danieltott
Copy link

@danieltott danieltott commented Feb 18, 2020

My current solution FWIW is to leave the configuration untouched, and then actually run a node script that reads and parses build/index.html to get all link/script/style in both the <head> and the <body> so that I can output them elsewhere.

@phdesign
Copy link

@phdesign phdesign commented Mar 25, 2020

Building on build-non-split.js, this removes the MiniCssExtractPlugin so that there's only one output artefact with embedded CSS (main.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)
    });
});

https://gist.github.com/phdesign/3fd306db2bc53f6368e6f0f73bbeff19

@vincerubinetti
Copy link

@vincerubinetti vincerubinetti commented Mar 25, 2020

My current solution FWIW is to leave the configuration untouched, and then actually run a node script that reads and parses build/index.html to get all link/script/style in both the <head> and the <body> so that I can output them elsewhere.

This is an interesting idea I hadn't thought of. Under normal circumstances I'd consider it hacky of course. But that actually might end up being cleaner and shorter than this build-non-split.js + rewire install that seems to slowly be getting bigger and also requires extra dependencies. Thanks!

@pashuka
Copy link

@pashuka pashuka commented May 6, 2020

https://webpack.js.org/api/module-methods/#magic-comments
This solve my problem without rewire and other solutions

const myComponent = lazy(() => import(
  /* webpackMode: "eager" */
  `./pages/${_}/index`
))

@learnitmyway
Copy link

@learnitmyway learnitmyway commented Jun 28, 2020

Using craco I was able to create a single bundle:

// craco.config.js
module.exports = {
  webpack: {
    configure: {
      optimization: {
        runtimeChunk: false,
        splitChunks: {
          chunks(chunk) {
            return false
          },
        },
      },
    },
  },
}

@leoloso
Copy link

@leoloso leoloso commented Sep 19, 2020

Improving on @learnitmyway's solution using Craco, this is the config to also avoid using hashnames for both the JS and CSS:

// craco.config.js
module.exports = {
  webpack: {
    configure: {
      output: {
        filename: 'static/js/[name].js'
      },
      optimization: {
        runtimeChunk: false,
        splitChunks: {
          chunks(chunk) {
            return false
          },
        },
      },
    },
  },
  plugins: [
    {
      plugin: {
        overrideWebpackConfig: ({ webpackConfig }) => {
          webpackConfig.plugins[5].options.filename = 'static/css/[name].css';
          return webpackConfig;
        },
      },
      options: {}
    }
  ],
}

@thinkawitch
Copy link

@thinkawitch thinkawitch commented Sep 24, 2020

One litte comment to CRACO solution by @leoloso if you want to change MiniCssExtractPlugin settings, or any other webpack plugin inside of craco.config.js

overrideWebpackConfig: ({ webpackConfig }) => {

  // find the plugin
  let mcep;
  webpackConfig.plugins.some(p => {
    if (p.constructor.name === 'MiniCssExtractPlugin') {
      mcep = p;
      return true;
    }
  });
  if (mcep) {
    // change settings
    mcep.options.filename = 'static/css/[name].css';
  }

  return webpackConfig;
}

phorvicheka added a commit to Yonsei-Maist/cdm-recommender-system that referenced this issue Oct 14, 2020
@ron766
Copy link

@ron766 ron766 commented Oct 19, 2020

const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
let config = defaults.get('config');

config.optimization.splitChunks = {
cacheGroups: {
default: false,
},
};

config.optimization.runtimeChunk = false;

Not working for me!

`
const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
let config = defaults.get('config');

config.optimization.splitChunks = {
cacheGroups: {
default: false,
},
};

config.optimization.runtimeChunk = false;

// JS
config.output.filename = 'static/js/[bundle].js';
`

"build": "node ./scripts/build-non-split.js",

@rutikwankhade
Copy link

@rutikwankhade rutikwankhade commented Dec 23, 2020

Improving on @learnitmyway's solution using Craco, this is the config to also avoid using hashnames for both the JS and CSS:

// craco.config.js
module.exports = {
  webpack: {
    configure: {
      output: {
        filename: 'static/js/[name].js'
      },
      optimization: {
        runtimeChunk: false,
        splitChunks: {
          chunks(chunk) {
            return false
          },
        },
      },
    },
  },
  plugins: [
    {
      plugin: {
        overrideWebpackConfig: ({ webpackConfig }) => {
          webpackConfig.plugins[5].options.filename = 'static/css/[name].css';
          return webpackConfig;
        },
      },
      options: {}
    }
  ],
}

Finally, I found it. I was struggling with this for the past 2-3 days. tried a lot of stuff, got frustrated, and was about to give up.vbut this worked perfectly. A big thank you!

@esped-dfds
Copy link

@esped-dfds esped-dfds commented Jan 5, 2021

What threw me off for a long time after trying the suggestions with react-app-rewired and craco was it how it always generated one extra file, but this came from the async import in reportWebVitals. So it you change that file to use normal imports or just delete it it plays along nicely.

@ajayep
Copy link

@ajayep ajayep commented Jan 21, 2021

 config.module.rules[2].oneOf.map

@phdesign I am getting error and dont know how to check the hardcoded 2 is correct in my case.

config.module.rules[2].oneOf = config.module.rules[2].oneOf.map(rule => {
                                                      ^

TypeError: Cannot read property 'oneOf' of undefined
    at Object.<anonymous> (C:\Users\ajay\Documents\react-base\scripts\build-non-split.js:22:55)

@vjsingh
Copy link

@vjsingh vjsingh commented Feb 5, 2021

@leoloso @gupta-ji6 Is this working for you on CRA 4? I'm running CRA 4.0.1, and even after using @leoloso's suggested fix I'm getting two chunks:

1.9649ccbf.chunk.js
main.js

src/index.js literally just has:

import("./extension");

And ./extension/index.js just has a console.log.

If I remove the import from index.js I get just the one main.js export and no chunk. But surely it should work with an import?

@gupta-ji6
Copy link

@gupta-ji6 gupta-ji6 commented Feb 8, 2021

@vjsingh yes, it's working for me with CRA 4 as well.

Here are my dependencies version.

dependencies : {
  "react-scripts": "4.0.1",
  "@craco/craco": "6.1.0",
  "craco-plugin-scoped-css": "1.1.0",
  "react": "17.0.1",
  "react-dom": "17.0.1",
  ...otherDependencies
}

Note that you shouldn't be using React.lazy() with Suspense to lazy load components. Otherwise, it was making several chunks even with this config of craco.

Also, you're using craco build command to create a production build, right?

Hope this helps!

@vjsingh
Copy link

@vjsingh vjsingh commented Feb 8, 2021

@gupta-ji6 Thanks for the response!

I have the exact same dependency versions as you, and yes I'm using craco build.

I'm not sure what I'm doing wrong, since as I mentioned src/index.js just has that single import line, and the imported file just has a single console log. We're not using React.lazy or Suspense.

We are trying to use multiple entry points as described here: https://blog.logrocket.com/multiple-entry-points-in-create-react-app-without-ejecting/. I removed all of that code now when testing though

@gupta-ji6
Copy link

@gupta-ji6 gupta-ji6 commented Feb 8, 2021

@gupta-ji6 Thanks for the response!

I have the exact same dependency versions as you, and yes I'm using craco build.

I'm not sure what I'm doing wrong, since as I mentioned src/index.js just has that single import line, and the imported file just has a single console log. We're not using React.lazy or Suspense.

We are trying to use multiple entry points as described here: https://blog.logrocket.com/multiple-entry-points-in-create-react-app-without-ejecting/. I removed all of that code now when testing though

@vjsingh I see. Maybe try opening an issue on craco repo to get help in resolving this.
If possible, make a sample codesandbox/repo with exact dependencies where we can test this. I'll also inspect what's wrong.

@vjsingh
Copy link

@vjsingh vjsingh commented Feb 8, 2021

@gupta-ji6 Filed - gsoft-inc/craco#248

Thank you!

@jpgilchrist
Copy link

@jpgilchrist jpgilchrist commented Feb 11, 2021

@gupta-ji6 @rutikwankhade @leoloso

Any chance you know what's going on here. Your configuration makes sense, but it's not working for me for some reason and I'm stumped.

Dependencies:
"@craco/craco": "^6.1.1",
"react-scripts": "4.0.1", // I also tried 4.0.2
"react": "^17.0.1",
"react-dom": "^17.0.1",

// craco.config.js
const { whenProd, POSTCSS_MODES } = require("@craco/craco");
const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const findRule = (rules, matcher) => {
  for (const rule of rules) {
    if (Array.isArray(rule.oneOf)) {
      return findRule(rule.oneOf, matcher);
    }
    if (rule.test && matcher.match(rule.test)) {
      if (!rule.exclude || !matcher.match(rule.exclude)) {
        return rule;
      }
    }
  }
  return undefined;
};

const replaceLoader = (rule, matchLoaderString, replacement) => {
  if (!rule.use) {
    return;
  }
  const loaderIndex = rule.use.findIndex((definition) =>
    typeof definition === "string"
      ? matchLoaderString === definition
      : matchLoaderString === definition.loader
  );
  rule.use[loaderIndex] = replacement;
};

module.exports = {
  style: {
    postcss: {
      mode: POSTCSS_MODES.file,
    },
  },
  webpack: {
    configure: {
      output: {
        filename: "static/js/[name].bundle.js",
      },
      optimization: {
        runtimeChunk: false,
        splitChunks: {
          chunks() {
            return false;
          },
        },
      },
    },
  },
  plugins: [
    {
      plugin: {
        overrideWebpackConfig: ({ webpackConfig }) => {
          whenProd(() => {
            // Remove InlineChunkHtmlPlugin because we don't want/have chunks
            const inlineChunkHtmlPluginIndex = webpackConfig.plugins.findIndex(
              (plugin) => plugin.constructor.name === InlineChunkHtmlPlugin.name
            );
            webpackConfig.plugins.splice(inlineChunkHtmlPluginIndex, 1);

            // Remove MiniCssExtractPlugin so they are bundled in JS
            const miniCssExtractPluginIndex = webpackConfig.plugins.findIndex(
              (plugin) => plugin.constructor.name === MiniCssExtractPlugin.name
            );
            webpackConfig.plugins.splice(miniCssExtractPluginIndex, 1);

            // Replace the MiniCssExtractPlugin.loader with style-loader for .css rule
            replaceLoader(
              findRule(webpackConfig.module.rules, "index.css"),
              MiniCssExtractPlugin.loader,
              require.resolve("style-loader")
            );

            // Replace the MiniCssExtractPlugin.loader with style-loader for .module.css rule
            replaceLoader(
              findRule(webpackConfig.module.rules, "index.module.css"),
              MiniCssExtractPlugin.loader,
              require.resolve("style-loader")
            );
            console.log("webpackconfig", webpackConfig);
          });
          return webpackConfig;
        },
      },
      options: {},
    },
  ],
};

Output:

File sizes after gzip:

  110.05 KB  build/static/js/main.bundle.js
  1.59 KB    build/static/js/1.3b939efc.chunk.js
// webpack.config after being overridden
{
  mode: 'production',
  bail: true,
  devtool: 'source-map',
  entry: '/home/jpgilchrist/TempProjects/cra-widgets/src/index.jsx',
  output: {
    path: '/home/jpgilchrist/TempProjects/cra-widgets/build',
    pathinfo: false,
    filename: 'static/js/[name].bundle.js',
    futureEmitAssets: true,
    chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
    publicPath: '/',
    devtoolModuleFilenameTemplate: [Function (anonymous)],
    jsonpFunction: 'webpackJsonpcra-widgets',
    globalObject: 'this'
  },
  optimization: {
    minimize: true,
    minimizer: [ [TerserPlugin], [OptimizeCssAssetsWebpackPlugin] ],
    splitChunks: { chunks: [Function: chunks], name: false },
    runtimeChunk: false
  },
  resolve: {
    modules: [
      'node_modules',
      '/home/jpgilchrist/TempProjects/cra-widgets/node_modules'
    ],
    extensions: [
      '.web.mjs', '.mjs',
      '.web.js',  '.js',
      '.web.ts',  '.ts',
      '.web.tsx', '.tsx',
      '.json',    '.web.jsx',
      '.jsx'
    ],
    alias: { 'react-native': 'react-native-web' },
    plugins: [ [Object], [ModuleScopePlugin] ]
  },
  resolveLoader: { plugins: [ [Object] ] },
  module: { strictExportPresence: true, rules: [ [Object], [Object] ] },
  plugins: [
    HtmlWebpackPlugin {
      options: [Object],
      childCompilerHash: undefined,
      assetJson: undefined,
      hash: undefined,
      version: 4
    },
    InterpolateHtmlPlugin {
      htmlWebpackPlugin: [Function],
      replacements: [Object]
    },
    ModuleNotFoundPlugin {
      appPath: '/home/jpgilchrist/TempProjects/cra-widgets',
      yarnLockFile: undefined,
      useYarnCommand: [Function: bound useYarnCommand],
      getRelativePath: [Function: bound getRelativePath],
      prettierError: [Function: bound prettierError]
    },
    DefinePlugin { definitions: [Object] },
    ManifestPlugin { opts: [Object] },
    IgnorePlugin {
      options: [Object],
      checkIgnore: [Function: bound checkIgnore]
    },
    ForkTsCheckerWebpackPlugin {
      eslint: false,
      eslintOptions: {},
      tsconfigPath: undefined,
      compiler: undefined,
      started: undefined,
      elapsed: undefined,
      cancellationToken: undefined,
      isWatching: false,
      checkDone: false,
      compilationDone: false,
      diagnostics: [],
      lints: [],
      eslintVersion: undefined,
      startAt: 0,
      nodeArgs: [],
      options: [Object],
      ignoreDiagnostics: [],
      ignoreLints: [],
      ignoreLintWarnings: false,
      reportFiles: [Array],
      logger: [Object [console]],
      silent: true,
      async: false,
      checkSyntacticErrors: true,
      resolveModuleNameModule: undefined,
      resolveTypeReferenceDirectiveModule: undefined,
      memoryLimit: 2048,
      formatter: [Function: formatter],
      rawFormatter: [Function: rawFormatter],
      emitCallback: [Function: noopEmitCallback],
      doneCallback: [Function: doneCallback],
      typescript: [Object],
      typescriptPath: '/home/jpgilchrist/TempProjects/cra-widgets/node_modules/typescript/lib/typescript.js',
      typescriptVersion: '4.1.5',
      tsconfig: '/home/jpgilchrist/TempProjects/cra-widgets/tsconfig.json',
      compilerOptions: {},
      vue: [Object],
      useTypescriptIncrementalApi: true,
      measureTime: false
    },
    ESLintWebpackPlugin {
      options: [Object],
      run: [Function: bound run] AsyncFunction
    }
  ],
  node: {
    module: 'empty',
    dgram: 'empty',
    dns: 'mock',
    fs: 'empty',
    http2: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  },
  performance: false
}

I am using typescript.. not sure if that plays into it?

@jpgilchrist
Copy link

@jpgilchrist jpgilchrist commented Feb 11, 2021

FWIW I also just created a brand new project and only did the following:

  1. npx create-react-app simple-test
  2. yarn add @craco/craco
  3. updated package.json to use craco instead of react-scripts
  4. update craco.config.js with the aforementioned solutions above (will paste below for completeness)
  5. yarn build
// craco.config.js
module.exports = {
  webpack: {
    configure: {
      output: {
        filename: "static/js/[name].js",
      },
      optimization: {
        runtimeChunk: false,
        splitChunks: {
          chunks(chunk) {
            return false;
          },
        },
      },
    },
  },
  plugins: [
    {
      plugin: {
        overrideWebpackConfig: ({ webpackConfig }) => {
          webpackConfig.plugins[5].options.filename = "static/css/[name].css";
          return webpackConfig;
        },
      },
      options: {},
    },
  ],
};

And the output is:

Compiled successfully.

File sizes after gzip:

  42.47 KB  build/static/js/main.js
  1.59 KB   build/static/js/1.635bd783.chunk.js
  518 B     build/static/css/main.css

EDIT: I found the problem. I started to inspect the chunk that was generated and I noticed that the contents where entirely coming from the default included reportWebVitals. This makes sense because report web vitals is using a lazy import

    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
      getCLS(onPerfEntry);
      getFID(onPerfEntry);
      getFCP(onPerfEntry);
      getLCP(onPerfEntry);
      getTTFB(onPerfEntry);
    });

Which is forcing a chunk to be formed.

So, the solution is to remove it from the index.js file completely and it will not generate an extra chunk. Or just leave it there and don't reference it in w/e production build pipeline you have.

@amerllica
Copy link

@amerllica amerllica commented Feb 24, 2021

Just thought I'd post my temporary solution to this in case it helps anyone. A bit hacky, but at least one doesn't have to eject:

const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
let config = defaults.__get__('config');

config.optimization.splitChunks = {
    cacheGroups: {
        default: false,
    },
};

config.optimization.runtimeChunk = false;

Save that as build-non-split.js or whatever and amend your package.json like:

"build": "./scripts/build-non-split.js",

Actually, "build": "node ./scripts/build-non-split.js",

@elsurudo
Copy link

@elsurudo elsurudo commented Mar 7, 2021

I had trouble with all of the above solutions, so my hacky solution was to read the manifest, and just concat the JS files together myself.

scripts/build-bundle.js:

const { exec } = require('child_process');

const assetManifest = require('../build/asset-manifest.json');
const files = assetManifest.entrypoints.filter((path) => path.endsWith('js')).map((path) => `build/${path}`).join(' ');
console.log(`Joining ${files}`);

// Just shorthand for joining with a newline between the files
exec(`awk 1 ${files} > dist/build/static/js/bundle.min.js`);

Then, in my package.json:

"scripts": {
    "start": "react-scripts start",
    "build": "npm run build:react && npm run build:bundle",
    "build:react": "react-scripts build",
    "build:bundle": "node scripts/build-bundle.js",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },

@teddybradford
Copy link

@teddybradford teddybradford commented Apr 12, 2021

  plugins: [
    {
      plugin: {
        overrideWebpackConfig: ({ webpackConfig }) => {
          webpackConfig.plugins[5].options.filename = 'static/css/[name].css';
          return webpackConfig;
        },
      },
      options: {}
    }
  ],

This worked great! I modified it a little to find MiniCssExtractPlugin by name in case the CRA webpack config plugin order changes in the future.

  plugins: [
    {
      plugin: {
        overrideWebpackConfig: ({ webpackConfig }) => {
          const miniCssExtractPluginIndex = webpackConfig.plugins.findIndex(
            (plugin) => plugin.constructor.name === 'MiniCssExtractPlugin'
          );

          if (miniCssExtractPluginIndex > -1) {
            webpackConfig.plugins[miniCssExtractPluginIndex].options.filename =
              'static/css/[name].css';
          }

          return webpackConfig;
        },
      },
      options: {},
    },
  ],

rishabhgrg added a commit to rishabhgrg/Portal that referenced this issue Jun 16, 2021
no issues

- updates Portal build script to use rewired react-scripts config
- updated config handles css embed as well as output location/name for portal bundle as part of cra build
- makes extra webpack bundling redundant for now
- updates dev mode to map the portal source map useful for testing build version locally
- updates custom webpack config with copy plugin for future use

refs -
facebook/create-react-app#5306 (comment)
https://gist.github.com/phdesign/3fd306db2bc53f6368e6f0f73bbeff19
rishabhgrg added a commit to TryGhost/Portal that referenced this issue Jun 16, 2021
no issues

- updates Portal build script to use rewired react-scripts config
- updated config handles css embed as well as output location/name for portal bundle as part of cra build
- makes extra webpack bundling redundant for now
- updates dev mode to map the portal source map useful for testing build version locally
- updates custom webpack config with copy plugin for future use

refs -
facebook/create-react-app#5306 (comment)
https://gist.github.com/phdesign/3fd306db2bc53f6368e6f0f73bbeff19
@avgurf
Copy link

@avgurf avgurf commented Aug 17, 2021

Hey @jpgilchrist & @teddybradford i just added this miniCssExtractPluginIndex change:

  plugins: [
    {
      plugin: {
        overrideWebpackConfig: ({ webpackConfig }) => {
          webpackConfig.plugins[5].options.filename = 'static/css/[name].css';
          return webpackConfig;
        },
      },
      options: {}
    }
  ],

This worked great! I modified it a little to find MiniCssExtractPlugin by name in case the CRA webpack config plugin order changes in the future.

  plugins: [
    {
      plugin: {
        overrideWebpackConfig: ({ webpackConfig }) => {
          const miniCssExtractPluginIndex = webpackConfig.plugins.findIndex(
            (plugin) => plugin.constructor.name === 'MiniCssExtractPlugin'
          );

          if (miniCssExtractPluginIndex > -1) {
            webpackConfig.plugins[miniCssExtractPluginIndex].options.filename =
              'static/css/[name].css';
          }

          return webpackConfig;
        },
      },
      options: {},
    },
  ],

but i get

Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

ENOENT: no such file or directory, open '/build/static/css/main.css'
error Command failed with exit code 1.

Thats what console.log(webpackConfig.plugins[miniCssExtractPluginIndex]) returns before the change:

MiniCssExtractPlugin {
  options: {
    filename: 'static/css/[name].[contenthash:8].css',
    moduleFilename: [Function: moduleFilename],
    ignoreOrder: false,
    chunkFilename: 'static/css/[name].[contenthash:8].chunk.css'
  }
}

EDIT: ok i've tested it now with a plain new CRA project + CRACO and it does seem to work.

HOWEVER when i use

   webpack: {
        configure: (webpackConfig)=> {
            return {
                ...webpackConfig,
                output: {
                  filename: "static/js/[name].js",
                },
                optimization: {
                  runtimeChunk: false,
                  splitChunks: {
                    chunks(chunk) {
                      return false;
                    },
                  },
                },
            }
        }
   }

This doesn't seem to work and i get the above error:

$ craco build
Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

ENOENT: no such file or directory, open '/test/build/static/css/main.css'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
v3.3
  
To do
Linked pull requests

Successfully merging a pull request may close this issue.

None yet