Skip to content

Switch to ESM #1726

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Apr 18, 2025
Merged

Switch to ESM #1726

merged 16 commits into from
Apr 18, 2025

Conversation

alexeyr-ci2
Copy link
Collaborator

@alexeyr-ci2 alexeyr-ci2 commented Apr 7, 2025

Summary

  • Publish the package as ESM instead of CommonJS
  • Document the implications

Pull Request checklist

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file

This change is Reviewable

Summary by CodeRabbit

Summary by CodeRabbit

  • Breaking Changes

    • The package is now distributed as ESM-only. Users relying on CommonJS must update their environments (e.g., Node and TypeScript settings) accordingly.
  • Enhancements

    • Improved workflows for code quality and testing, including streamlined linting and test execution.
    • Introduced new Jest tests for the dummy application to enhance testing coverage.
    • Updated global access methods for better compatibility and consistency.
    • Added a new step for executing JavaScript tests in the workflow.
  • Chores

    • Upgraded the TypeScript compiler and refined configuration settings to support explicit module resolution and modern import behaviors.
    • Added Jest dependency to the dummy application for testing purposes.

Copy link
Contributor

coderabbitai bot commented Apr 7, 2025

"""

Walkthrough

The pull request transitions the package to an ESM-only model. Linting and configuration files have been updated accordingly, including changes to workflows, ESLint, Jest, package.json, and tsconfig.json. Many source and test files now use explicit file extensions in their import and export statements. Documentation, release notes, and the changelog reflect breaking changes such as globalThis usage and the new module system. Additionally, a new workflow step runs JavaScript tests, and rendering control has been refactored via updates to reactApis and reactHydrateOrRender.

Changes

Files/Groups Change Summary
.github/workflows/lint-js-and-ruby.yml
.github/workflows/main.yml
Updated linting command to mark package as ESM-only; added new step “Dummy JS tests”.
CHANGELOG.md
docs/release-notes/15.0.0.md
Documented breaking changes: ESM-only package and usage of globalThis; provided Node/TypeScript upgrade recommendations.
eslint.config.ts
jest.config.js
package.json
tsconfig.json
spec/dummy/package.json
Revised configurations: ESLint rules for import extensions, Jest config moved to ES module syntax, package type changed to module, TypeScript version and compiler options updated, and added Jest dependency/script.
node_package/src/reactApis.cts
node_package/src/reactApis.ts
node_package/src/reactHydrateOrRender.ts
Replaced reactApis.ts with reactApis.cts; refactored reactHydrateOrRender to use new reactApis functions and added hydrate flag parameter.
node_package/src/** (many files) Updated numerous source files with explicit .ts, .cts, and index.ts extensions in import/export paths; adjusted imports for ReactDOMServer and other utilities; modified render and unmount logic in ClientSideRenderer.
node_package/tests/** Updated test files to reference source files with explicit file extensions (.ts or .js as appropriate).
spec/dummy/package.json Added Jest dependency and test script for dummy app.
spec/dummy/tests/react-on-rails.import.test.js
spec/dummy/tests/react-on-rails.require.test.js
Added new test files verifying ReactOnRails registration via import and require methods.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Client UI
    participant ROR as ReactOnRails
    participant RHR as reactHydrateOrRender
    participant API as reactApis.cts
    UI->>ROR: Trigger component render
    ROR->>RHR: Call render function with element & hydrate flag
    alt Hydrate flag is true
      RHR->>API: Call reactHydrate (checks supportsRootApi)
      API-->>RHR: Return hydration result
    else Hydrate flag is false
      RHR->>API: Call reactRender for standard render
      API-->>RHR: Return render result
    end
    RHR-->>ROR: Send back render outcome
Loading

Possibly related PRs

Suggested reviewers

  • alexeyr-ci2
  • Judahmeek

Poem

I'm a rabbit in the code field bright,
Hopping through ESM changes with delight.
Imports now shine with their extensions clear,
Workflows and tests sing a tune sincere.
I nibble bugs and dance with every fix,
In a garden of code, where new syntax tricks.
🐰 Happy hops to our updated mix!
"""

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d2eb8e and 642b86b.

⛔ Files ignored due to path filters (1)
  • spec/dummy/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • jest.config.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • jest.config.js
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: rspec-package-tests (oldest)
  • GitHub Check: rspec-package-tests (newest)
  • GitHub Check: build

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@alexeyr-ci alexeyr-ci force-pushed the alexeyr/esm branch 2 times, most recently from 4d2f0ff to 7426ebf Compare April 7, 2025 16:54
@alexeyr-ci alexeyr-ci force-pushed the alexeyr/esm branch 10 times, most recently from e78c389 to 23e6383 Compare April 11, 2025 10:07
@alexeyr-ci2 alexeyr-ci2 marked this pull request as ready for review April 11, 2025 10:41
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
node_package/src/reactApis.cts (1)

1-49: 💡 Verification agent

🧩 Analysis chain

Good abstraction for cross-version React rendering compatibility

This new file provides a clean abstraction for handling React rendering across different versions, particularly supporting both React 18's root API and older versions. The conditional logic and feature detection approach is well-implemented.

A few observations:

  1. The file uses .cts extension (CommonJS TypeScript) with require() statements while the project is moving to ESM
  2. The non-null assertions with ! operator could be handled more safely

🏁 Script executed:

#!/bin/bash
# Check React version in package.json to understand if this compatibility approach is necessary
grep -A 5 '"react":' package.json || grep -A 5 '"react":' node_package/package.json

Length of output: 326


Action Required: Refactor for Modern React API and ESM Compatibility

The abstraction currently handles both legacy and modern React rendering, but with our dependency now at React 19, the fallback path for React 16 is no longer needed. Specifically:

  • Remove Legacy Fallback: Since React is now always ≥ 18, the try-catch block that uses CommonJS require('react-dom/client') and the conditional branch for non-ESM usage can be safely removed.
  • Migrate to ESM: Convert the file from a .cts module to an ESM-compatible .ts module. Replace dynamic require() calls with static ESM imports. For example, import ReactDOMClient directly (e.g., import * as ReactDOMClient from 'react-dom/client';) and eliminate the non-null assertions.
  • Clean Up Non-Null Assertions: With the legacy branch removed and a direct ESM import in place, the need for the ! operator vanishes, allowing for safer, cleaner code.

