diff --git a/lib/builder/builder.js b/lib/builder/builder.js index 9d0de14900..8f9e859e95 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -43,6 +43,9 @@ export default class Builder extends Tapable { // Helper to resolve build paths this.relativeToBuild = (...args) => relativeTo(this.options.buildDir, ...args) + // Call builder plugin on parent nuxt to notify all modules of builder existence + this.nuxt.applyPluginsAsync('builder', this).catch(this.nuxt.errorHandler) + this._buildStatus = STATUS.INITIAL } @@ -66,6 +69,8 @@ export default class Builder extends Tapable { // Wait for nuxt ready await this.nuxt.ready() + await this.applyPluginsAsync('build', this) + // Check if pages dir exists and warn if not this._nuxtPages = typeof this.options.build.createRoutes !== 'function' if (this._nuxtPages) { @@ -95,6 +100,8 @@ export default class Builder extends Tapable { // Start webpack build await this.webpackBuild() + await this.applyPluginsAsync('built', this) + // Flag to set that building is done this._buildStatus = STATUS.BUILD_DONE @@ -173,6 +180,9 @@ export default class Builder extends Tapable { } else { templateVars.router.routes = this.options.build.createRoutes(this.options.srcDir) } + + await this.applyPluginsAsync('extendRoutes', {routes: templateVars.router.routes, templateVars, r}) + // router.extendRoutes method if (typeof this.options.router.extendRoutes === 'function') { // let the user extend the routes @@ -216,6 +226,8 @@ export default class Builder extends Tapable { }, t) })) + await this.applyPluginsAsync('generate', { builder: this, templatesFiles, templateVars }) + // Interpret and move template files to .nuxt/ await Promise.all(templatesFiles.map(async ({ src, dst, options, custom }) => { // Add template to watchers @@ -246,9 +258,11 @@ export default class Builder extends Tapable { const dateFS = Date.now() / 1000 - 1000 return utimes(path, dateFS, dateFS) })) + + await this.applyPluginsAsync('generated', this) } - webpackBuild () { + async webpackBuild () { debug('Building files...') let compilersOptions = [] @@ -291,7 +305,7 @@ export default class Builder extends Tapable { }) // Run after each compile - this.compiler.plugin('done', stats => { + this.compiler.plugin('done', async stats => { // Don't reload failed builds /* istanbul ignore if */ if (stats.hasErrors() || stats.hasWarnings()) { @@ -301,6 +315,8 @@ export default class Builder extends Tapable { if (this.nuxt.renderer) { this.nuxt.renderer.loadResources(sharedFS || fs) } + + await this.applyPluginsAsync('compiled', { builder: this, stats }) }) // Add dev Stuff @@ -308,6 +324,8 @@ export default class Builder extends Tapable { this.webpackDev() } + await this.applyPluginsAsync('compile', { builder: this, compiler: this.compiler }) + // Start Builds return parallel(this.compiler.compilers, compiler => new Promise((resolve, reject) => { if (this.options.dev) { diff --git a/lib/builder/generator.js b/lib/builder/generator.js index ee5ae785a7..5eb4bf0c02 100644 --- a/lib/builder/generator.js +++ b/lib/builder/generator.js @@ -15,6 +15,15 @@ export default class Generator extends Tapable { this.nuxt = nuxt this.options = nuxt.options this.builder = builder + + // Set variables + this.generateRoutes = resolve(this.options.srcDir, 'static') + this.srcBuiltPath = resolve(this.options.buildDir, 'dist') + this.distPath = resolve(this.options.rootDir, this.options.generate.dir) + this.distNuxtPath = join(this.distPath, (isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath)) + + // Call generator plugin on parent nuxt to notify all modules of generator existence + this.nuxt.applyPluginsAsync('generator', this).catch(this.nuxt.errorHandler) } async generate (doBuild = true) { @@ -22,12 +31,6 @@ export default class Generator extends Tapable { let errors = [] let generateRoutes = [] - // Set variables - let srcStaticPath = resolve(this.options.srcDir, 'static') - let srcBuiltPath = resolve(this.options.buildDir, 'dist') - let distPath = resolve(this.options.rootDir, this.options.generate.dir) - let distNuxtPath = join(distPath, (isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath)) - // Wait for nuxt be ready await this.nuxt.ready() @@ -36,16 +39,18 @@ export default class Generator extends Tapable { await this.builder.build() } + await this.applyPluginsAsync('before-generate', this) + // Clean destination folder - await remove(distPath) + await remove(this.distPath) debug('Destination folder cleaned') // Copy static and built files /* istanbul ignore if */ - if (fs.existsSync(srcStaticPath)) { - await copy(srcStaticPath, distPath) + if (fs.existsSync(this.generateRoutes)) { + await copy(this.generateRoutes, this.distPath) } - await copy(srcBuiltPath, distNuxtPath) + await copy(this.srcBuiltPath, this.distNuxtPath) debug('Static & build files copied') // Resolve config.generate.routes promises before generating the routes @@ -53,6 +58,7 @@ export default class Generator extends Tapable { try { console.log('Generating routes') // eslint-disable-line no-console generateRoutes = await promisifyRoute(this.options.generate.routes || []) + await this.applyPluginsAsync('generateRoutes', {generator: this, generateRoutes}) } catch (e) { console.error('Could not resolve routes') // eslint-disable-line no-console console.error(e) // eslint-disable-line no-console @@ -85,6 +91,8 @@ export default class Generator extends Tapable { let routes = (this.options.router.mode === 'hash') ? ['/'] : flatRoutes(this.options.router.routes) routes = decorateWithPayloads(routes) + await this.applyPluginsAsync('generate', {generator: this, routes}) + while (routes.length) { let n = 0 await Promise.all(routes.splice(0, 500).map(async ({ route, payload }) => { @@ -111,7 +119,7 @@ export default class Generator extends Tapable { let path = join(route, sep, 'index.html') // /about -> /about/index.html path = (path === '/404/index.html') ? '/404.html' : path // /404 -> /404.html debug('Generate file: ' + path) - path = join(distPath, path) + path = join(this.distPath, path) // Make sure the sub folders are created await mkdirp(dirname(path)) await writeFile(path, html, 'utf8') @@ -120,7 +128,7 @@ export default class Generator extends Tapable { // Add .nojekyll file to let Github Pages add the _nuxt/ folder // https://help.github.com/articles/files-that-start-with-an-underscore-are-missing/ - const nojekyllPath = resolve(distPath, '.nojekyll') + const nojekyllPath = resolve(this.distPath, '.nojekyll') writeFile(nojekyllPath, '') const duration = Math.round((Date.now() - s) / 100) / 10 debug(`HTML Files generated in ${duration}s`) @@ -137,6 +145,8 @@ export default class Generator extends Tapable { console.error('==== Error report ==== \n' + report.join('\n\n')) // eslint-disable-line no-console } + await this.applyPluginsAsync('generated', this) + return { duration, errors } } } diff --git a/lib/core/module.js b/lib/core/module.js index 4cb9a5bcb5..8876fab3b0 100755 --- a/lib/core/module.js +++ b/lib/core/module.js @@ -15,8 +15,9 @@ export default class ModuleContainer extends Tapable { this.options = nuxt.options this.requiredModules = [] - this.nuxt.plugin('beforeInit', () => { - return sequence(this.options.modules, this.addModule.bind(this)) + this.nuxt.plugin('beforeInit', async () => { + await sequence(this.options.modules, this.addModule.bind(this)) + await this.applyPluginsAsync('ready', this) }) } @@ -81,11 +82,14 @@ export default class ModuleContainer extends Tapable { return this.addModule(moduleOpts, true) } - addModule (moduleOpts, requireOnce) { + async addModule (moduleOpts, requireOnce) { /* istanbul ignore if */ if (!moduleOpts) { return } + + await this.applyPluginsAsync('add', {moduleOpts, requireOnce}) + // Allow using babel style array options if (Array.isArray(moduleOpts)) { moduleOpts = { @@ -93,12 +97,13 @@ export default class ModuleContainer extends Tapable { options: moduleOpts[1] } } + // Allows passing runtime options to each module const options = moduleOpts.options || (typeof moduleOpts === 'object' ? moduleOpts : {}) const originalSrc = moduleOpts.src || moduleOpts + // Resolve module let module = originalSrc - if (typeof module === 'string') { module = require(this.nuxt.resolvePath(module)) } @@ -108,6 +113,7 @@ export default class ModuleContainer extends Tapable { if (typeof module !== 'function') { throw new Error(`[nuxt] Module ${JSON.stringify(originalSrc)} should export a function`) } + // Module meta if (!module.meta) { module.meta = {} @@ -121,6 +127,7 @@ export default class ModuleContainer extends Tapable { this.requiredModules.push(module.meta.name) } } + // Call module with `this` context and pass options return new Promise((resolve, reject) => { const result = module.call(this, options, err => { diff --git a/lib/core/nuxt.js b/lib/core/nuxt.js index 3b138a5cdd..1c5dfbd95b 100644 --- a/lib/core/nuxt.js +++ b/lib/core/nuxt.js @@ -79,18 +79,23 @@ export default class Nuxt extends Tapable { } errorHandler /* istanbul ignore next */() { + // Apply plugins + // eslint-disable-next-line no-console + this.applyPluginsAsync('error', ...arguments).catch(console.error) + // Silent if (this.options.errorHandler === false) { return } + // Custom errorHandler if (typeof this.options.errorHandler === 'function') { return this.options.errorHandler.apply(this, arguments) } + // Default handler // eslint-disable-next-line no-console - console.error.apply(this, arguments) - process.exit(1) + console.error(...arguments) } resolvePath (path) { diff --git a/lib/core/renderer.js b/lib/core/renderer.js index c3bbe7205f..36338fa055 100644 --- a/lib/core/renderer.js +++ b/lib/core/renderer.js @@ -71,7 +71,7 @@ export default class Renderer extends Tapable { } // Setup all middleWare - this.setupMiddleware() + await this.setupMiddleware() // Load error template const errorTemplatePath = resolve(this.options.buildDir, 'views/error.html') @@ -165,9 +165,9 @@ export default class Renderer extends Tapable { } } - setupMiddleware () { + async setupMiddleware () { // Apply setupMiddleware from modules first - this.applyPlugins('setupMiddleware', this.app) + await this.applyPluginsAsync('setupMiddleware', this.app) // Gzip middleware for production if (!this.options.dev && this.options.render.gzip) {