Skip to content

Commit ee06d16

Browse files
authored
feat: use Vite and Webpack server for content hot reload (#3546)
1 parent 362056a commit ee06d16

File tree

8 files changed

+142
-282
lines changed

8 files changed

+142
-282
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@
7272
"defu": "^6.1.4",
7373
"destr": "^2.0.5",
7474
"git-url-parse": "^16.1.0",
75+
"hookable": "^5.5.3",
7576
"jiti": "^2.5.1",
7677
"json-schema-to-typescript": "^15.0.4",
7778
"knitwork": "^1.2.0",
78-
"listhen": "^1.9.0",
7979
"mdast-util-to-hast": "^13.2.0",
8080
"mdast-util-to-string": "^4.0.0",
8181
"micromark": "^4.0.2",
@@ -103,7 +103,7 @@
103103
"unified": "^11.0.5",
104104
"unist-util-stringify-position": "^4.0.0",
105105
"unist-util-visit": "^5.0.0",
106-
"ws": "^8.18.3",
106+
"unplugin": "^2.3.10",
107107
"zod": "^3.25.76",
108108
"zod-to-json-schema": "^3.24.6"
109109
},

pnpm-lock.yaml

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/module.ts

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
updateTemplates,
1212
addComponent,
1313
installModule,
14+
addVitePlugin,
1415
} from '@nuxt/kit'
1516
import type { Nuxt } from '@nuxt/schema'
1617
import type { ModuleOptions as MDCModuleOptions } from '@nuxtjs/mdc'
@@ -24,7 +25,7 @@ import { generateCollectionInsert, generateCollectionTableDefinition } from './u
2425
import { componentsManifestTemplate, contentTypesTemplate, fullDatabaseRawDumpTemplate, manifestTemplate, moduleTemplates } from './utils/templates'
2526
import type { ResolvedCollection } from './types/collection'
2627
import type { ModuleOptions } from './types/module'
27-
import { getContentChecksum, logger, watchContents, chunks, watchComponents, startSocketServer } from './utils/dev'
28+
import { getContentChecksum, logger, chunks, NuxtContentHMRUnplugin } from './utils/dev'
2829
import { loadContentConfig } from './utils/config'
2930
import { createParser } from './utils/content'
3031
import { installMDCModule } from './utils/mdc'
@@ -53,15 +54,7 @@ export default defineNuxtModule<ModuleOptions>({
5354
filename: '.data/content/contents.sqlite',
5455
},
5556
preview: {},
56-
watch: {
57-
enabled: true,
58-
port: {
59-
port: 4000,
60-
portRange: [4000, 4040],
61-
},
62-
hostname: 'localhost',
63-
showURL: false,
64-
},
57+
watch: { enabled: true },
6558
renderer: {
6659
alias: {},
6760
anchorLinks: {
@@ -96,12 +89,12 @@ export default defineNuxtModule<ModuleOptions>({
9689
// Detect installed validators and them into content context
9790
await initiateValidatorsContext()
9891

99-
const { collections } = await loadContentConfig(nuxt)
92+
const { collections } = await loadContentConfig(nuxt, options)
10093
manifest.collections = collections
10194

102-
nuxt.options.vite.optimizeDeps ||= {}
103-
nuxt.options.vite.optimizeDeps.exclude ||= []
104-
nuxt.options.vite.optimizeDeps.exclude.push('@sqlite.org/sqlite-wasm')
95+
nuxt.options.vite.optimizeDeps = defu(nuxt.options.vite.optimizeDeps, {
96+
exclude: ['@sqlite.org/sqlite-wasm'],
97+
})
10598

10699
// Ignore content directory files in building
107100
nuxt.options.ignore = [...(nuxt.options.ignore || []), 'content/**']
@@ -122,16 +115,18 @@ export default defineNuxtModule<ModuleOptions>({
122115
addComponent({ name: 'ContentRenderer', filePath: resolver.resolve('./runtime/components/ContentRenderer.vue') })
123116

124117
// Add Templates & aliases
125-
nuxt.options.nitro.alias = nuxt.options.nitro.alias || {}
126118
addTemplate(fullDatabaseRawDumpTemplate(manifest))
127-
nuxt.options.alias['#content/components'] = addTemplate(componentsManifestTemplate(manifest)).dst
128-
nuxt.options.alias['#content/manifest'] = addTemplate(manifestTemplate(manifest)).dst
119+
nuxt.options.alias = defu(nuxt.options.alias, {
120+
'#content/components': addTemplate(componentsManifestTemplate(manifest)).dst,
121+
'#content/manifest': addTemplate(manifestTemplate(manifest)).dst,
122+
})
129123

130124
// Add content types to Nuxt and Nitro
131125
const typesTemplateDst = addTypeTemplate(contentTypesTemplate(manifest.collections)).dst
132-
nuxt.options.nitro.typescript ||= {}
133-
nuxt.options.nitro.typescript.tsConfig = defu(nuxt.options.nitro.typescript.tsConfig, {
134-
include: [typesTemplateDst],
126+
nuxt.options.nitro.typescript = defu(nuxt.options.nitro.typescript, {
127+
tsConfig: {
128+
include: [typesTemplateDst],
129+
},
135130
})
136131

137132
// Register user components
@@ -195,11 +190,13 @@ export default defineNuxtModule<ModuleOptions>({
195190
})
196191

197192
// Handle HMR changes
198-
if (nuxt.options.dev) {
193+
if (nuxt.options.dev && options.watch?.enabled !== false) {
199194
addPlugin({ src: resolver.resolve('./runtime/plugins/websocket.dev'), mode: 'client' })
200-
await watchComponents(nuxt)
201-
const socket = await startSocketServer(nuxt, options, manifest)
202-
await watchContents(nuxt, options, manifest, socket)
195+
addVitePlugin(NuxtContentHMRUnplugin.vite({
196+
nuxt,
197+
moduleOptions: options,
198+
manifest,
199+
}))
203200
}
204201
})
205202

src/runtime/internal/websocket.ts

Lines changed: 0 additions & 101 deletions
This file was deleted.
Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
import { defineNuxtPlugin } from 'nuxt/app'
2-
import { useRuntimeConfig } from '#imports'
2+
import { refreshNuxtData } from '#imports'
33

4+
type HotEvent = (event: 'nuxt-content:update', callback: (data: { collection: string, key: string, queries: string[] }) => void) => void
45
export default defineNuxtPlugin(() => {
5-
const publicConfig = useRuntimeConfig().public.content as { wsUrl: string }
6+
if (!import.meta.hot || !import.meta.client) return
67

7-
if (import.meta.client && publicConfig.wsUrl) {
8-
// Connect to websocket
9-
import('../internal/websocket').then(({ useContentWebSocket }) => useContentWebSocket())
10-
}
8+
import('../internal/database.client').then(({ loadDatabaseAdapter }) => {
9+
;(import.meta.hot as unknown as { on: HotEvent }).on('nuxt-content:update', async (data) => {
10+
if (!data || !data.collection || !Array.isArray(data.queries)) return
11+
try {
12+
const db = await loadDatabaseAdapter(data.collection)
13+
for (const sql of data.queries) {
14+
try {
15+
await db.exec(sql)
16+
}
17+
catch (err) {
18+
console.log(err)
19+
}
20+
}
21+
refreshNuxtData()
22+
}
23+
catch {
24+
// ignore
25+
}
26+
})
27+
})
1128
})

src/types/module.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { ListenOptions } from 'listhen'
21
import type { LanguageRegistration, BuiltinLanguage as ShikiLang, BuiltinTheme as ShikiTheme, ThemeRegistrationAny, ThemeRegistrationRaw } from 'shiki'
32
import type { GitInfo } from '../utils/git'
43
import type { MarkdownPlugin } from './content'
@@ -70,7 +69,7 @@ export interface ModuleOptions {
7069
* Development HMR
7170
* @default { enabled: true }
7271
*/
73-
watch?: Partial<ListenOptions> & { enabled?: boolean }
72+
watch?: { enabled?: boolean }
7473

7574
renderer: {
7675
/**

src/utils/config.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { loadConfig, watchConfig, createDefineConfig } from 'c12'
22
import { relative } from 'pathe'
33
import type { Nuxt } from '@nuxt/schema'
4-
import type { DefinedCollection } from '../types'
4+
import type { DefinedCollection, ModuleOptions } from '../types'
55
import { defineCollection, resolveCollections } from './collection'
66
import { logger } from './dev'
77

@@ -20,8 +20,9 @@ const defaultConfig: NuxtContentConfig = {
2020

2121
export const defineContentConfig = createDefineConfig<NuxtContentConfig>()
2222

23-
export async function loadContentConfig(nuxt: Nuxt) {
24-
const loader: typeof watchConfig = nuxt.options.dev
23+
export async function loadContentConfig(nuxt: Nuxt, options?: ModuleOptions) {
24+
const watch = nuxt.options.dev && options?.watch?.enabled !== false
25+
const loader: typeof watchConfig = watch
2526
? opts => watchConfig({
2627
...opts,
2728
onWatch: (e) => {
@@ -44,7 +45,7 @@ export async function loadContentConfig(nuxt: Nuxt) {
4445
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4546
delete (globalThis as any).defineContentConfig
4647

47-
if (nuxt.options.dev) {
48+
if (watch) {
4849
nuxt.hook('close', () => Promise.all(contentConfigs.map(c => c.unwatch())).then(() => {}))
4950
}
5051

0 commit comments

Comments
 (0)