Skip to content

Commit

Permalink
Expose all built-in options for getConstructor
Browse files Browse the repository at this point in the history
Quoting reactjs/react-rails#264 (comment)

> Regarding `Encountered error "#<ExecJS::ProgramError: Invariant
> Violation: Element type is invalid: ...`:
>
> I think one of the core issues is that [module lookup uses
> `try...catch`](https://github.com/reactjs/react-rails/blob/master/react_ujs/src/getConstructor/fromRequireContextWithGlobalFallback.js#L11-L23).
> While the errors are logged to the console shim, that typically doesn't
> help as a later error (such as the invariant violation) will lead to a
> fatal error (triggering a 500). If that could be refactored to be a bit
> more intentional based on environment (instead of just reacting based on
> exceptions, or at the very least, throwing if the caught exception isn't
> very specific)

This enables us to easily override `getConstructor` to not use global
fallback, avoiding the all-consuming `try...catch`.
  • Loading branch information
goldapple911 authored and justin808 committed Sep 30, 2022
1 parent 6b5ee74 commit 4338da6
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 119 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,8 @@

#### New Features

- Expose alternative implementations for `ReactUJS.getConstructor` #1050

#### Deprecation

#### Bug Fixes
Expand Down
20 changes: 17 additions & 3 deletions README.md
Expand Up @@ -406,13 +406,27 @@ delete window.Turbolinks;

### `getConstructor`

Components are loaded with `ReactRailsUJS.getConstructor(className)`. This function has two built-in implementations:
Components are loaded with `ReactRailsUJS.getConstructor(className)`. This function has two default implementations, depending on if you're using the asset pipeline or Webpacker:

- On the asset pipeline, it looks up `className` in the global namespace.
- On Webpacker, it `require`s files and accesses named exports, as described in [Get started with Webpacker](#get-started-with-webpacker).
- On the asset pipeline, it looks up `className` in the global namespace (`ReactUJS.constructorFromGlobal`).
- On Webpacker, it `require`s files and accesses named exports, as described in [Get started with Webpacker](#get-started-with-webpacker), falling back to the global namespace (`ReactUJS.constructorFromRequireContextWithGlobalFallback`).

You can override this function to customize the mapping of name-to-constructor. [Server-side rendering](#server-side-rendering) also uses this function.

For example, the fallback behavior of
`ReactUJS.constructorFromRequireContextWithGlobalFallback` can sometimes make
server-side rendering errors hard to debug as it will swallow the original error
(more info
[here](https://github.com/reactjs/react-rails/issues/264#issuecomment-552326663)).
`ReactUJS.constructorFromRequireContext` is provided for this reason. You can
use it like so:

```js
// Replaces calls to `ReactUJS.useContext`
ReactUJS.getConstructor = ReactUJS.constructorFromRequireContext(require.context('components', true));
```


## Server-Side Rendering

You can render React components inside your Rails server with `prerender: true`:
Expand Down
123 changes: 65 additions & 58 deletions lib/assets/javascripts/react_ujs.js
Expand Up @@ -7,7 +7,7 @@
exports["ReactRailsUJS"] = factory(require("react-dom"), require("react"), require("react-dom/server"));
else
root["ReactRailsUJS"] = factory(root["ReactDOM"], root["React"], root["ReactDOMServer"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_5__, __WEBPACK_EXTERNAL_MODULE_6__) {
})(this, function(__WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_6__, __WEBPACK_EXTERNAL_MODULE_7__) {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
Expand Down Expand Up @@ -73,7 +73,7 @@ return /******/ (function(modules) { // webpackBootstrap
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 7);
/******/ return __webpack_require__(__webpack_require__.s = 8);
/******/ })
/************************************************************************/
/******/ ([
Expand Down Expand Up @@ -108,17 +108,47 @@ module.exports = function(className) {
/* 1 */
/***/ (function(module, exports) {

module.exports = __WEBPACK_EXTERNAL_MODULE_1__;
// Load React components by requiring them from "components/", for example:
//
// - "pages/index" -> `require("components/pages/index")`
// - "pages/show.Header" -> `require("components/pages/show").Header`
// - "pages/show.Body.Content" -> `require("components/pages/show").Body.Content`
//
module.exports = function(reqctx) {
return function(className) {
var parts = className.split(".")
var filename = parts.shift()
var keys = parts
// Load the module:
var component = reqctx("./" + filename)
// Then access each key:
keys.forEach(function(k) {
component = component[k]
})
// support `export default`
if (component.__esModule) {
component = component["default"]
}
return component
}
}


/***/ }),
/* 2 */
/***/ (function(module, exports) {

module.exports = __WEBPACK_EXTERNAL_MODULE_2__;

/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {

var nativeEvents = __webpack_require__(8)
var pjaxEvents = __webpack_require__(9)
var turbolinksEvents = __webpack_require__(10)
var turbolinksClassicDeprecatedEvents = __webpack_require__(12)
var turbolinksClassicEvents = __webpack_require__(11)
var nativeEvents = __webpack_require__(9)
var pjaxEvents = __webpack_require__(10)
var turbolinksEvents = __webpack_require__(11)
var turbolinksClassicDeprecatedEvents = __webpack_require__(13)
var turbolinksClassicEvents = __webpack_require__(12)

// see what things are globally available
// and setup event handlers to those things
Expand Down Expand Up @@ -170,14 +200,14 @@ module.exports = function(ujs) {


/***/ }),
/* 3 */
/* 4 */
/***/ (function(module, exports, __webpack_require__) {

// Make a function which:
// - First tries to require the name
// - Then falls back to global lookup
var fromGlobal = __webpack_require__(0)
var fromRequireContext = __webpack_require__(13)
var fromRequireContext = __webpack_require__(1)

module.exports = function(reqctx) {
var fromCtx = fromRequireContext(reqctx)
Expand All @@ -201,15 +231,15 @@ module.exports = function(reqctx) {


/***/ }),
/* 4 */
/* 5 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony export (immutable) */ __webpack_exports__["supportsHydration"] = supportsHydration;
/* harmony export (immutable) */ __webpack_exports__["reactHydrate"] = reactHydrate;
/* harmony export (immutable) */ __webpack_exports__["createReactRootLike"] = createReactRootLike;
const ReactDOM = __webpack_require__(1)
const ReactDOM = __webpack_require__(2)

function supportsHydration() {
return typeof ReactDOM.hydrate === "function" || typeof ReactDOM.hydrateRoot === "function"
Expand Down Expand Up @@ -238,29 +268,30 @@ function legacyReactRootLike(node) {


/***/ }),
/* 5 */
/* 6 */
/***/ (function(module, exports) {

module.exports = __WEBPACK_EXTERNAL_MODULE_5__;
module.exports = __WEBPACK_EXTERNAL_MODULE_6__;

/***/ }),
/* 6 */
/* 7 */
/***/ (function(module, exports) {

module.exports = __WEBPACK_EXTERNAL_MODULE_6__;
module.exports = __WEBPACK_EXTERNAL_MODULE_7__;

/***/ }),
/* 7 */
/* 8 */
/***/ (function(module, exports, __webpack_require__) {

var React = __webpack_require__(5)
var ReactDOM = __webpack_require__(1)
var ReactDOMServer = __webpack_require__(6)
var React = __webpack_require__(6)
var ReactDOM = __webpack_require__(2)
var ReactDOMServer = __webpack_require__(7)

var detectEvents = __webpack_require__(2)
var detectEvents = __webpack_require__(3)
var constructorFromGlobal = __webpack_require__(0)
var constructorFromRequireContextWithGlobalFallback = __webpack_require__(3)
const { supportsHydration, reactHydrate, createReactRootLike } = __webpack_require__(4)
var constructorFromRequireContext = __webpack_require__(1)
var constructorFromRequireContextWithGlobalFallback = __webpack_require__(4)
const { supportsHydration, reactHydrate, createReactRootLike } = __webpack_require__(5)

var ReactRailsUJS = {
// This attribute holds the name of component which should be mounted
Expand Down Expand Up @@ -321,6 +352,11 @@ var ReactRailsUJS = {
// the default is ReactRailsUJS.ComponentGlobal
getConstructor: constructorFromGlobal,

// Available for customizing `getConstructor`
constructorFromGlobal: constructorFromGlobal,
constructorFromRequireContext: constructorFromRequireContext,
constructorFromRequireContextWithGlobalFallback: constructorFromRequireContextWithGlobalFallback,

// Given a Webpack `require.context`,
// try finding components with `require`,
// then falling back to global lookup.
Expand Down Expand Up @@ -395,6 +431,7 @@ var ReactRailsUJS = {
detectEvents: function() {
detectEvents(this)
},

}

// These stable references are so that handlers can be added and removed:
Expand Down Expand Up @@ -429,7 +466,7 @@ module.exports = ReactRailsUJS


/***/ }),
/* 8 */
/* 9 */
/***/ (function(module, exports) {

module.exports = {
Expand All @@ -452,7 +489,7 @@ module.exports = {


/***/ }),
/* 9 */
/* 10 */
/***/ (function(module, exports) {

module.exports = {
Expand All @@ -472,7 +509,7 @@ module.exports = {


/***/ }),
/* 10 */
/* 11 */
/***/ (function(module, exports) {

module.exports = {
Expand All @@ -488,7 +525,7 @@ module.exports = {


/***/ }),
/* 11 */
/* 12 */
/***/ (function(module, exports) {

module.exports = {
Expand All @@ -506,7 +543,7 @@ module.exports = {


/***/ }),
/* 12 */
/* 13 */
/***/ (function(module, exports) {

module.exports = {
Expand All @@ -526,36 +563,6 @@ module.exports = {
}


/***/ }),
/* 13 */
/***/ (function(module, exports) {

// Load React components by requiring them from "components/", for example:
//
// - "pages/index" -> `require("components/pages/index")`
// - "pages/show.Header" -> `require("components/pages/show").Header`
// - "pages/show.Body.Content" -> `require("components/pages/show").Body.Content`
//
module.exports = function(reqctx) {
return function(className) {
var parts = className.split(".")
var filename = parts.shift()
var keys = parts
// Load the module:
var component = reqctx("./" + filename)
// Then access each key:
keys.forEach(function(k) {
component = component[k]
})
// support `export default`
if (component.__esModule) {
component = component["default"]
}
return component
}
}


/***/ })
/******/ ]);
});

0 comments on commit 4338da6

Please sign in to comment.