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

Add support for multiple require.context with addition of useContexts #1144

Merged
merged 11 commits into from
Feb 14, 2023

Conversation

cymen
Copy link
Contributor

@cymen cymen commented Sep 3, 2021

Summary

In some cases, it is desirable to be able to have multiple require.context and attempt to resolve in each before falling back to global. This PR adds useContexts (in the pattern of useContext) which accepts an array of require.context and attempts to resolve the component/module in each context before falling back to global.

Other Information

I was not able to get the test suite to run so I have not been able to add tests yet.

@RiccardoMargiotta
Copy link
Contributor

Thanks @cymen, this looks really promising for the way I'm trying to split JS bundles by route, but still use server-side rendering. I ran into the same problem as #1034 where I couldn't use multiple files for SSR, even when I disabled sourcemaps.

Here's a small example from my current setup, with a number of smaller split bundles. At the moment, because of the lack of SSR across all entry points, I've only split out components that I'm happy to be client-side rendered (prerender: false).

Before

server_rendering.js

var componentRequireContext = require.context('components', true);
var ReactRailsUJS = require('react_ujs');
ReactRailsUJS.useContext(componentRequireContext);

enquiry.js

var componentRequireContext = require.context('enquiry', true);
var ReactRailsUJS = require('react_ujs');
ReactRailsUJS.useContext(componentRequireContext);

search.js

var componentRequireContext = require.context('search', true);
var ReactRailsUJS = require('react_ujs');
ReactRailsUJS.useContext(componentRequireContext);

After

server_rendering.js

var componentsRequireContext = require.context('components', true);
var enquiryRequireContext = require.context('enquiry', true);
var searchRequireContext = require.context('search', true);

var ReactRailsUJS = require('react_ujs');
ReactRailsUJS.useContexts([
  componentsRequireContext,
  enquiryRequireContext,
  searchRequireContext
]);

No changes to enquiry or search.


I built a version of the package with your code, and I'm happy to say this lead to components from the enquiry and search entry points being bundled into server_rendering.js, allowing me to switch components from those bundles back to SSR (prerender: true). I was also able to run the test suite, and it doesn't seem like your changes have broken any of the existing tests. 👍

Hey @BookOfGreg, hope you're doing OK! This is a pretty useful addition. 😃

README.md Outdated
@@ -161,6 +161,8 @@ ReactRailsUJS.useContext(myCustomContext)

