mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-27 16:12:12 +00:00
feat(module): improve error handling
This commit is contained in:
parent
5dd7591e3f
commit
e5ffbdfcd2
@ -3,62 +3,69 @@ const fs = require('fs')
|
|||||||
const { uniq } = require('lodash')
|
const { uniq } = require('lodash')
|
||||||
const hash = require('hash-sum')
|
const hash = require('hash-sum')
|
||||||
const { chainFn, sequence } = require('../common/utils')
|
const { chainFn, sequence } = require('../common/utils')
|
||||||
const Debug = require('debug')
|
|
||||||
|
|
||||||
const debug = Debug('nuxt:module')
|
|
||||||
|
|
||||||
module.exports = class ModuleContainer {
|
module.exports = class ModuleContainer {
|
||||||
constructor(nuxt) {
|
constructor(nuxt) {
|
||||||
this.nuxt = nuxt
|
this.nuxt = nuxt
|
||||||
this.options = nuxt.options
|
this.options = nuxt.options
|
||||||
this.requiredModules = []
|
this.requiredModules = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async ready() {
|
async ready() {
|
||||||
|
// Call before hook
|
||||||
await this.nuxt.callHook('modules:before', this, this.options.modules)
|
await this.nuxt.callHook('modules:before', this, this.options.modules)
|
||||||
|
|
||||||
// Load every module in sequence
|
// Load every module in sequence
|
||||||
await sequence(this.options.modules, this.addModule.bind(this))
|
await sequence(this.options.modules, this.addModule.bind(this))
|
||||||
|
|
||||||
// Call done hook
|
// Call done hook
|
||||||
await this.nuxt.callHook('modules:done', this)
|
await this.nuxt.callHook('modules:done', this)
|
||||||
}
|
}
|
||||||
|
|
||||||
addVendor(vendor) {
|
addVendor(vendor) {
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
||||||
if (!vendor) {
|
if (!vendor || typeof vendor !== 'string') {
|
||||||
return
|
throw new Error('Invalid vendor:' + vendor)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.options.build.vendor = uniq(this.options.build.vendor.concat(vendor))
|
this.options.build.vendor = uniq(this.options.build.vendor.concat(vendor))
|
||||||
}
|
}
|
||||||
|
|
||||||
addTemplate(template) {
|
addTemplate(template) {
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
||||||
if (!template) {
|
if (!template || typeof template !== 'string') {
|
||||||
return
|
throw new Error('Invalid template:' + template)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate & parse source
|
// Validate & parse source
|
||||||
const src = template.src || template
|
const src = template.src || template
|
||||||
const srcPath = path.parse(src)
|
const srcPath = path.parse(src)
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
||||||
if (!src || typeof src !== 'string' || !fs.existsSync(src)) {
|
if (!src || typeof src !== 'string' || !fs.existsSync(src)) {
|
||||||
/* istanbul ignore next */
|
throw new Error('Template not found:' + template)
|
||||||
debug('[nuxt] invalid template', template)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate unique and human readable dst filename
|
// Generate unique and human readable dst filename
|
||||||
const dst = template.fileName ||
|
const dst =
|
||||||
(path.basename(srcPath.dir) + '.' + srcPath.name + '.' + hash(src) + srcPath.ext)
|
template.fileName ||
|
||||||
|
`${path.basename(srcPath.dir)}.${srcPath.name}.${hash(src)}.${
|
||||||
|
srcPath.ext
|
||||||
|
}`
|
||||||
|
|
||||||
// Add to templates list
|
// Add to templates list
|
||||||
const templateObj = {
|
const templateObj = {
|
||||||
src,
|
src,
|
||||||
dst,
|
dst,
|
||||||
options: template.options
|
options: template.options
|
||||||
}
|
}
|
||||||
|
|
||||||
this.options.build.templates.push(templateObj)
|
this.options.build.templates.push(templateObj)
|
||||||
return templateObj
|
return templateObj
|
||||||
}
|
}
|
||||||
|
|
||||||
addPlugin(template) {
|
addPlugin(template) {
|
||||||
const { dst } = this.addTemplate(template)
|
const { dst } = this.addTemplate(template)
|
||||||
|
|
||||||
// Add to nuxt plugins
|
// Add to nuxt plugins
|
||||||
this.options.plugins.unshift({
|
this.options.plugins.unshift({
|
||||||
src: path.join(this.options.buildDir, dst),
|
src: path.join(this.options.buildDir, dst),
|
||||||
@ -75,72 +82,93 @@ module.exports = class ModuleContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extendRoutes(fn) {
|
extendRoutes(fn) {
|
||||||
this.options.router.extendRoutes = chainFn(this.options.router.extendRoutes, fn)
|
this.options.router.extendRoutes = chainFn(
|
||||||
|
this.options.router.extendRoutes,
|
||||||
|
fn
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
requireModule(moduleOpts) {
|
requireModule(moduleOpts) {
|
||||||
// Require once
|
return this.addModule(moduleOpts, true /* require once */)
|
||||||
return this.addModule(moduleOpts, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async addModule(moduleOpts, requireOnce) {
|
async addModule(moduleOpts, requireOnce) {
|
||||||
/* istanbul ignore if */
|
let src
|
||||||
if (!moduleOpts) {
|
let options
|
||||||
return
|
let handler
|
||||||
|
|
||||||
|
// Type 1: String
|
||||||
|
if (typeof moduleOpts === 'string') {
|
||||||
|
src = moduleOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow using babel style array options
|
// Type 2: Babel style array
|
||||||
if (Array.isArray(moduleOpts)) {
|
if (Array.isArray(moduleOpts)) {
|
||||||
moduleOpts = {
|
src = moduleOpts[0]
|
||||||
src: moduleOpts[0],
|
options = moduleOpts[1]
|
||||||
options: moduleOpts[1]
|
}
|
||||||
|
|
||||||
|
// Type 3: Pure object
|
||||||
|
if (typeof moduleOpts === 'object') {
|
||||||
|
src = moduleOpts.src
|
||||||
|
options = moduleOpts.options
|
||||||
|
handler = moduleOpts.handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve handler
|
||||||
|
if (!handler) {
|
||||||
|
try {
|
||||||
|
handler = require(this.nuxt.resolvePath(src))
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e) // eslint-disable-line no-console
|
||||||
|
throw new Error('Error while resolving module: ' + src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allows passing runtime options to each module
|
// Validate handler
|
||||||
const options = moduleOpts.options || (typeof moduleOpts === 'object' ? moduleOpts : {})
|
|
||||||
const src = moduleOpts.src || moduleOpts
|
|
||||||
|
|
||||||
// Resolve module
|
|
||||||
let module
|
|
||||||
if (typeof src === 'string') {
|
|
||||||
module = require(this.nuxt.resolvePath(src))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate module
|
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
||||||
if (typeof module !== 'function') {
|
if (typeof handler !== 'function') {
|
||||||
throw new Error(`[nuxt] Module ${JSON.stringify(src)} should export a function`)
|
throw new Error('Module should export a function: ' + src)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module meta
|
// Resolve module meta
|
||||||
module.meta = module.meta || {}
|
const key = (handler.meta && handler.meta.name) || handler.name || src
|
||||||
let name = module.meta.name || module.name
|
|
||||||
|
|
||||||
// If requireOnce specified & module from NPM or with specified name
|
// Check requireOnce if possbile
|
||||||
if (requireOnce && name) {
|
if (requireOnce && typeof key === 'string') {
|
||||||
const alreadyRequired = this.requiredModules.indexOf(name) !== -1
|
const alreadyRequired = this.requiredModules[key]
|
||||||
if (alreadyRequired) {
|
if (alreadyRequired) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.requiredModules.push(name)
|
this.requiredModules[key] = { src, options, handler }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call module with `this` context and pass options
|
return new Promise((resolve, reject) => {
|
||||||
await new Promise((resolve, reject) => {
|
// Prepare callback
|
||||||
const result = module.call(this, options, (err) => {
|
const cb = err => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
'[Depricated] Consider using async/await for module handlers.' +
|
||||||
|
'Supporting callbacks will be removed in next releases: ' +
|
||||||
|
src
|
||||||
|
)
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err)
|
return reject(err)
|
||||||
}
|
}
|
||||||
resolve()
|
resolve()
|
||||||
})
|
|
||||||
// If module send back a promise
|
|
||||||
if (result && result.then instanceof Function) {
|
|
||||||
return result.then(resolve)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call module with `this` context and pass options
|
||||||
|
const result = handler.call(this, options, cb)
|
||||||
|
|
||||||
|
// If module send back a promise
|
||||||
|
if (result && result.then) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// If not expecting a callback but returns no promise (=synchronous)
|
// If not expecting a callback but returns no promise (=synchronous)
|
||||||
if (module.length < 2) {
|
if (handler.length < 2) {
|
||||||
return resolve()
|
return resolve()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
100
lib/core/nuxt.js
100
lib/core/nuxt.js
@ -31,7 +31,9 @@ module.exports = class Nuxt {
|
|||||||
// Backward compatibility
|
// Backward compatibility
|
||||||
this.render = this.renderer.app
|
this.render = this.renderer.app
|
||||||
this.renderRoute = this.renderer.renderRoute.bind(this.renderer)
|
this.renderRoute = this.renderer.renderRoute.bind(this.renderer)
|
||||||
this.renderAndGetWindow = this.renderer.renderAndGetWindow.bind(this.renderer)
|
this.renderAndGetWindow = this.renderer.renderAndGetWindow.bind(
|
||||||
|
this.renderer
|
||||||
|
)
|
||||||
|
|
||||||
this._ready = this.ready().catch(this.errorHandler)
|
this._ready = this.ready().catch(this.errorHandler)
|
||||||
}
|
}
|
||||||
@ -51,11 +53,16 @@ module.exports = class Nuxt {
|
|||||||
} else if (typeof this.options.hooks === 'function') {
|
} else if (typeof this.options.hooks === 'function') {
|
||||||
this.options.hooks(this.hook)
|
this.options.hooks(this.hook)
|
||||||
}
|
}
|
||||||
// Add nuxt modules
|
|
||||||
|
// Await for modules
|
||||||
await this.moduleContainer.ready()
|
await this.moduleContainer.ready()
|
||||||
|
|
||||||
|
// Await for renderer to be ready
|
||||||
await this.renderer.ready()
|
await this.renderer.ready()
|
||||||
|
|
||||||
this.initialized = true
|
this.initialized = true
|
||||||
|
|
||||||
|
// Call ready hook
|
||||||
await this.callHook('ready', this)
|
await this.callHook('ready', this)
|
||||||
|
|
||||||
return this
|
return this
|
||||||
@ -63,14 +70,16 @@ module.exports = class Nuxt {
|
|||||||
|
|
||||||
plugin(name, fn) {
|
plugin(name, fn) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(`[warn] nuxt.plugin('${name}',..) is deprecated. Please use new hooks system.`)
|
console.error(
|
||||||
|
`[Deprecated] nuxt.plugin('${name}',..) is deprecated. Please use new hooks system.`
|
||||||
|
)
|
||||||
|
|
||||||
// A tiny backward compatibility util
|
// A tiny backward compatibility util
|
||||||
const hookMap = {
|
const hookMap = {
|
||||||
'ready': 'ready',
|
ready: 'ready',
|
||||||
'close': 'close',
|
close: 'close',
|
||||||
'listen': 'listen',
|
listen: 'listen',
|
||||||
'built': 'build:done'
|
built: 'build:done'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hookMap[name]) {
|
if (hookMap[name]) {
|
||||||
@ -95,7 +104,7 @@ module.exports = class Nuxt {
|
|||||||
}
|
}
|
||||||
debug(`Call ${name} hooks (${this._hooks[name].length})`)
|
debug(`Call ${name} hooks (${this._hooks[name].length})`)
|
||||||
try {
|
try {
|
||||||
await sequence(this._hooks[name], (fn) => fn(...args))
|
await sequence(this._hooks[name], fn => fn(...args))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`> Error on hook "${name}":`) // eslint-disable-line no-console
|
console.error(`> Error on hook "${name}":`) // eslint-disable-line no-console
|
||||||
console.error(err) // eslint-disable-line no-console
|
console.error(err) // eslint-disable-line no-console
|
||||||
@ -103,11 +112,11 @@ module.exports = class Nuxt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addObjectHooks(hooksObj) {
|
addObjectHooks(hooksObj) {
|
||||||
Object.keys(hooksObj).forEach((name) => {
|
Object.keys(hooksObj).forEach(name => {
|
||||||
let hooks = hooksObj[name]
|
let hooks = hooksObj[name]
|
||||||
hooks = (Array.isArray(hooks) ? hooks : [hooks])
|
hooks = Array.isArray(hooks) ? hooks : [hooks]
|
||||||
|
|
||||||
hooks.forEach((hook) => {
|
hooks.forEach(hook => {
|
||||||
this.hook(name, hook)
|
this.hook(name, hook)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -115,38 +124,49 @@ module.exports = class Nuxt {
|
|||||||
|
|
||||||
listen(port = 3000, host = 'localhost') {
|
listen(port = 3000, host = 'localhost') {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const server = this.renderer.app.listen({ port, host, exclusive: false }, (err) => {
|
const server = this.renderer.app.listen(
|
||||||
/* istanbul ignore if */
|
{ port, host, exclusive: false },
|
||||||
if (err) {
|
err => {
|
||||||
return reject(err)
|
/* istanbul ignore if */
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const _host = host === '0.0.0.0' ? 'localhost' : host
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
'\n' +
|
||||||
|
chalk.bgGreen.black(' OPEN ') +
|
||||||
|
chalk.green(` http://${_host}:${port}\n`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Close server on nuxt close
|
||||||
|
this.hook(
|
||||||
|
'close',
|
||||||
|
() =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
// Destroy server by forcing every connection to be closed
|
||||||
|
server.destroy(err => {
|
||||||
|
debug('server closed')
|
||||||
|
/* istanbul ignore if */
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
this.callHook('listen', server, { port, host }).then(resolve)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
const _host = host === '0.0.0.0' ? 'localhost' : host
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('\n' + chalk.bgGreen.black(' OPEN ') + chalk.green(` http://${_host}:${port}\n`))
|
|
||||||
|
|
||||||
// Close server on nuxt close
|
|
||||||
this.hook('close', () => new Promise((resolve, reject) => {
|
|
||||||
// Destroy server by forcing every connection to be closed
|
|
||||||
server.destroy(err => {
|
|
||||||
debug('server closed')
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
|
|
||||||
this.callHook('listen', server, { port, host }).then(resolve)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add server.destroy(cb) method
|
// Add server.destroy(cb) method
|
||||||
enableDestroy(server)
|
enableDestroy(server)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
errorHandler/* istanbul ignore next */() {
|
errorHandler() {
|
||||||
// Apply plugins
|
// Apply plugins
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
this.callHook('error', ...arguments).catch(console.error)
|
this.callHook('error', ...arguments).catch(console.error)
|
||||||
@ -169,17 +189,21 @@ module.exports = class Nuxt {
|
|||||||
resolvePath(path) {
|
resolvePath(path) {
|
||||||
// Try to resolve using NPM resolve path first
|
// Try to resolve using NPM resolve path first
|
||||||
try {
|
try {
|
||||||
let resolvedPath = Module._resolveFilename(path, { paths: this.options.modulesDir })
|
let resolvedPath = Module._resolveFilename(path, {
|
||||||
|
paths: this.options.modulesDir
|
||||||
|
})
|
||||||
return resolvedPath
|
return resolvedPath
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Just continue
|
// Continue to try other methods
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shorthand to resolve from project dirs
|
// Shorthand to resolve from project dirs
|
||||||
if (path.indexOf('@@') === 0 || path.indexOf('~~') === 0) {
|
if (path.indexOf('@@') === 0 || path.indexOf('~~') === 0) {
|
||||||
return join(this.options.rootDir, path.substr(2))
|
return join(this.options.rootDir, path.substr(2))
|
||||||
} else if (path.indexOf('@') === 0 || path.indexOf('~') === 0) {
|
} else if (path.indexOf('@') === 0 || path.indexOf('~') === 0) {
|
||||||
return join(this.options.srcDir, path.substr(1))
|
return join(this.options.srcDir, path.substr(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolve(this.options.srcDir, path)
|
return resolve(this.options.srcDir, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user