Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
09a8b53
udpate Rspack snapshot
SyMind Sep 5, 2025
527e32b
test/development/acceptance/ReactRefreshLogBox.test.ts
SyMind Sep 5, 2025
72e808b
test/development/acceptance/ReactRefreshLogBox.test.ts
SyMind Sep 5, 2025
76a6a56
update @rspack/plugin-react-refresh
SyMind Sep 5, 2025
c2d4658
test/development/acceptance-app/ReactRefreshLogBox.test.ts
SyMind Sep 5, 2025
2174765
test/development/acceptance/ReactRefreshLogBox.test.ts
SyMind Sep 5, 2025
f4c7af7
test/development/acceptance/ReactRefreshLogBox-builtins.test.ts
SyMind Sep 5, 2025
d3ed1c7
server-component-next-dynamic-ssr-false.test.ts
SyMind Sep 5, 2025
bc56b91
font-loader-in-document-error.test.ts
SyMind Sep 5, 2025
23dace3
prettier
SyMind Sep 8, 2025
6e045cc
owner-stack.test.ts
SyMind Sep 8, 2025
bad8397
fix: test/development/basic/gssp-ssr-change-reloading/test/index.test.ts
SyMind Sep 8, 2025
4474343
update snapshot
SyMind Sep 8, 2025
da6a3f8
fix: ai suggestions
SyMind Sep 9, 2025
11ece96
fix: RspackReactRefresh
SyMind Sep 9, 2025
8a5d4c3
fix typo
SyMind Sep 9, 2025
2acb7ff
fix: remove stack in next/fount error
SyMind Sep 9, 2025
966cabf
fix: update snapshot
SyMind Sep 9, 2025
94967e8
fix: lint error
SyMind Sep 9, 2025
d6e377f
update snapshot test/development/acceptance/ReactRefreshLogBox.test.ts
SyMind Sep 9, 2025
b0304d2
fix: pnpm lock
SyMind Sep 9, 2025
962bb3b
Merge branch 'canary' into update-snapshot
SyMind Sep 9, 2025
fd8cc1a
Merge branch 'canary' into update-snapshot
SyMind Sep 15, 2025
2833007
fix: lint
SyMind Sep 15, 2025
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
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@
"@opentelemetry/api": "1.4.1",
"@picocss/pico": "1.5.10",
"@rspack/core": "1.5.0",
"@rspack/plugin-react-refresh": "1.2.0",
"@slack/web-api": "7.9.1",
"@swc/cli": "0.1.55",
"@swc/core": "1.11.24",
Expand Down
4 changes: 1 addition & 3 deletions packages/next-rspack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
},
"types": "index.d.ts",
"dependencies": {
"@rspack/core": "1.5.0",
"@rspack/plugin-react-refresh": "1.2.0",
"react-refresh": "0.12.0"
"@rspack/core": "1.5.0"
}
}
14 changes: 6 additions & 8 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ import {
NEXT_PROJECT_ROOT,
NEXT_PROJECT_ROOT_DIST_CLIENT,
} from './next-dir-paths'
import { getRspackCore, getRspackReactRefresh } from '../shared/lib/get-rspack'
import { getRspackCore } from '../shared/lib/get-rspack'
import { RspackProfilingPlugin } from './webpack/plugins/rspack-profiling-plugin'
import getWebpackBundler from '../shared/lib/get-webpack-bundler'
import type { NextBuildContext } from './build-context'
Expand Down Expand Up @@ -178,7 +178,7 @@ const reactRefreshLoaderName =

function getReactRefreshLoader() {
return process.env.NEXT_RSPACK
? getRspackReactRefresh().loader
? 'builtin:react-refresh-loader'
: require.resolve(reactRefreshLoaderName)
}

Expand Down Expand Up @@ -698,7 +698,9 @@ export default async function getBaseWebpackConfig(
]

