'@meforma/vue-toaster clear function only works when pulled into local project folder, not from node_modules

As stated, the package @meforma/vue-toaster has a clear() function in the API here.

My vue and vite project setup:

main.js

import Toaster from '@meforma/vue-toaster'

app.use(Toaster, {
  position: 'top',
  useDefaultCss: false,
  pauseOnHover: false
}).provide('toast', app.config.globalProperties.$toast)

Example.vue

<template>
  <div class="view">
    <div class="mt-10 flex gap-5">
      <button class="btn btn-stealth" @click="showToast('default')">Default</button>
      <button class="btn btn-primary" @click="showToast('primary')">Primary</button>
      <button class="btn btn-success" @click="showToast('success')">Success</button>
      <button class="btn btn-info" @click="showToast('info')">Info</button>
      <button class="btn btn-warning" @click="showToast('warning')">Warning</button>
      <button class="btn btn-danger" @click="showToast('error')">Error</button>
    </div>

    <div class="mt-10">
      <button class="btn" @click="toast.clear">Clear Toasts</button>
      <br><br>
      <button class="btn" @click="clearToasts">Timeout Clear</button>
    </div>
  </div>
</template>

<script setup>
import { inject } from 'vue'

const toast = inject('toast')

// this works
function showToast (type) {
  toast.show(`This is the ${type} type toast.`, {
    type: type,
    duration: false
  })
}

// only works when vue-toaster is local in my projects src folder :S
function clearToasts () {
  toast.clear()
}
</script>

package.json

{
  "name": "test-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "vite --open",
    "build": "vite build --out-dir dist",
    "lint": "eslint src"
  },
  "dependencies": {
    "@meforma/vue-toaster": "^1.3.0",
    "@vitejs/plugin-vue": "^2.2.4",
    "animate.css": "^4.1.1",
    "axios": "^0.26.0",
    "axios-auth-refresh": "^3.2.2",
    "core-js": "^3.21.1",
    "microtip": "^0.2.2",
    "pinia": "^2.0.13",
    "pinia-plugin-persistedstate": "^1.5.1",
    "sass": "^1.49.9",
    "vue": "^3.2.31",
    "vue-meta": "^3.0.0-alpha.10",
    "vue-router": "^4.0.13"
  },
  "devDependencies": {
    "@tailwindcss/aspect-ratio": "^0.4.0",
    "@tailwindcss/forms": "^0.5.0",
    "@tailwindcss/line-clamp": "^0.3.1",
    "@tailwindcss/typography": "^0.5.2",
    "@vue/compiler-sfc": "^3.2.31",
    "@vue/eslint-config-standard": "^6.1.0",
    "autoprefixer": "^10.4.2",
    "eslint": "^8.12.0",
    "eslint-plugin-import": "^2.25.4",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^6.0.0",
    "eslint-plugin-standard": "^4.1.0",
    "eslint-plugin-vue": "^8.6.0",
    "postcss": "^8.4.7",
    "tailwindcss": "^3.0.23",
    "vite": "^2.8.6"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/vue3-essential",
      "@vue/standard"
    ],
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

But, when I pull the package into the local src folder and import it via there, the clear works perfectly. No code changes other than moving where the code lives.

I have absolutely no idea why this is the case, if anyone has ever seen anything similar, it would be greatly appreciated.

I looked through all the code of the project github and created an issue there.

I thought it might be something to do with the event bus, but then again, it works when pulled from node_modules so hence why this is such a head scratcher for me.

Thanks!


Edit 1:

Updated code example that you can run locally to experience the same issue: https://codesandbox.io/s/vue3-toaster-test-forked-m64htx?file=/src/components/HelloWorld.vue:854-868

Specifically in the main.js, swapping where the vue-toaster is pulled in from causes the clear function to stop/start working. Clearing works when vue-toaster is located within my project /src but fails to work when pulled in from node_modules.

Edit 2:

Here is a stackblitz showing the same issue (using vite too): https://stackblitz.com/edit/vitejs-vite-uqcdgd?file=src%2Fmain.js,src%2FApp.vue



Solution 1:[1]

So after forking @meforma/vue-toaster, rewriting the Toaster in the composition API and changing the event bus to use mitt, even went as far as to publish it to npmjs so I could replicate everything as close as possible to how I use the package originally.... it turns out that my re-written package was having the exact same issue with Vite.

Digging in further, I noticed in my /node_modules/ there was a folder: /node_modules/.vite/deps/ which had files like: @shanehoban_vue-toaster.js... so I started googling and found out that perhaps I should exclude the package from Vite optimizations... like so:

vite.config.js

export default defineConfig({
  optimizeDeps: {
    exclude: [
      "@meforma/vue-toaster"
    ]
  },
  ....

And now, the clear... is working.

Solution 2:[2]

The original answer is a misunderstanding. So I edited it to follow the point of the question.

Problem

  • The package does NOT work when using it from node_modules.
  • The package does work when folks it into the project and use it directly

Reason

Vite only caches JS files from the package. We can see it in node_modules/.vite/deps/@meforma_vue-toaster.js

// The .vue file is point to original file
// node_modules/@meforma/vue-toaster/src/index.js
import Toaster2 from "/Users/admin/Work/test-projects/vite-demo-vue-plugin/node_modules/@meforma/vue-toaster/src/Toaster.vue";

// node_modules/@meforma/vue-toaster/src/api.js
import Toaster from "/Users/admin/Work/test-projects/vite-demo-vue-plugin/node_modules/@meforma/vue-toaster/src/Toaster.vue";

// The js file is cached here so Singleton pattern will breaks
// node_modules/@meforma/vue-toaster/src/helpers/event-bus.js
var Event = class {
  constructor() {
    this.queue = {};
  }
...
}

The event-bus.js is cached in the .vite folder but the Toaster.vue file is not. So when the Toaster.vue uses the EventBus, it will call the instance from node_modules/@meforma/vue-toaster/ instead of the one in the .vite cached folder. And it breaks the Singleton pattern of the original package.

This bug might happen to any package with uncachable files mixed with JS files.

Workaround

This bug can be fixed only from the Vite side. So before Vite fixes it we need to exclude the package from the Vite cache as shanehoban's answer points out:

// vite.config.js

export default defineConfig({
  optimizeDeps: {
    exclude: [
      "@meforma/vue-toaster"
    ]
  },
  ....

Github issue

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 shanehoban
Solution 2