Hooks ready to be tested

This commit is contained in:
Sébastien Chopin 2017-10-30 22:39:08 +01:00
parent f72e620d7d
commit 94ad5955e6
11 changed files with 84 additions and 87 deletions

View File

@ -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()
})

View File

@ -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 }) => {

View File

@ -291,6 +291,7 @@ Options.defaults = {
editor: {
editor: 'code'
},
hooks: () => {},
messages: {
error_404: 'This page could not be found',
server_error: 'Server error',

View File

@ -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 })
}
}

View File

@ -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') {

View File

@ -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) {

View File

@ -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",

View File

@ -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++
})
}

View File

@ -2,7 +2,7 @@ module.exports = {
loading: true,
modules: [
'~/modules/basic',
'~/modules/tapable',
'~/modules/hooks',
{
src: '~/modules/middleware',
options: {

View File

@ -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)

View File

@ -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"