Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rspack-2-node-guard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@callstack/repack": patch
---

Raise a clear error when running the Rspack-based commands with Rspack 2 on Node.js older than 20.19 (Rspack 2 requires Node `^20.19.0 || >=22.12.0`), instead of failing with `ERR_REQUIRE_ESM`. Rspack commands are now loaded lazily so the version check runs before `@rspack/core` is touched.
31 changes: 31 additions & 0 deletions packages/repack/src/commands/rspack/ensureNodeCompat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import semver from 'semver';
import { CLIError, getRspackVersion } from '../../helpers/index.js';

// Rspack 2 is published as a pure ESM package and declares
// engines.node: "^20.19.0 || >=22.12.0" - the versions where
// loading ESM through require() is supported.
const RSPACK_2_NODE_RANGE = '^20.19.0 || >=22.12.0';

/**
* Fails fast with an actionable error when the installed Rspack major
* requires a newer Node.js version than the current one.
*
* Without this guard, loading `@rspack/core` v2 on an unsupported Node
* version crashes with an obscure `ERR_REQUIRE_ESM` error.
*/
export function ensureNodeSupportsRspack() {
Comment thread
dannyhw marked this conversation as resolved.
const rspackVersion = getRspackVersion();

// not resolvable - let the import of '@rspack/core' surface its own error
if (!rspackVersion) return;

if (semver.major(semver.coerce(rspackVersion) ?? '0.0.0') < 2) return;

if (semver.satisfies(process.versions.node, RSPACK_2_NODE_RANGE)) return;

throw new CLIError(
`Rspack ${rspackVersion} requires Node.js ^20.19.0 || >=22.12.0 - ` +
`found ${process.versions.node}. ` +
'Upgrade Node.js or use @rspack/core@1 instead.'
);
}
31 changes: 25 additions & 6 deletions packages/repack/src/commands/rspack/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,50 @@
import { bundleCommandOptions, startCommandOptions } from '../options.js';
import { bundle } from './bundle.js';
import { start } from './start.js';

import type { bundle } from './bundle.js';
import type { start } from './start.js';

// The command modules load '@rspack/core' which is ESM-only in Rspack 2,
// so they are imported lazily: first the Node version guard runs and turns
// an unsupported setup into an actionable error instead of ERR_REQUIRE_ESM,
// and loading the project config stays free of bundler imports.
const lazyBundle = async (...args: Parameters<typeof bundle>) => {
const { ensureNodeSupportsRspack } = await import('./ensureNodeCompat.js');
ensureNodeSupportsRspack();
const { bundle } = await import('./bundle.js');
return bundle(...args);
};

const lazyStart = async (...args: Parameters<typeof start>) => {
const { ensureNodeSupportsRspack } = await import('./ensureNodeCompat.js');
ensureNodeSupportsRspack();
const { start } = await import('./start.js');
return start(...args);
};

const commands = [
{
name: 'bundle',
description: 'Build the bundle for the provided JavaScript entry file.',
options: bundleCommandOptions,
func: bundle,
func: lazyBundle,
},
{
name: 'webpack-bundle',
description: 'Build the bundle for the provided JavaScript entry file.',
options: bundleCommandOptions,
func: bundle,
func: lazyBundle,
},
{
name: 'start',
description: 'Start the React Native development server.',
options: startCommandOptions,
func: start,
func: lazyStart,
},
{
name: 'webpack-start',
description: 'Start the React Native development server.',
options: startCommandOptions,
func: start,
func: lazyStart,
},
] as const;

Expand Down
8 changes: 4 additions & 4 deletions packages/repack/src/commands/rspack/profile/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { rspackVersion } from '@rspack/core';
import { getRspackVersion } from '../../../helpers/index.js';

function getRspackVersion() {
function getInstalledRspackVersion() {
// get rid of `-beta`, `-rc` etc.
const version = rspackVersion.split('-')[0];
const version = (getRspackVersion() ?? '0.0.0').split('-')[0];
const [major, minor, patch] = version.split('.').map(Number);
return { major, minor, patch };
}

async function getProfilingHandler() {
const { major, minor } = getRspackVersion();
const { major, minor } = getInstalledRspackVersion();
if (major > 1 || (major === 1 && minor >= 4)) {
return await import('./profile-1.4.js');
}
Expand Down
Loading