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

Reload virtual module on demand #6871

Closed
4 tasks done
ConsoleTVs opened this issue Feb 11, 2022 · 7 comments · Fixed by #10324 or #10333
Closed
4 tasks done

Reload virtual module on demand #6871

ConsoleTVs opened this issue Feb 11, 2022 · 7 comments · Fixed by #10324 or #10333
Labels
enhancement New feature or request feat: hmr

Comments

@ConsoleTVs
Copy link

ConsoleTVs commented Feb 11, 2022

Clear and concise description of the problem

As a developer I want to be able to reload a specific virtual module on demand via vite's Server API.

The idea is that the contents of that virtual module change upon an external event / conditon. I want to be able
to manually invalidate such module and reload it (and all modules who depend on it) on demand without restarting the whole server.

Suggested solution

const virtualModuleId = 'virtual:example'
const resolvedVirtualModuleId = '\0' + virtualModuleId

const custom = {
  name: 'example',
  enforce: 'pre',
  resolveId: (id) => {
    if (id === virtualModuleId) {
      return resolvedVirtualModuleId
    }
  },
  load: (id) => {
    if (id === resolvedVirtualModuleId) {
      return `...`
    }
  },
}

const server = await createServer({
  plugins: [custom],
  ...
})

// For demo purposes, this is an external condition.
setInterval(() => {
    // This is what I would expect somehow.
    server.reloadModule(resolvedVirtualModuleId)
}, 5000)

await server.listen()

Alternative

No response

Additional context

No response

Validations

@bluwy
Copy link
Member

bluwy commented Feb 13, 2022

server.moduleGraph.invalidateModule()?

@patak-dev
Copy link
Member

I have a plugin to do it, but currently uses a full-reload: https://github.com/patak-dev/vite-plugin-virtual#vite-plugin-virtual

@bluwy I think we need to expose more from the server to not only invalidate but also trigger the HMR phase. At least it was like that last time I checked. I think this is a common use case and we should add to core what is needed to support it if not.

@bluwy
Copy link
Member

bluwy commented Feb 13, 2022

I see 👍 Was just tossing ideas. Not familiar with the module graph API myself. I'd expect invalidating would trigger HMR as well.

@ConsoleTVs
Copy link
Author

ConsoleTVs commented Feb 13, 2022

Exactly. The only way for me to do this right now is to use

server.moduleGraph.onFileChange(resolvedVirtual)
server.ws.send({
  type: 'full-reload',
})

The issue here is that this triggers a FULL RELOAD, and I would only want the modules that imported this virtual module to perform a reload or at least, control how they are updated. I noticed that internally, virtual modules dont store the name of the importees, but you have to use the encoded name for it. eg:

const virtual = 'virtual:pages'
const resolvedVirtual = '\0' + virtual
const encodedVirtual = '/@id/__x00__' + virtual

If you search the importees of resolvedVirtual, it will say nobody has used it. As indicated on the docs, the encoded one must be used. This allowed me to get a list of the modules that imported my virtual module, but I don't know how to perform something else based on that.

This is kinda tied to the following statement on the docs:

Note that Vite's HMR does not actually swap the originally imported module: if an HMR boundary module re-exports imports from a dep, then it is responsible for updating those re-exports (and these exports must be using let). In addition, importers up the chain from the boundary module will not be notified of the change.

So I guess my suggestion goes along the lines of, we should be able to "notify the change" to the modules affected by my virtual module changing its contents.

Something as simple as a virtual module like this:

export let n = 0

used by a module like this:

import { n } from 'virtual:pages'

console.log({ n })

If I ever need to change what's exported on the virtual module, say n is now 10, how do I notify the module about that change, so that i can re-compute or re-execute without a full page?

@ivands
Copy link

ivands commented Jun 18, 2022

I also need a way to only reload my virtual module.

@bluwy bluwy added enhancement New feature or request feat: hmr and removed enhancement: pending triage labels Jun 18, 2022
@Cecil0o0
Copy link

Cecil0o0 commented Aug 15, 2022

@ivands , Hi friend, here some information for you from my practice~
I try to write a virtual module for serving README.md file as a react component

[key point one]
I use handleHotUpdate interface to handle file updated. Some code shows below

async handleHotUpdate({ server, file, timestamp }) {
  if (file.endsWith('.md')) {
    const virtualModule = server.moduleGraph.getModuleById('\0@infra/plugin-cdoc')!;
    server.moduleGraph.invalidateModule(virtualModule);
    server.ws.send({
      type: 'update',
      updates: [
        {
          acceptedPath: '/@id/__x00__@infra/plugin-cdoc',
          path: '/@id/__x00__@infra/plugin-cdoc',
          timestamp: timestamp,
          type: 'js-update',
        },
      ],
    });
  }
}

To invalidateModule this virtual module in vite/serve, then tell vite/client to fetchUpdate new module.

[key point two]
also, I emit some source code from vite, the key function is addRefreshWrapper, some code block below

async load(id) {
  if (id === '\0@infra/plugin-cdoc') {
    const { compile } = await import('@mdx-js/mdx');
    const code = await compile(readFileSync(resolve(__dirname, 'README.md')), {
      jsx: false,
    });
    return {
      code: addRefreshWrapper(`
      ${code}

      export function Cdoc() {
        return <MDXContent components={{
          em: props => <i {...props} />,
          MinimumVersion: () => <>'MinimumVersion'</>,
          Demo: () => <>'Demo'</>,
          Contributors: () => <>'Contributors'</>
        }} />;
      }

      $RefreshReg$(Cdoc, "Cdoc");
    `, id, true),
    };
  }
}

$RefreshReg$(Cdoc, "Cdoc"); is an additional key statement for enabling react-refresh correctly.

To integrate react-refresh for this virtual module.

Finally, it works for me ^_^, and try to give some information to you.

@bluwy
Copy link
Member

bluwy commented Oct 3, 2022

Re-opening this. We could expose a server.invalidateModule api to help invalidate a virtual module easily. The HMR update part has been fixed in #10324

@bluwy bluwy reopened this Oct 3, 2022
@github-actions github-actions bot locked and limited conversation to collaborators Oct 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request feat: hmr
Projects
None yet
5 participants