How to cook Nuxt 3 modules
Published: 2022-02-08 11:45:50

**Ingredients**

1. 100gr of [Nuxt 3](https://v3.nuxtjs.org/)
2. 1 spoon of [@nuxt/kit](https://www.npmjs.com/package/@nuxt/kit)
3. 1L of [Nuxt Module Builder](https://github.com/nuxt/module-builder)
4. 100 tons of `node_modules`
5. 1 bowl of great ideas
6. 1 pinch of [Vitest](vitest.dev/) (optional)

**Preparation**

1) Start by pouring 1L of Nuxt Module Builder into a big bowl (this also works if you use a Terminal).

```bash
npx nuxi init -t module my-amazing-recipe
```

If you succeeded this simple but very important step, your folder structure should be nicely firm and moist. It should look like the image below; if not, you failed and should start over.

![Folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xyxz4h987v5k5asii3f6.png)

2) It is now time to add a 100 tons of `node_modules` to make sure your mixture doesn't end up too light. We want something really big and thick. Run this command that should do the trick.

```bash
yarn install # or npm install
```

3) The `runtime` folder is the place we're going to add most of our great ideas. If you bought your ingredients from Nuxt Module Builder as recommended, there should already be a `plugin.ts` file in it. Let's add our recipe there.

```ts
// ./src/runtime/plugin.ts

import { defineNuxtPlugin } from '#app'

export default defineNuxtPlugin(nuxtApp => {
   nuxtApp.provide('recipe', 'my-amazing-recipe')
})
```

4) Alright so now that we have `provided` this recipe to our dish, let's add a convenient way to read it.

```ts
// ./src/runtime/composables/useRecipe/index.ts

/**
 * Use the recipe.
 */
export function useRecipe () {
  const { $recipe } = useNuxtApp()

  return $recipe
}
```
 
But for our TypeScript lovers out there, you probably won't like that `any` type at all. So, let's spice things up a little bit.

```ts
// ./src/runtime/plugin.ts

import { defineNuxtPlugin } from '#app'

export default defineNuxtPlugin(() => ({
  provide: {
    recipe: 'my-amazing-recipe'
  }
}))
```

Go back to your composable and enjoy that sweet sweet `string` type.


5) At this point - and since you're an amazing cook - you should try to make sure that your dish tastes exactly as you'd `expect`. Unfortunately, at the time of writing, it's not yet super straight forward. I'm sure we'll get that nice and creamy `@nuxt/test-utils` ready for Nuxt 3 in no time but for the sake of our high quality meal, let's just pretend we had it already.

- Add some [vitest](https://vitest.dev) into the mix.
```bash
yarn add --dev vitest
```

- Immediately add a pinch of testing.
```ts
// ./src/runtime/composables/useRecipe/index.test.ts

import { describe, it, expect } from 'vitest'
import { useRecipe } from '.'

describe('useRecipe', () => {
  it('should show my recipe', () => {
    expect(useRecipe()).toBe('my-amazing-recipe')
  })
})
```

- Run this command and let it fail miserably (for now).
```bash
yarn vitest
```

6) Alright, we're almost done. The last step before putting our mixture into the oven is to ensure that our little `useRecipe` composable will evenly expand across all components. The technical cooking term is `auto-importing`. But some people like it better when they can choose where they want more flavour, so let's make it optional.

```typescript
// ./src/module.ts
import { resolve } from 'path'
import { fileURLToPath } from 'url'
import { defineNuxtModule, addPlugin, addAutoImportDir } from '@nuxt/kit'

export interface ModuleOptions {
  addPlugin: boolean;
  autoImport: boolean; // Add some type for your option.
}

export default defineNuxtModule<ModuleOptions>({
  ...,
  defaults: {
    addPlugin: true,
    autoImport: true // Set the default option to true.
  },
  setup (options, nuxt) {
    if (options.addPlugin) {
      const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
      nuxt.options.build.transpile.push(runtimeDir)
      addPlugin(resolve(runtimeDir, 'plugin'))

      if (options.autoImport) {
        // Use that magic helper to auto-import your composables.
        addAutoImportDir(resolve(runtimeDir, 'composables'))
      }
    }
  }
})
```

7) Great, I think we're all set. Let's put it in the oven.

```vue
// ./playground/app.vue
<script setup>
const { $recipe } = useNuxtApp()
</script>

<template>
  <div>
    {{ $recipe }}
  </div>
</template>
```

Let's set it to 180°C by running `yarn dev` for about a few seconds because it's running Vite and it's just way too fast, which kind of ruins this step for me. 

8) Let's head over to `http://localhost:3000` and see if our recipe really is as amazing as `my-amazing-recipe`. 

9) You're such a good cook. 🎉