react-rails icon indicating copy to clipboard operation
react-rails copied to clipboard

code chunking impossible as application.js imports every component

Open krazyjakee opened this issue 8 years ago • 6 comments

  • [x] I have another issue to discuss.

The app is large. I use code chunking like this...

shared.js

new webpack.optimize.CommonsChunkPlugin({
      names: ['common'],
      chunks: [
        "components/a",
        "components/b",
        "components/c",
        "components/d",
        "components/e"
      ],
      minChunks: Infinity
    })

The application.js looks like this: https://github.com/reactjs/react-rails/blob/master/lib/generators/templates/server_rendering_pack.js

The common and chunks appear to be working correctly but are useless as application.js already loaded them all. If I comment out the context, react_ujs cannot find the components loaded in the common chunk.

application.js

// var componentRequireContext = require.context("components", true)
var ReactRailsUJS = require("react_ujs")
// ReactRailsUJS.useContext(componentRequireContext)

krazyjakee avatar May 23 '17 12:05 krazyjakee

This is a downside of how webpack's require.context works. In order to serve files at runtime, it has to load all the files that match the pattern. Otherwise, it can't serve them. I found it hard to grok, but poking through the compiled webpack output started to make it clear.

You'll have to cook up a new way loading components. For example, what if you loaded components from a list of require contexts? Then, each chunk could register its context as it is loaded. Something like:

// application.js
var ReactRailsUJS = require("react_ujs")
// attach the ujs to the global namespace so that later bundles can access it:
window.ReactRailsUJS = ReactRailsUJS

// Start with an empty list of require contexts:
ReactRailsUJS.requireContexts = []
// Implement `getConstructor` to try _each_ requireContext:
ReactRailsUJS.getConstructor = function(className) {
  var contextLength = ReactRailsUJS.requireContexts.length 
  var requireContext 
  var constructor 
  for (var i = 0; i < contextLength; i++) {
    // Try each context:
    requireContext = ReactRailsUJS.requireContexts[i]
    try {
      // Look up the classname in this context:
      constructor = requireContext("./" + className + ".js")
      // If there wasn't an error, we found it:
      return constructor  // TODO: normalize the exported module
    } catch (err) {
      // It wasn't in this context 
    }
    // Failed to find the constructor at all:
    throw new Error("Couldn't find " + className + " in any requireContext")
  }
}

Then, each chunk could add its context:

// components/a.js
var requireContext = require.context("components/a", true)
// Add to the list which was created by application.js
window.ReactRailsUJS.requireContexts.push(requireContext)

So as the application runs, contexts are added and lookups will be made against each one.

TODO: ReactRailsUJS does some normalization of the exported module, see https://github.com/reactjs/react-rails/blob/master/react_ujs/src/getConstructor/fromRequireContext.js#L13-L22

Does that seem like it might work in your case?

rmosolgo avatar May 23 '17 14:05 rmosolgo

Thank you very much.

So as I understand it...

  • application.js loads react_ujs with the extended code as you've written
  • components/a.js updates the react_ujs context with it's imports
  • common.js ?

So to start with, in my common.js (path = app/javascript/common.js) I have...

var requireContext = require.context("components/common", true)
window.ReactRailsUJS.requireContexts.push(requireContext)

I moved my imports into a new file also called common.js in the components folder but I get this error.

client:119 ./app/javascript/packs/common.js
Module not found: Error: Can't resolve 'components/common' in 'app/javascript/packs'

That file definitely exists in that folder.

I may be mistaken but I can't help feeling this isn't an edge-case requirement. Surely it's not likely that a website/webapp will want all their assets crammed into a single monolith file by default?

Thanks again for your assistance on this.

krazyjakee avatar May 23 '17 15:05 krazyjakee

single monolith file

This is the Rails Way :tm: 😆 Sure, you have one big download, but then the JS is cached for the rest of the time, until a redeploy.

Can't resolve 'components/common' in 'app/javascript/packs'

it's looking for app/javascript/packs/components/common. Does that exist? Or is it app/javascript/components/common ? (The differents is the packs/ part)

rmosolgo avatar May 23 '17 15:05 rmosolgo

@rmosolgo lol, but sadly we're talking 3mb of javascript compressed.

app/javascript/packs/components/common.js exists. That is the context I want to use, just a single file. I tried putting it in an array but that didn't work. The docs say I can use regex var requireContext = require.context("components", true, /common\.js/) but the components are not found. application.js:35 Uncaught Error: Couldn't find AutoComplete in any requireContext

AutoComplete is imported in common.js

EDIT: I understand require.context was a good way to grab ALL components, but ReactRailsUJS needs to have specific files pushed in.

krazyjakee avatar May 23 '17 15:05 krazyjakee

The second argument to require.context is whether or not to include subdirectories. I was able to get chunking working on my build, using the exact same configuration from the React Router 4 documentation. When you write componentRequireContext in a pack file, write:

componentRequireContext = require.context("components", false)

kyleramirez avatar Jul 21 '17 14:07 kyleramirez

Going to make a branch of the example app with code chunking and add a wiki article based on content in this thread.

Thanks all, do any of you happen to have a small example I can link? https://github.com/reactjs/react-rails/wiki/Code-Chunking-with-React-Rails

BookOfGreg avatar Nov 06 '17 23:11 BookOfGreg

@BookOfGreg, it seems like this issue is resolved. Closing it for now.

alkesh26 avatar Oct 31 '22 12:10 alkesh26