-
-
Notifications
You must be signed in to change notification settings - Fork 40
React Render Intercept #121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Inrixia
wants to merge
7
commits into
master
Choose a base branch
from
jsxIntercept
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
a618bf7
feat: add component injection functionality (with docs haha)
vMohammad24 5fec858
feat: add replace/replaceChildren and fix some bugs
vMohammad24 75e8af2
chore: bump to 1.7.3
vMohammad24 742860a
Merge branch 'jsxIntercept' into newLibs
Inrixia f9fc875
Merge pull request #87 from vMohammad24/newLibs
Inrixia 8298c99
WIP refactor of react injection
Inrixia 30846cc
Working interceptRender
Inrixia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from "./intercept"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| import React from "react"; | ||
|
|
||
| import type { UnknownRecord } from "@inrixia/helpers"; | ||
| import type { LunaUnload, LunaUnloads } from "@luna/core"; | ||
| import jsxRuntime, { type JSX } from "react/jsx-runtime"; | ||
| import { unloads } from "../index.safe"; | ||
|
|
||
| export const renderJSX = jsxRuntime.jsx; | ||
| export const renderJSXS = jsxRuntime.jsxs; | ||
| unloads.add(() => { | ||
| jsxRuntime.jsx = renderJSX; | ||
| jsxRuntime.jsxs = renderJSXS; | ||
| }); | ||
|
|
||
| export type RenderJSX = typeof renderJSX; | ||
| export type RenderJSXS = typeof renderJSXS; | ||
| export type JSXProps<P = UnknownRecord> = P & { children?: React.ReactNode }; | ||
| export type JSXSProps<P = UnknownRecord> = P & { children?: React.ReactNode[] }; | ||
| export type JSXElementType = keyof JSX.IntrinsicElements; | ||
|
|
||
| type JSXRenderArgs = | ||
| | [isJSXS: true, elementType: JSXElementType, props: JSXSProps, key?: React.Key] | ||
| | [isJSXS: false, elementType: JSXElementType, props: JSXProps, key?: React.Key]; | ||
| /** | ||
| * @param elementType The React HTMLElementType | ||
| * @param props The React element props | ||
| * @param key The React element key | ||
| * @param isJSXS Indicates if calling render was from JSXS or JSX. JSXS children is an array, JSXS is not | ||
| * @returns `undefined` to continue, `ReactElement` to render returned element immediately or `null` to cancel. | ||
| */ | ||
| export type JSXRender = (...args: JSXRenderArgs) => undefined | React.ReactElement | null; | ||
| export const renderInterceptors: Partial<Record<JSXElementType, Set<JSXRender>>> = {}; | ||
|
|
||
| jsxRuntime.jsx = function (type, props, key) { | ||
| if (typeof type === "string") return interceptJSX(false, type, props, key)!; | ||
| return renderJSX(type, props, key); | ||
| }; | ||
| jsxRuntime.jsxs = function (type, props, key) { | ||
| if (typeof type === "string") return interceptJSX(true, type, props, key)!; | ||
| return renderJSXS(type, props, key); | ||
| }; | ||
| const interceptJSX = (isJSXS: boolean, type: JSXElementType, props: any, key?: React.Key) => { | ||
| if (type in renderInterceptors) { | ||
| // Run interceptors for JSXElementType | ||
| for (const interceptor of renderInterceptors[type]!) { | ||
| const res = interceptor(isJSXS, type, props, key); | ||
| // If res is null or ReactElement immediately return it. | ||
| if (res !== undefined) return res; | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| interceptRender("div", unloads, (isJSXS, elementType, props, key?) => { | ||
| if (props["data-test"] !== "footer-player") return; | ||
|
|
||
| const children = isJSXS ? (props.children ?? []) : [props.children]; | ||
| children.push(<>Hello Inside</>); | ||
| props.children = children; | ||
|
|
||
| return ( | ||
| <> | ||
| <span>Hello Above</span> | ||
| {renderJSXS(elementType, props, key)} | ||
| </> | ||
| ); | ||
| }); | ||
Inrixia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Intercept a React Componoent Render based on its `ElementType` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo: "Componoent" → "Component" |
||
| * | ||
| * **WARNING!** `cb` is called on every render for `ElementType`, only use this if you know what you are doing. This is performance critical code. | ||
| * @param elementType The React HTMLElementType to intercept | ||
| * @param cb Called when render is intercepted with props, if returning false element is not rendered | ||
Inrixia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * @param unloads Set of unload functions to add this to, can be nullish but only if you know what your doing | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: "your doing" → "you're doing" |
||
| * @param once If set true only intercepts once | ||
| * @returns Function to call to unload/cancel the intercept | ||
| */ | ||
| export function interceptRender(elementType: React.HTMLElementType, unloads: LunaUnloads, cb: JSXRender, once?: boolean): LunaUnload { | ||
| // If once is true then call unIntercept immediately to only run once | ||
| if (once) | ||
| cb = (isJSXS, type, props: any, key?) => { | ||
| unIntercept(); | ||
| return cb(isJSXS, type, props, key); | ||
| }; | ||
|
|
||
| // Wrap removing the callback from the interceptors in a unload function and return it | ||
| const unIntercept = () => { | ||
| // ?. so that it doesn't throw if the interceptor was already removed | ||
| renderInterceptors[elementType]?.delete(cb); | ||
| if (renderInterceptors[elementType]?.size === 0) delete renderInterceptors[elementType]; | ||
| }; | ||
| unIntercept.source = `intercept::${elementType}`; | ||
|
|
||
| renderInterceptors[elementType] ??= new Set<JSXRender>(); | ||
| renderInterceptors[elementType].add(cb); | ||
|
|
||
| unloads?.add(unIntercept); | ||
| return unIntercept; | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
typeis a string but no interceptor is registered for that element type,interceptJSXreturnsundefined. The!non-null assertion hides this, but the element won't be rendered at all.Should it fallback to
renderJSX/renderJSXSwheninterceptJSXreturnsundefined?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning undefined cancels the render, or renders nothing. But jsx doesn't like undefined as a return type.
Using ! was easy. Maybe I'll try
?? nullif it doesn't complain