Skip to content

[Bug]: HMR wrong patch for multiple import statements with same importee #5149

@lyonbot

Description

@lyonbot

Reproduction link or steps

if a hot module has something like this, it will generate wrong patches:

import { prefix } from './common.js';
import { prefix as p2 } from './common.js';
import { prefix as p3 } from './common.js';
// ^ same source, imported three times

POC

import { rolldown } from "rolldown";
import fs, { realpath } from 'node:fs/promises';
import { chdir } from "node:process";

async function main() {
  const baseDir = './temp-1/';

  await fs.rm(baseDir, { recursive: true, force: true });
  await fs.mkdir(baseDir, { recursive: true });
  chdir(baseDir);

  await fs.writeFile('index.js', `
    import { sayMessage } from './messenger.js';
    setInterval(sayMessage, 1000);
    `);

  await fs.writeFile('messenger.js', `
    import { foo } from './foo.js';
    import { bar } from './bar.js';
    import { prefix } from './common.js';
    import { prefix as p2 } from './common.js';
    import { prefix as p3 } from './common.js';

    export let msg = [
      prefix,
      foo,
      bar,
      p2,
      p3,
    ].join(",")
    export function sayMessage() {
      console.log(msg);
    }

    if (import.meta.hot) {
      import.meta.hot.accept((mod) => {
        console.log('replaced with new msg: ', mod.msg);
        msg = mod.msg;
      });
    }
    `)

  await fs.writeFile('foo.js', `
    import { prefix } from './common.js';
    export const foo = prefix + 'foo';
    `)

  await fs.writeFile('bar.js', `
    import { prefix } from './common.js';
    export const bar = prefix + 'bar';
    `)

  await fs.writeFile('common.js', `
    export const prefix = 'prefix:';
    `)

  const r = await rolldown({
    input: 'index.js',
    experimental: {
      hmr: true,
    },
  });
  const out = await r.generate({})
  fs.writeFile('_output.js', out.output[0].code);


  // ----------------------------------------------
  // hmr test

  // await fs.writeFile('common.js', `
  //   export const prefix = 'prefix-new:';
  //   `)
  await fs.writeFile('foo.js', `
    import { prefix } from './common.js';
    export const foo = prefix + 'foo-new';
    `)

  const hmrPatch = await r.generateHmrPatch([await realpath('foo.js')]);
  console.log('hmrPatch', hmrPatch);

  await fs.writeFile('_hmr-patch.js', hmrPatch.code);
}

main().catch(err => { console.error(err); process.exit(1); })

What is expected?

generate a working hmr patch

What is actually happening?

the generated _hmr-patch.js lacks import_common_3 and import_common_4:

var init_foo_0 = __rolldown_runtime__.createEsmInitializer(function() {
	try {
		var ns_foo = {};
		__rolldown_runtime__.__export(ns_foo, { foo: () => foo });
		__rolldown_runtime__.__toCommonJS(ns_foo);
		__rolldown_runtime__.registerModule("foo.js", { exports: ns_foo });
		const hot_foo = __rolldown_runtime__.createModuleHotContext("foo.js");
		var import_common_0 = __rolldown_runtime__.loadExports("common.js");
		const foo = import_common_0.prefix + "foo-new";
	} finally {}
});

var init_messenger_1 = __rolldown_runtime__.createEsmInitializer(function() {
	try {
		var ns_messenger = {};
		__rolldown_runtime__.__export(ns_messenger, {
			msg: () => msg,
			sayMessage: () => sayMessage
		});
		__rolldown_runtime__.__toCommonJS(ns_messenger);
		__rolldown_runtime__.registerModule("messenger.js", { exports: ns_messenger });
		init_foo_0();
		const hot_messenger = __rolldown_runtime__.createModuleHotContext("messenger.js");
		var import_foo_0 = __rolldown_runtime__.loadExports("foo.js");
		var import_bar_1 = __rolldown_runtime__.loadExports("bar.js");
		var import_common_2 = __rolldown_runtime__.loadExports("common.js");
		;
		;
		let msg = [
			import_common_2.prefix,
			import_foo_0.foo,
			import_bar_1.bar,
			import_common_3.prefix,
			import_common_4.prefix
		].join(",");
		function sayMessage() {
			console.log(msg);
		}
		if (hot_messenger) hot_messenger.accept((mod) => {
			console.log("replaced with new msg: ", mod.msg);
			msg = mod.msg;
		});
	} finally {}
});

init_messenger_1()
__rolldown_runtime__.applyUpdates(['messenger.js']);

System Info

System:
    OS: macOS 15.5
    CPU: (14) arm64 Apple M4 Pro
    Memory: 192.86 MB / 48.00 GB
    Shell: 5.2.37 - /opt/homebrew/bin/bash
  Binaries:
    Node: 20.19.2 - ~/.nvm/versions/node/v20.19.2/bin/node
    npm: 10.8.2 - ~/.nvm/versions/node/v20.19.2/bin/npm
    pnpm: 10.12.4 - ~/.nvm/versions/node/v20.19.2/bin/pnpm
  npmPackages:
    rolldown: 1.0.0-beta.21 => 1.0.0-beta.21

Any additional comments?

No response

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions