mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 22:25:12 +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)
|
process.exit(1)
|
||||||
})
|
})
|
||||||
} else {
|
} 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
|
// Disable minify to get exact results of nuxt start
|
||||||
nuxt.options.generate.minify = false
|
nuxt.options.generate.minify = false
|
||||||
// Generate on spa mode
|
// Generate on spa mode
|
||||||
|
@ -71,6 +71,38 @@ const generateOptions = {
|
|||||||
build: argv['build']
|
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)
|
generator.generate(generateOptions)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
debug('Generate done')
|
debug('Generate done')
|
||||||
|
@ -3,9 +3,6 @@ import _ from 'lodash'
|
|||||||
import { resolve, join, dirname, sep } from 'path'
|
import { resolve, join, dirname, sep } from 'path'
|
||||||
import { minify } from 'html-minifier'
|
import { minify } from 'html-minifier'
|
||||||
import { isUrl, promisifyRoute, waitFor, flatRoutes } from 'utils'
|
import { isUrl, promisifyRoute, waitFor, flatRoutes } from 'utils'
|
||||||
import Debug from 'debug'
|
|
||||||
|
|
||||||
const debug = Debug('nuxt:generate')
|
|
||||||
|
|
||||||
export default class Generator {
|
export default class Generator {
|
||||||
constructor(nuxt, builder) {
|
constructor(nuxt, builder) {
|
||||||
@ -14,22 +11,33 @@ export default class Generator {
|
|||||||
this.builder = builder
|
this.builder = builder
|
||||||
|
|
||||||
// Set variables
|
// Set variables
|
||||||
this.generateRoutes = resolve(this.options.srcDir, 'static')
|
this.staticRoutes = resolve(this.options.srcDir, 'static')
|
||||||
this.srcBuiltPath = resolve(this.options.buildDir, 'dist')
|
this.srcBuiltPath = resolve(this.options.buildDir, 'dist')
|
||||||
this.distPath = resolve(this.options.rootDir, this.options.generate.dir)
|
this.distPath = resolve(this.options.rootDir, this.options.generate.dir)
|
||||||
this.distNuxtPath = join(this.distPath, (isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath))
|
this.distNuxtPath = join(this.distPath, (isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
async generate({ build = true, init = true } = {}) {
|
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
|
// Wait for nuxt be ready
|
||||||
await this.nuxt.ready()
|
await this.nuxt.ready()
|
||||||
|
|
||||||
// Call before hook
|
// Call before hook
|
||||||
await this.nuxt.callHook('generate:before', this, this.options.generate)
|
await this.nuxt.callHook('generate:before', this, this.options.generate)
|
||||||
|
|
||||||
const s = Date.now()
|
|
||||||
let errors = []
|
|
||||||
|
|
||||||
// Add flag to set process.static
|
// Add flag to set process.static
|
||||||
this.builder.forGenerate()
|
this.builder.forGenerate()
|
||||||
|
|
||||||
@ -42,7 +50,9 @@ export default class Generator {
|
|||||||
if (init) {
|
if (init) {
|
||||||
await this.initDist()
|
await this.initDist()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async initRoutes() {
|
||||||
// Resolve config.generate.routes promises before generating the routes
|
// Resolve config.generate.routes promises before generating the routes
|
||||||
let generateRoutes = []
|
let generateRoutes = []
|
||||||
if (this.options.router.mode !== 'hash') {
|
if (this.options.router.mode !== 'hash') {
|
||||||
@ -61,16 +71,25 @@ export default class Generator {
|
|||||||
// extendRoutes hook
|
// extendRoutes hook
|
||||||
await this.nuxt.callHook('generate:extendRoutes', routes)
|
await this.nuxt.callHook('generate:extendRoutes', routes)
|
||||||
|
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateRoutes(routes) {
|
||||||
|
let errors = []
|
||||||
|
|
||||||
// Start generate process
|
// Start generate process
|
||||||
while (routes.length) {
|
while (routes.length) {
|
||||||
let n = 0
|
let n = 0
|
||||||
await Promise.all(routes.splice(0, this.options.generate.concurrency).map(async ({ route, payload }) => {
|
await Promise.all(routes.splice(0, this.options.generate.concurrency).map(async ({ route, payload }) => {
|
||||||
await waitFor(n++ * this.options.generate.interval)
|
await waitFor(n++ * this.options.generate.interval)
|
||||||
await this.generateRoute({ route, payload, errors })
|
await this.generateRoute({ route, payload, errors })
|
||||||
await this.nuxt.callHook('generate:routeCreated', route)
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
async afterGenerate() {
|
||||||
const indexPath = join(this.distPath, 'index.html')
|
const indexPath = join(this.distPath, 'index.html')
|
||||||
if (existsSync(indexPath)) {
|
if (existsSync(indexPath)) {
|
||||||
// Copy /index.html to /200.html for surge SPA
|
// Copy /index.html to /200.html for surge SPA
|
||||||
@ -80,37 +99,18 @@ export default class Generator {
|
|||||||
await copy(indexPath, _200Path)
|
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() {
|
async initDist() {
|
||||||
// Clean destination folder
|
// Clean destination folder
|
||||||
await remove(this.distPath)
|
await remove(this.distPath)
|
||||||
debug('Destination folder cleaned')
|
|
||||||
|
await this.nuxt.callHook('generate:distRemoved', this)
|
||||||
|
|
||||||
// Copy static and built files
|
// Copy static and built files
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
||||||
if (existsSync(this.generateRoutes)) {
|
if (existsSync(this.staticRoutes)) {
|
||||||
await copy(this.generateRoutes, this.distPath)
|
await copy(this.staticRoutes, this.distPath)
|
||||||
}
|
}
|
||||||
await copy(this.srcBuiltPath, this.distNuxtPath)
|
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) {
|
decorateWithPayloads(routes, generateRoutes) {
|
||||||
@ -159,16 +159,22 @@ export default class Generator {
|
|||||||
|
|
||||||
async generateRoute({ route, payload = {}, errors = [] }) {
|
async generateRoute({ route, payload = {}, errors = [] }) {
|
||||||
let html
|
let html
|
||||||
|
const pageErrors = []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await this.nuxt.renderer.renderRoute(route, { _generate: true, payload })
|
const res = await this.nuxt.renderer.renderRoute(route, { _generate: true, payload })
|
||||||
html = res.html
|
html = res.html
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
errors.push({ type: 'handled', route, error: res.error })
|
pageErrors.push({ type: 'handled', route, error: res.error })
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
/* istanbul ignore next */
|
/* 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) {
|
if (this.options.generate.minify) {
|
||||||
@ -176,7 +182,7 @@ export default class Generator {
|
|||||||
html = minify(html, this.options.generate.minify)
|
html = minify(html, this.options.generate.minify)
|
||||||
} catch (err) /* istanbul ignore next */ {
|
} catch (err) /* istanbul ignore next */ {
|
||||||
const minifyErr = new Error(`HTML minification failed. Make sure the route generates valid HTML. Failed HTML:\n ${html}`)
|
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 }
|
const page = { route, path, html }
|
||||||
await this.nuxt.callHook('generate:page', page)
|
await this.nuxt.callHook('generate:page', page)
|
||||||
|
|
||||||
debug('Generate file: ' + page.path)
|
|
||||||
page.path = join(this.distPath, page.path)
|
page.path = join(this.distPath, page.path)
|
||||||
|
|
||||||
// Make sure the sub folders are created
|
// Make sure the sub folders are created
|
||||||
await mkdirp(dirname(page.path))
|
await mkdirp(dirname(page.path))
|
||||||
await writeFile(page.path, page.html, 'utf8')
|
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
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ test('Search a country', async t => {
|
|||||||
|
|
||||||
await page.type('[data-test-search-input]', 'gu')
|
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]')
|
const newCountries = await page.$$text('[data-test-search-result]')
|
||||||
t.is(newCountries.length, 1)
|
t.is(newCountries.length, 1)
|
||||||
t.deepEqual(newCountries, ['Guinea'])
|
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