diff --git a/lib/builder/builder.js b/lib/builder/builder.js index d3cccca87a..1dbef90a59 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -6,7 +6,6 @@ import pify from 'pify' import webpack from 'webpack' import serialize from 'serialize-javascript' import { join, resolve, basename, extname, dirname } from 'path' -import Tapable from 'tappable' import MFS from 'memory-fs' import webpackDevMiddleware from 'webpack-dev-middleware' import webpackHotMiddleware from 'webpack-hot-middleware' @@ -24,9 +23,8 @@ debug.color = 2 // Force green color const glob = pify(Glob) -export default class Builder extends Tapable { +export default class Builder { constructor(nuxt) { - super() this.nuxt = nuxt this.isStatic = false // Flag to know if the build is for a generated app this.options = nuxt.options @@ -57,9 +55,6 @@ export default class Builder extends Tapable { this.vueLoader = vueLoaderConfig.bind(this) this._buildStatus = STATUS.INITIAL - - // Call class hook - this.nuxt.applyPlugins('builder', this) } get plugins() { @@ -121,8 +116,8 @@ export default class Builder extends Tapable { // Wait for nuxt ready await this.nuxt.ready() - // Wait for build plugins - await this.applyPluginsAsync('build', this.options.build) + // Call before hook + await this.nuxt.callHook('build:before', this, this.options.build) // Babel options this.babelOptions = _.defaults(this.options.build.babel, { @@ -183,7 +178,8 @@ export default class Builder extends Tapable { // Flag to set that building is done this._buildStatus = STATUS.BUILD_DONE - await this.applyPluginsAsync('built') + // Call done hook + await this.nuxt.callHook('build:done', this) return this } @@ -267,7 +263,7 @@ export default class Builder extends Tapable { templateVars.router.routes = this.options.build.createRoutes(this.options.srcDir) } - await this.applyPluginsAsync('extendRoutes', templateVars.router.routes) + await this.nuxt.callHook('build:extendRoutes', templateVars.router.routes, r) // router.extendRoutes method if (typeof this.options.router.extendRoutes === 'function') { @@ -335,7 +331,7 @@ export default class Builder extends Tapable { } } - await this.applyPluginsAsync('generate', { templatesFiles, templateVars, resolve: r }) + await this.nuxt.callHook('build:templates', { templatesFiles, templateVars, resolve: r }) // Interpret and move template files to .nuxt/ await Promise.all(templatesFiles.map(async ({ src, dst, options, custom }) => { @@ -374,8 +370,6 @@ export default class Builder extends Tapable { const dateFS = Date.now() / 1000 - 1000 return utimes(path, dateFS, dateFS) })) - - await this.applyPluginsAsync('generated') } async webpackBuild() { @@ -436,11 +430,11 @@ export default class Builder extends Tapable { // Start Builds await sequence(this.compilers, (compiler) => new Promise(async (resolve, reject) => { const name = compiler.options.name - await this.applyPluginsAsync('compile', { name, compiler }) + await this.nuxt.callHook('build:compile', { name, compiler }) // Resolve only when compiler emit done event compiler.plugin('done', async (stats) => { - await this.applyPluginsAsync('compiled', { name, compiler, stats }) + await this.nuxt.callHook('build:compiled', { name, compiler, stats }) process.nextTick(resolve) }) // --- Dev Build --- @@ -511,7 +505,7 @@ export default class Builder extends Tapable { } // Stop webpack middleware on nuxt.close() - this.nuxt.plugin('close', () => new Promise(resolve => { + this.nuxt.hook('close', () => new Promise(resolve => { this.webpackDevMiddleware.close(() => resolve()) })) @@ -548,7 +542,7 @@ export default class Builder extends Tapable { .on('change', refreshFiles) // Stop watching on nuxt.close() - this.nuxt.plugin('close', () => { + this.nuxt.hook('close', () => { filesWatcher.close() customFilesWatcher.close() }) diff --git a/lib/builder/generator.js b/lib/builder/generator.js index da49c7fafc..750bc51510 100644 --- a/lib/builder/generator.js +++ b/lib/builder/generator.js @@ -2,15 +2,13 @@ import { copy, remove, writeFile, mkdirp, removeSync, existsSync } from 'fs-extr import _ from 'lodash' import { resolve, join, dirname, sep } from 'path' import { minify } from 'html-minifier' -import Tapable from 'tappable' import { isUrl, promisifyRoute, waitFor, flatRoutes } from 'utils' import Debug from 'debug' const debug = Debug('nuxt:generate') -export default class Generator extends Tapable { +export default class Generator { constructor(nuxt, builder) { - super() this.nuxt = nuxt this.options = nuxt.options this.builder = builder @@ -20,28 +18,26 @@ export default class Generator extends Tapable { 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 class hook - this.nuxt.applyPlugins('generator', this) } async generate({ build = true, init = true } = {}) { + // Wait for nuxt be ready + await this.nuxt.ready() + + // Call before hook + await this.nuxt.callHook('generate:before', this, this.options.generate) + const s = Date.now() let errors = [] // Add flag to set process.static this.builder.forGenerate() - // Wait for nuxt be ready - await this.nuxt.ready() - // Start build process if (build) { await this.builder.build() } - await this.nuxt.applyPluginsAsync('generator', this) - // Initialize dist directory if (init) { await this.initDist() @@ -63,7 +59,8 @@ export default class Generator extends Tapable { let routes = (this.options.router.mode === 'hash') ? ['/'] : flatRoutes(this.options.router.routes) routes = this.decorateWithPayloads(routes, generateRoutes) - await this.applyPluginsAsync('generate', routes) + // extendRoutes hook + await this.nuxt.callHook('generate:extendRoutes', routes) // Start generate process while (routes.length) { @@ -87,7 +84,8 @@ export default class Generator extends Tapable { const duration = Math.round((Date.now() - s) / 100) / 10 debug(`HTML Files generated in ${duration}s`) - await this.applyPluginsAsync('generated', errors) + // done hook + await this.nuxt.callHook('generate:done', this, errors) if (errors.length) { const report = errors.map(({ type, route, error }) => { diff --git a/lib/common/options.js b/lib/common/options.js index 2d708f3b23..fc31e04878 100755 --- a/lib/common/options.js +++ b/lib/common/options.js @@ -291,6 +291,7 @@ Options.defaults = { editor: { editor: 'code' }, + hooks: () => {}, messages: { error_404: 'This page could not be found', server_error: 'Server error', diff --git a/lib/core/module.js b/lib/core/module.js index 257c5fad02..d55eed13f0 100755 --- a/lib/core/module.js +++ b/lib/core/module.js @@ -2,27 +2,24 @@ import path from 'path' import fs from 'fs' import { uniq } from 'lodash' import hash from 'hash-sum' -import Tapable from 'tappable' import { chainFn, sequence } from 'utils' import Debug from 'debug' const debug = Debug('nuxt:module') -export default class ModuleContainer extends Tapable { +export default class ModuleContainer { constructor(nuxt) { - super() this.nuxt = nuxt this.options = nuxt.options this.requiredModules = [] - - // Call class hook - this.nuxt.applyPlugins('moduleContainer', this) } async ready() { + this.nuxt.callHook('modules:before', this, this.options.modules) // Load every module in sequence await sequence(this.options.modules, this.addModule.bind(this)) - await this.applyPluginsAsync('ready') + // Call done hook + await this.nuxt.callHook('modules:done', this) } addVendor(vendor) { @@ -102,42 +99,43 @@ export default class ModuleContainer extends Tapable { // Allows passing runtime options to each module const options = moduleOpts.options || (typeof moduleOpts === 'object' ? moduleOpts : {}) - const originalSrc = moduleOpts.src || moduleOpts + const src = moduleOpts.src || moduleOpts // Resolve module - let module = originalSrc - if (typeof module === 'string') { - module = require(this.nuxt.resolvePath(module)) + let module + if (typeof src === 'string') { + module = require(this.nuxt.resolvePath(src)) } // Validate module /* istanbul ignore if */ if (typeof module !== 'function') { - throw new Error(`[nuxt] Module ${JSON.stringify(originalSrc)} should export a function`) + throw new Error(`[nuxt] Module ${JSON.stringify(src)} should export a function`) } // Module meta - if (!module.meta) { - module.meta = {} - } - if (module.meta.name) { - const alreadyRequired = this.requiredModules.indexOf(module.meta.name) !== -1 + module.meta = module.meta || {} + let name = module.meta.name || module.name + + // If requireOnce specified & module from NPM or with specified name + if (requireOnce && name) { + const alreadyRequired = this.requiredModules.indexOf(name) !== -1 if (requireOnce && alreadyRequired) { return } if (!alreadyRequired) { - this.requiredModules.push(module.meta.name) + this.requiredModules.push(name) } } // Call module with `this` context and pass options - const m = await new Promise((resolve, reject) => { - const result = module.call(this, options, (err, m) => { + await new Promise((resolve, reject) => { + const result = module.call(this, options, (err) => { /* istanbul ignore if */ if (err) { return reject(err) } - resolve(m) + resolve() }) // If module send back a promise if (result && result.then instanceof Function) { @@ -145,9 +143,8 @@ export default class ModuleContainer extends Tapable { } // If not expecting a callback but returns no promise (=synchronous) if (module.length < 2) { - return resolve(module) + return resolve() } }) - await this.applyPluginsAsync('module', { meta: module.meta, module: m }) } } diff --git a/lib/core/nuxt.js b/lib/core/nuxt.js index 9e94dd6267..da6dd0358d 100644 --- a/lib/core/nuxt.js +++ b/lib/core/nuxt.js @@ -1,6 +1,6 @@ -import Tapable from 'tappable' import chalk from 'chalk' import { Options } from 'common' +import { sequence } from 'utils' import ModuleContainer from './module' import Renderer from './renderer' import Debug from 'debug' @@ -11,10 +11,8 @@ import { join, resolve } from 'path' const debug = Debug('nuxt:') debug.color = 5 -export default class Nuxt extends Tapable { +export default class Nuxt { constructor(options = {}) { - super() - this.options = Options.from(options) // Paths for resolving requires from `rootDir` @@ -22,6 +20,9 @@ export default class Nuxt extends Tapable { this.initialized = false this.errorHandler = this.errorHandler.bind(this) + // Hooks + this._hooks = {} + this.hook = this.hook.bind(this) // Create instance of core components this.moduleContainer = new ModuleContainer(this) @@ -40,17 +41,35 @@ export default class Nuxt extends Tapable { return this._ready } + // Call hooks + if (typeof this.options.hooks === 'function') { + this.options.hooks(this.hook) + } + // Add nuxt modules await this.moduleContainer.ready() - await this.applyPluginsAsync('ready') await this.renderer.ready() this.initialized = true + await this.callHook('ready', this) + return this } + hook(name, fn) { + this._hooks[name] = this._hooks[name] || [] + this._hooks[name].push(fn) + } + + async callHook(name, ...args) { + if (!this._hooks[name]) { + return + } + await sequence(this._hooks[name], (fn) => fn(...args)) + } + 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) @@ -61,7 +80,7 @@ export default class Nuxt extends Tapable { console.log('\n' + chalk.bgGreen.black(' OPEN ') + chalk.green(` http://${_host}:${port}\n`)) // Close server on nuxt close - this.plugin('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') @@ -73,7 +92,7 @@ export default class Nuxt extends Tapable { }) })) - resolve(this.applyPluginsAsync('listen', { server, port, host })) + this.callHook('listen', server, { port, host }).then(resolve) }) // Add server.destroy(cb) method @@ -84,7 +103,7 @@ 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) + this.callHook('error', ...arguments).catch(console.error) // Silent if (this.options.errorHandler === false) { @@ -119,7 +138,7 @@ export default class Nuxt extends Tapable { } async close(callback) { - await this.applyPluginsAsync('close') + await this.callHook('close', this) /* istanbul ignore if */ if (typeof callback === 'function') { diff --git a/lib/core/renderer.js b/lib/core/renderer.js index e3dc16236e..6c09c18001 100644 --- a/lib/core/renderer.js +++ b/lib/core/renderer.js @@ -2,7 +2,6 @@ import ansiHTML from 'ansi-html' import serialize from 'serialize-javascript' import generateETag from 'etag' import fresh from 'fresh' -import Tapable from 'tappable' import serveStatic from 'serve-static' import compression from 'compression' import _ from 'lodash' @@ -24,9 +23,8 @@ setAnsiColors(ansiHTML) let jsdom = null -export default class Renderer extends Tapable { +export default class Renderer { constructor(nuxt) { - super() this.nuxt = nuxt this.options = nuxt.options @@ -49,12 +47,10 @@ export default class Renderer extends Tapable { spaTemplate: null, errorTemplate: parseTemplate('Nuxt.js Internal Server Error') } - - // Call class hook - this.nuxt.applyPlugins('renderer', this) } async ready() { + await this.nuxt.callHook('render:before', this, this.options.render) // Setup nuxt middleware await this.setupMiddleware() @@ -63,8 +59,8 @@ export default class Renderer extends Tapable { await this.loadResources() } - // Call ready plugin - await this.applyPluginsAsync('ready') + // Call done hook + await this.nuxt.callHook('render:done', this) } async loadResources(_fs = fs) { @@ -109,7 +105,7 @@ export default class Renderer extends Tapable { } // Call resourcesLoaded plugin - await this.applyPluginsAsync('resourcesLoaded', this.resources) + await this.nuxt.callHook('render:resourcesLoaded', this.resources) if (updated.length > 0) { this.createRenderer() @@ -195,7 +191,7 @@ export default class Renderer extends Tapable { async setupMiddleware() { // Apply setupMiddleware from modules first - await this.applyPluginsAsync('setupMiddleware', this.app) + await this.nuxt.callHook('render:setupMiddleware', this.app) // Gzip middleware for production if (!this.options.dev && this.options.render.gzip) { diff --git a/package.json b/package.json index d6397fdf3b..d3c3d6c877 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,6 @@ "server-destroy": "^1.0.1", "source-map": "^0.6.1", "source-map-support": "^0.5.0", - "tappable": "^1.1.0", "uglifyjs-webpack-plugin": "^1.0.1", "url-loader": "^0.6.2", "vue": "^2.5.2", diff --git a/test/fixtures/module/modules/tapable/index.js b/test/fixtures/module/modules/hooks/index.js similarity index 66% rename from test/fixtures/module/modules/tapable/index.js rename to test/fixtures/module/modules/hooks/index.js index fa5bd572ef..2c0e2949d0 100644 --- a/test/fixtures/module/modules/tapable/index.js +++ b/test/fixtures/module/modules/hooks/index.js @@ -2,17 +2,17 @@ module.exports = function () { let ctr = 1 // Add hook for module - this.plugin('ready', moduleContainer => { + this.plugin('modules:done', (moduleContainer) => { this.nuxt.__module_hook = moduleContainer && ctr++ }) // Add hook for renderer - this.nuxt.plugin('renderer', renderer => { + this.nuxt.hook('render:done', (renderer) => { this.nuxt.__renderer_hook = renderer && ctr++ }) // Add hook for build - this.nuxt.plugin('build', builder => { + this.nuxt.plugin('build:done', (builder) => { this.nuxt.__builder_hook = builder && ctr++ }) } diff --git a/test/fixtures/module/nuxt.config.js b/test/fixtures/module/nuxt.config.js index 9b53c2a7d1..d60660981f 100755 --- a/test/fixtures/module/nuxt.config.js +++ b/test/fixtures/module/nuxt.config.js @@ -2,7 +2,7 @@ module.exports = { loading: true, modules: [ '~/modules/basic', - '~/modules/tapable', + '~/modules/hooks', { src: '~/modules/middleware', options: { diff --git a/test/module.test.js b/test/module.test.js index 26ac78c81d..a7e6c8eeb1 100755 --- a/test/module.test.js +++ b/test/module.test.js @@ -36,7 +36,7 @@ test('Middleware', async t => { t.is(response, 'It works!', '/api response is correct') }) -test('Tapable', async t => { +test('Hooks', async t => { t.is(nuxt.__module_hook, 1) t.is(nuxt.__renderer_hook, 2) t.is(nuxt.__builder_hook, 3) diff --git a/yarn.lock b/yarn.lock index 1ec7e417c8..f36ff3232c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6488,17 +6488,10 @@ table@^4.0.1: slice-ansi "1.0.0" string-width "^2.1.1" -tapable@^0.2.6, tapable@^0.2.7: +tapable@^0.2.7: version "0.2.8" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" -tappable@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/tappable/-/tappable-1.1.0.tgz#521770dea7dc4715d48ddb4c471071afee012025" - dependencies: - pify "^3.0.0" - tapable "^0.2.6" - tar-pack@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"