Implementing these changes will simplify the code and align it with our updated dependency versions.

🧹 Nitpick comments (4)
docs/release-notes/15.0.0.md (1)

71-79: Release Notes Update: ESM-only Package Section.
The new section clearly informs users about the switch to ES Modules. A few minor formatting suggestions:

  • Consider inserting a comma after “In most cases” to read “In most cases, it shouldn’t affect your code…” for improved clarity.
  • For the text about troubleshooting TS1479, a slight rewording might strengthen the guidance.
  • Also, add a comma before “and” in the phrase “please contact us, and we'll help you…” to correctly join the independent clauses.
    These adjustments will enhance readability.
🧰 Tools
🪛 LanguageTool

[formatting] ~73-~73: Insert a comma after ‘cases’: “In most cases,”?
Context: ...shed as ES Modules instead of CommonJS. In most cases it shouldn't affect your code, as bundl...

(IN_MOST_CASES_COMMA)


[style] ~76-~76: Consider replacing this word to strengthen your wording.
Context: ... should work as well. - If you run into TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Typ...

(AND_THAT)


[uncategorized] ~78-~78: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...everything else fails, please contact us and we'll help you upgrade or release a dua...

(COMMA_COMPOUND_SENTENCE)

node_package/src/reactApis.cts (2)

13-25: Consider using dynamic import for better ESM compatibility

The current implementation uses CommonJS require() for conditional loading of react-dom/client. Since the project is moving to ESM, consider using dynamic import instead:

-  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');
-  }
+  // Use dynamic import for ESM compatibility
+  import('react-dom/client')
+    .then((module) => {
+      reactDomClient = module;
+    })
+    .catch((_e) => {
+      // We should never get here, but if we do, we'll just use the default ReactDOM
+      reactDomClient = ReactDOM as unknown as typeof import('react-dom/client');
+    });

35-47: Consider avoiding non-null assertions for better type safety

The code uses non-null assertions (!) with reactDomClient, which can be error-prone. Consider refactoring to avoid these assertions:

-export const reactHydrate: HydrateOrRenderType = supportsRootApi
-  ? reactDomClient!.hydrateRoot
-  : (domNode, reactElement) => ReactDOM[hydrateProp](reactElement, domNode);
+export const reactHydrate: HydrateOrRenderType = (domNode, reactElement) => {
+  if (supportsRootApi && reactDomClient) {
+    return reactDomClient.hydrateRoot(domNode, reactElement);
+  }
+  return ReactDOM[hydrateProp](reactElement, domNode);
+};

-export function reactRender(domNode: Element, reactElement: ReactElement): RenderReturnType {
-  if (supportsRootApi) {
-    const root = reactDomClient!.createRoot(domNode);
-    root.render(reactElement);
-    return root;
-  }
-
-  return ReactDOM[renderProp](reactElement, domNode);
-}
+export function reactRender(domNode: Element, reactElement: ReactElement): RenderReturnType {
+  if (supportsRootApi && reactDomClient) {
+    const root = reactDomClient.createRoot(domNode);
+    root.render(reactElement);
+    return root;
+  }
+
+  return ReactDOM[renderProp](reactElement, domNode);
+}

This approach makes the code more defensive and avoids potential runtime errors if reactDomClient is unexpectedly undefined.

node_package/src/ClientSideRenderer.ts (1)

157-159: Modified ReactDOM unmounting to handle deprecated API.

The code now uses string indexing to access the deprecated unmountComponentAtNode method, with an appropriate ESLint directive to suppress the deprecation warning. This approach allows continued use of the method while acknowledging its deprecated status.

Consider adding a comment explaining the reason for this workaround, such as:

 const unmountComponentAtNode = 'unmountComponentAtNode';
+// Using string indexing to access the deprecated API while maintaining compatibility
 // eslint-disable-next-line @typescript-eslint/no-deprecated
 ReactDOM[unmountComponentAtNode](domNode);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0a4c1c and b71851f.

⛔ Files ignored due to path filters (2)
  • spec/dummy/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (49)
  • .github/workflows/lint-js-and-ruby.yml (1 hunks)
  • .github/workflows/main.yml (1 hunks)
  • CHANGELOG.md (1 hunks)
  • docs/release-notes/15.0.0.md (1 hunks)
  • eslint.config.ts (2 hunks)
  • jest.config.js (1 hunks)
  • node_package/src/Authenticity.ts (1 hunks)
  • node_package/src/CallbackRegistry.ts (1 hunks)
  • node_package/src/ClientSideRenderer.ts (3 hunks)
  • node_package/src/ComponentRegistry.ts (1 hunks)
  • node_package/src/RSCClientRoot.ts (1 hunks)
  • node_package/src/ReactOnRails.client.ts (3 hunks)
  • node_package/src/ReactOnRails.full.ts (2 hunks)
  • node_package/src/ReactOnRails.node.ts (1 hunks)
  • node_package/src/ReactOnRailsRSC.ts (2 hunks)
  • node_package/src/StoreRegistry.ts (1 hunks)
  • node_package/src/buildConsoleReplay.ts (1 hunks)
  • node_package/src/clientStartup.ts (1 hunks)
  • node_package/src/context.ts (1 hunks)
  • node_package/src/createReactOutput.ts (1 hunks)
  • node_package/src/handleError.ts (1 hunks)
  • node_package/src/isRenderFunction.ts (1 hunks)
  • node_package/src/isServerRenderResult.ts (1 hunks)
  • node_package/src/pageLifecycle.ts (1 hunks)
  • node_package/src/reactApis.cts (1 hunks)
  • node_package/src/reactApis.ts (0 hunks)
  • node_package/src/reactHydrateOrRender.ts (1 hunks)
  • node_package/src/registerServerComponent/client.ts (1 hunks)
  • node_package/src/registerServerComponent/server.ts (1 hunks)
  • node_package/src/serverRenderReactComponent.ts (2 hunks)
  • node_package/src/serverRenderUtils.ts (1 hunks)
  • node_package/src/streamServerRenderedReactComponent.ts (1 hunks)
  • node_package/src/transformRSCStreamAndReplayConsoleLogs.ts (1 hunks)
  • node_package/tests/Authenticity.test.js (1 hunks)
  • node_package/tests/ComponentRegistry.test.js (2 hunks)
  • node_package/tests/RSCClientRoot.test.jsx (2 hunks)
  • node_package/tests/ReactOnRails.test.jsx (1 hunks)
  • node_package/tests/StoreRegistry.test.js (1 hunks)
  • node_package/tests/buildConsoleReplay.test.js (1 hunks)
  • node_package/tests/renderFunction.test.jsx (1 hunks)
  • node_package/tests/scriptSanitizedVal.test.js (1 hunks)
  • node_package/tests/serverRenderReactComponent.test.ts (1 hunks)
  • node_package/tests/streamServerRenderedReactComponent.test.jsx (1 hunks)
  • node_package/tests/utils.test.js (1 hunks)
  • package.json (2 hunks)
  • spec/dummy/package.json (2 hunks)
  • spec/dummy/tests/react-on-rails.import.test.js (1 hunks)
  • spec/dummy/tests/react-on-rails.require.test.js (1 hunks)
  • tsconfig.json (1 hunks)
