mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 22:25: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 hash = require('hash-sum')
|
||||
const { chainFn, sequence } = require('../common/utils')
|
||||
const Debug = require('debug')
|
||||
|
||||
const debug = Debug('nuxt:module')
|
||||
|
||||
module.exports = class ModuleContainer {
|
||||
constructor(nuxt) {
|
||||
this.nuxt = nuxt
|
||||
this.options = nuxt.options
|
||||
this.requiredModules = []
|
||||
this.requiredModules = {}
|
||||
}
|
||||
|
||||
async ready() {
|
||||
// Call before hook
|
||||
await this.nuxt.callHook('modules:before', this, this.options.modules)
|
||||
|
||||
// Load every module in sequence
|
||||
await sequence(this.options.modules, this.addModule.bind(this))
|
||||
|
||||
// Call done hook
|
||||
await this.nuxt.callHook('modules:done', this)
|
||||
}
|
||||
|
||||
addVendor(vendor) {
|
||||
/* istanbul ignore if */
|
||||
if (!vendor) {
|
||||
return
|
||||
if (!vendor || typeof vendor !== 'string') {
|
||||
throw new Error('Invalid vendor:' + vendor)
|
||||
}
|
||||
|
||||
this.options.build.vendor = uniq(this.options.build.vendor.concat(vendor))
|
||||
}
|
||||
|
||||
addTemplate(template) {
|
||||
/* istanbul ignore if */
|
||||
if (!template) {
|
||||
return
|
||||
if (!template || typeof template !== 'string') {
|
||||
throw new Error('Invalid template:' + template)
|
||||
}
|
||||
|
||||
// Validate & parse source
|
||||
const src = template.src || template
|
||||
const srcPath = path.parse(src)
|
||||
/* istanbul ignore if */
|
||||
if (!src || typeof src !== 'string' || !fs.existsSync(src)) {
|
||||
/* istanbul ignore next */
|
||||
debug('[nuxt] invalid template', template)
|
||||
return
|
||||
throw new Error('Template not found:' + template)
|
||||
}
|
||||
|
||||
// Generate unique and human readable dst filename
|
||||
const dst = template.fileName ||
|
||||
(path.basename(srcPath.dir) + '.' + srcPath.name + '.' + hash(src) + srcPath.ext)
|
||||
const dst =
|
||||
template.fileName ||
|
||||
`${path.basename(srcPath.dir)}.${srcPath.name}.${hash(src)}.${
|
||||
srcPath.ext
|
||||
}`
|
||||
|
||||
// Add to templates list
|
||||
const templateObj = {
|
||||
src,
|
||||
dst,
|
||||
options: template.options
|
||||
}
|
||||
|
||||
this.options.build.templates.push(templateObj)
|
||||
return templateObj
|
||||
}
|
||||
|
||||
addPlugin(template) {
|
||||
const { dst } = this.addTemplate(template)
|
||||
|
||||
// Add to nuxt plugins
|
||||
this.options.plugins.unshift({
|
||||
src: path.join(this.options.buildDir, dst),
|
||||
@ -75,72 +82,93 @@ module.exports = class ModuleContainer {
|
||||
}
|
||||
|
||||
extendRoutes(fn) {
|
||||
this.options.router.extendRoutes = chainFn(this.options.router.extendRoutes, fn)
|
||||
this.options.router.extendRoutes = chainFn(
|
||||
this.options.router.extendRoutes,
|
||||
fn
|
||||
)
|
||||
}
|
||||
|
||||
requireModule(moduleOpts) {
|
||||
// Require once
|
||||
return this.addModule(moduleOpts, true)
|
||||
return this.addModule(moduleOpts, true /* require once */)
|
||||
}
|
||||
|
||||
async addModule(moduleOpts, requireOnce) {
|
||||
/* istanbul ignore if */
|
||||
if (!moduleOpts) {
|
||||
return
|
||||
let src
|
||||
let options
|
||||
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)) {
|
||||
moduleOpts = {
|
||||
src: moduleOpts[0],
|
||||
options: moduleOpts[1]
|
||||
src = moduleOpts[0]
|
||||
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
|
||||
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
|
||||
// Validate handler
|
||||
/* istanbul ignore if */
|
||||
if (typeof module !== 'function') {
|
||||
throw new Error(`[nuxt] Module ${JSON.stringify(src)} should export a function`)
|
||||
if (typeof handler !== 'function') {
|
||||
throw new Error('Module should export a function: ' + src)
|
||||
}
|
||||
|
||||
// Module meta
|
||||
module.meta = module.meta || {}
|
||||
let name = module.meta.name || module.name
|
||||
// Resolve module meta
|
||||
const key = (handler.meta && handler.meta.name) || handler.name || src
|
||||
|
||||
// If requireOnce specified & module from NPM or with specified name
|
||||
if (requireOnce && name) {
|
||||
const alreadyRequired = this.requiredModules.indexOf(name) !== -1
|
||||
// Check requireOnce if possbile
|
||||
if (requireOnce && typeof key === 'string') {
|
||||
const alreadyRequired = this.requiredModules[key]
|
||||
if (alreadyRequired) {
|
||||
return
|
||||
}
|
||||
this.requiredModules.push(name)
|
||||
this.requiredModules[key] = { src, options, handler }
|
||||
}
|
||||
|
||||
// Call module with `this` context and pass options
|
||||
await new Promise((resolve, reject) => {
|
||||
const result = module.call(this, options, (err) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Prepare callback
|
||||
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 */
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
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 (module.length < 2) {
|
||||
if (handler.length < 2) {
|
||||
return resolve()
|
||||
}
|
||||
})
|
||||
|
@ -31,7 +31,9 @@ module.exports = class Nuxt {
|
||||
// Backward compatibility
|
||||
this.render = this.renderer.app
|
||||
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)
|
||||
}
|
||||
@ -51,11 +53,16 @@ module.exports = class Nuxt {
|
||||
} else if (typeof this.options.hooks === 'function') {
|
||||
this.options.hooks(this.hook)
|
||||
}
|
||||
// Add nuxt modules
|
||||
|
||||
// Await for modules
|
||||
await this.moduleContainer.ready()
|
||||
|
||||
// Await for renderer to be ready
|
||||
await this.renderer.ready()
|
||||
|
||||
this.initialized = true
|
||||
|
||||
// Call ready hook
|
||||
await this.callHook('ready', this)
|
||||
|
||||
return this
|
||||
@ -63,14 +70,16 @@ module.exports = class Nuxt {
|
||||
|
||||
plugin(name, fn) {
|
||||
// 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
|
||||
const hookMap = {
|
||||
'ready': 'ready',
|
||||
'close': 'close',
|
||||
'listen': 'listen',
|
||||
'built': 'build:done'
|
||||
ready: 'ready',
|
||||
close: 'close',
|
||||
listen: 'listen',
|
||||
built: 'build:done'
|
||||
}
|
||||
|
||||
if (hookMap[name]) {
|
||||
@ -95,7 +104,7 @@ module.exports = class Nuxt {
|
||||
}
|
||||
debug(`Call ${name} hooks (${this._hooks[name].length})`)
|
||||
try {
|
||||
await sequence(this._hooks[name], (fn) => fn(...args))
|
||||
await sequence(this._hooks[name], fn => fn(...args))
|
||||
} catch (err) {
|
||||
console.error(`> Error on hook "${name}":`) // eslint-disable-line no-console
|
||||
console.error(err) // eslint-disable-line no-console
|
||||
@ -103,11 +112,11 @@ module.exports = class Nuxt {
|
||||
}
|
||||
|
||||
addObjectHooks(hooksObj) {
|
||||
Object.keys(hooksObj).forEach((name) => {
|
||||
Object.keys(hooksObj).forEach(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)
|
||||
})
|
||||
})
|
||||
@ -115,7 +124,9 @@ module.exports = class Nuxt {
|
||||
|
||||
listen(port = 3000, host = 'localhost') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = this.renderer.app.listen({ port, host, exclusive: false }, (err) => {
|
||||
const server = this.renderer.app.listen(
|
||||
{ port, host, exclusive: false },
|
||||
err => {
|
||||
/* istanbul ignore if */
|
||||
if (err) {
|
||||
return reject(err)
|
||||
@ -123,10 +134,17 @@ module.exports = class Nuxt {
|
||||
|
||||
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`))
|
||||
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) => {
|
||||
this.hook(
|
||||
'close',
|
||||
() =>
|
||||
new Promise((resolve, reject) => {
|
||||
// Destroy server by forcing every connection to be closed
|
||||
server.destroy(err => {
|
||||
debug('server closed')
|
||||
@ -136,17 +154,19 @@ module.exports = class Nuxt {
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
this.callHook('listen', server, { port, host }).then(resolve)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// Add server.destroy(cb) method
|
||||
enableDestroy(server)
|
||||
})
|
||||
}
|
||||
|
||||
errorHandler/* istanbul ignore next */() {
|
||||
errorHandler() {
|
||||
// Apply plugins
|
||||
// eslint-disable-next-line no-console
|
||||
this.callHook('error', ...arguments).catch(console.error)
|
||||
@ -169,17 +189,21 @@ module.exports = class Nuxt {
|
||||
resolvePath(path) {
|
||||
// Try to resolve using NPM resolve path first
|
||||
try {
|
||||
let resolvedPath = Module._resolveFilename(path, { paths: this.options.modulesDir })
|
||||
let resolvedPath = Module._resolveFilename(path, {
|
||||
paths: this.options.modulesDir
|
||||
})
|
||||
return resolvedPath
|
||||
} catch (e) {
|
||||
// Just continue
|
||||
// Continue to try other methods
|
||||
}
|
||||
|
||||
// Shorthand to resolve from project dirs
|
||||
if (path.indexOf('@@') === 0 || path.indexOf('~~') === 0) {
|
||||
return join(this.options.rootDir, path.substr(2))
|
||||
} else if (path.indexOf('@') === 0 || path.indexOf('~') === 0) {
|
||||
return join(this.options.srcDir, path.substr(1))
|
||||
}
|
||||
|
||||
return resolve(this.options.srcDir, path)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user