Skip to content
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

feat: efficient HMR updates #11

Merged
merged 4 commits into from
Mar 13, 2021
Merged

feat: efficient HMR updates #11

merged 4 commits into from
Mar 13, 2021

Conversation

aleclarson
Copy link
Member

@aleclarson aleclarson commented Mar 5, 2021

The goal: Ensure HMR updates for pages never reload the window.

This PR introduces new React hooks for subscribing to hot-reloadable data provided by the vite-plugin-react-pages/dist/client module. These hooks include:

  • useStaticData: Subscribe to the .staticData of a specific page or all pages.
  • usePagePaths: Subscribe to the array of paths for all pages.
  • usePageModule: Subscribe to the module referred to by the dataPath of a specific page.
  • useTheme: Subscribe to the Theme component.

In most cases, useStaticData is the only hook used by the application/theme. It replaces the staticData prop previously passed to the Theme component. The idea is to depend on static data where it's needed, instead of having to re-render the entire application when the static data is updated.

By calling useStaticData(), the component subscribes to all static data. By calling useStaticData(path), the component only re-renders when the static data of that specific path is updated. You can even pass a selector if you only need a specific piece of static data. In the example below, the caller only re-renders when the page title is changed.

const title = useStaticData(path, data => data.main.title)

The other hooks are mostly for the internal infrastructure of react-pages.

useTheme is used by the PageLoader component (which is now wrapped with React.memo to avoid re-rendering when a page is added or removed).

usePagePaths is used by the App component for updating the page routes when a page is added or removed.

usePageModule is used by the useAppState hook (which I've rewritten) for reloading a page when its dataPath is changed. It even uses the /404 page if a path does not exist. Once the path does exist, HMR will re-render the PageLoader automatically with a new usePageModule promise.

Note: Hot reloading for useTheme is broken by a Vite bug. This being tracked by vitejs/vite#2380.

How does this all work?

When HMR is enabled, we use a library called jotai, which lets us re-render components only when the data they depend on is changed. The client/state.ts module (with access to our Jotai atoms) plugs into Vite's HMR API. When the @!virtual-modules/pages module is updated, we diff it with its previous module to determine what has changed (see setPagesAtom in client/state.ts).

In production, all that logic is tree-shaked, and we're left with simple hooks that return immutable data.


Depends on #10

}
}, [routePath])
}, [loading])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we put onLoadState in the deps arrary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. We only want the effect to run when loading changes.

This PR introduces new React hooks for subscribing to hot-reloadable data provided by the "vite-plugin-react-pages/dist/client" module. These hooks include:
  - useStaticData: Subscribe to the `.staticData` of a specific page or all pages.
  - usePagePaths: Subscribe to the array of paths for all pages.
  - usePageModule: Subscribe to the module referred to by the `dataPath` of a specific page.
  - useTheme: Subscribe to the `Theme` component.

In most cases, `useStaticData` is the only hook used by the application/theme. It replaces the `staticData` prop previously passed to the `Theme` component. The idea is to depend on static data where it's needed, instead of having to re-render the entire application when the static data is updated.

By calling `useStaticData()`, the component subscribes to **all static data.** By calling `useStaticData(path)`, the component only re-renders when the static data of that specific path is updated. You can even pass a selector if you only need a specific piece of static data. In the example below, the caller only re-renders when the page title is changed.

```ts
const title = useStaticData(path, data => data.main.title)
```

The other hooks are mostly for the internal infrastructure of react-pages.

`useTheme` is used by the `PageLoader` component (which is now wrapped with `React.memo` to avoid re-rendering when a page is added or removed).

`usePagePaths` is used by the `App` component for updating the page routes when a page is added or removed.

`usePageModule` is used by the `useAppState` hook (which I've rewritten) for reloading a page when its `dataPath` is changed. It even uses the `/404` page if a path does not exist. Once the path *does* exist, HMR will re-render the `PageLoader` automatically with a new `usePageModule` promise.

How does this all work?
---

When HMR is enabled, we use a library called [jotai](https://npmjs.org/jotai), which lets us re-render components only when the data they depend on is changed. The `client/state.ts` module (with access to our Jotai atoms) plugs into Vite's HMR API. When the `@!virtual-modules/pages` module is updated, we diff it with its previous module to determine what has changed (see `setPagesAtom` in `client/state.ts`).

In production, all that logic is tree-shaked, and we're left with simple hooks that return immutable data.
@csr632
Copy link
Member

csr632 commented Mar 8, 2021

Thanks! Great feature. I need some time to look into it more carefully.

By the way, I have set up basic tests and local playground. You can play with it locally.

This avoids the issue described in:
  vitejs/vite#2380
@aleclarson
Copy link
Member Author

I just pushed 7eb042b, which avoids vitejs/vite#2380, so hot-reloading for useTheme works as intended now!

@csr632 csr632 merged commit e4b4abb into vitejs:master Mar 13, 2021
@csr632
Copy link
Member

csr632 commented Mar 13, 2021

Sorry for keeping you waiting! I will release it after adding some tests.

@aleclarson
Copy link
Member Author

aleclarson commented Mar 13, 2021

The theme-basic package needs to be updated too, since themes don't receive static data through props anymore. (need to call useStaticData instead)

When you get around to doing that, I can review your work :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants