@movk/nuxt-docs 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -83,37 +83,14 @@ pnpm dev
83
83
  在现有 Nuxt 项目中使用 Movk Nuxt Docs 作为 layer:
84
84
 
85
85
  ```bash [Terminal]
86
- # 安装依赖
87
- pnpm add @movk/nuxt-docs better-sqlite3
88
- ```
89
-
90
- 在 CSS 中导入 Tailwind CSS 和 Nuxt UI
91
-
92
- ```css [app/assets/css/main.css]
93
- @import 'tailwindcss';
94
- @import '@nuxt/ui';
86
+ pnpm add @movk/nuxt-docs better-sqlite3 tailwindcss
95
87
  ```
96
88
 
97
89
  在 `nuxt.config.ts` 中配置:
98
90
 
99
91
  ```ts [nuxt.config.ts]
100
92
  export default defineNuxtConfig({
101
- extends: ['@movk/nuxt-docs'],
102
- css: ['~/assets/css/main.css'],
103
- aiChat: {
104
- model: 'mistral/devstral-2',
105
- models: ['mistral/devstral-2', 'openrouter/qwen/qwen3-4b:free']
106
- },
107
- mcp: {
108
- name: 'My Docs',
109
- browserRedirect: '/docs'
110
- },
111
- llms: {
112
- domain: 'https://your-domain.com',
113
- title: 'My Docs',
114
- description: '基于 Movk Nuxt Docs 构建的智能文档站点',
115
- notes: ['Nuxt 4', '文档主题', 'TypeScript']
116
- }
93
+ + extends: ['@movk/nuxt-docs']
117
94
  })
118
95
  ```
119
96
 
