Skip to content

Commit

Permalink
Merge pull request #161 from theKashey/better-HMR
Browse files Browse the repository at this point in the history
fix: autoload updates on passed HMR event
  • Loading branch information
theKashey authored Dec 29, 2019
2 parents d045966 + fea38e0 commit 07a4d93
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 59 deletions.
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import imported, { lazy } from './HOC';
import LazyBoundary from './LazyBoundary';
import {
assignImportedComponents,
clearImportedCache,
done as whenComponentsReady,
dryRender,
getLoadable as loadableResource,
Expand Down Expand Up @@ -43,5 +44,6 @@ export {
useImported,
useLazy,
addPreloader,
clearImportedCache,
};
export default imported;
71 changes: 37 additions & 34 deletions src/loadable.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import {assingLoadableMark} from './marks';
import {isBackend} from './detectBackend';
import {DefaultImport, Loadable, Mark, MarkMeta, Promised} from './types';
import {settings} from "./config";
import {getPreloaders} from "./preloaders";
import { settings } from './config';
import { isBackend } from './detectBackend';
import { assingLoadableMark } from './marks';
import { getPreloaders } from './preloaders';
import { DefaultImport, Loadable, Mark, MarkMeta, Promised } from './types';

type AnyFunction = (x: any) => any;

interface InnerLoadable<T> extends Loadable<T> {
export interface InnerLoadable<T> extends Loadable<T> {
ok: boolean;
promise: Promise<T> | undefined;
_probeChanges(): Promise<boolean>;
}

let pending: Array<Promise<any>> = [];
Expand All @@ -17,30 +18,22 @@ const LOADABLE_WEAK_SIGNATURE = new WeakMap<any, Loadable<any>>();
const LOADABLE_SIGNATURE = new Map<string, Loadable<any>>();

const addPending = (promise: Promise<any>) => pending.push(promise);
const removeFromPending = (promise: Promise<any>) => pending = pending.filter(a => a !== promise);
const removeFromPending = (promise: Promise<any>) => (pending = pending.filter(a => a !== promise));
const trimImport = (str: string) => str.replace(/['"]/g, '');

export const importMatch = (functionString: string): Mark => {
const markMatches = functionString.match(/`imported_(.*?)_component`/g) || [];
return markMatches
.map(match => match && trimImport((match.match(/`imported_(.*?)_component`/i) || [])[1]));
return markMatches.map(match => match && trimImport((match.match(/`imported_(.*?)_component`/i) || [])[1]));
};

export const getFunctionSignature = (fn: AnyFunction | string) => (
export const getFunctionSignature = (fn: AnyFunction | string) =>
String(fn)
.replace(/(["'])/g, '`')
.replace(/\/\*([^\*]*)\*\//ig, '')
);

.replace(/\/\*([^\*]*)\*\//gi, '');

export function toLoadable<T>(firstImportFunction: Promised<T>, autoImport = true): Loadable<T> {
let importFunction = firstImportFunction;
const _load = (): Promise<T> => (
Promise.all([
importFunction(),
...getPreloaders(),
]).then(([result]) => result)
);
const callLoad = (): Promise<T> => Promise.all([importFunction(), ...getPreloaders()]).then(([result]) => result);
const functionSignature = getFunctionSignature(importFunction);
const mark = importMatch(functionSignature);

Expand Down Expand Up @@ -102,8 +95,8 @@ export function toLoadable<T>(firstImportFunction: Promised<T>, autoImport = tru
// synchronous thenable - https://github.com/facebook/react/pull/14626
cb(result);
return Promise.resolve(result);
}
} as any
},
} as any;
}

return this.loadIfNeeded().then(then);
Expand All @@ -118,34 +111,45 @@ export function toLoadable<T>(firstImportFunction: Promised<T>, autoImport = tru
return Promise.resolve();
},

_probeChanges() {
return callLoad()
.then(payload => payload !== this.payload)
.catch(err => {
throw err;
});
},

load() {
if (!this.promise) {
const promise = this.promise = _load()
.then((payload) => {
const promise = (this.promise = callLoad().then(
payload => {
this.done = true;
this.ok = true;
this.payload = payload;
this.error = null;
removeFromPending(promise);
resolveResolution(payload);
return payload;
}, (err) => {
},
err => {
this.done = true;
this.ok = false;
this.error = err;
removeFromPending(promise);
throw err;
});
}
));
addPending(promise);
}
return this.promise;
}
},
};

if (mark && mark.length) {
LOADABLE_SIGNATURE.set(functionSignature, loadable);
assingLoadableMark(mark, loadable);
} else {
// tslint:disable-next-line:no-console
console.warn(
'react-imported-component: no mark found at',
importFunction,
Expand All @@ -167,8 +171,7 @@ export const isItReady = () => readyFlag;
export const done = (): Promise<void> => {
if (pending.length) {
readyFlag = false;
return Promise
.all(pending)
return Promise.all(pending)
.then(a => a[1])
.then(done);
}
Expand All @@ -181,20 +184,18 @@ export const done = (): Promise<void> => {
export const dryRender = (renderFunction: () => void) => {
renderFunction();

return Promise
.resolve()
.then(done);
return Promise.resolve().then(done);
};

export const markMeta: MarkMeta[] = [];

const assignMetaData = (mark: Mark, loadable: Loadable<any>, chunkName: string, fileName: string) => {
markMeta.push({mark, loadable, chunkName, fileName});
markMeta.push({ mark, loadable, chunkName, fileName });
};

type ImportedDefinition = [Promised<any>, string, string, boolean];

export const assignImportedComponents = (set: Array<ImportedDefinition>) => {
export const assignImportedComponents = (set: ImportedDefinition[]) => {
const countBefore = LOADABLE_SIGNATURE.size;
set.forEach(imported => {
const allowAutoLoad = !(imported[3] || !settings.fileFilter(imported[2]));
Expand All @@ -203,7 +204,8 @@ export const assignImportedComponents = (set: Array<ImportedDefinition>) => {
});

if (countBefore === LOADABLE_SIGNATURE.size) {
console.error('react-imported-component: no import-marks found, please check babel plugin')
// tslint:disable-next-line:no-console
console.error('react-imported-component: no import-marks found, please check babel plugin');
}

done();
Expand Down Expand Up @@ -232,6 +234,7 @@ export function getLoadable<T>(importFunction: DefaultImport<T> | Loadable<T>):
const functionSignature = getFunctionSignature(importFunction);

if (LOADABLE_SIGNATURE.has(functionSignature)) {
// tslint:disable-next-line:no-shadowed-variable
const loadable = LOADABLE_SIGNATURE.get(functionSignature)!;
loadable.replaceImportFunction(importFunction);

Expand Down
36 changes: 11 additions & 25 deletions src/useImported.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ComponentType, lazy, LazyExoticComponent, useCallback, useContext, useEffect, useRef, useState } from 'react';
import { ComponentType, lazy, LazyExoticComponent, useCallback, useContext, useEffect, useState } from 'react';
import { settings } from './config';
import { streamContext } from './context';
import { isBackend } from './detectBackend';
import { getLoadable, isItReady } from './loadable';
import { getLoadable, InnerLoadable, isItReady } from './loadable';
import { useMark } from './marks';
import { DefaultComponentImport, DefaultImport, DefaultImportedComponent, Loadable } from './types';
import { es6import } from './utils';
Expand All @@ -13,20 +13,6 @@ function loadLoadable(loadable: Loadable<any>, callback: (l: any) => void) {
loadable.loadIfNeeded().then(upd, upd);
}

const WEAK_MAP = new WeakMap<Loadable<any>, any>();

function executeLoadableEventually(loadable: Loadable<any>, cb: any) {
const tracker = {};
WEAK_MAP.set(loadable, tracker);

// execute loadable after some timeout to let HMR propagate thought
setTimeout(() => {
if (WEAK_MAP.get(loadable) === tracker) {
loadable.reload().then(cb);
}
}, 16);
}

interface ImportedShape<T> {
imported?: T;
error?: any;
Expand Down Expand Up @@ -107,17 +93,17 @@ export function useImported<T, K = T>(
options: HookOptions = {}
): ImportedShape<K> {
const [topLoadable] = useState(getLoadable(importer));
const postEffectRef = useRef(false);
const { loadable, retry, update } = useLoadable<T>(topLoadable, options);

// kick loading effect
useEffect(() => {
if (postEffectRef.current) {
executeLoadableEventually(loadable, () => settings.updateOnReload && update({}));
}

postEffectRef.current = true;
}, ['hot']);
// support HMR
if (process.env.NODE_ENV === 'development') {
// kick loading effect
useEffect(() => {
if (settings.updateOnReload) {
(topLoadable as InnerLoadable<any>)._probeChanges().then(changed => changed && update({}));
}
}, []);
}

if (loadable.error) {
return {
Expand Down

0 comments on commit 07a4d93

Please sign in to comment.