Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 6 additions & 38 deletions packages/nuxi/src/commands/dev.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { AddressInfo } from 'node:net'
import type { RequestListener } from 'node:http'
import { existsSync, readdirSync } from 'node:fs'
import { resolve, relative, normalize } from 'pathe'
import { resolve, relative } from 'pathe'
import chokidar from 'chokidar'
import { debounce } from 'perfect-debounce'
import type { Nuxt } from '@nuxt/schema'
Expand Down Expand Up @@ -160,42 +159,11 @@ export default defineNuxtCommand({
// Watch for config changes
// TODO: Watcher service, modules, and requireTree
const dLoad = debounce(load)
const watcher = chokidar.watch([rootDir], { ignoreInitial: true, depth: 1 })
watcher.on('all', (event, _file) => {
if (!currentNuxt) { return }
const file = normalize(_file)
const buildDir = withTrailingSlash(normalize(currentNuxt.options.buildDir))
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')
}
const watcher = chokidar.watch([rootDir], { ignoreInitial: true, depth: 0 })
watcher.on('all', (_event, _file) => {
const file = relative(rootDir, _file)
if (file.match(/^(nuxt\.config\.(js|ts|mjs|cjs)|\.nuxtignore|\.env|\.nuxtrc)$/)) {
dLoad(true, `${file} updated`)
}
})

Expand Down
11 changes: 11 additions & 0 deletions packages/nuxt/src/components/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,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
nuxt.hook('app:templates', async () => {
const newComponents = await scanComponents(componentDirs, nuxt.options.srcDir!)
Expand Down
27 changes: 23 additions & 4 deletions packages/nuxt/src/core/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,6 @@ async function initNuxt (nuxt: Nuxt) {
// Register user and then ad-hoc 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>
addComponent({
name: 'NuxtWelcome',
Expand Down Expand Up @@ -268,6 +264,29 @@ async function initNuxt (nuxt: Nuxt) {

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
nuxt.options.build.transpile = nuxt.options.build.transpile.map(t => typeof t === 'string' ? normalize(t) : t)

Expand Down
11 changes: 11 additions & 0 deletions packages/nuxt/src/imports/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
await nuxt.callHook('imports:dirs', composablesDirs)
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'
addTemplate({
filename: 'imports.mjs',
Expand Down
23 changes: 20 additions & 3 deletions packages/nuxt/src/pages/module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { existsSync, readdirSync } from 'node:fs'
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 escapeRE from 'escape-string-regexp'
import { joinURL } from 'ufo'
Expand All @@ -21,9 +21,10 @@ export default defineNuxtModule({

// 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 userPreference = nuxt.options.pages
const isPagesEnabled = () => {
if (typeof nuxt.options.pages === 'boolean') {
return nuxt.options.pages
if (typeof userPreference === 'boolean') {
return userPreference
}
if (nuxt.options._layers.some(layer => existsSync(resolve(layer.config.srcDir, 'app/router.options.ts')))) {
return true
Expand All @@ -35,6 +36,22 @@ export default defineNuxtModule({
}
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) {
addPlugin(resolve(distDir, 'app/plugins/router'))
addTemplate({
Expand Down
12 changes: 12 additions & 0 deletions packages/schema/src/config/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,18 @@ export default defineUntypedSchema({
].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`.
*/
Expand Down