const reactRefreshEntry = isRspack
? getRspackReactRefresh().entry
? require.resolve(
`next/dist/compiled/@next/react-refresh-utils/dist/rspack-runtime`
)
: require.resolve(
`next/dist/compiled/@next/react-refresh-utils/dist/runtime`
)
Expand Down Expand Up @@ -1990,11 +1992,7 @@ export default async function getBaseWebpackConfig(
isClient &&
(isRspack
? // eslint-disable-next-line
new (getRspackReactRefresh() as any)({
injectLoader: false,
injectEntry: false,
overlay: false,
})
new (require('next/dist/compiled/@next/react-refresh-utils/dist/ReactRefreshRspackPlugin').default)()
: new ReactRefreshWebpackPlugin(webpack)),
// Makes sure `Buffer` and `process` are polyfilled in client and flight bundles (same behavior as webpack 4)
(isClient || isEdgeServer) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export default async function nextFontLoader(this: any) {
`${bold('Cannot')} be used within ${cyan('pages/_document.js')}.`
)
err.name = 'NextFontError'
if (process.env.NEXT_RSPACK) {
// Rspack uses miette for error formatting, which automatically includes stack
// traces in the error message. To avoid showing redundant stack information
// in the final error output, we clear the stack property.
err.stack = undefined
}
callback(err)
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ const nextInvalidImportErrorLoader: webpack.LoaderDefinitionFunction<InvalidImpo
const { message } = this.getOptions()
const error = new Error(message)
if (process.env.NEXT_RSPACK) {
/**
* Rspack uses miette for error formatting, which automatically includes stack
* traces in the error message. To avoid showing redundant stack information
* in the final error output, we clear the stack property.
*/
// Rspack uses miette for error formatting, which automatically includes stack
// traces in the error message. To avoid showing redundant stack information
// in the final error output, we clear the stack property.
error.stack = undefined
}
throw error
Expand Down
25 changes: 0 additions & 25 deletions packages/next/src/shared/lib/get-rspack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,6 @@ export function getRspackCore() {
}
}

export function getRspackReactRefresh() {
warnRspack()
try {
const paths = [require.resolve('next-rspack')]
// eslint-disable-next-line import/no-extraneous-dependencies
const plugin = require(
require.resolve('@rspack/plugin-react-refresh', { paths })
)
const entry = require.resolve(
'@rspack/plugin-react-refresh/react-refresh-entry',
{ paths }
)
plugin.entry = entry
return plugin
} catch (e) {
if (e instanceof Error && 'code' in e && e.code === 'MODULE_NOT_FOUND') {
throw new Error(
'@rspack/plugin-react-refresh is not available. Please make sure `next-rspack` is correctly installed.'
)
}

throw e
}
}

function warnRspack() {
if (process.env.__NEXT_TEST_MODE) {
return
Expand Down
24 changes: 24 additions & 0 deletions packages/react-refresh-utils/ReactRefreshRspackPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Compiler } from 'webpack'

const PLUGIN_NAME = 'ReactRefreshRspackPlugin'

class ReactRefreshRspackPlugin {
static loader = 'builtin:react-refresh-loader'

apply(compiler: Compiler) {
new compiler.webpack.ProvidePlugin({
$ReactRefreshRuntime$: require.resolve('./internal/RspackReactRefresh'),
}).apply(compiler)

compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
compilation.hooks.additionalTreeRuntimeRequirements.tap(
PLUGIN_NAME,
(_, runtimeRequirements) => {
runtimeRequirements.add(compiler.webpack.RuntimeGlobals.moduleCache)
}
)
})
}
}

export default ReactRefreshRspackPlugin
96 changes: 96 additions & 0 deletions packages/react-refresh-utils/internal/RspackReactRefresh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import RefreshHelpers from './helpers'

declare const __webpack_require__: {
c: Record<string | number, { exports: unknown | (() => Promise<unknown>) }>
}

