├── .nuxtrc ├── .eslintignore ├── tsconfig.json ├── playground ├── package.json ├── nuxt.config.ts └── app.vue ├── .eslintrc ├── src ├── runtime │ ├── images │ │ └── cogwheel.png │ ├── plugin.ts │ ├── components │ │ ├── DevToolsWrapper.vue │ │ ├── DevToolsInputField.vue │ │ └── DevTools.vue │ └── composables │ │ ├── useCustomRuntimeConfig.ts │ │ ├── useModuleConfig.ts │ │ ├── useEnvVar.ts │ │ └── useDevToolsModel.ts ├── types.d.ts └── module.ts ├── .editorconfig ├── types.d.ts ├── README.md ├── .gitignore └── package.json /.nuxtrc: -------------------------------------------------------------------------------- 1 | imports.autoImport=false 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "my-module-playground" 4 | } 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [], 3 | "rules": { 4 | "quotes": ["error", "double"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/runtime/images/cogwheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulmelero/nuxt-module-example-custom-devtools/HEAD/src/runtime/images/cogwheel.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | export type AnyConfig = Record; 2 | 3 | export interface ModuleOptions { 4 | openByDefault: boolean; 5 | rootClass: string; 6 | defaultPosition: "top-left" | "top-right" | "bottom-left" | "bottom-right"; 7 | closeViaEscapeKey: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt/config'; 2 | import CustomDevtools from '..'; 3 | 4 | export default defineNuxtConfig({ 5 | modules: [CustomDevtools], 6 | runtimeConfig: { 7 | public: { testEnvVar: 'test' }, 8 | }, 9 | customDevtools: {}, 10 | }); 11 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, ToRefs } from "vue"; 2 | 3 | declare module "#app" { 4 | interface NuxtApp { 5 | $useEnvVar: ComputedRef; 6 | } 7 | } 8 | declare module "@vue/runtime-core" { 9 | interface ComponentCustomProperties { 10 | $useEnvVar: ComputedRef; 11 | } 12 | } 13 | export {}; 14 | -------------------------------------------------------------------------------- /src/runtime/plugin.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtPlugin } from "#imports"; 2 | 3 | import { useEnvVar } from "./composables/useEnvVar"; 4 | 5 | export default defineNuxtPlugin(() => { 6 | return { 7 | provide: { 8 | // injects $useEnvVar in the Nuxt app instance 9 | // const { $useEnvVar } = useNuxtApp(); 10 | useEnvVar, 11 | }, 12 | }; 13 | }); 14 | -------------------------------------------------------------------------------- /src/runtime/components/DevToolsWrapper.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/runtime/composables/useCustomRuntimeConfig.ts: -------------------------------------------------------------------------------- 1 | import { useRuntimeConfig } from "#app"; 2 | import { PublicRuntimeConfig } from "@nuxt/schema"; 3 | 4 | export function useCustomRuntimeConfig() { 5 | const staticConfig: Partial = Object.assign( 6 | {}, 7 | useRuntimeConfig().public 8 | ); 9 | // do not include this module config in the DevTools 10 | delete staticConfig.customDevtools; 11 | 12 | return staticConfig; 13 | } 14 | -------------------------------------------------------------------------------- /src/runtime/composables/useModuleConfig.ts: -------------------------------------------------------------------------------- 1 | import { useStorage, type RemovableRef } from "@vueuse/core"; 2 | import { useRuntimeConfig } from "#app"; 3 | import type { ModuleOptions } from "../../types"; 4 | 5 | export function useModuleConfig(): { 6 | moduleConfig: RemovableRef; 7 | } { 8 | const moduleConfig = useStorage( 9 | "module-config", 10 | useRuntimeConfig().public.customDevtools 11 | ); 12 | return { moduleConfig }; 13 | } 14 | -------------------------------------------------------------------------------- /src/runtime/composables/useEnvVar.ts: -------------------------------------------------------------------------------- 1 | import { computed } from "vue"; 2 | 3 | import { useDevToolsModel } from "./useDevToolsModel"; 4 | 5 | export function useEnvVar(key: string) { 6 | const { model: customConfig } = useDevToolsModel(); 7 | 8 | if (!key || !(key in customConfig.value)) { 9 | throw new Error( 10 | `Custom Devtools - ${String(key)} is not defined in form config` 11 | ); 12 | } 13 | 14 | return computed(() => customConfig.value[key]); 15 | } 16 | -------------------------------------------------------------------------------- /src/runtime/composables/useDevToolsModel.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "#app"; 2 | import { useCustomRuntimeConfig } from "./useCustomRuntimeConfig"; 3 | 4 | export const useDevToolsModel = () => { 5 | const initialState = useCustomRuntimeConfig(); 6 | 7 | const model = useState("config", () => { 8 | return initialState; 9 | }); 10 | 11 | const resetModel = () => { 12 | model.value = Object.assign({}, initialState); 13 | }; 14 | 15 | return { 16 | model, 17 | resetModel, 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /src/runtime/components/DevToolsInputField.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 15 | 16 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nuxt-module-example-custom-devtools 2 | 3 | This is the Nuxt module presented in the Nuxt Nation as an example of how to build custom Devtools for your Nuxt 3 application. 4 | 5 | Note that the work done here is merely for educational purposes. 6 | 7 | If you want to build a custom Devtools based on this example, I recommend improving the form: accepting more than just string type values and add other features as well: 8 | 9 | - [ ] share current configuration via URL parameters 10 | - [ ] add [msw.js](https://mswjs.io/) to intercept requests and mock the app state 11 | 12 | I hope you liked the presetation! 💚 13 | 14 | ## Let's connect! 15 | 16 | - [Twitter](https://twitter.com/paul_melero) 17 | - [LinkedIn](https://www.linkedin.com/in/paulmelero/) 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .vercel_build_output 23 | .build-* 24 | .env 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | !.vscode/*.code-snippets 43 | 44 | # Intellij idea 45 | *.iml 46 | .idea 47 | 48 | # OSX 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createResolver, 3 | defineNuxtModule, 4 | addPlugin, 5 | addComponentsDir, 6 | } from "@nuxt/kit"; 7 | import defu from "defu"; 8 | 9 | import { ModuleOptions } from "./types"; 10 | 11 | export default defineNuxtModule({ 12 | meta: { 13 | name: "custom-devtools", 14 | configKey: "customDevtools", 15 | }, 16 | defaults: { 17 | openByDefault: true, 18 | rootClass: "nuxt-custom-devtools", 19 | defaultPosition: "top-left", 20 | closeViaEscapeKey: true, 21 | }, 22 | setup(options, nuxt) { 23 | const isDev = nuxt.options.dev; 24 | if (isDev) { 25 | console.log("Custom Devtools - Running in dev mode"); 26 | } 27 | 28 | // options 29 | nuxt.options.runtimeConfig.public.customDevtools = defu( 30 | nuxt.options.runtimeConfig.public.customDevtools, 31 | options 32 | ); 33 | 34 | const { resolve } = createResolver(import.meta.url); 35 | 36 | // Add plugin 37 | addPlugin(resolve("./runtime/plugin")); 38 | 39 | // Add components 40 | addComponentsDir({ path: resolve("./runtime/components") }); 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-module-example-custom-devtools", 3 | "version": "0.0.0", 4 | "description": "Nuxt module example. Custom Devtools", 5 | "author": "Paul Melero", 6 | "license": "MIT", 7 | "repository": "https://github.com/gangsthub/nuxt-module-example-custom-devtools", 8 | "type": "module", 9 | "exports": { 10 | ".": { 11 | "import": "./dist/module.mjs", 12 | "require": "./dist/module.cjs" 13 | } 14 | }, 15 | "main": "./dist/module.cjs", 16 | "types": "./dist/types.d.ts", 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "prepack": "nuxt-module-build", 22 | "dev": "nuxi dev playground", 23 | "dev:build": "nuxi build playground", 24 | "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground" 25 | }, 26 | "dependencies": { 27 | "@nuxt/kit": "^3.0.0-rc.13", 28 | "@vueuse/core": "^9.5.0", 29 | "defu": "^6.1.1" 30 | }, 31 | "devDependencies": { 32 | "@nuxt/module-builder": "^0.2.0", 33 | "@nuxt/schema": "^3.0.0-rc.13", 34 | "@nuxtjs/eslint-config-typescript": "^11.0.0", 35 | "eslint": "^8.26.0", 36 | "nuxt": "^3.0.0-rc.13" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/runtime/components/DevTools.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 70 | 71 | 166 | --------------------------------------------------------------------------------