@@ -126,18 +103,16 @@ export default defineNuxtConfig({
126
103
  ```bash
127
104
  my-docs/
128
105
  ├── app/
129
- │ ├── assets/css/main.css # 全局样式
130
106
  │ └── composables/ # 自定义 Composables
131
107
  ├── content/ # Markdown 内容
132
108
  │ ├── index.md # 首页
133
109
  │ └── docs/ # 文档页面
134
110
  ├── public/ # 静态资源
135
111
  ├── nuxt.config.ts # Nuxt 配置
136
- ├── app.config.ts # 应用配置
137
- ├── content.config.ts # 内容配置
138
112
  ├── tsconfig.json # TypeScript 配置
139
113
  ├── package.json # 依赖与脚本
140
- └── README.md # 项目说明
114
+ ├── .env.example # 环境变量示例
115
+ └── pnpm-workspace.yaml # pnpm 工作区配置
141
116
  ```
142
117
 
143
118
  ### Monorepo 结构
@@ -0,0 +1,40 @@
1
+ @import "tailwindcss" theme(static) source("../../../..");
2
+ @import "@nuxt/ui";
3
+
4
+ @theme static {
5
+ --container-8xl: 90rem;
6
+
7
+ --font-sans: 'Public Sans', sans-serif;
8
+
9
+ --color-green-50: #EFFDF5;
10
+ --color-green-100: #D9FBE8;
11
+ --color-green-200: #B3F5D1;
12
+ --color-green-300: #75EDAE;
13
+ --color-green-400: #00DC82;
14
+ --color-green-500: #00C16A;
15
+ --color-green-600: #00A155;
16
+ --color-green-700: #007F45;
17
+ --color-green-800: #016538;
18
+ --color-green-900: #0A5331;
19
+ --color-green-950: #052E16;
20
+ }
21
+
22
+ :root {
23
+ --ui-container: var(--container-8xl);
24
+ }
25
+
26
+ /* Shiki icon highlight transformer styles */
27
+ .shiki-icon-highlight {
28
+ display: inline-block;
29
+ width: 1.25em;
30
+ height: 1.25em;
31
+ vertical-align: -0.25em;
32
+ margin-right: 0.125em;
33
+ background-color: var(--ui-text-highlighted);
34
+ -webkit-mask-repeat: no-repeat;
35
+ mask-repeat: no-repeat;
36
+ -webkit-mask-size: 100% 100%;
37
+ mask-size: 100% 100%;
38
+ -webkit-mask-image: var(--shiki-icon-url);
39
+ mask-image: var(--shiki-icon-url);
40
+ }
package/app/mdc.config.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  import { defineConfig } from '@nuxtjs/mdc/config'
2
- import { transformerColorHighlight } from 'shiki-transformer-color-highlight'
3
2
  import { transformerIconHighlight } from './utils/shiki-transformer-icon-highlight'
4
3
 
5
4
  export default defineConfig({
6
5
  shiki: {
7
6
  transformers: [
8
- transformerColorHighlight(),
9
7
  transformerIconHighlight()
10
8
  ]
11
9
  }
@@ -21,32 +21,32 @@ defineOgImageComponent('Nuxt', {
21
21
  description
22
22
  })
23
23
 
24
- const { data: versions } = await useFetch(page.value.releases || '', {
25
- server: false,
26
- transform: (data: {
27
- releases: {
28
- name?: string
29
- tag: string
30
- publishedAt: string
31
- markdown: string
32
- }[]
33
- }) => {
34
- return data.releases.map(release => ({
35
- tag: release.tag,
36
- title: release.name || release.tag,
37
- date: release.publishedAt,
38
- markdown: release.markdown
39
- }))
40
- }
41
- })
24
+ const { data: versions } = page.value.releases
25
+ ? await useFetch(page.value.releases, {
26
+ server: false,
27
+ transform: (data: {
28
+ releases: {
29
+ name?: string
30
+ tag: string
31
+ publishedAt: string
32
+ markdown: string
33
+ }[]
34
+ }) => data.releases.map(release => ({
35
+ tag: release.tag,
36
+ title: release.name || release.tag,
37
+ date: release.publishedAt,
38
+ markdown: release.markdown
39
+ }))
40
+ })
41
+ : { data: ref(null) }
42
42
  </script>
43
43
 
44
44
  <template>
45
45
  <main v-if="page">
46
46
  <UPageHero
47
- :title="page.hero.title"
48
- :description="page.hero.description"
49
- :links="(page.hero.links as ButtonProps[]) || []"
47
+ :title="page.hero?.title || page.title"
48
+ :description="page.hero?.description || page.description"
49
+ :links="(page.hero?.links as ButtonProps[]) || []"
50
50
  class="md:border-b border-default"
51
51
  :ui="{ container: 'relative py-10 sm:py-16 lg:py-24' }"
52
52
  >
@@ -61,8 +61,11 @@ const { data: versions } = await useFetch(page.value.releases || '', {
61
61
 
62
62
  <UPageSection :ui="{ container: 'py-0!' }">
63
63
  <div class="py-4 md:py-8 lg:py-16 md:border-x border-default">
64
- <UContainer class="max-w-5xl">
64
+ <UContainer class="flex flex-col max-w-5xl gap-y-8 sm:gap-y-12 lg:gap-y-16">
65
+ <ContentRenderer v-if="page.body" :value="page" />
66
+
65
67
  <UChangelogVersions
68
+ v-if="versions?.length"
66
69
  as="main"
67
70
  :indicator-motion="false"
68
71
  :ui="{
@@ -1,4 +1,3 @@
1
- /** copy from https://github.com/nuxt/ui/blob/v4/docs/app/utils/shiki-transformer-icon-highlight.ts */
2
1
  import type { ShikiTransformer } from 'shiki'
3
2
 
4
3
  export interface TransformerIconHighlightOptions {
package/content.config.ts CHANGED
@@ -67,8 +67,8 @@ export default defineContentConfig({
67
67
  schema: z.object({
68
68
  title: z.string(),
69
69
  description: z.string(),
70
- releases: z.string(),
71
- hero: PageHero
70
+ releases: z.string().optional(),
71
+ hero: PageHero.optional()
72
72
  })
73
73
  })
74
74
  }
package/modules/css.ts CHANGED
@@ -1,51 +1,36 @@
1
- import { addTemplate, createResolver, defineNuxtModule } from '@nuxt/kit'
1
+ import { addTemplate, defineNuxtModule } from '@nuxt/kit'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { dirname, join } from 'node:path'
2
4
  import { joinURL } from 'ufo'
5
+ import { resolveModulePath } from 'exsolve'
3
6
 
4
7
  export default defineNuxtModule({
5
8
  meta: {
6
9
  name: 'css'
7
10
  },
8
- async setup(_options, nuxt) {
9
- const dir = nuxt.options.rootDir
10
- const { resolve } = createResolver(import.meta.url)
11
+ setup(_options, nuxt) {
12
+ const currentDir = dirname(fileURLToPath(import.meta.url))
13
+ const mainCssPath = join(currentDir, '../app/assets/css/main.css')
14
+
15
+ nuxt.options.css ||= []
16
+ nuxt.options.css.unshift(mainCssPath)
11
17
 
12
- const layerDir = resolve('../app')
13
- const aiChatDir = resolve('../modules/ai-chat')
18
+ const dir = nuxt.options.rootDir
14
19
 
15
20
  const contentDir = joinURL(dir, 'content')
21
+ const uiPath = resolveModulePath('@nuxt/ui', { from: import.meta.url, conditions: ['style'] })
22
+ const tailwindPath = resolveModulePath('tailwindcss', { from: import.meta.url, conditions: ['style'] })
16
23
 
17
24
  const cssTemplate = addTemplate({
18
25
  filename: 'movk-nuxt-docs.css',
19
26
  getContents: () => {
20
- return `@import "tailwindcss";
21
- @import "tailwindcss/theme.css" layer(theme) theme(static);
22
- @import "@nuxt/ui";
23
-
24
- @source "${contentDir.replace(/\\/g, '/')}/**/*";
25
- @source "${layerDir.replace(/\\/g, '/')}/**/*";
26
- @source "${aiChatDir.replace(/\\/g, '/')}/**/*";
27
- @source "../../app.config.ts";
27
+ return `@import ${JSON.stringify(tailwindPath)};
28
+ @import ${JSON.stringify(uiPath)};
28
29
 
29
- /* Shiki icon highlight transformer styles */
30
- .shiki-icon-highlight {
31
- display: inline-block;
32
- width: 1.25em;
33
- height: 1.25em;
34
- vertical-align: -0.25em;
35
- margin-right: 0.125em;
36
- background-color: var(--ui-text-highlighted);
37
- -webkit-mask-repeat: no-repeat;
38
- mask-repeat: no-repeat;
39
- -webkit-mask-size: 100% 100%;
40
- mask-size: 100% 100%;
41
- -webkit-mask-image: var(--shiki-icon-url);
42
- mask-image: var(--shiki-icon-url);
43
- }`
30
+ @source "${contentDir.replace(/\\/g, '/')}/**/*";`
44
31
  }
45
32
  })
46
33
 
47
- if (Array.isArray(nuxt.options.css)) {
48
- nuxt.options.css.unshift(cssTemplate.dst)
49
- }
34
+ nuxt.options.css.unshift(cssTemplate.dst)
50
35
  }
51
36
  })
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule } from 'nuxt/kit'
1
+ import { defineNuxtModule } from '@nuxt/kit'
2
2
 
3
3
  export default defineNuxtModule((_options, nuxt) => {
4
4
  nuxt.hooks.hook('nitro:init', (nitro) => {
package/nuxt.config.ts CHANGED
@@ -1,12 +1,17 @@
1
- import { createResolver, extendViteConfig } from '@nuxt/kit'
2
1
  import { defineNuxtConfig } from 'nuxt/config'
3
2
  import pkg from './package.json'
4
3
 
5
- const { resolve } = createResolver(import.meta.url)
4
+ // WASM runtime imports that Rollup should not attempt to resolve
5
+ const WASM_EXTERNALS = ['env', 'wasi_snapshot_preview1']
6
+
7
+ function mergeExternals(existing: unknown, additions: string[]): string[] {
8
+ if (Array.isArray(existing)) return [...existing, ...additions]
9
+ if (typeof existing === 'string') return [existing, ...additions]
10
+ return additions
11
+ }
6
12
 
7
13
  export default defineNuxtConfig({
8
14
  modules: [
9
- resolve('./modules/config'),
10
15
  '@nuxt/ui',
11
16
  '@nuxt/content',
12
17
  '@nuxt/image',
@@ -16,30 +21,16 @@ export default defineNuxtConfig({
16
21
  'nuxt-component-meta',
17
22
  'nuxt-llms',
18
23
  'nuxt-og-image',
19
- 'motion-v/nuxt',
20
- () => {
21
- extendViteConfig((config) => {
22
- config.optimizeDeps ||= {}
23
- config.optimizeDeps.include ||= []
24
- config.optimizeDeps.include.push(
25
- '@nuxt/content > slugify',
26
- 'extend',
27
- '@ai-sdk/gateway > @vercel/oidc',
28
- 'mermaid',
29
- 'dompurify'
30
- )
31
- config.optimizeDeps.include = config.optimizeDeps.include
32
- .map(id => id.replace(/^@nuxt\/content > /, '@movk/nuxt-docs > @nuxt/content > '))
33
- })
34
- }
24
+ 'motion-v/nuxt'
35
25
  ],
26
+
36
27
  app: {
37
28
  rootAttrs: {
38
29
  'data-vaul-drawer-wrapper': '',
39
30
  'class': 'bg-default'
40
31
  }
41
32
  },
42
- // @ts-ignore - content 配置的类型定义在运行时才能正确解析
33
+
43
34
  content: {
44
35
  build: {
45
36
  markdown: {
@@ -56,22 +47,20 @@ export default defineNuxtConfig({
56
47
  }
57
48
  }
58
49
  },
50
+
59
51
  mdc: {
60
52
  highlight: {
61
53
  noApiRoute: false
62
54
  }
63
55
  },
56
+
64
57
  runtimeConfig: {
65
58
  public: {
66
59
  version: pkg.version
67
60
  }
68
61
  },
69
- routeRules: {
70
- '/llms.txt': { isr: true },
71
- '/llms-full.txt': { isr: true }
72
- },
62
+
73
63
  experimental: {
74
- typescriptPlugin: true,
75
64
  asyncContext: true,
76
65
  defaults: {
77
66
  nuxtLink: {
@@ -79,7 +68,9 @@ export default defineNuxtConfig({
79
68
  }
80
69
  }
81
70
  },
71
+
82
72
  compatibilityDate: 'latest',
73
+
83
74
  nitro: {
84
75
  prerender: {
85
76
  crawlLinks: true,
@@ -87,9 +78,51 @@ export default defineNuxtConfig({
87
78
  autoSubfolderIndex: false
88
79
  }
89
80
  },
81
+
82
+ hooks: {
83
+ 'vite:extendConfig': async (config) => {
84
+ // Rewrite optimizeDeps paths for layer architecture
85
+ const include = config.optimizeDeps?.include
86
+ if (!include) return
87
+
88
+ const layerPkgs = /^(?:@nuxt\/content|@nuxtjs\/mdc|@nuxt\/a11y) > /
89
+ include.forEach((id, i) => {
90
+ if (layerPkgs.test(id)) include[i] = `@movk/nuxt-docs > ${id}`
91
+ })
92
+
93
+ include.push(
94
+ '@movk/nuxt-docs > @nuxt/content > slugify',
95
+ '@movk/nuxt-docs > @ai-sdk/gateway > @vercel/oidc'
96
+ )
97
+
98
+ // WASM plugin support for Shiki
99
+ const [wasm, topLevelAwait] = await Promise.all([
100
+ import('vite-plugin-wasm'),
101
+ import('vite-plugin-top-level-await')
102
+ ])
103
+ config.plugins!.push(wasm.default(), topLevelAwait.default())
104
+
105
+ const build = config.build || ((config as any).build = {})
106
+ build.rollupOptions ??= {}
107
+ build.rollupOptions.external = mergeExternals(
108
+ build.rollupOptions.external,
109
+ WASM_EXTERNALS
110
+ )
111
+ },
112
+
113
+ 'nitro:config': (nitroConfig) => {
114
+ nitroConfig.rollupConfig ??= {}
115
+ nitroConfig.rollupConfig.external = mergeExternals(
116
+ nitroConfig.rollupConfig.external,
117
+ WASM_EXTERNALS
118
+ )
119
+ }
120
+ },
121
+
90
122
  a11y: {
91
123
  logIssues: false
92
124
  },
125
+
93
126
  componentMeta: {
94
127
  metaFields: {
95
128
  type: false,
@@ -110,6 +143,7 @@ export default defineNuxtConfig({
110
143
  'nuxt-og-image'
111
144
  ]
112
145
  },
146
+
113
147
  fonts: {
114
148
  families: [
115
149
  { name: 'Public Sans', provider: 'google', global: true },
@@ -121,18 +155,16 @@ export default defineNuxtConfig({
121
155
  { name: 'Raleway', provider: 'google', global: true }
122
156
  ]
123
157
  },
158
+
124
159
  icon: {
125
160
  provider: 'iconify'
126
161
  },
162
+
127
163
  ogImage: {
128
164
  zeroRuntime: true,
129
- googleFontMirror: 'fonts.loli.net',
130
165
  fonts: [
131
166
  'Noto+Sans+SC:400',
132
- 'Noto+Sans+SC:500',
133
- 'Noto+Sans+SC:700',
134
- 'Inter:400',
135
- 'Inter:700'
167
+ 'Inter:400'
136
168
  ]
137
169
  }
138
170
  })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@movk/nuxt-docs",
3
3
  "type": "module",
4
- "version": "1.9.0",
4
+ "version": "1.11.0",
5
5
  "private": false,
6
6
  "description": "Modern Nuxt 4 documentation theme with auto-generated component docs, AI chat assistant, MCP server, and complete developer experience optimization.",
7
7
  "author": "YiXuan <mhaibaraai@gmail.com>",
@@ -14,7 +14,8 @@
14
14
  "main": "./nuxt.config.ts",
15
15
  "peerDependencies": {
16
16
  "better-sqlite3": "12.x",
17
- "nuxt": "4.x"
17
+ "nuxt": "4.x",
18
+ "tailwindcss": "4.x"
18
19
  },
19
20
  "files": [
20
21
  "app",
@@ -28,13 +29,11 @@
28
29
  "README.md"
29
30
  ],
30
31
  "dependencies": {
31
- "@ai-sdk/gateway": "^3.0.22",
32
- "@ai-sdk/mcp": "^1.0.13",
33
- "@ai-sdk/vue": "^3.0.48",
34
- "@iconify-json/lucide": "^1.2.86",
35
- "@iconify-json/ph": "^1.2.2",
36
- "@iconify-json/simple-icons": "^1.2.67",
37
- "@iconify-json/tabler": "^1.2.26",
32
+ "@ai-sdk/gateway": "^3.0.29",
33
+ "@ai-sdk/mcp": "^1.0.16",
34
+ "@ai-sdk/vue": "^3.0.64",
35
+ "@iconify-json/lucide": "^1.2.87",
36
+ "@iconify-json/simple-icons": "^1.2.68",
38
37
  "@iconify-json/vscode-icons": "^1.2.40",
39
38
  "@movk/core": "^1.1.0",
40
39
  "@nuxt/a11y": "^1.0.0-alpha.1",
@@ -43,19 +42,21 @@
43
42
  "@nuxt/kit": "^4.3.0",
44
43
  "@nuxt/ui": "^4.4.0",
45
44
  "@nuxtjs/mcp-toolkit": "^0.6.2",
45
+ "@nuxtjs/mdc": "^0.20.0",
46
46
  "@octokit/rest": "^22.0.1",
47
- "@openrouter/ai-sdk-provider": "^2.0.0",
47
+ "@openrouter/ai-sdk-provider": "^2.1.1",
48
48
  "@vercel/analytics": "^1.6.1",
49
49
  "@vercel/speed-insights": "^1.3.1",
50
- "@vueuse/core": "^14.1.0",
51
- "@vueuse/nuxt": "^14.1.0",
52
- "ai": "^6.0.48",
50
+ "@vueuse/core": "^14.2.0",
51
+ "@vueuse/nuxt": "^14.2.0",
52
+ "ai": "^6.0.64",
53
53
  "defu": "^6.1.4",
54
54
  "dompurify": "^3.3.1",
55
55
  "exsolve": "^1.0.8",
56
56
  "git-url-parse": "^16.1.0",
57
57
  "mermaid": "^11.12.2",
58
- "motion-v": "^1.9.0",
58
+ "minimark": "^0.2.0",
59
+ "motion-v": "^1.10.2",
59
60
  "nuxt": "^4.3.0",
60
61
  "nuxt-component-meta": "^0.17.1",
61
62
  "nuxt-llms": "^0.2.0",
@@ -65,11 +66,14 @@
65
66
  "pkg-types": "^2.3.0",
66
67
  "prettier": "^3.8.1",
67
68
  "scule": "^1.3.0",
68
- "shiki": "^3.21.0",
69
+ "shiki": "^3.22.0",
69
70
  "shiki-stream": "^0.1.4",
70
- "shiki-transformer-color-highlight": "^1.0.0",
71
+ "tailwind-merge": "^3.4.0",
71
72
  "tailwindcss": "^4.1.18",
72
73
  "ufo": "^1.6.3",
74
+ "vue-component-meta": "^3.2.4",
75
+ "vite-plugin-top-level-await": "^1.6.0",
76
+ "vite-plugin-wasm": "^3.5.0",
73
77
  "zod": "^4.3.6"
74
78
  }
75
79
  }
@@ -6,9 +6,10 @@ export default defineNitroPlugin((nitroApp) => {
6
6
  await transformMDC(event, doc as any)
7
7
  })
8
8
 
9
+ // @ts-expect-error - no types available
9
10
  nitroApp.hooks.hook('llms:generate', (_, { sections }) => {
10
11
  // Move "Documentation Sets" to the end
11
- const docSetIdx = sections.findIndex(s => s.title === 'Documentation Sets')
12
+ const docSetIdx = sections.findIndex((s: any) => s.title === 'Documentation Sets')
12
13
  if (docSetIdx !== -1) {
13
14
  const [docSet] = sections.splice(docSetIdx, 1)
14
15
  if (docSet) {
@@ -3,10 +3,6 @@ import { join } from 'pathe'
3
3
 
4
4
  /**
5
5
  * 匹配组件是否符合用户定义的 include 模式
6
- * @param filePath 组件文件路径
7
- * @param pascalName 组件 PascalCase 名称
8
- * @param includePatterns 包含模式数组(字符串 glob、正则表达式或函数)
9
- * @returns 是否匹配
10
6
  */
11
7
  function matchesUserInclude(
12
8
  filePath: string,
@@ -35,9 +31,6 @@ function matchesUserInclude(
35
31
 
36
32
  /**
37
33
  * 检查组件是否为用户组件
38
- * @param filePath 组件文件路径
39
- * @param userComponentPaths 用户组件路径数组
40
- * @returns 是否为用户组件
41
34
  */
42
35
  export function isUserComponent(filePath: string, userComponentPaths: string[]): boolean {
43
36
  return userComponentPaths.some(path => filePath.startsWith(path))
@@ -45,11 +38,6 @@ export function isUserComponent(filePath: string, userComponentPaths: string[]):
45
38
 
46
39
  /**
47
40
  * 创建 component-meta exclude 过滤器
48
- * @param layerPath layer 路径
49
- * @param allowedComponents 允许的组件列表
50
- * @param userComponentPaths 用户组件路径数组
51
- * @param userInclude 用户定义的 include 模式
52
- * @returns exclude 过滤器数组
53
41
  */
54
42
  export function createComponentMetaExcludeFilters(
55
43
  resolve: Resolver['resolve'],