mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-27 08:02:01 +00:00
feat: static target DX improvements (#7712)
[release] Co-authored-by: Sébastien Chopin <seb@nuxtjs.com> Co-authored-by: pimlie <pimlie@hotmail.com>
This commit is contained in:
parent
f81c588bf6
commit
c5a4465572
@ -19,7 +19,8 @@ module.exports = {
|
|||||||
// But its performance overhead is pretty bad (30+%).
|
// But its performance overhead is pretty bad (30+%).
|
||||||
// detectOpenHandles: true
|
// detectOpenHandles: true
|
||||||
|
|
||||||
setupFilesAfterEnv: ['./test/utils/setup'],
|
setupFilesAfterEnv: ['./test/utils/setup-env'],
|
||||||
|
setupFiles: ['./test/utils/setup'],
|
||||||
|
|
||||||
coverageDirectory: './coverage',
|
coverageDirectory: './coverage',
|
||||||
|
|
||||||
|
@ -20,10 +20,13 @@
|
|||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"connect": "^3.7.0",
|
"connect": "^3.7.0",
|
||||||
"consola": "^2.14.0",
|
"consola": "^2.14.0",
|
||||||
|
"crc": "^3.8.0",
|
||||||
|
"destr": "^1.0.0",
|
||||||
"esm": "^3.2.25",
|
"esm": "^3.2.25",
|
||||||
"execa": "^3.4.0",
|
"execa": "^3.4.0",
|
||||||
"exit": "^0.1.2",
|
"exit": "^0.1.2",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
|
"globby": "^11.0.1",
|
||||||
"hable": "^3.0.0",
|
"hable": "^3.0.0",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"opener": "1.5.1",
|
"opener": "1.5.1",
|
||||||
|
@ -85,7 +85,7 @@ export default {
|
|||||||
const builder = await cmd.getBuilder(nuxt)
|
const builder = await cmd.getBuilder(nuxt)
|
||||||
await builder.build()
|
await builder.build()
|
||||||
|
|
||||||
const nextCommand = nuxt.options.target === TARGETS.static ? 'nuxt export' : 'nuxt start'
|
const nextCommand = nuxt.options.target === TARGETS.static ? 'nuxt generate' : 'nuxt start'
|
||||||
consola.info('Ready to run `' + (nextCommand) + '`')
|
consola.info('Ready to run `' + (nextCommand) + '`')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,10 @@
|
|||||||
import path from 'path'
|
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
import { TARGETS } from '@nuxt/utils'
|
import generate from './generate'
|
||||||
import { common, locking } from '../options'
|
|
||||||
import { createLock } from '../utils'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'export',
|
...generate,
|
||||||
description: 'Export a static generated web application',
|
run (...args) {
|
||||||
usage: 'export <dir>',
|
consola.warn('`nuxt export` has been deprecated! Please use `nuxt generate`.')
|
||||||
options: {
|
return generate.run.call(this, ...args)
|
||||||
...common,
|
|
||||||
...locking,
|
|
||||||
'fail-on-error': {
|
|
||||||
type: 'boolean',
|
|
||||||
default: false,
|
|
||||||
description: 'Exit with non-zero status code if there are errors when exporting pages'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async run (cmd) {
|
|
||||||
const config = await cmd.getNuxtConfig({
|
|
||||||
dev: false,
|
|
||||||
target: TARGETS.static,
|
|
||||||
_export: true
|
|
||||||
})
|
|
||||||
const nuxt = await cmd.getNuxt(config)
|
|
||||||
|
|
||||||
if (cmd.argv.lock) {
|
|
||||||
await cmd.setLock(await createLock({
|
|
||||||
id: 'export',
|
|
||||||
dir: nuxt.options.generate.dir,
|
|
||||||
root: config.rootDir
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const generator = await cmd.getGenerator(nuxt)
|
|
||||||
await nuxt.server.listen(0)
|
|
||||||
|
|
||||||
const { errors } = await generator.generate({
|
|
||||||
init: true,
|
|
||||||
build: false
|
|
||||||
})
|
|
||||||
|
|
||||||
await nuxt.close()
|
|
||||||
if (cmd.argv['fail-on-error'] && errors.length > 0) {
|
|
||||||
throw new Error('Error exporting pages, exiting with non-zero code')
|
|
||||||
}
|
|
||||||
consola.info('Ready to run `nuxt serve` or deploy `' + path.basename(nuxt.options.generate.dir) + '/` directory')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { TARGETS } from '@nuxt/utils'
|
import { TARGETS } from '@nuxt/utils'
|
||||||
import { common, locking } from '../options'
|
import { common, locking } from '../options'
|
||||||
import { normalizeArg, createLock } from '../utils'
|
import { normalizeArg, createLock } from '../utils'
|
||||||
|
import { ensureBuild, generate } from '../utils/generate'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'generate',
|
name: 'generate',
|
||||||
@ -54,23 +55,22 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async run (cmd) {
|
async run (cmd) {
|
||||||
const config = await cmd.getNuxtConfig({
|
const config = await cmd.getNuxtConfig({ dev: false })
|
||||||
dev: false,
|
|
||||||
_build: cmd.argv.build,
|
|
||||||
_generate: true
|
|
||||||
})
|
|
||||||
|
|
||||||
if (config.target === TARGETS.static) {
|
|
||||||
throw new Error("Please use `nuxt export` when using `target: 'static'`")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forcing static target anyway
|
|
||||||
config.target = TARGETS.static
|
|
||||||
|
|
||||||
// Disable analyze if set by the nuxt config
|
// Disable analyze if set by the nuxt config
|
||||||
config.build = config.build || {}
|
config.build = config.build || {}
|
||||||
config.build.analyze = false
|
config.build.analyze = false
|
||||||
|
|
||||||
|
// Full static
|
||||||
|
if (config.target === TARGETS.static) {
|
||||||
|
await ensureBuild(cmd)
|
||||||
|
await generate(cmd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forcing static target anyway
|
||||||
|
config.target = TARGETS.static
|
||||||
|
|
||||||
// Set flag to keep the prerendering behaviour
|
// Set flag to keep the prerendering behaviour
|
||||||
config._legacyGenerate = true
|
config._legacyGenerate = true
|
||||||
|
|
||||||
|
@ -1,84 +1,10 @@
|
|||||||
import { promises as fs } from 'fs'
|
import consola from 'consola'
|
||||||
import { join, extname, basename } from 'path'
|
import start from './start'
|
||||||
import connect from 'connect'
|
|
||||||
import serveStatic from 'serve-static'
|
|
||||||
import compression from 'compression'
|
|
||||||
import { getNuxtConfig } from '@nuxt/config'
|
|
||||||
import { TARGETS } from '@nuxt/utils'
|
|
||||||
import { common, server } from '../options'
|
|
||||||
import { showBanner } from '../utils/banner'
|
|
||||||
import * as imports from '../imports'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'serve',
|
...start,
|
||||||
description: 'Serve the exported static application (should be compiled with `nuxt build` and `nuxt export` first)',
|
run (...args) {
|
||||||
usage: 'serve <dir>',
|
consola.warn('`nuxt serve` has been deprecated! Please use `nuxt start`.')
|
||||||
options: {
|
return start.run.call(this, ...args)
|
||||||
'config-file': common['config-file'],
|
|
||||||
version: common.version,
|
|
||||||
help: common.help,
|
|
||||||
...server
|
|
||||||
},
|
|
||||||
async run (cmd) {
|
|
||||||
let options = await cmd.getNuxtConfig({ dev: false })
|
|
||||||
// add default options
|
|
||||||
options = getNuxtConfig(options)
|
|
||||||
try {
|
|
||||||
// overwrites with build config
|
|
||||||
const buildConfig = require(join(options.buildDir, 'nuxt/config.json'))
|
|
||||||
options.target = buildConfig.target
|
|
||||||
} catch (err) {}
|
|
||||||
|
|
||||||
if (options.target === TARGETS.server) {
|
|
||||||
throw new Error('You cannot use `nuxt serve` with ' + TARGETS.server + ' target, please use `nuxt start`')
|
|
||||||
}
|
|
||||||
const distStat = await fs.stat(options.generate.dir).catch(err => null) // eslint-disable-line handle-callback-err
|
|
||||||
if (!distStat || !distStat.isDirectory()) {
|
|
||||||
throw new Error('Output directory `' + basename(options.generate.dir) + '/` does not exists, please run `nuxt export` before `nuxt serve`.')
|
|
||||||
}
|
|
||||||
const app = connect()
|
|
||||||
app.use(compression({ threshold: 0 }))
|
|
||||||
app.use(
|
|
||||||
options.router.base,
|
|
||||||
serveStatic(options.generate.dir, {
|
|
||||||
extensions: ['html']
|
|
||||||
})
|
|
||||||
)
|
|
||||||
if (options.generate.fallback) {
|
|
||||||
const fallbackFile = await fs.readFile(join(options.generate.dir, options.generate.fallback), 'utf-8')
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
const ext = extname(req.url) || '.html'
|
|
||||||
|
|
||||||
if (ext !== '.html') {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
res.writeHeader(200, {
|
|
||||||
'Content-Type': 'text/html'
|
|
||||||
})
|
|
||||||
res.write(fallbackFile)
|
|
||||||
res.end()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const { port, host, socket, https } = options.server
|
|
||||||
const { Listener } = await imports.server()
|
|
||||||
const listener = new Listener({
|
|
||||||
port,
|
|
||||||
host,
|
|
||||||
socket,
|
|
||||||
https,
|
|
||||||
app,
|
|
||||||
dev: true, // try another port if taken
|
|
||||||
baseURL: options.router.base
|
|
||||||
})
|
|
||||||
await listener.listen()
|
|
||||||
const { Nuxt } = await imports.core()
|
|
||||||
showBanner({
|
|
||||||
constructor: Nuxt,
|
|
||||||
options,
|
|
||||||
server: {
|
|
||||||
listeners: [listener]
|
|
||||||
}
|
|
||||||
}, false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { TARGETS } from '@nuxt/utils'
|
import { TARGETS } from '@nuxt/utils'
|
||||||
|
import consola from 'consola'
|
||||||
import { common, server } from '../options'
|
import { common, server } from '../options'
|
||||||
import { showBanner } from '../utils/banner'
|
import { showBanner } from '../utils/banner'
|
||||||
|
import { serve } from '../utils/serve'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'start',
|
name: 'start',
|
||||||
@ -12,9 +14,12 @@ export default {
|
|||||||
},
|
},
|
||||||
async run (cmd) {
|
async run (cmd) {
|
||||||
const config = await cmd.getNuxtConfig({ dev: false, _start: true })
|
const config = await cmd.getNuxtConfig({ dev: false, _start: true })
|
||||||
|
|
||||||
if (config.target === TARGETS.static) {
|
if (config.target === TARGETS.static) {
|
||||||
throw new Error('You cannot use `nuxt start` with ' + TARGETS.static + ' target, please use `nuxt export` and `nuxt serve`')
|
consola.info('Serving static dist')
|
||||||
|
return serve(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
const nuxt = await cmd.getNuxt(config)
|
const nuxt = await cmd.getNuxt(config)
|
||||||
|
|
||||||
// Listen and show ready banner
|
// Listen and show ready banner
|
||||||
|
161
packages/cli/src/utils/generate.js
Normal file
161
packages/cli/src/utils/generate.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path, { relative } from 'path'
|
||||||
|
import crc32 from 'crc/lib/crc32'
|
||||||
|
import consola from 'consola'
|
||||||
|
import globby from 'globby'
|
||||||
|
import destr from 'destr'
|
||||||
|
import { TARGETS } from '@nuxt/utils'
|
||||||
|
|
||||||
|
export async function generate (cmd) {
|
||||||
|
const nuxt = await getNuxt({ server: true }, cmd)
|
||||||
|
const generator = await cmd.getGenerator(nuxt)
|
||||||
|
|
||||||
|
await nuxt.server.listen(0)
|
||||||
|
await generator.generate({ build: false })
|
||||||
|
await nuxt.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function ensureBuild (cmd) {
|
||||||
|
const nuxt = await getNuxt({ _build: true, server: false }, cmd)
|
||||||
|
const { options } = nuxt
|
||||||
|
|
||||||
|
if (options.generate.cache === false || process.env.NUXT_BUILD) {
|
||||||
|
const builder = await cmd.getBuilder(nuxt)
|
||||||
|
await builder.build()
|
||||||
|
await nuxt.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default build ignore files
|
||||||
|
const ignore = [
|
||||||
|
options.buildDir,
|
||||||
|
options.dir.static,
|
||||||
|
options.generate.dir,
|
||||||
|
'node_modules',
|
||||||
|
'.**/*',
|
||||||
|
'.*',
|
||||||
|
'README.md'
|
||||||
|
]
|
||||||
|
|
||||||
|
// Extend ignore
|
||||||
|
const { generate } = options
|
||||||
|
if (generate.cache.ignore === 'function') {
|
||||||
|
generate.cache.ignore = generate.cache.ignore(ignore)
|
||||||
|
} else if (Array.isArray(generate.cache.ignore)) {
|
||||||
|
generate.cache.ignore = generate.cache.ignore.concat(ignore)
|
||||||
|
}
|
||||||
|
await nuxt.callHook('generate:cache:ignore', generate.cache.ignore)
|
||||||
|
|
||||||
|
// Take a snapshot of current project
|
||||||
|
const snapshotOptions = {
|
||||||
|
rootDir: nuxt.options.rootDir,
|
||||||
|
ignore: nuxt.options.generate.cache.ignore,
|
||||||
|
globbyOptions: nuxt.options.generate.cache.globbyOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentBuildSnapshot = await snapshot(snapshotOptions)
|
||||||
|
|
||||||
|
// Current build meta
|
||||||
|
const currentBuild = {
|
||||||
|
// @ts-ignore
|
||||||
|
nuxtVersion: nuxt.constructor.version,
|
||||||
|
ssr: nuxt.options.ssr,
|
||||||
|
target: nuxt.options.target,
|
||||||
|
snapshot: currentBuildSnapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if build can be skipped
|
||||||
|
const nuxtBuildFile = path.resolve(nuxt.options.buildDir, 'build.json')
|
||||||
|
if (fs.existsSync(nuxtBuildFile)) {
|
||||||
|
const previousBuild = destr(fs.readFileSync(nuxtBuildFile, 'utf-8')) || {}
|
||||||
|
|
||||||
|
// Quick diff
|
||||||
|
const needBuild = false
|
||||||
|
for (const field of ['nuxtVersion', 'ssr', 'target']) {
|
||||||
|
if (previousBuild[field] !== currentBuild[field]) {
|
||||||
|
consola.info(`Doing webpack rebuild because ${field} changed`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full snapshot diff
|
||||||
|
if (!needBuild) {
|
||||||
|
const changed = compareSnapshots(previousBuild.snapshot, currentBuild.snapshot)
|
||||||
|
if (!changed) {
|
||||||
|
consola.success('Skipping webpack build as no changes detected')
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
consola.info(`Doing webpack rebuild because ${changed} modified`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webpack build
|
||||||
|
const builder = await cmd.getBuilder(nuxt)
|
||||||
|
await builder.build()
|
||||||
|
|
||||||
|
// Write build.json
|
||||||
|
fs.writeFileSync(nuxtBuildFile, JSON.stringify(currentBuild, null, 2), 'utf-8')
|
||||||
|
|
||||||
|
await nuxt.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNuxt (args, cmd) {
|
||||||
|
const config = await cmd.getNuxtConfig({ dev: false, ...args })
|
||||||
|
|
||||||
|
if (config.target === TARGETS.static) {
|
||||||
|
config._export = true
|
||||||
|
} else {
|
||||||
|
config._legacyGenerate = true
|
||||||
|
}
|
||||||
|
config.buildDir = (config.static && config.static.cacheDir) || path.resolve(config.rootDir, 'node_modules/.cache/nuxt')
|
||||||
|
config.build = config.build || {}
|
||||||
|
config.build.transpile = config.build.transpile || []
|
||||||
|
config.build.transpile.push(config.buildDir)
|
||||||
|
|
||||||
|
const nuxt = await cmd.getNuxt(config)
|
||||||
|
|
||||||
|
return nuxt
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compareSnapshots (from, to) {
|
||||||
|
const allKeys = Array.from(new Set([
|
||||||
|
...Object.keys(from).sort(),
|
||||||
|
...Object.keys(to).sort()
|
||||||
|
]))
|
||||||
|
|
||||||
|
for (const key of allKeys) {
|
||||||
|
if (JSON.stringify(from[key]) !== JSON.stringify(to[key])) {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function snapshot ({ globbyOptions, ignore, rootDir }) {
|
||||||
|
const snapshot = {}
|
||||||
|
|
||||||
|
const files = await globby('**/*.*', {
|
||||||
|
...globbyOptions,
|
||||||
|
ignore,
|
||||||
|
cwd: rootDir,
|
||||||
|
absolute: true
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(files.map(async (p) => {
|
||||||
|
const key = relative(rootDir, p)
|
||||||
|
try {
|
||||||
|
const fileContent = await fs.readFile(p)
|
||||||
|
snapshot[key] = {
|
||||||
|
checksum: await crc32(fileContent).toString(16)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: Check for other errors like permission denied
|
||||||
|
snapshot[key] = {
|
||||||
|
exists: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return snapshot
|
||||||
|
}
|
73
packages/cli/src/utils/serve.js
Normal file
73
packages/cli/src/utils/serve.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { promises as fs } from 'fs'
|
||||||
|
import { join, extname, basename } from 'path'
|
||||||
|
import connect from 'connect'
|
||||||
|
import serveStatic from 'serve-static'
|
||||||
|
import compression from 'compression'
|
||||||
|
import { getNuxtConfig } from '@nuxt/config'
|
||||||
|
import { showBanner } from '../utils/banner'
|
||||||
|
import * as imports from '../imports'
|
||||||
|
|
||||||
|
export async function serve (cmd) {
|
||||||
|
const _config = await cmd.getNuxtConfig({ dev: false })
|
||||||
|
|
||||||
|
// add default options
|
||||||
|
const options = getNuxtConfig(_config)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// overwrites with build config
|
||||||
|
const buildConfig = require(join(options.buildDir, 'nuxt/config.json'))
|
||||||
|
options.target = buildConfig.target
|
||||||
|
} catch (err) { }
|
||||||
|
|
||||||
|
const distStat = await fs.stat(options.generate.dir).catch(err => null) // eslint-disable-line handle-callback-err
|
||||||
|
if (!distStat || !distStat.isDirectory()) {
|
||||||
|
throw new Error('Output directory `' + basename(options.generate.dir) + '/` does not exists, please use `nuxt generate` before `nuxt start` for static target.')
|
||||||
|
}
|
||||||
|
const app = connect()
|
||||||
|
app.use(compression({ threshold: 0 }))
|
||||||
|
app.use(
|
||||||
|
options.router.base,
|
||||||
|
serveStatic(options.generate.dir, {
|
||||||
|
extensions: ['html']
|
||||||
|
})
|
||||||
|
)
|
||||||
|
if (options.generate.fallback) {
|
||||||
|
const fallbackFile = await fs.readFile(join(options.generate.dir, options.generate.fallback), 'utf-8')
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
const ext = extname(req.url) || '.html'
|
||||||
|
|
||||||
|
if (ext !== '.html') {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
res.writeHeader(200, {
|
||||||
|
'Content-Type': 'text/html'
|
||||||
|
})
|
||||||
|
res.write(fallbackFile)
|
||||||
|
res.end()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { port, host, socket, https } = options.server
|
||||||
|
const { Listener } = await imports.server()
|
||||||
|
const listener = new Listener({
|
||||||
|
port,
|
||||||
|
host,
|
||||||
|
socket,
|
||||||
|
https,
|
||||||
|
app,
|
||||||
|
dev: true, // try another port if taken
|
||||||
|
baseURL: options.router.base
|
||||||
|
})
|
||||||
|
|
||||||
|
await listener.listen()
|
||||||
|
|
||||||
|
const { Nuxt } = await imports.core()
|
||||||
|
|
||||||
|
showBanner({
|
||||||
|
constructor: Nuxt,
|
||||||
|
options,
|
||||||
|
server: {
|
||||||
|
listeners: [listener]
|
||||||
|
}
|
||||||
|
}, false)
|
||||||
|
}
|
@ -29,29 +29,29 @@ describe('export', () => {
|
|||||||
|
|
||||||
expect(generator).toHaveBeenCalled()
|
expect(generator).toHaveBeenCalled()
|
||||||
expect(generator.mock.calls[0][0].init).toBe(true)
|
expect(generator.mock.calls[0][0].init).toBe(true)
|
||||||
expect(generator.mock.calls[0][0].build).toBe(false)
|
// expect(generator.mock.calls[0][0].build).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('force-exits by default', async () => {
|
test('force-exits by default', async () => {
|
||||||
mockGetNuxt({ generate: { dir: 'dist' } })
|
mockGetNuxt({ generate: { dir: 'dist' } })
|
||||||
mockGetGenerator()
|
mockGetGenerator()
|
||||||
|
|
||||||
const cmd = NuxtCommand.from(exportCommand, ['export', '.'])
|
const cmd = NuxtCommand.from(exportCommand, ['generate', '.'])
|
||||||
await cmd.run()
|
await cmd.run()
|
||||||
|
|
||||||
expect(utils.forceExit).toHaveBeenCalledTimes(1)
|
expect(utils.forceExit).toHaveBeenCalledTimes(1)
|
||||||
expect(utils.forceExit).toHaveBeenCalledWith('export', 5)
|
expect(utils.forceExit).toHaveBeenCalledWith('generate', 5)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('can set force exit explicitly', async () => {
|
test('can set force exit explicitly', async () => {
|
||||||
mockGetNuxt({ generate: { dir: 'dist' } })
|
mockGetNuxt({ generate: { dir: 'dist' } })
|
||||||
mockGetGenerator()
|
mockGetGenerator()
|
||||||
|
|
||||||
const cmd = NuxtCommand.from(exportCommand, ['export', '.', '--force-exit'])
|
const cmd = NuxtCommand.from(exportCommand, ['generate', '.', '--force-exit'])
|
||||||
await cmd.run()
|
await cmd.run()
|
||||||
|
|
||||||
expect(utils.forceExit).toHaveBeenCalledTimes(1)
|
expect(utils.forceExit).toHaveBeenCalledTimes(1)
|
||||||
expect(utils.forceExit).toHaveBeenCalledWith('export', false)
|
expect(utils.forceExit).toHaveBeenCalledWith('generate', false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('can disable force exit explicitly', async () => {
|
test('can disable force exit explicitly', async () => {
|
||||||
@ -97,7 +97,7 @@ describe('export', () => {
|
|||||||
mockGetGenerator(() => ({ errors: [{ type: 'dummy' }] }))
|
mockGetGenerator(() => ({ errors: [{ type: 'dummy' }] }))
|
||||||
|
|
||||||
const cmd = NuxtCommand.from(exportCommand, ['export', '.', '--fail-on-error'])
|
const cmd = NuxtCommand.from(exportCommand, ['export', '.', '--fail-on-error'])
|
||||||
await expect(cmd.run()).rejects.toThrow('Error exporting pages, exiting with non-zero code')
|
await expect(cmd.run()).rejects.toThrow('Error generating pages, exiting with non-zero code')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('do not throw an error when fail-on-error disabled and page errors', async () => {
|
test('do not throw an error when fail-on-error disabled and page errors', async () => {
|
||||||
|
@ -19,16 +19,10 @@ describe('serve', () => {
|
|||||||
expect(typeof serve.run).toBe('function')
|
expect(typeof serve.run).toBe('function')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('error if starts with server target', () => {
|
|
||||||
mockGetNuxtConfig({ target: TARGETS.server })
|
|
||||||
const cmd = NuxtCommand.from(serve)
|
|
||||||
expect(cmd.run()).rejects.toThrow(new Error('You cannot use `nuxt serve` with server target, please use `nuxt start`'))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('error if dist/ does not exists', () => {
|
test('error if dist/ does not exists', () => {
|
||||||
mockGetNuxtConfig({ target: TARGETS.static })
|
mockGetNuxtConfig({ target: TARGETS.static })
|
||||||
const cmd = NuxtCommand.from(serve)
|
const cmd = NuxtCommand.from(serve)
|
||||||
expect(cmd.run()).rejects.toThrow(new Error('Output directory `dist/` does not exists, please run `nuxt export` before `nuxt serve`.'))
|
expect(cmd.run()).rejects.toThrow(new Error('Output directory `dist/` does not exists, please use `nuxt generate` before `nuxt start` for static target.'))
|
||||||
})
|
})
|
||||||
|
|
||||||
test('no error if dist/ dir exists', async () => {
|
test('no error if dist/ dir exists', async () => {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
import { TARGETS } from '@nuxt/utils'
|
|
||||||
import * as utils from '../../src/utils/'
|
import * as utils from '../../src/utils/'
|
||||||
import { consola, mockGetNuxtStart, mockGetNuxtConfig, NuxtCommand } from '../utils'
|
import { consola, mockGetNuxtStart, mockGetNuxtConfig, NuxtCommand } from '../utils'
|
||||||
|
|
||||||
@ -36,13 +35,6 @@ describe('start', () => {
|
|||||||
expect(consola.fatal).not.toHaveBeenCalled()
|
expect(consola.fatal).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('error if starts with static target', () => {
|
|
||||||
mockGetNuxtStart()
|
|
||||||
mockGetNuxtConfig({ target: TARGETS.static })
|
|
||||||
const cmd = NuxtCommand.from(start)
|
|
||||||
expect(cmd.run()).rejects.toThrow(new Error('You cannot use `nuxt start` with static target, please use `nuxt export` and `nuxt serve`'))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('start doesnt force-exit by default', async () => {
|
test('start doesnt force-exit by default', async () => {
|
||||||
mockGetNuxtStart()
|
mockGetNuxtStart()
|
||||||
mockGetNuxtConfig()
|
mockGetNuxtConfig()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
export default () => ({
|
export default () => ({
|
||||||
dir: 'dist',
|
dir: 'dist',
|
||||||
routes: [],
|
routes: [],
|
||||||
@ -8,6 +7,12 @@ export default () => ({
|
|||||||
subFolders: true,
|
subFolders: true,
|
||||||
fallback: '200.html',
|
fallback: '200.html',
|
||||||
crawler: true,
|
crawler: true,
|
||||||
|
cache: {
|
||||||
|
ignore: [],
|
||||||
|
globbyOptions: {
|
||||||
|
gitignore: true
|
||||||
|
}
|
||||||
|
},
|
||||||
staticAssets: {
|
staticAssets: {
|
||||||
base: undefined, // Default: "/_nuxt/static:
|
base: undefined, // Default: "/_nuxt/static:
|
||||||
versionBase: undefined, // Default: "_nuxt/static/{version}""
|
versionBase: undefined, // Default: "_nuxt/static/{version}""
|
||||||
|
@ -115,9 +115,9 @@ export function getNuxtConfig (_options) {
|
|||||||
options.router.base += '/'
|
options.router.base += '/'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alias export to generate
|
// Legacy support for export
|
||||||
// TODO: switch to export by default for nuxt3
|
|
||||||
if (options.export) {
|
if (options.export) {
|
||||||
|
consola.warn('export option is deprecated and will be removed in a future version! Please switch to generate')
|
||||||
options.generate = defu(options.export, options.generate)
|
options.generate = defu(options.export, options.generate)
|
||||||
}
|
}
|
||||||
exports.export = options.generate
|
exports.export = options.generate
|
||||||
|
@ -196,6 +196,12 @@ Object {
|
|||||||
"server": true,
|
"server": true,
|
||||||
},
|
},
|
||||||
"generate": Object {
|
"generate": Object {
|
||||||
|
"cache": Object {
|
||||||
|
"globbyOptions": Object {
|
||||||
|
"gitignore": true,
|
||||||
|
},
|
||||||
|
"ignore": Array [],
|
||||||
|
},
|
||||||
"concurrency": 500,
|
"concurrency": 500,
|
||||||
"crawler": true,
|
"crawler": true,
|
||||||
"dir": "/var/nuxt/test/dist",
|
"dir": "/var/nuxt/test/dist",
|
||||||
|
@ -175,6 +175,12 @@ Object {
|
|||||||
"server": true,
|
"server": true,
|
||||||
},
|
},
|
||||||
"generate": Object {
|
"generate": Object {
|
||||||
|
"cache": Object {
|
||||||
|
"globbyOptions": Object {
|
||||||
|
"gitignore": true,
|
||||||
|
},
|
||||||
|
"ignore": Array [],
|
||||||
|
},
|
||||||
"concurrency": 500,
|
"concurrency": 500,
|
||||||
"crawler": true,
|
"crawler": true,
|
||||||
"dir": "dist",
|
"dir": "dist",
|
||||||
@ -548,6 +554,12 @@ Object {
|
|||||||
"server": true,
|
"server": true,
|
||||||
},
|
},
|
||||||
"generate": Object {
|
"generate": Object {
|
||||||
|
"cache": Object {
|
||||||
|
"globbyOptions": Object {
|
||||||
|
"gitignore": true,
|
||||||
|
},
|
||||||
|
"ignore": Array [],
|
||||||
|
},
|
||||||
"concurrency": 500,
|
"concurrency": 500,
|
||||||
"crawler": true,
|
"crawler": true,
|
||||||
"dir": "dist",
|
"dir": "dist",
|
||||||
|
@ -35,7 +35,17 @@ export default class Nuxt extends Hookable {
|
|||||||
to: '_render:context',
|
to: '_render:context',
|
||||||
message: '`render:routeContext(nuxt)` is deprecated, Please use `vue-renderer:ssr:context(context)`'
|
message: '`render:routeContext(nuxt)` is deprecated, Please use `vue-renderer:ssr:context(context)`'
|
||||||
},
|
},
|
||||||
showReady: 'webpack:done'
|
showReady: 'webpack:done',
|
||||||
|
// Introduced in 2.13
|
||||||
|
'export:done': 'generate:done',
|
||||||
|
'export:before': 'generate:before',
|
||||||
|
'export:extendRoutes': 'generate:extendRoutes',
|
||||||
|
'export:distRemoved': 'generate:distRemoved',
|
||||||
|
'export:distCopied': 'generate:distCopied',
|
||||||
|
'export:route': 'generate:route',
|
||||||
|
'export:routeFailed': 'generate:routeFailed',
|
||||||
|
'export:page': 'generate:page',
|
||||||
|
'export:routeCreated': 'generate:routeCreated'
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add Legacy aliases
|
// Add Legacy aliases
|
||||||
|
@ -6,16 +6,16 @@ import defu from 'defu'
|
|||||||
import htmlMinifier from 'html-minifier'
|
import htmlMinifier from 'html-minifier'
|
||||||
import { parse } from 'node-html-parser'
|
import { parse } from 'node-html-parser'
|
||||||
|
|
||||||
import { isFullStatic, flatRoutes, isString, isUrl, promisifyRoute, waitFor, TARGETS } from '@nuxt/utils'
|
import { isFullStatic, flatRoutes, isString, isUrl, promisifyRoute, waitFor } from '@nuxt/utils'
|
||||||
|
|
||||||
export default class Generator {
|
export default class Generator {
|
||||||
constructor (nuxt, builder) {
|
constructor (nuxt, builder) {
|
||||||
this.nuxt = nuxt
|
this.nuxt = nuxt
|
||||||
this.options = nuxt.options
|
this.options = nuxt.options
|
||||||
this.builder = builder
|
this.builder = builder
|
||||||
this.isFullStatic = false
|
|
||||||
|
|
||||||
// Set variables
|
// Set variables
|
||||||
|
this.isFullStatic = isFullStatic(this.options)
|
||||||
this.staticRoutes = path.resolve(this.options.srcDir, this.options.dir.static)
|
this.staticRoutes = path.resolve(this.options.srcDir, this.options.dir.static)
|
||||||
this.srcBuiltPath = path.resolve(this.options.buildDir, 'dist', 'client')
|
this.srcBuiltPath = path.resolve(this.options.buildDir, 'dist', 'client')
|
||||||
this.distPath = this.options.generate.dir
|
this.distPath = this.options.generate.dir
|
||||||
@ -23,6 +23,12 @@ export default class Generator {
|
|||||||
this.distPath,
|
this.distPath,
|
||||||
isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath
|
isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath
|
||||||
)
|
)
|
||||||
|
// Payloads for full static
|
||||||
|
if (this.isFullStatic) {
|
||||||
|
const { staticAssets } = this.options.generate
|
||||||
|
this.staticAssetsDir = path.resolve(this.distNuxtPath, staticAssets.dir, staticAssets.version)
|
||||||
|
this.staticAssetsBase = this.options.generate.staticAssets.versionBase
|
||||||
|
}
|
||||||
|
|
||||||
// Shared payload
|
// Shared payload
|
||||||
this._payload = null
|
this._payload = null
|
||||||
@ -35,18 +41,10 @@ export default class Generator {
|
|||||||
consola.debug('Initializing generator...')
|
consola.debug('Initializing generator...')
|
||||||
await this.initiate({ build, init })
|
await this.initiate({ build, init })
|
||||||
|
|
||||||
// Payloads for full static
|
|
||||||
if (this.isFullStatic) {
|
|
||||||
consola.info('Full static mode activated')
|
|
||||||
const { staticAssets } = this.options.generate
|
|
||||||
this.staticAssetsDir = path.resolve(this.distNuxtPath, staticAssets.dir, staticAssets.version)
|
|
||||||
this.staticAssetsBase = this.options.generate.staticAssets.versionBase
|
|
||||||
}
|
|
||||||
|
|
||||||
consola.debug('Preparing routes for generate...')
|
consola.debug('Preparing routes for generate...')
|
||||||
const routes = await this.initRoutes()
|
const routes = await this.initRoutes()
|
||||||
|
|
||||||
consola.info('Generating pages')
|
consola.info('Generating pages' + (this.isFullStatic ? ' with full static mode' : ''))
|
||||||
const errors = await this.generateRoutes(routes)
|
const errors = await this.generateRoutes(routes)
|
||||||
|
|
||||||
await this.afterGenerate()
|
await this.afterGenerate()
|
||||||
@ -72,23 +70,13 @@ export default class Generator {
|
|||||||
|
|
||||||
// Start build process
|
// Start build process
|
||||||
await this.builder.build()
|
await this.builder.build()
|
||||||
this.isFullStatic = isFullStatic(this.options)
|
|
||||||
} else {
|
} else {
|
||||||
const hasBuilt = await fsExtra.exists(path.resolve(this.options.buildDir, 'dist', 'server', 'client.manifest.json'))
|
const hasBuilt = await fsExtra.exists(path.resolve(this.options.buildDir, 'dist', 'server', 'client.manifest.json'))
|
||||||
if (!hasBuilt) {
|
if (!hasBuilt) {
|
||||||
const fullStaticArgs = isFullStatic(this.options) ? ' --target static' : ''
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`No build files found in ${this.srcBuiltPath}.\nPlease run \`nuxt build${fullStaticArgs}\` before calling \`nuxt export\``
|
`No build files found in ${this.srcBuiltPath}.\nPlease run \`nuxt build\``
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const config = this.getBuildConfig()
|
|
||||||
if (!config || (config.target !== TARGETS.static && !this.options._legacyGenerate)) {
|
|
||||||
throw new Error(
|
|
||||||
`In order to use \`nuxt export\`, you need to run \`nuxt build --target static\``
|
|
||||||
)
|
|
||||||
}
|
|
||||||
this.isFullStatic = config.isFullStatic
|
|
||||||
this.options.render.ssr = config.ssr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize dist directory
|
// Initialize dist directory
|
||||||
@ -366,9 +354,15 @@ export default class Generator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call hook to let user update the path & html
|
// Call hook to let user update the path & html
|
||||||
const page = { route, path: fileName, html, exclude: false }
|
const page = {
|
||||||
|
route,
|
||||||
|
path: fileName,
|
||||||
|
html,
|
||||||
|
exclude: false,
|
||||||
|
errors: pageErrors
|
||||||
|
}
|
||||||
|
page.page = page // Backward compatibility for export:page hook
|
||||||
await this.nuxt.callHook('generate:page', page)
|
await this.nuxt.callHook('generate:page', page)
|
||||||
await this.nuxt.callHook('export:page', { page, errors: pageErrors })
|
|
||||||
|
|
||||||
if (page.exclude) {
|
if (page.exclude) {
|
||||||
return false
|
return false
|
||||||
|
6
packages/types/config/generate.d.ts
vendored
6
packages/types/config/generate.d.ts
vendored
@ -1,3 +1,5 @@
|
|||||||
|
import { GlobbyOptions } from 'globby'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NuxtOptionsGenerate
|
* NuxtOptionsGenerate
|
||||||
* Documentation: https://nuxtjs.org/api/configuration-generate
|
* Documentation: https://nuxtjs.org/api/configuration-generate
|
||||||
@ -18,4 +20,8 @@ export interface NuxtOptionsGenerate {
|
|||||||
interval?: number
|
interval?: number
|
||||||
routes?: NuxtOptionsGenerateRoute[] | NuxtOptionsGenerateRoutesFunction | NuxtOptionsGenerateRoutesFunctionWithCallback
|
routes?: NuxtOptionsGenerateRoute[] | NuxtOptionsGenerateRoutesFunction | NuxtOptionsGenerateRoutesFunctionWithCallback
|
||||||
subFolders?: boolean
|
subFolders?: boolean
|
||||||
|
cache?: false | {
|
||||||
|
ignore?: string[] | function,
|
||||||
|
globbyOptions?: GlobbyOptions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ export const template = {
|
|||||||
dependencies,
|
dependencies,
|
||||||
dir: path.join(__dirname, '..', 'template'),
|
dir: path.join(__dirname, '..', 'template'),
|
||||||
files: [
|
files: [
|
||||||
'nuxt/config.json',
|
|
||||||
'App.js',
|
'App.js',
|
||||||
'client.js',
|
'client.js',
|
||||||
'index.js',
|
'index.js',
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
<%= JSON.stringify({
|
|
||||||
isFullStatic: isFullStatic,
|
|
||||||
ssr: nuxtOptions.render.ssr,
|
|
||||||
target: nuxtOptions.target
|
|
||||||
}, null, 2) %>
|
|
23
test/utils/setup-env.js
Normal file
23
test/utils/setup-env.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import consola from 'consola'
|
||||||
|
import env from 'std-env'
|
||||||
|
import exit from 'exit'
|
||||||
|
|
||||||
|
const isWin = env.windows
|
||||||
|
|
||||||
|
describe.win = isWin ? describe : describe.skip
|
||||||
|
test.win = isWin ? test : test.skip
|
||||||
|
|
||||||
|
describe.posix = !isWin ? describe : describe.skip
|
||||||
|
test.posix = !isWin ? test : test.skip
|
||||||
|
|
||||||
|
jest.setTimeout(60000)
|
||||||
|
|
||||||
|
consola.mockTypes(() => jest.fn())
|
||||||
|
|
||||||
|
function errorTrap (error) {
|
||||||
|
process.stderr.write('\n' + error.stack + '\n')
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('unhandledRejection', errorTrap)
|
||||||
|
process.on('uncaughtException', errorTrap)
|
@ -1,26 +1,6 @@
|
|||||||
import consola from 'consola'
|
process.env.FORCE_COLOR = 0
|
||||||
import chalk from 'chalk'
|
|
||||||
import env from 'std-env'
|
|
||||||
import exit from 'exit'
|
|
||||||
|
|
||||||
const isWin = env.windows
|
|
||||||
|
|
||||||
describe.win = isWin ? describe : describe.skip
|
|
||||||
test.win = isWin ? test : test.skip
|
|
||||||
|
|
||||||
describe.posix = !isWin ? describe : describe.skip
|
|
||||||
test.posix = !isWin ? test : test.skip
|
|
||||||
|
|
||||||
|
const chalk = require('chalk')
|
||||||
chalk.level = 0
|
chalk.level = 0
|
||||||
|
chalk.supportsColor = false
|
||||||
jest.setTimeout(60000)
|
process.env.FORCE_COLOR = 0
|
||||||
|
|
||||||
consola.mockTypes(() => jest.fn())
|
|
||||||
|
|
||||||
function errorTrap (error) {
|
|
||||||
process.stderr.write('\n' + error.stack + '\n')
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
process.on('unhandledRejection', errorTrap)
|
|
||||||
process.on('uncaughtException', errorTrap)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user