🔥 Hot Sub-Module Replacement with Parcel 2

This is a short post that simply makes up for an omission, at the time of writing, in Parcel’s HMR documentation, which covers the most basic use-case, but doesn’t cover how it works when one module depends on another module, and we want changes to the dependency to be hot reloaded in the dependent module. In Parcel, the accept callback receives a function as its first argument that returns an array of module data. Each item in the array is another array, in which the second argument is a unique identifier for the module:

type DependentModule = [/*...*/, string];
interface Hot {
  accept(callback?: ((getDependents: () => Array<DependentModule>) => Array<DependentModule>) /*...*/): void;
}

So, if we want to reload all dependent modules:

module.hot.accept((getDependents) => getDependents());

Reloading Specific Dependent Modules

But what if we want to only reload specific dependent modules? The id property of the module might look promising, but it’s just a randomly generated string, so not very helpful. There’s also a hot property, which is the same type as module.hot, so we might try to use module.hot.data to identify modules, the dependency module is completely hot reloaded before the dependent module, so we’ll have to look elsewhere.

Hopefully the Parcel developers improve the API at some point, but for now I’ve found the following workaround acceptable, personally. I created a module, let’s call it meta, that exports a “module record map”:

// meta.ts
interface ModuleRecord {
  path: string;
}
export const MODULE_RECORD_MAP: Record<string, ModuleRecord> = {};

Then, in the dependent module, I import this map and associate its gobbledygook id with its path, which is a much more human-friendly kind of identifier.

// foo.ts
import "bar"
import {MODULE_RECORD_MAP} from "./meta"

MODULE_RECORD_MAP[module.id] = "foo"

Finally, I can import this map in the dependency to exclude this specific dependent when hot reloading:

// bar.ts
import {MODULE_RECORD_MAP} from "./meta"

if (module.hot) {
  module.hot.accept((getDependents) => {
    return getDependents().filter(([_module, id]) => MODULE_RECORD_MAP[id].path !== "foo");
  });
}

I should add that since this approach isn’t documented by Parcel, there’s the risk that future releases will break it, but if you’re like me and already using Parcel and want to benefit from its HMR feature on real-world projects, it might be worth it.

What’s Next?

You can get involved with my efforts to improve Parcel’s HMR feature. In the mean time, I’ve written two other articles on HMR/Parcel.

If you’re not as deep down this rabbit hole as I am, don’t worry, I’ll write about other topics soon!


Posted

in

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *