Hot update failed
I overheard student A saying that it has been so long since the development of project B that the page content has not been re-rendered after modifying the code during the development, and the page has not even been refreshed 😨. Therefore, I have manually refreshed the browser at normal times, and I was surprised that I had to solve this problem quickly.
Hot Update Introduction
Different from the process restart of PM2, Nodemon, Forever after the nodeJS project modifys the code, the hot update process after the front-end code modification is still quite long. Mainly for webpack-dev-server to notify the browser through websocket, see figure below
Images from segmentfault.com/a/119000002…
There is another way to use webpack-dev-server for hot updates, and that is to write a dev-server that integrates webpack-hot-middleware. The latter notifies the browser using SSE server push events, because dev-server is used to one-way notify the browser, no need for a two-way Websocket
The project in question this time is an old NextJS project, which adopts the SSE method of the latter. The core of SSE server side is also briefly described here
- The main thing is that the content-type setting should be text/event-stream
- X-accel-buffering is usually not required, which is mainly used when there is an nginx agent in the middle. It allows nginx to send data directly, without storing it
Var headers = {' access-Control-allow-origin ': '*', 'content-type ': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-cache, no-transform', 'X-Accel-Buffering': 'no', }; res.writeHead(200, headers); res.write('\n');Copy the code
The following figure is displayed in devtool
Problem of repetition
After compiling, the browser also seems to receive the message. The last log stopped at [Fast Refresh] done, so there is no further context. The page content is not updated, and the browser is not refreshed
Problem orientation
1. Is old code returned after modification?
- Analysis: usually, after modifying code HotModuleReplacementPlugin generates a XXX. Hot – update. Js, if it is something wrong with the return of the js have problem can explain hot update failure
- Conclusion: ❌ took a close look at the.hot-update.js content and found that it actually contains the latest changes, so rule out this possibility
2. Did the process of applying the new code fail?
This is where you need to debug the SSE part of the NextJS client from the starting point
- What’s wrong with the SSE client implementation file, which is usually the standard API call
// packages/next/client/dev/error-overlay/eventsource.js
source = new window.EventSource(options.path)
source.onopen = handleOnline
source.onerror = handleDisconnect
source.onmessage = handleMessage
Copy the code
- Received server messages add key listener function, found the key ⚠️ [Fast Refresh] done log, continue to dig onRefresh function implementation
packages/next/client/dev/error-overlay/hot-dev-client.js
function onFastRefresh(hasUpdates) {
DevOverlay.onBuildOk()
if (hasUpdates) {
DevOverlay.onRefresh()
}
console.log('[Fast Refresh] done')
}
Copy the code
- It looks like NextJS implements a simple event, and the onRefresh function publishes TYPE_REFFRESH events
// packages/react-dev-overlay/src/client.ts
function onRefresh() {
Bus.emit({ type: Bus.TYPE_REFFRESH })
}
Copy the code
- The ReactDevOverlay component at the top of the App subscribes to the TYPE_REFFRESH event, and then the state changes, triggering a rerender
// packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx const ReactDevOverlay: React.FunctionComponent = function ReactDevOverlay({ children, }) { const [state, dispatch] = React.useReducer< React.Reducer<OverlayState, Bus.BusEvent> >(reducer, { nextId: 1, buildError: null, errors: [] }) React.useEffect(() => { Bus.on(dispatch) return function () { Bus.off(dispatch) } }, [dispatch]) const isMounted = hasBuildError || hasRuntimeErrors return ( <React.Fragment> <ErrorBoundary onError={onComponentError}> {children ?? null} </ErrorBoundary> {isMounted ? ( <ShadowPortal> <CssReset /> <Base /> <ComponentStyles /> {hasBuildError ? ( <BuildError message={state.buildError! } /> ) : hasRuntimeErrors ? ( <Errors errors={state.errors} /> ) : undefined} </ShadowPortal> ) : undefined} </React.Fragment> ) }Copy the code
- Conclusion: ❌ makes a breakpoint to step 4 and finds that it works fine. Since the hot update of xxx.hot-update.js is up to date and the top-level component triggers rerendering when the client receives the message, what is the problem?
3. Is there a problem with nextJS internal components?
- Analysis: This possibility is mainly due to the fact that NextJS wraps too many parent components on the user’s component, which can cause hot update failure if a parent component fails
// packages/next/next-server/server/render.tsx
const AppContainer = ({ children }: any) => (
<RouterContext.Provider value={router}>
<AmpStateContext.Provider value={ampState}>
<HeadManagerContext.Provider
value={{
updateHead: (state) => {
head = state
},
updateScripts: (scripts) => {
scriptLoader = scripts
},
scripts: {},
mountedInstances: new Set(),
}}
>
<LoadableContext.Provider
value={(moduleName) => reactLoadableModules.push(moduleName)}
>
{children}
</LoadableContext.Provider>
</HeadManagerContext.Provider>
</AmpStateContext.Provider>
</RouterContext.Provider>
)
Copy the code
🐛 debug is a more important point is segmented inspection, just like the network has a problem, professional maintenance personnel will always be segmented to check, until the investigation to the recently unconnected line
Here we move the hot update listening code inside NextJS to our own component, test our own wiring, and then perform a forced render operation after the TYPE_REFFRESH event to see if the hot update takes effect
componentDidMount() {
if (process.env.NODE_ENV === "development") {
const Bus = require("@next/react-dev-overlay/lib/internal/bus")
Bus.on((event: Record<string, string>) => {
if (event.type === Bus.TYPE_REFFRESH) {
this.forceUpdate()
}
})
}
}
Copy the code
❌ the answer is still not hot update, in fact, here need 🤔 to think about the nature of hot update?
- Why didn’t the parent component forceUpdate cause the page to be rerendered when the code for the child component of this component was updated
When the client receives the xxx.hot-update.js code, the installedModules object cached in memory for all modules will be updated as follows: @components/App However, if it does not require a callback in a hot update to fetch the newly assigned value, it will still reference the old value if there is a parent component
/ / a simple example of the realization of the hot replacement function __enableHotModuleReplacement () {if (module. Hot) {if (module.hot._acceptedDependencies['@components/App']) { console.warn('[${PKG_NAME}]: Hot updates have already been registered') } else { module.hot.accept('@components/App', () => { const _App = require('@components/App').default if (_App) { ReactDOM.render(<_App />, document.getElementById('root')) } else { location.reload() } }) console.log('[${PKG_NAME}]: Hot Update Registration Successful') } } } __enableHotModuleReplacement()Copy the code
Through the analysis of the above example, our patch code needs the following changes to be updated successfully
- Rerequire in the update subscription callback to get the latest reference value
- Update render the latest child Component with setState
componentDidMount() { if (process.env.NODE_ENV === "development") { const Bus = require("@next/react-dev-overlay/lib/internal/bus") Bus.on((event: Record<string, string>) => { if (event.type === Bus.TYPE_REFFRESH) { const NewComponent = require('views').default this.setState({ Component: NewComponent }) } }) } } render() { const { Component } = this.state return <Component {... this.props}/> }Copy the code
So far we have patched our own code to fix the hot update capability, but we also need to test whether the grandson of this component works properly.
❌ The answer is no, the sun tzu component did not work! So there’s nothing wrong with the nextJS internal component. Who broke the installedModules cache?
4. React-refresh webpack-plugin problem?
- When executing from the NewComponent starting point back down, its import component references should be the latest. Who touched the installedModules cache? React-refresh, on the other hand, adds some registration code before and after each file at build time for minimal local updates, which may be the cause if the logic is not well thought out
- Conclusions: ❌ upgraded the react-refresh-webpack-plugin to the latest version of the minor version, which was not found to be resolved, and the conjecture of a minor version of the bug is most likely not true
So we continue to improve the above code patch, manually clear all module cache to solve the remaining problems
componentDidMount() { if (process.env.NODE_ENV === "development") { const Bus = require("@next/react-dev-overlay/lib/internal/bus") Bus.on((event: Record<string, string>) => { if (event.type === Bus.TYPE_REFFRESH) { Object.keys(require.cache).forEach(key => { delete require.cache[key] }) const NewComponent = require('views').default this.setState({ Component: NewComponent }) } }) } } render() { const { Component } = this.state return <Component {... this.props}/> }Copy the code
Yes, although we haven’t found the real problem yet, we have fixed it with a rough patch based on the phenomena reflected in the problem.
5. Check the next. Config. Js
At step 4, I was ready to close the case with the existing patch when I discovered the real cause of the hot update failure due to another minor issue
There is an externals configuration in next.config.js. Those who have known about it should know that if externals is configured, the CDN JS file with the externals configuration package needs to be manually imported into the HTML of the template
// next.config.js if (! isServer) { const e = { react: "React", "react-dom": "ReactDOM" } config.externals.unshift(e) }Copy the code
React-dom is a DLL entry, and the autoDLL plugin inserts it into the template HTML file automatically
⚠️ react/react-dom Let’s remove externals from the react/react-dom file.
✅ Found that after removing the patch, the hot update can run normally, the problem is resolved ~
summary
Although the old project has some problems, we should try our best to basically solve the problems through some rough patches. Some black boxes cannot spend too much time to study. The second is to find similarities and differences. For example, the nextJS project, the biggest difference is the configuration file next-config. js and package version, these key points need to be checked.
Read more:Github.com/xiaoxiaojx/…Please subscribe to 🌟 Star 🌟