Skip to content

Commit d337826

Browse files
committed
Replace context with globalThis
1 parent 2b692cc commit d337826

7 files changed

+46
-100
lines changed

node_package/src/CallbackRegistry.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ItemRegistrationCallback } from './types';
22
import { onPageLoaded, onPageUnloaded } from './pageLifecycle';
3-
import { getContextAndRailsContext } from './context';
3+
import { getRailsContext } from './context';
44

55
/**
66
* Represents information about a registered item including its value,
@@ -47,7 +47,7 @@ export default class CallbackRegistry<T> {
4747
};
4848

4949
onPageLoaded(() => {
50-
const registryTimeout = getContextAndRailsContext().railsContext?.componentRegistryTimeout;
50+
const registryTimeout = getRailsContext().railsContext?.componentRegistryTimeout;
5151
if (!registryTimeout) return;
5252

5353
timeoutId = setTimeout(triggerTimeout, registryTimeout);

node_package/src/ClientSideRenderer.ts

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as ReactDOM from 'react-dom';
55
import type { ReactElement } from 'react';
66
import type { RailsContext, RegisteredComponent, RenderFunction, Root } from './types';
77

8-
import { getContextAndRailsContext, resetContextAndRailsContext, type Context } from './context';
8+
import { getRailsContext, resetRailsContext } from './context';
99
import createReactOutput from './createReactOutput';
1010
import { isServerRenderHash } from './isServerRenderResult';
1111
import reactHydrateOrRender from './reactHydrateOrRender';
@@ -61,23 +61,23 @@ class ComponentRenderer {
6161
const storeDependencies = el.getAttribute('data-store-dependencies');
6262
const storeDependenciesArray = storeDependencies ? (JSON.parse(storeDependencies) as string[]) : [];
6363

64-
const { context, railsContext } = getContextAndRailsContext();
65-
if (!context || !railsContext) return;
64+
const { railsContext } = getRailsContext();
65+
if (!railsContext) return;
6666

6767
// Wait for all store dependencies to be loaded
6868
this.renderPromise = Promise.all(
69-
storeDependenciesArray.map((storeName) => context.ReactOnRails.getOrWaitForStore(storeName)),
69+
storeDependenciesArray.map((storeName) => globalThis.ReactOnRails.getOrWaitForStore(storeName)),
7070
).then(() => {
7171
if (this.state === 'unmounted') return Promise.resolve();
72-
return this.render(el, context, railsContext);
72+
return this.render(el, railsContext);
7373
});
7474
}
7575

7676
/**
7777
* Used for client rendering by ReactOnRails. Either calls ReactDOM.hydrate, ReactDOM.render, or
7878
* delegates to a renderer registered by the user.
7979
*/
80-
private async render(el: Element, context: Context, railsContext: RailsContext): Promise<void> {
80+
private async render(el: Element, railsContext: RailsContext): Promise<void> {
8181
// This must match lib/react_on_rails/helper.rb
8282
const name = el.getAttribute('data-component-name') || '';
8383
const { domNodeId } = this;
@@ -87,7 +87,7 @@ class ComponentRenderer {
8787
try {
8888
const domNode = document.getElementById(domNodeId);
8989
if (domNode) {
90-
const componentObj = await context.ReactOnRails.getOrWaitForComponent(name);
90+
const componentObj = await globalThis.ReactOnRails.getOrWaitForComponent(name);
9191
if (this.state === 'unmounted') {
9292
return;
9393
}
@@ -181,8 +181,8 @@ class StoreRenderer {
181181

182182
constructor(storeDataElement: Element) {
183183
this.state = 'hydrating';
184-
const { context, railsContext } = getContextAndRailsContext();
185-
if (!context || !railsContext) {
184+
const { railsContext } = getRailsContext();
185+
if (!railsContext) {
186186
return;
187187
}
188188

@@ -191,22 +191,17 @@ class StoreRenderer {
191191
storeDataElement.textContent !== null
192192
? (JSON.parse(storeDataElement.textContent) as Record<string, unknown>)
193193
: {};
194-
this.hydratePromise = this.hydrate(context, railsContext, name, props);
194+
this.hydratePromise = this.hydrate(railsContext, name, props);
195195
}
196196

197-
private async hydrate(
198-
context: Context,
199-
railsContext: RailsContext,
200-
name: string,
201-
props: Record<string, unknown>,
202-
) {
203-
const storeGenerator = await context.ReactOnRails.getOrWaitForStoreGenerator(name);
197+
private async hydrate(railsContext: RailsContext, name: string, props: Record<string, unknown>) {
198+
const storeGenerator = await globalThis.ReactOnRails.getOrWaitForStoreGenerator(name);
204199
if (this.state === 'unmounted') {
205200
return;
206201
}
207202

208203
const store = storeGenerator(props, railsContext);
209-
context.ReactOnRails.setStore(name, store);
204+
globalThis.ReactOnRails.setStore(name, store);
210205
this.state = 'hydrated';
211206
}
212207

@@ -252,7 +247,7 @@ export const renderOrHydrateAllComponents = () =>
252247
function unmountAllComponents(): void {
253248
renderedRoots.forEach((root) => root.unmount());
254249
renderedRoots.clear();
255-
resetContextAndRailsContext();
250+
resetRailsContext();
256251
}
257252

258253
const storeRenderers = new Map<string, StoreRenderer>();

node_package/src/ReactOnRails.client.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import * as StoreRegistry from './StoreRegistry';
66
import buildConsoleReplay from './buildConsoleReplay';
77
import createReactOutput from './createReactOutput';
88
import * as Authenticity from './Authenticity';
9-
import context from './context';
109
import type {
1110
RegisteredComponent,
1211
RenderResult,
@@ -19,27 +18,19 @@ import type {
1918
} from './types';
2019
import reactHydrateOrRender from './reactHydrateOrRender';
2120

22-
const ctx = context();
23-
24-
if (ctx === undefined) {
25-
throw new Error("The context (usually Window or NodeJS's Global) is undefined.");
26-
}
27-
28-
if (ctx.ReactOnRails !== undefined) {
29-
/* eslint-disable @typescript-eslint/no-base-to-string -- Window and Global both have useful toString() */
21+
if (globalThis.ReactOnRails !== undefined) {
3022
throw new Error(`\
31-
The ReactOnRails value exists in the ${ctx} scope, it may not be safe to overwrite it.
23+
The ReactOnRails value exists in the ${globalThis} scope, it may not be safe to overwrite it.
3224
This could be caused by setting Webpack's optimization.runtimeChunk to "true" or "multiple," rather than "single."
3325
Check your Webpack configuration. Read more at https://github.com/shakacode/react_on_rails/issues/1558.`);
34-
/* eslint-enable @typescript-eslint/no-base-to-string */
3526
}
3627

3728
const DEFAULT_OPTIONS = {
3829
traceTurbolinks: false,
3930
turbo: false,
4031
};
4132

42-
ctx.ReactOnRails = {
33+
globalThis.ReactOnRails = {
4334
options: {},
4435

4536
register(components: Record<string, ReactComponentOrRenderFunction>): void {
@@ -199,9 +190,9 @@ ctx.ReactOnRails = {
199190
},
200191
};
201192

202-
ctx.ReactOnRails.resetOptions();
193+
globalThis.ReactOnRails.resetOptions();
203194

204-
ClientStartup.clientStartup(ctx);
195+
ClientStartup.clientStartup();
205196

206197
export * from './types';
207-
export default ctx.ReactOnRails;
198+
export default globalThis.ReactOnRails;

node_package/src/clientStartup.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { type Context, isWindow } from './context';
21
import {
3-
renderOrHydrateForceLoadedComponents,
4-
renderOrHydrateAllComponents,
5-
hydrateForceLoadedStores,
62
hydrateAllStores,
3+
hydrateForceLoadedStores,
4+
renderOrHydrateAllComponents,
5+
renderOrHydrateForceLoadedComponents,
76
unmountAll,
87
} from './ClientSideRenderer';
98
import { onPageLoaded, onPageUnloaded } from './pageLifecycle';
@@ -19,20 +18,20 @@ function reactOnRailsPageUnloaded(): void {
1918
unmountAll();
2019
}
2120

22-
export function clientStartup(context: Context) {
21+
export function clientStartup() {
2322
// Check if server rendering
24-
if (!isWindow(context)) {
23+
if (globalThis.document === undefined) {
2524
return;
2625
}
2726

2827
// Tried with a file local variable, but the install handler gets called twice.
2928
// eslint-disable-next-line no-underscore-dangle
30-
if (context.__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__) {
29+
if (globalThis.__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__) {
3130
return;
3231
}
3332

3433
// eslint-disable-next-line no-underscore-dangle
35-
context.__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__ = true;
34+
globalThis.__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__ = true;
3635

3736
// Force loaded components and stores are rendered and hydrated immediately.
3837
// The hydration process can handle the concurrent hydration of components and stores,

node_package/src/context.ts

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,37 @@
1-
import type { ReactOnRailsInternal as ReactOnRailsType, RailsContext } from './types';
1+
import type { ReactOnRailsInternal, RailsContext } from './types';
22

33
declare global {
4-
interface Window {
5-
ReactOnRails: ReactOnRailsType;
6-
__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__?: boolean;
7-
}
8-
9-
namespace globalThis {
10-
/* eslint-disable no-var,vars-on-top */
11-
var ReactOnRails: ReactOnRailsType;
12-
/* eslint-enable no-var,vars-on-top */
13-
}
14-
}
15-
16-
export type Context = Window | typeof globalThis;
17-
18-
/**
19-
* Get the context, be it window or global
20-
*/
21-
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
22-
export default function context(this: void): Context | void {
23-
return (typeof window !== 'undefined' && window) || (typeof global !== 'undefined' && global) || this;
4+
/* eslint-disable no-var,vars-on-top,no-underscore-dangle */
5+
var ReactOnRails: ReactOnRailsInternal;
6+
var __REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__: boolean;
7+
/* eslint-enable no-var,vars-on-top,no-underscore-dangle */
248
}
259

26-
export function isWindow(ctx: Context): ctx is Window {
27-
return (ctx as Window).document !== undefined;
28-
}
29-
30-
export function reactOnRailsContext(): Context {
31-
const ctx = context();
32-
if (ctx === undefined || typeof ctx.ReactOnRails === 'undefined') {
33-
throw new Error('ReactOnRails is undefined in both global and window namespaces.');
34-
}
35-
return ctx;
36-
}
37-
38-
let currentContext: Context | null = null;
3910
let currentRailsContext: RailsContext | null = null;
4011

4112
// caches context and railsContext to avoid re-parsing rails-context each time a component is rendered
42-
// Cached values will be reset when resetContextAndRailsContext() is called
43-
export function getContextAndRailsContext(): { context: Context | null; railsContext: RailsContext | null } {
13+
// Cached values will be reset when resetRailsContext() is called
14+
export function getRailsContext(): { railsContext: RailsContext | null } {
4415
// Return cached values if already set
45-
if (currentContext && currentRailsContext) {
46-
return { context: currentContext, railsContext: currentRailsContext };
16+
if (currentRailsContext) {
17+
return { railsContext: currentRailsContext };
4718
}
4819

49-
currentContext = reactOnRailsContext();
50-
5120
const el = document.getElementById('js-react-on-rails-context');
52-
if (!el || !el.textContent) {
53-
return { context: null, railsContext: null };
21+
if (!el?.textContent) {
22+
return { railsContext: null };
5423
}
5524

5625
try {
5726
currentRailsContext = JSON.parse(el.textContent) as RailsContext;
5827
} catch (e) {
5928
console.error('Error parsing Rails context:', e);
60-
return { context: null, railsContext: null };
29+
return { railsContext: null };
6130
}
6231

63-
return { context: currentContext, railsContext: currentRailsContext };
32+
return { railsContext: currentRailsContext };
6433
}
6534

66-
export function resetContextAndRailsContext(): void {
67-
currentContext = null;
35+
export function resetRailsContext(): void {
6836
currentRailsContext = null;
6937
}

node_package/src/turbolinksUtils.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { reactOnRailsContext } from './context';
2-
31
declare global {
42
namespace Turbolinks {
53
interface TurbolinksStatic {
@@ -18,8 +16,7 @@ export function debugTurbolinks(...msg: unknown[]): void {
1816
return;
1917
}
2018

21-
const context = reactOnRailsContext();
22-
if (context.ReactOnRails?.option('traceTurbolinks')) {
19+
if (globalThis.ReactOnRails?.option('traceTurbolinks')) {
2320
console.log('TURBO:', ...msg);
2421
}
2522
}
@@ -29,11 +26,7 @@ export function turbolinksInstalled(): boolean {
2926
}
3027

3128
export function turboInstalled() {
32-
const context = reactOnRailsContext();
33-
if (context.ReactOnRails) {
34-
return context.ReactOnRails.option('turbo') === true;
35-
}
36-
return false;
29+
return globalThis.ReactOnRails?.option('turbo') === true;
3730
}
3831

3932
export function turbolinksVersion5(): boolean {

node_package/tests/ComponentRegistry.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jest.mock('../src/pageLifecycle', () => ({
2323
}));
2424

2525
jest.mock('../src/context', () => ({
26-
getContextAndRailsContext: () => ({ railsContext: { componentRegistryTimeout: 100 } }),
26+
getRailsContext: () => ({ railsContext: { componentRegistryTimeout: 100 } }),
2727
}));
2828

2929
describe('ComponentRegistry', () => {

0 commit comments

Comments
 (0)