Nuxt モジュールパーフェクトガイド 🍜 Nuxt.js 2.15 🍜 TypeScriptでの開発対応

22 min read読了の目安(約20100字

Nuxt自体の動きを外部から変えたいと思ったことはありませんか? もちろん、ベタに nuxt.conf.js を弄るのもよいのですが、共通分に関しては切り分けて個別の部品として管理したいですよね。そんなときに、 モジュール が役に立ちます。

Nuxt モジュールができること

モジュールnuxt.conf.js で記述するようなビルドの動きを拡張することができます。例えば、ミドルウェアを追加したり、事前に特定のファイルを作成したいときなどに便利です。
一方で、Nuxt は混乱しやすい用語として プラグイン という仕組みが存在します。
プラグインは、Vue インスタンスと、サーバコンテキストから特定のオブジェクトを利用できるようにするために利用されるものです。

Nuxt モジュールからは、Nuxt プラグインを追加することができるので、使い分けとしては以下のようになります。

種類 用途
プラグイン $rootcontextから呼び出すことができる何かを提供する
モジュール ビルドの動きを変えたい場合。設定により動的な挙動を持つプラグインとして提供したい場合

参考例として、 gtm-module では、やりたいことはプラグインとしてGoogle Tag Manager 操作オブジェクトとして $gtm を使えるようにすることなので、一見プラグイン単体で良さそうです。ただし、以下のような状況があるため、モジュールからプラグインを動的に生成する必要があります。

  • IDのスクリプトへの埋め込み
  • head へのスクリプト読み込みタグの直接追加
  • 開発環境での mock の読み出し (読み込むプラグインを環境によって分ける)

https://github.com/nuxt-community/gtm-module/

モジュールの骨格と読み込み

モジュールは単純な関数によって提供されます。
試しに、modulesというフォルダをNuxtプロジェクトのルート下に作り、その中に、example.js を作成します。

modules/example.js
export default function myModule (option) {
}

設定ファイルに、モジュールを読み込む設定を追加します。

nuxt.config.js
export default {
  // 中略..
  // modules 利用
  modules: [
    '~/modules/example'
  ]
}

これにより、Nuxtは、実行時に modules/example.js に記載されているexportされたdefault関数を実行しようとします。
この関数は、Promiseを返すことにより、非同期処理が必要な作業も行うことができます。nuxt.config.jsmodules に記載された順序どおりに、1つづつ順次実行する流れになっているので、仮に別のモジュールが先に読み込まれている必要がある場合などには注意が必要です。

今回は Nuxtプロジェクトフォルダにモジュールを作成しましたが、実際には、npmパッケージを作成して複数のプロジェクトで共有できるようにしたり、単体でテストできるようにしたほうが良いでしょう。npmパッケージの場合は通常、文字列に modules@nuxtjs/gtm のような文字列を渡すことで対応できます。
npmパッケージ名を指定した場合は、node.js上で、require した場合と同じく、package.jsonmain に記載されたファイルを読み込みに行きます。(デフォルトはnpmの標準挙動で、npmパッケージ直下の index.js を読み込みに行こうとします。)

モジュールは実行時にインポートされ読み込まれることになるので、トランスパイルが完了されていて、実行環境 (node.js) が解釈できるものである必要があります。
この文章上は、最終的にTypeScriptで記載するつもりですが、簡単のため最初はJavaScript(node.js 14が解釈できるもの)で記載していきます。

nuxt.config.js からモジュールへは、オプションを渡すことができます。先程から引用している、@nuxtjs/gtm の場合は、 GTMのIDを渡すときなどに利用されます。

nuxt.config.js
export default {
  // 中略..
  // modules 利用
  modules: [
    ['~/modules/example', {id: 'sample'}]
  ]
}

オプションは関数の引数として渡され、利用することができます。特に何もオプションを渡していない場合については空のオブジェクト {} が option として渡されます。

modules/example.js
export default function myModule (option) {
  console.log(option.id) // sampleがモジュールを読み込んだタイミングで出力される
}

モジュールはファイルを作らずに、nuxt.config.js に直接インラインで記載することもできます。プロジェクト固有で、再利用不要・量が少ない場合においては、この方法を使う考え方もあります。

nuxt.config.js
export default {
  // 中略..
  // modules 利用
  modules: [
    function () {
      // モジュールの処理
    }
  ]
}

もし、モジュールが本番環境には不要な場合は、設定 buildModules でモジュールをロードします。開発限定の機能拡張 (例えば、dotenv-module) は、こちらで読み込みます。

nuxt.config.js
export default {
  // 中略..
  // buildModules 利用
  buildModules: [
    '~/modules/example'
  ]
}

Hook

モジュールでできることをそれぞれ考えていきます。1つめは Hook です。Nuxtで用意されている特定のタイミングで何かを行うことができます。

modules/example.js
export default function myModule (option) {
  this.nuxt.hook('ready', async nuxt => {
    console.log('Nuxt.js準備完了!!')
  })
}

module 中で使える this は、 ModuleContainer と呼ばれるオブジェクトになります。このオブジェクトは、Nuxtを拡張するために必要な便利な機能を提供しています。

this.nuxt は、Nuxtの根幹をなす Nuxt オブジェクトです。このオブジェクトは、hookable を継承したものとなっており、Nuxtのビルド等で生じるさまざまなイベントにフックを行うことができます。

フックする関数についてはPromiseを返すことにより、非同期にすることができます。
通常は、フックした処理が完了するまで待って次の処理に進む動きになります。

以下のようなHookが確認されています。(現在廃止されているものを除く, 2.15時点)
hook名はバージョン間で変更がなされたり、渡されれる値が変わったりするので要注意です。

一応はドキュメントはあるのですが、ここではドキュメントに記載されていないものも含め2.15時点で確認されている最新のものを記載します。

Nuxt全体の動き

hook名 args タイミング
ready nuxt:Nuxt Nuxtが動作する準備が完了した段階。
close nuxt:Nuxt Nuxtのインスタンスが正常に終了する手前。
listen server:any, {host: string, port: number | string} Nuxt内部のサーバがListenを始めたとき。
error error:any フックを呼び出したタイミングでエラーが発生した場合。errorには、例外としてキャッチされたものが渡される。これは、Nuxt上の定義というよりは、hookableで定義されたもの。

ドキュメント:

https://ja.nuxtjs.org/docs/2.x/internals-glossary/internals-nuxt

モジュールのロード

準備フェーズで行われます

hook名 args タイミング
modules:before moduleContainer:ModuleContainer, options:NuxtOptionsModule[] すべてのモジュールのロードの前。つまり、モジュール内でこちらにフックしようとしても機能しません。後述しますが、フックで呼び出される関数は nuxt.config.js からも定義できるため、そちらにモジュール関係で必要な前処理を記載するために使うのが良さそうです。
modules:done moduleContainer:ModuleContainer すべてのモジュールがロードされた後

ドキュメント:

https://ja.nuxtjs.org/docs/2.x/internals-glossary/internals-module-container

Renderer

サーバ関連のための処理について。つまり、 npm run build や、 npm run generate を行う際にはこのイベントは呼び出されません。

準備段階

hook名 args タイミング
render:before renderer:Server (NuxtのServerインスタンス), options:NuxtOptionsRender Rendererクラスのミドルウェアとリソースを設定する前
render:setupMiddleware app:Server (connectのインスタンス) Nuxtがミドルウェアをセットアップする前
render:errorMiddleware app:Server (connectのインスタンス) Nuxtがエラー用のミドルウェアをセットアップする前。具体的には、Sentryモジュール のようなエラー収集ツールで使うのが良さそうです。
render:resourcesLoaded resources:any リソースのロード後。リソースとは、サーバを動かすために必要なファイル等を指します。(server.manifest.json 等)
render:done renderer:Server (NuxtのServerインスタンス) Rendererの準備が完了した段階

レンダリング段階

特にSSR周りの挙動

hook名 args タイミング
vue-renderer:context renderContext:any SPA/SSR問わず、サーバがHTMLレンダリングに関して必要な処理をしようとする前
vue-renderer:ssr:prepareContext renderContext:any SSRでアプリケーションをレンダリングする前。Vueを通してHTMLへの変換を行っておらず、asyncData等はまだ呼ばれていない。
vue-renderer:spa:prepareContext renderContext:any SPAでアプリケーションをレンダリングする前
vue-renderer:ssr:context renderContext:any SSRでアプリケーションをレンダリングした後。asyncData等は呼ばれているため、renderContext.nuxt には window.__NUXT__ (asyncDataの結果等はこちらに書き込まれる) にシリアライズする前の値があり、参照したり、書き換えたりすることもできる。
vue-renderer:ssr:csp cspScriptSrcHashes:string[] SSRで、コンテンツセキュリティポリシー 用のメタ情報生成前
vue-renderer:ssr:templateParams templateParams:Record<string, string>, renderContext:any SSRで、HTMLをレンダリングする前。templaraParamsには、Vueを通して生成したHTMLなどが入っており、それを組み立ててレスポンスのbodyを作る前の状態になっている。templateParams.HTML_ATTRS, templateParams.HEAD_ATTRS, templateParams.BODY_ATTRS, templateParams.HEAD, templateParams.APP, templateParams.ENV を参照・書き換えを行うことができる。これらの値は、app.htmlに渡されHTMLが完成する。
vue-renderer:spa:templateParams templateParams:Record<string, string> SPAで、HTMLをレンダリングする前。SSR用のrenderContextがない以外は、vue-renderer:ssr:templateParamsと同じ
render:route url:string, result:any, context:{req, res} routeがレンダリングされるとき。レスポンスを送る前
render:routeDone url:string, result:any, context:{req, res} routeがレンダリングされるとき。レスポンスを送った後
server:nuxt:renderLoading req, res routeの結果が空だった場合。開発環境でビルド中の場合にローディング画面を出すために利用されるようです

ドキュメント:

https://ja.nuxtjs.org/docs/2.x/internals-glossary/internals-renderer

Build

ビルド時に発生する挙動です。npm run dev 実行時は、Nuxt が ready になった後にこれらのフックが呼び出されます。

hook名 args タイミング
build:before builder:Builder, buildOption:NuxtOptionsBuild Nuxtビルドの開始前
builder:prepared builder:Builder, buildOption:NuxtOptionsBuild ビルド用のディレクトリが作成されたとき
builder:extendPlugins plugins:NuxtOptionsPlugin[] プラグイン作成時
build:templates { templatesFiles, templateVars, resolve } .nuxtテンプレートファイル生成時
build:extendRoutes routes, resolve ルーティング生成時
webpack:config webpackConfigs:Configuration[] (WebPack の Configuration オブジェクト。name key に client (SPA用), server (SSR用), modern (モダンモード) がそれぞれ設定されている) コンパイラの設定前
build:compile {name: string, compiler} webpackコンパイル前。univasalモードの場合は、clientserver の名前で2度呼び出される
build:compiled {name: string, compiler, stats} webpackのビルド終了時
build:done builder:Builder Nuxtビルドの終了時

ドキュメント:

https://ja.nuxtjs.org/docs/2.x/internals-glossary/internals-builder

Generator

静的ファイル生成ジェネレーター利用時に発生する挙動です。サーバとして動作させている場合は発生しません。

hook名 args タイミング
generator:before generator:Generator, generateOptions:NuxtOptionsGenerate 生成前
generate:distRemoved generator:Generator ビルド先のフォルダが削除されるとき
generate:distCopied generator:Generator 静的ファイルとビルドされたファイルがコピーされたとき
generate:extendRoutes routes:NuxtRouteConfig[] route初期設定の最後。ジェネレーターのためにroute情報を書き換えたりなどに利用できる。
generate:route route:NuxtRouteConfig, setPayload:(payload: object) => void ページ生成前。動的にペイロードを渡すときに利用する。ルートごとに呼ばれる
generate:page {route:NuxtRouteConfig, path:string, html:string, exclude: boolean, errors: {type: string, error: Error}[]} 生成後のパスとHTMLを更新する時
generate:routeCreated {route:NuxtRouteConfig, path:string, errors: {type: string, error: Error}[]} 生成されたページの保存に成功した時
generate:routeFailed {route:NuxtRouteConfig, errors: {type: string, error: Error}[]} 生成されたページの保存に失敗した時。失敗ごとに呼ばれる
generate:manifest manifest, generator:Generator manifest.js作成前。このファイルは、Nuxtがアセットなどのパス情報を知るために使われるもの。PWAの manifest.json とは違う
generate:done generator:Generator, errors 生成完了時

ドキュメント:

https://ja.nuxtjs.org/docs/2.x/internals-glossary/internals-generator

Command / CLI

hook名 args タイミング
run:before {argv: any, cmd: {name: string, usage: string, description: string}, rootDir: string} CLIコマンド実行前
config options:NuxtConfig configロード時
cli:buildError error:any yarn run dev時にビルドエラー発生時。catchしたエラーをハンドリングしたい時に利用できる

Hook を設定から定義する

modules:before のようなモジュールのロード以前に呼ばれるHookについては、モジュールから利用することができません。
これを利用する場合は、nuxt.confg.js 設定に直接記載する必要があります。

https://ja.nuxtjs.org/docs/2.x/configuration-glossary/configuration-hooks
nuxt.config.js
export default {
  hooks: {
    modules: {
      before(moduleContainer, options) {
         // モジュールのロード前に行う必要のある処理を記載できます
      }
    }
  }
}

Template

モジュールからは動的なファイルを生成してPluginとして読み込ませたりといった使い方ができます。
この機能は lodash.template を利用しているので、元となるファイルはこの形式で記載することになります。

Plugin

ModuleContainerのaddPlugin()を利用することで、プラグイン用のファイルの生成と、Pluginのロードを行うことができます。

modules/plugin.js
const id = '<%= options.id %>'

export default function (ctx, inject) {
  const example = {
    logId: () => {
      console.log(id)
    }
  }

  inject('example', example)
}
modules/example.js
const path = require('path')

export default function myModule (option) {
  const { id } = option

  this.addPlugin({
    src: path.resolve(__dirname, 'plugin.js'),
    fileName: 'example.js', // ここのKeyはfilename でも良い
    options: { // テンプレートに渡したい値は options で指定する
      id
    }
  })
}
nuxt.config.js
export default {
  // 中略..
  // modules 利用
  modules: [
    ['~/modules/example', {id: 'sample'}]
  ]
}

これで、Vue$root や、サーバコンテキストから、$example を使えるようになります。
addPlugin() では、テンプレートからのファイル作成に必要な値のほかに、pluginsプロパティで利用する値を渡すこともできます。
クライアントのみに利用したいプラグインの場合は、 mode: 'client' を指定することで、サーバサイドで読み込まれることを抑止することができます。

ファイルのみ生成

順番が前後しますが、ModuleContainerのaddPlugin() の中では、addTemplate() という関数が呼ばれ、ファイルを生成した後に、設定の pluginsプロパティ に、生成したプラグインファイルを読み込むように挿入します。

単純にファイルだけを生成しておき、そのファイルをプラグインから読み込むといった使い方ができます。

先程から説明に使っている、gtm-moduleでは、以下のようにプラグインが利用するファイルを作成しています。コードを読みながら流れを理解してみます。

テンプレートの追加:

https://github.com/nuxt-community/gtm-module/blob/ae9199e6cab39485bcc85758bdfb36ebace3cdcf/lib/module.js#L32-L36
plugin.utils.js を読み取り、gtm.utils.js を配置する。

テンプレート側:

https://github.com/nuxt-community/gtm-module/blob/ae9199e6cab39485bcc85758bdfb36ebace3cdcf/lib/plugin.utils.js
plugin.utils.js はデバッグモード (options.debug) が true であれば、console.logを行うようなコードを生成する。

プラグイン側:

https://github.com/nuxt-community/gtm-module/blob/ae9199e6cab39485bcc85758bdfb36ebace3cdcf/lib/plugin.js#L1
生成された、gtm.utils.js を読み、その中で定義されている log() を利用する。モジュールのデバッグオプション (options.debug) が true であれば、console.logによりログが出力される。

Layout

ModuleContainerのaddLayout() を利用することで、モジュールから新たなLayoutを追加することもできちゃいます。流れとしては、addPlugin() の時と同じで、内部的に addTemplate() が呼ばれて、第2引数として指定した名前で呼び出せるようにします。

デザイン系のモジュールとして、ガワごと提供する場合に利用できそうな機能です。

modules/example.js
const path = require('path')

export default function myModule (option) {
  const { id } = option

  this.addLayout({
    src: path.resolve(__dirname, 'exampleLayout.vue'),
    fileName: 'exampleLayout.vue',
    options: {
      id
    }
  }, 'example')
}

利用するときは、利用側のpageコンポーネントで以下のように呼び出します。
ドキュメント: https://ja.nuxtjs.org/docs/2.x/components-glossary/pages-layout/

page/sample.vue
<template>
  <div>Test</div>
</template>

<script>
export default {
  layout: 'example'
}
</script>

なお、第2引数に 'error' を指定すると、唯一のエラーページ用のレイアウトが差し替わります。Nuxtは、エラーページを1つしか指定できないので、この処理を行うモジュールが2つある場合はコンフリクトを起こします。(特にエラー等は出ないようです。)

ServerMiddleware の拡張

Server用のミドルウェアの拡張を行うときは、ModuleContainerのaddServerMiddleware() を利用します。
実体は、設定の serverMiddleware に渡した値を追加するだけです。

よって指定方法は、ドキュメント に記載されているとおりです。

ビルドの拡張

ModuleContainerのextendBuild()を利用することで、モジュールからビルド設定の拡張を行うことができます。挙動は、設定の build.extend() と同じです。
webpackの挙動を変える必要がある場合などに使います。

https://ja.nuxtjs.org/docs/2.x/configuration-glossary/configuration-build#extend
modules/example.js
export default function myModule (option) {
  this.extendBuild((config, ctx) => {
    // config は、WebpackConfiguration が入っている。
    // このオブジェクトを書き換えることで、webpackビルドの挙動を変更することができる。

    // ctxには、isClient, isDev, isModern, isServer, loaders などが入っている
  })
}

ルートの拡張

ModuleContainerのextendRoutes()を利用することで、モジュールからルートを弄ったり、追加することができます。挙動は、設定の router.extendRoutes() と同じです。

https://ja.nuxtjs.org/docs/2.x/configuration-glossary/configuration-router#extendroutes

addLayout()等と組み合わせて、管理用モジュール等を提供する (切り離し可能) といった使い方が想定できるでしょう。

modules/example.js
export default function myModule (option) {
  this.extendRoutes((routes, resolve) => {
    routes.push({
      name: 'sample',
      path: '/sample',
      chunkName: 'sample',
      component: resolve(__dirname, 'sample.vue')
    })
  })
}

モジュールから別のモジュールを読み込む

ModuleContainerのrequireModule()を利用することで、モジュールからさらにサブモジュールを読んだり、別の依存のあるモジュールを呼ぶこともできます。

modules/example.js
export default function myModule (option) {
  this.requireModule(['~/modules/example-sub', option])
}

モジュールから設定にアクセスする

nuxt.config.js で定義した設定を直接使いたい場合は、ModuleContainerのoptionsを使うことができます。

modules/example.js
export default function myModule (option) {
  console.log(this.options) // 設定された情報がそのまま出てくる
}

この値は書き換えることもできるので、APIとして用意されていないCSSの追加や、アセットの追加については直接このオブジェクトを操作することになります。

https://ja.nuxtjs.org/docs/2.x/directory-structure/modules/#2-thisoptions

meta情報の定義

モジュールには、モジュール名・バージョンといったメタ情報を追加することができます。これは、現状はモジュールの一意性確保のためにされます。
基本的には、フォーマットは、package.jsonと同じなため、それをそのまま利用しているケースも見受けられます。

https://github.com/nuxt-community/proxy-module/blob/master/src/index.ts#L38
modules/example.js
const myModule function(option) {
  this.requireModule(['~/modules/example-sub', option])
}

myModule.meta = {
  "name": "@mymodule/example"
}

export default myModule

TypeScript によるモジュールの記述

さて、ここまでモジュールができることを述べてきました。実際に開発する際は、TypeScriptを利用してIDEの入力補完などを大いに活用して、安全な拡張ライフを送りたいものです。
先日、ModuleContainerに関して、typesを追加するPull Request を送りましたが、hookに関してはまだまだ不完全な状況ではあります。

また、モジュールに関しては、トランスパイル済みのものを提供する必要があるため、開発時には何らかのビルドツールを導入する必要が出てきます。

現状リリースされているいくつかのモジュールの実装を確認すると、siroc を利用しているケースが多いようです。
こちらは、Nuxtの開発等のために、一般的なJavaScriptでも使える製品として切り出されたものの1つで、TypeScriptからJavaScriptのコードへ、設定無しでトランスパイルできるようにすることを目指しているようです。
中身的には、Rollupを含んでおり、少なくとも Nuxt 用のモジュール開発のためには、本当に設定無しで対応できるようです。

実際に、空のプロジェクトからTypeScriptでモジュールを作成していきます。(利用するNuxtプロジェクトは用意されているものとします。)

プロジェクト作成

$ mkdir sample-nuxt-module
$ yarn init
yarn init v1.22.4
question name (sample-nuxt-module):
question version (1.0.0): 0.0.1
question description: sample
question entry point (index.js): dist/index.js
question repository url:
question author:
question license (MIT):
question private:
success Saved package.json

$ yarn add -D siroc @nuxt/types

コミットするべきでないファイルを記載します。

.gitignore
node_modules
*.iml
.idea
*.log*
.nuxt
.vscode
.DS_Store
coverage
dist

tsconfig.json には以下のように記述します。

{
  "compilerOptions": {
    "target": "ESNext",
    "moduleResolution": "Node",
    "strict": true,
    "types": [
      "@nuxt/types"
    ]
  }
}

少し、dependencies を書き換えます。現在まだリリースされていない Nuxt の types を利用したいがためです。
リポジトリ中のフォルダにあるnpmパッケージを直接ロードするには、GitPkg を利用するのが便利です。

また、ビルド用のスクリプト追加や、types があることについても記載します。(これを書かない場合 index.d.ts が生成されません。)

package.json
{
  "name": "sample-nuxt-module",
  "version": "0.0.1",
  "description": "sample",
  "license": "MIT",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "siroc build"
  },
  "devDependencies": {
    "@nuxt/types": "https://gitpkg.now.sh/nuxt/nuxt.js/packages/types?dev",
    "siroc": "^0.9.2"
  }
}

