-
Notifications
You must be signed in to change notification settings - Fork 4
feat: support custom rehydratable query selectors #49
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
base: chrisvxd/remove-markup-containers
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| interface IOptions { | ||
| allSelectors: { [key: string]: string }; | ||
| compoundSelector: string; | ||
| extra: object; | ||
| } | ||
|
|
||
| export default IOptions; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| interface IOptions { | ||
| extra: object; | ||
| getQuerySelector?: (key: string) => string; | ||
| } | ||
|
|
||
| export default IOptions; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,24 @@ | ||
| import domElementToReact from "dom-element-to-react"; | ||
| import * as ReactDOM from "react-dom"; | ||
|
|
||
| import ILoadedOptions from "./ILoadedOptions"; | ||
| import IOptions from "./IOptions"; | ||
| import IRehydrator from "./IRehydrator"; | ||
|
|
||
| const rehydratableToReactElement = async ( | ||
| el: Element, | ||
| rehydrators: IRehydrator, | ||
| options: IOptions | ||
| options: ILoadedOptions | ||
| ): Promise<React.ReactElement<any>> => { | ||
| const rehydratorName = el.getAttribute("data-rehydratable"); | ||
| const rehydratorSelector = Object.keys(options.allSelectors).find(selector => | ||
| el.matches(selector) | ||
| ); | ||
|
|
||
| if (!rehydratorSelector) { | ||
| throw new Error("No rehydrator selector matched the element."); | ||
| } | ||
|
|
||
| const rehydratorName = options.allSelectors[rehydratorSelector]; | ||
|
|
||
| if (!rehydratorName) { | ||
| throw new Error("Rehydrator name is missing from element."); | ||
|
|
@@ -31,13 +40,13 @@ const rehydratableToReactElement = async ( | |
|
|
||
| const createCustomHandler = ( | ||
| rehydrators: IRehydrator, | ||
| options: IOptions | ||
| options: ILoadedOptions | ||
| ) => async (node: Node) => { | ||
| // This function will run on _every_ node that domElementToReact encounters. | ||
| // Make sure to keep the conditional highly performant. | ||
| if ( | ||
| node.nodeType === Node.ELEMENT_NODE && | ||
| (node as Element).hasAttribute("data-rehydratable") | ||
| (node as Element).matches(options.compoundSelector) | ||
|
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. Performance consideration: How does |
||
| ) { | ||
| return rehydratableToReactElement(node as Element, rehydrators, options); | ||
| } | ||
|
|
@@ -61,7 +70,7 @@ const createReactRoot = (el: Node) => { | |
| const rehydrateChildren = async ( | ||
| el: Node, | ||
| rehydrators: IRehydrator, | ||
| options: IOptions | ||
| options: ILoadedOptions | ||
| ) => { | ||
| const container = createReactRoot(el); | ||
|
|
||
|
|
@@ -91,30 +100,55 @@ const render = ({ | |
| ReactDOM.render(rehydrated as React.ReactElement<any>, root); | ||
| }; | ||
|
|
||
| const createQuerySelector = (rehydratableIds: string[]) => | ||
| rehydratableIds.reduce( | ||
| (acc: string, rehydratableId: string) => | ||
| `${acc ? `${acc}, ` : ""}[data-rehydratable*="${rehydratableId}"]`, | ||
| const defaultGetQuerySelector = (key: string) => | ||
| `[data-rehydratable*="${key}"]`; | ||
|
|
||
| const createQuerySelectors = ( | ||
| rehydratableIds: string[], | ||
| getQuerySelector: ((key: string) => string) = defaultGetQuerySelector | ||
| ) => { | ||
| const allSelectors: { [key: string]: string } = rehydratableIds.reduce( | ||
| (acc, key) => ({ ...acc, [getQuerySelector(key)]: key }), | ||
| {} | ||
| ); | ||
|
|
||
| const compoundSelector = Object.keys(allSelectors).reduce( | ||
| (acc: string, selector: string) => `${acc ? `${acc}, ` : ""}${selector}`, | ||
|
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. Performance consideration: Additional loop on each |
||
| "" | ||
| ); | ||
|
|
||
| return { | ||
| allSelectors, | ||
| compoundSelector | ||
| }; | ||
| }; | ||
|
|
||
| export default async ( | ||
| container: Element, | ||
| rehydrators: IRehydrator, | ||
| options: IOptions | ||
| ) => { | ||
| const selector = createQuerySelector(Object.keys(rehydrators)); | ||
|
|
||
| const roots = Array.from( | ||
| // TODO: allow setting a container identifier so multiple rehydration instances can exist | ||
| container.querySelectorAll(selector) | ||
| ).reduce((acc: Element[], root: Element) => { | ||
| // filter roots that are contained within other roots | ||
| if (!acc.some(r => r.contains(root))) { | ||
| acc.push(root); | ||
| } | ||
| return acc; | ||
| }, []); | ||
| const { allSelectors, compoundSelector } = createQuerySelectors( | ||
| Object.keys(rehydrators), | ||
| options.getQuerySelector | ||
| ); | ||
|
|
||
| const loadedOptions: ILoadedOptions = { | ||
| allSelectors, | ||
| compoundSelector, | ||
| extra: options.extra | ||
| }; | ||
|
|
||
| const roots = Array.from(container.querySelectorAll(compoundSelector)).reduce( | ||
| (acc: Element[], root: Element) => { | ||
| // filter roots that are contained within other roots | ||
| if (!acc.some(r => r.contains(root))) { | ||
| acc.push(root); | ||
| } | ||
| return acc; | ||
| }, | ||
| [] | ||
| ); | ||
|
|
||
| // TODO: solve race condition when a second rehydrate runs | ||
|
|
||
|
|
@@ -128,7 +162,7 @@ export default async ( | |
| const { | ||
| container: rootContainer, | ||
| rehydrated | ||
| } = await rehydrateChildren(root, rehydrators, options); | ||
| } = await rehydrateChildren(root, rehydrators, loadedOptions); | ||
|
|
||
| return { root: rootContainer, rehydrated }; | ||
| } catch (e) { | ||
|
|
||
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.
Performance consideration: This introduces a
findloop on each call ofrehydratableToReactElement.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.
Worth considering switch to for loop:
Source. Need more data.