// Extracts exports from a webpack module object.
function getModuleExports(moduleId: string) {
if (typeof moduleId === 'undefined') {
// `moduleId` is unavailable, which indicates that this module is not in the cache,
// which means we won't be able to capture any exports,
// and thus they cannot be refreshed safely.
// These are likely runtime or dynamically generated modules.
return {}
}

var maybeModule = __webpack_require__.c[moduleId]
if (typeof maybeModule === 'undefined') {
// `moduleId` is available but the module in cache is unavailable,
// which indicates the module is somehow corrupted (e.g. broken Webpack `module` globals).
// We will warn the user (as this is likely a mistake) and assume they cannot be refreshed.
console.warn(
'[React Refresh] Failed to get exports for module: ' + moduleId + '.'
)
return {}
}

var exportsOrPromise = maybeModule.exports
if (typeof Promise !== 'undefined' && exportsOrPromise instanceof Promise) {
return exportsOrPromise.then(function (exports) {
return exports
})
}
return exportsOrPromise
}

function executeRuntime(moduleExports, moduleId, webpackHot) {
RefreshHelpers.registerExportsForReactRefresh(moduleExports, moduleId)

if (webpackHot) {
var isHotUpdate = !!webpackHot.data
var prevSignature: unknown[] | null = webpackHot.data?.prevSignature ?? null

if (RefreshHelpers.isReactRefreshBoundary(moduleExports)) {
webpackHot.dispose(
// Save the previous exports signature on update so we can compare the boundary
// signatures. We avoid saving exports themselves since it causes memory leaks (https://github.com/vercel/next.js/pull/53797)
function hotDisposeCallback(data) {
data.prevSignature =
RefreshHelpers.getRefreshBoundarySignature(moduleExports)
}
)
webpackHot.accept()

// This field is set when the previous version of this module was a
// Refresh Boundary, letting us know we need to check for invalidation or
// enqueue an update.
if (prevSignature !== null) {
if (isHotUpdate) {
if (
RefreshHelpers.shouldInvalidateReactRefreshBoundary(
prevSignature,
RefreshHelpers.getRefreshBoundarySignature(moduleExports)
)
) {
webpackHot.invalidate()
} else {
RefreshHelpers.scheduleUpdate()
}
}
}
} else {
if (isHotUpdate && prevSignature !== null) {
webpackHot.invalidate()
}
}
}
}

// Port from https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/loader/utils/getRefreshModuleRuntime.js#L29
export function refresh(moduleId, webpackHot) {
const currentExports = getModuleExports(moduleId)
const fn = (exports) => {
executeRuntime(exports, moduleId, webpackHot)
}
if (typeof Promise !== 'undefined' && currentExports instanceof Promise) {
currentExports.then(fn)
} else {
fn(currentExports)
}
}

export {
register,
createSignatureFunctionForTransform,
} from 'react-refresh/runtime'
20 changes: 20 additions & 0 deletions packages/react-refresh-utils/rspack-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import RefreshRuntime from 'react-refresh/runtime'
import type { RefreshRuntimeGlobals } from './runtime'

declare const self: Window & RefreshRuntimeGlobals

if (typeof self !== 'undefined') {
var $RefreshInjected$ = '__reactRefreshInjected'

// Only inject the runtime if it hasn't been injected
if (!self[$RefreshInjected$]) {
RefreshRuntime.injectIntoGlobalHook(self)

// Empty implementation to avoid "ReferenceError: variable is not defined" in module which didn't pass builtin:react-refresh-loader
self.$RefreshSig$ = () => (type) => type
self.$RefreshReg$ = () => {}

// Mark the runtime as injected to prevent double-injection
self[$RefreshInjected$] = true
}
}
38 changes: 0 additions & 38 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions test/development/acceptance-app/ReactRefreshLogBox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,27 @@ describe('ReactRefreshLogBox app', () => {
],
}
`)
} else if (isRspack) {
await expect(browser).toDisplayRedbox(`
{
"description": "no",
"environmentLabel": null,
"label": "Runtime Error",
"source": "index.js (3:7) @ eval
> 3 | throw new Error('no')
| ^",
"stack": [
"eval index.js (3:7)",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"eval ./app/page.js",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
"<FIXME-next-dist-dir>",
],
}
`)
} else {
await expect(browser).toDisplayRedbox(`
[
Expand Down
Loading
Loading