DEV Community

Vladyslav at Prime Board
Vladyslav at Prime Board

Posted on

7 2 1

Implementing HMR for Pinia in NativeScript-Vue with Webpack 5

In a recent project, we encountered a challenge involving Pinia, the default state management library for Vue 3, and NativeScript-Vue. Our objective was to enable Hot Module Reload (HMR) functionality with Webpack 5, despite Pinia's official HMR support being limited to Vite. This article outlines our approach and the solution we developed.

Implementing HMR for Pinia in NativeScript-Vue with Webpack 5

The Challenge: Bridging Pinia with NativeScript-Vue in Webpack 5

With the beta release of Vue 3 support in NativeScript-Vue, there was a clear opportunity to leverage Pinia. However, Pinia's HMR support is designed for Vite, while NativeScript-Vue operates on Webpack 5. This required a creative solution to enable HMR for Pinia within a Webpack environment.

Our Solution Process

Step 1: Configuring TypeScript for Webpack

Our first step was to configure TypeScript for compatibility with Webpack's module system. This involved updating projectRoot/types/node.d.ts to include a reference to webpack/module and ensuring the "types" were correctly configured in the tsconfig.

Step 2: Understanding Webpack's .accept() Method

We identified a key difference in how Webpack handles its .accept() method compared to Vite. In Webpack, this method does not automatically call the callback function. This necessitated a manual approach to managing module updates.

Step 3: Adapting acceptHMRUpdate for Webpack

Next, we adapted Pinia's acceptHMRUpdate function to suit Webpack's methodology. The modified function now receives the initial store ID directly and uses getActivePinia to obtain the Pinia instance.

Step 4: Implementing HMR in Store Files

In our store files, we implemented HMR using import.meta.webpackHot.accept() and import.meta.webpackHot.dispose(). The dispose method is essential for preserving the store ID for the updated acceptHMRUpdate function.

Step 5: Handling the Module Update

A significant hurdle was managing the new module, especially given the limitations of module.exports in ES Modules. We addressed this by passing an object containing only the necessary stores to piniaAccept.

Step 6: Creating a Helper Function

For ease of use, we wrapped the HMR logic in a helper function. For a single store, the implementation is as follows:

if (import.meta.webpackHot) {
  usePiniaWebpackHotHMR(import.meta.webpackHot, useAuthStore);
}
Enter fullscreen mode Exit fullscreen mode

For multiple stores in one file (which should not usually be the case), you can pass an array of stores:

if (import.meta.webpackHot) {
  usePiniaWebpackHotHMR(import.meta.webpackHot, [useAuthStore, useUiStore]);
}
Enter fullscreen mode Exit fullscreen mode

And then add a utils/piniaHmr.ts file with the below contents, or visit this gist link:

import {
Pinia,
SetupStoreDefinition,
StoreDefinition,
StoreGeneric,
_ActionsTree,
_GettersTree,
getActivePinia,
} from "pinia";
type StoreForHMR = SetupStoreDefinition<string, unknown> | StoreDefinition;
export function usePiniaWebpackHotHMR(
hot: webpack.Hot,
store: StoreForHMR | StoreForHMR[]
) {
hot.accept();
const stores = Array.isArray(store) ? store : [store];
stores.forEach((store) => {
hot.dispose((data) => {
(data as any).initialUseStoreId = store.$id;
});
const piniaAccept = acceptWebpackHMRUpdate(store.$id, hot);
piniaAccept({ ...store });
});
}
/**
* Checks if a function is a `StoreDefinition`.
*
* @param fn - object to test
* @returns true if `fn` is a StoreDefinition
*/
export const isUseStore = (fn: any): fn is StoreDefinition => {
return typeof fn === "function" && typeof fn.$id === "string";
};
type PiniaPlus = Pinia & {
_s: Map<string, StoreGeneric>;
};
export function acceptWebpackHMRUpdate(
initialUseStoreId: string,
hot: webpack.Hot
) {
// strip as much as possible from iife.prod
if (!__DEV__) {
return () => {};
}
return (newModule: any) => {
// @ts-expect-error
const pinia: PiniaPlus | undefined = hot.data?.pinia || getActivePinia();
if (!pinia) {
// this store is still not used
return;
}
// preserve the pinia instance across loads
// hot.data.pinia = pinia // FIXME: this doesn't work as data does not let us set functions.
for (const exportName in newModule) {
const useStore = newModule[exportName];
if (isUseStore(useStore) && pinia._s.has(useStore.$id)) {
// console.log('Accepting update for', useStore.$id)
const id = useStore.$id;
if (id !== initialUseStoreId) {
console.warn(
`The id of the store changed from "${initialUseStoreId}" to "${id}". Reloading.`
);
// return import.meta.hot.invalidate()
return hot.invalidate();
}
const existingStore: StoreGeneric = pinia._s.get(id)!;
if (!existingStore) {
console.log(`[Pinia]: skipping hmr because store doesn't exist yet`);
return;
}
useStore(pinia, existingStore);
}
}
};
}
view raw piniaHmr.ts hosted with ❤ by GitHub

Your stores should be importing the usePiniaWebpackHotHMR method from the above piniaHmr.ts

Conclusion

Integrating HMR support for Pinia in NativeScript-Vue with Webpack 5 was a complex but rewarding endeavor. Our solution demonstrates the importance of adapting and overcoming challenges in technology integrations. We hope this solution proves valuable for developers working with NativeScript-Vue and Pinia in a Webpack environment.

Separate thanks to @vallemar and @mrsnoozles - their initial work on HMR improvement has been paramount to this success.

Keep coding and exploring the vast world of web development!

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (1)

Collapse
 
tmcdos profile image
TMCDOS

Thank you very much for your work!
This did not work for me with non-native Vue - I had to make a few changes which I posted in the Github gist above.
Excellent work, by the way.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more