docs: update module author guide (#18551)

Co-authored-by: lihbr <lihbr@users.noreply.github.com>
This commit is contained in:
Sébastien Chopin 2023-03-15 13:50:10 +01:00 committed by GitHub
parent 98b9afa6d7
commit e383d06bf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 451 additions and 162 deletions

View File

@ -1,53 +1,123 @@
---
title: "Module Author Guide"
description: "Learn how to create a Nuxt module."
description: "Learn how to create a Nuxt Module to integrate, enhance or extend any Nuxt applications."
image: '/socials/module-author-guide.jpg'
---
# 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.
Learn how to create a Nuxt Module to integrate, enhance or extend any Nuxt applications.
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](/docs/guide/going-further/internals) section.
Nuxt's [configuration](/docs/api/configuration/nuxt-config) and [hooks](/docs/guide/going-further/hooks) systems make it possible to customize every aspect of Nuxt and add any integration you might need (Vue plugins, CMS, server routes, components, logging, etc.).
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.
**Nuxt Modules** are functions that sequentially run when starting Nuxt in development mode using `nuxi dev` or building a project for production with `nuxi build`.
With modules, you can encapsulate, properly test, and share custom solutions as npm packages without adding unnecessary boilerplate to your project, or requiring changes to Nuxt itself.
## Quick Start
For the impatient ones, You can quickly start with [module-builder](https://github.com/nuxt/module-builder) and [module starter template](https://github.com/nuxt/starter/tree/module):
We recommend you get started with Nuxt Modules using our [starter template](https://github.com/nuxt/starter/tree/module):
```bash
npx nuxi init -t module my-module
```
Starter template and module starter is a standard path of creating a Nuxt module.
This will create a `my-module` project with all the boilerplate necessary to develop and publish your 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
1. Open `my-module` in your IDE of choice
2. Install dependencies using your favorite package manager
3. Prepare local files for development using `npm run dev:prepare`
4. 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.
### Using the Starter
Learn how to perform basic tasks with the module starter.
#### How to Develop
While your module source code lives inside the `src` directory, in most cases, to develop a module, you need a Nuxt application. That's what the `playground` directory is about. It's a Nuxt application you can tinker with that is already configured to run with your module.
You can interact with the playground like with any Nuxt application.
- Launch its development server with `npm run dev`, it should reload itself as you make changes to your module in the `src` directory
- Build it with `npm run dev:build`
::alert{type=info}
All other `nuxi` commands can be used against the `playground` directory (e.g. `nuxi <COMMAND> playground`). Feel free to declare additional `dev:*` scripts within your `package.json` referencing them for convenience.
::
## Module Anatomy
#### How to Test
A Nuxt module is a simple function accepting inline user options and `nuxt` arguments.
The module starter comes with a basic test suite:
It is totally up to you, as the module author, how to handle the rest of the logic.
- A linter powered by [ESLint](https://eslint.org), run it with `npm run lint`
- A test runner powered by [Vitest](https://vitest.dev), run it with `npm run test` or `npm run test:watch`
Starting with Nuxt 3, modules can benefit all [Nuxt Kit](/docs/api/advanced/kit) utilities.
::alert{type=info}
Feel free to augment this default test strategy to better suit your needs.
::
```ts [modules/example.ts]
// modules/module.mjs
export default async (inlineOptions, nuxt) => {
#### How to Build
Nuxt Modules come with their own builder provided by [`@nuxt/module-builder`](https://github.com/nuxt/module-builder#readme). This builder doesn't require any configuration on your end, supports TypeScript, and makes sure your assets are properly bundled to be distributed to other Nuxt applications.
You can build your module by running `npm run prepack`.
::alert{type=info}
While building your module can be useful in some cases, most of the time you won't need to build it on your own: the `playground` takes care of it while developing, and the release script also has you covered when publishing.
::
#### How to Publish
::alert{type=warning}
Before publishing your module to npm, makes sure you have an [npmjs.com](https://www.npmjs.com) account and that you're authenticated to it locally with `npm login`.
::
While you can publish your module by bumping its version and using the `npm publish` command, the module starter comes with a release script that helps you make sure you publish a working version of your module to npm and more.
To use the release script, first, commit all your changes (we recommend you follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) to also take advantage of automatic version bump and changelog update), then run the release script with `npm run release`.
When running the release script, the following will happen:
- First, it will run your test suite by:
- Running the linter (`npm run lint`)
- Running unit, integration, and e2e tests (`npm run test`)
- Building the module (`npm run prepack`)
- Then, if your test suite went well, it will proceed to publish your module by:
- Bumping your module version and generating a changelog according to your Conventional Commits
- Publishing the module to npm (for that purpose, the module will be built again to ensure its updated version number is taken into account in the published artifact)
- Pushing a git tag representing the newly published version to your git remote origin
::alert{type=info}
As with other scripts, feel free to fine-tune the default `release` script in your `package.json` to better suit your need.
::
## Developing Modules
Nuxt Modules come with a variety of powerful APIs and patterns allowing them to alter a Nuxt application in pretty much any way possible. This section teaches you how to take advantage of those.
### Module Anatomy
We can consider two kinds of Nuxt Modules:
- published modules are distributed on npm - you can see a list of some community modules on [the Nuxt website](/modules).
- "local" modules, they exist within a Nuxt project itself, either [inlined in Nuxt config](/docs/api/configuration/nuxt-config#modules) or as part of [the `modules` directory](/docs/guide/directory-structure/modules).
In either case, their anatomy is similar.
#### Module Definition
::alert{type=info}
When using the starter, your module definition is available at `src/module.ts`.
::
The module definition is the entry point of your module. It's what gets loaded by Nuxt when your module is referenced within a Nuxt configuration.
At a low level, a Nuxt Module definition is a simple, potentially asynchronous, function accepting inline user options and a `nuxt` object to interact with Nuxt.
```js
export default function (inlineOptions, nuxt) {
// You can do whatever you like here..
console.log(inlineOptions.token) // `123`
console.log(nuxt.options.dev) // `true` or `false`
@ -57,34 +127,16 @@ export default async (inlineOptions, nuxt) => {
}
```
```ts [nuxt.config]
export default defineNuxtConfig({
modules: [
// Using package name (recommended usage)
'@nuxtjs/example',
Outside of short inline modules defined in `nuxt.config.ts`, **we do not recommend** using this low-level function definition. Instead, to define a module, **we recommend** using the higher-level `defineNuxtModule` helper provided by [Nuxt Kit](/docs/api/advanced/kit).
// 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](/docs/api/advanced/kit), provides a convenient and standard API to define Nuxt modules using `defineNuxtModule`:
This helper makes writing Nuxt Module more straightforward by implementing many common patterns seen in modules, guaranteeing future compatibility, and improving your module author developer experience and the one of your module users.
```js
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
meta: {
// Usually npm package name of your module
// Usually the npm package name of your module
name: '@nuxtjs/example',
// The key in `nuxt.config` that holds your module options
configKey: 'sample',
@ -94,21 +146,20 @@ export default defineNuxtModule({
nuxt: '^3.0.0'
}
},
// Default configuration options for your module
// Default configuration options for your module, can also be a function returning those
defaults: {},
// Shorthand sugar to register Nuxt hooks
hooks: {},
async setup(moduleOptions, nuxt) {
// -- Add your module logic here --
// The function holding your module logic, it can be asynchronous
setup(moduleOptions, nuxt) {
// ...
}
})
```
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:**
Ultimately `defineNuxtModule` returns a wrapper function with the lower level `(inlineOptions, nuxt)` module signature. This wrapper function applies defaults and other necessary steps before calling your `setup` function:
::list
- Support `defaults` and `meta.configKey` for automatically merging module options
- Type hints and automated type inference
- Add shims for basic Nuxt 2 compatibility
@ -118,136 +169,127 @@ The result of `defineNuxtModule` is a wrapper function with an `(inlineOptions,
- 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
#### Runtime Directory
### 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.
::alert{type=info}
When using the starter, the runtime directory is available at `src/runtime`.
::
### Always Prefix Exposed Interfaces
Modules, like everything in a Nuxt configuration, aren't included in your application runtime. However, you might want your module to provide, or inject runtime code to the application it's installed on. That's what the runtime directory enables you to do.
Nuxt Modules should provide an explicit prefix for any exposed configuration, plugin, API, composable, or component to avoid conflict with other modules and internals.
Inside the runtime directory, you can provide any kind of assets related to the Nuxt App:
- Vue components
- Composables
- [Nuxt plugins](/docs/guide/directory-structure/plugins)
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()`).
To the [server engine](/docs/guide/concepts/server-engine), Nitro:
- API routes
- Middlewares
- Nitro plugins
### Be TypeScript Friendly
Or any other kind of asset you want to inject in users' Nuxt applications:
- Stylesheets
- 3D models
- Images
- etc.
Nuxt 3, has first-class typescript integration for the best developer experience.
You'll then be able to inject all those assets inside the application from your [module definition](#module-definition).
Exposing types and using typescript to develop modules can benefit users even when not using typescript directly.
::alert{type=info}
Learn more about asset injection in [the recipes section](#recipes).
::
### Avoid CommonJS Syntax
::alert{type=warning}
Published modules cannot leverage auto-imports for assets within their runtime directory. Instead, they have to import them explicitly from `#imports` or alike.
Nuxt 3, relies on native ESM. Please read [Native ES Modules](/docs/guide/concepts/esm) for more information.
Indeed, auto-imports are not enabled for files within `node_modules` (the location where a published module will eventually live) for performance reasons. That's why the module starter [deliberately disables them](https://github.com/nuxt/starter/blob/module/.nuxtrc#L1) while developing a module.
## Modules Ecosystem
If you are using the module starter, auto-imports will not be enabled in your playground either.
::
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!
### Tooling
### Document Module Usage
Modules come with a set of first-party tools to help you with their development.
Consider documenting module usage in the readme file:
#### `@nuxt/module-builder`
- Why use this module
- How to use this module
- What this module does?
[Nuxt Module Builder](https://github.com/nuxt/module-builder#readme) is a zero-configuration build tool taking care of all the heavy lifting to build and ship your module. It ensures proper compatibility of your module build artifact with Nuxt applications.
Linking to the integration website and documentation is always a good idea.
#### `@nuxt/kit`
### Use `nuxt-` Prefix for npm Packages
[Nuxt Kit](/docs/guide/going-further/kit) provides composable utilities to help your module interact with Nuxt applications. It's recommended to use Nuxt Kit utilities over manual alternatives whenever possible to ensure better compatibility and code readability of your module.
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!
:ReadMore{link="/docs/api/advanced/kit" title="API > Advanced > Kit"}
### Listing Module
#### `@nuxt/test-utils`
Do you have a working Module and want it [listed](/modules)? Open an issue in [nuxt/modules](https://github.com/nuxt/modules/issues/new) repository.
Nuxt team can help you to apply best practices before listing.
[Nuxt Test Utils](/docs/getting-started/testing) is a collection of utilities to help set up and run Nuxt applications within your module tests.
### Do Not Advertize With a Specific Nuxt Version
### Recipes
Nuxt 3, Nuxt Kit, and other new toolings are made to have both forward and backward compatibility in mind.
Find here common patterns used to author modules.
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.
#### Altering Nuxt Configuration
### Joining nuxt-community
Nuxt configuration can be read and altered by modules. Here's an example of a module enabling an experimental feature.
By moving your modules to [nuxt-community](https://github.com/nuxt-community), there is always someone else to help, and this way, we can join forces to make one perfect solution.
```js
import { defineNuxtModule } from '@nuxt/kit'
If you have an already published and working module and want to transfer it to nuxt-community, open an issue in [nuxt/modules](https://github.com/nuxt/modules/issues/new).
## 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:
```ts
// nuxt.config.js
import MyModule from '../../src'
export default defineNuxtConfig({
modules: [
MyModule
]
export default defineNuxtModule({
setup (options, nuxt) {
// We create the `experimental` object if it doesn't exist yet
nuxt.options.experimental ||= {}
nuxt.options.experimental.componentIslands = true
}
})
```
#### Tests Setup
When you need to handle more complex configuration alterations, you should consider using [defu](https://github.com/unjs/defu).
We can create a test file and use the `rootDir` to test the fixture.
#### Exposing Options to Runtime
```ts
// basic.test.js
import { describe, it, expect } from 'vitest'
import { fileURLToPath } from 'node:url'
import { setup, $fetch } from '@nuxt/test-utils'
Because modules aren't part of the application runtime, their options aren't too. However, in many cases, you might need access to some of these module options within your runtime code. We recommend exposing the needed config using Nuxt's [`runtimeConfig`](/docs/api/configuration/nuxt-config#runtimeconfig).
describe('ssr', async () => {
await setup({
rootDir: fileURLToPath(new URL('./fixture', import.meta.url)),
})
<!-- TODO: Update after #18466 (or equivalent) -->
it('renders the index page', async () => {
// Get response to a server-rendered page with `$fetch`.
const html = await $fetch('/')
```js
import { defineNuxtModule } from '@nuxt/kit'
import { defu } from 'defu'
expect(html).toContain('<a>A Link</a>')
})
export default defineNuxtModule({
setup (options, nuxt) {
nuxt.options.runtimeConfig.public.myModule = defu(nuxt.options.runtimeConfig.public.myModule, {
foo: options.foo
})
}
})
```
For more usage, please refer to our [tests for Nuxt 3 framework](https://github.com/nuxt/nuxt/blob/main/test/basic.test.ts).
Note that we use [`defu`](https://github.com/unjs/defu) to extend the public runtime configuration the user can provides instead of overwritting it.
### Mock utils
You can then access your module options in a plugin, component, the application like any other runtime configuration:
- `mockFn()`: Returns a mocked function based on test runner.
- `mockLogger()`: Mocks logger using [`consola.mockTypes`](https://github.com/unjs/consola#mocktypes) and `mockFn()`. Returns an object of mocked logger types.
```js
const options = useRuntimeConfig().public.myModule
```
### Testing Externally
::alert{type=warning}
Be careful not to expose any sensitive module configuration on the public runtime config, such as private API keys, as they will end up in the public bundle.
::
If you wish to test your module outside of the module playground before publishing to npm, you can use [`npm pack`](https://docs.npmjs.com/cli/v7/commands/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"`.
:ReadMore{link="/docs/guide/going-further/runtime-config" title="Guide > Going Further > Runtime Config"}
After that, you should be able to reference `nuxt-module-name` like in any regular project.
#### Injecting Plugins With `addPlugin`
## Examples
Plugins are a common way for a module to add runtime logic. You can use the `addPlugin` utility to register them from your module.
### Provide Nuxt Plugins
Commonly, modules provide one or more run plugins to add runtime logic.
```ts
```js
import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit'
export default defineNuxtModule<ModuleOptions>({
export default defineNuxtModule({
setup (options, nuxt) {
// Create resolver to resolve relative paths
const { resolve } = createResolver(import.meta.url)
@ -257,44 +299,37 @@ export default defineNuxtModule<ModuleOptions>({
})
```
::ReadMore{link="/docs/api/advanced/kit" title="API > Advanced > Kit"}
::
: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.
```ts
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
nuxt.options.css.push('font-awesome/css/font-awesome.css')
}
})
```
### Adding Vue Components
#### Injecting Vue Components With `addComponent`
If your module should provide Vue components, you can use the `addComponent` utility to add them as auto-imports for Nuxt to resolve.
```ts
```js
import { defineNuxtModule, addComponent } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const resolver = createResolver(import.meta.url)
// From the runtime directory
addComponent({
name: 'MyComponent', // name of the component to be used in vue templates
name: 'MySuperComponent', // name of the component to be used in vue templates
export: 'MySuperComponent', // (optional) if the component is a named (rather than default) export
filePath: resolver.resolve('runtime/components/MySuperComponent.vue')
})
// From a library
addComponent({
name: 'MyAwesomeComponent', // 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')
filePath: '@vue/awesome-components'
})
}
})
```
### Adding Composables
#### Injecting Composables With `addImports` and `addImportsDir`
If your module should provide composables, you can use the `addImports` utility to add them as auto-imports for Nuxt to resolve.
@ -304,6 +339,7 @@ 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',
@ -321,18 +357,76 @@ 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](https://github.com/nuxt/starter/blob/module/.nuxtrc#L1) while developing a module. If you are using the module starter, auto-imports will not be enabled in your playground either.
::
#### Injecting Other Assets
### Clean Up Module
If your module should provide other kinds of assets, they can also be injected. Here's a simple example module injecting a stylesheet through Nuxt's `css` array.
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:
```js
import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const { resolve } = createResolver(import.meta.url)
nuxt.options.css.push(resolve('./runtime/style.css'))
}
})
```
And a more advanced one, exposing a folder of assets through [Nitro](/docs/guide/concepts/server-engine)'s `publicAssets` option:
```js
import { defineNuxtModule, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const { resolve } = createResolver(import.meta.url)
nuxt.hook('nitro:config', async (nitroConfig) => {
nitroConfig.publicAssets ||= []
nitroConfig.publicAssets.push({
dir: resolve('./runtime/public'),
maxAge: 60 * 60 * 24 * 365 // 1 year
})
})
}
})
```
#### Using Hooks
[Lifecycle hooks](/docs/guide/going-further/hooks) allow you to expand almost every aspect of Nuxt. Modules can hook to them programmatically or through the `hooks` map in their definition.
```js
import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
// Hook to the `app:error` hook through the `hooks` map
hooks: {
'app:error': (err) => {
console.info(`This error happened: ${err}`);
}
},
setup (options, nuxt) {
// Programmatically hook to the `page:extend` hook
nuxt.hook('page:extend', (pages) => {
console.info(`Discovered ${pages.length} pages`);
})
}
})
```
:ReadMore{link="/docs/api/advanced/hooks" title="API > Advanced > Hooks"}
**Module cleanup**
If your module opens, handles, or starts a watcher, you should close it when the Nuxt lifecycle is done. The `close` hook is available for this.
```ts
import { defineNuxtModule } from '@nuxt/kit'
@ -345,3 +439,198 @@ export default defineNuxtModule({
}
})
```
#### Augmenting Types
If your module should augment types handled by Nuxt, you can use the `prepare:types` hook to perform this operation.
```js
import { defineNuxtModule, addTemplate, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
const { resolve } = createResolver(import.meta.url)
// Generating types to be injected
addTemplate({
filename: 'my-module.d.ts',
getContents: () => {
return `// Generated by my-module
interface MyModuleNitroRules {
myModule?: { foo: 'bar' }
}
declare module 'nitropack' {
interface NitroRouteRules extends MyModuleNitroRules {}
interface NitroRouteConfig extends MyModuleNitroRules {}
}
export {}`
},
})
// Injecting previously generated types
nuxt.hooks.hook('prepare:types', ({ references }) => {
references.push({ path: resolve(nuxt.options.buildDir, 'my-module.d.ts') })
})
}
})
```
### Testing
Testing helps ensuring your module works as expected given various setup. Find in this section how to perform various kinds of tests against your module.
#### Unit and Integration
::alert{type=info}
We're still discussing and exploring how to ease unit and integration testing on Nuxt Modules.
[Check out this RFC to join the conversation](https://github.com/nuxt/nuxt/discussions/18399).
::
#### End to End
[Nuxt Test Utils](/docs/getting-started/testing) is the go-to library to help you test your module in an end-to-end way. Here's the workflow to adopt with it:
1. Create a Nuxt application to be used as a "fixture" inside `test/fixtures/*`
2. Setup Nuxt with this fixture inside your test file
3. Interact with the fixture using utilities from `@nuxt/test-utils` (e.g. fetching a page)
4. Perform checks related to this fixture (e.g. "HTML contains ...")
5. Repeat
In practice, the fixture:
```js [test/fixtures/ssr/nuxt.config.ts]
// 1. Create a Nuxt application to be used as a "fixture"
import MyModule from '../../../src/module'
export default defineNuxtConfig({
ssr: true,
modules: [
MyModule
]
})
```
And its test:
```js [test/rendering.ts]
import { describe, it, expect } from 'vitest'
import { fileURLToPath } from 'node:url'
import { setup, $fetch } from '@nuxt/test-utils'
describe('ssr', async () => {
// 2. Setup Nuxt with this fixture inside your test file
await setup({
rootDir: fileURLToPath(new URL('./fixtures/ssr', import.meta.url)),
})
it('renders the index page', async () => {
// 3. Interact with the fixture using utilities from `@nuxt/test-utils`
const html = await $fetch('/')
// 4. Perform checks related to this fixture
expect(html).toContain('<div>ssr</div>')
})
})
// 5. Repeat
describe('csr', async () => { /* ... */ })
```
::alert{type=info}
An example of such a workflow is available on [the module starter](https://github.com/nuxt/starter/blob/module/test/basic.test.ts).
::
#### Manual QA With Playground and Externally
Having a playground Nuxt application to test your module when developing it is really useful. [The module starter integrates one for that purpose](#how-to-develop).
You can test your module with other Nuxt applications (applications that are not part of your module repository) locally. To do so, you can use [`npm pack`](https://docs.npmjs.com/cli/v7/commands/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: `"my-module": "file:/path/to/tarball.tgz"`.
After that, you should be able to reference `my-module` like in any regular project.
### Best Practices
With great power comes great responsibility. While modules are powerful, here are some best practices to keep in mind while authoring modules to keep applications performant and developer experience great.
#### Async Modules
As we've seen, Nuxt Modules can be asynchronous. For example, you may want to develop a module that needs fetching some API or calling an async function.
However, be careful with asynchronous behaviors as Nuxt will wait for your module to setup before going to the next module and starting the development server, build process, etc. Prefer deferring time-consuming logic to Nuxt hooks.
::alert{type="warning"}
If your module takes more than **1 second** to setup, Nuxt will emit a warning about it.
::
#### 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 benefits users even when not using TypeScript directly.
#### Avoid CommonJS Syntax
Nuxt 3, relies on native ESM. Please read [Native ES Modules](/docs/guide/concepts/esm) for more information.
#### Document Module Usage
Consider documenting module usage in the readme file:
- Why use this module?
- How to use this module?
- What does this module do?
Linking to the integration website and documentation is always a good idea.
#### Provide a Stackblitz Demo or Boilerplate
It's a good practice to make a minimal reproduction with your module and [StackBlitz](https://nuxt.new/s/v3) that you add to your module readme.
This not only provides potential users of your module a quick and easy way to experiment with the module but also an easy way for them to build minimal reproductions they can send you when they encounter issues.
#### 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.
#### Stick With Starter Defaults
The module starter comes with a default set of tools and configurations (e.g. ESLint configuration). If you plan on open-sourcing your module, sticking with those defaults ensures your module shares a consistent coding style with other [community modules](/modules) out there, making it easier for others to contribute.
## Ecosystem
[Nuxt Module ecosystem](/modules) represents more than 15 million monthly NPM downloads and provides extended functionalities and integrations with all sort of tools. You can be part of this ecosystem!
### Module Types
**Official modules** are modules prefixed (scoped) with `@nuxt/` (e.g. [`@nuxt/content`](https://content.nuxtjs.org)). They are made and maintained actively by the Nuxt team. Like with the framework, contributions from the community are more than welcome to help make them better!
**Community modules** are modules prefixed (scoped) with `@nuxtjs/` (e.g. [`@nuxtjs/tailwindcss`](https://tailwindcss.nuxtjs.org)). They are proven modules made and maintained by community members. Again, contributions are welcome from anyone.
**Third party and other community modules** are modules (often) prefixed with `nuxt-`. Anyone can make them, using this prefix allows these modules to be discoverable on npm. This is the best starting point to draft and try an idea!
**Private or personal modules** are modules made for your own use case or company. They don't need to follow any naming rules to work with Nuxt and are often seen scoped under an npm organization (e.g. `@my-company/nuxt-auth`)
### Listing Your Community Module
Any community modules are welcome to be listed on [the module list](/modules). To be listed, open an issue in the [nuxt/modules](https://github.com/nuxt/modules/issues/new) repository, the Nuxt team can help you to apply best practices before listing.
### Joining `nuxt-modules` and `@nuxtjs/`
By moving your modules to [nuxt-modules](https://github.com/nuxt-modules), 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-modules`, open an issue in [nuxt/modules](https://github.com/nuxt/modules/issues/new).
By joining `nuxt-modules` we can rename your community module under the `@nuxtjs/` scope and provide a subdomain (e.g. `my-module.nuxtjs.org`) for its documentation.
<!-- ## Module Internals
Maybe just a quick section touching on "how modules work" under the hood, priority, etc. -->