How to develop a local package and use in a vite app
Tags: vite, webdev, react
Published: 2024-04-18 11:35:17

This might be a niche topic, but recently I had to link and develop a shared library that is not part of the same repo as the main app codebase. And there is no monorepo involved either. 

On the web the resources to do this are scattered and don't cover hot reloading.

## The problem

if you ever came across such a scenario, in such cases the usual workflow is develop the package in isolation, build and release. Then go to the app and update the version number so you can bring the changes in.

BUT, wouldn't it be cool to be able to develop the shared package side by side with live reloading and reflect the changes in the main app, make sure everything is working as expected and THEN release ?

In this article, we are gonna do just that.

## Setup

The setup for the purposes of this tutorial is the following:

1. Package boilerplate generated using [tsdx](https://tsdx.io/) 
2. Basic vite react app with typescript
3. Using yarn

## Shared package

Let's call it @awesome/package (do that by changing your package.json name).

The changes needed here are very few. The main thing to know is that Vite uses ESM and needs to know exactly where to look for the root file so lets help it

in `package.json`

```
"exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/shared.esm.js"
      },
      "require": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      }
    },
  },
```

So what we are saying above is the following. 

If you are an ESM consumer (import) use the these types and this is the entry point.

If you are a commonJS consumer (require) use these types and this is the entry point.

Now vite knows what to look for, great!

## Vite setup

in `vite.config.ts`

```
import {defineConfig, ViteDevServer} from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react()],
    optimizeDeps: {
        exclude: ["@awesome/shared"]
    },
})
```

By default, Vite likes to optimize deps but in our case since its an ESM lib we want to skip that.

Great, now vite knows about our package.

## Yarn setup

Now we need to do two things.

Install the package as a local dependency.

### In shared package

The first step is to make sure yarn knows where to look for the package.

```
yarn link
```

### In main app

```
yarn add link:./../your-project // here adjust to be the relative path to your project. In case you use pnpm or npm you need to adjust this accordingly - most probably use file: in front.
```

If you already released and use the package with a specific version. e.g in package.json you already have the package as a dependency like so - `"@awesome/shared": "0.0.1"` then do the following:

```
yarn link @awesome/shared
```

At this point, we have made sure vite is aware of the package, and our package manager, in this case yarn - has made the necessary linking.

## Development

In shared package do a change that is meaningful and can be easily observed in the vite app.

Afterwards, 

In `shared package`
```
yarn start // if you used tsdx to scaffold or else use the equivalent command with your setup
```

In `vite app`

```
yarn dev
```

You will notice that the shared package change you made is reflected in your vite app. 

However, you will notice that making a second change to the package it is not being reflected to the vite app.

Thats because, Vite by design does not watch changes in `node_modules` and because does caching of the dependencies under `node_modules/.vite` directory.

The suggestion is to run the following command

```
yarn dev --force // underneath its vite dev --force
```

This is too manual for our liking, isn't it?

So, let's see how to fix that.

## Adding a plugin to watch specific packages for changes

We need to add a plugin in order to watch for our packages, so here is the final `vite.config.ts`


```
import {defineConfig, ViteDevServer} from 'vite'
import react from '@vitejs/plugin-react'

// This plugin will watch node_modules for changes and trigger a reload.
// Works only for build --watch mode.
// https://github.com/vitejs/vite/issues/8619
export function pluginWatchNodeModules(modules: string[]) {
    // Merge module into pipe separated string for RegExp() below.
    const pattern = `/node_modules\\/(?!${modules.join('|')}).*/`;
    return {
        name: 'watch-node-modules',
        configureServer: (server: ViteDevServer): void => {
            server.watcher.options = {
                ...server.watcher.options,
                ignored: [
                    new RegExp(pattern),
                    '**/.git/**',
                ]
            }
        }
    }
}

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react(), pluginWatchNodeModules(['@awesome/shared'])],
    optimizeDeps: {
        exclude: ["@awesome/shared"]
    },
})
```

Start the vite app again and do changes to the shared package. They should be reflected to vite app almost instantly.

## Bonus

Now that you are able to develop and test your package, you need to unlink the local package and use the version you used when releasing the package with the changes

```
yarn unlink @awesome/shared 
```

Now update `package.json` (in vite app) with the latest release version and run `yarn` to install it.

## Conclusion

You have learned how you can easily make changes to a shared package and they are then reflected to your vite app. Now you can be sure the changes work before releasing the shared package.

I hope you found this useful and if you want to keep in touch you can always find me on [Twitter](https://twitter.com/9pitops).