fix(builder): watch store dir and `serverMiddleware` paths (#5681)

* fix(builder): Watch store dir to restart Nuxt app when options.store=false

* hotfix: Linting issues

* hotfix: Use path.resolve instead of path.join

* test: Update test for watcher

* hotfix: revert to path.join and fix tests

* hotfix: Fix coverage for hard to test condition

* hotfix: Fix test for Windows

* Update builder.js

* fix lint error

* fix: Cache serverMiddlewarePaths
This commit is contained in:
Sébastien Chopin 2019-05-10 15:03:07 +02:00 committed by GitHub
parent f2bd2f56de
commit 03eb049677
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 101 additions and 29 deletions

View File

@ -22,7 +22,9 @@ import {
determineGlobals,
stripWhitespace,
isString,
isIndexFileAndFolder
isIndexFileAndFolder,
isPureObject,
clearRequireCache
} from '@nuxt/utils'
import Ignore from './ignore'
@ -30,7 +32,6 @@ import BuildContext from './context/build'
import TemplateContext from './context/template'
const glob = pify(Glob)
export default class Builder {
constructor(nuxt, bundleBuilder) {
this.nuxt = nuxt
@ -616,10 +617,12 @@ export default class Builder {
let patterns = [
r(src, this.options.dir.layouts),
r(src, this.options.dir.store),
r(src, this.options.dir.middleware),
...rGlob(this.options.dir.layouts)
]
if (this.options.store) {
patterns.push(r(src, this.options.dir.store))
}
if (this._nuxtPages) {
patterns.push(
@ -648,10 +651,25 @@ export default class Builder {
this.createFileWatcher(customPatterns, ['change'], refreshFiles, this.assignWatcher('custom'))
}
getServerMiddlewarePaths() {
return this.options.serverMiddleware
.map((serverMiddleware) => {
if (isString(serverMiddleware)) {
return serverMiddleware
}
if (isPureObject(serverMiddleware) && isString(serverMiddleware.handler)) {
return serverMiddleware.handler
}
})
.filter(Boolean)
.map(p => path.extname(p) ? p : this.nuxt.resolver.resolvePath(p))
}
watchRestart() {
const serverMiddlewarePaths = this.getServerMiddlewarePaths()
const nuxtRestartWatch = [
// Server middleware
...this.options.serverMiddleware.filter(isString),
...serverMiddlewarePaths,
// Custom watchers
...this.options.watch
].map(this.nuxt.resolver.resolveAlias)
@ -659,6 +677,10 @@ export default class Builder {
if (this.ignore.ignoreFile) {
nuxtRestartWatch.push(this.ignore.ignoreFile)
}
// If store not activated, watch for a file in the directory
if (!this.options.store) {
nuxtRestartWatch.push(path.join(this.options.srcDir, this.options.dir.store))
}
this.createFileWatcher(
nuxtRestartWatch,
@ -667,6 +689,11 @@ export default class Builder {
if (['add', 'change', 'unlink'].includes(event) === false) {
return
}
/* istanbul ignore if */
if (serverMiddlewarePaths.includes(fileName)) {
consola.debug(`Clear cache for ${fileName}`)
clearRequireCache(fileName)
}
await this.nuxt.callHook('watch:fileChanged', this, fileName) // Legacy
await this.nuxt.callHook('watch:restart', { event, path: fileName })
},

View File

@ -10,6 +10,7 @@ export const createNuxt = () => ({
callHook: jest.fn(),
resolver: {
requireModule: jest.fn(() => ({ template: 'builder-template' })),
resolveAlias: jest.fn(src => `resolveAlias(${src})`)
resolveAlias: jest.fn(src => `resolveAlias(${src})`),
resolvePath: jest.fn(src => `resolvePath(${src})`)
}
})

View File

@ -1,7 +1,8 @@
import path from 'path'
import chokidar from 'chokidar'
import upath from 'upath'
import debounce from 'lodash/debounce'
import { r, isString } from '@nuxt/utils'
import { r, isString, isPureObject } from '@nuxt/utils'
import Builder from '../src/builder'
import { createNuxt } from './__utils__'
@ -41,31 +42,51 @@ describe('builder: builder watch', () => {
const patterns = [
'/var/nuxt/src/layouts',
'/var/nuxt/src/store',
'/var/nuxt/src/middleware',
'/var/nuxt/src/layouts/*.{vue,js,ts,tsx}',
'/var/nuxt/src/layouts/**/*.{vue,js,ts,tsx}'
]
expect(r).toBeCalledTimes(5)
expect(r).toBeCalledTimes(4)
expect(r).nthCalledWith(1, '/var/nuxt/src', '/var/nuxt/src/layouts')
expect(r).nthCalledWith(2, '/var/nuxt/src', '/var/nuxt/src/store')
expect(r).nthCalledWith(3, '/var/nuxt/src', '/var/nuxt/src/middleware')
expect(r).nthCalledWith(4, '/var/nuxt/src', '/var/nuxt/src/layouts/*.{vue,js,ts,tsx}')
expect(r).nthCalledWith(5, '/var/nuxt/src', '/var/nuxt/src/layouts/**/*.{vue,js,ts,tsx}')
expect(r).nthCalledWith(2, '/var/nuxt/src', '/var/nuxt/src/middleware')
expect(r).nthCalledWith(3, '/var/nuxt/src', '/var/nuxt/src/layouts/*.{vue,js,ts,tsx}')
expect(r).nthCalledWith(4, '/var/nuxt/src', '/var/nuxt/src/layouts/**/*.{vue,js,ts,tsx}')
expect(upath.normalizeSafe).toBeCalledTimes(5)
expect(upath.normalizeSafe).toBeCalledTimes(4)
expect(upath.normalizeSafe).nthCalledWith(1, '/var/nuxt/src/layouts', 0, patterns)
expect(upath.normalizeSafe).nthCalledWith(2, '/var/nuxt/src/store', 1, patterns)
expect(upath.normalizeSafe).nthCalledWith(3, '/var/nuxt/src/middleware', 2, patterns)
expect(upath.normalizeSafe).nthCalledWith(4, '/var/nuxt/src/layouts/*.{vue,js,ts,tsx}', 3, patterns)
expect(upath.normalizeSafe).nthCalledWith(5, '/var/nuxt/src/layouts/**/*.{vue,js,ts,tsx}', 4, patterns)
expect(upath.normalizeSafe).nthCalledWith(2, '/var/nuxt/src/middleware', 1, patterns)
expect(upath.normalizeSafe).nthCalledWith(3, '/var/nuxt/src/layouts/*.{vue,js,ts,tsx}', 2, patterns)
expect(upath.normalizeSafe).nthCalledWith(4, '/var/nuxt/src/layouts/**/*.{vue,js,ts,tsx}', 3, patterns)
expect(builder.createFileWatcher).toBeCalledTimes(1)
expect(builder.createFileWatcher).toBeCalledWith(patterns, ['add', 'unlink'], expect.any(Function), expect.any(Function))
expect(builder.assignWatcher).toBeCalledTimes(1)
})
test('should watch store files', () => {
const nuxt = createNuxt()
nuxt.options.store = true
nuxt.options.srcDir = '/var/nuxt/src'
nuxt.options.dir = {
layouts: '/var/nuxt/src/layouts',
pages: '/var/nuxt/src/pages',
store: '/var/nuxt/src/store',
middleware: '/var/nuxt/src/middleware'
}
nuxt.options.build.watch = []
const builder = new Builder(nuxt, {})
builder.createFileWatcher = jest.fn()
builder.assignWatcher = jest.fn(() => () => {})
r.mockImplementation((dir, src) => src)
builder.watchClient()
expect(r).toBeCalledTimes(5)
expect(r).nthCalledWith(5, '/var/nuxt/src', '/var/nuxt/src/store')
})
test('should watch pages files', () => {
const nuxt = createNuxt()
nuxt.options.srcDir = '/var/nuxt/src'
@ -86,10 +107,10 @@ describe('builder: builder watch', () => {
builder.watchClient()
expect(r).toBeCalledTimes(8)
expect(r).nthCalledWith(6, '/var/nuxt/src', '/var/nuxt/src/pages')
expect(r).nthCalledWith(7, '/var/nuxt/src', '/var/nuxt/src/pages/*.{vue,js,ts,tsx}')
expect(r).nthCalledWith(8, '/var/nuxt/src', '/var/nuxt/src/pages/**/*.{vue,js,ts,tsx}')
expect(r).toBeCalledTimes(7)
expect(r).nthCalledWith(5, '/var/nuxt/src', '/var/nuxt/src/pages')
expect(r).nthCalledWith(6, '/var/nuxt/src', '/var/nuxt/src/pages/*.{vue,js,ts,tsx}')
expect(r).nthCalledWith(7, '/var/nuxt/src', '/var/nuxt/src/pages/**/*.{vue,js,ts,tsx}')
})
test('should invoke generateRoutesAndFiles on file refresh', () => {
@ -215,6 +236,13 @@ describe('builder: builder watch', () => {
test('should watch files for restarting server', () => {
const nuxt = createNuxt()
nuxt.options.srcDir = '/var/nuxt/src'
nuxt.options.dir = {
layouts: '/var/nuxt/src/layouts',
pages: '/var/nuxt/src/pages',
store: '/var/nuxt/src/store',
middleware: '/var/nuxt/src/middleware'
}
nuxt.options.watchers = {
chokidar: { test: true }
}
@ -222,21 +250,25 @@ describe('builder: builder watch', () => {
'/var/nuxt/src/watch/test'
]
nuxt.options.serverMiddleware = [
'/var/nuxt/src/middleware/test',
'/var/nuxt/src/serverMiddleware/test',
{ path: '/test', handler: '/var/nuxt/src/serverMiddleware/test-handler' },
{ obj: 'test' }
]
const builder = new Builder(nuxt, {})
builder.ignore.ignoreFile = '/var/nuxt/src/.nuxtignore'
isString.mockImplementationOnce(src => typeof src === 'string')
isString.mockImplementation(src => typeof src === 'string')
isPureObject.mockImplementation(obj => typeof obj === 'object')
builder.watchRestart()
expect(chokidar.watch).toBeCalledTimes(1)
expect(chokidar.watch).toBeCalledWith(
[
'resolveAlias(/var/nuxt/src/middleware/test)',
'resolveAlias(resolvePath(/var/nuxt/src/serverMiddleware/test))',
'resolveAlias(resolvePath(/var/nuxt/src/serverMiddleware/test-handler))',
'resolveAlias(/var/nuxt/src/watch/test)',
'/var/nuxt/src/.nuxtignore'
'/var/nuxt/src/.nuxtignore',
path.join('/var/nuxt/src/var/nuxt/src/store') // because store == false + using path.join()
],
{ test: true }
)
@ -246,6 +278,13 @@ describe('builder: builder watch', () => {
test('should trigger restarting when files changed', async () => {
const nuxt = createNuxt()
nuxt.options.srcDir = '/var/nuxt/src'
nuxt.options.dir = {
layouts: '/var/nuxt/src/layouts',
pages: '/var/nuxt/src/pages',
store: '/var/nuxt/src/store',
middleware: '/var/nuxt/src/middleware'
}
nuxt.options.watchers = {
chokidar: { test: true }
}
@ -274,6 +313,13 @@ describe('builder: builder watch', () => {
test('should ignore other events in watchRestart', () => {
const nuxt = createNuxt()
nuxt.options.srcDir = '/var/nuxt/src'
nuxt.options.dir = {
layouts: '/var/nuxt/src/layouts',
pages: '/var/nuxt/src/pages',
store: '/var/nuxt/src/store',
middleware: '/var/nuxt/src/middleware'
}
nuxt.options.watchers = {
chokidar: { test: true }
}

View File

@ -41,7 +41,7 @@ export default {
const nuxt = await cmd.getNuxt(config)
// Setup hooks
nuxt.hook('watch:restart', payload => this.onWatchRestart(payload, { nuxt, builder, cmd, argv }))
nuxt.hook('watch:restart', payload => this.onWatchRestart(payload, { nuxt, cmd, argv }))
nuxt.hook('bundler:change', changedFileName => this.onBundlerChange(changedFileName))
// Wait for nuxt to be ready

View File

@ -6,9 +6,7 @@ export const isString = obj => typeof obj === 'string' || obj instanceof String
export const isNonEmptyString = obj => Boolean(obj && isString(obj))
export const isPureObject = function isPureObject(o) {
return !Array.isArray(o) && typeof o === 'object'
}
export const isPureObject = obj => !Array.isArray(obj) && typeof obj === 'object'
export const isUrl = function isUrl(url) {
return ['http', '//'].some(str => url.startsWith(str))