next
Version:
The React Framework
413 lines (411 loc) • 16.8 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = HotReload;
var _interop_require_default = require("@swc/helpers/lib/_interop_require_default.js").default;
var _react = require("react");
var _appRouterContext = require("../../shared/lib/app-router-context");
var _client = require("next/dist/compiled/@next/react-dev-overlay/dist/client");
var _stripAnsi = _interop_require_default(require("next/dist/compiled/strip-ansi"));
var _formatWebpackMessages = _interop_require_default(require("../dev/error-overlay/format-webpack-messages"));
var _hooksClient = require("./hooks-client");
function HotReload({ assetPrefix }) {
const { tree } = (0, _react).useContext(_appRouterContext.GlobalLayoutRouterContext);
const router = (0, _hooksClient).useRouter();
const webSocketRef = (0, _react).useRef();
const sendMessage = (0, _react).useCallback((data)=>{
const socket = webSocketRef.current;
if (!socket || socket.readyState !== socket.OPEN) return;
return socket.send(data);
}, []);
(0, _react).useEffect(()=>{
(0, _client).register();
const onError = ()=>{
hadRuntimeError = true;
};
window.addEventListener('error', onError);
window.addEventListener('unhandledrejection', onError);
return ()=>{
(0, _client).unregister();
window.removeEventListener('error', onError);
window.removeEventListener('unhandledrejection', onError);
};
}, []);
(0, _react).useEffect(()=>{
if (webSocketRef.current) {
return;
}
const { hostname , port } = window.location;
const protocol = getSocketProtocol(assetPrefix || '');
const normalizedAssetPrefix = assetPrefix.replace(/^\/+/, '');
let url = `${protocol}://${hostname}:${port}${normalizedAssetPrefix ? `/${normalizedAssetPrefix}` : ''}`;
if (normalizedAssetPrefix.startsWith('http')) {
url = `${protocol}://${normalizedAssetPrefix.split('://')[1]}`;
}
webSocketRef.current = new window.WebSocket(`${url}/_next/webpack-hmr`);
}, [
assetPrefix
]);
(0, _react).useEffect(()=>{
// Taken from on-demand-entries-client.js
// TODO-APP: check 404 case
const interval = setInterval(()=>{
sendMessage(JSON.stringify({
event: 'ping',
// TODO-APP: fix case for dynamic parameters, this will be resolved wrong currently.
tree,
appDirRoute: true
}));
}, 2500);
return ()=>clearInterval(interval);
}, [
tree,
sendMessage
]);
(0, _react).useEffect(()=>{
const handler = (event)=>{
if (event.data.indexOf('action') === -1 && // TODO-APP: clean this up for consistency
event.data.indexOf('pong') === -1) {
return;
}
try {
processMessage(event, sendMessage, router);
} catch (ex) {
console.warn('Invalid HMR message: ' + event.data + '\n', ex);
}
};
if (webSocketRef.current) {
webSocketRef.current.addEventListener('message', handler);
}
return ()=>webSocketRef.current && webSocketRef.current.removeEventListener('message', handler);
}, [
sendMessage,
router
]);
// useEffect(() => {
// const interval = setInterval(function () {
// if (
// lastActivityRef.current &&
// Date.now() - lastActivityRef.current > TIMEOUT
// ) {
// handleDisconnect()
// }
// }, 2500)
// return () => clearInterval(interval)
// })
return null;
}
function getSocketProtocol(assetPrefix) {
let protocol = window.location.protocol;
try {
// assetPrefix is a url
protocol = new URL(assetPrefix).protocol;
} catch (_) {}
return protocol === 'http:' ? 'ws' : 'wss';
}
let mostRecentCompilationHash = null;
let __nextDevClientId = Math.round(Math.random() * 100 + Date.now());
let hadRuntimeError = false;
// let startLatency = undefined
function onFastRefresh(hasUpdates) {
(0, _client).onBuildOk();
if (hasUpdates) {
(0, _client).onRefresh();
}
// if (startLatency) {
// const endLatency = Date.now()
// const latency = endLatency - startLatency
// console.log(`[Fast Refresh] done in ${latency}ms`)
// sendMessage(
// JSON.stringify({
// event: 'client-hmr-latency',
// id: __nextDevClientId,
// startTime: startLatency,
// endTime: endLatency,
// })
// )
// // if (self.__NEXT_HMR_LATENCY_CB) {
// // self.__NEXT_HMR_LATENCY_CB(latency)
// // }
// }
}
// There is a newer version of the code available.
function handleAvailableHash(hash) {
// Update last known compilation hash.
mostRecentCompilationHash = hash;
}
// Is there a newer version of this code available?
function isUpdateAvailable() {
/* globals __webpack_hash__ */ // __webpack_hash__ is the hash of the current compilation.
// It's a global variable injected by Webpack.
// @ts-expect-error __webpack_hash__ exists
return mostRecentCompilationHash !== __webpack_hash__;
}
// Webpack disallows updates in other states.
function canApplyUpdates() {
// @ts-expect-error module.hot exists
return module.hot.status() === 'idle';
}
// function afterApplyUpdates(fn: any) {
// if (canApplyUpdates()) {
// fn()
// } else {
// function handler(status: any) {
// if (status === 'idle') {
// // @ts-expect-error module.hot exists
// module.hot.removeStatusHandler(handler)
// fn()
// }
// }
// // @ts-expect-error module.hot exists
// module.hot.addStatusHandler(handler)
// }
// }
// Attempt to update code on the fly, fall back to a hard reload.
function tryApplyUpdates(onHotUpdateSuccess, sendMessage) {
// @ts-expect-error module.hot exists
if (!module.hot) {
// HotModuleReplacementPlugin is not in Webpack configuration.
console.error('HotModuleReplacementPlugin is not in Webpack configuration.');
// window.location.reload();
return;
}
if (!isUpdateAvailable() || !canApplyUpdates()) {
(0, _client).onBuildOk();
return;
}
function handleApplyUpdates(err, updatedModules) {
if (err || hadRuntimeError || !updatedModules) {
if (err) {
console.warn('[Fast Refresh] performing full reload\n\n' + "Fast Refresh will perform a full reload when you edit a file that's imported by modules outside of the React rendering tree.\n" + 'You might have a file which exports a React component but also exports a value that is imported by a non-React component file.\n' + 'Consider migrating the non-React component export to a separate file and importing it into both files.\n\n' + 'It is also possible the parent component of the component you edited is a class component, which disables Fast Refresh.\n' + 'Fast Refresh requires at least one parent function component in your React tree.');
} else if (hadRuntimeError) {
console.warn('[Fast Refresh] performing full reload because your application had an unrecoverable error');
}
performFullReload(err, sendMessage);
return;
}
const hasUpdates = Boolean(updatedModules.length);
if (typeof onHotUpdateSuccess === 'function') {
// Maybe we want to do something.
onHotUpdateSuccess(hasUpdates);
}
if (isUpdateAvailable()) {
// While we were updating, there was a new update! Do it again.
tryApplyUpdates(hasUpdates ? _client.onBuildOk : onHotUpdateSuccess, sendMessage);
} else {
(0, _client).onBuildOk();
// if (process.env.__NEXT_TEST_MODE) {
// afterApplyUpdates(() => {
// if (self.__NEXT_HMR_CB) {
// self.__NEXT_HMR_CB()
// self.__NEXT_HMR_CB = null
// }
// })
// }
}
}
// https://webpack.js.org/api/hot-module-replacement/#check
// @ts-expect-error module.hot exists
module.hot.check(/* autoApply */ true).then((updatedModules)=>{
handleApplyUpdates(null, updatedModules);
}, (err)=>{
handleApplyUpdates(err, null);
});
}
function performFullReload(err, sendMessage) {
const stackTrace = err && (err.stack && err.stack.split('\n').slice(0, 5).join('\n') || err.message || err + '');
sendMessage(JSON.stringify({
event: 'client-full-reload',
stackTrace
}));
window.location.reload();
}
function processMessage(e, sendMessage, router) {
const obj = JSON.parse(e.data);
switch(obj.action){
case 'building':
{
// startLatency = Date.now()
console.log('[Fast Refresh] rebuilding');
break;
}
case 'built':
case 'sync':
{
if (obj.hash) {
handleAvailableHash(obj.hash);
}
const { errors , warnings } = obj;
const hasErrors = Boolean(errors && errors.length);
// Compilation with errors (e.g. syntax error or missing modules).
if (hasErrors) {
sendMessage(JSON.stringify({
event: 'client-error',
errorCount: errors.length,
clientId: __nextDevClientId
}));
// "Massage" webpack messages.
var formatted = (0, _formatWebpackMessages).default({
errors: errors,
warnings: []
});
// Only show the first error.
(0, _client).onBuildError(formatted.errors[0]);
// Also log them to the console.
for(let i = 0; i < formatted.errors.length; i++){
console.error((0, _stripAnsi).default(formatted.errors[i]));
}
// Do not attempt to reload now.
// We will reload on next success instead.
// if (process.env.__NEXT_TEST_MODE) {
// if (self.__NEXT_HMR_CB) {
// self.__NEXT_HMR_CB(formatted.errors[0])
// self.__NEXT_HMR_CB = null
// }
// }
return;
}
const hasWarnings = Boolean(warnings && warnings.length);
if (hasWarnings) {
sendMessage(JSON.stringify({
event: 'client-warning',
warningCount: warnings.length,
clientId: __nextDevClientId
}));
// Compilation with warnings (e.g. ESLint).
const isHotUpdate = obj.action !== 'sync';
// Print warnings to the console.
const formattedMessages = (0, _formatWebpackMessages).default({
warnings: warnings,
errors: []
});
for(let i = 0; i < formattedMessages.warnings.length; i++){
if (i === 5) {
console.warn('There were more warnings in other files.\n' + 'You can find a complete log in the terminal.');
break;
}
console.warn((0, _stripAnsi).default(formattedMessages.warnings[i]));
}
// Attempt to apply hot updates or reload.
if (isHotUpdate) {
tryApplyUpdates(function onSuccessfulHotUpdate(hasUpdates) {
// Only dismiss it when we're sure it's a hot update.
// Otherwise it would flicker right before the reload.
onFastRefresh(hasUpdates);
}, sendMessage);
}
return;
}
sendMessage(JSON.stringify({
event: 'client-success',
clientId: __nextDevClientId
}));
const isHotUpdate = obj.action !== 'sync' || (!window.__NEXT_DATA__ || window.__NEXT_DATA__.page !== '/_error') && isUpdateAvailable();
// Attempt to apply hot updates or reload.
if (isHotUpdate) {
tryApplyUpdates(function onSuccessfulHotUpdate(hasUpdates) {
// Only dismiss it when we're sure it's a hot update.
// Otherwise it would flicker right before the reload.
onFastRefresh(hasUpdates);
}, sendMessage);
}
return;
}
// TODO-APP: make server component change more granular
case 'serverComponentChanges':
{
sendMessage(JSON.stringify({
event: 'server-component-reload-page',
clientId: __nextDevClientId
}));
if (hadRuntimeError) {
return window.location.reload();
}
(0, _react).startTransition(()=>{
router.reload();
(0, _client).onRefresh();
});
return;
}
case 'reloadPage':
{
sendMessage(JSON.stringify({
event: 'client-reload-page',
clientId: __nextDevClientId
}));
return window.location.reload();
}
case 'removedPage':
{
// const [page] = obj.data
// if (page === window.next.router.pathname) {
// sendMessage(
// JSON.stringify({
// event: 'client-removed-page',
// clientId: window.__nextDevClientId,
// page,
// })
// )
// return window.location.reload()
// }
return;
}
case 'addedPage':
{
// const [page] = obj.data
// if (
// page === window.next.router.pathname &&
// typeof window.next.router.components[page] === 'undefined'
// ) {
// sendMessage(
// JSON.stringify({
// event: 'client-added-page',
// clientId: window.__nextDevClientId,
// page,
// })
// )
// return window.location.reload()
// }
return;
}
case 'pong':
{
const { invalid } = obj;
if (invalid) {
// Payload can be invalid even if the page does exist.
// So, we check if it can be created.
fetch(location.href, {
credentials: 'same-origin'
}).then((pageRes)=>{
if (pageRes.status === 200) {
// Page exists now, reload
location.reload();
} else {
// TODO-APP: fix this
// Page doesn't exist
// if (
// self.__NEXT_DATA__.page === Router.pathname &&
// Router.pathname !== '/_error'
// ) {
// // We are still on the page,
// // reload to show 404 error page
// location.reload()
// }
}
});
}
return;
}
default:
{
throw new Error('Unexpected action ' + obj.action);
}
}
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=hot-reloader.client.js.map
;