From 12fb16acacd8666e3232b2cb4252a0d4128d51d8 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Fri, 11 Apr 2025 14:31:55 +0000 Subject: [PATCH 1/3] Switch to named imports --- docs/api/javascript-api.md | 2 +- docs/getting-started.md | 8 +- ...ystem-based-automated-bundle-generation.md | 6 +- ...t-files-for-client-and-server-rendering.md | 8 +- docs/javascript/code-splitting.md | 6 +- docs/javascript/react-router.md | 2 +- .../app/javascript/packs/registration.js.tt | 4 +- .../app/javascript/packs/server-bundle.js | 4 +- lib/react_on_rails/packs_generator.rb | 4 +- node_package/src/ReactOnRails.client.ts | 276 +++++++++--------- node_package/src/ReactOnRails.full.ts | 16 +- node_package/src/ReactOnRails.node.ts | 8 +- node_package/src/ReactOnRailsRSC.ts | 10 +- node_package/src/clientStartup.ts | 6 + node_package/src/context.ts | 9 +- .../src/registerServerComponent/client.tsx | 4 +- .../src/registerServerComponent/server.rsc.ts | 4 +- .../src/registerServerComponent/server.tsx | 4 +- node_package/src/turbolinksUtils.ts | 7 +- node_package/tests/Authenticity.test.js | 2 +- node_package/tests/ReactOnRails.test.jsx | 2 +- .../registerServerComponent.client.test.jsx | 8 +- ...treamServerRenderedReactComponent.test.jsx | 14 +- .../pages/client_side_hello_world.html.erb | 4 +- .../pages/server_side_hello_world.html.erb | 4 +- .../pages/server_side_redux_app.html.erb | 4 +- .../server_side_redux_app_cached.html.erb | 2 +- .../app/views/pages/xhr_refresh.html.erb | 4 +- .../startup/ReduxSharedStoreApp.client.jsx | 4 +- spec/dummy/client/app/packs/client-bundle.js | 2 +- spec/dummy/client/app/packs/server-bundle.js | 2 +- .../app/startup/HelloWorldRehydratable.jsx | 4 +- .../startup/ReduxSharedStoreApp.client.jsx | 4 +- .../startup/ReduxSharedStoreApp.server.jsx | 4 +- spec/dummy/spec/packs_generator_spec.rb | 10 +- .../dummy/tests/react-on-rails.import.test.js | 2 +- .../tests/react-on-rails.require.test.js | 4 +- 37 files changed, 233 insertions(+), 235 deletions(-) diff --git a/docs/api/javascript-api.md b/docs/api/javascript-api.md index e17d9cfc30..20e9cf300b 100644 --- a/docs/api/javascript-api.md +++ b/docs/api/javascript-api.md @@ -5,7 +5,7 @@ Rails has built-in protection for Cross-Site Request Forgery (CSRF), see [Rails Documentation](http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf). To nicely utilize this feature in JavaScript requests, React on Rails provides two helpers that can be used as following for POST, PUT or DELETE requests: ```js -import ReactOnRails from 'react-on-rails'; +import * as ReactOnRails from 'react-on-rails'; // reads from DOM csrf token generated by Rails in <%= csrf_meta_tags %> csrfToken = ReactOnRails.authenticityToken(); diff --git a/docs/getting-started.md b/docs/getting-started.md index 265f964891..c4b256de38 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -90,9 +90,9 @@ issue. - The `component_name` parameter is a string matching the name you used to expose your React component globally. So, in the above examples, if you had a React component named "HelloWorld", you would register it with the following lines: ```js - import ReactOnRails from 'react-on-rails'; + import { register } from 'react-on-rails'; import HelloWorld from './HelloWorld'; - ReactOnRails.register({ HelloWorld }); + register({ HelloWorld }); ``` Exposing your component in this way allows you to reference the component from a Rails view. You can expose as many components as you like, but their names must be unique. See below for the details of how you expose your components via the React on Rails Webpack configuration. You may call `ReactOnRails.register` many times. @@ -133,8 +133,8 @@ This is how to expose a component to the `react_component` view helper. ```javascript // app/javascript/packs/hello-world-bundle.js import HelloWorld from '../components/HelloWorld'; -import ReactOnRails from 'react-on-rails'; -ReactOnRails.register({ HelloWorld }); +import { register } from 'react-on-rails'; +register({ HelloWorld }); ``` #### Different Server-Side Rendering Code (and a Server-Specific Bundle) diff --git a/docs/guides/file-system-based-automated-bundle-generation.md b/docs/guides/file-system-based-automated-bundle-generation.md index 4812818731..5ae60fb1ab 100644 --- a/docs/guides/file-system-based-automated-bundle-generation.md +++ b/docs/guides/file-system-based-automated-bundle-generation.md @@ -115,15 +115,15 @@ app/javascript: └── logo.svg ``` -Previously, many applications would use one pack (webpack entrypoint) for many components. In this example, the`application.js` file manually registers server components, `FooComponentOne`, `BarComponentOne` and `BarComponentTwo`. +Previously, many applications would use one pack (webpack entrypoint) for many components. In this example, the `application.js` file manually registers server components, `FooComponentOne`, `BarComponentOne` and `BarComponentTwo`. ```jsx -import ReactOnRails from 'react-on-rails'; +import { register } from 'react-on-rails'; import FooComponentOne from '../src/Foo/FooComponentOne'; import BarComponentOne from '../src/Foo/BarComponentOne'; import BarComponentTwo from '../src/Foo/BarComponentTwo'; -ReactOnRails.register({ FooComponentOne, BarComponentOne, BarComponentTwo }); +register({ FooComponentOne, BarComponentOne, BarComponentTwo }); ``` Your layout would contain: diff --git a/docs/guides/how-to-use-different-files-for-client-and-server-rendering.md b/docs/guides/how-to-use-different-files-for-client-and-server-rendering.md index 4e347c83ee..b60b3ab7f8 100644 --- a/docs/guides/how-to-use-different-files-for-client-and-server-rendering.md +++ b/docs/guides/how-to-use-different-files-for-client-and-server-rendering.md @@ -9,17 +9,17 @@ Many projects will have different entry points for client and server rendering. Your Client Entry can look like this: ```js -import ReactOnRails from 'react-on-rails/client'; +import { register } from 'react-on-rails/client'; import App from './ClientApp'; -ReactOnRails.register({ App }); +register({ App }); ``` So your Server Entry can look like: ```js -import ReactOnRails from 'react-on-rails'; +import { register } from 'react-on-rails'; import App from './ServerApp'; -ReactOnRails.register({ App }); +register({ App }); ``` Note that the only difference is in the imports. diff --git a/docs/javascript/code-splitting.md b/docs/javascript/code-splitting.md index 8b747300a2..b33ffbd1f7 100644 --- a/docs/javascript/code-splitting.md +++ b/docs/javascript/code-splitting.md @@ -39,7 +39,7 @@ Here's an example of how you might use this in practice: #### clientRegistration.js ```js -import ReactOnRails from 'react-on-rails/client'; +import * as ReactOnRails from 'react-on-rails/client'; import NavigationApp from './NavigationApp'; // Note that we're importing a different RouterApp than in serverRegistration.js @@ -57,7 +57,7 @@ ReactOnRails.register({ #### serverRegistration.js ```js -import ReactOnRails from 'react-on-rails'; +import * as ReactOnRails from 'react-on-rails'; import NavigationApp from './NavigationApp'; // Note that we're importing a different RouterApp than in clientRegistration.js @@ -76,7 +76,7 @@ Note that you should not register a renderer on the server, since there won't be #### RouterAppRenderer.jsx ```jsx -import ReactOnRails from 'react-on-rails/client'; +import * as ReactOnRails from 'react-on-rails/client'; import React from 'react'; import ReactDOM from 'react-dom'; import Router from 'react-router/lib/Router'; diff --git a/docs/javascript/react-router.md b/docs/javascript/react-router.md index 3cae7cbdf1..0af04d009a 100644 --- a/docs/javascript/react-router.md +++ b/docs/javascript/react-router.md @@ -46,7 +46,7 @@ import React from 'react'; import { renderToString } from 'react-dom/server'; import { StaticRouter } from 'react-router'; import { Provider } from 'react-redux'; -import ReactOnRails from 'react-on-rails'; +import * as ReactOnRails from 'react-on-rails'; // App.jsx from src/client/App.jsx import App from '../App'; diff --git a/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt b/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt index d20e720f2d..9a61949e5f 100644 --- a/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt +++ b/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt @@ -1,8 +1,8 @@ -import ReactOnRails from 'react-on-rails/client'; +import { register } from 'react-on-rails/client'; import <%= config[:component_name] %> from '<%= config[:app_relative_path] %>'; // This is how react_on_rails can see the HelloWorld in the browser. -ReactOnRails.register({ +register({ <%= config[:component_name] %>, }); diff --git a/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js b/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js index 7d764f1139..20ec58dad8 100644 --- a/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +++ b/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js @@ -1,8 +1,8 @@ -import ReactOnRails from 'react-on-rails'; +import { register } from 'react-on-rails'; import HelloWorld from '../bundles/HelloWorld/components/HelloWorldServer'; // This is how react_on_rails can see the HelloWorld in the browser. -ReactOnRails.register({ +register({ HelloWorld, }); diff --git a/lib/react_on_rails/packs_generator.rb b/lib/react_on_rails/packs_generator.rb index a2628caa91..6b1f593881 100644 --- a/lib/react_on_rails/packs_generator.rb +++ b/lib/react_on_rails/packs_generator.rb @@ -111,7 +111,7 @@ def pack_file_contents(file_path) relative_component_path = relative_component_path_from_generated_pack(file_path) <<~FILE_CONTENT.strip - import ReactOnRails from 'react-on-rails/client'; + import * as ReactOnRails from 'react-on-rails/client'; import #{registered_component_name} from '#{relative_component_path}'; ReactOnRails.register({#{registered_component_name}}); @@ -127,7 +127,7 @@ def create_server_pack def build_server_pack_content(component_on_server_imports, server_components, client_components) content = <<~FILE_CONTENT - import ReactOnRails from 'react-on-rails'; + import * as ReactOnRails from 'react-on-rails'; #{component_on_server_imports.join("\n")}\n FILE_CONTENT diff --git a/node_package/src/ReactOnRails.client.ts b/node_package/src/ReactOnRails.client.ts index 752fba1d4c..5a4b45dc46 100644 --- a/node_package/src/ReactOnRails.client.ts +++ b/node_package/src/ReactOnRails.client.ts @@ -3,7 +3,6 @@ import * as ClientStartup from './clientStartup.ts'; import { renderOrHydrateComponent, hydrateStore } from './ClientSideRenderer.ts'; import * as ComponentRegistry from './ComponentRegistry.ts'; import * as StoreRegistry from './StoreRegistry.ts'; -import buildConsoleReplay from './buildConsoleReplay.ts'; import createReactOutput from './createReactOutput.ts'; import * as Authenticity from './Authenticity.ts'; import type { @@ -16,185 +15,200 @@ import type { StoreGenerator, ReactOnRailsOptions, } from './types/index.ts'; -import reactHydrateOrRender from './reactHydrateOrRender.ts'; +import reactHydrateOrRenderInternal from './reactHydrateOrRender.ts'; -if (globalThis.ReactOnRails !== undefined) { +export { default as buildConsoleReplay } from './buildConsoleReplay.ts'; + +declare global { + /* eslint-disable no-var,vars-on-top,no-underscore-dangle */ + var __REACT_ON_RAILS_LOADED__: boolean; + /* eslint-enable no-var,vars-on-top,no-underscore-dangle */ +} + +if (globalThis.__REACT_ON_RAILS_LOADED__) { throw new Error(`\ The ReactOnRails value exists in the ${globalThis} scope, it may not be safe to overwrite it. This could be caused by setting Webpack's optimization.runtimeChunk to "true" or "multiple," rather than "single." Check your Webpack configuration. Read more at https://github.com/shakacode/react_on_rails/issues/1558.`); } +globalThis.__REACT_ON_RAILS_LOADED__ = true; + const DEFAULT_OPTIONS = { traceTurbolinks: false, turbo: false, }; -globalThis.ReactOnRails = { - options: {}, - - register(components: Record): void { - ComponentRegistry.register(components); - }, +let options: ReactOnRailsOptions = {}; - registerStore(stores: Record): void { - this.registerStoreGenerators(stores); - }, +// TODO: convert to re-exports if everything works fine +export function register(components: Record): void { + ComponentRegistry.register(components); +} - registerStoreGenerators(storeGenerators: Record): void { - if (!storeGenerators) { - throw new Error( - 'Called ReactOnRails.registerStoreGenerators with a null or undefined, rather than ' + - 'an Object with keys being the store names and the values are the store generators.', - ); - } +// eslint-disable-next-line @typescript-eslint/no-shadow +export function registerStoreGenerators(storeGenerators: Record): void { + if (!storeGenerators) { + throw new Error( + 'Called ReactOnRails.registerStoreGenerators with a null or undefined, rather than ' + + 'an Object with keys being the store names and the values are the store generators.', + ); + } - StoreRegistry.register(storeGenerators); - }, + StoreRegistry.register(storeGenerators); +} - getStore(name: string, throwIfMissing = true): Store | undefined { - return StoreRegistry.getStore(name, throwIfMissing); - }, +// eslint-disable-next-line @typescript-eslint/no-shadow +export function registerStore(stores: Record): void { + registerStoreGenerators(stores); +} - getOrWaitForStore(name: string): Promise { - return StoreRegistry.getOrWaitForStore(name); - }, +export function getStore(name: string, throwIfMissing = true): Store | undefined { + return StoreRegistry.getStore(name, throwIfMissing); +} - getOrWaitForStoreGenerator(name: string): Promise { - return StoreRegistry.getOrWaitForStoreGenerator(name); - }, +export function getOrWaitForStore(name: string): Promise { + return StoreRegistry.getOrWaitForStore(name); +} - reactHydrateOrRender(domNode: Element, reactElement: ReactElement, hydrate: boolean): RenderReturnType { - return reactHydrateOrRender(domNode, reactElement, hydrate); - }, +export function getOrWaitForStoreGenerator(name: string): Promise { + return StoreRegistry.getOrWaitForStoreGenerator(name); +} - setOptions(newOptions: Partial): void { - if (typeof newOptions.traceTurbolinks !== 'undefined') { - this.options.traceTurbolinks = newOptions.traceTurbolinks; +export function reactHydrateOrRender( + domNode: Element, + reactElement: ReactElement, + hydrate: boolean, +): RenderReturnType { + return reactHydrateOrRenderInternal(domNode, reactElement, hydrate); +} - // eslint-disable-next-line no-param-reassign - delete newOptions.traceTurbolinks; - } +export function setOptions(newOptions: Partial): void { + if (typeof newOptions.traceTurbolinks !== 'undefined') { + options.traceTurbolinks = newOptions.traceTurbolinks; - if (typeof newOptions.turbo !== 'undefined') { - this.options.turbo = newOptions.turbo; + // eslint-disable-next-line no-param-reassign + delete newOptions.traceTurbolinks; + } - // eslint-disable-next-line no-param-reassign - delete newOptions.turbo; - } + if (typeof newOptions.turbo !== 'undefined') { + options.turbo = newOptions.turbo; - if (Object.keys(newOptions).length > 0) { - throw new Error(`Invalid options passed to ReactOnRails.options: ${JSON.stringify(newOptions)}`); - } - }, + // eslint-disable-next-line no-param-reassign + delete newOptions.turbo; + } - reactOnRailsPageLoaded() { - return ClientStartup.reactOnRailsPageLoaded(); - }, + if (Object.keys(newOptions).length > 0) { + throw new Error(`Invalid options passed to ReactOnRails.options: ${JSON.stringify(newOptions)}`); + } +} - reactOnRailsComponentLoaded(domId: string): Promise { - return renderOrHydrateComponent(domId); - }, +export function reactOnRailsPageLoaded() { + return ClientStartup.reactOnRailsPageLoaded(); +} - reactOnRailsStoreLoaded(storeName: string): Promise { - return hydrateStore(storeName); - }, +export function reactOnRailsComponentLoaded(domId: string): Promise { + return renderOrHydrateComponent(domId); +} - authenticityToken(): string | null { - return Authenticity.authenticityToken(); - }, +export function reactOnRailsStoreLoaded(storeName: string): Promise { + return hydrateStore(storeName); +} - authenticityHeaders(otherHeaders: Record = {}): AuthenticityHeaders { - return Authenticity.authenticityHeaders(otherHeaders); - }, +export function authenticityToken(): string | null { + return Authenticity.authenticityToken(); +} - // ///////////////////////////////////////////////////////////////////////////// - // INTERNALLY USED APIs - // ///////////////////////////////////////////////////////////////////////////// +export function authenticityHeaders(otherHeaders: Record = {}): AuthenticityHeaders { + return Authenticity.authenticityHeaders(otherHeaders); +} - option(key: K): ReactOnRailsOptions[K] | undefined { - return this.options[key]; - }, +// ///////////////////////////////////////////////////////////////////////////// +// INTERNALLY USED APIs +// ///////////////////////////////////////////////////////////////////////////// - getStoreGenerator(name: string): StoreGenerator { - return StoreRegistry.getStoreGenerator(name); - }, +export function option(key: K): ReactOnRailsOptions[K] | undefined { + return options[key]; +} - setStore(name: string, store: Store): void { - StoreRegistry.setStore(name, store); - }, +export function getStoreGenerator(name: string): StoreGenerator { + return StoreRegistry.getStoreGenerator(name); +} - clearHydratedStores(): void { - StoreRegistry.clearHydratedStores(); - }, +export function setStore(name: string, store: Store): void { + StoreRegistry.setStore(name, store); +} - render(name: string, props: Record, domNodeId: string, hydrate: boolean): RenderReturnType { - const componentObj = ComponentRegistry.get(name); - const reactElement = createReactOutput({ componentObj, props, domNodeId }); - - return reactHydrateOrRender( - document.getElementById(domNodeId) as Element, - reactElement as ReactElement, - hydrate, - ); - }, +export function clearHydratedStores(): void { + StoreRegistry.clearHydratedStores(); +} - getComponent(name: string): RegisteredComponent { - return ComponentRegistry.get(name); - }, +export function render( + name: string, + props: Record, + domNodeId: string, + hydrate: boolean, +): RenderReturnType { + const componentObj = ComponentRegistry.get(name); + const reactElement = createReactOutput({ componentObj, props, domNodeId }); + + return reactHydrateOrRenderInternal( + document.getElementById(domNodeId) as Element, + reactElement as ReactElement, + hydrate, + ); +} - getOrWaitForComponent(name: string): Promise { - return ComponentRegistry.getOrWaitForComponent(name); - }, +export function getComponent(name: string): RegisteredComponent { + return ComponentRegistry.get(name); +} - serverRenderReactComponent(): null | string | Promise { - throw new Error( - 'serverRenderReactComponent is not available in "react-on-rails/client". Import "react-on-rails" server-side.', - ); - }, +export function getOrWaitForComponent(name: string): Promise { + return ComponentRegistry.getOrWaitForComponent(name); +} - streamServerRenderedReactComponent() { - throw new Error( - 'streamServerRenderedReactComponent is only supported when using a bundle built for Node.js environments', - ); - }, +export function serverRenderReactComponent(): null | string | Promise { + throw new Error( + 'serverRenderReactComponent is not available in "react-on-rails/client". Import "react-on-rails" server-side.', + ); +} - serverRenderRSCReactComponent() { - throw new Error('serverRenderRSCReactComponent is supported in RSC bundle only.'); - }, +export function streamServerRenderedReactComponent() { + throw new Error( + 'streamServerRenderedReactComponent is only supported when using a bundle built for Node.js environments', + ); +} - handleError(): string | undefined { - throw new Error( - 'handleError is not available in "react-on-rails/client". Import "react-on-rails" server-side.', - ); - }, +export function serverRenderRSCReactComponent() { + throw new Error('serverRenderRSCReactComponent is supported in RSC bundle only.'); +} - buildConsoleReplay(): string { - return buildConsoleReplay(); - }, +export function handleError(): string | undefined { + throw new Error( + 'handleError is not available in "react-on-rails/client". Import "react-on-rails" server-side.', + ); +} - registeredComponents(): Map { - return ComponentRegistry.components(); - }, +export function registeredComponents(): Map { + return ComponentRegistry.components(); +} - storeGenerators(): Map { - return StoreRegistry.storeGenerators(); - }, +export function storeGenerators(): Map { + return StoreRegistry.storeGenerators(); +} - stores(): Map { - return StoreRegistry.stores(); - }, +export function stores(): Map { + return StoreRegistry.stores(); +} - resetOptions(): void { - this.options = { ...DEFAULT_OPTIONS }; - }, +export function resetOptions(): void { + options = { ...DEFAULT_OPTIONS }; +} - isRSCBundle: false, -}; +export const isRSCBundle = false; -globalThis.ReactOnRails.resetOptions(); +resetOptions(); ClientStartup.clientStartup(); export * from './types/index.ts'; -export default globalThis.ReactOnRails; diff --git a/node_package/src/ReactOnRails.full.ts b/node_package/src/ReactOnRails.full.ts index 4f03bfb531..d8a25ff8c5 100644 --- a/node_package/src/ReactOnRails.full.ts +++ b/node_package/src/ReactOnRails.full.ts @@ -1,9 +1,3 @@ -import handleError from './handleError.ts'; -import serverRenderReactComponent from './serverRenderReactComponent.ts'; -import type { RenderParams, RenderResult, ErrorOptions } from './types/index.ts'; - -import Client from './ReactOnRails.client.ts'; - if (typeof window !== 'undefined') { // warn to include a collapsed stack trace console.warn( @@ -11,10 +5,6 @@ if (typeof window !== 'undefined') { ); } -Client.handleError = (options: ErrorOptions): string | undefined => handleError(options); - -Client.serverRenderReactComponent = (options: RenderParams): null | string | Promise => - serverRenderReactComponent(options); - -export * from './types/index.ts'; -export default Client; +export * from './ReactOnRails.client.ts'; +export { default as handleError } from './handleError.ts'; +export { default as serverRenderReactComponent } from './serverRenderReactComponent.ts'; diff --git a/node_package/src/ReactOnRails.node.ts b/node_package/src/ReactOnRails.node.ts index 407d2658b9..3b3fdf2da3 100644 --- a/node_package/src/ReactOnRails.node.ts +++ b/node_package/src/ReactOnRails.node.ts @@ -1,8 +1,2 @@ -import ReactOnRails from './ReactOnRails.full.ts'; -import streamServerRenderedReactComponent from './streamServerRenderedReactComponent.ts'; - -ReactOnRails.streamServerRenderedReactComponent = streamServerRenderedReactComponent; - export * from './ReactOnRails.full.ts'; -// eslint-disable-next-line no-restricted-exports -- see https://github.com/eslint/eslint/issues/15617 -export { default } from './ReactOnRails.full.ts'; +export { default as streamServerRenderedReactComponent } from './streamServerRenderedReactComponent.ts'; diff --git a/node_package/src/ReactOnRailsRSC.ts b/node_package/src/ReactOnRailsRSC.ts index 4089238ca0..38767cbdbe 100644 --- a/node_package/src/ReactOnRailsRSC.ts +++ b/node_package/src/ReactOnRailsRSC.ts @@ -8,7 +8,6 @@ import { StreamRenderState, StreamableComponentResult, } from './types/index.ts'; -import ReactOnRails from './ReactOnRails.full.ts'; import handleError from './handleError.ts'; import { convertToError } from './serverRenderUtils.ts'; @@ -83,7 +82,9 @@ const streamRenderRSCComponent = ( return readableStream; }; -ReactOnRails.serverRenderRSCReactComponent = (options: RSCRenderParams) => { +export * from './ReactOnRails.full.ts'; + +export const serverRenderRSCReactComponent = (options: RSCRenderParams) => { try { return streamServerRenderedComponent(options, streamRenderRSCComponent); } finally { @@ -91,7 +92,4 @@ ReactOnRails.serverRenderRSCReactComponent = (options: RSCRenderParams) => { } }; -ReactOnRails.isRSCBundle = true; - -export * from './types/index.ts'; -export default ReactOnRails; +export const isRSCBundle = true; diff --git a/node_package/src/clientStartup.ts b/node_package/src/clientStartup.ts index bffefaa3ad..45634d2f51 100644 --- a/node_package/src/clientStartup.ts +++ b/node_package/src/clientStartup.ts @@ -8,6 +8,12 @@ import { import { onPageLoaded, onPageUnloaded } from './pageLifecycle.ts'; import { debugTurbolinks } from './turbolinksUtils.ts'; +declare global { + /* eslint-disable no-var,vars-on-top,no-underscore-dangle */ + var __REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__: boolean; + /* eslint-enable no-var,vars-on-top,no-underscore-dangle */ +} + export async function reactOnRailsPageLoaded() { debugTurbolinks('reactOnRailsPageLoaded'); await Promise.all([hydrateAllStores(), renderOrHydrateAllComponents()]); diff --git a/node_package/src/context.ts b/node_package/src/context.ts index 8d5485cf23..7930b71fee 100644 --- a/node_package/src/context.ts +++ b/node_package/src/context.ts @@ -1,11 +1,4 @@ -import type { ReactOnRailsInternal, RailsContext } from './types/index.ts'; - -declare global { - /* eslint-disable no-var,vars-on-top,no-underscore-dangle */ - var ReactOnRails: ReactOnRailsInternal; - var __REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__: boolean; - /* eslint-enable no-var,vars-on-top,no-underscore-dangle */ -} +import type { RailsContext } from './types/index.ts'; let currentRailsContext: RailsContext | null = null; diff --git a/node_package/src/registerServerComponent/client.tsx b/node_package/src/registerServerComponent/client.tsx index e24122a39c..537e52cc1b 100644 --- a/node_package/src/registerServerComponent/client.tsx +++ b/node_package/src/registerServerComponent/client.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import ReactOnRails from '../ReactOnRails.client.ts'; +import { register } from '../ReactOnRails.client.ts'; import RSCRoute from '../RSCRoute.tsx'; import { ReactComponentOrRenderFunction } from '../types/index.ts'; import wrapServerComponentRenderer from '../wrapServerComponentRenderer/client.tsx'; @@ -41,7 +41,7 @@ const registerServerComponent = (...componentNames: string[]) => { )); } - ReactOnRails.register(componentsWrappedInRSCRoute); + register(componentsWrappedInRSCRoute); }; export default registerServerComponent; diff --git a/node_package/src/registerServerComponent/server.rsc.ts b/node_package/src/registerServerComponent/server.rsc.ts index c5ee59c0be..492006bcd4 100644 --- a/node_package/src/registerServerComponent/server.rsc.ts +++ b/node_package/src/registerServerComponent/server.rsc.ts @@ -1,4 +1,4 @@ -import ReactOnRails from '../ReactOnRails.client.ts'; +import { register } from '../ReactOnRails.client.ts'; import { ReactComponent, RenderFunction } from '../types/index.ts'; /** @@ -20,6 +20,6 @@ import { ReactComponent, RenderFunction } from '../types/index.ts'; * ``` */ const registerServerComponent = (components: { [id: string]: ReactComponent | RenderFunction }) => - ReactOnRails.register(components); + register(components); export default registerServerComponent; diff --git a/node_package/src/registerServerComponent/server.tsx b/node_package/src/registerServerComponent/server.tsx index 6ece231672..70e320c7a6 100644 --- a/node_package/src/registerServerComponent/server.tsx +++ b/node_package/src/registerServerComponent/server.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import ReactOnRails from '../ReactOnRails.client.ts'; +import { register } from '../ReactOnRails.client.ts'; import RSCRoute from '../RSCRoute.tsx'; import { ReactComponent, RenderFunction } from '../types/index.ts'; import wrapServerComponentRenderer from '../wrapServerComponentRenderer/server.tsx'; @@ -29,7 +29,7 @@ const registerServerComponent = (components: Record) => )); } - ReactOnRails.register(componentsWrappedInRSCRoute); + register(componentsWrappedInRSCRoute); }; export default registerServerComponent; diff --git a/node_package/src/turbolinksUtils.ts b/node_package/src/turbolinksUtils.ts index 4ec70415bf..2813a4869a 100644 --- a/node_package/src/turbolinksUtils.ts +++ b/node_package/src/turbolinksUtils.ts @@ -1,3 +1,6 @@ +// TODO: move option to a separate file to avoid circular dependencies +import { option } from './ReactOnRails.client'; + declare global { namespace Turbolinks { interface TurbolinksStatic { @@ -16,7 +19,7 @@ export function debugTurbolinks(...msg: unknown[]): void { return; } - if (globalThis.ReactOnRails?.option('traceTurbolinks')) { + if (option('traceTurbolinks')) { console.log('TURBO:', ...msg); } } @@ -26,7 +29,7 @@ export function turbolinksInstalled(): boolean { } export function turboInstalled() { - return globalThis.ReactOnRails?.option('turbo') === true; + return option('turbo') === true; } export function turbolinksVersion5(): boolean { diff --git a/node_package/tests/Authenticity.test.js b/node_package/tests/Authenticity.test.js index 743b18c512..a3d7009ed9 100644 --- a/node_package/tests/Authenticity.test.js +++ b/node_package/tests/Authenticity.test.js @@ -1,4 +1,4 @@ -import ReactOnRails from '../src/ReactOnRails.client.ts'; +import * as ReactOnRails from '../src/ReactOnRails.client.ts'; const testToken = 'TEST_CSRF_TOKEN'; diff --git a/node_package/tests/ReactOnRails.test.jsx b/node_package/tests/ReactOnRails.test.jsx index a39b67040d..afd63a459f 100644 --- a/node_package/tests/ReactOnRails.test.jsx +++ b/node_package/tests/ReactOnRails.test.jsx @@ -4,7 +4,7 @@ import { createStore } from 'redux'; import * as React from 'react'; import * as createReactClass from 'create-react-class'; -import ReactOnRails from '../src/ReactOnRails.client.ts'; +import * as ReactOnRails from '../src/ReactOnRails.client.ts'; describe('ReactOnRails', () => { it('render returns a virtual DOM element for component', () => { diff --git a/node_package/tests/registerServerComponent.client.test.jsx b/node_package/tests/registerServerComponent.client.test.jsx index e9bad6cf8f..e36f76e5c5 100644 --- a/node_package/tests/registerServerComponent.client.test.jsx +++ b/node_package/tests/registerServerComponent.client.test.jsx @@ -10,7 +10,7 @@ import '@testing-library/jest-dom'; import * as path from 'path'; import * as fs from 'fs'; import { createNodeReadableStream, getNodeVersion } from './testUtils.js'; -import ReactOnRails from '../src/ReactOnRails.client.ts'; +import { getComponent } from '../src/ReactOnRails.client.ts'; import registerServerComponent from '../src/registerServerComponent/client.tsx'; import { clear as clearComponentRegistry } from '../src/ComponentRegistry.ts'; @@ -68,7 +68,7 @@ enableFetchMocks(); // Execute the render const render = async () => { - const Component = ReactOnRails.getComponent('TestComponent'); + const Component = getComponent('TestComponent'); await Component.component({}, railsContext, mockDomNodeId); }; @@ -188,7 +188,7 @@ enableFetchMocks(); }; await act(async () => { - const Component = ReactOnRails.getComponent('TestComponent'); + const Component = getComponent('TestComponent'); await Component.component({}, railsContext, mockDomNodeId); }); @@ -212,7 +212,7 @@ enableFetchMocks(); }; await act(async () => { - const Component = ReactOnRails.getComponent('TestComponent'); + const Component = getComponent('TestComponent'); await Component.component({}, railsContext, mockDomNodeId); }); diff --git a/node_package/tests/streamServerRenderedReactComponent.test.jsx b/node_package/tests/streamServerRenderedReactComponent.test.jsx index 7fb0421869..655a368017 100644 --- a/node_package/tests/streamServerRenderedReactComponent.test.jsx +++ b/node_package/tests/streamServerRenderedReactComponent.test.jsx @@ -6,7 +6,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import streamServerRenderedReactComponent from '../src/streamServerRenderedReactComponent.ts'; import * as ComponentRegistry from '../src/ComponentRegistry.ts'; -import ReactOnRails from '../src/ReactOnRails.node.ts'; +import { register } from '../src/ReactOnRails.node.ts'; const AsyncContent = async ({ throwAsyncError }) => { await new Promise((resolve) => { @@ -74,21 +74,21 @@ describe('streamServerRenderedReactComponent', () => { } = {}) => { switch (componentType) { case 'reactComponent': - ReactOnRails.register({ TestComponentForStreaming }); + register({ TestComponentForStreaming }); break; case 'renderFunction': - ReactOnRails.register({ + register({ TestComponentForStreaming: (props, _railsContext) => () => , }); break; case 'asyncRenderFunction': - ReactOnRails.register({ + register({ TestComponentForStreaming: (props, _railsContext) => () => Promise.resolve(), }); break; case 'erroneousRenderFunction': - ReactOnRails.register({ + register({ TestComponentForStreaming: (_props, _railsContext) => { // The error happen inside the render function itself not inside the returned React component throw new Error('Sync Error from render function'); @@ -96,7 +96,7 @@ describe('streamServerRenderedReactComponent', () => { }); break; case 'erroneousAsyncRenderFunction': - ReactOnRails.register({ + register({ TestComponentForStreaming: (_props, _railsContext) => // The error happen inside the render function itself not inside the returned React component Promise.reject(new Error('Async Error from render function')), @@ -325,7 +325,7 @@ describe('streamServerRenderedReactComponent', () => { it('streams a string from a Promise that resolves to a string', async () => { const StringPromiseComponent = () => Promise.resolve('
String from Promise
'); - ReactOnRails.register({ StringPromiseComponent }); + register({ StringPromiseComponent }); const renderResult = streamServerRenderedReactComponent({ name: 'StringPromiseComponent', diff --git a/spec/dummy/app/views/pages/client_side_hello_world.html.erb b/spec/dummy/app/views/pages/client_side_hello_world.html.erb index 06e291d79b..3a4c084a23 100644 --- a/spec/dummy/app/views/pages/client_side_hello_world.html.erb +++ b/spec/dummy/app/views/pages/client_side_hello_world.html.erb @@ -22,8 +22,8 @@
       import HelloWorld from '../components/HelloWorld';