💤 Files with no reviewable changes (1)
  • node_package/src/reactApis.ts
🧰 Additional context used
🧬 Code Graph Analysis (3)
spec/dummy/tests/react-on-rails.import.test.js (1)
spec/dummy/tests/react-on-rails.require.test.js (1)
  • ReactOnRails (1-1)
node_package/src/ReactOnRails.node.ts (2)
node_package/src/types/index.ts (1)
  • ReactOnRails (209-273)
node_package/src/ReactOnRails.client.ts (1)
  • streamServerRenderedReactComponent (156-160)
node_package/src/ClientSideRenderer.ts (1)
spec/dummy/client/app/startup/ManualRenderApp.jsx (1)
  • domNode (12-12)
🪛 LanguageTool
docs/release-notes/15.0.0.md

[formatting] ~73-~73: Insert a comma after ‘cases’: “In most cases,”?
Context: ...shed as ES Modules instead of CommonJS. In most cases it shouldn't affect your code, as bundl...

(IN_MOST_CASES_COMMA)


[style] ~76-~76: Consider replacing this word to strengthen your wording.
Context: ... should work as well. - If you run into TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Typ...

(AND_THAT)


[uncategorized] ~78-~78: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...everything else fails, please contact us and we'll help you upgrade or release a dua...

(COMMA_COMPOUND_SENTENCE)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: dummy-app-integration-tests (oldest)
  • GitHub Check: dummy-app-integration-tests (newest)
🔇 Additional comments (58)
tsconfig.json (1)

11-12: Appropriate TypeScript configuration for ESM compatibility.

The added TypeScript compiler options properly support the transition to ECMAScript Modules:

  • allowImportingTsExtensions enables imports with .ts extensions in source code
  • rewriteRelativeImportExtensions ensures proper runtime compatibility by rewriting extensions in emitted JS files

These changes align well with the PR objective to switch to an ESM-only package.

jest.config.js (1)

3-10: Updated Jest configuration for ESM compatibility.

Good conversion from CommonJS to ESM export syntax. The added globals configuration correctly disables rewriteRelativeImportExtensions for ts-jest, which is a sensible choice as testing environments often require different handling than production code.

This configuration prevents potential issues with ts-jest's handling of file extensions while still maintaining ESM compatibility.

.github/workflows/lint-js-and-ruby.yml (1)

92-93: CI workflow appropriately updated for ESM verification.

The updated linting command with --profile esm-only correctly configures the type checking tool to validate ESM compatibility, which is essential for ensuring that the package's TypeScript definitions work properly in an ESM environment.

This change aligns with the PR's objective to transition to an ESM-only package.

eslint.config.ts (2)

136-141: Enforce ESM import standards for node_package files.

This new rule configuration properly enforces the use of file extensions in import statements for the node_package directory, which is a requirement for ESM compatibility. The 'ignorePackages' option correctly exempts package imports while enforcing extensions for relative imports.

This rule is essential for ensuring that all imports in the package follow ESM standards.


152-152: Expanded TypeScript file pattern for comprehensive linting.

The updated file pattern improves TypeScript linting coverage to include:

  • All .tsx files with the more concise pattern ts{x,}
  • Module TypeScript files with [cm]ts extension

This ensures that all TypeScript files, including those specifically for ESM or CommonJS modules, are properly linted with TypeScript-specific rules.

node_package/src/Authenticity.ts (1)

1-1: Update import path to include .ts extension.

The import statement now includes the .ts file extension, which is required for proper module resolution in ESM. This change is aligned with the transition to ECMAScript Modules.

.github/workflows/main.yml (1)

157-160: Added new step for running JS tests in the dummy app.

This step runs JavaScript tests in the dummy application, which is beneficial for ensuring the ESM transition works properly. The step is correctly positioned between Node module installation and Ruby Gems installation.

spec/dummy/tests/react-on-rails.import.test.js (1)

1-5: New test file using ESM import syntax.

This test verifies that the ReactOnRails module can be imported and used with ESM syntax. The test is simple but effective in validating that the basic functionality works after the transition to ESM.

spec/dummy/tests/react-on-rails.require.test.js (1)

1-5: New test file using CommonJS require syntax.

This test ensures backward compatibility by verifying that the ReactOnRails module can still be imported using CommonJS syntax. Having both import and require tests provides good coverage for the ESM transition.

However, since the package is transitioning to ESM-only (as mentioned in the PR objectives), verify if maintaining CommonJS compatibility is intentional or if this should eventually be deprecated.

node_package/src/serverRenderUtils.ts (1)

7-7: Explicit File Extension in Import Statement

The import now explicitly uses './types/index.ts' instead of the ambiguous './types'. This update aligns with the ESM transition and improves clarity in module resolution.

node_package/src/pageLifecycle.ts (1)

7-7: Updated Import for Turbolinks Utilities

The import path for turbolinksUtils has been updated to include the .ts extension ('./turbolinksUtils.ts'), making it explicit and consistent with the ESM module requirements.

node_package/src/handleError.ts (1)

3-3: Explicit Extension for ErrorOptions Import

The import for ErrorOptions now specifies the file extension ('./types/index.ts'), which is essential for proper ESM resolution. The rest of the file remains unchanged and functions as expected.

node_package/src/isRenderFunction.ts (1)

3-3: Updated Import with Explicit File Extension

The import of { ReactComponentOrRenderFunction, RenderFunction } now explicitly includes .ts, ensuring consistency with the overall ESM migration and reducing potential resolution issues.

