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

Feature: introduce hook for dynamic client side imports #10804

Open
2 tasks done
lebalz opened this issue Dec 30, 2024 · 3 comments
Open
2 tasks done

Feature: introduce hook for dynamic client side imports #10804

lebalz opened this issue Dec 30, 2024 · 3 comments
Labels
feature This is not a bug or issue with Docusausus, per se. It is a feature request for the future.

Comments

@lebalz
Copy link
Contributor

lebalz commented Dec 30, 2024

Have you read the Contributing Guidelines on issues?

Description

There exist many libraries that depend on accessing window, so they can't be top-level imported and instead

Has this been requested on Canny?

No response

Motivation

Since this is a common usecase, it could be streamlined as a hook and be document s.t. new users can directly use it

API design

Introduce a new hook in the client api and document it here: https://docusaurus.io/docs/docusaurus-core.

The hook would look something like this:

import React from 'react';

const cachedLibs = new Map<string, any>();
export const useClientLib = <T>(dynamicImport: () => Promise<T>, moduleName?: string): T | null => {
    const [Lib, setLib] = React.useState<T>(moduleName ? cachedLibs.get(moduleName) : null);
    React.useEffect(() => {
        if (Lib) {
            return;
        }
        dynamicImport().then((Lib) => {
            setLib(Lib);
            if (moduleName) {
                cachedLibs.set(moduleName, Lib);
            }
        });
    }, []);
    return Lib || null;
};

And can then easily be used like this:

import React from 'react';
import type { default as ClientLib } from 'some-lib'; 
const SomeComponent = () => {
    const Lib = useClientLib<typeof ClientLib>(() => import('some-lib'), 'some-lib');
    if (!Lib) {
        return <div>Loading...</div>;
    }
    return <Lib.LibComponent />;
};

Have you tried building it?

I add this hook to all my docusaurus pages which needs some-lib-using-window - if there is interest to include this in dpcusaurus, i'm happy to put together a PR.

What i"ve not tried is to use it with relative imports (as you do here for the css (which should be fine to import directly?) https://github.com/facebook/docusaurus/blob/main/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx#L63 , anyway, the rest of the importDocSearchModalIfNeeded should work with the new hook...)

Self-service

  • I'd be willing to contribute this feature to Docusaurus myself.
@lebalz lebalz added feature This is not a bug or issue with Docusausus, per se. It is a feature request for the future. status: needs triage This issue has not been triaged by maintainers labels Dec 30, 2024
@slorber
Copy link
Collaborator

slorber commented Dec 30, 2024

I'm not sure I want to introduce such an API until we have support for React 19, which may solve similar integration problems using core React APIs instead of Docusaurus APIs.

In the future, I plan to use the new React 19 prerender() API and encourage the usage of React.lazy(), <Suspense>, <ErrorBoundary> and

If we create a Docusaurus API, we'd probably build a quite limited one if we do not support loading placeholders, and handle failure/retry etc. To do it properly means implementing our own subset of React Query, while we could just use React and avoid introducing new APIs.


In the future, users of React component libraries using window could try this:

const LazyComp = React.lazy(() => import("some-lib-using-window"));

export default function Page() {
  return (
    <BrowserOnly>
      <Suspense fallback="...">
        <LazyComp>
      </Suspense>
    </BrowserOnly>
  )
}

For other imperative libraries, this could work:

// simple promise caching, but could be something else
const libPromise = import("some-lib-using-window");

function LibComp() {
  const lib = React.use(libPromise)
  return <div>{lib.doSomething()}</div>
}

export default function Page() {
  return (
    <BrowserOnly>
      <Suspense fallback="...">
        <LibComp>
      </Suspense>
    </BrowserOnly>
  )
}

Does it make sense?

@slorber slorber removed the status: needs triage This issue has not been triaged by maintainers label Dec 30, 2024
@lebalz
Copy link
Contributor Author

lebalz commented Dec 30, 2024

Thanks for the detailed insights, this makes sense and staying with the standards is indeed the better option. The sketched solution for the dynamic client side import looks elegant too... looking forward to this ;)
Do you want to keep the issue open as reference, or should i close it?

@slorber
Copy link
Collaborator

slorber commented Dec 31, 2024

Great

We can keep it open and close it once we leverage React 19 properly to achieve that.

In Docusaurus v3 the React lazy/Suspense integration is quite experimental (I feel like they forgot SSG still exists, and not everybody uses HTTP streaming) and I discouraged users to use these features until we figure out how to use them properly: https://docusaurus.io/blog/releases/3.0#react-18

Now in React 19 there's proper support for SSG so in Docusaurus v4 we should be able to integrate properly and flag these features as stable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature This is not a bug or issue with Docusausus, per se. It is a feature request for the future.
Projects
None yet
Development

No branches or pull requests

2 participants