From fa713e9624b1ed4970c6618e073c6e440eac1ac2 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Thu, 3 Apr 2025 07:25:46 +0000 Subject: [PATCH 1/9] Initial build scripts --- node_package/scripts/build | 9 +++++++++ package-scripts.yml | 4 ++-- package.json | 36 +++++++++++++++++++++++++++--------- tsconfig.cjs.json | 8 ++++++++ tsconfig.json | 5 +++-- 5 files changed, 49 insertions(+), 13 deletions(-) create mode 100755 node_package/scripts/build create mode 100644 tsconfig.cjs.json diff --git a/node_package/scripts/build b/node_package/scripts/build new file mode 100755 index 0000000000..c4ea6a7caa --- /dev/null +++ b/node_package/scripts/build @@ -0,0 +1,9 @@ +#!/bin/sh + +set -e + +yarn run clean +yarn run tsc --declaration +echo '{ "type": "module" }' > node_package/lib/esm/package.json +yarn run tsc -p tsconfig.cjs.json --declaration +echo '{ "type": "commonjs" }' > node_package/lib/cjs/package.json diff --git a/package-scripts.yml b/package-scripts.yml index 19bc0d9ea5..3f363f1098 100644 --- a/package-scripts.yml +++ b/package-scripts.yml @@ -25,9 +25,9 @@ scripts: # 3. Check if the project is built now; # 4. If it failed, print an error message (still follow https://docs.npmjs.com/cli/v8/using-npm/scripts#best-practices). script: > - [ -f node_package/lib/ReactOnRails.full.js ] || + [ -f node_package/lib/esm/ReactOnRails.full.js ] || (npm run build >/dev/null 2>&1 || true) && - [ -f node_package/lib/ReactOnRails.full.js ] || + [ -f node_package/lib/esm/ReactOnRails.full.js ] || { echo 'Building react-on-rails seems to have failed!'; } format: diff --git a/package.json b/package.json index 77c6d0b8be..c087313873 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,35 @@ "name": "react-on-rails", "version": "15.0.0-alpha.2", "description": "react-on-rails JavaScript for react_on_rails Ruby gem", - "main": "node_package/lib/ReactOnRails.full.js", + "main": "node_package/lib/esm/ReactOnRails.full.js", "type": "commonjs", "exports": { ".": { - "react-server": "./node_package/lib/ReactOnRailsRSC.js", - "node": "./node_package/lib/ReactOnRails.node.js", - "default": "./node_package/lib/ReactOnRails.full.js" + "react-server": { + "import": "./node_package/lib/esm/ReactOnRailsRSC.js", + "require": "./node_package/lib/cjs/ReactOnRailsRSC.js" + }, + "node": { + "import": "./node_package/lib/esm/ReactOnRails.node.js", + "require": "./node_package/lib/cjs/ReactOnRails.node.js" + }, + "default": { + "import": "./node_package/lib/esm/ReactOnRails.full.js", + "require": "./node_package/lib/cjs/ReactOnRails.full.js" + } }, - "./client": "./node_package/lib/ReactOnRails.client.js", - "./registerServerComponent/client": "./node_package/lib/registerServerComponent/client.js", - "./registerServerComponent/server": "./node_package/lib/registerServerComponent/server.js" + "./client": { + "import": "./node_package/lib/esm/ReactOnRails.client.js", + "require": "./node_package/lib/cjs/ReactOnRails.client.js" + }, + "./registerServerComponent/client": { + "import": "./node_package/lib/esm/registerServerComponent/client.js", + "require": "./node_package/lib/cjs/registerServerComponent/client.js" + }, + "./registerServerComponent/server": { + "import": "./node_package/lib/esm/registerServerComponent/server.js", + "require": "./node_package/lib/cjs/registerServerComponent/server.js" + } }, "directories": { "doc": "docs" @@ -75,8 +93,8 @@ "prepack": "nps build.prepack", "prepare": "nps build.prepack", "prepublishOnly": "yarn run build", - "build": "yarn run clean && yarn run tsc --declaration", - "build-watch": "yarn run clean && yarn run tsc --watch", + "build": "node_package/scripts/build", + "build-watch": "yarn run clean && yarn run tsc --watch --no-declaration", "lint": "nps eslint", "check": "yarn run lint && yarn run test && yarn run type-check", "type-check": "yarn run tsc --noEmit --noErrorTruncation", diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 0000000000..d71b170ff0 --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "node16", + "moduleResolution": "node16", + "outDir": "node_package/lib/cjs" + } +} diff --git a/tsconfig.json b/tsconfig.json index c657d15878..f8935aaf40 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,9 +6,10 @@ // needed for Jest tests even though we don't use .tsx "jsx": "react-jsx", "lib": ["dom", "es2020"], - "module": "node16", + "module": "es2020", + "moduleResolution": "bundler", "noImplicitAny": true, - "outDir": "node_package/lib", + "outDir": "node_package/lib/esm", "strict": true, "incremental": true, "target": "es2020", From 5ed95ab5c83c510c3025a0f0bf6c686647dc5cd3 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Thu, 3 Apr 2025 07:36:21 +0000 Subject: [PATCH 2/9] Use paths with extensions --- eslint.config.ts | 6 +++++ node_package/src/Authenticity.ts | 2 +- node_package/src/CallbackRegistry.ts | 6 ++--- node_package/src/ClientSideRenderer.ts | 16 +++++++------- node_package/src/ComponentRegistry.ts | 6 ++--- node_package/src/RSCClientRoot.ts | 6 ++--- node_package/src/ReactOnRails.client.ts | 22 +++++++++---------- node_package/src/ReactOnRails.full.ts | 10 ++++----- node_package/src/ReactOnRails.node.ts | 8 +++---- node_package/src/ReactOnRailsRSC.ts | 16 +++++++------- node_package/src/StoreRegistry.ts | 2 +- node_package/src/buildConsoleReplay.ts | 4 ++-- node_package/src/clientStartup.ts | 8 +++---- node_package/src/context.ts | 2 +- node_package/src/createReactOutput.ts | 4 ++-- node_package/src/handleError.ts | 2 +- node_package/src/isRenderFunction.ts | 2 +- node_package/src/isServerRenderResult.ts | 2 +- node_package/src/pageLifecycle.ts | 2 +- node_package/src/reactHydrateOrRender.ts | 4 ++-- .../src/registerServerComponent/client.ts | 6 ++--- .../src/registerServerComponent/server.ts | 4 ++-- .../src/serverRenderReactComponent.ts | 14 ++++++------ node_package/src/serverRenderUtils.ts | 2 +- .../src/streamServerRenderedReactComponent.ts | 14 ++++++------ .../transformRSCStreamAndReplayConsoleLogs.ts | 2 +- node_package/src/turbolinksUtils.ts | 2 +- package.json | 2 +- tsconfig.json | 2 ++ yarn.lock | 8 +++---- 30 files changed, 97 insertions(+), 89 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index a2f4bf83b6..4c4c79fb22 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -128,6 +128,12 @@ const config = tsEslint.config([ 'jsx-a11y/anchor-is-valid': 'off', }, }, + { + files: ['node_package/src/**/*'], + rules: { + 'import/extensions': ['error', 'ignorePackages'], + }, + }, { files: ['lib/generators/react_on_rails/templates/**/*'], rules: { diff --git a/node_package/src/Authenticity.ts b/node_package/src/Authenticity.ts index 3da0fd34d4..8267c47cbd 100644 --- a/node_package/src/Authenticity.ts +++ b/node_package/src/Authenticity.ts @@ -1,4 +1,4 @@ -import type { AuthenticityHeaders } from './types/index'; +import type { AuthenticityHeaders } from './types/index.ts'; export default { authenticityToken(): string | null { diff --git a/node_package/src/CallbackRegistry.ts b/node_package/src/CallbackRegistry.ts index 3858c52d10..98262e075b 100644 --- a/node_package/src/CallbackRegistry.ts +++ b/node_package/src/CallbackRegistry.ts @@ -1,6 +1,6 @@ -import { ItemRegistrationCallback } from './types'; -import { onPageLoaded, onPageUnloaded } from './pageLifecycle'; -import { getContextAndRailsContext } from './context'; +import { ItemRegistrationCallback } from './types/index.ts'; +import { onPageLoaded, onPageUnloaded } from './pageLifecycle.ts'; +import { getContextAndRailsContext } from './context.ts'; /** * Represents information about a registered item including its value, diff --git a/node_package/src/ClientSideRenderer.ts b/node_package/src/ClientSideRenderer.ts index 8d14405e0b..75c662b1cc 100644 --- a/node_package/src/ClientSideRenderer.ts +++ b/node_package/src/ClientSideRenderer.ts @@ -3,14 +3,14 @@ import * as ReactDOM from 'react-dom'; import type { ReactElement } from 'react'; -import type { RailsContext, RegisteredComponent, RenderFunction, Root } from './types'; - -import { getContextAndRailsContext, resetContextAndRailsContext, type Context } from './context'; -import createReactOutput from './createReactOutput'; -import { isServerRenderHash } from './isServerRenderResult'; -import reactHydrateOrRender from './reactHydrateOrRender'; -import { supportsRootApi } from './reactApis'; -import { debugTurbolinks } from './turbolinksUtils'; +import type { RailsContext, RegisteredComponent, RenderFunction, Root } from './types/index.ts'; + +import { getContextAndRailsContext, resetContextAndRailsContext, type Context } from './context.ts'; +import createReactOutput from './createReactOutput.ts'; +import { isServerRenderHash } from './isServerRenderResult.ts'; +import reactHydrateOrRender from './reactHydrateOrRender.ts'; +import { supportsRootApi } from './reactApis.ts'; +import { debugTurbolinks } from './turbolinksUtils.ts'; const REACT_ON_RAILS_STORE_ATTRIBUTE = 'data-js-react-on-rails-store'; diff --git a/node_package/src/ComponentRegistry.ts b/node_package/src/ComponentRegistry.ts index b16fcf9d08..0c9b22405f 100644 --- a/node_package/src/ComponentRegistry.ts +++ b/node_package/src/ComponentRegistry.ts @@ -1,6 +1,6 @@ -import { type RegisteredComponent, type ReactComponentOrRenderFunction } from './types'; -import isRenderFunction from './isRenderFunction'; -import CallbackRegistry from './CallbackRegistry'; +import { type RegisteredComponent, type ReactComponentOrRenderFunction } from './types/index.ts'; +import isRenderFunction from './isRenderFunction.ts'; +import CallbackRegistry from './CallbackRegistry.ts'; const componentRegistry = new CallbackRegistry('component'); diff --git a/node_package/src/RSCClientRoot.ts b/node_package/src/RSCClientRoot.ts index eb2820267c..7a1d095721 100644 --- a/node_package/src/RSCClientRoot.ts +++ b/node_package/src/RSCClientRoot.ts @@ -3,9 +3,9 @@ import * as React from 'react'; import * as ReactDOMClient from 'react-dom/client'; import { createFromReadableStream } from 'react-on-rails-rsc/client'; -import { fetch } from './utils'; -import transformRSCStreamAndReplayConsoleLogs from './transformRSCStreamAndReplayConsoleLogs'; -import { RailsContext, RenderFunction } from './types'; +import { fetch } from './utils.ts'; +import transformRSCStreamAndReplayConsoleLogs from './transformRSCStreamAndReplayConsoleLogs.ts'; +import { RailsContext, RenderFunction } from './types/index.ts'; const { use } = React; diff --git a/node_package/src/ReactOnRails.client.ts b/node_package/src/ReactOnRails.client.ts index fe7ee96235..be7227ea73 100644 --- a/node_package/src/ReactOnRails.client.ts +++ b/node_package/src/ReactOnRails.client.ts @@ -1,12 +1,12 @@ import type { ReactElement } from 'react'; -import * as ClientStartup from './clientStartup'; -import { renderOrHydrateComponent, hydrateStore } from './ClientSideRenderer'; -import ComponentRegistry from './ComponentRegistry'; -import StoreRegistry from './StoreRegistry'; -import buildConsoleReplay from './buildConsoleReplay'; -import createReactOutput from './createReactOutput'; -import Authenticity from './Authenticity'; -import context from './context'; +import * as ClientStartup from './clientStartup.ts'; +import { renderOrHydrateComponent, hydrateStore } from './ClientSideRenderer.ts'; +import ComponentRegistry from './ComponentRegistry.ts'; +import StoreRegistry from './StoreRegistry.ts'; +import buildConsoleReplay from './buildConsoleReplay.ts'; +import createReactOutput from './createReactOutput.ts'; +import Authenticity from './Authenticity.ts'; +import context from './context.ts'; import type { RegisteredComponent, RenderResult, @@ -16,8 +16,8 @@ import type { Store, StoreGenerator, ReactOnRailsOptions, -} from './types'; -import reactHydrateOrRender from './reactHydrateOrRender'; +} from './types/index.ts'; +import reactHydrateOrRender from './reactHydrateOrRender.ts'; const ctx = context(); @@ -203,5 +203,5 @@ ctx.ReactOnRails.resetOptions(); ClientStartup.clientStartup(ctx); -export * from './types'; +export * from './types/index.ts'; export default ctx.ReactOnRails; diff --git a/node_package/src/ReactOnRails.full.ts b/node_package/src/ReactOnRails.full.ts index 91ca57eb16..4915b38cb7 100644 --- a/node_package/src/ReactOnRails.full.ts +++ b/node_package/src/ReactOnRails.full.ts @@ -1,8 +1,8 @@ -import handleError from './handleError'; -import serverRenderReactComponent from './serverRenderReactComponent'; -import type { RenderParams, RenderResult, ErrorOptions } from './types'; +import handleError from './handleError.ts'; +import serverRenderReactComponent from './serverRenderReactComponent.ts'; +import type { RenderParams, RenderResult, ErrorOptions } from './types/index.ts'; -import Client from './ReactOnRails.client'; +import Client from './ReactOnRails.client.ts'; if (typeof window !== 'undefined') { console.log( @@ -15,5 +15,5 @@ Client.handleError = (options: ErrorOptions): string | undefined => handleError( Client.serverRenderReactComponent = (options: RenderParams): null | string | Promise => serverRenderReactComponent(options); -export * from './types'; +export * from './types/index.ts'; export default Client; diff --git a/node_package/src/ReactOnRails.node.ts b/node_package/src/ReactOnRails.node.ts index e01e67cb85..407d2658b9 100644 --- a/node_package/src/ReactOnRails.node.ts +++ b/node_package/src/ReactOnRails.node.ts @@ -1,8 +1,8 @@ -import ReactOnRails from './ReactOnRails.full'; -import streamServerRenderedReactComponent from './streamServerRenderedReactComponent'; +import ReactOnRails from './ReactOnRails.full.ts'; +import streamServerRenderedReactComponent from './streamServerRenderedReactComponent.ts'; ReactOnRails.streamServerRenderedReactComponent = streamServerRenderedReactComponent; -export * from './ReactOnRails.full'; +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'; +export { default } from './ReactOnRails.full.ts'; diff --git a/node_package/src/ReactOnRailsRSC.ts b/node_package/src/ReactOnRailsRSC.ts index 718a62de52..fd2dcd0b98 100644 --- a/node_package/src/ReactOnRailsRSC.ts +++ b/node_package/src/ReactOnRailsRSC.ts @@ -2,17 +2,17 @@ import { renderToPipeableStream } from 'react-on-rails-rsc/server.node'; import { PassThrough, Readable } from 'stream'; import type { ReactElement } from 'react'; -import { RSCRenderParams, StreamRenderState } from './types'; -import ReactOnRails from './ReactOnRails.full'; -import buildConsoleReplay from './buildConsoleReplay'; -import handleError from './handleError'; -import { convertToError, createResultObject } from './serverRenderUtils'; +import { RSCRenderParams, StreamRenderState } from './types/index.ts'; +import ReactOnRails from './ReactOnRails.full.ts'; +import buildConsoleReplay from './buildConsoleReplay.ts'; +import handleError from './handleError.ts'; +import { convertToError, createResultObject } from './serverRenderUtils.ts'; import { streamServerRenderedComponent, transformRenderStreamChunksToResultObject, -} from './streamServerRenderedReactComponent'; -import loadReactClientManifest from './loadReactClientManifest'; +} from './streamServerRenderedReactComponent.ts'; +import loadReactClientManifest from './loadReactClientManifest.ts'; const stringToStream = (str: string) => { const stream = new PassThrough(); @@ -65,5 +65,5 @@ ReactOnRails.serverRenderRSCReactComponent = (options: RSCRenderParams) => { } }; -export * from './types'; +export * from './types/index.ts'; export default ReactOnRails; diff --git a/node_package/src/StoreRegistry.ts b/node_package/src/StoreRegistry.ts index 3129b0dbee..15b85ec9f0 100644 --- a/node_package/src/StoreRegistry.ts +++ b/node_package/src/StoreRegistry.ts @@ -1,5 +1,5 @@ import CallbackRegistry from './CallbackRegistry'; -import type { Store, StoreGenerator } from './types'; +import type { Store, StoreGenerator } from './types/index.ts'; const storeGeneratorRegistry = new CallbackRegistry('store generator'); const hydratedStoreRegistry = new CallbackRegistry('hydrated store'); diff --git a/node_package/src/buildConsoleReplay.ts b/node_package/src/buildConsoleReplay.ts index 19cb60509f..6cb975e461 100644 --- a/node_package/src/buildConsoleReplay.ts +++ b/node_package/src/buildConsoleReplay.ts @@ -1,5 +1,5 @@ -import RenderUtils from './RenderUtils'; -import scriptSanitizedVal from './scriptSanitizedVal'; +import RenderUtils from './RenderUtils.ts'; +import scriptSanitizedVal from './scriptSanitizedVal.ts'; declare global { interface Console { diff --git a/node_package/src/clientStartup.ts b/node_package/src/clientStartup.ts index b733c75d17..ba1b3bc6aa 100644 --- a/node_package/src/clientStartup.ts +++ b/node_package/src/clientStartup.ts @@ -1,13 +1,13 @@ -import { type Context, isWindow } from './context'; +import { type Context, isWindow } from './context.ts'; import { renderOrHydrateForceLoadedComponents, renderOrHydrateAllComponents, hydrateForceLoadedStores, hydrateAllStores, unmountAll, -} from './ClientSideRenderer'; -import { onPageLoaded, onPageUnloaded } from './pageLifecycle'; -import { debugTurbolinks } from './turbolinksUtils'; +} from './ClientSideRenderer.ts'; +import { onPageLoaded, onPageUnloaded } from './pageLifecycle.ts'; +import { debugTurbolinks } from './turbolinksUtils.ts'; export async function reactOnRailsPageLoaded() { debugTurbolinks('reactOnRailsPageLoaded'); diff --git a/node_package/src/context.ts b/node_package/src/context.ts index 1ce2f49a51..516a92264b 100644 --- a/node_package/src/context.ts +++ b/node_package/src/context.ts @@ -1,4 +1,4 @@ -import type { ReactOnRailsInternal as ReactOnRailsType, RailsContext } from './types'; +import type { ReactOnRailsInternal as ReactOnRailsType, RailsContext } from './types/index.ts'; declare global { interface Window { diff --git a/node_package/src/createReactOutput.ts b/node_package/src/createReactOutput.ts index 13851d52f4..4d6964fa1d 100644 --- a/node_package/src/createReactOutput.ts +++ b/node_package/src/createReactOutput.ts @@ -5,8 +5,8 @@ import type { ReactComponent, RenderFunction, CreateReactOutputResult, -} from './types/index'; -import { isServerRenderHash, isPromise } from './isServerRenderResult'; +} from './types/index.ts'; +import { isServerRenderHash, isPromise } from './isServerRenderResult.ts'; /** * Logic to either call the renderFunction or call React.createElement to get the diff --git a/node_package/src/handleError.ts b/node_package/src/handleError.ts index b57d998cb6..8d02ad9d8c 100644 --- a/node_package/src/handleError.ts +++ b/node_package/src/handleError.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import * as ReactDOMServer from 'react-dom/server'; -import type { ErrorOptions } from './types/index'; +import type { ErrorOptions } from './types/index.ts'; function handleRenderFunctionIssue(options: ErrorOptions): string { const { e, name } = options; diff --git a/node_package/src/isRenderFunction.ts b/node_package/src/isRenderFunction.ts index a85ba8c0dd..19e81a2f11 100644 --- a/node_package/src/isRenderFunction.ts +++ b/node_package/src/isRenderFunction.ts @@ -1,6 +1,6 @@ // See discussion: // https://discuss.reactjs.org/t/how-to-determine-if-js-object-is-react-component/2825/2 -import { ReactComponentOrRenderFunction, RenderFunction } from './types/index'; +import { ReactComponentOrRenderFunction, RenderFunction } from './types/index.ts'; /** * Used to determine we'll call be calling React.createElement on the component of if this is a diff --git a/node_package/src/isServerRenderResult.ts b/node_package/src/isServerRenderResult.ts index cf8772b1f1..2ff393524c 100644 --- a/node_package/src/isServerRenderResult.ts +++ b/node_package/src/isServerRenderResult.ts @@ -1,4 +1,4 @@ -import type { CreateReactOutputResult, ServerRenderResult } from './types/index'; +import type { CreateReactOutputResult, ServerRenderResult } from './types/index.ts'; export function isServerRenderHash(testValue: CreateReactOutputResult): testValue is ServerRenderResult { return !!( diff --git a/node_package/src/pageLifecycle.ts b/node_package/src/pageLifecycle.ts index 0f8f2ef203..d9cc32b1ff 100644 --- a/node_package/src/pageLifecycle.ts +++ b/node_package/src/pageLifecycle.ts @@ -4,7 +4,7 @@ import { turbolinksSupported, turboInstalled, turbolinksVersion5, -} from './turbolinksUtils'; +} from './turbolinksUtils.ts'; type PageLifecycleCallback = () => void | Promise; type PageState = 'load' | 'unload' | 'initial'; diff --git a/node_package/src/reactHydrateOrRender.ts b/node_package/src/reactHydrateOrRender.ts index 0ea6a9b960..b960fc2286 100644 --- a/node_package/src/reactHydrateOrRender.ts +++ b/node_package/src/reactHydrateOrRender.ts @@ -1,7 +1,7 @@ import type { ReactElement } from 'react'; import * as ReactDOM from 'react-dom'; -import type { RenderReturnType } from './types'; -import { supportsRootApi } from './reactApis'; +import type { RenderReturnType } from './types/index.ts'; +import { supportsRootApi } from './reactApis.ts'; type HydrateOrRenderType = (domNode: Element, reactElement: ReactElement) => RenderReturnType; diff --git a/node_package/src/registerServerComponent/client.ts b/node_package/src/registerServerComponent/client.ts index cefcbfab51..778df1828b 100644 --- a/node_package/src/registerServerComponent/client.ts +++ b/node_package/src/registerServerComponent/client.ts @@ -1,6 +1,6 @@ -import ReactOnRails from '../ReactOnRails.client'; -import RSCClientRoot from '../RSCClientRoot'; -import { RegisterServerComponentOptions, RailsContext, ReactComponentOrRenderFunction } from '../types'; +import ReactOnRails from '../ReactOnRails.client.ts'; +import RSCClientRoot from '../RSCClientRoot.ts'; +import { RegisterServerComponentOptions, RailsContext, ReactComponentOrRenderFunction } from '../types/index.ts'; /** * Registers React Server Components (RSC) with React on Rails. diff --git a/node_package/src/registerServerComponent/server.ts b/node_package/src/registerServerComponent/server.ts index 02c335bf42..40d68a1da2 100644 --- a/node_package/src/registerServerComponent/server.ts +++ b/node_package/src/registerServerComponent/server.ts @@ -1,5 +1,5 @@ -import ReactOnRails from '../ReactOnRails.client'; -import { ReactComponent } from '../types'; +import ReactOnRails from '../ReactOnRails.client.ts'; +import { ReactComponent } from '../types/index.ts'; /** * Registers React Server Components (RSC) with React on Rails for both server and RSC bundles. diff --git a/node_package/src/serverRenderReactComponent.ts b/node_package/src/serverRenderReactComponent.ts index f7a5e3bcc4..347c7f2d57 100644 --- a/node_package/src/serverRenderReactComponent.ts +++ b/node_package/src/serverRenderReactComponent.ts @@ -1,12 +1,12 @@ import * as ReactDOMServer from 'react-dom/server'; import type { ReactElement } from 'react'; -import ComponentRegistry from './ComponentRegistry'; -import createReactOutput from './createReactOutput'; -import { isPromise, isServerRenderHash } from './isServerRenderResult'; -import buildConsoleReplay from './buildConsoleReplay'; -import handleError from './handleError'; -import { createResultObject, convertToError, validateComponent } from './serverRenderUtils'; +import ComponentRegistry from './ComponentRegistry.ts'; +import createReactOutput from './createReactOutput.ts'; +import { isPromise, isServerRenderHash } from './isServerRenderResult.ts'; +import buildConsoleReplay from './buildConsoleReplay.ts'; +import handleError from './handleError.ts'; +import { createResultObject, convertToError, validateComponent } from './serverRenderUtils.ts'; import type { CreateReactOutputResult, RenderParams, @@ -14,7 +14,7 @@ import type { RenderState, RenderOptions, ServerRenderResult, -} from './types'; +} from './types/index.ts'; function processServerRenderHash(result: ServerRenderResult, options: RenderOptions): RenderState { const { redirectLocation, routeError } = result; diff --git a/node_package/src/serverRenderUtils.ts b/node_package/src/serverRenderUtils.ts index c77926cec7..ae49f6dc65 100644 --- a/node_package/src/serverRenderUtils.ts +++ b/node_package/src/serverRenderUtils.ts @@ -1,4 +1,4 @@ -import type { RegisteredComponent, RenderResult, RenderState, StreamRenderState } from './types'; +import type { RegisteredComponent, RenderResult, RenderState, StreamRenderState } from './types/index.ts'; export function createResultObject( html: string | null, diff --git a/node_package/src/streamServerRenderedReactComponent.ts b/node_package/src/streamServerRenderedReactComponent.ts index 5346b6a6c1..b0e8dcbdb6 100644 --- a/node_package/src/streamServerRenderedReactComponent.ts +++ b/node_package/src/streamServerRenderedReactComponent.ts @@ -2,13 +2,13 @@ import * as ReactDOMServer from 'react-dom/server'; import { PassThrough, Readable } from 'stream'; import type { ReactElement } from 'react'; -import ComponentRegistry from './ComponentRegistry'; -import createReactOutput from './createReactOutput'; -import { isPromise, isServerRenderHash } from './isServerRenderResult'; -import buildConsoleReplay from './buildConsoleReplay'; -import handleError from './handleError'; -import { createResultObject, convertToError, validateComponent } from './serverRenderUtils'; -import type { RenderParams, StreamRenderState } from './types'; +import ComponentRegistry from './ComponentRegistry.ts'; +import createReactOutput from './createReactOutput.ts'; +import { isPromise, isServerRenderHash } from './isServerRenderResult.ts'; +import buildConsoleReplay from './buildConsoleReplay.ts'; +import handleError from './handleError.ts'; +import { createResultObject, convertToError, validateComponent } from './serverRenderUtils.ts'; +import type { RenderParams, StreamRenderState } from './types/index.ts'; const stringToStream = (str: string): Readable => { const stream = new PassThrough(); diff --git a/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts b/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts index 094e786afb..6c44c1ab5e 100644 --- a/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts +++ b/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts @@ -1,4 +1,4 @@ -import { RenderResult } from './types'; +import { RenderResult } from './types/index.ts'; export default function transformRSCStreamAndReplayConsoleLogs(stream: ReadableStream) { return new ReadableStream({ diff --git a/node_package/src/turbolinksUtils.ts b/node_package/src/turbolinksUtils.ts index b5dd0dc0a6..cceb4f0c82 100644 --- a/node_package/src/turbolinksUtils.ts +++ b/node_package/src/turbolinksUtils.ts @@ -1,4 +1,4 @@ -import { reactOnRailsContext } from './context'; +import { reactOnRailsContext } from './context.ts'; declare global { namespace Turbolinks { diff --git a/package.json b/package.json index c087313873..c37c094145 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "react-on-rails-rsc": "19.0.0", "redux": "^4.2.1", "ts-jest": "^29.2.5", - "typescript": "^5.6.2" + "typescript": "^5.8.2" }, "peerDependencies": { "react": ">= 16", diff --git a/tsconfig.json b/tsconfig.json index f8935aaf40..0fb20a9163 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,8 @@ "outDir": "node_package/lib/esm", "strict": true, "incremental": true, + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true, "target": "es2020", "typeRoots": ["./node_modules/@types", "./node_package/types"] }, diff --git a/yarn.lock b/yarn.lock index d736790c1c..85184934c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6009,10 +6009,10 @@ typescript@5.6.1-rc: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.1-rc.tgz#d5e4d7d8170174fed607b74cc32aba3d77018e02" integrity sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ== -typescript@^5.6.2: - version "5.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" - integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== +typescript@^5.8.2: + version "5.8.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4" + integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ== unbox-primitive@^1.1.0: version "1.1.0" From 5df4ac3d13156e2840b55dd679024fdfc6b9f6c3 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Thu, 3 Apr 2025 07:36:53 +0000 Subject: [PATCH 3/9] Remove the fixed attw rule --- .github/workflows/lint-js-and-ruby.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/lint-js-and-ruby.yml b/.github/workflows/lint-js-and-ruby.yml index b30db2b541..6858533b59 100644 --- a/.github/workflows/lint-js-and-ruby.yml +++ b/.github/workflows/lint-js-and-ruby.yml @@ -90,9 +90,7 @@ jobs: run: yarn pack -f react-on-rails.tgz - name: Lint package types # --profile because we don't care about node10 - # --ignore-rules CJS default export can't be resolved at the moment, - # revisit in 15.0.0 - run: yarn run attw react-on-rails.tgz --profile node16 --ignore-rules cjs-only-exports-default + run: yarn run attw react-on-rails.tgz --profile node16 - name: Lint package publishing run: yarn run publint --strict react-on-rails.tgz # We only download and run Actionlint if there is any difference in GitHub Action workflows From e1ad62b10a17e320dff9fe430bf4f5144934d443 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Thu, 3 Apr 2025 08:07:41 +0000 Subject: [PATCH 4/9] Make loadReactClientManifest always CJS --- node_package/src/ReactOnRailsRSC.ts | 2 +- .../{loadReactClientManifest.ts => loadReactClientManifest.cts} | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) rename node_package/src/{loadReactClientManifest.ts => loadReactClientManifest.cts} (95%) diff --git a/node_package/src/ReactOnRailsRSC.ts b/node_package/src/ReactOnRailsRSC.ts index fd2dcd0b98..ad0b7bce3c 100644 --- a/node_package/src/ReactOnRailsRSC.ts +++ b/node_package/src/ReactOnRailsRSC.ts @@ -12,7 +12,7 @@ import { streamServerRenderedComponent, transformRenderStreamChunksToResultObject, } from './streamServerRenderedReactComponent.ts'; -import loadReactClientManifest from './loadReactClientManifest.ts'; +import loadReactClientManifest from './loadReactClientManifest.cts'; const stringToStream = (str: string) => { const stream = new PassThrough(); diff --git a/node_package/src/loadReactClientManifest.ts b/node_package/src/loadReactClientManifest.cts similarity index 95% rename from node_package/src/loadReactClientManifest.ts rename to node_package/src/loadReactClientManifest.cts index 0295c23915..a4488392b7 100644 --- a/node_package/src/loadReactClientManifest.ts +++ b/node_package/src/loadReactClientManifest.cts @@ -8,6 +8,7 @@ export default async function loadReactClientManifest(reactClientManifestFileNam // React client manifest is uploaded to node renderer as an asset. // Renderer copies assets to the same place as the server-bundle.js and rsc-bundle.js. // Thus, the __dirname of this code is where we can find the manifest file. + // And we need to be .cts to use __dirname const manifestPath = path.resolve(__dirname, reactClientManifestFileName); const loadedReactClientManifest = loadedReactClientManifests.get(manifestPath); if (loadedReactClientManifest) { From 5e1e266c39798ee193edbe8052379b96839eca5c Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Thu, 3 Apr 2025 08:09:29 +0000 Subject: [PATCH 5/9] Shorten types import --- eslint.config.ts | 2 +- node_package/src/Authenticity.ts | 2 +- node_package/src/CallbackRegistry.ts | 2 +- node_package/src/ClientSideRenderer.ts | 2 +- node_package/src/ComponentRegistry.ts | 2 +- node_package/src/RSCClientRoot.ts | 2 +- node_package/src/ReactOnRails.client.ts | 4 ++-- node_package/src/ReactOnRails.full.ts | 4 ++-- node_package/src/ReactOnRailsRSC.ts | 4 ++-- node_package/src/StoreRegistry.ts | 4 ++-- node_package/src/{types/index.ts => _types.ts} | 0 node_package/src/context.ts | 2 +- node_package/src/createReactOutput.ts | 2 +- node_package/src/handleError.ts | 2 +- node_package/src/isRenderFunction.ts | 2 +- node_package/src/isServerRenderResult.ts | 2 +- node_package/src/reactHydrateOrRender.ts | 2 +- node_package/src/registerServerComponent/client.ts | 2 +- node_package/src/registerServerComponent/server.ts | 2 +- node_package/src/serverRenderReactComponent.ts | 2 +- node_package/src/serverRenderUtils.ts | 2 +- node_package/src/streamServerRenderedReactComponent.ts | 2 +- node_package/src/transformRSCStreamAndReplayConsoleLogs.ts | 2 +- 23 files changed, 26 insertions(+), 26 deletions(-) rename node_package/src/{types/index.ts => _types.ts} (100%) diff --git a/eslint.config.ts b/eslint.config.ts index 4c4c79fb22..d5e393041b 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -144,7 +144,7 @@ const config = tsEslint.config([ }, }, { - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.[cm]ts', '**/*.tsx'], extends: tsEslint.configs.strictTypeChecked, diff --git a/node_package/src/Authenticity.ts b/node_package/src/Authenticity.ts index 8267c47cbd..d594f32b04 100644 --- a/node_package/src/Authenticity.ts +++ b/node_package/src/Authenticity.ts @@ -1,4 +1,4 @@ -import type { AuthenticityHeaders } from './types/index.ts'; +import type { AuthenticityHeaders } from './_types.ts'; export default { authenticityToken(): string | null { diff --git a/node_package/src/CallbackRegistry.ts b/node_package/src/CallbackRegistry.ts index 98262e075b..bd8d3a0b6b 100644 --- a/node_package/src/CallbackRegistry.ts +++ b/node_package/src/CallbackRegistry.ts @@ -1,4 +1,4 @@ -import { ItemRegistrationCallback } from './types/index.ts'; +import { ItemRegistrationCallback } from './_types.ts'; import { onPageLoaded, onPageUnloaded } from './pageLifecycle.ts'; import { getContextAndRailsContext } from './context.ts'; diff --git a/node_package/src/ClientSideRenderer.ts b/node_package/src/ClientSideRenderer.ts index 75c662b1cc..dbce778a50 100644 --- a/node_package/src/ClientSideRenderer.ts +++ b/node_package/src/ClientSideRenderer.ts @@ -3,7 +3,7 @@ import * as ReactDOM from 'react-dom'; import type { ReactElement } from 'react'; -import type { RailsContext, RegisteredComponent, RenderFunction, Root } from './types/index.ts'; +import type { RailsContext, RegisteredComponent, RenderFunction, Root } from './_types.ts'; import { getContextAndRailsContext, resetContextAndRailsContext, type Context } from './context.ts'; import createReactOutput from './createReactOutput.ts'; diff --git a/node_package/src/ComponentRegistry.ts b/node_package/src/ComponentRegistry.ts index 0c9b22405f..8eda677db4 100644 --- a/node_package/src/ComponentRegistry.ts +++ b/node_package/src/ComponentRegistry.ts @@ -1,4 +1,4 @@ -import { type RegisteredComponent, type ReactComponentOrRenderFunction } from './types/index.ts'; +import { type RegisteredComponent, type ReactComponentOrRenderFunction } from './_types.ts'; import isRenderFunction from './isRenderFunction.ts'; import CallbackRegistry from './CallbackRegistry.ts'; diff --git a/node_package/src/RSCClientRoot.ts b/node_package/src/RSCClientRoot.ts index 7a1d095721..ffaaf6a2b8 100644 --- a/node_package/src/RSCClientRoot.ts +++ b/node_package/src/RSCClientRoot.ts @@ -5,7 +5,7 @@ import * as ReactDOMClient from 'react-dom/client'; import { createFromReadableStream } from 'react-on-rails-rsc/client'; import { fetch } from './utils.ts'; import transformRSCStreamAndReplayConsoleLogs from './transformRSCStreamAndReplayConsoleLogs.ts'; -import { RailsContext, RenderFunction } from './types/index.ts'; +import { RailsContext, RenderFunction } from './_types.ts'; const { use } = React; diff --git a/node_package/src/ReactOnRails.client.ts b/node_package/src/ReactOnRails.client.ts index be7227ea73..7a2b62610f 100644 --- a/node_package/src/ReactOnRails.client.ts +++ b/node_package/src/ReactOnRails.client.ts @@ -16,7 +16,7 @@ import type { Store, StoreGenerator, ReactOnRailsOptions, -} from './types/index.ts'; +} from './_types.ts'; import reactHydrateOrRender from './reactHydrateOrRender.ts'; const ctx = context(); @@ -203,5 +203,5 @@ ctx.ReactOnRails.resetOptions(); ClientStartup.clientStartup(ctx); -export * from './types/index.ts'; +export * from './_types.ts'; export default ctx.ReactOnRails; diff --git a/node_package/src/ReactOnRails.full.ts b/node_package/src/ReactOnRails.full.ts index 4915b38cb7..5f6c89a1ed 100644 --- a/node_package/src/ReactOnRails.full.ts +++ b/node_package/src/ReactOnRails.full.ts @@ -1,6 +1,6 @@ import handleError from './handleError.ts'; import serverRenderReactComponent from './serverRenderReactComponent.ts'; -import type { RenderParams, RenderResult, ErrorOptions } from './types/index.ts'; +import type { RenderParams, RenderResult, ErrorOptions } from './_types.ts'; import Client from './ReactOnRails.client.ts'; @@ -15,5 +15,5 @@ Client.handleError = (options: ErrorOptions): string | undefined => handleError( Client.serverRenderReactComponent = (options: RenderParams): null | string | Promise => serverRenderReactComponent(options); -export * from './types/index.ts'; +export * from './_types.ts'; export default Client; diff --git a/node_package/src/ReactOnRailsRSC.ts b/node_package/src/ReactOnRailsRSC.ts index ad0b7bce3c..14a90ad5bf 100644 --- a/node_package/src/ReactOnRailsRSC.ts +++ b/node_package/src/ReactOnRailsRSC.ts @@ -2,7 +2,7 @@ import { renderToPipeableStream } from 'react-on-rails-rsc/server.node'; import { PassThrough, Readable } from 'stream'; import type { ReactElement } from 'react'; -import { RSCRenderParams, StreamRenderState } from './types/index.ts'; +import { RSCRenderParams, StreamRenderState } from './_types.ts'; import ReactOnRails from './ReactOnRails.full.ts'; import buildConsoleReplay from './buildConsoleReplay.ts'; import handleError from './handleError.ts'; @@ -65,5 +65,5 @@ ReactOnRails.serverRenderRSCReactComponent = (options: RSCRenderParams) => { } }; -export * from './types/index.ts'; +export * from './_types.ts'; export default ReactOnRails; diff --git a/node_package/src/StoreRegistry.ts b/node_package/src/StoreRegistry.ts index 15b85ec9f0..dd394dad90 100644 --- a/node_package/src/StoreRegistry.ts +++ b/node_package/src/StoreRegistry.ts @@ -1,5 +1,5 @@ -import CallbackRegistry from './CallbackRegistry'; -import type { Store, StoreGenerator } from './types/index.ts'; +import CallbackRegistry from './CallbackRegistry.ts'; +import type { Store, StoreGenerator } from './_types.ts'; const storeGeneratorRegistry = new CallbackRegistry('store generator'); const hydratedStoreRegistry = new CallbackRegistry('hydrated store'); diff --git a/node_package/src/types/index.ts b/node_package/src/_types.ts similarity index 100% rename from node_package/src/types/index.ts rename to node_package/src/_types.ts diff --git a/node_package/src/context.ts b/node_package/src/context.ts index 516a92264b..aa9c521996 100644 --- a/node_package/src/context.ts +++ b/node_package/src/context.ts @@ -1,4 +1,4 @@ -import type { ReactOnRailsInternal as ReactOnRailsType, RailsContext } from './types/index.ts'; +import type { ReactOnRailsInternal as ReactOnRailsType, RailsContext } from './_types.ts'; declare global { interface Window { diff --git a/node_package/src/createReactOutput.ts b/node_package/src/createReactOutput.ts index 4d6964fa1d..6e31404d75 100644 --- a/node_package/src/createReactOutput.ts +++ b/node_package/src/createReactOutput.ts @@ -5,7 +5,7 @@ import type { ReactComponent, RenderFunction, CreateReactOutputResult, -} from './types/index.ts'; +} from './_types.ts'; import { isServerRenderHash, isPromise } from './isServerRenderResult.ts'; /** diff --git a/node_package/src/handleError.ts b/node_package/src/handleError.ts index 8d02ad9d8c..209fc4612b 100644 --- a/node_package/src/handleError.ts +++ b/node_package/src/handleError.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import * as ReactDOMServer from 'react-dom/server'; -import type { ErrorOptions } from './types/index.ts'; +import type { ErrorOptions } from './_types.ts'; function handleRenderFunctionIssue(options: ErrorOptions): string { const { e, name } = options; diff --git a/node_package/src/isRenderFunction.ts b/node_package/src/isRenderFunction.ts index 19e81a2f11..8a7c411322 100644 --- a/node_package/src/isRenderFunction.ts +++ b/node_package/src/isRenderFunction.ts @@ -1,6 +1,6 @@ // See discussion: // https://discuss.reactjs.org/t/how-to-determine-if-js-object-is-react-component/2825/2 -import { ReactComponentOrRenderFunction, RenderFunction } from './types/index.ts'; +import { ReactComponentOrRenderFunction, RenderFunction } from './_types.ts'; /** * Used to determine we'll call be calling React.createElement on the component of if this is a diff --git a/node_package/src/isServerRenderResult.ts b/node_package/src/isServerRenderResult.ts index 2ff393524c..8e4ae84585 100644 --- a/node_package/src/isServerRenderResult.ts +++ b/node_package/src/isServerRenderResult.ts @@ -1,4 +1,4 @@ -import type { CreateReactOutputResult, ServerRenderResult } from './types/index.ts'; +import type { CreateReactOutputResult, ServerRenderResult } from './_types.ts'; export function isServerRenderHash(testValue: CreateReactOutputResult): testValue is ServerRenderResult { return !!( diff --git a/node_package/src/reactHydrateOrRender.ts b/node_package/src/reactHydrateOrRender.ts index b960fc2286..7c7d29f23a 100644 --- a/node_package/src/reactHydrateOrRender.ts +++ b/node_package/src/reactHydrateOrRender.ts @@ -1,6 +1,6 @@ import type { ReactElement } from 'react'; import * as ReactDOM from 'react-dom'; -import type { RenderReturnType } from './types/index.ts'; +import type { RenderReturnType } from './_types.ts'; import { supportsRootApi } from './reactApis.ts'; type HydrateOrRenderType = (domNode: Element, reactElement: ReactElement) => RenderReturnType; diff --git a/node_package/src/registerServerComponent/client.ts b/node_package/src/registerServerComponent/client.ts index 778df1828b..ae3910adbe 100644 --- a/node_package/src/registerServerComponent/client.ts +++ b/node_package/src/registerServerComponent/client.ts @@ -1,6 +1,6 @@ import ReactOnRails from '../ReactOnRails.client.ts'; import RSCClientRoot from '../RSCClientRoot.ts'; -import { RegisterServerComponentOptions, RailsContext, ReactComponentOrRenderFunction } from '../types/index.ts'; +import { RegisterServerComponentOptions, RailsContext, ReactComponentOrRenderFunction } from '../_types.ts'; /** * Registers React Server Components (RSC) with React on Rails. diff --git a/node_package/src/registerServerComponent/server.ts b/node_package/src/registerServerComponent/server.ts index 40d68a1da2..8ffa0e6ea5 100644 --- a/node_package/src/registerServerComponent/server.ts +++ b/node_package/src/registerServerComponent/server.ts @@ -1,5 +1,5 @@ import ReactOnRails from '../ReactOnRails.client.ts'; -import { ReactComponent } from '../types/index.ts'; +import { ReactComponent } from '../_types.ts'; /** * Registers React Server Components (RSC) with React on Rails for both server and RSC bundles. diff --git a/node_package/src/serverRenderReactComponent.ts b/node_package/src/serverRenderReactComponent.ts index 347c7f2d57..66e44d36be 100644 --- a/node_package/src/serverRenderReactComponent.ts +++ b/node_package/src/serverRenderReactComponent.ts @@ -14,7 +14,7 @@ import type { RenderState, RenderOptions, ServerRenderResult, -} from './types/index.ts'; +} from './_types.ts'; function processServerRenderHash(result: ServerRenderResult, options: RenderOptions): RenderState { const { redirectLocation, routeError } = result; diff --git a/node_package/src/serverRenderUtils.ts b/node_package/src/serverRenderUtils.ts index ae49f6dc65..fc16544c09 100644 --- a/node_package/src/serverRenderUtils.ts +++ b/node_package/src/serverRenderUtils.ts @@ -1,4 +1,4 @@ -import type { RegisteredComponent, RenderResult, RenderState, StreamRenderState } from './types/index.ts'; +import type { RegisteredComponent, RenderResult, RenderState, StreamRenderState } from './_types.ts'; export function createResultObject( html: string | null, diff --git a/node_package/src/streamServerRenderedReactComponent.ts b/node_package/src/streamServerRenderedReactComponent.ts index b0e8dcbdb6..29d1052c01 100644 --- a/node_package/src/streamServerRenderedReactComponent.ts +++ b/node_package/src/streamServerRenderedReactComponent.ts @@ -8,7 +8,7 @@ import { isPromise, isServerRenderHash } from './isServerRenderResult.ts'; import buildConsoleReplay from './buildConsoleReplay.ts'; import handleError from './handleError.ts'; import { createResultObject, convertToError, validateComponent } from './serverRenderUtils.ts'; -import type { RenderParams, StreamRenderState } from './types/index.ts'; +import type { RenderParams, StreamRenderState } from './_types.ts'; const stringToStream = (str: string): Readable => { const stream = new PassThrough(); diff --git a/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts b/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts index 6c44c1ab5e..3d14459058 100644 --- a/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts +++ b/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts @@ -1,4 +1,4 @@ -import { RenderResult } from './types/index.ts'; +import { RenderResult } from './_types.ts'; export default function transformRSCStreamAndReplayConsoleLogs(stream: ReadableStream) { return new ReadableStream({ From 7eed8d69c22b866f726fed04d48e7f40e1d007c5 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Thu, 3 Apr 2025 08:30:12 +0000 Subject: [PATCH 6/9] Update docs and changelog --- CHANGELOG.md | 1 + docs/release-notes/15.0.0.md | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c94aba1daf..762a53829b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Changes since the last non-beta release. #### Added - Configuration option `generated_component_packs_loading_strategy` to control how generated component packs are loaded. It supports `sync`, `async`, and `defer` strategies. [PR 1712](https://github.com/shakacode/react_on_rails/pull/1712) by [AbanoubGhadban](https://github.com/AbanoubGhadban). +- The package now includes both ESM and CJS versions. [PR 1722](https://github.com/shakacode/react_on_rails/pull/1722) by [alexeyr-ci2](https://github.com/alexeyr-ci2). ### Removed (Breaking Changes) diff --git a/docs/release-notes/15.0.0.md b/docs/release-notes/15.0.0.md index 50722bcfde..685feab562 100644 --- a/docs/release-notes/15.0.0.md +++ b/docs/release-notes/15.0.0.md @@ -30,6 +30,14 @@ Major improvements to component and store hydration: - `:sync` - Loads scripts synchronously (default for Shakapacker < 8.2.0) (better to upgrade to Shakapacker 8.2.0 and use `:async` strategy) - Improves page performance by optimizing how component packs are loaded +### ESM Support + +The package is now published in both ES Module and CommonJS formats. +In the next major release we'll work on making it much more tree-shakable to reduce bundle size. +In most cases you don't need to do anything to take advantage of that, +but if you use `require('react-on-rails')` (or `'react-on-rails/...'`), +consider replacing it by `import`. + ## Breaking Changes ### Component Hydration Changes From e45c669363e782c2bdbee76064d8d5f187c82245 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Thu, 3 Apr 2025 09:19:14 +0000 Subject: [PATCH 7/9] Fix Jest tests --- jest.config.js | 5 +++++ tsconfig.jest.json | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 tsconfig.jest.json diff --git a/jest.config.js b/jest.config.js index 70377255f2..f041678099 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,6 +2,11 @@ const nodeVersion = parseInt(process.version.slice(1), 10); module.exports = { preset: 'ts-jest/presets/js-with-ts', + globals: { + 'ts-jest': { + tsconfig: 'tsconfig.jest.json', + }, + }, testEnvironment: 'jsdom', setupFiles: ['/node_package/tests/jest.setup.js'], // React Server Components tests are compatible with React 19 diff --git a/tsconfig.jest.json b/tsconfig.jest.json new file mode 100644 index 0000000000..9d0866e6f6 --- /dev/null +++ b/tsconfig.jest.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": false + } +} From c667a15f94a15a990096ab0253ecbcbafed1c7f5 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Thu, 3 Apr 2025 09:28:07 +0000 Subject: [PATCH 8/9] Fix ESM issue with React 19 --- node_package/src/ClientSideRenderer.ts | 21 ++++++++++++++++----- node_package/src/reactHydrateOrRender.ts | 22 ++++++++++++++-------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/node_package/src/ClientSideRenderer.ts b/node_package/src/ClientSideRenderer.ts index dbce778a50..d438c5817f 100644 --- a/node_package/src/ClientSideRenderer.ts +++ b/node_package/src/ClientSideRenderer.ts @@ -1,7 +1,6 @@ /* eslint-disable max-classes-per-file */ -/* eslint-disable react/no-deprecated,@typescript-eslint/no-deprecated -- while we need to support React 16 */ +/* eslint-disable @typescript-eslint/no-deprecated -- while we need to support React 16 */ -import * as ReactDOM from 'react-dom'; import type { ReactElement } from 'react'; import type { RailsContext, RegisteredComponent, RenderFunction, Root } from './_types.ts'; @@ -14,6 +13,19 @@ import { debugTurbolinks } from './turbolinksUtils.ts'; const REACT_ON_RAILS_STORE_ATTRIBUTE = 'data-js-react-on-rails-store'; +// Can't just import react-dom because that breaks ESM under React 19 +let reactDom: typeof import('react-dom'); + +const getReactDom = () => { + try { + // eslint-disable-next-line global-require,@typescript-eslint/no-require-imports + reactDom ||= require('react-dom') as typeof import('react-dom'); + return reactDom; + } catch (_e) { + return undefined; + } +}; + async function delegateToRenderer( componentObj: RegisteredComponent, props: Record, @@ -101,8 +113,7 @@ class ComponentRenderer { } // Hydrate if available and was server rendered - // @ts-expect-error potentially present if React 18 or greater - const shouldHydrate = !!(ReactDOM.hydrate || ReactDOM.hydrateRoot) && !!domNode.innerHTML; + const shouldHydrate = (supportsRootApi || !!getReactDom()?.hydrate) && !!domNode.innerHTML; const reactElementOrRouterResult = createReactOutput({ componentObj, @@ -154,7 +165,7 @@ You should return a React.Component always for the client side entry point.`); } try { - ReactDOM.unmountComponentAtNode(domNode); + getReactDom()?.unmountComponentAtNode(domNode); } catch (e: unknown) { const error = e instanceof Error ? e : new Error('Unknown error'); console.info( diff --git a/node_package/src/reactHydrateOrRender.ts b/node_package/src/reactHydrateOrRender.ts index 7c7d29f23a..4473510794 100644 --- a/node_package/src/reactHydrateOrRender.ts +++ b/node_package/src/reactHydrateOrRender.ts @@ -1,5 +1,5 @@ +/* eslint-disable global-require,@typescript-eslint/no-require-imports */ import type { ReactElement } from 'react'; -import * as ReactDOM from 'react-dom'; import type { RenderReturnType } from './_types.ts'; import { supportsRootApi } from './reactApis.ts'; @@ -8,26 +8,33 @@ type HydrateOrRenderType = (domNode: Element, reactElement: ReactElement) => Ren // TODO: once React dependency is updated to >= 18, we can remove this and just // import ReactDOM from 'react-dom/client'; let reactDomClient: typeof import('react-dom/client'); +// Can't just import react-dom because that breaks ESM under React 19 +let reactDom: typeof import('react-dom'); if (supportsRootApi) { // This will never throw an exception, but it's the way to tell Webpack the dependency is optional // https://github.com/webpack/webpack/issues/339#issuecomment-47739112 // Unfortunately, it only converts the error to a warning. try { - // eslint-disable-next-line global-require,@typescript-eslint/no-require-imports reactDomClient = require('react-dom/client') as typeof import('react-dom/client'); } catch (_e) { // We should never get here, but if we do, we'll just use the default ReactDOM // and live with the warning. - reactDomClient = ReactDOM as unknown as typeof import('react-dom/client'); + reactDomClient = require('react-dom') as unknown as typeof import('react-dom/client'); + } +} else { + try { + reactDom = require('react-dom') as typeof import('react-dom'); + } catch (_e) { + // Also should never happen } } -/* eslint-disable react/no-deprecated,@typescript-eslint/no-deprecated,@typescript-eslint/no-non-null-assertion -- +/* eslint-disable @typescript-eslint/no-deprecated,@typescript-eslint/no-non-null-assertion -- * while we need to support React 16 */ const reactHydrate: HydrateOrRenderType = supportsRootApi ? reactDomClient!.hydrateRoot - : (domNode, reactElement) => ReactDOM.hydrate(reactElement, domNode); + : (domNode, reactElement) => reactDom!.hydrate(reactElement, domNode); function reactRender(domNode: Element, reactElement: ReactElement): RenderReturnType { if (supportsRootApi) { @@ -36,10 +43,9 @@ function reactRender(domNode: Element, reactElement: ReactElement): RenderReturn return root; } - // eslint-disable-next-line react/no-render-return-value - return ReactDOM.render(reactElement, domNode); + return reactDom!.render(reactElement, domNode); } -/* eslint-enable react/no-deprecated,@typescript-eslint/no-deprecated,@typescript-eslint/no-non-null-assertion */ +/* eslint-enable @typescript-eslint/no-deprecated,@typescript-eslint/no-non-null-assertion */ export default function reactHydrateOrRender( domNode: Element, From afe9f7b2817c351f64cbed8bcf05dffac441d88c Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Thu, 3 Apr 2025 13:20:47 +0000 Subject: [PATCH 9/9] Fix 'require is not defined' --- node_package/src/ClientSideRenderer.ts | 21 ++++--------------- node_package/src/ReactOnRails.client.ts | 2 +- .../src/{reactApis.ts => reactApis.cts} | 7 +++++-- ...teOrRender.ts => reactHydrateOrRender.cts} | 2 +- 4 files changed, 11 insertions(+), 21 deletions(-) rename node_package/src/{reactApis.ts => reactApis.cts} (57%) rename node_package/src/{reactHydrateOrRender.ts => reactHydrateOrRender.cts} (97%) diff --git a/node_package/src/ClientSideRenderer.ts b/node_package/src/ClientSideRenderer.ts index d438c5817f..de785d44ee 100644 --- a/node_package/src/ClientSideRenderer.ts +++ b/node_package/src/ClientSideRenderer.ts @@ -7,25 +7,12 @@ import type { RailsContext, RegisteredComponent, RenderFunction, Root } from './ import { getContextAndRailsContext, resetContextAndRailsContext, type Context } from './context.ts'; import createReactOutput from './createReactOutput.ts'; import { isServerRenderHash } from './isServerRenderResult.ts'; -import reactHydrateOrRender from './reactHydrateOrRender.ts'; -import { supportsRootApi } from './reactApis.ts'; +import reactHydrateOrRender from './reactHydrateOrRender.cts'; +import { canHydrate, unmountComponentAtNode, supportsRootApi } from './reactApis.cts'; import { debugTurbolinks } from './turbolinksUtils.ts'; const REACT_ON_RAILS_STORE_ATTRIBUTE = 'data-js-react-on-rails-store'; -// Can't just import react-dom because that breaks ESM under React 19 -let reactDom: typeof import('react-dom'); - -const getReactDom = () => { - try { - // eslint-disable-next-line global-require,@typescript-eslint/no-require-imports - reactDom ||= require('react-dom') as typeof import('react-dom'); - return reactDom; - } catch (_e) { - return undefined; - } -}; - async function delegateToRenderer( componentObj: RegisteredComponent, props: Record, @@ -113,7 +100,7 @@ class ComponentRenderer { } // Hydrate if available and was server rendered - const shouldHydrate = (supportsRootApi || !!getReactDom()?.hydrate) && !!domNode.innerHTML; + const shouldHydrate = canHydrate && !!domNode.innerHTML; const reactElementOrRouterResult = createReactOutput({ componentObj, @@ -165,7 +152,7 @@ You should return a React.Component always for the client side entry point.`); } try { - getReactDom()?.unmountComponentAtNode(domNode); + unmountComponentAtNode(domNode); } catch (e: unknown) { const error = e instanceof Error ? e : new Error('Unknown error'); console.info( diff --git a/node_package/src/ReactOnRails.client.ts b/node_package/src/ReactOnRails.client.ts index 7a2b62610f..59b1bad802 100644 --- a/node_package/src/ReactOnRails.client.ts +++ b/node_package/src/ReactOnRails.client.ts @@ -17,7 +17,7 @@ import type { StoreGenerator, ReactOnRailsOptions, } from './_types.ts'; -import reactHydrateOrRender from './reactHydrateOrRender.ts'; +import reactHydrateOrRender from './reactHydrateOrRender.cts'; const ctx = context(); diff --git a/node_package/src/reactApis.ts b/node_package/src/reactApis.cts similarity index 57% rename from node_package/src/reactApis.ts rename to node_package/src/reactApis.cts index 4db560e326..f880afaeb6 100644 --- a/node_package/src/reactApis.ts +++ b/node_package/src/reactApis.cts @@ -1,8 +1,11 @@ +/* eslint-disable react/no-deprecated,@typescript-eslint/no-deprecated */ import * as ReactDOM from 'react-dom'; const reactMajorVersion = Number(ReactDOM.version?.split('.')[0]) || 16; // TODO: once we require React 18, we can remove this and inline everything guarded by it. -// Not the default export because others may be added for future React versions. -// eslint-disable-next-line import/prefer-default-export export const supportsRootApi = reactMajorVersion >= 18; + +export const canHydrate = supportsRootApi || !!ReactDOM.hydrate; + +export const { unmountComponentAtNode } = ReactDOM; diff --git a/node_package/src/reactHydrateOrRender.ts b/node_package/src/reactHydrateOrRender.cts similarity index 97% rename from node_package/src/reactHydrateOrRender.ts rename to node_package/src/reactHydrateOrRender.cts index 4473510794..76811197c8 100644 --- a/node_package/src/reactHydrateOrRender.ts +++ b/node_package/src/reactHydrateOrRender.cts @@ -1,7 +1,7 @@ /* eslint-disable global-require,@typescript-eslint/no-require-imports */ import type { ReactElement } from 'react'; import type { RenderReturnType } from './_types.ts'; -import { supportsRootApi } from './reactApis.ts'; +import { supportsRootApi } from './reactApis.cts'; type HydrateOrRenderType = (domNode: Element, reactElement: ReactElement) => RenderReturnType;