If `require` fails to find your component, [`ReactRailsUJS`](#ujs) falls back to the global namespace, described in [Use with Asset Pipeline](#use-with-asset-pipeline).

In some cases, having multiple `require.context` may be desired. If so, an array of `require.context` can be passed to `ReactRailsUJS.useContexts`. With an array of contexts, an attempt will be made to `require` the component from each context before falling back to the global namespace as described above.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you provide a more concrete example?

Or maybe point to an example in the codebase?

@justin808
Copy link
Collaborator

@cymen, thanks for your contribution. Sorry for the delays to get this merged and shipped. Looks useful!

Please:

  1. rebase on master
  2. add unit tests
  3. add a CHANGELOG.md entry
  4. improve the clarity of the docs

Thanks!

@alkesh26
Copy link
Collaborator

alkesh26 commented Oct 7, 2022

@cymen, Do you have the availability to work on the changes suggested by @justin808? Please resolve the conflicts.

@cymen, thanks for your contribution. Sorry for the delays to get this merged and shipped. Looks useful!

Please:

  1. rebase on master
  2. add unit tests
  3. add a CHANGELOG.md entry
  4. improve the clarity of the docs

Thanks!

@justin808 justin808 added this to the 2.7 milestone Oct 19, 2022
README.md Outdated
@@ -161,6 +161,8 @@ ReactRailsUJS.useContext(myCustomContext)

If `require` fails to find your component, [`ReactRailsUJS`](#ujs) falls back to the global namespace, described in [Use with Asset Pipeline](#use-with-asset-pipeline).

In some cases, having multiple `require.context` may be desired. If so, an array of `require.context` can be passed to `ReactRailsUJS.useContexts`. With an array of contexts, an attempt will be made to `require` the component from each context before falling back to the global namespace as described above.
Copy link
Contributor

@RiccardoMargiotta RiccardoMargiotta Nov 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@justin808 Had a go at adding a specific use case for multiple contexts, what do you think? @cymen not sure if there's a different example you had in mind.

Suggested change
In some cases, having multiple `require.context` may be desired. If so, an array of `require.context` can be passed to `ReactRailsUJS.useContexts`. With an array of contexts, an attempt will be made to `require` the component from each context before falling back to the global namespace as described above.
In some cases, having multiple `require.context` entries may be desired. In a larger application, you might find it helpful to split your JavaScript by routes/controllers to avoid serving unused components and improve your site performance by keeping bundles smaller. For example, you might have separate bundles for homepage, search, and checkout routes. In that scenario, you can add an array of `require.context` component directory paths via `useContexts` to `server_rendering.js`, to allow for [Server-Side Rendering](#server-side-rendering) across your application
```js
// server_rendering.js
var homepageRequireContext = require.context('homepage', true);
var searchRequireContext = require.context('search', true);
var checkoutRequireContext = require.context('checkout', true);
var ReactRailsUJS = require('react_ujs');
ReactRailsUJS.useContexts([
homepageRequireContext,
searchRequireContext,
checkoutRequireContext
]);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the suggestion based on changes from my other PR to hopefully provide a clearer usage example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me (as mentioned elsewhere). Thanks for fleshing it out. I added another example use case with c5cd6c6 which is the one that sent me down this path of wanting multiple contexts (we've been using a forked version of react-rails in production for quite a while and it's been working well).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really interesting to hear your use case for this.

@justin808
Copy link
Collaborator

@RiccardoMargiotta do you have time to push a new PR that includes your suggestions?

@RiccardoMargiotta
Copy link
Contributor

@justin808 I've created #1221. Could you run the workflow please?

@cymen
Copy link
Contributor Author

cymen commented Nov 28, 2022

@RiccardoMargiotta Thanks for pushing this forward. I didn't mean to disappear for a while. That documentation update looks good to me.

@RiccardoMargiotta
Copy link
Contributor

No worries! As I said on the new PR, I'd be more than happy to continue on this feature on your existing PR here (but I couldn't work out a way to cherry-pick commits across forks, if that's even possible). Either way, I'm really excited that this is getting closer to a release, it'll be hugely helpful.

@cymen
Copy link
Contributor Author

cymen commented Nov 28, 2022

@RiccardoMargiotta Sure, I'm happy to try -- I rebased my branch to master and cherry picked in your updates. I'm going to reread the documentation changes as the use case I wrote this for is slightly different so it might be useful to add another example there.

Add additional use case for multiple require contexts to documentation.
@cymen
Copy link
Contributor Author

cymen commented Nov 28, 2022

Okay, I've updated the documentation for another use case -- it was a bit hard to condense the use case I wrote this originally for into a few sentences but hopefully it makes sense.

@justin808 Can you run the workflow on this PR?

Co-authored-by: Riccardo Margiotta <riccardo.margiotta@gmail.com>
Co-authored-by: Riccardo Margiotta <riccardo.margiotta@gmail.com>
@RiccardoMargiotta
Copy link
Contributor

Really interesting to hear your use-case for this, and great to know it's been working well in production for you for so long - definitely a good sign!

Thanks for getting it all sorted on your original branch. I'm really looking forward to working with this on my app, glad I could help nudge it forward a little. What do you think, @justin808?

@justin808
Copy link
Collaborator

  1. Should this PR have tests?
  2. What are the risks of this PR?
  3. We need an embedded example Rails app that demonstrates such new functionality, like https://github.com/shakacode/react_on_rails/tree/master/spec/dummy

@RiccardoMargiotta
Copy link
Contributor

@cymen It possibly got lost in the merge, but we're now missing line 9 of react_ujs/index.js.

var constructorFromRequireContextsWithGlobalFallback = require("./src/getConstructor/fromRequireContextsWithGlobalFallback")

Without this, we'll fail with:

ReferenceError: constructorFromRequireContextsWithGlobalFallback is not defined

@RiccardoMargiotta
Copy link
Contributor

@justin808

  1. It should. But it doesn't appear like we have any tests for the react_ujs JS library part of the codebase at present, and quite honestly I wouldn't know where to begin.
  2. I think the risks are relatively low, given that it's an entirely new function based on an existing one. We won't have changed any existing use of the original function, people will have to opt-in to making use of this new feature.
  3. I'll try to create a sample app. Not something I've done before for a project like this, but I'll try to follow existing examples.

@cymen
Copy link
Contributor Author

cymen commented Jan 22, 2023

@RiccardoMargiotta Oops, I think that did go missing in the merge. I've fixed that. I'll take a look at the merge conflict on the changelog.

@cymen
Copy link
Contributor Author

cymen commented Jan 22, 2023

I took a look at adding testings -- I saw the test/dummy_webpacker3 directory and tried running that (after updating the ffi gem in order to get this running on a Mac M1). My thinking being that making a copy of this and using multiple contexts in the copy would be one way to go. However, it looks like the dummy Rails apps are not working.

For test/dummy_webpacker3 when running bundle exec rails server and attempting to access URL http://localhost:3000/pages/hi in a browser:

uninitialized constant PagesController::WebpackerHelpers
Rails.root: /Users/cymen/dev/react-rails/test/dummy_webpacker3

[Application Trace](http://localhost:3000/pages/hi#) | [Framework Trace](http://localhost:3000/pages/hi#) | [Full Trace](http://localhost:3000/pages/hi#)
[app/controllers/pages_controller.rb:2:in `<class:PagesController>'](http://localhost:3000/pages/hi#)
[app/controllers/pages_controller.rb:1:in `<top (required)>'](http://localhost:3000/pages/hi#)

For test/dummy_sprockets when running bundle exec rails server:

~/dev/react-rails/test/dummy_sprockets (master) % bundle exec rails server
Traceback (most recent call last):
	11: from bin/rails:4:in `<main>'
	10: from bin/rails:4:in `require'
	 9: from /Users/cymen/.rbenv/versions/2.7.6/lib/ruby/gems/2.7.0/gems/railties-6.0.2.1/lib/rails/commands.rb:18:in `<top (required)>'
	 8: from /Users/cymen/.rbenv/versions/2.7.6/lib/ruby/gems/2.7.0/gems/railties-6.0.2.1/lib/rails/command.rb:46:in `invoke'
	 7: from /Users/cymen/.rbenv/versions/2.7.6/lib/ruby/gems/2.7.0/gems/railties-6.0.2.1/lib/rails/command/base.rb:69:in `perform'
	 6: from /Users/cymen/.rbenv/versions/2.7.6/lib/ruby/gems/2.7.0/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
	 5: from /Users/cymen/.rbenv/versions/2.7.6/lib/ruby/gems/2.7.0/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
	 4: from /Users/cymen/.rbenv/versions/2.7.6/lib/ruby/gems/2.7.0/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
	 3: from /Users/cymen/.rbenv/versions/2.7.6/lib/ruby/gems/2.7.0/gems/railties-6.0.2.1/lib/rails/commands/server/server_command.rb:138:in `perform'
	 2: from /Users/cymen/.rbenv/versions/2.7.6/lib/ruby/gems/2.7.0/gems/railties-6.0.2.1/lib/rails/commands/server/server_command.rb:138:in `tap'
	 1: from /Users/cymen/.rbenv/versions/2.7.6/lib/ruby/gems/2.7.0/gems/railties-6.0.2.1/lib/rails/commands/server/server_command.rb:141:in `block in perform'
/Users/cymen/dev/react-rails/test/dummy_sprockets/config/application.rb:9:in `<top (required)>': uninitialized constant SprocketsHelpers (NameError)

I agree with @RiccardoMargiotta that is hard to know where to start. I also agree that there is very little risk because we're expanding the API by adding useContexts not changing the existing behavior by modifying the current useContext.

@justin808
Copy link
Collaborator

@cymen and @RiccardoMargiotta, should we merge?

I can merge and put this into a 2.7 release.

@cymen
Copy link
Contributor Author

cymen commented Feb 14, 2023

@justin808 Sure, I think it's ready. Thanks!

@RiccardoMargiotta
Copy link
Contributor

@justin808 I'd be good with merging! Like Cymen, I've forked out my own version of the JS package with this change, and have been using it in production for a while.

For my use case of route-based packs, it's been working great.

@justin808 justin808 merged commit b6acd36 into reactjs:master Feb 14, 2023
@justin808
Copy link
Collaborator

Thanks @RiccardoMargiotta and @cymen!

@RiccardoMargiotta
Copy link
Contributor

Thanks @justin808! Appreciate your efforts on this project. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants