mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 14:15:13 +00:00
fix: improvements for build and dev stability (#4470)
This commit is contained in:
parent
669ffa51ed
commit
fe0516978a
0
examples/hello-world/nuxt.config.js
Normal file
0
examples/hello-world/nuxt.config.js
Normal file
@ -8,14 +8,11 @@ import pify from 'pify'
|
||||
import serialize from 'serialize-javascript'
|
||||
import upath from 'upath'
|
||||
|
||||
import concat from 'lodash/concat'
|
||||
import debounce from 'lodash/debounce'
|
||||
import map from 'lodash/map'
|
||||
import omit from 'lodash/omit'
|
||||
import template from 'lodash/template'
|
||||
import uniq from 'lodash/uniq'
|
||||
import uniqBy from 'lodash/uniqBy'
|
||||
import values from 'lodash/values'
|
||||
|
||||
import devalue from '@nuxtjs/devalue'
|
||||
|
||||
@ -59,10 +56,11 @@ export default class Builder {
|
||||
this.nuxt.hook('build:done', () => {
|
||||
consola.info('Waiting for file changes')
|
||||
this.watchClient()
|
||||
this.watchRestart()
|
||||
})
|
||||
|
||||
// Stop watching on nuxt.close()
|
||||
this.nuxt.hook('close', () => this.unwatch())
|
||||
// Close hook
|
||||
this.nuxt.hook('close', () => this.close())
|
||||
}
|
||||
|
||||
if (this.options.build.analyze) {
|
||||
@ -502,6 +500,7 @@ export default class Builder {
|
||||
|
||||
watchClient() {
|
||||
const src = this.options.srcDir
|
||||
|
||||
let patterns = [
|
||||
r(src, this.options.dir.layouts),
|
||||
r(src, this.options.dir.store),
|
||||
@ -509,6 +508,7 @@ export default class Builder {
|
||||
r(src, `${this.options.dir.layouts}/*.{vue,js}`),
|
||||
r(src, `${this.options.dir.layouts}/**/*.{vue,js}`)
|
||||
]
|
||||
|
||||
if (this._nuxtPages) {
|
||||
patterns.push(
|
||||
r(src, this.options.dir.pages),
|
||||
@ -516,7 +516,8 @@ export default class Builder {
|
||||
r(src, `${this.options.dir.pages}/**/*.{vue,js}`)
|
||||
)
|
||||
}
|
||||
patterns = map(patterns, upath.normalizeSafe)
|
||||
|
||||
patterns = patterns.map(upath.normalizeSafe)
|
||||
|
||||
const options = this.options.watchers.chokidar
|
||||
/* istanbul ignore next */
|
||||
@ -529,43 +530,49 @@ export default class Builder {
|
||||
.on('unlink', refreshFiles)
|
||||
|
||||
// Watch for custom provided files
|
||||
let customPatterns = concat(
|
||||
this.options.build.watch,
|
||||
...values(omit(this.options.build.styleResources, ['options']))
|
||||
)
|
||||
customPatterns = map(uniq(customPatterns), upath.normalizeSafe)
|
||||
const customPatterns = uniq([
|
||||
...this.options.build.watch,
|
||||
...Object.values(omit(this.options.build.styleResources, ['options']))
|
||||
]).map(upath.normalizeSafe)
|
||||
|
||||
this.watchers.custom = chokidar
|
||||
.watch(customPatterns, options)
|
||||
.on('change', refreshFiles)
|
||||
}
|
||||
|
||||
watchServer() {
|
||||
const nuxtRestartWatch = concat(
|
||||
this.options.serverMiddleware
|
||||
.filter(isString)
|
||||
.map(this.nuxt.resolver.resolveAlias),
|
||||
this.options.watch.map(this.nuxt.resolver.resolveAlias),
|
||||
path.join(this.options.rootDir, 'nuxt.config.js')
|
||||
)
|
||||
watchRestart() {
|
||||
const nuxtRestartWatch = [
|
||||
// Server middleware
|
||||
...this.options.serverMiddleware.filter(isString),
|
||||
// Custom watchers
|
||||
...this.options.watch
|
||||
].map(this.nuxt.resolver.resolveAlias)
|
||||
|
||||
this.watchers.restart = chokidar
|
||||
.watch(nuxtRestartWatch, this.options.watchers.chokidar)
|
||||
.on('change', (_path) => {
|
||||
this.watchers.restart.close()
|
||||
const { name, ext } = path.parse(_path)
|
||||
this.nuxt.callHook('watch:fileChanged', this, `${name}${ext}`)
|
||||
.on('all', (event, _path) => {
|
||||
if (['add', 'change', 'unlink'].includes(event) === false) return
|
||||
this.nuxt.callHook('watch:fileChanged', this, _path) // Legacy
|
||||
this.nuxt.callHook('watch:restart', { event, path: _path })
|
||||
})
|
||||
}
|
||||
|
||||
async unwatch() {
|
||||
unwatch() {
|
||||
for (const watcher in this.watchers) {
|
||||
if (this.watchers[watcher]) {
|
||||
this.watchers[watcher].close()
|
||||
}
|
||||
this.watchers[watcher].close()
|
||||
}
|
||||
}
|
||||
|
||||
if (this.bundleBuilder.unwatch) {
|
||||
await this.bundleBuilder.unwatch()
|
||||
async close() {
|
||||
if (this.__closed) return
|
||||
this.__closed = true
|
||||
|
||||
// Unwatch
|
||||
this.unwatch()
|
||||
|
||||
// Close bundleBuilder
|
||||
if (typeof this.bundleBuilder.close === 'function') {
|
||||
await this.bundleBuilder.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,8 @@ import * as commands from './commands'
|
||||
import * as imports from './imports'
|
||||
|
||||
export default class NuxtCommand {
|
||||
constructor({ name, description, usage, options, run } = {}) {
|
||||
this.name = name || ''
|
||||
this.description = description || ''
|
||||
this.usage = usage || ''
|
||||
this.options = Object.assign({}, options)
|
||||
this._run = run
|
||||
constructor(cmd = { name: '', usage: '', description: '', options: {} }) {
|
||||
this.cmd = cmd
|
||||
}
|
||||
|
||||
static async load(name) {
|
||||
@ -33,7 +29,7 @@ export default class NuxtCommand {
|
||||
}
|
||||
|
||||
run() {
|
||||
return this._run(this)
|
||||
return this.cmd.run(this)
|
||||
}
|
||||
|
||||
showVersion() {
|
||||
@ -63,8 +59,8 @@ export default class NuxtCommand {
|
||||
const config = await loadNuxtConfig(argv)
|
||||
const options = Object.assign(config, extraOptions || {})
|
||||
|
||||
for (const name of Object.keys(this.options)) {
|
||||
this.options[name].prepare && this.options[name].prepare(this, options, argv)
|
||||
for (const name of Object.keys(this.cmd.options)) {
|
||||
this.cmd.options[name].prepare && this.cmd.options[name].prepare(this, options, argv)
|
||||
}
|
||||
|
||||
return options
|
||||
@ -97,8 +93,8 @@ export default class NuxtCommand {
|
||||
default: {}
|
||||
}
|
||||
|
||||
for (const name of Object.keys(this.options)) {
|
||||
const option = this.options[name]
|
||||
for (const name of Object.keys(this.cmd.options)) {
|
||||
const option = this.cmd.options[name]
|
||||
|
||||
if (option.alias) {
|
||||
minimistOptions.alias[option.alias] = name
|
||||
@ -118,8 +114,8 @@ export default class NuxtCommand {
|
||||
const options = []
|
||||
let maxOptionLength = 0
|
||||
|
||||
for (const name in this.options) {
|
||||
const option = this.options[name]
|
||||
for (const name in this.cmd.options) {
|
||||
const option = this.cmd.options[name]
|
||||
|
||||
let optionHelp = '--'
|
||||
optionHelp += option.type === 'boolean' && option.default ? 'no-' : ''
|
||||
@ -141,12 +137,12 @@ export default class NuxtCommand {
|
||||
)
|
||||
}).join('\n')
|
||||
|
||||
const usage = foldLines(`Usage: nuxt ${this.usage} [options]`, startSpaces)
|
||||
const description = foldLines(this.description, startSpaces)
|
||||
const usage = foldLines(`Usage: nuxt ${this.cmd.usage} [options]`, startSpaces)
|
||||
const description = foldLines(this.cmd.description, startSpaces)
|
||||
const opts = foldLines(`Options:`, startSpaces) + '\n\n' + _opts
|
||||
|
||||
let helpText = colorize(`${usage}\n\n`)
|
||||
if (this.description) helpText += colorize(`${description}\n\n`)
|
||||
if (this.cmd.description) helpText += colorize(`${description}\n\n`)
|
||||
if (options.length) helpText += colorize(`${opts}\n\n`)
|
||||
|
||||
return helpText
|
||||
|
@ -1,8 +1,7 @@
|
||||
import consola from 'consola'
|
||||
import chalk from 'chalk'
|
||||
import env from 'std-env'
|
||||
import { common, server } from '../options'
|
||||
import { showBanner } from '../utils'
|
||||
import { showBanner, eventsMapping, formatPath } from '../utils'
|
||||
|
||||
export default {
|
||||
name: 'dev',
|
||||
@ -12,68 +11,68 @@ export default {
|
||||
...common,
|
||||
...server
|
||||
},
|
||||
|
||||
async run(cmd) {
|
||||
const argv = cmd.getArgv()
|
||||
await this.startDev(cmd, argv)
|
||||
},
|
||||
|
||||
const errorHandler = (err, instance) => {
|
||||
instance && instance.builder.watchServer()
|
||||
consola.error(err)
|
||||
async startDev(cmd, argv) {
|
||||
try {
|
||||
await this._startDev(cmd, argv)
|
||||
} catch (error) {
|
||||
consola.error(error)
|
||||
}
|
||||
},
|
||||
|
||||
// Start dev
|
||||
async function startDev(oldInstance) {
|
||||
let nuxt, builder
|
||||
async _startDev(cmd, argv) {
|
||||
// Load config
|
||||
const config = await cmd.getNuxtConfig(argv, { dev: true })
|
||||
|
||||
try {
|
||||
nuxt = await cmd.getNuxt(
|
||||
await cmd.getNuxtConfig(argv, { dev: true })
|
||||
)
|
||||
builder = await cmd.getBuilder(nuxt)
|
||||
} catch (err) {
|
||||
return errorHandler(err, oldInstance)
|
||||
}
|
||||
// Initialize nuxt instance
|
||||
const nuxt = await cmd.getNuxt(config)
|
||||
|
||||
const logChanged = (name) => {
|
||||
consola.log({
|
||||
type: 'change',
|
||||
icon: chalk.blue.bold(env.windows ? '»' : '↻'),
|
||||
message: chalk.blue(name)
|
||||
})
|
||||
}
|
||||
// Setup hooks
|
||||
nuxt.hook('watch:restart', payload => this.onWatchRestart(payload, { nuxt, builder, cmd, argv }))
|
||||
nuxt.hook('bundler:change', changedFileName => this.onBundlerChange(changedFileName))
|
||||
|
||||
nuxt.hook('watch:fileChanged', async (builder, name) => {
|
||||
logChanged(name)
|
||||
await startDev({ nuxt: builder.nuxt, builder })
|
||||
})
|
||||
// Start listening
|
||||
await nuxt.server.listen()
|
||||
|
||||
nuxt.hook('bundler:change', (name) => {
|
||||
logChanged(name)
|
||||
})
|
||||
// Create builder instance
|
||||
const builder = await cmd.getBuilder(nuxt)
|
||||
|
||||
return (
|
||||
Promise.resolve()
|
||||
.then(() => oldInstance && oldInstance.nuxt.clearHook('watch:fileChanged'))
|
||||
.then(() => oldInstance && oldInstance.builder.unwatch())
|
||||
// Start build
|
||||
.then(() => builder.build())
|
||||
// Close old nuxt no matter if build successfully
|
||||
.catch((err) => {
|
||||
oldInstance && oldInstance.nuxt.close()
|
||||
// Jump to errorHandler
|
||||
throw err
|
||||
})
|
||||
.then(() => oldInstance && oldInstance.nuxt.close())
|
||||
// Start listening
|
||||
.then(() => nuxt.server.listen())
|
||||
// Show banner
|
||||
.then(() => showBanner(nuxt))
|
||||
// Start watching serverMiddleware changes
|
||||
.then(() => builder.watchServer())
|
||||
// Handle errors
|
||||
.catch(err => errorHandler(err, { builder, nuxt }))
|
||||
)
|
||||
}
|
||||
// Start Build
|
||||
await builder.build()
|
||||
|
||||
await startDev()
|
||||
// Show banner after build
|
||||
showBanner(nuxt)
|
||||
|
||||
// Return instance
|
||||
return nuxt
|
||||
},
|
||||
|
||||
logChanged({ event, path }) {
|
||||
const { icon, color, action } = eventsMapping[event] || eventsMapping.change
|
||||
consola.log({
|
||||
type: event,
|
||||
icon: chalk[color].bold(icon),
|
||||
message: `${action} ${chalk.cyan(path)}`
|
||||
})
|
||||
},
|
||||
|
||||
async onWatchRestart({ event, path }, { nuxt, cmd, argv }) {
|
||||
this.logChanged({
|
||||
event,
|
||||
path: formatPath(path)
|
||||
})
|
||||
|
||||
await nuxt.close()
|
||||
|
||||
await this.startDev(cmd, argv)
|
||||
},
|
||||
|
||||
onBundlerChange(changedFileName) {
|
||||
this.logChanged(changedFileName)
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,12 @@ const _require = esm(module, {
|
||||
}
|
||||
})
|
||||
|
||||
export const eventsMapping = {
|
||||
add: { icon: '+', color: 'green', action: 'Created' },
|
||||
change: { icon: env.windows ? '»' : '↻', color: 'blue', action: 'Updated' },
|
||||
unlink: { icon: '-', color: 'red', action: 'Removed' }
|
||||
}
|
||||
|
||||
const getRootDir = argv => path.resolve(argv._[0] || '.')
|
||||
const getNuxtConfigFile = argv => path.resolve(getRootDir(argv), argv['config-file'])
|
||||
|
||||
@ -45,6 +51,9 @@ export async function loadNuxtConfig(argv) {
|
||||
consola.fatal('Error while fetching async configuration')
|
||||
}
|
||||
}
|
||||
|
||||
// Keep _nuxtConfigFile for watching
|
||||
options._nuxtConfigFile = nuxtConfigFile
|
||||
} else if (argv['config-file'] !== 'nuxt.config.js') {
|
||||
consola.fatal('Could not load config file: ' + argv['config-file'])
|
||||
}
|
||||
@ -128,3 +137,10 @@ export function normalizeArg(arg, defaultValue) {
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
export function formatPath(filePath) {
|
||||
if (!filePath) {
|
||||
return
|
||||
}
|
||||
return filePath.replace(process.cwd() + path.sep, '')
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ describe('dev', () => {
|
||||
expect(typeof dev.run).toBe('function')
|
||||
})
|
||||
|
||||
test('reloads on fileChanged hook', async () => {
|
||||
test('reloads on fileRestartHook', async () => {
|
||||
const Nuxt = mockNuxt()
|
||||
const Builder = mockBuilder()
|
||||
|
||||
@ -23,21 +23,18 @@ describe('dev', () => {
|
||||
|
||||
expect(Builder.prototype.build).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.server.listen).toHaveBeenCalled()
|
||||
expect(Builder.prototype.watchServer).toHaveBeenCalled()
|
||||
// expect(Builder.prototype.watchRestart).toHaveBeenCalled()
|
||||
|
||||
jest.clearAllMocks()
|
||||
|
||||
const builder = new Builder()
|
||||
builder.nuxt = new Nuxt()
|
||||
await Nuxt.fileChangedHook(builder)
|
||||
await Nuxt.fileRestartHook(builder)
|
||||
expect(consola.log).toHaveBeenCalled()
|
||||
|
||||
expect(Nuxt.prototype.clearHook).toHaveBeenCalled()
|
||||
expect(Builder.prototype.unwatch).toHaveBeenCalled()
|
||||
expect(Builder.prototype.build).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.close).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.server.listen).toHaveBeenCalled()
|
||||
expect(Builder.prototype.watchServer).toHaveBeenCalled()
|
||||
|
||||
expect(consola.error).not.toHaveBeenCalled()
|
||||
})
|
||||
@ -53,13 +50,13 @@ describe('dev', () => {
|
||||
const builder = new Builder()
|
||||
builder.nuxt = new Nuxt()
|
||||
Builder.prototype.build = jest.fn().mockImplementationOnce(() => Promise.reject(new Error('Build Error')))
|
||||
await Nuxt.fileChangedHook(builder)
|
||||
await Nuxt.fileRestartHook(builder)
|
||||
|
||||
expect(Nuxt.prototype.close).toHaveBeenCalled()
|
||||
expect(consola.error).toHaveBeenCalledWith(new Error('Build Error'))
|
||||
})
|
||||
|
||||
test('catches watchServer error', async () => {
|
||||
test.skip('catches watchRestart error', async () => {
|
||||
const Nuxt = mockNuxt()
|
||||
const Builder = mockBuilder()
|
||||
|
||||
@ -68,11 +65,11 @@ describe('dev', () => {
|
||||
|
||||
const builder = new Builder()
|
||||
builder.nuxt = new Nuxt()
|
||||
Builder.prototype.watchServer = jest.fn().mockImplementationOnce(() => Promise.reject(new Error('watchServer Error')))
|
||||
await Nuxt.fileChangedHook(builder)
|
||||
Builder.prototype.watchRestart = jest.fn().mockImplementationOnce(() => Promise.reject(new Error('watchRestart Error')))
|
||||
await Nuxt.fileRestartHook(builder)
|
||||
|
||||
expect(consola.error).toHaveBeenCalledWith(new Error('watchServer Error'))
|
||||
expect(Builder.prototype.watchServer).toHaveBeenCalledTimes(2)
|
||||
expect(consola.error).toHaveBeenCalledWith(new Error('watchRestart Error'))
|
||||
expect(Builder.prototype.watchRestart).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
test('catches error on hook error', async () => {
|
||||
@ -87,10 +84,10 @@ describe('dev', () => {
|
||||
})
|
||||
const builder = new Builder()
|
||||
builder.nuxt = new Nuxt()
|
||||
await Nuxt.fileChangedHook(builder)
|
||||
await Nuxt.fileRestartHook(builder)
|
||||
|
||||
expect(consola.error).toHaveBeenCalledWith(new Error('Config Error'))
|
||||
expect(Builder.prototype.watchServer).toHaveBeenCalledTimes(1)
|
||||
// expect(Builder.prototype.watchRestart).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('catches error on startDev', async () => {
|
||||
|
@ -84,12 +84,13 @@ export const mockNuxt = (implementation) => {
|
||||
const Nuxt = function () {}
|
||||
Object.assign(Nuxt.prototype, {
|
||||
hook(type, fn) {
|
||||
if (type === 'watch:fileChanged') {
|
||||
Nuxt.fileChangedHook = fn
|
||||
if (type === 'watch:restart') {
|
||||
Nuxt.fileRestartHook = fn
|
||||
}
|
||||
},
|
||||
options: {},
|
||||
clearHook: jest.fn(),
|
||||
clearHooks: jest.fn(),
|
||||
close: jest.fn(),
|
||||
ready: jest.fn(),
|
||||
server: {
|
||||
@ -108,7 +109,7 @@ export const mockBuilder = (implementation) => {
|
||||
Object.assign(Builder.prototype, {
|
||||
build: jest.fn().mockImplementationOnce(() => Promise.resolve()),
|
||||
unwatch: jest.fn().mockImplementationOnce(() => Promise.resolve()),
|
||||
watchServer: jest.fn().mockImplementationOnce(() => Promise.resolve())
|
||||
watchRestart: jest.fn().mockImplementationOnce(() => Promise.resolve())
|
||||
}, implementation || {})
|
||||
|
||||
imports.builder.mockImplementation(() => ({ Builder }))
|
||||
|
@ -45,6 +45,10 @@ export default class Hookable {
|
||||
}
|
||||
}
|
||||
|
||||
clearHooks() {
|
||||
this._hooks = {}
|
||||
}
|
||||
|
||||
flatHooks(configHooks, hooks = {}, parentName) {
|
||||
Object.keys(configHooks).forEach((key) => {
|
||||
const subHook = configHooks[key]
|
||||
|
@ -27,6 +27,7 @@ export default () => ({
|
||||
serverMiddleware: [],
|
||||
|
||||
// Dirs and extensions
|
||||
_nuxtConfigFile: undefined,
|
||||
srcDir: undefined,
|
||||
buildDir: '.nuxt',
|
||||
modulesDir: [
|
||||
|
@ -10,8 +10,14 @@ import { guardDir, isNonEmptyString, isPureObject, isUrl } from '@nuxt/common'
|
||||
import { getDefaultNuxtConfig } from './config'
|
||||
|
||||
export function getNuxtConfig(_options) {
|
||||
// Prevent duplicate calls
|
||||
if (_options.__normalized__) {
|
||||
return _options
|
||||
}
|
||||
|
||||
// Clone options to prevent unwanted side-effects
|
||||
const options = Object.assign({}, _options)
|
||||
options.__normalized__ = true
|
||||
|
||||
// Normalize options
|
||||
if (options.loading === true) {
|
||||
@ -76,6 +82,14 @@ export function getNuxtConfig(_options) {
|
||||
// Resolve buildDir
|
||||
options.buildDir = path.resolve(options.rootDir, options.buildDir)
|
||||
|
||||
// Default value for _nuxtConfigFile
|
||||
if (!options._nuxtConfigFile) {
|
||||
options._nuxtConfigFile = path.resolve(options.rootDir, 'nuxt.config.js')
|
||||
}
|
||||
|
||||
// Watch for _nuxtConfigFile changes
|
||||
options.watch.push(options._nuxtConfigFile)
|
||||
|
||||
// Protect rootDir against buildDir
|
||||
guardDir(options, 'rootDir', 'buildDir')
|
||||
|
||||
|
@ -79,5 +79,7 @@ export default class Nuxt extends Hookable {
|
||||
if (typeof callback === 'function') {
|
||||
await callback()
|
||||
}
|
||||
|
||||
this.clearHooks()
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import path from 'path'
|
||||
import consola from 'consola'
|
||||
import launchMiddleware from 'launch-editor-middleware'
|
||||
import serveStatic from 'serve-static'
|
||||
import servePlaceholder from 'serve-placeholder'
|
||||
@ -35,6 +36,9 @@ export default class Server {
|
||||
|
||||
// Create new connect instance
|
||||
this.app = connect()
|
||||
|
||||
// Close hook
|
||||
this.nuxt.hook('close', () => this.close())
|
||||
}
|
||||
|
||||
async ready() {
|
||||
@ -52,14 +56,6 @@ export default class Server {
|
||||
|
||||
// Call done hook
|
||||
await this.nuxt.callHook('render:done', this)
|
||||
|
||||
// Close all listeners after nuxt close
|
||||
this.nuxt.hook('close', async () => {
|
||||
for (const listener of this.listeners) {
|
||||
await listener.close()
|
||||
}
|
||||
this.listeners = []
|
||||
})
|
||||
}
|
||||
|
||||
async setupMiddleware() {
|
||||
@ -175,16 +171,18 @@ export default class Server {
|
||||
}
|
||||
|
||||
useMiddleware(middleware) {
|
||||
// Resolve middleware
|
||||
if (typeof middleware === 'string') {
|
||||
middleware = this.nuxt.resolver.requireModule(middleware)
|
||||
}
|
||||
let handler = middleware.handler || middleware
|
||||
|
||||
// Resolve handler
|
||||
if (typeof middleware.handler === 'string') {
|
||||
middleware.handler = this.nuxt.resolver.requireModule(middleware.handler)
|
||||
// Resolve handler setup as string (path)
|
||||
if (typeof handler === 'string') {
|
||||
try {
|
||||
handler = this.nuxt.resolver.requireModule(middleware.handler || middleware)
|
||||
} catch (err) {
|
||||
if (!this.options.dev) throw err[0]
|
||||
// Only warn missing file in development
|
||||
consola.warn(err[0])
|
||||
}
|
||||
}
|
||||
const handler = middleware.handler || middleware
|
||||
|
||||
// Resolve path
|
||||
const path = (
|
||||
@ -231,4 +229,25 @@ export default class Server {
|
||||
|
||||
await this.nuxt.callHook('listen', listener.server, listener)
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this.__closed) return
|
||||
this.__closed = true
|
||||
|
||||
for (const listener of this.listeners) {
|
||||
await listener.close()
|
||||
}
|
||||
this.listeners = []
|
||||
|
||||
if (typeof this.renderer.close === 'function') {
|
||||
await this.renderer.close()
|
||||
}
|
||||
|
||||
this.app.removeAllListeners()
|
||||
this.app = null
|
||||
|
||||
for (const key in this.resources) {
|
||||
delete this.resources[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -448,4 +448,13 @@ export default class VueRenderer {
|
||||
interpolate: /{{([\s\S]+?)}}/g
|
||||
})
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.__closed) return
|
||||
this.__closed = true
|
||||
|
||||
for (const key in this.renderer) {
|
||||
delete this.renderer[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,8 +30,10 @@ export class WebpackBundler {
|
||||
// Initialize shared MFS for dev
|
||||
if (this.context.options.dev) {
|
||||
this.mfs = new MFS()
|
||||
this.mfs.exists = function (...args) { return Promise.resolve(this.existsSync(...args)) }
|
||||
this.mfs.readFile = function (...args) { return Promise.resolve(this.readFileSync(...args)) }
|
||||
|
||||
// TODO: Enable when async FS rquired
|
||||
// this.mfs.exists = function (...args) { return Promise.resolve(this.existsSync(...args)) }
|
||||
// this.mfs.readFile = function (...args) { return Promise.resolve(this.readFileSync(...args)) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,63 +123,59 @@ export class WebpackBundler {
|
||||
})
|
||||
}
|
||||
|
||||
webpackCompile(compiler) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const name = compiler.options.name
|
||||
const { nuxt, options } = this.context
|
||||
async webpackCompile(compiler) {
|
||||
const name = compiler.options.name
|
||||
const { nuxt, options } = this.context
|
||||
|
||||
await nuxt.callHook('build:compile', { name, compiler })
|
||||
await nuxt.callHook('build:compile', { name, compiler })
|
||||
|
||||
// Load renderer resources after build
|
||||
compiler.hooks.done.tap('load-resources', async (stats) => {
|
||||
await nuxt.callHook('build:compiled', {
|
||||
name,
|
||||
compiler,
|
||||
stats
|
||||
})
|
||||
|
||||
// Reload renderer if available
|
||||
await nuxt.callHook('build:resources', this.mfs)
|
||||
|
||||
// Resolve on next tick
|
||||
process.nextTick(resolve)
|
||||
// Load renderer resources after build
|
||||
compiler.hooks.done.tap('load-resources', async (stats) => {
|
||||
await nuxt.callHook('build:compiled', {
|
||||
name,
|
||||
compiler,
|
||||
stats
|
||||
})
|
||||
|
||||
if (options.dev) {
|
||||
// --- Dev Build ---
|
||||
// Client Build, watch is started by dev-middleware
|
||||
if (['client', 'modern'].includes(name)) {
|
||||
return this.webpackDev(compiler)
|
||||
}
|
||||
// Server, build and watch for changes
|
||||
this.compilersWatching.push(
|
||||
compiler.watch(options.watchers.webpack, (err) => {
|
||||
/* istanbul ignore if */
|
||||
if (err) return reject(err)
|
||||
})
|
||||
)
|
||||
} else {
|
||||
// --- Production Build ---
|
||||
compiler.run((err, stats) => {
|
||||
/* istanbul ignore next */
|
||||
if (err) {
|
||||
return reject(err)
|
||||
} else if (stats.hasErrors()) {
|
||||
if (options.build.quiet === true) {
|
||||
err = stats.toString(options.build.stats)
|
||||
}
|
||||
if (!err) {
|
||||
// actual errors will be printed by webpack itself
|
||||
err = 'Nuxt Build Error'
|
||||
}
|
||||
// Reload renderer
|
||||
await nuxt.callHook('build:resources', this.mfs)
|
||||
})
|
||||
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
resolve()
|
||||
// --- Dev Build ---
|
||||
if (options.dev) {
|
||||
// Client Build, watch is started by dev-middleware
|
||||
if (['client', 'modern'].includes(name)) {
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.hooks.done.tap('nuxt-dev', () => resolve())
|
||||
this.webpackDev(compiler)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Server, build and watch for changes
|
||||
return new Promise((resolve, reject) => {
|
||||
const watching = compiler.watch(options.watchers.webpack, (err) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
watching.close = pify(watching.close)
|
||||
this.compilersWatching.push(watching)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// --- Production Build ---
|
||||
compiler.run = pify(compiler.run)
|
||||
const stats = await compiler.run()
|
||||
|
||||
if (stats.hasErrors()) {
|
||||
if (options.build.quiet === true) {
|
||||
return Promise.reject(stats.toString(options.build.stats))
|
||||
} else {
|
||||
// Actual error will be printet by webpack
|
||||
throw new Error('Nuxt Build Error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
webpackDev(compiler) {
|
||||
@ -229,12 +227,33 @@ export class WebpackBundler {
|
||||
|
||||
async unwatch() {
|
||||
for (const watching of this.compilersWatching) {
|
||||
watching.close()
|
||||
await watching.close()
|
||||
}
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this.__closed) return
|
||||
this.__closed = true
|
||||
|
||||
// Unwatch
|
||||
await this.unwatch()
|
||||
|
||||
// Stop webpack middleware
|
||||
for (const devMiddleware of Object.values(this.devMiddleware)) {
|
||||
await devMiddleware.close()
|
||||
}
|
||||
|
||||
// Cleanup MFS
|
||||
if (this.mfs) {
|
||||
delete this.mfs.data
|
||||
delete this.mfs
|
||||
}
|
||||
|
||||
// Cleanup more resources
|
||||
delete this.compilers
|
||||
delete this.compilersWatching
|
||||
delete this.devMiddleware
|
||||
delete this.hotMiddleware
|
||||
}
|
||||
|
||||
forGenerate() {
|
||||
|
@ -75,8 +75,11 @@ describe('fallback generate', () => {
|
||||
})
|
||||
|
||||
test('generate.fallback = true is transformed to /404.html', () => {
|
||||
nuxt.options.generate.fallback = true
|
||||
const options = getNuxtConfig(nuxt.options)
|
||||
const options = getNuxtConfig({
|
||||
generate: {
|
||||
fallback: true
|
||||
}
|
||||
})
|
||||
expect(options.generate.fallback).toBe('404.html')
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user