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..c8496e2923 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,8 @@ 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'; + globalThis.ReactOnRails = ReactOnRails; #{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..0ef92d8bef 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 { @@ -14,187 +13,170 @@ import type { AuthenticityHeaders, Store, StoreGenerator, - ReactOnRailsOptions, } from './types/index.ts'; -import reactHydrateOrRender from './reactHydrateOrRender.ts'; +import reactHydrateOrRenderInternal from './reactHydrateOrRender.ts'; +import { resetOptions } from './options.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 */ +} + +// 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. 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.`); } -const DEFAULT_OPTIONS = { - traceTurbolinks: false, - turbo: false, -}; - -globalThis.ReactOnRails = { - options: {}, - - register(components: Record): void { - ComponentRegistry.register(components); - }, - - registerStore(stores: Record): void { - this.registerStoreGenerators(stores); - }, +// eslint-disable-next-line no-underscore-dangle +globalThis.__REACT_ON_RAILS_LOADED__ = true; - 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); - }, - - getStore(name: string, throwIfMissing = true): Store | undefined { - return StoreRegistry.getStore(name, throwIfMissing); - }, - - getOrWaitForStore(name: string): Promise { - return StoreRegistry.getOrWaitForStore(name); - }, - - getOrWaitForStoreGenerator(name: string): Promise { - return StoreRegistry.getOrWaitForStoreGenerator(name); - }, - - reactHydrateOrRender(domNode: Element, reactElement: ReactElement, hydrate: boolean): RenderReturnType { - return reactHydrateOrRender(domNode, reactElement, hydrate); - }, +// TODO: convert to re-exports if everything works fine +export function register(components: Record): void { + ComponentRegistry.register(components); +} - setOptions(newOptions: Partial): void { - if (typeof newOptions.traceTurbolinks !== 'undefined') { - this.options.traceTurbolinks = newOptions.traceTurbolinks; +// 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.', + ); + } - // eslint-disable-next-line no-param-reassign - delete newOptions.traceTurbolinks; - } + StoreRegistry.register(storeGenerators); +} - if (typeof newOptions.turbo !== 'undefined') { - this.options.turbo = newOptions.turbo; +// eslint-disable-next-line @typescript-eslint/no-shadow +export function registerStore(stores: Record): void { + registerStoreGenerators(stores); +} - // eslint-disable-next-line no-param-reassign - delete newOptions.turbo; - } +export function getStore(name: string, throwIfMissing = true): Store | undefined { + return StoreRegistry.getStore(name, throwIfMissing); +} - if (Object.keys(newOptions).length > 0) { - throw new Error(`Invalid options passed to ReactOnRails.options: ${JSON.stringify(newOptions)}`); - } - }, +export function getOrWaitForStore(name: string): Promise { + return StoreRegistry.getOrWaitForStore(name); +} - reactOnRailsPageLoaded() { - return ClientStartup.reactOnRailsPageLoaded(); - }, +export function getOrWaitForStoreGenerator(name: string): Promise { + return StoreRegistry.getOrWaitForStoreGenerator(name); +} - reactOnRailsComponentLoaded(domId: string): Promise { - return renderOrHydrateComponent(domId); - }, +export function reactHydrateOrRender( + domNode: Element, + reactElement: ReactElement, + hydrate: boolean, +): RenderReturnType { + return reactHydrateOrRenderInternal(domNode, reactElement, hydrate); +} - reactOnRailsStoreLoaded(storeName: string): Promise { - return hydrateStore(storeName); - }, +export function reactOnRailsPageLoaded() { + return ClientStartup.reactOnRailsPageLoaded(); +} - authenticityToken(): string | null { - return Authenticity.authenticityToken(); - }, +export function reactOnRailsComponentLoaded(domId: string): Promise { + return renderOrHydrateComponent(domId); +} - authenticityHeaders(otherHeaders: Record = {}): AuthenticityHeaders { - return Authenticity.authenticityHeaders(otherHeaders); - }, +export function reactOnRailsStoreLoaded(storeName: string): Promise { + return hydrateStore(storeName); +} - // ///////////////////////////////////////////////////////////////////////////// - // INTERNALLY USED APIs - // ///////////////////////////////////////////////////////////////////////////// +export function authenticityToken(): string | null { + return Authenticity.authenticityToken(); +} - option(key: K): ReactOnRailsOptions[K] | undefined { - return this.options[key]; - }, +export function authenticityHeaders(otherHeaders: Record = {}): AuthenticityHeaders { + return Authenticity.authenticityHeaders(otherHeaders); +} - getStoreGenerator(name: string): StoreGenerator { - return StoreRegistry.getStoreGenerator(name); - }, +// ///////////////////////////////////////////////////////////////////////////// +// INTERNALLY USED APIs +// ///////////////////////////////////////////////////////////////////////////// - setStore(name: string, store: Store): void { - StoreRegistry.setStore(name, store); - }, +export function getStoreGenerator(name: string): StoreGenerator { + return StoreRegistry.getStoreGenerator(name); +} - clearHydratedStores(): void { - StoreRegistry.clearHydratedStores(); - }, - - 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 setStore(name: string, store: Store): void { + StoreRegistry.setStore(name, store); +} - getComponent(name: string): RegisteredComponent { - return ComponentRegistry.get(name); - }, +export function clearHydratedStores(): void { + StoreRegistry.clearHydratedStores(); +} - getOrWaitForComponent(name: string): Promise { - return ComponentRegistry.getOrWaitForComponent(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, + ); +} - serverRenderReactComponent(): null | string | Promise { - throw new Error( - 'serverRenderReactComponent is not available in "react-on-rails/client". Import "react-on-rails" server-side.', - ); - }, +export function getComponent(name: string): RegisteredComponent { + return ComponentRegistry.get(name); +} - streamServerRenderedReactComponent() { - throw new Error( - 'streamServerRenderedReactComponent is only supported when using a bundle built for Node.js environments', - ); - }, +export function getOrWaitForComponent(name: string): Promise { + return ComponentRegistry.getOrWaitForComponent(name); +} - serverRenderRSCReactComponent() { - throw new Error('serverRenderRSCReactComponent is supported in RSC bundle only.'); - }, +export function serverRenderReactComponent(): null | string | Promise { + throw new Error( + 'serverRenderReactComponent is not available in "react-on-rails/client". Import "react-on-rails" server-side.', + ); +} - handleError(): string | undefined { - throw new Error( - 'handleError is not available in "react-on-rails/client". Import "react-on-rails" server-side.', - ); - }, +export function streamServerRenderedReactComponent() { + throw new Error( + 'streamServerRenderedReactComponent is only supported when using a bundle built for Node.js environments', + ); +} - buildConsoleReplay(): string { - return buildConsoleReplay(); - }, +export function serverRenderRSCReactComponent() { + throw new Error('serverRenderRSCReactComponent is supported in RSC bundle only.'); +} - registeredComponents(): Map { - return ComponentRegistry.components(); - }, +export function handleError(): string | undefined { + throw new Error( + 'handleError is not available in "react-on-rails/client". Import "react-on-rails" server-side.', + ); +} - storeGenerators(): Map { - return StoreRegistry.storeGenerators(); - }, +export function registeredComponents(): Map { + return ComponentRegistry.components(); +} - stores(): Map { - return StoreRegistry.stores(); - }, +export function storeGenerators(): Map { + return StoreRegistry.storeGenerators(); +} - resetOptions(): void { - this.options = { ...DEFAULT_OPTIONS }; - }, +export function stores(): Map { + return StoreRegistry.stores(); +} - isRSCBundle: false, -}; +export const isRSCBundle = false; -globalThis.ReactOnRails.resetOptions(); +resetOptions(); ClientStartup.clientStartup(); export * from './types/index.ts'; -export default globalThis.ReactOnRails; +export * from './options.ts'; 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/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/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..eeca843c1c 100644 --- a/node_package/src/turbolinksUtils.ts +++ b/node_package/src/turbolinksUtils.ts @@ -1,3 +1,5 @@ +import { option } from './options.ts'; + declare global { namespace Turbolinks { interface TurbolinksStatic { @@ -16,7 +18,7 @@ export function debugTurbolinks(...msg: unknown[]): void { return; } - if (globalThis.ReactOnRails?.option('traceTurbolinks')) { + if (option('traceTurbolinks')) { console.log('TURBO:', ...msg); } } @@ -26,7 +28,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(); });