Skip to content

Instantly share code, notes, and snippets.

@primeboard-dev
Last active November 13, 2025 21:38
Show Gist options
  • Select an option

  • Save primeboard-dev/79d230e4178c4dea2ff598601ed265bf to your computer and use it in GitHub Desktop.

Select an option

Save primeboard-dev/79d230e4178c4dea2ff598601ed265bf to your computer and use it in GitHub Desktop.
Hot Module Reload (HMR) for Pinia in NativeScript-Vue with Webpack 5
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);
}
}
};
}
@tmcdos
Copy link

tmcdos commented Jul 29, 2024

This did not work for me with non-native Vue so I had to make some changes:

import { getActivePinia } from 'pinia';

export function usePiniaWebpackHotHMR(hot, store)
{
  hot.accept();
  const stores = Array.isArray(store) ? store : [store];

  stores.forEach((storeInstance) =>
  {
    hot.dispose((data) =>
    {
      (data).initialUseStoreId = storeInstance.$id;
    });
    const piniaAccept = acceptWebpackHMRUpdate(storeInstance.$id, hot);
    piniaAccept(storeInstance);
  });
}

/**
 * Checks if a function is a `StoreDefinition`.
 *
 * @param fn - object to test
 * @returns true if `fn` is a StoreDefinition
 */
export const isUseStore = (fn) => typeof fn === 'function' && typeof fn.$id === 'string';

export function acceptWebpackHMRUpdate(initialUseStoreId, hot)
{
  // strip as much as possible from iife.prod
  if (process.env.NODE_ENV === 'production')
  {
    return () =>
    {};
  }
  return (newModule) =>
  {
    const pinia = 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.

    if (isUseStore(newModule) && pinia._s.has(newModule.$id))
    {
      console.log(`[HMR-Pinia] Accepting update for "${newModule.$id}"`);
      const id = newModule.$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(); // eslint-disable-line consistent-return
      }

      const existingStore = pinia._s.get(id);
      if (!existingStore)
      {
        console.log(`[Pinia]: skipping HMR of "${id}" because store doesn't exist yet`);
        return;
      }
      newModule(pinia, existingStore);
    }
  };
}

@vpiskunov
Copy link

@tmcdos great to see this used, and hope it helped! Wanted to check, you mentioned using it in non native Vue - do you mean not in NativeScript, and if so where? Are you adopting this for “normal”/web Vue?

@tmcdos
Copy link

tmcdos commented Jul 29, 2024

@vpiskunov Thank you very much for your work! I was struggling for months without success to make the HMR working with Webpack.
Yes, I meant I am using your code in normal web Vue v3 projects - Quasar with Webpack, Pinia and WindiCSS. I inherited the project configured with Vite but Vite has problems with the debugging DX (developer experience) - sourcemaps do not provide access to the original *.vue files and I can not put breakpoints inside my original (unminified and untranspiled) code. The breakpoints are only possible inside the transpiled "modules" that are loaded by the browser. So I decided to trade-off the Vite speed for a better debugging experience with Webpack.
I am aware about RsPack but have not found free time to try it on a real project.
If you are interested, I can share several patches for various NPM packages which (the patches) I use in my projects through the https://www.npmjs.com/package/custompatch tool.
It takes too much effort to get these patches merged so I drag and update them with all my projects :)

@rbdk-dev
Copy link

Thanks a lot, but can you share/explain step 1.
I am quite far from ts( and can't catch it.
Seems without step 1 it's not working due
`ERROR in ./src/utils/piniaHmr.ts:14:8
TS2503: Cannot find namespace 'webpack'.
12 |
13 | export function usePiniaWebpackHotHMR(

14 | hot: webpack.Hot,
| ^^^^^^^
15 | store: StoreForHMR | StoreForHMR[]
16 | ) {
17 | hot.accept();

ERROR in ./src/utils/piniaHmr.ts:21:18
TS7006: Parameter 'data' implicitly has an 'any' type.
19 |
20 | stores.forEach((store) => {

21 | hot.dispose((data) => {
| ^^^^
22 | (data as any).initialUseStoreId = store.$id;
23 | });
24 | const piniaAccept = acceptWebpackHMRUpdate(store.$id, hot);

ERROR in ./src/utils/piniaHmr.ts:45:8
TS2503: Cannot find namespace 'webpack'.
43 | export function acceptWebpackHMRUpdate(
44 | initialUseStoreId: string,

45 | hot: webpack.Hot
| ^^^^^^^
46 | ) {
47 | // strip as much as possible from iife.prod
48 | if (!DEV) {

ERROR in ./src/utils/piniaHmr.ts:52:5
TS2578: Unused '@ts-expect-error' directive.
50 | }
51 | return (newModule: any) => {

52 | // @ts-expect-error
| ^^^^^^^^^^^^^^^^^^^
53 | const pinia: PiniaPlus | undefined = hot.data?.pinia || getActivePinia();
54 | if (!pinia) {
55 | // this store is still not used

Found 4 errors in 489 ms.`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment