Repository: vitejs/vite
File: packages/vite/src/module-runner/importMetaResolver.ts (TypeScript)

Code before:

export async function resolve(specifier, context, nextResolve) {
  if (specifier.startsWith(${JSON.stringify(customizationHookNamespace)})) {
    const data = specifier.slice(${JSON.stringify(customizationHookNamespace)}.length)
    const [parsedSpecifier, parsedImporter] = JSON.parse(data)
    specifier = parsedSpecifier
    context.parentURL = parsedImporter
  }

  return nextResolve(specifier, context)
}

`

export async function createImportMetaResolver(): Promise<
  ImportMetaResolver | undefined
> {
  let module: typeof import('node:module')
  try {
    module = (await import('node:module')).Module
  } catch {
    return
  }
  if (!module.register) {
    return
  }

  try {
    const hookModuleContent = `data:text/javascript,${encodeURI(customizationHooksModule)}`
    module.register(hookModuleContent)
  } catch (e) {
    // For `--experimental-network-imports` flag that exists in Node before v22
    if ('code' in e && e.code === 'ERR_NETWORK_IMPORT_DISALLOWED') {
      return
    }
    throw e
  }

  return (specifier: string, importer: string) =>
    import.meta.resolve(
      `${customizationHookNamespace}${JSON.stringify([specifier, importer])}`,
    )
}


Review comment (bug):
It looks like this code fails to throw when running `vite/module-runner` on Vite dev browser like https://github.com/hi-ogawa/vite-environment-examples/blob/c54e8e3b1ba0195677bb838d6f12be6510dc361d/examples/web-worker/src/lib/runner.ts#L1, so it fails at `module.register` where `module === undefined`

```
module-runner.js?v=87ff6c55:983 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'register')
    at createImportMetaResolver (module-runner.js?v=87ff6c55:983:13)
    at async ModuleRunner.directRequest (module-runner.js?v=87ff6c55:1122:28)
    at async ModuleRunner.cachedRequest (module-runner.js?v=87ff6c55:1068:73)
    at async ModuleRunner.import (module-runner.js?v=87ff6c55:1018:10)
```

This is because `node:module` becomes `/@id/__vite-browser-external:node:module`, which exports only `export default` and other export name access simply becomes `undefined`.

```js
// /@id/__vite-browser-external:node:module
  export default new Proxy({}, {
    get(_, key) {
      throw new Error(`Module "node:module" has been externalized for browser compatibility. Cannot access "node:module.${key}" in client code.  See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.`)
    }
  })
```

This use case isn't practical level yet, but I think we should just save this by additional null check?

```js
  if (!module || !module.register) {
    return
  }
```


Code after:

export async function resolve(specifier, context, nextResolve) {
  if (specifier.startsWith(${JSON.stringify(customizationHookNamespace)})) {
    const data = specifier.slice(${JSON.stringify(customizationHookNamespace)}.length)
    const [parsedSpecifier, parsedImporter] = JSON.parse(data)
    specifier = parsedSpecifier
    context.parentURL = parsedImporter
  }

  return nextResolve(specifier, context)
}

`

export async function createImportMetaResolver(): Promise<
  ImportMetaResolver | undefined
> {
  let module: typeof import('node:module')
  try {
    module = (await import('node:module')).Module
  } catch {
    return
  }
  // `module.Module` may be `undefined` when `node:module` is mocked
  if (!module?.register) {
    return
  }

  try {
    const hookModuleContent = `data:text/javascript,${encodeURI(customizationHooksModule)}`
    module.register(hookModuleContent)
  } catch (e) {
    // For `--experimental-network-imports` flag that exists in Node before v22
    if ('code' in e && e.code === 'ERR_NETWORK_IMPORT_DISALLOWED') {
      return
    }
    throw e
  }

  return (specifier: string, importer: string) =>
    import.meta.resolve(
      `${customizationHookNamespace}${JSON.stringify([specifier, importer])}`,
    )
}