spec/dummy/package.json (2)

48-48: Addition of Jest Dependency

The Jest dependency ("jest": "^29.7.0") has been added to devDependencies. This inclusion supports the updated testing setup and aligns with the project's shift toward ESM.


72-73: New "test:js" Script for Jest

A new script "test:js": "yarn run jest ./tests" has been introduced to run JavaScript tests using Jest. Please ensure that the test files in ./tests are properly configured to work with ESM.

node_package/src/transformRSCStreamAndReplayConsoleLogs.ts (1)

1-1: Explicit Import Extension for ESM Compliance

The updated import statement now explicitly references index.ts, which aligns well with the ESM-only strategy and aids in unambiguous module resolution.

node_package/src/isServerRenderResult.ts (1)

6-6: Clear TypeScript Module Resolution

Changing the import path to include the .ts extension improves clarity and consistency with the ESM architecture. This makes the source of imported types explicit.

node_package/tests/StoreRegistry.test.js (1)

3-3: Explicit TypeScript Extension in Test Import

Updating the import statement to use ../src/StoreRegistry.ts ensures that the test file now consistently references the TypeScript module. This explicit extension supports improved module resolution under the ESM model.

node_package/tests/ReactOnRails.test.jsx (1)

7-7: Consistent Import of ReactOnRails Client Module

The modification to import from ../src/ReactOnRails.client.ts reflects the shift to an explicit TypeScript reference, ensuring consistency across the codebase as part of the ESM transition.

node_package/tests/Authenticity.test.js (1)

1-1: Updated Import for ReactOnRails with Explicit .ts Extension

Switching the import to ../src/ReactOnRails.client.ts is appropriate and consistent with the overall ESM and TypeScript migration. This change improves clarity for module resolution.

node_package/tests/renderFunction.test.jsx (1)

8-8: Explicit File Extension in Import Statement

The updated import now explicitly references isRenderFunction.ts, which is in line with the project's ESM transition. This change ensures that module resolution works correctly in an ESM environment.

node_package/tests/scriptSanitizedVal.test.js (1)

1-1: Updated Import to Include TypeScript Extension

The import statement now points to scriptSanitizedVal.ts instead of relying on implicit resolution. This update improves clarity and module resolution consistency across the codebase.

package.json (2)

6-6: Switching to ESM Mode in Package Configuration

Changing the "type" field from "commonjs" to "module" correctly signals that the package should be treated as ESM. This is a vital step for the overall transition and aligns with the PR objectives.


62-62: Upgrading TypeScript Version

The update from "^5.6.2" to "^5.8.3" ensures that the project can take advantage of the latest TypeScript improvements and bug fixes. Verify that this newer version is fully compatible with your existing code.

node_package/tests/buildConsoleReplay.test.js (1)

1-1: Consistent Module Import with File Extension

The modified import statement now includes the .ts extension for buildConsoleReplay, ensuring that module resolution under ESM is handled correctly and consistently throughout the test suite.

node_package/src/context.ts (1)

1-1: Explicit Import Path for Type Declarations

Updating the import statement to reference ./types/index.ts explicitly removes any ambiguity related to module resolution. This change promotes greater clarity in the codebase, particularly in an ESM setup.

node_package/src/buildConsoleReplay.ts (1)

1-2: Update import paths for ESM compatibility

The import statements now include the explicit “.ts” extension. This change aligns with the ESM-only model and ensures that module resolution behaves as expected under the new configuration.

node_package/src/registerServerComponent/server.ts (1)

1-2: Adjust import paths for explicit TypeScript extensions

By specifying the “.ts” extension in the import paths (e.g. for ReactOnRails and ReactComponent), the changes ensure consistency with the broader ESM transition across the codebase. This update improves clarity in module resolution.

CHANGELOG.md (1)

38-40: Document breaking ESM changes and global context update

The changelog updates now clearly state the breaking change to an ESM-only package (line 38) and the switch to using globalThis for global context access (line 39). This update informs users adequately about the migration steps and potential code modifications.

node_package/tests/streamServerRenderedReactComponent.test.jsx (1)

7-8: Update test import paths for explicit TypeScript extensions

The changed import statements now reference “.ts” extensions (for both streamServerRenderedReactComponent and ComponentRegistry), ensuring that the test file accurately maps to the updated ESM module locations. This consistency helps avoid module resolution issues during testing.

node_package/src/createReactOutput.ts (1)

2-3: Ensure explicit file extensions in import paths for ESM

Updating the import statements in lines 2 and 3 to include “.ts” (for the types and utility functions) is a necessary adjustment for the ESM-only transition. This ensures that TypeScript’s module resolution remains unambiguous and consistent with the rest of the project.

node_package/src/registerServerComponent/client.ts (1)

1-7: ESM Import Paths Updated.
The import statements now explicitly include the .ts extension, which aligns with the ESM-only transition across the codebase. Please verify that these updated paths correctly resolve to their intended modules throughout all environments.

node_package/src/RSCClientRoot.ts (1)

6-8: Updated ESM Import Statements.
The imports for utilities and types now specify the .ts extension (e.g. ./utils.ts, ./transformRSCStreamAndReplayConsoleLogs.ts, and ./types/index.ts), ensuring correct module resolution under ESM. This update is consistent with the project's new module strategy.

node_package/src/StoreRegistry.ts (1)

1-2: Explicit File Extensions in Imports.
The import paths for CallbackRegistry and the types now include the .ts extension, which is crucial for ESM module resolution in TypeScript. This change improves clarity and consistency.

node_package/src/CallbackRegistry.ts (1)

1-3: Consistent Module Resolution with Explicit Extensions.
The updated import statements now refer to ./types/index.ts, ./pageLifecycle.ts, and ./context.ts, ensuring adherence to the ESM pattern. This consistency helps prevent module resolution issues moving forward.

node_package/src/ComponentRegistry.ts (1)

1-3: Update import paths to include file extensions for ESM compatibility

The changes to explicitly include .ts file extensions in import statements are necessary for ESM compatibility. ESM requires explicit file extensions for local imports, unlike CommonJS where they could be omitted.

These changes align with the PR's objective of switching to ESM and don't affect the component registry functionality.

node_package/tests/utils.test.js (1)

3-4: Explicit file extensions added to import paths