次に、src/main.ts を作成し、適当なモジュールを作ります。
IDEなどで開発しているのであれば、thisで利用できる関数がわかるはずです。

src/main.ts
import type { Module } from '@nuxt/types'

interface Option {
  id?: string
}

const exampleModule: Module<Option> = function(options: Option) {
  this.nuxt.hook('ready', () => {
    console.log('Nuxt準備完了', options.id)
  })
}

// @ts-ignore
exampleModule.meta = require('../package.json')

export default exampleModule

ビルドを行います。

$ yarn run build
yarn run v1.22.4
$ siroc build
ℹ Beginning build                                                                                                                                                                                              sample-nuxt-module 23:41:33
✔ Built sample-nuxt-module      index.d.ts   153  B                                                                                                                                                            sample-nuxt-module 23:41:35
✔ Built sample-nuxt-module        index.js   168  B                                                                                                                                                            sample-nuxt-module 23:41:35
✔ Finished building in 1.7s                                                                                                                                                                                    sample-nuxt-module 23:41:35
✨  Done in 2.37s.

目的のブツができているかを確認します。

dist/index.js
'use strict';

const exampleModule = function(options) {
  this.nuxt.hook("ready", () => {
    console.log("Nuxt\u6E96\u5099\u5B8C\u4E86", options.id);
  });
};
exampleModule.meta = require("../package.json");

module.exports = exampleModule;

yarn link を利用してローカルで、パッケージが利用できるか試してみましょう。

新しく作成したモジュールディレクトリ
$ yarn link
yarn link v1.22.4
success Registered "sample-nuxt-module".
info You can now run `yarn link "sample-nuxt-module"` in the projects where you want to use this package and it will be used instead.
✨  Done in 0.03s.
Nuxtプロジェクトディレクトリ
$ yarn link sample-nuxt-module
yarn link v1.22.4
success Using linked package for "sample-nuxt-module".

nuxt.config.js で今回作ったモジュールをロードするように設定して、yarn run dev を実行するとどうなるか試してみましょう。

nuxt.config.js (Nuxtプロジェクト側)
export default {
  modules: [
    ['sample-nuxt-module', {id: 'sample'}]
  ]
}
Nuxt.jsプロジェクトディレクトリ
$ yarn run dev
...(中略)...
Nuxt準備完了 sample

...

おわりに

これで、Nuxtモジュールの作り方の全容について説明しました。思ったよりはシンプルな仕組みです。

モジュールについて公開できるものであれば、nuxt/modules リポジトリにPull Requestを送ることで、Explore Nuxt Modulesに記載してもらえるようです。
もし実装方法などに迷った場合は上記のディレクトリに掲載されているモジュールのソースコードを読んで見ることと、Nuxt自体のソースコードを読んでみることで理解が進みます。

元々は、TypeScriptでNuxtモジュールを作成しようと思いメモ程度に文章を作りましたが、Nuxt自体へPull Requestを送ったり、文章がかなりボリューミーになるなど、想像より話が広がってしまいました。
NuxtのTypeScriptサポートについては、まだまだなところもあるので、必要に応じて修正を送っていこうと思います。