Vue Exit Intent & How to Publish on NPM | Vue
Tags: vue, webdev, javascript, tutorial
Published: 2022-11-11 20:04:42

_update:
The package is refactored, and it's now a composable instead of a component.
This guide is about the old "component version"_
_You can check the composable_ [here](https://dev.to/nickap/use-vue-exit-intent-a-vue-composable-to-power-your-exit-intent-content-4hlh)

## Vue Exit Intent
Let's create an exit intent component, and then, see how we can publish it to NPM.

We will:
- Make use of the new [`<script setup>`](https://vuejs.org/api/sfc-script-setup.html) API to build our component.
- Publish on NPM.
- Check our package with [NPM LINK](https://docs.npmjs.com/cli/v8/commands/npm-link)

Visit the repo [here](https://github.com/nickap/vue-exit-intent).
Component file [here](https://github.com/nickap/vue-exit-intent/blob/2d79671ef1bdf447dba9e389f15191bee41fc23f/src/components/vueExitIntent.vue).


First...
##   ...Let's create our new project

`npm init vue@latest`
This command will install and execute [create-vue](https://github.com/vuejs/create-vue)
The recommended way to start a Vite-powered Vue project.

Let's name our project **vue-exit-intent** and select default answers (Answer No) to anything else, so we can focus on our component. 
_If you wish to work on this component even further you can make your choises or at least accept Prettier and ESLint as they are two necessary tools._

So you will end up with something like this:
```
✔ Project name: … vue-exit-intent
✔ Add TypeScript? … No
✔ Add JSX Support? … No
✔ Add Vue Router for Single Page Application development? … No
✔ Add Pinia for state management? … No
✔ Add Vitest for Unit Testing? … No
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … Yes
✔ Add Prettier for code formatting? … Yes
```
Now, 
`cd vue-exit-intent` to your project directory, and run: 
`npm install` to install all the dependencies according to package.json, and then run: 
`npm run dev`. 
A simple Vue app will be exposed in your localhost. Follow the link to see it.

Now, let's create a new component with the name 'vueExitIntent.vue' under the /src/components directory.

![files in vscode editor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i5z4mjimpb0nysgdw7i4.png)

Delete the components that are selected in the picture.
No need to have them in our app.

Let's edit our **vueExitIntent.vue** component to this:
```vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

/**
 * This is exposed to our template cause we use <script setup/>.
 * So, we can use the value of this reference in our template.
 */
const show = ref(false);

onMounted(() => {
  /**
   * onMounted we attach a mouseleave Event Listener to the documentElement
   * */
  document.documentElement.addEventListener('mouseleave', handleMouseLeave);
});

onUnmounted(() => {
  /**
   * onUnmounted this hook will run as a callback.
   * It's the perfect time to remove our Event Listener.
   */
  document.documentElement.removeEventListener('mouseleave', handleMouseLeave);
});

const handleMouseLeave = () => {
  showModal();
};

/**
 * Remember? closeModal() is available to our template.
 * No need to export it like in the first version of Composition API because of <script setup>
 * */
const closeModal = () => {
  show.value = false;
  document.body.style.overflowY = 'auto';
};

const showModal = () => {
  show.value = true;
  document.body.style.overflowY = 'hidden';
};
</script>

<template>
  <div class="exit-intent-backdrop" v-show="show">
    <div class="exit-intent-content">
      <!-- Show an X button to close the Pop-up  -->
      <button
        type="button"
        class="btn-close"
        aria-label="Close"
        @click="closeModal()"
      >
        &#x2715;
      </button>
      <!-- We use slots to show the content of the Pop-up -->
      <slot>
        <!-- For the needs of this tutorial.. -->
        <!-- Let's create a fallback content -->
        <div class="fallback-content">
          <h2 class="fallback-content-header">
            Subscribe and get a 10% discount!
          </h2>
        </div>
      </slot>
    </div>
  </div>
</template>

<style scoped>
.exit-intent-backdrop {
  display: flex;
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: rgba(0, 0, 0, 0.7);
  z-index: 999;
}
.exit-intent-content {
  position: relative;
  margin: auto;
  color: #555;
  background-color: #fefefe;
}

.btn-close {
  position: absolute;
  right: 0;
  top: 0;
  border: none;
  font-size: 20px;
  padding: 4px;
  cursor: pointer;
  font-weight: bold;
  color: #555;
  background-color: transparent;
  z-index: 999;
}

.fallback-content {
  background-color: #fefefe;
  overflow-x: auto;
  display: flex;
  flex-direction: column;
  margin: 0px;
}

.fallback-content-header {
  margin: 0;
  padding: 25px 10px;
  text-align: center;
  border-bottom: 1px solid #eee;
  color: #555;
}
</style>

```

Now we also have to change the code in our **src/App.vue** file.
Remove the unneeded code (components we deleted in the previous step), and use our newly created component.
So you will end up to something like this:
```vue
<template>
  <vue-exit-intent></vue-exit-intent>
</template>

<script setup>
import vueExitIntent from "./components/vueExitIntent.vue";
</script>
```
So simple with `<script setup>`.

Done! We have the basic functionality of an Exit Intent Pop-up.
You can add your content or another child component **in between the opening and closing tags**, this way you will make use of slots, and you will not get the fallback content
`<vue-exit-intent>Your Content Here</vue-exit-intent>`
or even better
`<vue-exit-intent><create-and-add-a-new-component-here/></vue-exit-intent>`

## Take it to another level
Of course, showing the pop-up continuously in every mouseleave Event is at least annoying.

So, how to make it even better.
- We can think of using **LocalStorage** to add a key when the popup is showed, and check if that key exists next time the user will visit your page.
- Add a **timestamp** as the value of the LS key, so you can check next time and show the popup if more than 7-days passed from last time.
- Add a **Scroll Event Listener** and show the popup if the user scrolled to let's say 40% of the page-height.

I actually added this functionality in my file, and you can grab it from [here](https://github.com/nickap/vue-exit-intent/blob/2d79671ef1bdf447dba9e389f15191bee41fc23f/src/components/vueExitIntent.vue)

Feel free to fork and contribute [repo](https://github.com/nickap/vue-exit-intent/).

## Package and Publish on NPM

We are going to build this component library so to be used as a global component.

To create our Vue 3 plugin, we need to create a new **src/index.js** file:
```vue
import vueExitIntent from './components/vueExitIntent.vue';

export default {
   /**
   * We need an install step
   */
  install: (app, options) => {
    /**
     * 1st argument is the name of our component.
     * 2nd argument is our actuall component we imported at the top of the file.
     */
    app.component('vueExitIntent', vueExitIntent);
  }
};

```

And also, we have to make some changes on **vite's configuration** file. Let's define what we want to happen when vite builds:
```javascript
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';

/* https://vitejs.dev/config/ */
export default defineConfig({
  /* Define a build Property */
  build: {
    /* We want to create a library */
    lib: {
      /**
       * 1st argument is our directory name.
       * 2nd is the file we defined the install
       * */
      entry: resolve(__dirname, 'src/index.js'),
      /* Name of our library */
      name: 'vue-exit-intent',
      /**
       * 'format' will be 'es' and 'umd'.
       * So, we will create 2 files for the user to use either with 'import' (ES Modules) or 'require' (CommonJS Format)
       */
      fileName: (format) => `vue-exit-intent.${format}.js`
    },
    /* Options for Rollup*/
    rollupOptions: {
      /* Externalize vue so its won't get bundled in our library */
      external: ['vue'],
      output: {
        globals: {
          /* Global variable to use in our .umd build */
          vue: 'Vue'
        }
      }
    }
  },
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
});

```


Finally, let's add some configuration to our **package.json**:
- Key 'files' shows where vite will export our bundled files.
- 'main' is for the .umd export we just configured in vite.
- 'module' is for the .es export.
- In key 'exports' we define the entrypoints for our app.
- 'import' and 'require' will resolve to the path in the right. 
```
  "files": [
    "dist"
  ],
  "main": "./dist/vue-exit-intent.umd.js",
  "module": "./dist/vue-exit-intent.es.js",
  "exports": {
    ".": {
      "import": "./dist/vue-exit-intent.es.js",
      "require": "./dist/vue-exit-intent.umd.js"
    },
    "./dist/style.css": "./dist/style.css"
  },
```

**All done!** 
Run `npm run build` and you will have a new **/dist directory** with the files that vite built.
These are the files we will share through NPM.

If you are sure you have tested your app, you can login to npm running
`npm login`
and after you login run: 
`npm publish` to publish your app.

and... **voila** our package is [published](https://www.npmjs.com/package/vue-exit-intent)

## NPM LINK
I couldn't resist to not mention [npm link](https://docs.npmjs.com/cli/v8/commands/npm-link)

It is a very handy tool that creates a symlink to your global folder `/lib/node_modules/` that links to the package that the `npm link` command executed.
You can try and run `npm link` while you are in your **vue-exit-intent** project.
Now, you can go and create a different project **PROJECT2** and run `npm link vue-exit-intent`

Remember to unlink when you are done!

Now you will have your package **vue-exit-intent** installed in **PROJECT2**, and you can test it before publishing.
Remember that, to use your component, you need to import it in **PROJECT2** **main.js** like this:
```javascript
import { createApp } from "vue";
import App from "./App.vue";
import "./assets/main.css";


import vueExitIntent from "vue-exit-intent";
import 'vue-exit-intent/dist/style.css';


const app = createApp(App);

app.use(vueExitIntent);

app.mount("#app");
```

Thanks for reading this!