Adding explicit file extensions (.ts for TypeScript files and .js for JavaScript files) is required for ESM compatibility. This ensures proper module resolution in the test environment.

The changes maintain correct references to the source files while supporting the transition to ESM.

node_package/tests/ComponentRegistry.test.js (3)

9-9: Updated ComponentRegistry import path with .ts extension

Adding the .ts extension to the ComponentRegistry import path ensures compatibility with ESM module resolution requirements.


14-23: Updated mock path for pageLifecycle with .ts extension

The mock definition now correctly references the TypeScript file with its extension, maintaining consistency with the ESM migration while preserving the same mock implementation.


25-27: Updated mock path for context with .ts extension

The context mock path now includes the .ts extension, adhering to ESM import requirements while maintaining the same mock functionality.

node_package/src/reactHydrateOrRender.ts (2)

2-3: Updated imports with refactored rendering approach

The imports have been updated to include file extensions and now leverage the new reactApis.cts module. Note the .cts extension indicates a CommonJS TypeScript module, suggesting this particular module might still be in CommonJS format while the rest of the codebase transitions to ESM.


5-11: Simplified hydration/rendering logic with improved API

The function has been refactored to:

  1. Accept an explicit hydrate boolean parameter
  2. Delegate rendering to the appropriate specialized function

This is a cleaner approach that improves readability and separates concerns by moving the React version detection logic to the imported functions. The new signature makes the hydration behavior explicit rather than determined internally.

node_package/src/clientStartup.ts (1)

7-9: Updated imports with explicit file extensions

The changes to use explicit file extensions in import statements (adding .ts) are aligned with the ESM module system requirements, which is the main objective of this PR.

node_package/tests/RSCClientRoot.test.jsx (3)

12-12: Added explicit .js extension to testUtils import

This change correctly adds the .js file extension for the ESM module system requirements.


14-14: Updated RSCClientRoot import path with .ts extension

The import statement now correctly includes the .ts extension as required for ESM compatibility.


46-46: Updated jest.requireActual path with .ts extension

The path in jest.requireActual now correctly includes the .ts extension to maintain consistency with the import statement on line 14.

node_package/src/ReactOnRails.full.ts (3)

1-3: Updated import paths with .ts extensions

The import statements now correctly include .ts extensions for ESM compatibility.


5-5: Updated Client import path with .ts extension

The import statement now correctly includes the .ts extension.


18-18: Updated types export path with index.ts extension

The export statement now correctly specifies the full path including /index.ts which is necessary for ESM module resolution.

node_package/src/serverRenderReactComponent.ts (2)

5-10: Updated import paths with .ts extensions

All import statements have been properly updated to include .ts file extensions to ensure compatibility with the ESM module system.


20-20: Updated types import path to reference index.ts

The import path for types has been properly updated to reference ./types/index.ts instead of just ./types, which is necessary for explicit module resolution in ESM.

node_package/src/streamServerRenderedReactComponent.ts (1)

5-11: Import statements updated correctly for ESM compatibility

The changes to add file extensions (.ts) to all imports are necessary for ESM compatibility. This change is appropriate as part of the transition to ESM modules as mentioned in the PR objectives.

node_package/tests/serverRenderReactComponent.test.ts (1)

2-10: Import statements updated correctly for ESM compatibility

The changes to add file extensions (.ts) to import statements are appropriate and consistent with the ESM transition. The test file correctly references imported modules with their file extensions.

node_package/src/ReactOnRails.node.ts (1)

1-8: Import and export statements updated correctly for ESM compatibility

The changes to add file extensions (.ts) to import and export statements are appropriate for ESM compatibility and consistent with the other changes in this PR.

node_package/src/ReactOnRails.client.ts (1)

2-8: Updated import/export statements to include file extensions for ESM compatibility.

The file extensions have been added to all import and export statements as required for ECMAScript Modules (ESM). This change is part of the broader transition to ESM as mentioned in the PR objectives.

Also applies to: 18-19, 197-197

node_package/src/ReactOnRailsRSC.ts (1)

4-15: Updated import/export statements to include file extensions for ESM compatibility.

All import and export statements have been updated to include .ts file extensions as required for ECMAScript Modules. This is consistent with the changes in other files.

Also applies to: 70-70

node_package/src/ClientSideRenderer.ts (2)

5-14: Updated import statements to include file extensions for ESM compatibility.

Import statements have been updated to include .ts or .cts file extensions as required for ECMAScript Modules.


105-105: Improved hydration detection for better cross-version React compatibility.

The hydration detection logic has been updated to use supportsRootApi or check for the existence of hydrate in ReactDOM, making the code more resilient across different React versions during the ESM transition.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
node_package/src/reactApis.cts (1)

27-40: Consider simplifying the ReactDOMServer import pattern.

The nested try-catch blocks add complexity. Since this is a new file, you could simplify it by using a more direct approach with a version check.

-export const ReactDOMServer = (() => {
-  try {
-    // in react-dom v18+
-    return require('react-dom/server') as typeof import('react-dom/server');
-  } catch (_e) {
-    try {
-      // in react-dom v16 or 17
-      return require('react-dom/server.js') as typeof import('react-dom/server');
-    } catch (_e2) {
-      // this should never happen, one of the above requires should succeed
-      return undefined as unknown as typeof import('react-dom/server');
-    }
-  }
-})();
+export const ReactDOMServer = (() => {
+  const serverPath = reactMajorVersion >= 18 ? 'react-dom/server' : 'react-dom/server.js';
+  try {
+    return require(serverPath) as typeof import('react-dom/server');
+  } catch (e) {
+    console.warn(`Failed to load ReactDOMServer from ${serverPath}`, e);
+    return undefined as unknown as typeof import('react-dom/server');
+  }
+})();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b71851f and 18fd77e.

📒 Files selected for processing (4)
  • node_package/src/handleError.ts (1 hunks)
  • node_package/src/reactApis.cts (1 hunks)
  • node_package/src/serverRenderReactComponent.ts (2 hunks)
  • node_package/src/streamServerRenderedReactComponent.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • node_package/src/handleError.ts
  • node_package/src/serverRenderReactComponent.ts
  • node_package/src/streamServerRenderedReactComponent.ts
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: dummy-app-integration-tests (newest)
  • GitHub Check: dummy-app-integration-tests (oldest)
  • GitHub Check: build
  • GitHub Check: rspec-package-tests (newest)
  • GitHub Check: rspec-package-tests (oldest)
