mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 14:15:13 +00:00
refactor generator + cli tests (#2205)
* Rename this.generateRoutes to this.staticRoutes * Refactor generator to separate logic * Move routeCreated hook to generateRoute Add routeFailed hook for unhandled exceptions Keep page errors separately until page hooks have been called * Move debug and report statements to hooks * pageErrors can be a const Push pageErrors to errors * fix done hook, errors are 2nd param * Add generator hooks to nuxt-build for spa mode * Added a cli integration test for bin/nuxt-(build|start|generate) * Removed unnecessary waitFor * Use pify instead util.promisify for v6 compatibility * Fix windows build You cant execute .js files directly on Windows/Appveyor so call node with nuxt-*.js file as argument * Fix windows build (2) Use correct folder separators in text search * Fix possible timing quirck in children.path.test
This commit is contained in:
parent
3764dc73c3
commit
65f4a030f4
@ -80,6 +80,38 @@ if (options.mode !== 'spa') {
|
||||
process.exit(1)
|
||||
})
|
||||
} else {
|
||||
const s = Date.now()
|
||||
|
||||
nuxt.hook('generate:distRemoved', function() {
|
||||
debug('Destination folder cleaned')
|
||||
})
|
||||
|
||||
nuxt.hook('generate:distCopied', function() {
|
||||
debug('Static & build files copied')
|
||||
})
|
||||
|
||||
nuxt.hook('generate:page', function(page) {
|
||||
debug('Generate file: ' + page.path)
|
||||
})
|
||||
|
||||
nuxt.hook('generate:done', function(generator, errors) {
|
||||
const duration = Math.round((Date.now() - s) / 100) / 10
|
||||
|
||||
debug(`HTML Files generated in ${duration}s`)
|
||||
|
||||
if (errors.length) {
|
||||
const report = errors.map(({ type, route, error }) => {
|
||||
/* istanbul ignore if */
|
||||
if (type === 'unhandled') {
|
||||
return `Route: '${route}'\n${error.stack}`
|
||||
} else {
|
||||
return `Route: '${route}' thrown an error: \n` + JSON.stringify(error)
|
||||
}
|
||||
})
|
||||
console.error('==== Error report ==== \n' + report.join('\n\n')) // eslint-disable-line no-console
|
||||
}
|
||||
})
|
||||
|
||||
// Disable minify to get exact results of nuxt start
|
||||
nuxt.options.generate.minify = false
|
||||
// Generate on spa mode
|
||||
|
@ -71,6 +71,38 @@ const generateOptions = {
|
||||
build: argv['build']
|
||||
}
|
||||
|
||||
const s = Date.now()
|
||||
|
||||
nuxt.hook('generate:distRemoved', function() {
|
||||
debug('Destination folder cleaned')
|
||||
})
|
||||
|
||||
nuxt.hook('generate:distCopied', function() {
|
||||
debug('Static & build files copied')
|
||||
})
|
||||
|
||||
nuxt.hook('generate:page', function(page) {
|
||||
debug('Generate file: ' + page.path)
|
||||
})
|
||||
|
||||
nuxt.hook('generate:done', function(generator, errors) {
|
||||
const duration = Math.round((Date.now() - s) / 100) / 10
|
||||
|
||||
debug(`HTML Files generated in ${duration}s`)
|
||||
|
||||
if (errors.length) {
|
||||
const report = errors.map(({ type, route, error }) => {
|
||||
/* istanbul ignore if */
|
||||
if (type === 'unhandled') {
|
||||
return `Route: '${route}'\n${error.stack}`
|
||||
} else {
|
||||
return `Route: '${route}' thrown an error: \n` + JSON.stringify(error)
|
||||
}
|
||||
})
|
||||
console.error('==== Error report ==== \n' + report.join('\n\n')) // eslint-disable-line no-console
|
||||
}
|
||||
})
|
||||
|
||||
generator.generate(generateOptions)
|
||||
.then(() => {
|
||||
debug('Generate done')
|
||||
|
@ -3,9 +3,6 @@ import _ from 'lodash'
|
||||
import { resolve, join, dirname, sep } from 'path'
|
||||
import { minify } from 'html-minifier'
|
||||
import { isUrl, promisifyRoute, waitFor, flatRoutes } from 'utils'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('nuxt:generate')
|
||||
|
||||
export default class Generator {
|
||||
constructor(nuxt, builder) {
|
||||
@ -14,22 +11,33 @@ export default class Generator {
|
||||
this.builder = builder
|
||||
|
||||
// Set variables
|
||||
this.generateRoutes = resolve(this.options.srcDir, 'static')
|
||||
this.staticRoutes = 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))
|
||||
}
|
||||
|
||||
async generate({ build = true, init = true } = {}) {
|
||||
await this.initiate({ build, init })
|
||||
|
||||
const routes = await this.initRoutes()
|
||||
|
||||
const errors = await this.generateRoutes(routes)
|
||||
await this.afterGenerate()
|
||||
|
||||
// done hook
|
||||
await this.nuxt.callHook('generate:done', this, errors)
|
||||
|
||||
return { errors }
|
||||
}
|
||||
|
||||
async initiate({ 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()
|
||||
|
||||
@ -42,7 +50,9 @@ export default class Generator {
|
||||
if (init) {
|
||||
await this.initDist()
|
||||
}
|
||||
}
|
||||
|
||||
async initRoutes() {
|
||||
// Resolve config.generate.routes promises before generating the routes
|
||||
let generateRoutes = []
|
||||
if (this.options.router.mode !== 'hash') {
|
||||
@ -61,16 +71,25 @@ export default class Generator {
|
||||
// extendRoutes hook
|
||||
await this.nuxt.callHook('generate:extendRoutes', routes)
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
async generateRoutes(routes) {
|
||||
let errors = []
|
||||
|
||||
// Start generate process
|
||||
while (routes.length) {
|
||||
let n = 0
|
||||
await Promise.all(routes.splice(0, this.options.generate.concurrency).map(async ({ route, payload }) => {
|
||||
await waitFor(n++ * this.options.generate.interval)
|
||||
await this.generateRoute({ route, payload, errors })
|
||||
await this.nuxt.callHook('generate:routeCreated', route)
|
||||
}))
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
async afterGenerate() {
|
||||
const indexPath = join(this.distPath, 'index.html')
|
||||
if (existsSync(indexPath)) {
|
||||
// Copy /index.html to /200.html for surge SPA
|
||||
@ -80,37 +99,18 @@ export default class Generator {
|
||||
await copy(indexPath, _200Path)
|
||||
}
|
||||
}
|
||||
|
||||
const duration = Math.round((Date.now() - s) / 100) / 10
|
||||
debug(`HTML Files generated in ${duration}s`)
|
||||
|
||||
// done hook
|
||||
await this.nuxt.callHook('generate:done', this, errors)
|
||||
|
||||
if (errors.length) {
|
||||
const report = errors.map(({ type, route, error }) => {
|
||||
/* istanbul ignore if */
|
||||
if (type === 'unhandled') {
|
||||
return `Route: '${route}'\n${error.stack}`
|
||||
} else {
|
||||
return `Route: '${route}' thrown an error: \n` + JSON.stringify(error)
|
||||
}
|
||||
})
|
||||
console.error('==== Error report ==== \n' + report.join('\n\n')) // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
return { duration, errors }
|
||||
}
|
||||
|
||||
async initDist() {
|
||||
// Clean destination folder
|
||||
await remove(this.distPath)
|
||||
debug('Destination folder cleaned')
|
||||
|
||||
await this.nuxt.callHook('generate:distRemoved', this)
|
||||
|
||||
// Copy static and built files
|
||||
/* istanbul ignore if */
|
||||
if (existsSync(this.generateRoutes)) {
|
||||
await copy(this.generateRoutes, this.distPath)
|
||||
if (existsSync(this.staticRoutes)) {
|
||||
await copy(this.staticRoutes, this.distPath)
|
||||
}
|
||||
await copy(this.srcBuiltPath, this.distNuxtPath)
|
||||
|
||||
@ -133,7 +133,7 @@ export default class Generator {
|
||||
}
|
||||
})
|
||||
|
||||
debug('Static & build files copied')
|
||||
await this.nuxt.callHook('generate:distCopied', this)
|
||||
}
|
||||
|
||||
decorateWithPayloads(routes, generateRoutes) {
|
||||
@ -159,16 +159,22 @@ export default class Generator {
|
||||
|
||||
async generateRoute({ route, payload = {}, errors = [] }) {
|
||||
let html
|
||||
const pageErrors = []
|
||||
|
||||
try {
|
||||
const res = await this.nuxt.renderer.renderRoute(route, { _generate: true, payload })
|
||||
html = res.html
|
||||
if (res.error) {
|
||||
errors.push({ type: 'handled', route, error: res.error })
|
||||
pageErrors.push({ type: 'handled', route, error: res.error })
|
||||
}
|
||||
} catch (err) {
|
||||
/* istanbul ignore next */
|
||||
return errors.push({ type: 'unhandled', route, error: err })
|
||||
pageErrors.push({ type: 'unhandled', route, error: err })
|
||||
Array.prototype.push.apply(errors, pageErrors)
|
||||
|
||||
await this.nuxt.callHook('generate:routeFailed', { route, errors: pageErrors })
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.options.generate.minify) {
|
||||
@ -176,7 +182,7 @@ export default class Generator {
|
||||
html = minify(html, this.options.generate.minify)
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
const minifyErr = new Error(`HTML minification failed. Make sure the route generates valid HTML. Failed HTML:\n ${html}`)
|
||||
errors.push({ type: 'unhandled', route, error: minifyErr })
|
||||
pageErrors.push({ type: 'unhandled', route, error: minifyErr })
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,13 +200,18 @@ export default class Generator {
|
||||
const page = { route, path, html }
|
||||
await this.nuxt.callHook('generate:page', page)
|
||||
|
||||
debug('Generate file: ' + page.path)
|
||||
page.path = join(this.distPath, page.path)
|
||||
|
||||
// Make sure the sub folders are created
|
||||
await mkdirp(dirname(page.path))
|
||||
await writeFile(page.path, page.html, 'utf8')
|
||||
|
||||
await this.nuxt.callHook('generate:routeCreated', { route, path: page.path, errors: pageErrors })
|
||||
|
||||
if (pageErrors.length) {
|
||||
Array.prototype.push.apply(errors, pageErrors)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ test('Search a country', async t => {
|
||||
|
||||
await page.type('[data-test-search-input]', 'gu')
|
||||
|
||||
await Utils.waitFor(100)
|
||||
await Utils.waitFor(250)
|
||||
const newCountries = await page.$$text('[data-test-search-result]')
|
||||
t.is(newCountries.length, 1)
|
||||
t.deepEqual(newCountries, ['Guinea'])
|
||||
|
93
test/cli.test.js
Normal file
93
test/cli.test.js
Normal file
@ -0,0 +1,93 @@
|
||||
import test from 'ava'
|
||||
import { resolve, sep } from 'path'
|
||||
import rp from 'request-promise-native'
|
||||
import { Utils } from '../index.js'
|
||||
import pify from 'pify'
|
||||
import { exec, spawn } from 'child_process'
|
||||
|
||||
const execify = pify(exec, { multiArgs: true })
|
||||
const rootDir = resolve(__dirname, 'fixtures/basic')
|
||||
|
||||
const port = 4011
|
||||
const url = (route) => 'http://localhost:' + port + route
|
||||
|
||||
test('bin/nuxt-build', async t => {
|
||||
const binBuild = resolve(__dirname, '..', 'bin', 'nuxt-build')
|
||||
|
||||
const [ stdout, stderr ] = await execify(`node ${binBuild} ${rootDir}`)
|
||||
|
||||
t.true(stdout.includes('server-bundle.json'))
|
||||
t.true(stderr.includes('Building done'))
|
||||
})
|
||||
|
||||
test('bin/nuxt-start', async t => {
|
||||
const binStart = resolve(__dirname, '..', 'bin', 'nuxt-start')
|
||||
|
||||
let stdout = ''
|
||||
let stderr = ''
|
||||
let error
|
||||
let exitCode
|
||||
|
||||
const env = process.env
|
||||
env.PORT = port
|
||||
|
||||
const nuxtStart = spawn('node', [binStart, rootDir], { env: env })
|
||||
|
||||
nuxtStart.stdout.on('data', (data) => {
|
||||
stdout += data
|
||||
})
|
||||
|
||||
nuxtStart.stderr.on('data', (data) => {
|
||||
stderr += data
|
||||
})
|
||||
|
||||
nuxtStart.on('error', (err) => {
|
||||
error = err
|
||||
})
|
||||
|
||||
nuxtStart.on('close', (code) => {
|
||||
exitCode = code
|
||||
})
|
||||
|
||||
// Give the process max 10s to start
|
||||
let iterator = 0
|
||||
while (!stdout.includes('OPEN') && iterator < 40) {
|
||||
await Utils.waitFor(250)
|
||||
iterator++
|
||||
}
|
||||
|
||||
t.is(error, undefined)
|
||||
t.true(stdout.includes('OPEN'))
|
||||
|
||||
const html = await rp(url('/users/1'))
|
||||
t.true(html.includes('<h1>User: 1</h1>'))
|
||||
|
||||
nuxtStart.kill()
|
||||
|
||||
// Wait max 10s for the process to be killed
|
||||
iterator = 0
|
||||
while (exitCode === undefined && iterator < 40) { // eslint-disable-line no-unmodified-loop-condition
|
||||
await Utils.waitFor(250)
|
||||
iterator++
|
||||
}
|
||||
|
||||
if (iterator >= 40) {
|
||||
t.log(`WARN: we were unable to automatically kill the child process with pid: ${nuxtStart.pid}`)
|
||||
}
|
||||
|
||||
t.is(stderr, '')
|
||||
t.is(exitCode, null)
|
||||
})
|
||||
|
||||
test('bin/nuxt-generate', async t => {
|
||||
const binGenerate = resolve(__dirname, '..', 'bin', 'nuxt-generate')
|
||||
|
||||
const [ stdout, stderr ] = await execify(`node ${binGenerate} ${rootDir}`)
|
||||
|
||||
t.true(stdout.includes('server-bundle.json'))
|
||||
t.true(stderr.includes('Destination folder cleaned'))
|
||||
t.true(stderr.includes('Static & build files copied'))
|
||||
t.true(stderr.includes(`Generate file: ${sep}users${sep}1${sep}index.html`))
|
||||
t.true(stderr.includes('Error report'))
|
||||
t.true(stderr.includes('Generate done'))
|
||||
})
|
Loading…
Reference in New Issue
Block a user