feat: more clean hot hookz

This commit is contained in:
Ivan Shmidt
2020-06-15 22:36:36 +02:00
parent 9fe15e6726
commit 417a4d538e
3 changed files with 66 additions and 25 deletions
+57 -16
View File
@@ -13,11 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function useHotEffect(
_module: NodeModule,
effectFactory: () => () => void,
) {
const cancelEffect = effectFactory();
/**
* This function allows devs to cleanup
* ongoing effects when module gets hot-reloaded
* Useful for cleaning intervals, timers, requests etc
* @example
* ```ts
* const intervalId = setInterval(doStuff, 1000);
* useHotCleanup(module, () => clearInterval(intervalId));
* ```
* @param _module Reference to the current module where you invoke the fn
* @param cancelEffect Fn that cleans up the ongoing effects
*/
export function useHotCleanup(_module: NodeModule, cancelEffect: () => void) {
if (_module.hot) {
_module.hot.addDisposeHandler(() => {
cancelEffect();
@@ -25,27 +34,59 @@ export function useHotEffect(
}
}
/**
* This function allows devs to preserve
* some value between hot-reloads.
* Useful for stateful parts of the backend
* @example
* ```ts
* const db = useHotMemoize(module, () => createDB(dbParams));
* ```
* @param _module Reference to the current module where you invoke the fn
* @param valueFactory Fn that returns the value you want to memoize
* @warning Don't use inside conditionals or loops,
* same rules as for hooks apply (https://reactjs.org/docs/hooks-rules.html)
*/
export function useHotMemoize<T>(
_module: NodeModule,
valueFactory: () => T,
): T {
const CURRENT_HOT_MEMOIZE_INDEX_KEY = 'backstage.io/hmr-memoize-key';
if (!_module.hot) {
// Just return value straight away
return valueFactory();
}
const index = (useHotMemoize as any).index ?? 0;
(useHotMemoize as any).index += 1;
const prevValue = _module.hot?.data?.[index];
if (_module.hot && typeof _module.hot.data === 'undefined') {
// First run, init the module data
_module.hot.data = {
[CURRENT_HOT_MEMOIZE_INDEX_KEY]: 0,
};
}
// Let's store data per module based on the order of the code invocation
const index = _module.hot.data?.[CURRENT_HOT_MEMOIZE_INDEX_KEY];
// Increasing the counter after each call
_module.hot.data[CURRENT_HOT_MEMOIZE_INDEX_KEY] += 1;
const prevValue = _module.hot.data?.[index];
const createDisposeHandler = (value: any) => (data: {
[key: number]: any;
[indexKey: string]: number;
}) => {
// Preserving the value through the HMR process
data[index] = value;
// Decreasing the counter after each handler
data[CURRENT_HOT_MEMOIZE_INDEX_KEY] = index - 1;
};
if (prevValue) {
_module.hot!.addDisposeHandler(data => {
data[index] = prevValue;
});
_module.hot!.addDisposeHandler(createDisposeHandler(prevValue));
return prevValue;
}
const newValue = valueFactory();
if (_module.hot) {
_module.hot.addDisposeHandler(data => {
data[index] = newValue;
});
}
_module.hot.addDisposeHandler(createDisposeHandler(newValue));
return newValue;
}
@@ -28,7 +28,7 @@ import {
requestLoggingHandler,
} from '../middleware';
import { ServiceBuilder } from './types';
import { useHotEffect } from '../hot';
import { useHotCleanup } from '../hot';
const DEFAULT_PORT = 7000;
@@ -96,12 +96,11 @@ export class ServiceBuilderImpl implements ServiceBuilder {
0,
);
useHotEffect(this.module, () => {
return () =>
server.stop((e: any) => {
if (e) console.error(e);
});
});
useHotCleanup(this.module, () =>
server.stop((e: any) => {
if (e) console.error(e);
}),
);
resolve(server);
});
+3 -2
View File
@@ -24,7 +24,7 @@ import {
runPeriodically,
} from '@backstage/plugin-catalog-backend';
import { PluginEnvironment } from '../types';
import { useHotEffect } from '@backstage/backend-common';
import { useHotCleanup } from '@backstage/backend-common';
export default async function createPlugin({
logger,
@@ -42,7 +42,8 @@ export default async function createPlugin({
logger,
);
useHotEffect(module, () =>
useHotCleanup(
module,
runPeriodically(() => higherOrderOperation.refreshAllLocations(), 10000),
);