🔇 Additional comments (6)
node_package/src/reactApis.cts (6)

1-4: ESLint rule disabling and import syntax updated for ESM compatibility.

The import syntax using with { 'resolution-mode': 'import' } on line 4 indicates the file has been updated to support ESM module resolution, which aligns with the PR's objective to switch to ESM.


6-9: Good feature detection for React 18 support.

The code correctly detects React's major version and sets a flag for Root API support introduced in React 18. The TODO comment clearly explains the future cleanup path once React 18 becomes the minimum requirement.


42-49: Good ESLint configuration for backward compatibility.

The code correctly disables specific ESLint rules only where needed for React 16 support, with clear explanatory comments.


50-53: Clean implementation of hydration API with version support.

The ternary operator provides an elegant solution for selecting the appropriate API based on React version, handling the parameter order difference correctly.


54-63: Well-structured render function with appropriate return values.

The function correctly handles both React 18+ root-based rendering and legacy rendering, returning the appropriate type in each case. This ensures compatibility while enabling new features in React 18.


11-25:

❓ Verification inconclusive

Robust conditional import pattern for backward compatibility.

The implementation correctly handles the transition to React 18's new client API while maintaining compatibility with older versions. The try-catch pattern is a good approach for optional dependencies, though the fallback might not fully match the expected API surface.

Can you verify the compatibility of this approach with bundlers other than Webpack, such as Vite or ESBuild?


🌐 Web query:

Does the dynamic require pattern used for optional dependencies work consistently across Webpack, Vite, and ESBuild in ESM modules?

💡 Result:

The dynamic require pattern for optional dependencies behaves inconsistently across Webpack, Vite, and ESBuild when working with ESM modules. Here's a breakdown:

Vite

  • Does not support CommonJS dynamic require in ESM contexts due to its reliance on static ESM analysis for optimizations like dependency pre-bundling and tree-shaking[2][4].
  • Workarounds include:
    1. Static imports for known dependencies:
      import optionalDep from 'optional-dep';
    2. Dynamic import() for conditional loading:
      const loadOptional = async () => {
        try {
          return await import('optional-dep');
        } catch {
          return fallbackImplementation;
        }
      };
    3. Import maps for URL resolution[2].

ESBuild

  • Handles dynamic require only in CommonJS output formats (format: 'cjs')[8].
  • In ESM mode:
    • Converts require to __require helper that throws "Dynamic require is not supported"[6].
    • Requires manual intervention for Node.js built-ins:
      import { createRequire } from 'module';
      const require = createRequire(import.meta.url);
  • Recommended to use static imports or import() instead[8].

Webpack

  • Supports dynamic require through context-aware bundling:
    const modules = require.context('./modules', true);
  • Creates async chunks for dynamically required modules.
  • Maintains better CommonJS interoperability than Vite/ESBuild but:
    • Loses tree-shaking advantages
    • Increases bundle size
    • Behaves differently in ESM vs CJS modes

Key Consistency Issues

Feature Webpack Vite ESBuild
CJS require in ESM
Dynamic import()
Optional dependencies ⚠️ ⚠️
Tree-shaking

For modern ESM workflows, import() is the most consistent cross-bundler solution for optional dependencies[7]. The traditional require pattern works reliably only in CommonJS contexts or Webpack-specific configurations.

Citations:


Bundler Compatibility Caution: Dynamic require may fail in ESM contexts
The current implementation robustly handles React 18's new client API for Webpack using a try-catch with dynamic require. However, as verified by recent web research, using dynamic require for optional dependencies is inconsistent in ESM modules with bundlers like Vite and ESBuild. These bundlers generally expect a dynamic import() pattern rather than a CommonJS require.

  • Impact:

    • Webpack: Works as intended.
    • Vite/ESBuild: Likely to throw errors or fail during static dependency analysis.
  • Recommendation:
    If your project might run on Vite or ESBuild, consider refactoring to use dynamic import() (or leveraging createRequire for Node built-ins in ESBuild’s ESM mode) to ensure cross-bundler compatibility.

@alexeyr-ci2 alexeyr-ci2 marked this pull request as draft April 11, 2025 18:45
@alexeyr-ci alexeyr-ci force-pushed the alexeyr/esm branch 4 times, most recently from 0f05bd8 to 3a44eee Compare April 12, 2025 15:31
// TODO: once we require React 18, we can remove this and inline everything guarded by it.
export const supportsRootApi = reactMajorVersion >= 18;

export const supportsHydrate = supportsRootApi || 'hydrate' in ReactDOM;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check shouldn't be necessary as hydrate is in 16.0: https://legacy.reactjs.org/blog/2017/09/26/react-v16.0.html If that's the earliest version we support, this should always be true.

@alexeyr-ci alexeyr-ci force-pushed the alexeyr/esm branch 2 times, most recently from 306fbca to 2c07f43 Compare April 13, 2025 14:45
@alexeyr-ci2 alexeyr-ci2 marked this pull request as ready for review April 14, 2025 06:24
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
node_package/src/reactApis.cts (1)

6-12: Improved React version detection and API support.

The code elegantly determines the React version and whether it supports the modern Root API. This approach ensures compatibility across different React versions.

Note: The check for supportsHydrate may be unnecessary since hydrate has been available since React 16.0, which appears to be the minimum supported version.

🧹 Nitpick comments (1)
node_package/src/ClientSideRenderer.ts (1)

156-157:

❓ Verification inconclusive

Using abstracted unmount function with deprecated API notice

The code now uses the imported unmountComponentAtNode function instead of calling ReactDOM directly, with an appropriate ESLint comment acknowledging its deprecated status.

Consider planning for the eventual replacement of this deprecated API in a future update, perhaps using the Root API exclusively since you already have conditional support for it.


🏁 Script executed:

#!/bin/bash
# Check React documentation for unmountComponentAtNode deprecation and replacement
echo "Checking for usage of deprecated unmountComponentAtNode across the codebase"
rg "unmountComponentAtNode" --type ts --type tsx

Length of output: 237


Below is a revised review comment. I've also generated an additional shell script to double-check that we aren’t missing any other occurrences of the deprecated API due to file type issues. Please run the following script manually:

