From 03eb0496772e8d5fc2e559933b6a96c4e075dd57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Fri, 10 May 2019 15:03:07 +0200 Subject: [PATCH] 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 --- packages/builder/src/builder.js | 35 ++++++++- packages/builder/test/__utils__/index.js | 3 +- packages/builder/test/builder.watch.test.js | 86 ++++++++++++++++----- packages/cli/src/commands/dev.js | 2 +- packages/utils/src/lang.js | 4 +- 5 files changed, 101 insertions(+), 29 deletions(-) diff --git a/packages/builder/src/builder.js b/packages/builder/src/builder.js index 561e15d9a9..04ea198a79 100644 --- a/packages/builder/src/builder.js +++ b/packages/builder/src/builder.js @@ -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 }) }, diff --git a/packages/builder/test/__utils__/index.js b/packages/builder/test/__utils__/index.js index 054ce5eeda..70888b8267 100644 --- a/packages/builder/test/__utils__/index.js +++ b/packages/builder/test/__utils__/index.js @@ -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})`) } }) diff --git a/packages/builder/test/builder.watch.test.js b/packages/builder/test/builder.watch.test.js index ab9e2dd5b3..7e2fbd2639 100644 --- a/packages/builder/test/builder.watch.test.js +++ b/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__' @@ -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 } } diff --git a/packages/cli/src/commands/dev.js b/packages/cli/src/commands/dev.js index 8348c72f1a..7415a1750e 100644 --- a/packages/cli/src/commands/dev.js +++ b/packages/cli/src/commands/dev.js @@ -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 diff --git a/packages/utils/src/lang.js b/packages/utils/src/lang.js index a98f294ea3..6ce1507bee 100644 --- a/packages/utils/src/lang.js +++ b/packages/utils/src/lang.js @@ -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))