Skip to content
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

feat: update refresh utils for React Router 7 support #363

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
12 changes: 5 additions & 7 deletions packages/plugin-react/src/fast-refresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ if (import.meta.hot && !inWebWorker) {
window.$RefreshReg$ = prevRefreshReg;
window.$RefreshSig$ = prevRefreshSig;
}`
const sharedFooter = `
const sharedFooter = (id: string) => `
if (import.meta.hot && !inWebWorker) {
RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
RefreshRuntime.registerExportsForReactRefresh(__SOURCE__, currentExports);
RefreshRuntime.registerExportsForReactRefresh("${id}", currentExports);
ArnaudBarre marked this conversation as resolved.
Show resolved Hide resolved
import.meta.hot.accept((nextExports) => {
if (!nextExports) return;
const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(currentExports, nextExports);
const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate("${id}", currentExports, nextExports);
ArnaudBarre marked this conversation as resolved.
Show resolved Hide resolved
if (invalidateMessage) import.meta.hot.invalidate(invalidateMessage);
});
});
Expand All @@ -76,15 +76,13 @@ export function addRefreshWrapper(code: string, id: string): string {
functionHeader.replace('__SOURCE__', JSON.stringify(id)) +
code +
functionFooter +
sharedFooter.replace('__SOURCE__', JSON.stringify(id))
sharedFooter(id)
)
}

export function addClassComponentRefreshWrapper(
code: string,
id: string,
): string {
return (
sharedHeader + code + sharedFooter.replace('__SOURCE__', JSON.stringify(id))
)
return sharedHeader + code + sharedFooter(id)
}
40 changes: 31 additions & 9 deletions packages/plugin-react/src/refreshUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ function debounce(fn, delay) {
}

/* eslint-disable no-undef */
const enqueueUpdate = debounce(exports.performReactRefresh, 16)
const hooks = []
window.__registerBeforePerformReactRefresh = (cb) => {
hooks.push(cb)
}
const enqueueUpdate = debounce(async () => {
if (hooks.length) await Promise.all(hooks.map((cb) => cb()))
exports.performReactRefresh()
}, 16)

// Taken from https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/lib/runtime/RefreshUtils.js#L141
// This allows to resister components not detected by SWC like styled component
Expand All @@ -25,36 +32,51 @@ function registerExportsForReactRefresh(filename, moduleExports) {
}
}

function validateRefreshBoundaryAndEnqueueUpdate(prevExports, nextExports) {
if (!predicateOnExport(prevExports, (key) => key in nextExports)) {
function validateRefreshBoundaryAndEnqueueUpdate(id, prevExports, nextExports) {
const ignoredExports = window.__getReactRefreshIgnoredExports?.({ id }) ?? []
if (
predicateOnExport(
ignoredExports,
prevExports,
(key) => key in nextExports,
) !== true
) {
return 'Could not Fast Refresh (export removed)'
}
if (!predicateOnExport(nextExports, (key) => key in prevExports)) {
if (
predicateOnExport(
ignoredExports,
nextExports,
(key) => key in prevExports,
) !== true
) {
return 'Could not Fast Refresh (new export)'
}

let hasExports = false
const allExportsAreComponentsOrUnchanged = predicateOnExport(
ignoredExports,
nextExports,
(key, value) => {
hasExports = true
if (exports.isLikelyComponentType(value)) return true
return prevExports[key] === nextExports[key]
},
)
if (hasExports && allExportsAreComponentsOrUnchanged) {
if (hasExports && allExportsAreComponentsOrUnchanged === true) {
enqueueUpdate()
} else {
return 'Could not Fast Refresh. Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#consistent-components-exports'
return `Could not Fast Refresh ("${allExportsAreComponentsOrUnchanged}" export is incompatible). Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#consistent-components-exports`
}
}

function predicateOnExport(moduleExports, predicate) {
function predicateOnExport(ignoredExports, moduleExports, predicate) {
for (const key in moduleExports) {
if (key === '__esModule') continue
if (ignoredExports.includes(key)) continue
const desc = Object.getOwnPropertyDescriptor(moduleExports, key)
if (desc && desc.get) return false
if (!predicate(key, moduleExports[key])) return false
if (desc && desc.get) return key
if (!predicate(key, moduleExports[key])) return key
}
return true
}
Expand Down
4 changes: 2 additions & 2 deletions playground/react/__tests__/react.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ if (!isBuild) {
code.replace('An Object', 'Updated'),
),
[
'[vite] invalidate /hmr/no-exported-comp.jsx: Could not Fast Refresh. Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#consistent-components-exports',
'[vite] invalidate /hmr/no-exported-comp.jsx: Could not Fast Refresh ("Foo" export is incompatible). Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#consistent-components-exports',
'[vite] hot updated: /hmr/no-exported-comp.jsx',
'[vite] hot updated: /hmr/parent.jsx',
'Parent rendered',
Expand All @@ -103,7 +103,7 @@ if (!isBuild) {
code.replace('context provider', 'context provider updated'),
),
[
'[vite] invalidate /context/CountProvider.jsx: Could not Fast Refresh. Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#consistent-components-exports',
'[vite] invalidate /context/CountProvider.jsx: Could not Fast Refresh ("CountContext" export is incompatible). Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#consistent-components-exports',
'[vite] hot updated: /context/CountProvider.jsx',
'[vite] hot updated: /App.jsx',
'[vite] hot updated: /context/ContextButton.jsx',
Expand Down
Loading