#!/bin/bash
# Explicitly search for "unmountComponentAtNode" in all .ts and .tsx files
find . -type f \( -name "*.ts" -o -name "*.tsx" \) -exec grep -H "unmountComponentAtNode" {} \;

Action Required: Plan for Deprecated API Replacement

The updated code in node_package/src/ClientSideRenderer.ts (lines 156–157) now uses the abstracted unmountComponentAtNode function with the proper ESLint disable comment acknowledging its deprecated status. This change abstracts the ReactDOM call and prepares for a future migration to the React Root API.

  • Current Implementation:
    // eslint-disable-next-line @typescript-eslint/no-deprecated
    unmountComponentAtNode(domNode);
  • Recommendation: Verify that this deprecated API isn’t being used elsewhere in the codebase (see the manual check above) and consider planning a complete switch to the Root API once it’s universally supported.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18fd77e and 69a59fa.

⛔ Files ignored due to path filters (2)
  • spec/dummy/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (50)
  • .github/workflows/lint-js-and-ruby.yml (1 hunks)
  • .github/workflows/main.yml (1 hunks)
  • CHANGELOG.md (1 hunks)
  • docs/release-notes/15.0.0.md (1 hunks)
  • eslint.config.ts (2 hunks)
  • jest.config.js (1 hunks)
  • node_package/src/Authenticity.ts (1 hunks)
  • node_package/src/CallbackRegistry.ts (1 hunks)
  • node_package/src/ClientSideRenderer.ts (3 hunks)
  • node_package/src/ComponentRegistry.ts (1 hunks)
  • node_package/src/RSCClientRoot.ts (1 hunks)
  • node_package/src/ReactDOMServer.cts (1 hunks)
  • node_package/src/ReactOnRails.client.ts (3 hunks)
  • node_package/src/ReactOnRails.full.ts (2 hunks)
  • node_package/src/ReactOnRails.node.ts (1 hunks)
  • node_package/src/ReactOnRailsRSC.ts (2 hunks)
  • node_package/src/StoreRegistry.ts (1 hunks)
  • node_package/src/buildConsoleReplay.ts (1 hunks)
  • node_package/src/clientStartup.ts (1 hunks)
  • node_package/src/context.ts (1 hunks)
  • node_package/src/createReactOutput.ts (1 hunks)
  • node_package/src/handleError.ts (2 hunks)
  • node_package/src/isRenderFunction.ts (1 hunks)
  • node_package/src/isServerRenderResult.ts (1 hunks)
  • node_package/src/pageLifecycle.ts (1 hunks)
  • node_package/src/reactApis.cts (1 hunks)
  • node_package/src/reactApis.ts (0 hunks)
  • node_package/src/reactHydrateOrRender.ts (1 hunks)
  • node_package/src/registerServerComponent/client.ts (1 hunks)
  • node_package/src/registerServerComponent/server.ts (1 hunks)
  • node_package/src/serverRenderReactComponent.ts (3 hunks)
  • node_package/src/serverRenderUtils.ts (1 hunks)
  • node_package/src/streamServerRenderedReactComponent.ts (3 hunks)
  • node_package/src/transformRSCStreamAndReplayConsoleLogs.ts (1 hunks)
  • node_package/tests/Authenticity.test.js (1 hunks)
  • node_package/tests/ComponentRegistry.test.js (2 hunks)
  • node_package/tests/RSCClientRoot.test.jsx (2 hunks)
  • node_package/tests/ReactOnRails.test.jsx (1 hunks)
  • node_package/tests/StoreRegistry.test.js (1 hunks)
  • node_package/tests/buildConsoleReplay.test.js (1 hunks)
  • node_package/tests/renderFunction.test.jsx (1 hunks)
  • node_package/tests/scriptSanitizedVal.test.js (1 hunks)
  • node_package/tests/serverRenderReactComponent.test.ts (1 hunks)
  • node_package/tests/streamServerRenderedReactComponent.test.jsx (1 hunks)
  • node_package/tests/utils.test.js (1 hunks)
  • package.json (2 hunks)
  • spec/dummy/package.json (2 hunks)
  • spec/dummy/tests/react-on-rails.import.test.js (1 hunks)
  • spec/dummy/tests/react-on-rails.require.test.js (1 hunks)
  • tsconfig.json (1 hunks)
💤 Files with no reviewable changes (1)
  • node_package/src/reactApis.ts
✅ Files skipped from review due to trivial changes (1)
  • node_package/src/ReactDOMServer.cts
🚧 Files skipped from review as they are similar to previous changes (43)
  • node_package/src/Authenticity.ts
  • .github/workflows/main.yml
  • node_package/src/serverRenderUtils.ts
  • node_package/src/isServerRenderResult.ts
  • node_package/src/transformRSCStreamAndReplayConsoleLogs.ts
  • spec/dummy/tests/react-on-rails.require.test.js
  • node_package/tests/StoreRegistry.test.js
  • node_package/tests/ReactOnRails.test.jsx
  • node_package/tests/buildConsoleReplay.test.js
  • node_package/src/registerServerComponent/server.ts
  • node_package/src/pageLifecycle.ts
  • jest.config.js
  • node_package/src/isRenderFunction.ts
  • .github/workflows/lint-js-and-ruby.yml
  • node_package/src/buildConsoleReplay.ts
  • spec/dummy/tests/react-on-rails.import.test.js
  • node_package/src/handleError.ts
  • node_package/tests/renderFunction.test.jsx
  • node_package/tests/scriptSanitizedVal.test.js
  • node_package/src/context.ts
  • spec/dummy/package.json
  • package.json
  • node_package/src/registerServerComponent/client.ts
  • eslint.config.ts
  • tsconfig.json
  • node_package/src/StoreRegistry.ts
  • node_package/src/ComponentRegistry.ts
  • node_package/tests/utils.test.js
  • node_package/src/createReactOutput.ts
  • node_package/tests/serverRenderReactComponent.test.ts
  • node_package/tests/Authenticity.test.js
  • node_package/tests/ComponentRegistry.test.js
  • node_package/tests/RSCClientRoot.test.jsx
  • node_package/src/RSCClientRoot.ts
  • node_package/src/ReactOnRails.full.ts
  • node_package/src/CallbackRegistry.ts
  • node_package/src/clientStartup.ts
  • node_package/tests/streamServerRenderedReactComponent.test.jsx
  • node_package/src/ReactOnRails.node.ts
  • node_package/src/serverRenderReactComponent.ts
  • node_package/src/streamServerRenderedReactComponent.ts
  • node_package/src/ReactOnRails.client.ts
  • node_package/src/ReactOnRailsRSC.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
node_package/src/ClientSideRenderer.ts (1)
spec/dummy/client/app/startup/ManualRenderApp.jsx (1)
  • domNode (12-12)
🪛 LanguageTool
docs/release-notes/15.0.0.md

[formatting] ~73-~73: Insert a comma after ‘cases’: “In most cases,”?
Context: ...shed as ES Modules instead of CommonJS. In most cases it shouldn't affect your code, as bundl...

(IN_MOST_CASES_COMMA)


[style] ~76-~76: Consider replacing this word to strengthen your wording.
Context: ... should work as well. - If you run into TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Typ...

(AND_THAT)


[uncategorized] ~78-~78: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...everything else fails, please contact us and we'll help you upgrade or release a dua...

(COMMA_COMPOUND_SENTENCE)

🔇 Additional comments (11)
docs/release-notes/15.0.0.md (1)

71-78: Good addition of ESM transition guidance.

The clear explanation of the package's transition to ESM and the provided alternatives for users who still need to use require() will help minimize migration issues. This section appropriately addresses the main concerns users might have when upgrading.

A few minor improvements to consider:

  • In line 73, consider adding a comma after "In most cases"
  • In line 78, consider adding a comma before "and" for better readability
🧰 Tools
🪛 LanguageTool

[formatting] ~73-~73: Insert a comma after ‘cases’: “In most cases,”?
Context: ...shed as ES Modules instead of CommonJS. In most cases it shouldn't affect your code, as bundl...

(IN_MOST_CASES_COMMA)


[style] ~76-~76: Consider replacing this word to strengthen your wording.
Context: ... should work as well. - If you run into TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Typ...

(AND_THAT)


[uncategorized] ~78-~78: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...everything else fails, please contact us and we'll help you upgrade or release a dua...

(COMMA_COMPOUND_SENTENCE)

CHANGELOG.md (1)

38-38: Properly documented breaking change with reference to detailed docs.

The changelog entry correctly identifies the ESM transition as a breaking change and helpfully links to the detailed release notes for more information.

node_package/src/reactHydrateOrRender.ts (1)

1-11: Clean refactoring of render logic.

The function has been refactored to delegate rendering to specialized functions imported from reactApis.cts, making the code more modular and maintainable. The signature has been updated to include a hydrate parameter that determines which rendering method to use.

node_package/src/reactApis.cts (4)

13-27: Well-handled conditional import for react-dom/client.

The code properly handles importing the client API for React 18+ while providing a graceful fallback for older versions. The defensive try/catch pattern prevents runtime failures if the optional dependency isn't available.

The TODO comments clearly indicate future cleanup opportunities once React 18 becomes the minimum supported version.


34-37: Clean implementation of hydration API selection.

The reactHydrate function intelligently selects between the modern hydrateRoot API and legacy hydrate method based on the detected React version.


38-47: Well-structured rendering implementation with version support.

The reactRender function handles both modern and legacy rendering approaches with appropriate type safety. The conditional logic ensures compatibility across React versions while preparing for future simplification.


49-52: Appropriate unmount function handling.

The exported unmountComponentAtNode function correctly handles the differences between React 18's root API (where manual unmounting isn't required) and older versions.

node_package/src/ClientSideRenderer.ts (4)

4-4: File extension added for imports

The import statement has been updated to include file extensions, which is necessary for ESM compatibility. This change is appropriate for the transition to ECMAScript Modules.


6-13: Updated import statements with file extensions

All import statements have been properly updated with .ts or .cts file extensions, which is required for ESM compatibility. The introduction of imports from reactApis.cts suggests a refactoring to abstract React API access.


9-9: New abstraction for React APIs

The import of supportsHydrate, supportsRootApi, and unmountComponentAtNode from reactApis.cts encapsulates React version-specific logic, which is a good design choice for maintainability as React APIs evolve.


104-104: Refactored hydration logic

The condition for determining whether to hydrate now uses the imported supportsHydrate variable instead of checking ReactDOM directly. This change centralizes React API compatibility logic, making it easier to maintain as React evolves.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
jest.config.js (1)

5-10: Consider adding documentation comments about the TypeScript configuration

The rewriteRelativeImportExtensions: false setting is critical for ESM compatibility but its purpose might not be immediately clear to other developers. Consider adding a brief comment explaining why this configuration is needed.

 export default {
   ...createJsWithTsPreset({
     tsconfig: {
+      // Prevent TypeScript from automatically adding extensions to imports
+      // which is required for ESM compatibility
       rewriteRelativeImportExtensions: false,
     },
   }),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ebbe7e and 6b8274d.

📒 Files selected for processing (1)
  • jest.config.js (1 hunks)
🔇 Additional comments (2)
jest.config.js (2)

1-10: Appropriate migration to ESM syntax with ts-jest configuration

The conversion from CommonJS to ESM syntax is correctly implemented with the import statement and export default. Using createJsWithTsPreset with rewriteRelativeImportExtensions: false is a good approach for handling file extensions in the ESM context, as it prevents TypeScript from automatically rewriting relative imports, which is necessary when explicit file extensions are required in ESM.


5-10:

❓ Verification inconclusive

Verify warning about deprecated configuration style

There was a previous comment about Jest warnings regarding deprecated configuration style. Has this been fully resolved? The current approach using createJsWithTsPreset is valid, but it's worth confirming that it doesn't trigger deprecation warnings.


🏁 Script executed:

#!/bin/bash
# Run jest with the --no-cache flag to ensure we get fresh output including any warnings
NODE_OPTIONS=--no-warnings yarn jest --no-cache --config=jest.config.js --listTests

Length of output: 444


Verify no deprecated Jest configuration warnings

I wasn’t able to run Jest in this environment (Node binary not found). Please run locally and confirm there are no deprecation warnings:

  • yarn jest --no-cache --config=jest.config.js --listTests

@alexeyr-ci2 alexeyr-ci2 merged commit d09352e into master Apr 18, 2025
11 checks passed
@alexeyr-ci2 alexeyr-ci2 deleted the alexeyr/esm branch April 18, 2025 09:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants