Skip to content

Commit

Permalink
Merge pull request #15 from XantreGodlike/lazy-support
Browse files Browse the repository at this point in the history
Wanna add lazy support
  • Loading branch information
XantreDev authored Oct 7, 2023
2 parents 5a60f8c + f057209 commit 5aa2616
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 40 deletions.
5 changes: 5 additions & 0 deletions .changeset/quiet-sloths-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-fast-hoc": minor
---

Added support for components wrapped with `React.lazy`
17 changes: 9 additions & 8 deletions packages/react-fast-hoc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@
"types": "./lib/index.d.ts",
"module": "./lib/esm/index.mjs",
"scripts": {
"test": "vitest run && tsc --project tsconfig.node.json",
"test": "vitest run",
"test:watch": "vitest watch",
"typecheck": "tsc --noEmit",
"typecheck": "tsc --noEmit && tsc --project tsconfig.node.json",
"lint": "pnpm typecheck",
"build:collect-readme": "cpx ../../README.md .",
"build": "pnpm build:collect-readme & rollup -c"
},
"devDependencies": {
"@rollup/plugin-typescript": "^11.1.1",
"@testing-library/react": "^14.0.0",
"@types/node": "^20.2.5",
"@types/react": "^18.2.0",
Expand All @@ -37,17 +38,17 @@
"hotscript": "^1.0.12",
"jsdom": "^21.1.1",
"nanobundle": "^1.6.0",
"radash": "^11.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ts-toolbelt": "^9.6.0",
"tsconfig": "workspace:*",
"type-fest": "^3.9.0",
"vitest": "^0.30.1",
"@rollup/plugin-typescript": "^11.1.1",
"rollup": "^3.23.1",
"rollup-plugin-dts": "^5.3.0",
"rollup-plugin-esbuild": "^5.0.0",
"rollup-plugin-node-externals": "^6.1.1"
"rollup-plugin-node-externals": "^6.1.1",
"ts-toolbelt": "^9.6.0",
"tsconfig": "workspace:*",
"type-fest": "^3.9.0",
"vitest": "^0.30.1"
},
"peerDependencies": {
"hotscript": "^1.0.12",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export class MimicToNewComponentHandler implements ProxyHandler<Function> {
export class MimicToNewComponentHandler implements ProxyHandler<object> {
private _componentProps = new WeakMap<Function, Map<PropertyKey, unknown>>();

get(target: Function, p: PropertyKey, receiver: any) {
Expand Down
109 changes: 83 additions & 26 deletions packages/react-fast-hoc/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { cleanup, render } from "@testing-library/react";
import { cleanup, render, waitFor } from "@testing-library/react";
import { sleep } from "radash";
import { Objects } from "hotscript";
import React, { createElement, forwardRef, memo } from "react";
import React, { ComponentType, createElement, forwardRef, memo } from "react";
import { Function } from "ts-toolbelt";
import { afterEach, describe, expect, expectTypeOf, test, vi } from "vitest";
import { createTransformProps, transformProps, wrapIntoProxy } from ".";

declare module "react" {
export interface ExoticComponent {
_payload?: {
_result: any;
};
_init?: () => void;
}
}

const identityProps = <T>(props: T) => props;

afterEach(() => {
Expand Down Expand Up @@ -33,7 +43,6 @@ describe("transforms component to needed type", () => {
).toBeTypeOf("function");
});
});

describe("transformProps", () => {
const addBebeProp = vi.fn(
(props: Record<never, never>) =>
Expand All @@ -56,7 +65,6 @@ describe("transformProps", () => {

class ClassComponent extends React.Component {
constructor(props: unknown) {
console.log("class created", props);
super(props as {});
propsDetector(this.props);
}
Expand Down Expand Up @@ -127,28 +135,77 @@ describe("transformProps", () => {
expect(Component).toHaveBeenCalledWith({ bebe: true }, null);
});

// TODO: add support for lazy
// test("works with unloaded lazy", async () => {
// const Cmp = vi.fn(Component);
// const Lazy = React.lazy(() => Promise.resolve({ default: Cmp }));

// console.log(Lazy._payload._result.toString());
// console.log(Lazy._init.toString());
// render(
// createElement(
// React.Suspense,
// {},
// createElement(addBebeHoc(Lazy))
// )
// );
// await waitFor(() => {
// expect(Cmp).toHaveBeenCalled();
// console.log(Lazy)
// expect(addBebeProp).toHaveBeenCalled();
// expect(addBebeProp).lastCalledWith({});
// expect(Cmp).toHaveBeenCalledWith({ bebe: true }, {});
// });
// });
describe("hocs: lazy", () => {
test("unloaded lazy", async () => {
const Cmp = vi.fn(Component);
const Lazy = React.lazy(() => Promise.resolve({ default: Cmp }));

render(
createElement(React.Suspense, {}, createElement(addBebeHoc(Lazy)))
);
await waitFor(() => {
expect(Cmp).toHaveBeenCalled();
expect(addBebeProp).toHaveBeenCalled();
expect(addBebeProp).lastReturnedWith({
bebe: true,
});
});
});
test("pending lazy", async () => {
const Cmp = vi.fn(Component);
const lazyInit = vi.fn(() => sleep(20).then(() => ({ default: Cmp })));
const Lazy = React.lazy(lazyInit);
const r = render(createElement(React.Suspense, {}, createElement(Lazy)));
expect(Cmp).not.toHaveBeenCalled();
expect(lazyInit).toHaveBeenCalled();

r.rerender(
createElement(React.Suspense, {}, createElement(addBebeHoc(Lazy)))
);

await waitFor(
() => {
expect(Cmp).toHaveBeenCalled();
expect(addBebeProp).toHaveBeenCalled();
expect(addBebeProp).lastReturnedWith({
bebe: true,
});
},
{
timeout: 100,
}
);
});
test("resolved lazy", async () => {
const Cmp = vi.fn(Component);
const Lazy = React.lazy(() => Promise.resolve({ default: Cmp }));
const r = render(createElement(React.Suspense, {}, createElement(Lazy)));
await waitFor(
() => {
expect(Cmp).toHaveBeenCalled();
expect(addBebeProp).not.toHaveBeenCalled();
},
{
timeout: 100,
}
);

r.rerender(
createElement(React.Suspense, {}, createElement(addBebeHoc(Lazy)))
);
expect(Cmp).toHaveBeenCalledTimes(2);
expect(addBebeProp).toHaveBeenCalled();
expect(Cmp).lastCalledWith(
{
bebe: true,
},
{}
);
expect(addBebeProp).lastReturnedWith({
bebe: true,
});
});
});
});

describe.skip("type tests", () => {
Expand Down
33 changes: 28 additions & 5 deletions packages/react-fast-hoc/src/internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,12 @@ type RealComponentType<TProps extends object, IRef = unknown> =
}
| {
$$typeof: typeof REACT_LAZY_TYPE;
_status: -1 | 0 | 1 | 2;
_result: unknown;
_payload: {
_status: -1 | 0 | 1 | 2;
_result: unknown;
};
// returns component or throws promise
_init: (arg: unknown) => React.ComponentType<unknown>;
}
| React.ComponentClass<TProps>
| React.FC<TProps>;
Expand Down Expand Up @@ -99,7 +103,7 @@ export const wrapComponentIntoHoc = <TProps extends object>(
Component: RealComponentType<TProps>,
handler: HocTransformer,
mimicToNewComponentHandler: null | MimicToNewComponentHandler
) => {
): unknown => {
// this case assumes that it's ClassComponent
if (isClassComponent(Component)) {
return wrapFunctionalFROrDefault(
Expand Down Expand Up @@ -128,11 +132,30 @@ export const wrapComponentIntoHoc = <TProps extends object>(
};
}
if ("$$typeof" in Component && Component["$$typeof"] === REACT_LAZY_TYPE) {
return Component;
let result: RealComponentType<any>;
return {
$$typeof: REACT_LAZY_TYPE,
_payload: Component._payload,
_init: (arg: unknown) => {
const initRes = Component._init(arg);
if (!result) {
result = wrapComponentIntoHoc(
initRes,
handler,
mimicToNewComponentHandler
) as RealComponentType<any>;
}
return result;
},
} as RealComponentType<any>;
}

const proxied = new Proxy(Component, handler);

return mimicToNewComponentHandler
? new Proxy(proxied, mimicToNewComponentHandler)
? (new Proxy(
proxied,
mimicToNewComponentHandler
) as RealComponentType<TProps>)
: proxied;
};
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5aa2616

Please sign in to comment.