Skip to content

Commit

Permalink
feat(nuxt): add watch option and refactor dev server restarting (#1…
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe committed Mar 9, 2023
1 parent eb1bb59 commit 9036142
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 45 deletions.
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 @@ -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
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 @@ -285,6 +281,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

0 comments on commit 9036142

Please sign in to comment.