react-rails
react-rails copied to clipboard
code chunking impossible as application.js imports every component
- [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)
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?
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.
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 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.
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)
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, it seems like this issue is resolved. Closing it for now.