feat(nuxt): add watch option and refactor dev server restarting (#19530)

This commit is contained in:
Daniel Roe 2023-03-09 11:46:08 +00:00 committed by GitHub
parent eb1bb59542
commit 9036142b14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 83 additions and 45 deletions

View File

@ -1,7 +1,6 @@
import type { AddressInfo } from 'node:net' import type { AddressInfo } from 'node:net'
import type { RequestListener } from 'node:http' import type { RequestListener } from 'node:http'
import { existsSync, readdirSync } from 'node:fs' import { resolve, relative } from 'pathe'
import { resolve, relative, normalize } from 'pathe'
import chokidar from 'chokidar' import chokidar from 'chokidar'
import { debounce } from 'perfect-debounce' import { debounce } from 'perfect-debounce'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt } from '@nuxt/schema'
@ -160,42 +159,11 @@ export default defineNuxtCommand({
// Watch for config changes // Watch for config changes
// TODO: Watcher service, modules, and requireTree // TODO: Watcher service, modules, and requireTree
const dLoad = debounce(load) const dLoad = debounce(load)
const watcher = chokidar.watch([rootDir], { ignoreInitial: true, depth: 1 }) const watcher = chokidar.watch([rootDir], { ignoreInitial: true, depth: 0 })
watcher.on('all', (event, _file) => { watcher.on('all', (_event, _file) => {
if (!currentNuxt) { return } const file = relative(rootDir, _file)
const file = normalize(_file) if (file.match(/^(nuxt\.config\.(js|ts|mjs|cjs)|\.nuxtignore|\.env|\.nuxtrc)$/)) {
const buildDir = withTrailingSlash(normalize(currentNuxt.options.buildDir)) dLoad(true, `${file} updated`)
if (file.startsWith(buildDir)) { return }
const relativePath = relative(rootDir, file)
if (file.match(/(nuxt\.config\.(js|ts|mjs|cjs)|\.nuxtignore|\.env|\.nuxtrc)$/)) {
dLoad(true, `${relativePath} updated`)
}
const isDirChange = ['addDir', 'unlinkDir'].includes(event)
const isFileChange = ['add', 'unlink'].includes(event)
const pagesDir = resolve(currentNuxt.options.srcDir, currentNuxt.options.dir.pages)
const reloadDirs = ['components', 'composables', 'utils'].map(d => resolve(currentNuxt.options.srcDir, d))
if (isDirChange) {
if (reloadDirs.includes(file)) {
return dLoad(true, `Directory \`${relativePath}/\` ${event === 'addDir' ? 'created' : 'removed'}`)
}
}
if (isFileChange) {
if (file.match(/(app|error|app\.config)\.(js|ts|mjs|jsx|tsx|vue)$/)) {
return dLoad(true, `\`${relativePath}\` ${event === 'add' ? 'created' : 'removed'}`)
}
}
if (file.startsWith(pagesDir)) {
const hasPages = existsSync(pagesDir) ? readdirSync(pagesDir).length > 0 : false
if (currentNuxt && !currentNuxt.options.pages && hasPages) {
return dLoad(true, 'Pages enabled')
}
if (currentNuxt && currentNuxt.options.pages && !hasPages) {
return dLoad(true, 'Pages disabled')
}
} }
}) })

View File

@ -148,6 +148,17 @@ export default defineNuxtModule<ComponentsOptions>({
} }
}) })
// Restart dev server when component directories are added/removed
nuxt.hook('builder:watch', (event, path) => {
const isDirChange = ['addDir', 'unlinkDir'].includes(event)
const fullPath = resolve(nuxt.options.srcDir, path)
if (isDirChange && componentDirs.some(dir => dir.path === fullPath)) {
console.info(`Directory \`${path}/\` ${event === 'addDir' ? 'created' : 'removed'}`)
return nuxt.callHook('restart')
}
})
// Scan components and add to plugin // Scan components and add to plugin
nuxt.hook('app:templates', async () => { nuxt.hook('app:templates', async () => {
const newComponents = await scanComponents(componentDirs, nuxt.options.srcDir!) const newComponents = await scanComponents(componentDirs, nuxt.options.srcDir!)

View File

@ -161,10 +161,6 @@ async function initNuxt (nuxt: Nuxt) {
// Register user and then ad-hoc modules // Register user and then ad-hoc modules
modulesToInstall.push(...nuxt.options.modules, ...nuxt.options._modules) modulesToInstall.push(...nuxt.options.modules, ...nuxt.options._modules)
nuxt.hooks.hookOnce('builder:watch', (event, path) => {
if (watchedPaths.has(path)) { nuxt.callHook('restart', { hard: true }) }
})
// Add <NuxtWelcome> // Add <NuxtWelcome>
addComponent({ addComponent({
name: 'NuxtWelcome', name: 'NuxtWelcome',
@ -285,6 +281,29 @@ async function initNuxt (nuxt: Nuxt) {
await nuxt.callHook('modules:done') await nuxt.callHook('modules:done')
nuxt.hooks.hook('builder:watch', (event, path) => {
// Local module patterns
if (watchedPaths.has(path)) {
return nuxt.callHook('restart', { hard: true })
}
// User provided patterns
for (const pattern of nuxt.options.watch) {
if (typeof pattern === 'string') {
if (pattern === path) { return nuxt.callHook('restart') }
continue
}
if (pattern.test(path)) { return nuxt.callHook('restart') }
}
// Core Nuxt files: app.vue, error.vue and app.config.ts
const isFileChange = ['add', 'unlink'].includes(event)
if (isFileChange && path.match(/^(app|error|app\.config)\.(js|ts|mjs|jsx|tsx|vue)$/i)) {
console.info(`\`${path}\` ${event === 'add' ? 'created' : 'removed'}`)
return nuxt.callHook('restart')
}
})
// Normalize windows transpile paths added by modules // Normalize windows transpile paths added by modules
nuxt.options.build.transpile = nuxt.options.build.transpile.map(t => typeof t === 'string' ? normalize(t) : t) nuxt.options.build.transpile = nuxt.options.build.transpile.map(t => typeof t === 'string' ? normalize(t) : t)

View File

@ -61,6 +61,17 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
await nuxt.callHook('imports:dirs', composablesDirs) await nuxt.callHook('imports:dirs', composablesDirs)
composablesDirs = composablesDirs.map(dir => normalize(dir)) composablesDirs = composablesDirs.map(dir => normalize(dir))
// Restart nuxt when composable directories are added/removed
nuxt.hook('builder:watch', (event, path) => {
const isDirChange = ['addDir', 'unlinkDir'].includes(event)
const fullPath = resolve(nuxt.options.srcDir, path)
if (isDirChange && composablesDirs.includes(fullPath)) {
console.info(`Directory \`${path}/\` ${event === 'addDir' ? 'created' : 'removed'}`)
return nuxt.callHook('restart')
}
})
// Support for importing from '#imports' // Support for importing from '#imports'
addTemplate({ addTemplate({
filename: 'imports.mjs', filename: 'imports.mjs',

View File

@ -1,6 +1,6 @@
import { existsSync, readdirSync } from 'node:fs' import { existsSync, readdirSync } from 'node:fs'
import { defineNuxtModule, addTemplate, addPlugin, addVitePlugin, addWebpackPlugin, findPath, addComponent, updateTemplates } from '@nuxt/kit' import { defineNuxtModule, addTemplate, addPlugin, addVitePlugin, addWebpackPlugin, findPath, addComponent, updateTemplates } from '@nuxt/kit'
import { relative, resolve } from 'pathe' import { join, relative, resolve } from 'pathe'
import { genString, genImport, genObjectFromRawEntries } from 'knitwork' import { genString, genImport, genObjectFromRawEntries } from 'knitwork'
import escapeRE from 'escape-string-regexp' import escapeRE from 'escape-string-regexp'
import { joinURL } from 'ufo' import { joinURL } from 'ufo'
@ -21,9 +21,10 @@ export default defineNuxtModule({
// Disable module (and use universal router) if pages dir do not exists or user has disabled it // Disable module (and use universal router) if pages dir do not exists or user has disabled it
const isNonEmptyDir = (dir: string) => existsSync(dir) && readdirSync(dir).length const isNonEmptyDir = (dir: string) => existsSync(dir) && readdirSync(dir).length
const userPreference = nuxt.options.pages
const isPagesEnabled = () => { const isPagesEnabled = () => {
if (typeof nuxt.options.pages === 'boolean') { if (typeof userPreference === 'boolean') {
return nuxt.options.pages return userPreference
} }
if (nuxt.options._layers.some(layer => existsSync(resolve(layer.config.srcDir, 'app/router.options.ts')))) { if (nuxt.options._layers.some(layer => existsSync(resolve(layer.config.srcDir, 'app/router.options.ts')))) {
return true return true
@ -35,6 +36,22 @@ export default defineNuxtModule({
} }
nuxt.options.pages = isPagesEnabled() nuxt.options.pages = isPagesEnabled()
// Restart Nuxt when pages dir is added or removed
const restartPaths = nuxt.options._layers.flatMap(layer => [
join(layer.config.srcDir, 'app/router.options.ts'),
join(layer.config.srcDir, layer.config.dir?.pages || 'pages')
])
nuxt.hooks.hook('builder:watch', (event, path) => {
const fullPath = join(nuxt.options.srcDir, path)
if (restartPaths.some(path => path === fullPath || fullPath.startsWith(path + '/'))) {
const newSetting = isPagesEnabled()
if (nuxt.options.pages !== newSetting) {
console.info('Pages', newSetting ? 'enabled' : 'disabled')
return nuxt.callHook('restart')
}
}
})
if (!nuxt.options.pages) { if (!nuxt.options.pages) {
addPlugin(resolve(distDir, 'app/plugins/router')) addPlugin(resolve(distDir, 'app/plugins/router'))
addTemplate({ addTemplate({

View File

@ -345,6 +345,18 @@ export default defineUntypedSchema({
].concat(val).filter(Boolean) ].concat(val).filter(Boolean)
}, },
/**
* The watch property lets you define patterns that will restart the Nuxt dev server when changed.
*
* It is an array of strings or regular expressions, which will be matched against the file path
* relative to the project `srcDir`.
*
* @type {Array<string | RegExp>}
*/
watch: {
$resolve: val => [].concat(val).filter((b: unknown) => typeof b === 'string' || b instanceof RegExp),
},
/** /**
* The watchers property lets you overwrite watchers configuration in your `nuxt.config`. * The watchers property lets you overwrite watchers configuration in your `nuxt.config`.
*/ */