-      import ReactOnRails from 'react-on-rails/client';
-      ReactOnRails.register({ HelloWorld });
+      import { register } from 'react-on-rails/client';
+      register({ HelloWorld });
     
  • diff --git a/spec/dummy/app/views/pages/server_side_hello_world.html.erb b/spec/dummy/app/views/pages/server_side_hello_world.html.erb index 8dcb1ea96e..d2f602890b 100644 --- a/spec/dummy/app/views/pages/server_side_hello_world.html.erb +++ b/spec/dummy/app/views/pages/server_side_hello_world.html.erb @@ -48,8 +48,8 @@
           import HelloWorld from '../components/HelloWorld';
    -      import ReactOnRails from 'react-on-rails/client';
    -      ReactOnRails.register({ HelloWorld });
    +      import { register } from 'react-on-rails/client';
    +      register({ HelloWorld });
         
  • diff --git a/spec/dummy/app/views/pages/server_side_redux_app.html.erb b/spec/dummy/app/views/pages/server_side_redux_app.html.erb index b11ad675f8..246a41c0cc 100644 --- a/spec/dummy/app/views/pages/server_side_redux_app.html.erb +++ b/spec/dummy/app/views/pages/server_side_redux_app.html.erb @@ -37,8 +37,8 @@
         import ReduxApp from './ClientReduxApp';
    -    import ReactOnRails from 'react-on-rails/client';
    -    ReactOnRails.register({ ReduxApp });
    +    import { register } from 'react-on-rails/client';
    +    register({ ReduxApp });
         
  • diff --git a/spec/dummy/app/views/pages/server_side_redux_app_cached.html.erb b/spec/dummy/app/views/pages/server_side_redux_app_cached.html.erb index dec8fb17db..5b2e5f0c33 100644 --- a/spec/dummy/app/views/pages/server_side_redux_app_cached.html.erb +++ b/spec/dummy/app/views/pages/server_side_redux_app_cached.html.erb @@ -43,7 +43,7 @@
         import ReduxApp from './ClientReduxApp';
    -    import ReactOnRails from 'react-on-rails/client';
    +    import * as ReactOnRails from 'react-on-rails/client';
         ReactOnRails.register({ ReduxApp });
         
  • diff --git a/spec/dummy/app/views/pages/xhr_refresh.html.erb b/spec/dummy/app/views/pages/xhr_refresh.html.erb index fdad0ee637..df2eb1252a 100644 --- a/spec/dummy/app/views/pages/xhr_refresh.html.erb +++ b/spec/dummy/app/views/pages/xhr_refresh.html.erb @@ -32,8 +32,8 @@
           import HellowWorldRehydratable from '../components/HellowWorldRehydratable';
    -      import ReactOnRails from 'react-on-rails/client';
    -      ReactOnRails.register({ HellowWorldRehydratable });
    +      import { register } from 'react-on-rails/client';
    +      register({ HellowWorldRehydratable });
         
  • diff --git a/spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx b/spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx index 05706772ab..7c6ddc63c5 100644 --- a/spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx +++ b/spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx @@ -3,7 +3,7 @@ import React from 'react'; import { Provider } from 'react-redux'; -import ReactOnRails from 'react-on-rails/client'; +import { getStore } from 'react-on-rails/client'; import ReactDOM from 'react-dom'; import HelloWorldContainer from '../../app/components/HelloWorldContainer'; @@ -19,7 +19,7 @@ export default (props, _railsContext, domNodeId) => { delete props.prerender; // This is where we get the existing store. - const store = ReactOnRails.getStore('SharedReduxStore'); + const store = getStore('SharedReduxStore'); // renderApp is a function required for hot reloading. see // https://github.com/retroalgic/react-on-rails-hot-minimal/blob/master/client/src/entry.js diff --git a/spec/dummy/client/app/packs/client-bundle.js b/spec/dummy/client/app/packs/client-bundle.js index 93016fe4a2..07140a012e 100644 --- a/spec/dummy/client/app/packs/client-bundle.js +++ b/spec/dummy/client/app/packs/client-bundle.js @@ -4,7 +4,7 @@ import 'jquery'; import 'jquery-ujs'; import '@hotwired/turbo-rails'; -import ReactOnRails from 'react-on-rails/client'; +import * as ReactOnRails from 'react-on-rails/client'; import HelloTurboStream from '../startup/HelloTurboStream'; import SharedReduxStore from '../stores/SharedReduxStore'; diff --git a/spec/dummy/client/app/packs/server-bundle.js b/spec/dummy/client/app/packs/server-bundle.js index ac246c2edf..ca0facc873 100644 --- a/spec/dummy/client/app/packs/server-bundle.js +++ b/spec/dummy/client/app/packs/server-bundle.js @@ -1,7 +1,7 @@ // import statement added by react_on_rails:generate_packs rake task import './../generated/server-bundle-generated.js'; // eslint-disable-line import/extensions // Shows the mapping from the exported object to the name used by the server rendering. -import ReactOnRails from 'react-on-rails'; +import * as ReactOnRails from 'react-on-rails'; // Example of server rendering with no React import HelloString from '../non_react/HelloString'; diff --git a/spec/dummy/client/app/startup/HelloWorldRehydratable.jsx b/spec/dummy/client/app/startup/HelloWorldRehydratable.jsx index 2b894ec399..f1cc4e6439 100644 --- a/spec/dummy/client/app/startup/HelloWorldRehydratable.jsx +++ b/spec/dummy/client/app/startup/HelloWorldRehydratable.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import React from 'react'; -import ReactOnRails from 'react-on-rails/client'; +import { render } from 'react-on-rails/client'; import RailsContext from '../components/RailsContext'; class HelloWorldRehydratable extends React.Component { @@ -51,7 +51,7 @@ class HelloWorldRehydratable extends React.Component { // Read props from the component specification tag and merge railsContext const mergedProps = { ...JSON.parse(componentSpecificationTag.textContent), railsContext }; // Hydrate - ReactOnRails.render(registeredComponentName, mergedProps, component.id, true); + render(registeredComponentName, mergedProps, component.id, true); } } diff --git a/spec/dummy/client/app/startup/ReduxSharedStoreApp.client.jsx b/spec/dummy/client/app/startup/ReduxSharedStoreApp.client.jsx index ba0bd726b1..b7b2cb90ca 100644 --- a/spec/dummy/client/app/startup/ReduxSharedStoreApp.client.jsx +++ b/spec/dummy/client/app/startup/ReduxSharedStoreApp.client.jsx @@ -3,7 +3,7 @@ import React from 'react'; import { Provider } from 'react-redux'; -import ReactOnRails from 'react-on-rails/client'; +import { getStore } from 'react-on-rails/client'; import ReactDOMClient from 'react-dom/client'; import HelloWorldContainer from '../components/HelloWorldContainer'; @@ -24,7 +24,7 @@ export default (props, _railsContext, domNodeId) => { delete props.prerender; // This is where we get the existing store. - const store = ReactOnRails.getStore('SharedReduxStore'); + const store = getStore('SharedReduxStore'); // renderApp is a function required for hot reloading. see // https://github.com/retroalgic/react-on-rails-hot-minimal/blob/master/client/src/entry.js diff --git a/spec/dummy/client/app/startup/ReduxSharedStoreApp.server.jsx b/spec/dummy/client/app/startup/ReduxSharedStoreApp.server.jsx index 5982ab4415..23619588fa 100644 --- a/spec/dummy/client/app/startup/ReduxSharedStoreApp.server.jsx +++ b/spec/dummy/client/app/startup/ReduxSharedStoreApp.server.jsx @@ -2,7 +2,7 @@ // Compare this to the ./ReduxSharedStoreApp.client.jsx file which is used for client side rendering. import React from 'react'; -import ReactOnRails from 'react-on-rails'; +import { getStore } from 'react-on-rails'; import { Provider } from 'react-redux'; import HelloWorldContainer from '../components/HelloWorldContainer'; @@ -14,7 +14,7 @@ import HelloWorldContainer from '../components/HelloWorldContainer'; */ export default () => { // This is where we get the existing store. - const store = ReactOnRails.getStore('SharedReduxStore'); + const store = getStore('SharedReduxStore'); return ( diff --git a/spec/dummy/spec/packs_generator_spec.rb b/spec/dummy/spec/packs_generator_spec.rb index c6443d3152..c0d86eddae 100644 --- a/spec/dummy/spec/packs_generator_spec.rb +++ b/spec/dummy/spec/packs_generator_spec.rb @@ -257,7 +257,7 @@ def self.configuration component_name = "ReactClientComponentWithClientAndServer" component_pack = "#{generated_directory}/#{component_name}.js" pack_content = File.read(component_pack) - expect(pack_content).to include("import ReactOnRails from 'react-on-rails/client';") + expect(pack_content).to include("import * as ReactOnRails from 'react-on-rails/client';") expect(pack_content).to include("ReactOnRails.register({#{component_name}});") expect(pack_content).not_to include("registerServerComponent") end @@ -291,7 +291,7 @@ def self.configuration component_name = "ReactClientComponent" component_pack = "#{generated_directory}/#{component_name}.js" pack_content = File.read(component_pack) - expect(pack_content).to include("import ReactOnRails from 'react-on-rails/client';") + expect(pack_content).to include("import * as ReactOnRails from 'react-on-rails/client';") expect(pack_content).to include("ReactOnRails.register({#{component_name}});") expect(pack_content).not_to include("registerServerComponent") end @@ -307,7 +307,7 @@ def self.configuration component_name = "ReactServerComponent" component_pack = "#{generated_directory}/#{component_name}.js" pack_content = File.read(component_pack) - expect(pack_content).to include("import ReactOnRails from 'react-on-rails/client';") + expect(pack_content).to include("import * as ReactOnRails from 'react-on-rails/client';") expect(pack_content).to include("ReactOnRails.register({#{component_name}});") expect(pack_content).not_to include("registerServerComponent") end @@ -323,7 +323,7 @@ def self.configuration component_name = "ReactServerComponent" component_pack = "#{generated_directory}/#{component_name}.js" pack_content = File.read(component_pack) - expect(pack_content).to include("import ReactOnRails from 'react-on-rails/client';") + expect(pack_content).to include("import * as ReactOnRails from 'react-on-rails/client';") expect(pack_content).to include("ReactOnRails.register({#{component_name}});") expect(pack_content).not_to include("registerServerComponent") end @@ -341,7 +341,7 @@ def self.configuration ) generated_server_bundle_content = File.read(generated_server_bundle_path) expected_content = <<~CONTENT.strip - import ReactOnRails from 'react-on-rails'; + import * as ReactOnRails from 'react-on-rails'; import ReactClientComponent from '../components/ReactServerComponents/ror_components/ReactClientComponent.jsx'; import ReactServerComponent from '../components/ReactServerComponents/ror_components/ReactServerComponent.jsx'; diff --git a/spec/dummy/tests/react-on-rails.import.test.js b/spec/dummy/tests/react-on-rails.import.test.js index e2af3df943..87da0faa2b 100644 --- a/spec/dummy/tests/react-on-rails.import.test.js +++ b/spec/dummy/tests/react-on-rails.import.test.js @@ -1,4 +1,4 @@ -import ReactOnRails from 'react-on-rails'; +import * as ReactOnRails from 'react-on-rails'; test('ReactOnRails', () => { expect(() => { diff --git a/spec/dummy/tests/react-on-rails.require.test.js b/spec/dummy/tests/react-on-rails.require.test.js index 7d96d5a996..c9e6cdfd1f 100644 --- a/spec/dummy/tests/react-on-rails.require.test.js +++ b/spec/dummy/tests/react-on-rails.require.test.js @@ -1,7 +1,7 @@ -const ReactOnRails = require('react-on-rails').default; +const { register } = require('react-on-rails'); test('ReactOnRails', () => { expect(() => { - ReactOnRails.register({}); + register({}); }).not.toThrow(); }); From 7156b5fb337dcb3670db3ae9b32606c85c7cc3d5 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Fri, 11 Apr 2025 14:48:02 +0000 Subject: [PATCH 2/3] Separate options file --- node_package/src/ReactOnRails.client.ts | 40 +++---------------------- node_package/src/options.ts | 36 ++++++++++++++++++++++ node_package/src/turbolinksUtils.ts | 3 +- 3 files changed, 41 insertions(+), 38 deletions(-) create mode 100644 node_package/src/options.ts diff --git a/node_package/src/ReactOnRails.client.ts b/node_package/src/ReactOnRails.client.ts index 5a4b45dc46..0ef92d8bef 100644 --- a/node_package/src/ReactOnRails.client.ts +++ b/node_package/src/ReactOnRails.client.ts @@ -13,9 +13,9 @@ import type { AuthenticityHeaders, Store, StoreGenerator, - ReactOnRailsOptions, } from './types/index.ts'; import reactHydrateOrRenderInternal from './reactHydrateOrRender.ts'; +import { resetOptions } from './options.ts'; export { default as buildConsoleReplay } from './buildConsoleReplay.ts'; @@ -25,6 +25,7 @@ declare global { /* eslint-enable no-var,vars-on-top,no-underscore-dangle */ } +// eslint-disable-next-line no-underscore-dangle if (globalThis.__REACT_ON_RAILS_LOADED__) { throw new Error(`\ The ReactOnRails value exists in the ${globalThis} scope, it may not be safe to overwrite it. @@ -32,15 +33,9 @@ This could be caused by setting Webpack's optimization.runtimeChunk to "true" or Check your Webpack configuration. Read more at https://github.com/shakacode/react_on_rails/issues/1558.`); } +// eslint-disable-next-line no-underscore-dangle globalThis.__REACT_ON_RAILS_LOADED__ = true; -const DEFAULT_OPTIONS = { - traceTurbolinks: false, - turbo: false, -}; - -let options: ReactOnRailsOptions = {}; - // TODO: convert to re-exports if everything works fine export function register(components: Record): void { ComponentRegistry.register(components); @@ -83,26 +78,6 @@ export function reactHydrateOrRender( return reactHydrateOrRenderInternal(domNode, reactElement, hydrate); } -export function setOptions(newOptions: Partial): void { - if (typeof newOptions.traceTurbolinks !== 'undefined') { - options.traceTurbolinks = newOptions.traceTurbolinks; - - // eslint-disable-next-line no-param-reassign - delete newOptions.traceTurbolinks; - } - - if (typeof newOptions.turbo !== 'undefined') { - options.turbo = newOptions.turbo; - - // eslint-disable-next-line no-param-reassign - delete newOptions.turbo; - } - - if (Object.keys(newOptions).length > 0) { - throw new Error(`Invalid options passed to ReactOnRails.options: ${JSON.stringify(newOptions)}`); - } -} - export function reactOnRailsPageLoaded() { return ClientStartup.reactOnRailsPageLoaded(); } @@ -127,10 +102,6 @@ export function authenticityHeaders(otherHeaders: Record = {}): // INTERNALLY USED APIs // ///////////////////////////////////////////////////////////////////////////// -export function option(key: K): ReactOnRailsOptions[K] | undefined { - return options[key]; -} - export function getStoreGenerator(name: string): StoreGenerator { return StoreRegistry.getStoreGenerator(name); } @@ -201,10 +172,6 @@ export function stores(): Map { return StoreRegistry.stores(); } -export function resetOptions(): void { - options = { ...DEFAULT_OPTIONS }; -} - export const isRSCBundle = false; resetOptions(); @@ -212,3 +179,4 @@ resetOptions(); ClientStartup.clientStartup(); export * from './types/index.ts'; +export * from './options.ts'; diff --git a/node_package/src/options.ts b/node_package/src/options.ts new file mode 100644 index 0000000000..af3fc87ba2 --- /dev/null +++ b/node_package/src/options.ts @@ -0,0 +1,36 @@ +import type { ReactOnRailsOptions } from './types/index.ts'; + +const DEFAULT_OPTIONS = { + traceTurbolinks: false, + turbo: false, +}; + +let options: ReactOnRailsOptions = {}; + +export function setOptions(newOptions: Partial): void { + if (typeof newOptions.traceTurbolinks !== 'undefined') { + options.traceTurbolinks = newOptions.traceTurbolinks; + + // eslint-disable-next-line no-param-reassign + delete newOptions.traceTurbolinks; + } + + if (typeof newOptions.turbo !== 'undefined') { + options.turbo = newOptions.turbo; + + // eslint-disable-next-line no-param-reassign + delete newOptions.turbo; + } + + if (Object.keys(newOptions).length > 0) { + throw new Error(`Invalid options passed to ReactOnRails.options: ${JSON.stringify(newOptions)}`); + } +} + +export function option(key: K): ReactOnRailsOptions[K] | undefined { + return options[key]; +} + +export function resetOptions(): void { + options = { ...DEFAULT_OPTIONS }; +} diff --git a/node_package/src/turbolinksUtils.ts b/node_package/src/turbolinksUtils.ts index 2813a4869a..eeca843c1c 100644 --- a/node_package/src/turbolinksUtils.ts +++ b/node_package/src/turbolinksUtils.ts @@ -1,5 +1,4 @@ -// TODO: move option to a separate file to avoid circular dependencies -import { option } from './ReactOnRails.client'; +import { option } from './options.ts'; declare global { namespace Turbolinks { From 25eece5a02c8073157959eca13991c00402687e8 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Tue, 22 Apr 2025 07:49:48 +0000 Subject: [PATCH 3/3] Try to fix server-bundle --- lib/react_on_rails/packs_generator.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/react_on_rails/packs_generator.rb b/lib/react_on_rails/packs_generator.rb index 6b1f593881..c8496e2923 100644 --- a/lib/react_on_rails/packs_generator.rb +++ b/lib/react_on_rails/packs_generator.rb @@ -128,6 +128,7 @@ def create_server_pack def build_server_pack_content(component_on_server_imports, server_components, client_components) content = <<~FILE_CONTENT import * as ReactOnRails from 'react-on-rails'; + globalThis.ReactOnRails = ReactOnRails; #{component_on_server_imports.join("\n")}\n FILE_CONTENT