preactjs/wmr

Suspense combined with the directory plugin preventing component mounting on page load.

chopfitzroy opened this issue · 5 comments

Describe the bug

Using suspense (via pmndrs/use-asset) and the directory plugin is causing components to not be mounted on page load. Components do mount when navigating to a new route and back again as long as the browser is not refreshed.

It's odd, there are no errors in the console and I can see the correct network request being made, as far as I can tell the component is simply not mounting.

I have tried a number of suspense style hooks for loading data and all produce the same results so I am fairly confident this is related to suspense itself.

I have tried both normally mounting the /notes/ route and using lazy and the result seems to be the same.

Additionally wmr build --prerender produces the following error:

Error: Could not load dir:../content (imported by public/pages/show-notes.tsx): ENOENT: no such file or directory, stat '/Users/otis/Developer/runtime.fm/content'

Which makes sense because the content is located at runtime.fm/public/content (the above path is missing public) but I am not sure how to resolve.

To Reproduce

Code is all open source and can be seen on this branch.

Video of the problem in action:

CleanShot.2023-06-01.at.20.12.55.mp4

Expected behavior

Component should mount on page load.

Bug occurs with:

  • wmr or wmr start (development)
  • wmr build (production)
  • wmr serve

Desktop (please complete the following information):

  • OS: [e.g. MacOS 13.2]
  • Browser: [Chrome]
  • Node Version: [v19.8.1]
  • WMR Version: [^3.8.0]

Additional context

Not really sure how to debug this as there isn't any errors, it just simply doesn't mound.

Additionally wmr build --prerender produces the following error:

Error: Could not load dir:../content (imported by public/pages/show-notes.tsx): ENOENT: no such file or directory, stat '/Users/otis/Developer/runtime.fm/content'

Which makes sense because the content is located at runtime.fm/public/content (the above path is missing public) but I am not sure how to resolve.

It's a bug in the directory plugin. I guess we never tested to ensure it could be used anywhere but directly inside public/. I'll PR a fix for it later, but here's what it should be corrected to really:

import { promises as fs } from "fs";
import path from "path";

/**
 * @param {import("wmr").Options} options
 * @param {string} options.root
 * @returns {import('rollup').Plugin}
 */
function directoryPlugin(options) {
    const PREFIX = "dir:";
    const INTERNAL = "\0dir:";

    options.plugins.push({
        name: "directory",
        async resolveId(id, importer) {
            if (!id.startsWith(PREFIX)) return;

            id = id.slice(PREFIX.length);
            let resolved = await this.resolve(id, importer, { skipSelf: true });
            if (!resolved) {
                const r = path.join(path.dirname(importer), id);
                const stats = await fs.stat(r);
                if (!stats.isDirectory()) throw Error(`Not a directory.`);
                resolved = { id: r };
            }
            return INTERNAL + (resolved ? resolved.id : id);
        },
        async load(id) {
            if (!id.startsWith(INTERNAL)) return;

            // remove the "\dir:" prefix and convert to an absolute path:
            id = id.slice(INTERNAL.length);
            let dir = id.split(path.posix.sep).join(path.sep);
            if (!options.prod) {
                dir = path.join(options.root, dir);
            }

            // watch the directory for changes:
            this.addWatchFile(dir);

            // generate a module that exports the directory contents as an Array:
            const files = (await fs.readdir(dir)).filter((d) => d[0] != ".");
            return `export default ${JSON.stringify(files)}`;
        },
    });
}

export default directoryPlugin;

You could use patch-package to use this, or, just stick it in your wmr.config.js and use it instead of the @wmrjs/directory-import package.

Will look into the suspense thing further though, not sure what that is.

Thank you will rig that up with patch-package this afternoon.

In regards to the suspense issue I thought I should probably mention I am successfully able to use useAsset in this provider which was why I thought it might be to do with using lazy with the route.

Let me know if there is any additional context I can provide.

For the suspense issue, I'd maybe just avoid using use-asset? I'm far from familiar with suspense, I tend to avoid it, but I'm not quite sure how useAsset is ever supposed to rerender after the promise is resolved. This might be a difference between React and Preact, might be an issue with the router or lazy loading, dunno.

What I suggested a few months back certainly does work, however: #950 (comment)

Your absolutely correct, using the hook described in #950 did the trick.

If you are able to track down the specifics, and it's something we need to fix, certainly let us know. Just thought I'd point you in the way of something that works as I don't quite understand what's not lining up with use-asset myself.