Nuxt/docs/2.guide/3.going-further/3.modules.md
Rafael Magalhaes 99a47c1dd0
docs: improved going further modules documentation (#19219)
Co-authored-by: Rafael Magalhaes <rafael.magalhaes@yapily.com>
2023-02-22 05:45:22 -08:00

12 KiB

title description
Module Author Guide Learn how to create a Nuxt module.

Module Author Guide

A powerful configuration and hooks system makes it possible to customize almost every aspect of Nuxt Framework and add endless possible integrations when it comes to customization.

Nuxt provides a zero-config experience with a preset of integrations and best practices to develop Web applications. A powerful configuration and hooks system makes it possible to customize almost every aspect of Nuxt Framework and add endless possible integrations when it comes to customization. You can learn more about how Nuxt works in the Nuxt internals section.

Nuxt exposes a powerful API called Nuxt Modules. Nuxt modules are simple async functions that sequentially run when starting Nuxt in development mode using nuxi dev or building a project for production with nuxi build. Using Nuxt Modules, we can encapsulate, properly test and share custom solutions as npm packages without adding unnecessary boilerplate to the Nuxt project itself.

Nuxt Modules can hook into lifecycle events of Nuxt builder, provide runtime app templates, update the configuration or do any other custom action based on needs.

Quick Start

For the impatient ones, You can quickly start with module-builder and module starter template:

npx nuxi init -t module my-module

Starter template and module starter is a standard path of creating a Nuxt module.

Next steps:

  1. Open my-module in the IDE of your choice (Visual Studio Code is recommended)
  2. Install dependencies using the package manager of your choice (Yarn is recommended)
  3. Ensure local files are generated using npm run dev:prepare
  4. Start playground using npm run dev
  5. Follow this document to learn more about Nuxt modules

::alert{type=info icon=🚧} This is an under-the-progress guide. Please regularly check for updates. ::

Module Anatomy

A Nuxt module is a simple function accepting inline user options and nuxt arguments.

It is totally up to you, as the module author, how to handle the rest of the logic.

Starting with Nuxt 3, modules can benefit all Nuxt Kit utilities.

// modules/module.mjs
export default async (inlineOptions, nuxt) => {
  // You can do whatever you like here..
  console.log(inlineOptions.token) // `123`
  console.log(nuxt.options.dev) // `true` or `false`
  nuxt.hook('ready', async nuxt => {
    console.log('Nuxt is ready')
  })
}
export default defineNuxtConfig({
  modules: [
    // Using package name (recommended usage)
    '@nuxtjs/example',

    // Load a local module
    './modules/example',

    // Add module with inline-options
    ['./modules/example', { token: '123' }]

    // Inline module definition
    async (inlineOptions, nuxt) => { }
  ]
})

Defining Nuxt Modules

Creating Nuxt modules involves tedious and common tasks. Nuxt Kit, provides a convenient and standard API to define Nuxt modules using defineNuxtModule:

import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  meta: {
    // Usually  npm package name of your module
    name: '@nuxtjs/example',
    // The key in `nuxt.config` that holds your module options
    configKey: 'sample',
    // Compatibility constraints
    compatibility: {
      // Semver version of supported nuxt versions
      nuxt: '^3.0.0'
    }
  },
  // Default configuration options for your module
  defaults: {},
  hooks: {},
  async setup(moduleOptions, nuxt) {
    // -- Add your module logic here --
  }
})

The result of defineNuxtModule is a wrapper function with an (inlineOptions, nuxt) signature. It applies defaults and other necessary steps and calls the setup function when called.

defineNuxtModule features:

::list

  • Support defaults and meta.configKey for automatically merging module options
  • Type hints and automated type inference
  • Add shims for basic Nuxt 2 compatibility
  • Ensure module gets installed only once using a unique key computed from meta.name or meta.configKey
  • Automatically register Nuxt hooks
  • Automatically check for compatibility issues based on module meta
  • Expose getOptions and getMeta for internal usage of Nuxt
  • Ensuring backward and upward compatibility as long as the module is using defineNuxtModule from the latest version of @nuxt/kit
  • Integration with module builder tooling

::

Best Practices

Async Modules

Nuxt Modules can do asynchronous operations. For example, you may want to develop a module that needs fetching some API or calling an async function.

::alert{type="warning"} Be careful that nuxi dev waits for your module setup before going to the next module and starting the development server. Do time-consuming logic using deferred Nuxt hooks. ::

Always Prefix Exposed Interfaces

Nuxt Modules should provide an explicit prefix for any exposed configuration, plugin, API, composable, or component to avoid conflict with other modules and internals.

Ideally, you should prefix them with your module's name (e.g. if your module is called nuxt-foo, expose <FooButton> and useFooBar() and not <Button> and useBar()).

Be TypeScript Friendly

Nuxt 3, has first-class typescript integration for the best developer experience.

Exposing types and using typescript to develop modules can benefit users even when not using typescript directly.

Avoid CommonJS Syntax

Nuxt 3, relies on native ESM. Please read Native ES Modules for more information.

Modules Ecosystem

Nuxt tends to have a healthy and rich ecosystem of Nuxt modules and integrations. Here are some best practices if you want to jump in and contribute!

Document Module Usage

Consider documenting module usage in the readme file:

  • Why use this module
  • How to use this module
  • What this module does?

Linking to the integration website and documentation is always a good idea.

Use nuxt- Prefix for npm Packages

To make your modules discoverable, use nuxt- prefix for the npm package name. This is always the best starting point to draft and try an idea!

Listing Module

Do you have a working Module and want it listed? Open an issue in nuxt/modules repository. Nuxt team can help you to apply best practices before listing.

Do Not Advertize With a Specific Nuxt Version

Nuxt 3, Nuxt Kit, and other new toolings are made to have both forward and backward compatibility in mind.

Please use "X for Nuxt" instead of "X for Nuxt 3" to avoid fragmentation in the ecosystem and prefer using meta.compatibility to set Nuxt version constraints.

Joining nuxt-community

By moving your modules to nuxt-community, there is always someone else to help, and this way, we can join forces to make one perfect solution.

If you have an already published and working module and want to transfer it to nuxt-community, open an issue in nuxt/modules.

Testing

@nuxt/test-utils

Fixture Setup

To test the modules we create, we could set up some Nuxt apps as fixtures and test their behaviors. For example, we can create a simple Nuxt app under ./test/fixture with the configuration like:

// nuxt.config.js
import MyModule from '../../src'

export default defineNuxtConfig({
  modules: [
    MyModule
  ]
})

Tests Setup

We can create a test file and use the rootDir to test the fixture.

// basic.test.js
import { describe, it, expect } from 'vitest'
import { fileURLToPath } from 'node:url'
import { setup, $fetch } from '@nuxt/test-utils'

describe('ssr', async () => {
  await setup({
    rootDir: fileURLToPath(new URL('./fixture', import.meta.url)),
  })

  it('renders the index page', async () => {
    // Get response to a server-rendered page with `$fetch`.
    const html = await $fetch('/')

    expect(html).toContain('<a>A Link</a>')
  })
})

For more usage, please refer to our tests for Nuxt 3 framework.

Mock utils

  • mockFn(): Returns a mocked function based on test runner.
  • mockLogger(): Mocks logger using consola.mockTypes and mockFn(). Returns an object of mocked logger types.

Testing Externally

If you wish to test your module outside of the module playground before publishing to npm, you can use npm pack command, or your package manager equivalent, to create a tarball from your module. Then in your test project, you can add your module to package.json packages as: "nuxt-module-name": "file:/path/to/tarball.tgz".

After that, you should be able to reference nuxt-module-name like in any regular project.

Examples

Provide Nuxt Plugins

Commonly, modules provide one or more run plugins to add runtime logic.

import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit'

export default defineNuxtModule<ModuleOptions>({
  setup (options, nuxt) {
    // Create resolver to resolve relative paths
    const { resolve } = createResolver(import.meta.url)

    addPlugin(resolve('./runtime/plugin'))
  }
})

::ReadMore{link="/docs/api/advanced/kit" title="API > Advanced > Kit"} ::

Add a CSS Library

If your module will provide a CSS library, make sure to check if the user already included the library to avoid duplicates and add an option to disable the CSS library in the module.

import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    nuxt.options.css.push('font-awesome/css/font-awesome.css')
  }
})

Adding Vue Components

If your module should provide Vue components, you can use the addComponent utility to add them as auto-imports for Nuxt to resolve.

import { defineNuxtModule, addComponent } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options, nuxt) {
    addComponent({
      name: 'MyComponent', // name of the component to be used in vue templates
      export: 'MyAwesomeComponent', // (optional) if the component is a named (rather than default) export
      // filePath should be package name or resolved path
      // if the component is created locally, preferably in `runtime` dir
      filePath: '@vue/awesome-components' // resolve(runtimeDir, 'components', 'MyComponent.vue')
    })
  }
})

Adding Composables

If your module should provide composables, you can use the addImports utility to add them as auto-imports for Nuxt to resolve.

import { defineNuxtModule, addImports, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options, nuxt) {
    const resolver = createResolver(import.meta.url)
    addImports({
      name: 'useComposable', // name of the composable to be used
      as: 'useComposable', 
      from: resolver.resolve('runtime/composables/useComposable') // path of composable 
    })
  }
})

Alternatively, you can add an entire directory by using addImportsDir.

import { defineNuxtModule, addImportsDir, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options, nuxt) {
    const resolver = createResolver(import.meta.url)
    addImportsDir(resolver.resolve('runtime/composables'))
  }
})

::alert Note that auto-imports are not enabled for files within node_modules for performance reasons, so the module starter deliberately disables them while developing a module. If you are using the module starter, auto-imports will not be enabled in your playground either. ::

Clean Up Module

If your module opens, handles or starts a watcher, you should close it when the Nuxt lifecycle is done. For this, use the close hook:

import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    nuxt.hook('close', async nuxt => {
      // Your custom code here
    })
  }
})