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

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

Merged
merged 12 commits into from May 10, 2019
34 changes: 30 additions & 4 deletions packages/builder/src/builder.js
Expand Up @@ -22,15 +22,16 @@ import {
determineGlobals,
stripWhitespace,
isString,
isIndexFileAndFolder
isIndexFileAndFolder,
isPureObject,
clearRequireCache
} from '@nuxt/utils'

import Ignore from './ignore'
import BuildContext from './context/build'
import TemplateContext from './context/template'

const glob = pify(Glob)

export default class Builder {
constructor(nuxt, bundleBuilder) {
this.nuxt = nuxt
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -648,17 +651,35 @@ export default class Builder {
this.createFileWatcher(customPatterns, ['change'], refreshFiles, this.assignWatcher('custom'))
}

get serverMiddlewarePaths() {
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 nuxtRestartWatch = [
// Server middleware
...this.options.serverMiddleware.filter(isString),
...this.serverMiddlewarePaths,
// Custom watchers
...this.options.watch
].map(this.nuxt.resolver.resolveAlias)

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,
Expand All @@ -667,6 +688,11 @@ export default class Builder {
if (['add', 'change', 'unlink'].includes(event) === false) {
return
}
/* istanbul ignore if */
if (this.serverMiddlewarePaths.includes(fileName)) {
Atinux marked this conversation as resolved.
Show resolved Hide resolved
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 })
},
Expand Down
3 changes: 2 additions & 1 deletion packages/builder/test/__utils__/index.js
Expand Up @@ -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})`)
}
})
86 changes: 66 additions & 20 deletions packages/builder/test/builder.watch.test.js
@@ -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__'
Expand Down Expand Up @@ -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'
Expand All @@ -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', () => {
Expand Down Expand Up @@ -215,28 +236,39 @@ 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 }
}
nuxt.options.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 }
)
Expand All @@ -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 }
}
Expand Down Expand Up @@ -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 }
}
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/dev.js
Expand Up @@ -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 }))
Atinux marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
4 changes: 1 addition & 3 deletions packages/utils/src/lang.js
Expand Up @@ -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))
Expand Down