From 4338da69345588d9ad8375c094499ae653a0858c Mon Sep 17 00:00:00 2001 From: kevin Date: Thu, 9 Jan 2020 23:57:31 -0800 Subject: [PATCH] Expose all built-in options for `getConstructor` Quoting https://github.com/reactjs/react-rails/issues/264#issuecomment-552326663 > Regarding `Encountered error "# 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`. --- CHANGELOG.md | 2 + README.md | 20 ++++- lib/assets/javascripts/react_ujs.js | 123 +++++++++++++++------------- react_ujs/dist/react_ujs.js | 123 +++++++++++++++------------- react_ujs/index.js | 7 ++ 5 files changed, 156 insertions(+), 119 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7f4c84..5a77b94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ #### New Features +- Expose alternative implementations for `ReactUJS.getConstructor` #1050 + #### Deprecation #### Bug Fixes diff --git a/README.md b/README.md index 3bb4b34..6fab04d 100644 --- a/README.md +++ b/README.md @@ -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`: diff --git a/lib/assets/javascripts/react_ujs.js b/lib/assets/javascripts/react_ujs.js index 1cfe8b4..8bde1d6 100644 --- a/lib/assets/javascripts/react_ujs.js +++ b/lib/assets/javascripts/react_ujs.js @@ -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 = {}; @@ -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); /******/ }) /************************************************************************/ /******/ ([ @@ -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 @@ -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) @@ -201,7 +231,7 @@ module.exports = function(reqctx) { /***/ }), -/* 4 */ +/* 5 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -209,7 +239,7 @@ 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" @@ -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 @@ -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. @@ -395,6 +431,7 @@ var ReactRailsUJS = { detectEvents: function() { detectEvents(this) }, + } // These stable references are so that handlers can be added and removed: @@ -429,7 +466,7 @@ module.exports = ReactRailsUJS /***/ }), -/* 8 */ +/* 9 */ /***/ (function(module, exports) { module.exports = { @@ -452,7 +489,7 @@ module.exports = { /***/ }), -/* 9 */ +/* 10 */ /***/ (function(module, exports) { module.exports = { @@ -472,7 +509,7 @@ module.exports = { /***/ }), -/* 10 */ +/* 11 */ /***/ (function(module, exports) { module.exports = { @@ -488,7 +525,7 @@ module.exports = { /***/ }), -/* 11 */ +/* 12 */ /***/ (function(module, exports) { module.exports = { @@ -506,7 +543,7 @@ module.exports = { /***/ }), -/* 12 */ +/* 13 */ /***/ (function(module, exports) { module.exports = { @@ -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 - } -} - - /***/ }) /******/ ]); }); \ No newline at end of file diff --git a/react_ujs/dist/react_ujs.js b/react_ujs/dist/react_ujs.js index 1cfe8b4..8bde1d6 100644 --- a/react_ujs/dist/react_ujs.js +++ b/react_ujs/dist/react_ujs.js @@ -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 = {}; @@ -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); /******/ }) /************************************************************************/ /******/ ([ @@ -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 @@ -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) @@ -201,7 +231,7 @@ module.exports = function(reqctx) { /***/ }), -/* 4 */ +/* 5 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -209,7 +239,7 @@ 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" @@ -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 @@ -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. @@ -395,6 +431,7 @@ var ReactRailsUJS = { detectEvents: function() { detectEvents(this) }, + } // These stable references are so that handlers can be added and removed: @@ -429,7 +466,7 @@ module.exports = ReactRailsUJS /***/ }), -/* 8 */ +/* 9 */ /***/ (function(module, exports) { module.exports = { @@ -452,7 +489,7 @@ module.exports = { /***/ }), -/* 9 */ +/* 10 */ /***/ (function(module, exports) { module.exports = { @@ -472,7 +509,7 @@ module.exports = { /***/ }), -/* 10 */ +/* 11 */ /***/ (function(module, exports) { module.exports = { @@ -488,7 +525,7 @@ module.exports = { /***/ }), -/* 11 */ +/* 12 */ /***/ (function(module, exports) { module.exports = { @@ -506,7 +543,7 @@ module.exports = { /***/ }), -/* 12 */ +/* 13 */ /***/ (function(module, exports) { module.exports = { @@ -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 - } -} - - /***/ }) /******/ ]); }); \ No newline at end of file diff --git a/react_ujs/index.js b/react_ujs/index.js index d78d1b7..4c4c991 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -4,6 +4,7 @@ var ReactDOMServer = require("react-dom/server") var detectEvents = require("./src/events/detect") var constructorFromGlobal = require("./src/getConstructor/fromGlobal") +var constructorFromRequireContext = require("./src/getConstructor/fromRequireContext") var constructorFromRequireContextWithGlobalFallback = require("./src/getConstructor/fromRequireContextWithGlobalFallback") const { supportsHydration, reactHydrate, createReactRootLike } = require("./src/renderHelpers") @@ -66,6 +67,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. @@ -140,6 +146,7 @@ var ReactRailsUJS = { detectEvents: function() { detectEvents(this) }, + } // These stable references are so that handlers can be added and removed: