mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-26 23:52:06 +00:00
feat: options.target and full-static export (#6159)
* feat: add options.target * fix(lint): lint * fix(test): update snapshots * fix(builder): default value for target * fix(test): fix test * fix(test): test fixing * fix: use this.options.target * fix: final test * Update packages/vue-renderer/src/renderer.js Co-Authored-By: Alexander Lichter <manniL@gmx.net> * feat: Add target option and update banner * fix(lint): fix * feat: Add warning when using serverMiddleware in static target * chore(utils): add TARGETS and MODES as constants * hotfix: lint * chore(module): add filename as alias of fileName * feat: introducing nuxt export and router/routes.json * hotfix: Fix the linting lord * chore(core): add comment for filename vs fileName * fix: use targets constant * chore: remove warning * fix: unit testing * wip: refactor and use TARGETS * fix: lint * feat: add target as alias for first arg value * fix: generate only for SPA * chore: explain to use nuxt static X * fix: render SPA fallback on redirect for static target * fix: lint issue * fix: only target is useful for now * wip * wip: nuxt static export is looking good * Update packages/generator/src/generator.js Co-Authored-By: Devon Rueckner <indirectlylit@users.noreply.github.com> * Update packages/cli/src/options/common.js Co-Authored-By: Alexander Lichter <manniL@gmx.net> * feat: add options.target * fix(lint): lint * fix(test): update snapshots * fix(builder): default value for target * fix(test): fix test * fix(test): test fixing * fix: use this.options.target * fix: final test * Update packages/vue-renderer/src/renderer.js Co-Authored-By: Alexander Lichter <manniL@gmx.net> * feat: Add target option and update banner * fix(lint): fix * feat: Add warning when using serverMiddleware in static target * chore(utils): add TARGETS and MODES as constants * hotfix: lint * chore(module): add filename as alias of fileName * feat: introducing nuxt export and router/routes.json * hotfix: Fix the linting lord * chore(core): add comment for filename vs fileName * fix: use targets constant * chore: remove warning * fix: unit testing * wip: refactor and use TARGETS * fix: lint * feat: add target as alias for first arg value * chore: explain to use nuxt static X * fix: render SPA fallback on redirect for static target * fix: lint issue * fix: only target is useful for now * wip * wip: nuxt static export is looking good * Update packages/generator/src/generator.js Co-Authored-By: Devon Rueckner <indirectlylit@users.noreply.github.com> * Update packages/cli/src/options/common.js Co-Authored-By: Alexander Lichter <manniL@gmx.net> * fix: duplicate imports * chore: don't server render if an error happens on static target * test: update unit and add export * lint: fix * lint: fix * fix: e2e test * fix: fallback only for static target * fix: dev test * feat: add generate.crawler * fix: full static is when generate.static is given * chore: improvements * fix: Add isFullStatic in nuxt/config.json * feat: handle fetch for full static * feat: router.prefetchPayloads for full static * chore: use fetch in async-data example * fix: add target only if given * fix: use created to have access to props in fetchOnServer * chore: add console.error in dev for easy debugging * feat: payload smart pre-fetching * fix: remove alias for target * fix: increment payloadFetchIndex is static set to false * chore: lint * chore: add serve command * chore: rename universal to server-side * fix: handle payloadPath on SPA fallback * fix: lint * chore lint again * feat: handle spa fallback * feat: support string for exclude * fix: fallback only if no extension or html * chore: use JSON.stringify() for static target * chore: lint again, dammit * chore: fix tests and remove too early return * fix: early return only for server target * fix: update tests * fix: unit tests * chore: add ssr option * chore: add logic for ssr option * fix: #6682 * chore(dx): add next command to run * fix: lint * fix: tests * chore: keep old behaviour for nuxt build in spa * fix: test again, oh boy * fix: alright this is good now * chore: add comment for spa fallback * chore: move routes.json to dot nuxt dir * chore: simplify check for promise * chore: unique lock id * chore: refactor isFullStatic * fix: dont set default in build context * chore: add test for serve * chore: update tests * hotfix: lint tests * chore(dx): improve message for bundling * feat: js payload extraction with jsonp * fix: keep serialized session script for legacy generate * fix: call to setPagePayload from fetchPayload * use devalue for payload chunks * feat: add initial load state chunk * feat: preload payload and state scripts * fix(vue-app): don't re-render the app if trailing slash on SSG * hotfix: remove console.log * chore(dx): add deploy infos for nuxt export Co-authored-by: Pooya Parsa <pyapar@gmail.com> * chore: handle fetching payload.js for nuxt state * chore(dx): error when using nuxt generate and static * chore: remove static option for clarity * chore: remove serverless target * hotfix: lint * hotfix: unit tests * chore: update legacy js resource * chore: remove query params from url in static target * fix: use globalName and urlJoin * chore: typo * feat: previewMode 👀 * chore: rename to enablePreview * fix: wait next tick to avoid error on spa * chore: try 1 sec * hotfix: test only for linux, wtf azure * refactor: static assets - generalize logic for modules need emit export static assets - allow customization for version, dir and base - serialization logic is only in ssr now * feat: smart state chunk creates * fix(client): ignore payload load error * perf: avoide payload loading for spa initial * perf: avoid loading failed chunks again * chore(cli): add simple compression for nuxt serve * test: update snapshots * fix version snapshot * fix(generator): set staticAssetsBase on context only for full static * fix tests * fix: honor shouldHashCspScriptSrc * chore(dx): add log for client-side fallback creation Co-authored-by: Xin Du (Clark) <clark.duxin@gmail.com> Co-authored-by: Alexander Lichter <manniL@gmx.net> Co-authored-by: Pooya Parsa <pooya@pi0.ir> Co-authored-by: Devon Rueckner <indirectlylit@users.noreply.github.com> Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
parent
a0db3644f6
commit
917adc0618
@ -12,13 +12,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async asyncData ({ params }) {
|
async asyncData ({ params }) {
|
||||||
// We can use async/await ES6 feature
|
// We can use async/await ES6 feature
|
||||||
const { data } = await axios.get(`https://jsonplaceholder.typicode.com/posts/${params.id}`)
|
const post = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`).then(res => res.json())
|
||||||
return { post: data }
|
|
||||||
|
return { post }
|
||||||
},
|
},
|
||||||
head () {
|
head () {
|
||||||
return {
|
return {
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
<NuxtLink :to="{ name: 'posts-id', params: { id: post.id } }">
|
<NuxtLink :to="{ name: 'posts-id', params: { id: post.id } }">
|
||||||
{{ post.title }}
|
{{ post.title }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
<NuxtLink :to="{ name: 'posts-id', params: { id: post.id } }">
|
||||||
|
{{ post.title }}
|
||||||
|
</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
@ -18,14 +21,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
asyncData ({ req, params }) {
|
asyncData ({ req, params }) {
|
||||||
// We can return a Promise instead of calling the callback
|
// We can return a Promise instead of calling the callback
|
||||||
return axios.get('https://jsonplaceholder.typicode.com/posts')
|
return fetch('https://jsonplaceholder.typicode.com/posts')
|
||||||
.then((res) => {
|
.then(res => res.json())
|
||||||
return { posts: res.data.slice(0, 5) }
|
.then((data) => {
|
||||||
|
return { posts: data.slice(0, 5) }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
head: {
|
head: {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import chalk from 'chalk'
|
||||||
import chokidar from 'chokidar'
|
import chokidar from 'chokidar'
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
import fsExtra from 'fs-extra'
|
import fsExtra from 'fs-extra'
|
||||||
@ -7,7 +8,6 @@ import hash from 'hash-sum'
|
|||||||
import pify from 'pify'
|
import pify from 'pify'
|
||||||
import upath from 'upath'
|
import upath from 'upath'
|
||||||
import semver from 'semver'
|
import semver from 'semver'
|
||||||
import chalk from 'chalk'
|
|
||||||
|
|
||||||
import debounce from 'lodash/debounce'
|
import debounce from 'lodash/debounce'
|
||||||
import omit from 'lodash/omit'
|
import omit from 'lodash/omit'
|
||||||
@ -23,7 +23,9 @@ import {
|
|||||||
determineGlobals,
|
determineGlobals,
|
||||||
stripWhitespace,
|
stripWhitespace,
|
||||||
isIndexFileAndFolder,
|
isIndexFileAndFolder,
|
||||||
scanRequireTree
|
scanRequireTree,
|
||||||
|
TARGETS,
|
||||||
|
isFullStatic
|
||||||
} from '@nuxt/utils'
|
} from '@nuxt/utils'
|
||||||
|
|
||||||
import Ignore from './ignore'
|
import Ignore from './ignore'
|
||||||
@ -102,6 +104,7 @@ export default class Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
forGenerate () {
|
forGenerate () {
|
||||||
|
this.options.target = TARGETS.static
|
||||||
this.bundleBuilder.forGenerate()
|
this.bundleBuilder.forGenerate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +125,13 @@ export default class Builder {
|
|||||||
consola.info('Initial build may take a while')
|
consola.info('Initial build may take a while')
|
||||||
} else {
|
} else {
|
||||||
consola.info('Production build')
|
consola.info('Production build')
|
||||||
|
if (this.options.render.ssr) {
|
||||||
|
consola.info(`Bundling for ${chalk.bold.yellow('server')} and ${chalk.bold.green('client')} side`)
|
||||||
|
} else {
|
||||||
|
consola.info(`Bundling only for ${chalk.bold.green('client')} side`)
|
||||||
|
}
|
||||||
|
const target = isFullStatic(this.options) ? 'full static' : this.options.target
|
||||||
|
consola.info(`Target: ${chalk.bold.cyan(target)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for nuxt ready
|
// Wait for nuxt ready
|
||||||
|
@ -3,7 +3,7 @@ export default class BuildContext {
|
|||||||
this._builder = builder
|
this._builder = builder
|
||||||
this.nuxt = builder.nuxt
|
this.nuxt = builder.nuxt
|
||||||
this.options = builder.nuxt.options
|
this.options = builder.nuxt.options
|
||||||
this.isStatic = false
|
this.target = builder.nuxt.options.target
|
||||||
}
|
}
|
||||||
|
|
||||||
get buildOptions () {
|
get buildOptions () {
|
||||||
|
@ -4,7 +4,7 @@ import uniqBy from 'lodash/uniqBy'
|
|||||||
import serialize from 'serialize-javascript'
|
import serialize from 'serialize-javascript'
|
||||||
|
|
||||||
import devalue from '@nuxt/devalue'
|
import devalue from '@nuxt/devalue'
|
||||||
import { r, wp, wChunk, serializeFunction } from '@nuxt/utils'
|
import { r, wp, wChunk, serializeFunction, isFullStatic } from '@nuxt/utils'
|
||||||
|
|
||||||
export default class TemplateContext {
|
export default class TemplateContext {
|
||||||
constructor (builder, options) {
|
constructor (builder, options) {
|
||||||
@ -20,6 +20,7 @@ export default class TemplateContext {
|
|||||||
uniqBy,
|
uniqBy,
|
||||||
isDev: options.dev,
|
isDev: options.dev,
|
||||||
isTest: options.test,
|
isTest: options.test,
|
||||||
|
isFullStatic: isFullStatic(options),
|
||||||
debug: options.debug,
|
debug: options.debug,
|
||||||
buildIndicator: options.dev && options.build.indicator,
|
buildIndicator: options.dev && options.build.indicator,
|
||||||
vue: { config: options.vue.config },
|
vue: { config: options.vue.config },
|
||||||
|
@ -5,6 +5,9 @@ export const createNuxt = () => ({
|
|||||||
build: {
|
build: {
|
||||||
watch: []
|
watch: []
|
||||||
},
|
},
|
||||||
|
render: {
|
||||||
|
ssr: true
|
||||||
|
},
|
||||||
router: {},
|
router: {},
|
||||||
dir: {
|
dir: {
|
||||||
app: 'app'
|
app: 'app'
|
||||||
|
@ -35,6 +35,7 @@ describe('builder: builder build', () => {
|
|||||||
nuxt.options.dir = { pages: '/var/nuxt/src/pages' }
|
nuxt.options.dir = { pages: '/var/nuxt/src/pages' }
|
||||||
nuxt.options.build.template = { dir: '/var/nuxt/src/template' }
|
nuxt.options.build.template = { dir: '/var/nuxt/src/template' }
|
||||||
nuxt.options.build.createRoutes = jest.fn()
|
nuxt.options.build.createRoutes = jest.fn()
|
||||||
|
nuxt.options.render = { ssr: true }
|
||||||
|
|
||||||
const bundleBuilder = { build: jest.fn() }
|
const bundleBuilder = { build: jest.fn() }
|
||||||
const builder = new Builder(nuxt, bundleBuilder)
|
const builder = new Builder(nuxt, bundleBuilder)
|
||||||
@ -47,7 +48,7 @@ describe('builder: builder build', () => {
|
|||||||
|
|
||||||
const buildReturn = await builder.build()
|
const buildReturn = await builder.build()
|
||||||
|
|
||||||
expect(consola.info).toBeCalledTimes(1)
|
expect(consola.info).toBeCalledTimes(3)
|
||||||
expect(consola.info).toBeCalledWith('Production build')
|
expect(consola.info).toBeCalledWith('Production build')
|
||||||
expect(nuxt.ready).toBeCalledTimes(1)
|
expect(nuxt.ready).toBeCalledTimes(1)
|
||||||
expect(nuxt.callHook).toBeCalledTimes(3)
|
expect(nuxt.callHook).toBeCalledTimes(3)
|
||||||
@ -117,6 +118,7 @@ describe('builder: builder build', () => {
|
|||||||
nuxt.options.buildDir = '/var/nuxt/build'
|
nuxt.options.buildDir = '/var/nuxt/build'
|
||||||
nuxt.options.dir = { pages: '/var/nuxt/src/pages' }
|
nuxt.options.dir = { pages: '/var/nuxt/src/pages' }
|
||||||
nuxt.options.build.createRoutes = jest.fn()
|
nuxt.options.build.createRoutes = jest.fn()
|
||||||
|
nuxt.options.render = { ssr: true }
|
||||||
|
|
||||||
const bundleBuilder = { build: jest.fn() }
|
const bundleBuilder = { build: jest.fn() }
|
||||||
const builder = new Builder(nuxt, bundleBuilder)
|
const builder = new Builder(nuxt, bundleBuilder)
|
||||||
|
@ -30,6 +30,7 @@ TemplateContext {
|
|||||||
],
|
],
|
||||||
"head": "test_head",
|
"head": "test_head",
|
||||||
"isDev": "test_dev",
|
"isDev": "test_dev",
|
||||||
|
"isFullStatic": false,
|
||||||
"isTest": "test_test",
|
"isTest": "test_test",
|
||||||
"layoutTransition": Object {
|
"layoutTransition": Object {
|
||||||
"name": "test_layout_trans",
|
"name": "test_layout_trans",
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
|
import { TARGETS } from '@nuxt/utils'
|
||||||
import BuildContext from '../../src/context/build'
|
import BuildContext from '../../src/context/build'
|
||||||
|
|
||||||
describe('builder: buildContext', () => {
|
describe('builder: buildContext', () => {
|
||||||
test('should construct context', () => {
|
test('should construct context', () => {
|
||||||
const builder = {
|
const builder = {
|
||||||
nuxt: { options: {} }
|
nuxt: {
|
||||||
|
options: {
|
||||||
|
target: TARGETS.server
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const context = new BuildContext(builder)
|
const context = new BuildContext(builder)
|
||||||
expect(context._builder).toEqual(builder)
|
expect(context._builder).toEqual(builder)
|
||||||
expect(context.nuxt).toEqual(builder.nuxt)
|
expect(context.nuxt).toEqual(builder.nuxt)
|
||||||
expect(context.options).toEqual(builder.nuxt.options)
|
expect(context.options).toEqual(builder.nuxt.options)
|
||||||
expect(context.isStatic).toEqual(false)
|
expect(context.target).toEqual('server')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should return builder plugins context', () => {
|
test('should return builder plugins context', () => {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import consola from 'consola'
|
||||||
|
import { MODES, TARGETS } from '@nuxt/utils'
|
||||||
import { common, locking } from '../options'
|
import { common, locking } from '../options'
|
||||||
import { createLock } from '../utils'
|
import { createLock } from '../utils'
|
||||||
|
|
||||||
@ -62,7 +64,7 @@ export default {
|
|||||||
},
|
},
|
||||||
async run (cmd) {
|
async run (cmd) {
|
||||||
const config = await cmd.getNuxtConfig({ dev: false, server: false, _build: true })
|
const config = await cmd.getNuxtConfig({ dev: false, server: false, _build: true })
|
||||||
config.server = config.mode === 'spa' && cmd.argv.generate !== false
|
config.server = (config.mode === MODES.spa || config.ssr === false) && cmd.argv.generate !== false
|
||||||
const nuxt = await cmd.getNuxt(config)
|
const nuxt = await cmd.getNuxt(config)
|
||||||
|
|
||||||
if (cmd.argv.lock) {
|
if (cmd.argv.lock) {
|
||||||
@ -73,7 +75,8 @@ export default {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nuxt.options.mode === 'spa' && cmd.argv.generate !== false) {
|
// TODO: remove if in Nuxt 3
|
||||||
|
if (nuxt.options.mode === MODES.spa && nuxt.options.target === TARGETS.server && cmd.argv.generate !== false) {
|
||||||
// Build + Generate for static deployment
|
// Build + Generate for static deployment
|
||||||
const generator = await cmd.getGenerator(nuxt)
|
const generator = await cmd.getGenerator(nuxt)
|
||||||
await generator.generate({ build: true })
|
await generator.generate({ build: true })
|
||||||
@ -81,6 +84,9 @@ export default {
|
|||||||
// Build only
|
// Build only
|
||||||
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'
|
||||||
|
consola.info('Ready to run `' + (nextCommand) + '`')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
50
packages/cli/src/commands/export.js
Normal file
50
packages/cli/src/commands/export.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import consola from 'consola'
|
||||||
|
import { TARGETS } from '@nuxt/utils'
|
||||||
|
import { common, locking } from '../options'
|
||||||
|
import { createLock } from '../utils'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'export',
|
||||||
|
description: 'Export a static generated web application',
|
||||||
|
usage: 'export <dir>',
|
||||||
|
options: {
|
||||||
|
...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,
|
||||||
|
_build: cmd.argv.build
|
||||||
|
})
|
||||||
|
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()
|
||||||
|
|
||||||
|
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,3 +1,4 @@
|
|||||||
|
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'
|
||||||
|
|
||||||
@ -53,14 +54,25 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async run (cmd) {
|
async run (cmd) {
|
||||||
const config = await cmd.getNuxtConfig({ dev: false, _generate: true, _build: cmd.argv.build })
|
const config = await cmd.getNuxtConfig({
|
||||||
|
dev: false,
|
||||||
|
_build: cmd.argv.build
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
if (!config.build) {
|
config.build = config.build || {}
|
||||||
config.build = {}
|
|
||||||
}
|
|
||||||
config.build.analyze = false
|
config.build.analyze = false
|
||||||
|
|
||||||
|
// Set flag to keep the prerendering behaviour
|
||||||
|
config._legacyGenerate = true
|
||||||
|
|
||||||
const nuxt = await cmd.getNuxt(config)
|
const nuxt = await cmd.getNuxt(config)
|
||||||
|
|
||||||
if (cmd.argv.lock) {
|
if (cmd.argv.lock) {
|
||||||
@ -82,12 +94,14 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const generator = await cmd.getGenerator(nuxt)
|
const generator = await cmd.getGenerator(nuxt)
|
||||||
|
await nuxt.server.listen()
|
||||||
|
|
||||||
const { errors } = await generator.generate({
|
const { errors } = await generator.generate({
|
||||||
init: true,
|
init: true,
|
||||||
build: cmd.argv.build
|
build: cmd.argv.build
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await nuxt.close()
|
||||||
if (cmd.argv['fail-on-error'] && errors.length > 0) {
|
if (cmd.argv['fail-on-error'] && errors.length > 0) {
|
||||||
throw new Error('Error generating pages, exiting with non-zero code')
|
throw new Error('Error generating pages, exiting with non-zero code')
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
const commands = {
|
const commands = {
|
||||||
start: () => import('./start'),
|
start: () => import('./start'),
|
||||||
|
serve: () => import('./serve'),
|
||||||
dev: () => import('./dev'),
|
dev: () => import('./dev'),
|
||||||
build: () => import('./build'),
|
build: () => import('./build'),
|
||||||
generate: () => import('./generate'),
|
generate: () => import('./generate'),
|
||||||
|
export: () => import('./export'),
|
||||||
webpack: () => import('./webpack'),
|
webpack: () => import('./webpack'),
|
||||||
help: () => import('./help')
|
help: () => import('./help')
|
||||||
}
|
}
|
||||||
|
83
packages/cli/src/commands/serve.js
Normal file
83
packages/cli/src/commands/serve.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
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 { TARGETS } from '@nuxt/utils'
|
||||||
|
import { Listener } from '@nuxt/server'
|
||||||
|
import { common, server } from '../options'
|
||||||
|
import { showBanner } from '../utils/banner'
|
||||||
|
import * as imports from '../imports'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'serve',
|
||||||
|
description: 'Serve the exported static application (should be compiled with `nuxt build` and `nuxt export` first)',
|
||||||
|
usage: 'serve <dir>',
|
||||||
|
options: {
|
||||||
|
'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(
|
||||||
|
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 = 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,3 +1,4 @@
|
|||||||
|
import { TARGETS } from '@nuxt/utils'
|
||||||
import { common, server } from '../options'
|
import { common, server } from '../options'
|
||||||
import { showBanner } from '../utils/banner'
|
import { showBanner } from '../utils/banner'
|
||||||
|
|
||||||
@ -11,6 +12,9 @@ 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) {
|
||||||
|
throw new Error('You cannot use `nuxt start` with ' + TARGETS.static + ' target, please use `nuxt export` and `nuxt serve`')
|
||||||
|
}
|
||||||
const nuxt = await cmd.getNuxt(config)
|
const nuxt = await cmd.getNuxt(config)
|
||||||
|
|
||||||
// Listen and show ready banner
|
// Listen and show ready banner
|
||||||
|
@ -28,10 +28,20 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
target: {
|
||||||
|
alias: 't',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Build/start app for a different target, e.g. server, serverless and static',
|
||||||
|
prepare (cmd, options, argv) {
|
||||||
|
if (argv.target) {
|
||||||
|
options.target = argv.target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
'force-exit': {
|
'force-exit': {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default (cmd) {
|
default (cmd) {
|
||||||
return ['build', 'generate'].includes(cmd.name)
|
return ['build', 'generate', 'export'].includes(cmd.name)
|
||||||
},
|
},
|
||||||
description: 'Whether Nuxt.js should force exit after the command has finished'
|
description: 'Whether Nuxt.js should force exit after the command has finished'
|
||||||
},
|
},
|
||||||
|
@ -20,11 +20,14 @@ export function showBanner (nuxt, showMemoryUsage = true) {
|
|||||||
const messageLines = []
|
const messageLines = []
|
||||||
|
|
||||||
// Name and version
|
// Name and version
|
||||||
const { bannerColor } = nuxt.options.cli
|
const { bannerColor, badgeMessages } = nuxt.options.cli
|
||||||
titleLines.push(`${chalk[bannerColor].bold('Nuxt.js')} ${nuxt.constructor.version}`)
|
titleLines.push(`${chalk[bannerColor].bold('Nuxt.js')} ${nuxt.constructor.version}`)
|
||||||
|
|
||||||
// Running mode
|
// Running mode
|
||||||
titleLines.push(`Running in ${nuxt.options.dev ? chalk.bold.blue('development') : chalk.bold.green('production')} mode (${chalk.bold(nuxt.options.mode)})`)
|
const rendering = nuxt.options.render.ssr ? chalk.bold.yellow('server-side') : chalk.bold.yellow('client-side')
|
||||||
|
const envMode = nuxt.options.dev ? chalk.bold.blue('development') : chalk.bold.green('production')
|
||||||
|
const sentence = `Running in ${envMode}, with ${rendering} rendering and ${chalk.bold.cyan(nuxt.options.target)} target.`
|
||||||
|
titleLines.push(sentence)
|
||||||
|
|
||||||
if (showMemoryUsage) {
|
if (showMemoryUsage) {
|
||||||
titleLines.push(getFormattedMemoryUsage())
|
titleLines.push(getFormattedMemoryUsage())
|
||||||
@ -36,8 +39,8 @@ export function showBanner (nuxt, showMemoryUsage = true) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom badge messages
|
// Add custom badge messages
|
||||||
if (nuxt.options.cli.badgeMessages.length) {
|
if (badgeMessages.length) {
|
||||||
messageLines.push('', ...nuxt.options.cli.badgeMessages)
|
messageLines.push('', ...badgeMessages)
|
||||||
}
|
}
|
||||||
|
|
||||||
process.stdout.write(successBox(messageLines.join('\n'), titleLines.join('\n')))
|
process.stdout.write(successBox(messageLines.join('\n'), titleLines.join('\n')))
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import defaultsDeep from 'lodash/defaultsDeep'
|
import defaultsDeep from 'lodash/defaultsDeep'
|
||||||
import { loadNuxtConfig as _loadNuxtConfig, getDefaultNuxtConfig } from '@nuxt/config'
|
import { loadNuxtConfig as _loadNuxtConfig, getDefaultNuxtConfig } from '@nuxt/config'
|
||||||
|
import { MODES } from '@nuxt/utils'
|
||||||
|
|
||||||
export async function loadNuxtConfig (argv, configContext) {
|
export async function loadNuxtConfig (argv, configContext) {
|
||||||
const rootDir = path.resolve(argv._[0] || '.')
|
const rootDir = path.resolve(argv._[0] || '.')
|
||||||
@ -14,7 +15,8 @@ export async function loadNuxtConfig (argv, configContext) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Nuxt Mode
|
// Nuxt Mode
|
||||||
options.mode = (argv.spa && 'spa') || (argv.universal && 'universal') || options.mode
|
options.mode =
|
||||||
|
(argv.spa && MODES.spa) || (argv.universal && MODES.universal) || options.mode
|
||||||
|
|
||||||
// Server options
|
// Server options
|
||||||
options.server = defaultsDeep({
|
options.server = defaultsDeep({
|
||||||
|
@ -18,6 +18,9 @@ exports[`cli/command builds help text 1`] = `
|
|||||||
--modern, -m Build/Start app for
|
--modern, -m Build/Start app for
|
||||||
modern browsers, e.g. server, client and
|
modern browsers, e.g. server, client and
|
||||||
false
|
false
|
||||||
|
--target, -t Build/start app for a
|
||||||
|
different target, e.g. server,
|
||||||
|
serverless and static
|
||||||
--force-exit Whether Nuxt.js
|
--force-exit Whether Nuxt.js
|
||||||
should force exit after the command has
|
should force exit after the command has
|
||||||
finished
|
finished
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { MODES, TARGETS } from '@nuxt/utils'
|
||||||
import * as utils from '../../src/utils'
|
import * as utils from '../../src/utils'
|
||||||
import { mockGetNuxt, mockGetBuilder, mockGetGenerator, NuxtCommand } from '../utils'
|
import { mockGetNuxt, mockGetBuilder, mockGetGenerator, NuxtCommand } from '../utils'
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ describe('build', () => {
|
|||||||
|
|
||||||
test('builds on universal mode', async () => {
|
test('builds on universal mode', async () => {
|
||||||
mockGetNuxt({
|
mockGetNuxt({
|
||||||
mode: 'universal',
|
mode: MODES.universal,
|
||||||
build: {
|
build: {
|
||||||
analyze: true
|
analyze: true
|
||||||
}
|
}
|
||||||
@ -37,7 +38,8 @@ describe('build', () => {
|
|||||||
|
|
||||||
test('generates on spa mode', async () => {
|
test('generates on spa mode', async () => {
|
||||||
mockGetNuxt({
|
mockGetNuxt({
|
||||||
mode: 'spa',
|
mode: MODES.spa,
|
||||||
|
target: TARGETS.server,
|
||||||
build: {
|
build: {
|
||||||
analyze: false
|
analyze: false
|
||||||
}
|
}
|
||||||
@ -51,7 +53,7 @@ describe('build', () => {
|
|||||||
|
|
||||||
test('build with devtools', async () => {
|
test('build with devtools', async () => {
|
||||||
mockGetNuxt({
|
mockGetNuxt({
|
||||||
mode: 'universal'
|
mode: MODES.universal
|
||||||
})
|
})
|
||||||
const builder = mockGetBuilder(Promise.resolve())
|
const builder = mockGetBuilder(Promise.resolve())
|
||||||
|
|
||||||
@ -67,7 +69,7 @@ describe('build', () => {
|
|||||||
|
|
||||||
test('build with modern mode', async () => {
|
test('build with modern mode', async () => {
|
||||||
mockGetNuxt({
|
mockGetNuxt({
|
||||||
mode: 'universal'
|
mode: MODES.universal
|
||||||
})
|
})
|
||||||
mockGetBuilder(Promise.resolve())
|
mockGetBuilder(Promise.resolve())
|
||||||
|
|
||||||
@ -114,7 +116,7 @@ describe('build', () => {
|
|||||||
|
|
||||||
test('build locks project by default', async () => {
|
test('build locks project by default', async () => {
|
||||||
mockGetNuxt({
|
mockGetNuxt({
|
||||||
mode: 'universal'
|
mode: MODES.universal
|
||||||
})
|
})
|
||||||
mockGetBuilder(Promise.resolve())
|
mockGetBuilder(Promise.resolve())
|
||||||
|
|
||||||
@ -131,7 +133,7 @@ describe('build', () => {
|
|||||||
|
|
||||||
test('build can disable locking', async () => {
|
test('build can disable locking', async () => {
|
||||||
mockGetNuxt({
|
mockGetNuxt({
|
||||||
mode: 'universal'
|
mode: MODES.universal
|
||||||
})
|
})
|
||||||
mockGetBuilder(Promise.resolve())
|
mockGetBuilder(Promise.resolve())
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ describe('cli/command', () => {
|
|||||||
const cmd = new Command({ options: allOptions })
|
const cmd = new Command({ options: allOptions })
|
||||||
const minimistOptions = cmd._getMinimistOptions()
|
const minimistOptions = cmd._getMinimistOptions()
|
||||||
|
|
||||||
expect(minimistOptions.string.length).toBe(5)
|
expect(minimistOptions.string.length).toBe(6)
|
||||||
expect(minimistOptions.boolean.length).toBe(5)
|
expect(minimistOptions.boolean.length).toBe(5)
|
||||||
expect(minimistOptions.alias.c).toBe('config-file')
|
expect(minimistOptions.alias.c).toBe('config-file')
|
||||||
expect(minimistOptions.default.c).toBe(common['config-file'].default)
|
expect(minimistOptions.default.c).toBe(common['config-file'].default)
|
||||||
|
118
packages/cli/test/unit/export.test.js
Normal file
118
packages/cli/test/unit/export.test.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import * as utils from '../../src/utils'
|
||||||
|
import { mockGetNuxt, mockGetGenerator, NuxtCommand } from '../utils'
|
||||||
|
|
||||||
|
describe('export', () => {
|
||||||
|
let exportCommand
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
exportCommand = await import('../../src/commands/export').then(m => m.default)
|
||||||
|
jest.spyOn(process, 'exit').mockImplementation(code => code)
|
||||||
|
jest.spyOn(utils, 'forceExit').mockImplementation(() => {})
|
||||||
|
jest.spyOn(utils, 'createLock').mockImplementation(() => () => {})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
process.exit.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => jest.resetAllMocks())
|
||||||
|
|
||||||
|
test('has run function', () => {
|
||||||
|
expect(typeof exportCommand.run).toBe('function')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('init by default, build false', async () => {
|
||||||
|
mockGetNuxt({ generate: { dir: 'dist' } })
|
||||||
|
const generator = mockGetGenerator()
|
||||||
|
|
||||||
|
await NuxtCommand.from(exportCommand).run()
|
||||||
|
|
||||||
|
expect(generator).toHaveBeenCalled()
|
||||||
|
expect(generator.mock.calls[0][0].init).toBe(true)
|
||||||
|
expect(generator.mock.calls[0][0].build).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('force-exits by default', async () => {
|
||||||
|
mockGetNuxt({ generate: { dir: 'dist' } })
|
||||||
|
mockGetGenerator()
|
||||||
|
|
||||||
|
const cmd = NuxtCommand.from(exportCommand, ['export', '.'])
|
||||||
|
await cmd.run()
|
||||||
|
|
||||||
|
expect(utils.forceExit).toHaveBeenCalledTimes(1)
|
||||||
|
expect(utils.forceExit).toHaveBeenCalledWith('export', 5)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can set force exit explicitly', async () => {
|
||||||
|
mockGetNuxt({ generate: { dir: 'dist' } })
|
||||||
|
mockGetGenerator()
|
||||||
|
|
||||||
|
const cmd = NuxtCommand.from(exportCommand, ['export', '.', '--force-exit'])
|
||||||
|
await cmd.run()
|
||||||
|
|
||||||
|
expect(utils.forceExit).toHaveBeenCalledTimes(1)
|
||||||
|
expect(utils.forceExit).toHaveBeenCalledWith('export', false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can disable force exit explicitly', async () => {
|
||||||
|
mockGetNuxt({ generate: { dir: 'dist' } })
|
||||||
|
mockGetGenerator()
|
||||||
|
|
||||||
|
const cmd = NuxtCommand.from(exportCommand, ['generate', '.', '--no-force-exit'])
|
||||||
|
await cmd.run()
|
||||||
|
|
||||||
|
expect(utils.forceExit).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('locks project by default', async () => {
|
||||||
|
const releaseLock = jest.fn(() => Promise.resolve())
|
||||||
|
const createLock = jest.fn(() => releaseLock)
|
||||||
|
jest.spyOn(utils, 'createLock').mockImplementation(createLock)
|
||||||
|
|
||||||
|
mockGetNuxt({ generate: { dir: 'dist' } })
|
||||||
|
mockGetGenerator()
|
||||||
|
|
||||||
|
const cmd = NuxtCommand.from(exportCommand, ['export', '.'])
|
||||||
|
await cmd.run()
|
||||||
|
|
||||||
|
expect(createLock).toHaveBeenCalledTimes(1)
|
||||||
|
expect(releaseLock).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can disable locking', async () => {
|
||||||
|
mockGetNuxt({ generate: { dir: 'dist' } })
|
||||||
|
mockGetGenerator()
|
||||||
|
|
||||||
|
const createLock = jest.fn(() => Promise.resolve())
|
||||||
|
jest.spyOn(utils, 'createLock').mockImplementationOnce(() => createLock)
|
||||||
|
|
||||||
|
const cmd = NuxtCommand.from(exportCommand, ['export', '.', '--no-lock'])
|
||||||
|
await cmd.run()
|
||||||
|
|
||||||
|
expect(createLock).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('throw an error when fail-on-error enabled and page errors', async () => {
|
||||||
|
mockGetNuxt({ generate: { dir: 'dist' } })
|
||||||
|
mockGetGenerator(() => ({ errors: [{ type: 'dummy' }] }))
|
||||||
|
|
||||||
|
const cmd = NuxtCommand.from(exportCommand, ['export', '.', '--fail-on-error'])
|
||||||
|
await expect(cmd.run()).rejects.toThrow('Error exporting pages, exiting with non-zero code')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('do not throw an error when fail-on-error disabled and page errors', async () => {
|
||||||
|
mockGetNuxt({ generate: { dir: 'dist' } })
|
||||||
|
mockGetGenerator(() => ({ errors: [{ type: 'dummy' }] }))
|
||||||
|
|
||||||
|
const cmd = NuxtCommand.from(exportCommand, ['export', '.'])
|
||||||
|
await cmd.run()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('do not throw an error when fail-on-error enabled and no page errors', async () => {
|
||||||
|
mockGetNuxt({ generate: { dir: 'dist' } })
|
||||||
|
mockGetGenerator()
|
||||||
|
|
||||||
|
const cmd = NuxtCommand.from(exportCommand, ['export', '.', '--fail-on-error'])
|
||||||
|
await cmd.run()
|
||||||
|
})
|
||||||
|
})
|
44
packages/cli/test/unit/serve.test.js
Normal file
44
packages/cli/test/unit/serve.test.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { promises as fs } from 'fs'
|
||||||
|
import { TARGETS } from '@nuxt/utils'
|
||||||
|
import * as utils from '../../src/utils/'
|
||||||
|
import { consola, mockNuxt, mockGetNuxtConfig, NuxtCommand } from '../utils'
|
||||||
|
|
||||||
|
describe('serve', () => {
|
||||||
|
let serve
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
serve = await import('../../src/commands/serve').then(m => m.default)
|
||||||
|
jest.spyOn(utils, 'forceExit').mockImplementation(() => {})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('has run 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', () => {
|
||||||
|
mockGetNuxtConfig({ target: TARGETS.static })
|
||||||
|
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`.'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('no error if dist/ dir exists', async () => {
|
||||||
|
mockGetNuxtConfig({ target: TARGETS.static })
|
||||||
|
mockNuxt()
|
||||||
|
fs.stat = jest.fn().mockImplementationOnce(() => Promise.resolve(({
|
||||||
|
isDirectory: () => true
|
||||||
|
})))
|
||||||
|
fs.readFile = jest.fn().mockImplementationOnce(() => Promise.resolve('HTML here'))
|
||||||
|
await NuxtCommand.from(serve).run()
|
||||||
|
expect(consola.fatal).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
@ -1,4 +1,5 @@
|
|||||||
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'
|
||||||
|
|
||||||
@ -35,8 +36,16 @@ 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()
|
||||||
|
|
||||||
const cmd = NuxtCommand.from(start, ['start', '.'])
|
const cmd = NuxtCommand.from(start, ['start', '.'])
|
||||||
await cmd.run()
|
await cmd.run()
|
||||||
@ -46,6 +55,7 @@ describe('start', () => {
|
|||||||
|
|
||||||
test('start can set force exit explicitly', async () => {
|
test('start can set force exit explicitly', async () => {
|
||||||
mockGetNuxtStart()
|
mockGetNuxtStart()
|
||||||
|
mockGetNuxtConfig()
|
||||||
|
|
||||||
const cmd = NuxtCommand.from(start, ['start', '.', '--force-exit'])
|
const cmd = NuxtCommand.from(start, ['start', '.', '--force-exit'])
|
||||||
await cmd.run()
|
await cmd.run()
|
||||||
@ -56,6 +66,7 @@ describe('start', () => {
|
|||||||
|
|
||||||
test('start can disable force exit explicitly', async () => {
|
test('start can disable force exit explicitly', async () => {
|
||||||
mockGetNuxtStart()
|
mockGetNuxtStart()
|
||||||
|
mockGetNuxtConfig()
|
||||||
|
|
||||||
const cmd = NuxtCommand.from(start, ['start', '.', '--no-force-exit'])
|
const cmd = NuxtCommand.from(start, ['start', '.', '--no-force-exit'])
|
||||||
await cmd.run()
|
await cmd.run()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { getDefaultNuxtConfig } from '@nuxt/config'
|
import { getDefaultNuxtConfig } from '@nuxt/config'
|
||||||
|
import { TARGETS, MODES } from '@nuxt/utils'
|
||||||
import { consola } from '../utils'
|
import { consola } from '../utils'
|
||||||
import { loadNuxtConfig } from '../../src/utils/config'
|
import { loadNuxtConfig } from '../../src/utils/config'
|
||||||
import * as utils from '../../src/utils'
|
import * as utils from '../../src/utils'
|
||||||
@ -24,7 +25,7 @@ describe('cli/utils', () => {
|
|||||||
|
|
||||||
const options = await loadNuxtConfig(argv)
|
const options = await loadNuxtConfig(argv)
|
||||||
expect(options.rootDir).toBe(process.cwd())
|
expect(options.rootDir).toBe(process.cwd())
|
||||||
expect(options.mode).toBe('universal')
|
expect(options.mode).toBe(MODES.universal)
|
||||||
expect(options.server.host).toBe('localhost')
|
expect(options.server.host).toBe('localhost')
|
||||||
expect(options.server.port).toBe(3000)
|
expect(options.server.port).toBe(3000)
|
||||||
expect(options.server.socket).not.toBeDefined()
|
expect(options.server.socket).not.toBeDefined()
|
||||||
@ -40,7 +41,7 @@ describe('cli/utils', () => {
|
|||||||
const options = await loadNuxtConfig(argv)
|
const options = await loadNuxtConfig(argv)
|
||||||
expect(options.testOption).toBe(true)
|
expect(options.testOption).toBe(true)
|
||||||
expect(options.rootDir).toBe('/some/path')
|
expect(options.rootDir).toBe('/some/path')
|
||||||
expect(options.mode).toBe('spa')
|
expect(options.mode).toBe(MODES.spa)
|
||||||
expect(options.server.host).toBe('nuxt-host')
|
expect(options.server.host).toBe('nuxt-host')
|
||||||
expect(options.server.port).toBe(3001)
|
expect(options.server.port).toBe(3001)
|
||||||
expect(options.server.socket).toBe('/var/run/nuxt.sock')
|
expect(options.server.socket).toBe('/var/run/nuxt.sock')
|
||||||
@ -149,6 +150,9 @@ describe('cli/utils', () => {
|
|||||||
|
|
||||||
showBanner({
|
showBanner({
|
||||||
options: {
|
options: {
|
||||||
|
render: {
|
||||||
|
ssr: true
|
||||||
|
},
|
||||||
cli: {
|
cli: {
|
||||||
badgeMessages,
|
badgeMessages,
|
||||||
bannerColor
|
bannerColor
|
||||||
@ -179,6 +183,9 @@ describe('cli/utils', () => {
|
|||||||
cli: {
|
cli: {
|
||||||
badgeMessages: [],
|
badgeMessages: [],
|
||||||
bannerColor: 'green'
|
bannerColor: 'green'
|
||||||
|
},
|
||||||
|
render: {
|
||||||
|
ssr: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
@ -193,6 +200,37 @@ describe('cli/utils', () => {
|
|||||||
stdout.mockRestore()
|
stdout.mockRestore()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('showBanner does print env, rendering mode and target', () => {
|
||||||
|
const stdout = jest.spyOn(process.stdout, 'write').mockImplementation(() => {})
|
||||||
|
const successBox = jest.fn().mockImplementation((m, t) => t + m)
|
||||||
|
jest.spyOn(fmt, 'successBox').mockImplementation(successBox)
|
||||||
|
|
||||||
|
showBanner({
|
||||||
|
options: {
|
||||||
|
dev: false,
|
||||||
|
target: TARGETS.static,
|
||||||
|
render: {
|
||||||
|
ssr: false
|
||||||
|
},
|
||||||
|
cli: {
|
||||||
|
bannerColor: 'green',
|
||||||
|
badgeMessages: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
listeners: []
|
||||||
|
}
|
||||||
|
}, false)
|
||||||
|
|
||||||
|
expect(successBox).toHaveBeenCalledTimes(1)
|
||||||
|
expect(stdout).toHaveBeenCalledTimes(1)
|
||||||
|
expect(stdout).toHaveBeenCalledWith(expect.stringMatching('Nuxt.js'))
|
||||||
|
expect(stdout).toHaveBeenCalledWith(expect.stringMatching('Running in production'))
|
||||||
|
expect(stdout).toHaveBeenCalledWith(expect.stringMatching('client-side rendering'))
|
||||||
|
expect(stdout).toHaveBeenCalledWith(expect.stringMatching('static target'))
|
||||||
|
stdout.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
test('showMemoryUsage prints memory usage', () => {
|
test('showMemoryUsage prints memory usage', () => {
|
||||||
showMemoryUsage()
|
showMemoryUsage()
|
||||||
|
|
||||||
|
@ -22,6 +22,10 @@ export const mockGetNuxt = (options = {}, implementation) => {
|
|||||||
Command.prototype.getNuxt = jest.fn().mockImplementationOnce(() => {
|
Command.prototype.getNuxt = jest.fn().mockImplementationOnce(() => {
|
||||||
return Object.assign({
|
return Object.assign({
|
||||||
hook: jest.fn(),
|
hook: jest.fn(),
|
||||||
|
server: {
|
||||||
|
listen: jest.fn()
|
||||||
|
},
|
||||||
|
close: jest.fn(),
|
||||||
options
|
options
|
||||||
}, implementation)
|
}, implementation)
|
||||||
})
|
})
|
||||||
@ -68,8 +72,9 @@ export const mockGetNuxtStart = (ssr) => {
|
|||||||
return { listen }
|
return { listen }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mockGetNuxtConfig = () => {
|
export const mockGetNuxtConfig = (config = {}) => {
|
||||||
const spy = jest.fn()
|
const spy = jest.fn()
|
||||||
|
spy.mockReturnValue(config)
|
||||||
Command.prototype.getNuxtConfig = spy
|
Command.prototype.getNuxtConfig = spy
|
||||||
return spy
|
return spy
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import capitalize from 'lodash/capitalize'
|
import capitalize from 'lodash/capitalize'
|
||||||
import env from 'std-env'
|
import env from 'std-env'
|
||||||
|
import { TARGETS, MODES } from '@nuxt/utils'
|
||||||
|
|
||||||
export default () => ({
|
export default () => ({
|
||||||
// Env
|
// Env
|
||||||
@ -8,8 +9,15 @@ export default () => ({
|
|||||||
debug: undefined, // = dev
|
debug: undefined, // = dev
|
||||||
env: {},
|
env: {},
|
||||||
|
|
||||||
|
// Target
|
||||||
|
target: TARGETS.server,
|
||||||
|
|
||||||
|
// Rendering
|
||||||
|
ssr: true,
|
||||||
|
|
||||||
|
// TODO: remove in Nuxt 3
|
||||||
// Mode
|
// Mode
|
||||||
mode: 'universal',
|
mode: MODES.universal,
|
||||||
modern: undefined,
|
modern: undefined,
|
||||||
|
|
||||||
globalName: undefined,
|
globalName: undefined,
|
||||||
@ -53,17 +61,6 @@ export default () => ({
|
|||||||
'**/*.spec.*'
|
'**/*.spec.*'
|
||||||
],
|
],
|
||||||
|
|
||||||
// Generate
|
|
||||||
generate: {
|
|
||||||
dir: 'dist',
|
|
||||||
routes: [],
|
|
||||||
exclude: [],
|
|
||||||
concurrency: 500,
|
|
||||||
interval: 0,
|
|
||||||
subFolders: true,
|
|
||||||
fallback: '200.html'
|
|
||||||
},
|
|
||||||
|
|
||||||
// Watch
|
// Watch
|
||||||
watch: [],
|
watch: [],
|
||||||
watchers: {
|
watchers: {
|
||||||
|
17
packages/config/src/config/generate.js
Normal file
17
packages/config/src/config/generate.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
export default () => ({
|
||||||
|
dir: 'dist',
|
||||||
|
routes: [],
|
||||||
|
exclude: [],
|
||||||
|
concurrency: 500,
|
||||||
|
interval: 0,
|
||||||
|
subFolders: true,
|
||||||
|
fallback: '200.html',
|
||||||
|
crawler: true,
|
||||||
|
staticAssets: {
|
||||||
|
base: undefined, // Default: "/_nuxt/static:
|
||||||
|
versionBase: undefined, // Default: "_nuxt/static/{version}""
|
||||||
|
dir: 'static',
|
||||||
|
version: undefined // Default: "{timeStampSec}"
|
||||||
|
}
|
||||||
|
})
|
@ -9,6 +9,7 @@ import render from './render'
|
|||||||
import router from './router'
|
import router from './router'
|
||||||
import server from './server'
|
import server from './server'
|
||||||
import cli from './cli'
|
import cli from './cli'
|
||||||
|
import generate from './generate'
|
||||||
|
|
||||||
export const defaultNuxtConfigFile = 'nuxt.config'
|
export const defaultNuxtConfigFile = 'nuxt.config'
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ export function getDefaultNuxtConfig (options = {}) {
|
|||||||
render: render(),
|
render: render(),
|
||||||
router: router(),
|
router: router(),
|
||||||
server: server(options),
|
server: server(options),
|
||||||
cli: cli()
|
cli: cli(),
|
||||||
|
generate: generate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import { MODES } from '@nuxt/utils'
|
||||||
|
|
||||||
export default () => ({
|
export default () => ({
|
||||||
universal: {
|
[MODES.universal]: {
|
||||||
build: {
|
build: {
|
||||||
ssr: true
|
ssr: true
|
||||||
},
|
},
|
||||||
@ -7,7 +9,7 @@ export default () => ({
|
|||||||
ssr: true
|
ssr: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
spa: {
|
[MODES.spa]: {
|
||||||
build: {
|
build: {
|
||||||
ssr: false
|
ssr: false
|
||||||
},
|
},
|
||||||
|
@ -13,5 +13,6 @@ export default () => ({
|
|||||||
stringifyQuery: false,
|
stringifyQuery: false,
|
||||||
fallback: false,
|
fallback: false,
|
||||||
prefetchLinks: true,
|
prefetchLinks: true,
|
||||||
|
prefetchPayloads: true,
|
||||||
trailingSlash: undefined
|
trailingSlash: undefined
|
||||||
})
|
})
|
||||||
|
@ -5,7 +5,7 @@ import defu from 'defu'
|
|||||||
import pick from 'lodash/pick'
|
import pick from 'lodash/pick'
|
||||||
import uniq from 'lodash/uniq'
|
import uniq from 'lodash/uniq'
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
import { guardDir, isNonEmptyString, isPureObject, isUrl, getMainModule } from '@nuxt/utils'
|
import { TARGETS, MODES, guardDir, isNonEmptyString, isPureObject, isUrl, getMainModule, urlJoin } from '@nuxt/utils'
|
||||||
import { defaultNuxtConfigFile, getDefaultNuxtConfig } from './config'
|
import { defaultNuxtConfigFile, getDefaultNuxtConfig } from './config'
|
||||||
|
|
||||||
export function getNuxtConfig (_options) {
|
export function getNuxtConfig (_options) {
|
||||||
@ -89,6 +89,26 @@ export function getNuxtConfig (_options) {
|
|||||||
|
|
||||||
defaultsDeep(options, nuxtConfig)
|
defaultsDeep(options, nuxtConfig)
|
||||||
|
|
||||||
|
// Target
|
||||||
|
options.target = options.target || 'server'
|
||||||
|
if (!Object.values(TARGETS).includes(options.target)) {
|
||||||
|
consola.warn(`Unknown target: ${options.target}. Falling back to server`)
|
||||||
|
options.target = 'server'
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSR root option
|
||||||
|
if (options.ssr === false) {
|
||||||
|
options.mode = MODES.spa
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply mode preset
|
||||||
|
const modePreset = options.modes[options.mode || MODES.universal]
|
||||||
|
|
||||||
|
if (!modePreset) {
|
||||||
|
consola.warn(`Unknown mode: ${options.mode}. Falling back to ${MODES.universal}`)
|
||||||
|
}
|
||||||
|
defaultsDeep(options, modePreset || options.modes[MODES.universal])
|
||||||
|
|
||||||
// Sanitize router.base
|
// Sanitize router.base
|
||||||
if (!/\/$/.test(options.router.base)) {
|
if (!/\/$/.test(options.router.base)) {
|
||||||
options.router.base += '/'
|
options.router.base += '/'
|
||||||
@ -241,7 +261,7 @@ export function getNuxtConfig (_options) {
|
|||||||
hashAlgorithm: 'sha256',
|
hashAlgorithm: 'sha256',
|
||||||
allowedSources: undefined,
|
allowedSources: undefined,
|
||||||
policies: undefined,
|
policies: undefined,
|
||||||
addMeta: Boolean(options._generate),
|
addMeta: Boolean(options.target === TARGETS.static),
|
||||||
unsafeInlineCompatibility: false,
|
unsafeInlineCompatibility: false,
|
||||||
reportOnly: options.debug
|
reportOnly: options.debug
|
||||||
})
|
})
|
||||||
@ -316,14 +336,6 @@ export function getNuxtConfig (_options) {
|
|||||||
delete options.render.gzip
|
delete options.render.gzip
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply mode preset
|
|
||||||
const modePreset = options.modes[options.mode || 'universal']
|
|
||||||
|
|
||||||
if (!modePreset) {
|
|
||||||
consola.warn(`Unknown mode: ${options.mode}. Falling back to universal`)
|
|
||||||
}
|
|
||||||
defaultsDeep(options, modePreset || options.modes.universal)
|
|
||||||
|
|
||||||
// If no server-side rendering, add appear true transition
|
// If no server-side rendering, add appear true transition
|
||||||
if (options.render.ssr === false && options.pageTransition) {
|
if (options.render.ssr === false && options.pageTransition) {
|
||||||
options.pageTransition.appear = true
|
options.pageTransition.appear = true
|
||||||
@ -436,5 +448,18 @@ export function getNuxtConfig (_options) {
|
|||||||
.map(([path, handler]) => ({ path, handler }))
|
.map(([path, handler]) => ({ path, handler }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate staticAssets
|
||||||
|
const { staticAssets } = options.generate
|
||||||
|
if (!staticAssets.version) {
|
||||||
|
staticAssets.version = String(Math.round(Date.now() / 1000))
|
||||||
|
}
|
||||||
|
if (!staticAssets.base) {
|
||||||
|
const publicPath = isUrl(options.build.publicPath) ? '' : options.build.publicPath // "/_nuxt" or custom CDN URL
|
||||||
|
staticAssets.base = urlJoin(publicPath, staticAssets.dir)
|
||||||
|
}
|
||||||
|
if (!staticAssets.versionBase) {
|
||||||
|
staticAssets.versionBase = urlJoin(staticAssets.base, staticAssets.version)
|
||||||
|
}
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
@ -190,11 +190,18 @@ Object {
|
|||||||
},
|
},
|
||||||
"generate": Object {
|
"generate": Object {
|
||||||
"concurrency": 500,
|
"concurrency": 500,
|
||||||
|
"crawler": true,
|
||||||
"dir": "/var/nuxt/test/dist",
|
"dir": "/var/nuxt/test/dist",
|
||||||
"exclude": Array [],
|
"exclude": Array [],
|
||||||
"fallback": "200.html",
|
"fallback": "200.html",
|
||||||
"interval": 0,
|
"interval": 0,
|
||||||
"routes": Array [],
|
"routes": Array [],
|
||||||
|
"staticAssets": Object {
|
||||||
|
"base": "/_nuxt/static",
|
||||||
|
"dir": "static",
|
||||||
|
"version": "x",
|
||||||
|
"versionBase": "/_nuxt/static/x",
|
||||||
|
},
|
||||||
"subFolders": true,
|
"subFolders": true,
|
||||||
},
|
},
|
||||||
"globalName": "nuxt",
|
"globalName": "nuxt",
|
||||||
@ -339,6 +346,7 @@ Object {
|
|||||||
"mode": "history",
|
"mode": "history",
|
||||||
"parseQuery": false,
|
"parseQuery": false,
|
||||||
"prefetchLinks": true,
|
"prefetchLinks": true,
|
||||||
|
"prefetchPayloads": true,
|
||||||
"routeNameSplitter": "-",
|
"routeNameSplitter": "-",
|
||||||
"routes": Array [],
|
"routes": Array [],
|
||||||
"scrollBehavior": null,
|
"scrollBehavior": null,
|
||||||
@ -354,6 +362,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"serverMiddleware": Array [],
|
"serverMiddleware": Array [],
|
||||||
"srcDir": "/var/nuxt/test",
|
"srcDir": "/var/nuxt/test",
|
||||||
|
"ssr": true,
|
||||||
"styleExtensions": Array [
|
"styleExtensions": Array [
|
||||||
"css",
|
"css",
|
||||||
"pcss",
|
"pcss",
|
||||||
@ -364,6 +373,7 @@ Object {
|
|||||||
"sass",
|
"sass",
|
||||||
"less",
|
"less",
|
||||||
],
|
],
|
||||||
|
"target": "server",
|
||||||
"test": true,
|
"test": true,
|
||||||
"vue": Object {
|
"vue": Object {
|
||||||
"config": Object {
|
"config": Object {
|
||||||
|
@ -171,11 +171,18 @@ Object {
|
|||||||
},
|
},
|
||||||
"generate": Object {
|
"generate": Object {
|
||||||
"concurrency": 500,
|
"concurrency": 500,
|
||||||
|
"crawler": true,
|
||||||
"dir": "dist",
|
"dir": "dist",
|
||||||
"exclude": Array [],
|
"exclude": Array [],
|
||||||
"fallback": "200.html",
|
"fallback": "200.html",
|
||||||
"interval": 0,
|
"interval": 0,
|
||||||
"routes": Array [],
|
"routes": Array [],
|
||||||
|
"staticAssets": Object {
|
||||||
|
"base": undefined,
|
||||||
|
"dir": "static",
|
||||||
|
"version": undefined,
|
||||||
|
"versionBase": undefined,
|
||||||
|
},
|
||||||
"subFolders": true,
|
"subFolders": true,
|
||||||
},
|
},
|
||||||
"globalName": undefined,
|
"globalName": undefined,
|
||||||
@ -310,6 +317,7 @@ Object {
|
|||||||
"mode": "history",
|
"mode": "history",
|
||||||
"parseQuery": false,
|
"parseQuery": false,
|
||||||
"prefetchLinks": true,
|
"prefetchLinks": true,
|
||||||
|
"prefetchPayloads": true,
|
||||||
"routeNameSplitter": "-",
|
"routeNameSplitter": "-",
|
||||||
"routes": Array [],
|
"routes": Array [],
|
||||||
"scrollBehavior": null,
|
"scrollBehavior": null,
|
||||||
@ -325,6 +333,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"serverMiddleware": Array [],
|
"serverMiddleware": Array [],
|
||||||
"srcDir": undefined,
|
"srcDir": undefined,
|
||||||
|
"ssr": true,
|
||||||
"styleExtensions": Array [
|
"styleExtensions": Array [
|
||||||
"css",
|
"css",
|
||||||
"pcss",
|
"pcss",
|
||||||
@ -335,6 +344,7 @@ Object {
|
|||||||
"sass",
|
"sass",
|
||||||
"less",
|
"less",
|
||||||
],
|
],
|
||||||
|
"target": "server",
|
||||||
"test": true,
|
"test": true,
|
||||||
"vue": Object {
|
"vue": Object {
|
||||||
"config": Object {
|
"config": Object {
|
||||||
@ -527,11 +537,18 @@ Object {
|
|||||||
},
|
},
|
||||||
"generate": Object {
|
"generate": Object {
|
||||||
"concurrency": 500,
|
"concurrency": 500,
|
||||||
|
"crawler": true,
|
||||||
"dir": "dist",
|
"dir": "dist",
|
||||||
"exclude": Array [],
|
"exclude": Array [],
|
||||||
"fallback": "200.html",
|
"fallback": "200.html",
|
||||||
"interval": 0,
|
"interval": 0,
|
||||||
"routes": Array [],
|
"routes": Array [],
|
||||||
|
"staticAssets": Object {
|
||||||
|
"base": undefined,
|
||||||
|
"dir": "static",
|
||||||
|
"version": undefined,
|
||||||
|
"versionBase": undefined,
|
||||||
|
},
|
||||||
"subFolders": true,
|
"subFolders": true,
|
||||||
},
|
},
|
||||||
"globalName": undefined,
|
"globalName": undefined,
|
||||||
@ -666,6 +683,7 @@ Object {
|
|||||||
"mode": "history",
|
"mode": "history",
|
||||||
"parseQuery": false,
|
"parseQuery": false,
|
||||||
"prefetchLinks": true,
|
"prefetchLinks": true,
|
||||||
|
"prefetchPayloads": true,
|
||||||
"routeNameSplitter": "-",
|
"routeNameSplitter": "-",
|
||||||
"routes": Array [],
|
"routes": Array [],
|
||||||
"scrollBehavior": null,
|
"scrollBehavior": null,
|
||||||
@ -681,6 +699,7 @@ Object {
|
|||||||
},
|
},
|
||||||
"serverMiddleware": Array [],
|
"serverMiddleware": Array [],
|
||||||
"srcDir": undefined,
|
"srcDir": undefined,
|
||||||
|
"ssr": true,
|
||||||
"styleExtensions": Array [
|
"styleExtensions": Array [
|
||||||
"css",
|
"css",
|
||||||
"pcss",
|
"pcss",
|
||||||
@ -691,6 +710,7 @@ Object {
|
|||||||
"sass",
|
"sass",
|
||||||
"less",
|
"less",
|
||||||
],
|
],
|
||||||
|
"target": "server",
|
||||||
"test": true,
|
"test": true,
|
||||||
"vue": Object {
|
"vue": Object {
|
||||||
"config": Object {
|
"config": Object {
|
||||||
|
@ -25,7 +25,7 @@ describe('config: options', () => {
|
|||||||
jest.spyOn(path, 'resolve').mockImplementation((...args) => args.join('/').replace(/\\+/, '/'))
|
jest.spyOn(path, 'resolve').mockImplementation((...args) => args.join('/').replace(/\\+/, '/'))
|
||||||
jest.spyOn(path, 'join').mockImplementation((...args) => args.join('/').replace(/\\+/, '/'))
|
jest.spyOn(path, 'join').mockImplementation((...args) => args.join('/').replace(/\\+/, '/'))
|
||||||
|
|
||||||
expect(getNuxtConfig({})).toMatchSnapshot()
|
expect(getNuxtConfig({ generate: { staticAssets: { version: 'x' } } })).toMatchSnapshot()
|
||||||
|
|
||||||
process.cwd.mockRestore()
|
process.cwd.mockRestore()
|
||||||
path.resolve.mockRestore()
|
path.resolve.mockRestore()
|
||||||
@ -124,6 +124,17 @@ describe('config: options', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should fallback to server target', () => {
|
||||||
|
const { target } = getNuxtConfig({ target: 0 })
|
||||||
|
expect(target).toEqual('server')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should check unknown target', () => {
|
||||||
|
const { target } = getNuxtConfig({ target: 'test' })
|
||||||
|
expect(consola.warn).toHaveBeenCalledWith('Unknown target: test. Falling back to server')
|
||||||
|
expect(target).toEqual('server')
|
||||||
|
})
|
||||||
|
|
||||||
test('should check unknown mode', () => {
|
test('should check unknown mode', () => {
|
||||||
const { build, render } = getNuxtConfig({ mode: 'test' })
|
const { build, render } = getNuxtConfig({ mode: 'test' })
|
||||||
expect(consola.warn).toHaveBeenCalledWith('Unknown mode: test. Falling back to universal')
|
expect(consola.warn).toHaveBeenCalledWith('Unknown mode: test. Falling back to universal')
|
||||||
|
@ -45,11 +45,10 @@ export default class ModuleContainer {
|
|||||||
throw new Error('Template src not found: ' + src)
|
throw new Error('Template src not found: ' + src)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate unique and human readable dst filename
|
// Mostly for DX, some people prefers `filename` vs `fileName`
|
||||||
const dst =
|
const fileName = template.fileName || template.filename
|
||||||
template.fileName ||
|
// Generate unique and human readable dst filename if not provided
|
||||||
path.basename(srcPath.dir) + `.${srcPath.name}.${hash(src)}` + srcPath.ext
|
const dst = fileName || `${path.basename(srcPath.dir)}.${srcPath.name}.${hash(src)}${srcPath.ext}`
|
||||||
|
|
||||||
// Add to templates list
|
// Add to templates list
|
||||||
const templateObj = {
|
const templateObj = {
|
||||||
src,
|
src,
|
||||||
@ -58,6 +57,7 @@ export default class ModuleContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.options.build.templates.push(templateObj)
|
this.options.build.templates.push(templateObj)
|
||||||
|
|
||||||
return templateObj
|
return templateObj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
"consola": "^2.11.3",
|
"consola": "^2.11.3",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"html-minifier": "^4.0.0"
|
"html-minifier": "^4.0.0",
|
||||||
|
"node-html-parser": "^1.2.4"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import Chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
import fsExtra from 'fs-extra'
|
import fsExtra from 'fs-extra'
|
||||||
import htmlMinifier from 'html-minifier'
|
import htmlMinifier from 'html-minifier'
|
||||||
|
import { parse } from 'node-html-parser'
|
||||||
|
|
||||||
import { flatRoutes, isString, isUrl, promisifyRoute, waitFor } from '@nuxt/utils'
|
import { isFullStatic, flatRoutes, isString, isUrl, promisifyRoute, waitFor, TARGETS, MODES } 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.staticRoutes = path.resolve(this.options.srcDir, this.options.dir.static)
|
this.staticRoutes = path.resolve(this.options.srcDir, this.options.dir.static)
|
||||||
@ -20,19 +22,25 @@ 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
|
||||||
)
|
)
|
||||||
|
this.generatedRoutes = new Set()
|
||||||
}
|
}
|
||||||
|
|
||||||
async generate ({ build = true, init = true } = {}) {
|
async generate ({ build = true, init = true } = {}) {
|
||||||
consola.debug('Initializing generator...')
|
consola.debug('Initializing generator...')
|
||||||
|
|
||||||
await this.initiate({ build, init })
|
await this.initiate({ build, init })
|
||||||
|
|
||||||
consola.debug('Preparing routes for generate...')
|
// 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...')
|
||||||
const routes = await this.initRoutes()
|
const routes = await this.initRoutes()
|
||||||
|
|
||||||
consola.info('Generating pages')
|
consola.info('Generating pages')
|
||||||
|
|
||||||
const errors = await this.generateRoutes(routes)
|
const errors = await this.generateRoutes(routes)
|
||||||
|
|
||||||
await this.afterGenerate()
|
await this.afterGenerate()
|
||||||
@ -56,6 +64,22 @@ export default class Generator {
|
|||||||
|
|
||||||
// Start build process
|
// Start build process
|
||||||
await this.builder.build()
|
await this.builder.build()
|
||||||
|
this.isFullStatic = isFullStatic(this.options)
|
||||||
|
} else {
|
||||||
|
const hasBuilt = await fsExtra.exists(this.srcBuiltPath)
|
||||||
|
if (!hasBuilt) {
|
||||||
|
throw new Error(
|
||||||
|
`No build files found in ${this.srcBuiltPath}.\nPlease run \`nuxt build --target static\` before calling \`nuxt export\``
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const config = this.getBuildConfig()
|
||||||
|
if (config.target !== TARGETS.static) {
|
||||||
|
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
|
||||||
@ -67,7 +91,7 @@ export default class Generator {
|
|||||||
async initRoutes (...args) {
|
async initRoutes (...args) {
|
||||||
// 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.mode === MODES.universal && this.options.router.mode !== 'hash') {
|
||||||
try {
|
try {
|
||||||
generateRoutes = await promisifyRoute(
|
generateRoutes = await promisifyRoute(
|
||||||
this.options.generate.routes || [],
|
this.options.generate.routes || [],
|
||||||
@ -78,14 +102,14 @@ export default class Generator {
|
|||||||
throw e // eslint-disable-line no-unreachable
|
throw e // eslint-disable-line no-unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Generate only index.html for router.mode = 'hash'
|
let routes = []
|
||||||
let routes =
|
// Generate only index.html for router.mode = 'hash' or client-side apps
|
||||||
this.options.router.mode === 'hash'
|
if (this.options.mode === MODES.spa || this.options.router.mode === 'hash') {
|
||||||
? ['/']
|
routes = ['/']
|
||||||
: flatRoutes(this.options.router.routes)
|
} else {
|
||||||
|
routes = flatRoutes(this.getAppRoutes())
|
||||||
routes = routes.filter(route => this.options.generate.exclude.every(regex => !regex.test(route)))
|
}
|
||||||
|
routes = routes.filter(route => this.shouldGenerateRoute(route))
|
||||||
routes = this.decorateWithPayloads(routes, generateRoutes)
|
routes = this.decorateWithPayloads(routes, generateRoutes)
|
||||||
|
|
||||||
// extendRoutes hook
|
// extendRoutes hook
|
||||||
@ -94,14 +118,34 @@ export default class Generator {
|
|||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldGenerateRoute (route) {
|
||||||
|
return this.options.generate.exclude.every((regex) => {
|
||||||
|
if (typeof regex === 'string') {
|
||||||
|
return regex !== route
|
||||||
|
}
|
||||||
|
return !regex.test(route)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getBuildConfig () {
|
||||||
|
return require(path.join(this.options.buildDir, 'nuxt/config.json'))
|
||||||
|
}
|
||||||
|
|
||||||
|
getAppRoutes () {
|
||||||
|
return require(path.join(this.options.buildDir, 'routes.json'))
|
||||||
|
}
|
||||||
|
|
||||||
async generateRoutes (routes) {
|
async generateRoutes (routes) {
|
||||||
const errors = []
|
const errors = []
|
||||||
|
|
||||||
|
this.routes = routes
|
||||||
|
// Add routes to the tracked generated routes (for crawler)
|
||||||
|
this.routes.forEach(({ route }) => this.generatedRoutes.add(route))
|
||||||
// Start generate process
|
// Start generate process
|
||||||
while (routes.length) {
|
while (this.routes.length) {
|
||||||
let n = 0
|
let n = 0
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
routes
|
this.routes
|
||||||
.splice(0, this.options.generate.concurrency)
|
.splice(0, this.options.generate.concurrency)
|
||||||
.map(async ({ route, payload }) => {
|
.map(async ({ route, payload }) => {
|
||||||
await waitFor(n++ * this.options.generate.interval)
|
await waitFor(n++ * this.options.generate.interval)
|
||||||
@ -123,12 +167,12 @@ export default class Generator {
|
|||||||
const isHandled = type === 'handled'
|
const isHandled = type === 'handled'
|
||||||
const color = isHandled ? 'yellow' : 'red'
|
const color = isHandled ? 'yellow' : 'red'
|
||||||
|
|
||||||
let line = Chalk[color](` ${route}\n\n`)
|
let line = chalk[color](` ${route}\n\n`)
|
||||||
|
|
||||||
if (isHandled) {
|
if (isHandled) {
|
||||||
line += Chalk.grey(JSON.stringify(error, undefined, 2) + '\n')
|
line += chalk.grey(JSON.stringify(error, undefined, 2) + '\n')
|
||||||
} else {
|
} else {
|
||||||
line += Chalk.grey(error.stack || error.message || `${error}`)
|
line += chalk.grey(error.stack || error.message || `${error}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return line
|
return line
|
||||||
@ -153,7 +197,10 @@ export default class Generator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render and write the SPA template to the fallback path
|
// Render and write the SPA template to the fallback path
|
||||||
let { html } = await this.nuxt.server.renderRoute('/', { spa: true })
|
let { html } = await this.nuxt.server.renderRoute('/', {
|
||||||
|
spa: true,
|
||||||
|
staticAssetsBase: this.staticAssetsBase
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
html = this.minifyHtml(html)
|
html = this.minifyHtml(html)
|
||||||
@ -162,20 +209,27 @@ export default class Generator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await fsExtra.writeFile(fallbackPath, html, 'utf8')
|
await fsExtra.writeFile(fallbackPath, html, 'utf8')
|
||||||
|
consola.success('Client-side fallback created: `' + fallback + '`')
|
||||||
}
|
}
|
||||||
|
|
||||||
async initDist () {
|
async initDist () {
|
||||||
// Clean destination folder
|
// Clean destination folder
|
||||||
await fsExtra.remove(this.distPath)
|
await fsExtra.remove(this.distPath)
|
||||||
|
|
||||||
|
consola.info(`Generating output directory: ${path.basename(this.distPath)}/`)
|
||||||
await this.nuxt.callHook('generate:distRemoved', this)
|
await this.nuxt.callHook('generate:distRemoved', this)
|
||||||
|
|
||||||
// Copy static and built files
|
// Copy static and built files
|
||||||
if (await fsExtra.exists(this.staticRoutes)) {
|
if (await fsExtra.exists(this.staticRoutes)) {
|
||||||
await fsExtra.copy(this.staticRoutes, this.distPath)
|
await fsExtra.copy(this.staticRoutes, this.distPath)
|
||||||
}
|
}
|
||||||
|
// Copy .nuxt/dist/client/ to dist/_nuxt/
|
||||||
await fsExtra.copy(this.srcBuiltPath, this.distNuxtPath)
|
await fsExtra.copy(this.srcBuiltPath, this.distNuxtPath)
|
||||||
|
|
||||||
|
if (this.payloadDir) {
|
||||||
|
await fsExtra.ensureDir(this.payloadDir)
|
||||||
|
}
|
||||||
|
|
||||||
// Add .nojekyll file to let GitHub Pages add the _nuxt/ folder
|
// Add .nojekyll file to let GitHub Pages add the _nuxt/ folder
|
||||||
// https://help.github.com/articles/files-that-start-with-an-underscore-are-missing/
|
// https://help.github.com/articles/files-that-start-with-an-underscore-are-missing/
|
||||||
const nojekyllPath = path.resolve(this.distPath, '.nojekyll')
|
const nojekyllPath = path.resolve(this.distPath, '.nojekyll')
|
||||||
@ -207,11 +261,34 @@ export default class Generator {
|
|||||||
const pageErrors = []
|
const pageErrors = []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await this.nuxt.server.renderRoute(route, {
|
const renderContext = {
|
||||||
_generate: true,
|
payload,
|
||||||
payload
|
staticAssetsBase: this.staticAssetsBase
|
||||||
|
}
|
||||||
|
const res = await this.nuxt.server.renderRoute(route, renderContext)
|
||||||
|
html = res.html
|
||||||
|
|
||||||
|
// If crawler activated and called from generateRoutes()
|
||||||
|
if (this.options.generate.crawler && this.options.render.ssr) {
|
||||||
|
parse(html).querySelectorAll('a').map((el) => {
|
||||||
|
const href = (el.getAttribute('href') || '').split('?')[0].split('#')[0].trim()
|
||||||
|
|
||||||
|
if (href.startsWith('/') && this.shouldGenerateRoute(href) && !this.generatedRoutes.has(href)) {
|
||||||
|
this.generatedRoutes.add(href) // add the route to the tracked list
|
||||||
|
this.routes.push({ route: href })
|
||||||
|
}
|
||||||
})
|
})
|
||||||
;({ html } = res)
|
}
|
||||||
|
|
||||||
|
// Save Static Assets
|
||||||
|
if (this.staticAssetsDir && renderContext.staticAssets) {
|
||||||
|
for (const asset of renderContext.staticAssets) {
|
||||||
|
const assetPath = path.join(this.staticAssetsDir, asset.path)
|
||||||
|
await fsExtra.ensureDir(path.dirname(assetPath))
|
||||||
|
await fsExtra.writeFile(assetPath, asset.src, 'utf-8')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
pageErrors.push({ type: 'handled', route, error: res.error })
|
pageErrors.push({ type: 'handled', route, error: res.error })
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,12 @@ export const createNuxt = () => ({
|
|||||||
renderRoute: jest.fn(() => ({ html: 'rendered html' }))
|
renderRoute: jest.fn(() => ({ html: 'rendered html' }))
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
mode: 'universal',
|
||||||
srcDir: '/var/nuxt/src',
|
srcDir: '/var/nuxt/src',
|
||||||
buildDir: '/var/nuxt/build',
|
buildDir: '/var/nuxt/build',
|
||||||
generate: { dir: '/var/nuxt/generate' },
|
generate: { dir: '/var/nuxt/generate' },
|
||||||
build: { publicPath: '__public' },
|
build: { publicPath: '__public' },
|
||||||
dir: { static: '/var/nuxt/static' }
|
dir: { static: '/var/nuxt/static' },
|
||||||
|
render: {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -76,6 +76,8 @@ describe('generator: initialize', () => {
|
|||||||
const generator = new Generator(nuxt, builder)
|
const generator = new Generator(nuxt, builder)
|
||||||
|
|
||||||
generator.initDist = jest.fn()
|
generator.initDist = jest.fn()
|
||||||
|
fsExtra.exists.mockReturnValueOnce(true)
|
||||||
|
generator.getBuildConfig = jest.fn(() => ({ ssr: true, target: 'static' }))
|
||||||
|
|
||||||
await generator.initiate({ build: false, init: false })
|
await generator.initiate({ build: false, init: false })
|
||||||
|
|
||||||
@ -87,7 +89,7 @@ describe('generator: initialize', () => {
|
|||||||
expect(generator.initDist).not.toBeCalled()
|
expect(generator.initDist).not.toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should init routes with generate.routes and router.routes', async () => {
|
test('should init routes with generate.routes and routes.json', async () => {
|
||||||
const nuxt = createNuxt()
|
const nuxt = createNuxt()
|
||||||
nuxt.options = {
|
nuxt.options = {
|
||||||
...nuxt.options,
|
...nuxt.options,
|
||||||
@ -97,14 +99,14 @@ describe('generator: initialize', () => {
|
|||||||
routes: ['/foo', '/foo/bar']
|
routes: ['/foo', '/foo/bar']
|
||||||
},
|
},
|
||||||
router: {
|
router: {
|
||||||
mode: 'history',
|
mode: 'history'
|
||||||
routes: ['/index', '/about', '/test']
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const generator = new Generator(nuxt)
|
const generator = new Generator(nuxt)
|
||||||
|
|
||||||
flatRoutes.mockImplementationOnce(routes => routes)
|
flatRoutes.mockImplementationOnce(routes => routes)
|
||||||
promisifyRoute.mockImplementationOnce(routes => routes)
|
promisifyRoute.mockImplementationOnce(routes => routes)
|
||||||
|
generator.getAppRoutes = jest.fn(() => ['/index', '/about', '/test'])
|
||||||
generator.decorateWithPayloads = jest.fn(() => 'decoratedRoutes')
|
generator.decorateWithPayloads = jest.fn(() => 'decoratedRoutes')
|
||||||
|
|
||||||
const routes = await generator.initRoutes()
|
const routes = await generator.initRoutes()
|
||||||
@ -130,8 +132,7 @@ describe('generator: initialize', () => {
|
|||||||
routes: ['/foo', '/foo/bar']
|
routes: ['/foo', '/foo/bar']
|
||||||
},
|
},
|
||||||
router: {
|
router: {
|
||||||
mode: 'hash',
|
mode: 'hash'
|
||||||
routes: ['/index', '/about', '/test']
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const generator = new Generator(nuxt)
|
const generator = new Generator(nuxt)
|
||||||
|
@ -43,7 +43,7 @@ describe('generator: generate route', () => {
|
|||||||
const returned = await generator.generateRoute({ route, payload, errors })
|
const returned = await generator.generateRoute({ route, payload, errors })
|
||||||
|
|
||||||
expect(nuxt.server.renderRoute).toBeCalledTimes(1)
|
expect(nuxt.server.renderRoute).toBeCalledTimes(1)
|
||||||
expect(nuxt.server.renderRoute).toBeCalledWith('/foo/', { _generate: true, payload })
|
expect(nuxt.server.renderRoute).toBeCalledWith(route, { payload })
|
||||||
expect(path.join).toBeCalledTimes(2)
|
expect(path.join).toBeCalledTimes(2)
|
||||||
expect(path.join).nthCalledWith(1, '[sep]', '/foo.html')
|
expect(path.join).nthCalledWith(1, '[sep]', '/foo.html')
|
||||||
expect(path.join).nthCalledWith(2, generator.distPath, 'join([sep], /foo.html)')
|
expect(path.join).nthCalledWith(2, generator.distPath, 'join([sep], /foo.html)')
|
||||||
@ -81,7 +81,7 @@ describe('generator: generate route', () => {
|
|||||||
const returned = await generator.generateRoute({ route, payload, errors })
|
const returned = await generator.generateRoute({ route, payload, errors })
|
||||||
|
|
||||||
expect(nuxt.server.renderRoute).toBeCalledTimes(1)
|
expect(nuxt.server.renderRoute).toBeCalledTimes(1)
|
||||||
expect(nuxt.server.renderRoute).toBeCalledWith('/foo', { _generate: true, payload })
|
expect(nuxt.server.renderRoute).toBeCalledWith('/foo', { payload })
|
||||||
expect(nuxt.callHook).toBeCalledTimes(1)
|
expect(nuxt.callHook).toBeCalledTimes(1)
|
||||||
expect(nuxt.callHook).toBeCalledWith('generate:routeFailed', {
|
expect(nuxt.callHook).toBeCalledWith('generate:routeFailed', {
|
||||||
route,
|
route,
|
||||||
|
@ -7,7 +7,6 @@ export default async function renderAndGetWindow (
|
|||||||
{
|
{
|
||||||
loadedCallback,
|
loadedCallback,
|
||||||
loadingTimeout = 2000,
|
loadingTimeout = 2000,
|
||||||
ssr,
|
|
||||||
globals
|
globals
|
||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
@ -49,9 +48,7 @@ export default async function renderAndGetWindow (
|
|||||||
const { window } = await jsdom.JSDOM.fromURL(url, options)
|
const { window } = await jsdom.JSDOM.fromURL(url, options)
|
||||||
|
|
||||||
// If Nuxt could not be loaded (error from the server-side)
|
// If Nuxt could not be loaded (error from the server-side)
|
||||||
const nuxtExists = window.document.body.innerHTML.includes(
|
const nuxtExists = window.document.body.innerHTML.includes(`id="${globals.id}"`)
|
||||||
ssr ? `window.${globals.context}` : `<div id="${globals.id}">`
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!nuxtExists) {
|
if (!nuxtExists) {
|
||||||
const error = new Error('Could not load the nuxt app')
|
const error = new Error('Could not load the nuxt app')
|
||||||
|
@ -2,7 +2,7 @@ import generateETag from 'etag'
|
|||||||
import fresh from 'fresh'
|
import fresh from 'fresh'
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
|
|
||||||
import { getContext } from '@nuxt/utils'
|
import { getContext, TARGETS } from '@nuxt/utils'
|
||||||
|
|
||||||
export default ({ options, nuxt, renderRoute, resources }) => async function nuxtMiddleware (req, res, next) {
|
export default ({ options, nuxt, renderRoute, resources }) => async function nuxtMiddleware (req, res, next) {
|
||||||
// Get context
|
// Get context
|
||||||
@ -28,7 +28,7 @@ export default ({ options, nuxt, renderRoute, resources }) => async function nux
|
|||||||
preloadFiles
|
preloadFiles
|
||||||
} = result
|
} = result
|
||||||
|
|
||||||
if (redirected) {
|
if (redirected && context.target !== TARGETS.static) {
|
||||||
await nuxt.callHook('render:routeDone', url, result, context)
|
await nuxt.callHook('render:routeDone', url, result, context)
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
|
@ -320,13 +320,11 @@ export default class Server {
|
|||||||
renderAndGetWindow (url, opts = {}, {
|
renderAndGetWindow (url, opts = {}, {
|
||||||
loadingTimeout = 2000,
|
loadingTimeout = 2000,
|
||||||
loadedCallback = this.globals.loadedCallback,
|
loadedCallback = this.globals.loadedCallback,
|
||||||
ssr = this.options.render.ssr,
|
|
||||||
globals = this.globals
|
globals = this.globals
|
||||||
} = {}) {
|
} = {}) {
|
||||||
return renderAndGetWindow(url, opts, {
|
return renderAndGetWindow(url, opts, {
|
||||||
loadingTimeout,
|
loadingTimeout,
|
||||||
loadedCallback,
|
loadedCallback,
|
||||||
ssr,
|
|
||||||
globals
|
globals
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
9
packages/utils/src/constants.js
Normal file
9
packages/utils/src/constants.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export const TARGETS = {
|
||||||
|
server: 'server',
|
||||||
|
static: 'static'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MODES = {
|
||||||
|
universal: 'universal',
|
||||||
|
spa: 'spa'
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import { TARGETS } from './constants'
|
||||||
|
|
||||||
export const getContext = function getContext (req, res) {
|
export const getContext = function getContext (req, res) {
|
||||||
return { req, res }
|
return { req, res }
|
||||||
@ -14,3 +15,7 @@ export const determineGlobals = function determineGlobals (globalName, globals)
|
|||||||
}
|
}
|
||||||
return _globals
|
return _globals
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isFullStatic = function (options) {
|
||||||
|
return !options.dev && !options._legacyGenerate && options.target === TARGETS.static && options.render.ssr
|
||||||
|
}
|
||||||
|
@ -8,3 +8,4 @@ export * from './task'
|
|||||||
export * from './timer'
|
export * from './timer'
|
||||||
export * from './cjs'
|
export * from './cjs'
|
||||||
export * from './modern'
|
export * from './modern'
|
||||||
|
export * from './constants'
|
||||||
|
@ -9,6 +9,7 @@ import * as task from '../src/task'
|
|||||||
import * as timer from '../src/timer'
|
import * as timer from '../src/timer'
|
||||||
import * as cjs from '../src/cjs'
|
import * as cjs from '../src/cjs'
|
||||||
import * as modern from '../src/modern'
|
import * as modern from '../src/modern'
|
||||||
|
import * as constants from '../src/constants'
|
||||||
|
|
||||||
describe('util: entry', () => {
|
describe('util: entry', () => {
|
||||||
test('should export all methods from utils folder', () => {
|
test('should export all methods from utils folder', () => {
|
||||||
@ -22,7 +23,8 @@ describe('util: entry', () => {
|
|||||||
...task,
|
...task,
|
||||||
...timer,
|
...timer,
|
||||||
...cjs,
|
...cjs,
|
||||||
...modern
|
...modern,
|
||||||
|
...constants
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -5,11 +5,14 @@ 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',
|
||||||
|
'jsonp.js',
|
||||||
'router.js',
|
'router.js',
|
||||||
'router.scrollBehavior.js',
|
'router.scrollBehavior.js',
|
||||||
|
'routes.json',
|
||||||
'server.js',
|
'server.js',
|
||||||
'utils.js',
|
'utils.js',
|
||||||
'empty.js',
|
'empty.js',
|
||||||
|
@ -4,7 +4,8 @@ import Vue from 'vue'
|
|||||||
'getMatchedComponentsInstances',
|
'getMatchedComponentsInstances',
|
||||||
'getChildrenComponentInstancesUsingFetch',
|
'getChildrenComponentInstancesUsingFetch',
|
||||||
'promisify',
|
'promisify',
|
||||||
'globalHandleError'
|
'globalHandleError',
|
||||||
|
'urlJoin'
|
||||||
] : [],
|
] : [],
|
||||||
...features.layouts ? [
|
...features.layouts ? [
|
||||||
'sanitizeComponent'
|
'sanitizeComponent'
|
||||||
@ -57,6 +58,7 @@ export default {
|
|||||||
domProps: {
|
domProps: {
|
||||||
id: '__layout'
|
id: '__layout'
|
||||||
},
|
},
|
||||||
|
|
||||||
key: this.layoutName
|
key: this.layoutName
|
||||||
}, [layoutEl])
|
}, [layoutEl])
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
@ -110,8 +112,8 @@ export default {
|
|||||||
created () {
|
created () {
|
||||||
// Add this.$nuxt in child instances
|
// Add this.$nuxt in child instances
|
||||||
Vue.prototype.<%= globals.nuxt %> = this
|
Vue.prototype.<%= globals.nuxt %> = this
|
||||||
// add to window so we can listen when ready
|
|
||||||
if (process.client) {
|
if (process.client) {
|
||||||
|
// add to window so we can listen when ready
|
||||||
window.<%= globals.nuxt %> = <%= (globals.nuxt !== '$nuxt' ? 'window.$nuxt = ' : '') %>this
|
window.<%= globals.nuxt %> = <%= (globals.nuxt !== '$nuxt' ? 'window.$nuxt = ' : '') %>this
|
||||||
<% if (features.clientOnline) { %>
|
<% if (features.clientOnline) { %>
|
||||||
this.refreshOnlineStatus()
|
this.refreshOnlineStatus()
|
||||||
@ -125,10 +127,22 @@ export default {
|
|||||||
// Add $nuxt.context
|
// Add $nuxt.context
|
||||||
this.context = this.$options.context
|
this.context = this.$options.context
|
||||||
},
|
},
|
||||||
<% if (loading) { %>
|
<% if (loading || isFullStatic) { %>
|
||||||
mounted () {
|
async mounted () {
|
||||||
this.$loading = this.$refs.loading
|
<% if (loading) { %>this.$loading = this.$refs.loading<% } %>
|
||||||
|
<% if (isFullStatic) {%>
|
||||||
|
if (this.isPreview) {
|
||||||
|
if (this.$store && this.$store._actions.nuxtServerInit) {
|
||||||
|
<% if (loading) { %>this.$loading.start()<% } %>
|
||||||
|
await app.$store.dispatch('nuxtServerInit', this.context)
|
||||||
|
}
|
||||||
|
await this.refresh()
|
||||||
|
<% if (loading) { %>this.$loading.finish()<% } %>
|
||||||
|
}
|
||||||
|
<% } %>
|
||||||
},
|
},
|
||||||
|
<% } %>
|
||||||
|
<% if (loading) { %>
|
||||||
watch: {
|
watch: {
|
||||||
'nuxt.err': 'errorChanged'
|
'nuxt.err': 'errorChanged'
|
||||||
},
|
},
|
||||||
@ -139,10 +153,13 @@ export default {
|
|||||||
return !this.isOnline
|
return !this.isOnline
|
||||||
},
|
},
|
||||||
<% if (features.fetch) { %>
|
<% if (features.fetch) { %>
|
||||||
isFetching() {
|
isFetching () {
|
||||||
return this.nbFetching > 0
|
return this.nbFetching > 0
|
||||||
}
|
},<% } %>
|
||||||
<% } %>
|
<% if (nuxtOptions.target === 'static') { %>
|
||||||
|
isPreview () {
|
||||||
|
return Boolean(this.$options.previewData)
|
||||||
|
},<% } %>
|
||||||
},
|
},
|
||||||
<% } %>
|
<% } %>
|
||||||
methods: {
|
methods: {
|
||||||
@ -257,7 +274,7 @@ export default {
|
|||||||
return this.<%= globals.nuxt %>.error({ statusCode: 500, message: e.message })
|
return this.<%= globals.nuxt %>.error({ statusCode: 500, message: e.message })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
setLayout (layout) {
|
setLayout (layout) {
|
||||||
<% if (debug) { %>
|
<% if (debug) { %>
|
||||||
@ -277,9 +294,27 @@ export default {
|
|||||||
layout = 'default'
|
layout = 'default'
|
||||||
}
|
}
|
||||||
return Promise.resolve(layouts['_' + layout])
|
return Promise.resolve(layouts['_' + layout])
|
||||||
}
|
},
|
||||||
<% } /* splitChunks.layouts */ %>
|
<% } /* splitChunks.layouts */ %>
|
||||||
<% } /* features.layouts */ %>
|
<% } /* features.layouts */ %>
|
||||||
|
<% if (isFullStatic) { %>
|
||||||
|
setPagePayload(payload) {
|
||||||
|
this._pagePayload = payload
|
||||||
|
this._payloadFetchIndex = 0
|
||||||
|
},
|
||||||
|
async fetchPayload(route) {
|
||||||
|
route = (route.replace(/\/$/, '') || '/').split('?')[0]
|
||||||
|
try {
|
||||||
|
const src = urlJoin(window.__NUXT_STATIC__, route, 'payload.js')
|
||||||
|
const payload = await window.__NUXT_IMPORT__(route, src)
|
||||||
|
this.setPagePayload(payload)
|
||||||
|
return payload
|
||||||
|
} catch (err) {
|
||||||
|
this.setPagePayload(false)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<% } %>
|
||||||
},
|
},
|
||||||
<% if (loading) { %>
|
<% if (loading) { %>
|
||||||
components: {
|
components: {
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
import { createApp<% if (features.layouts) { %>, NuxtError<% } %> } from './index.js'
|
import { createApp<% if (features.layouts) { %>, NuxtError<% } %> } from './index.js'
|
||||||
<% if (features.fetch) { %>import fetchMixin from './mixins/fetch.client'<% } %>
|
<% if (features.fetch) { %>import fetchMixin from './mixins/fetch.client'<% } %>
|
||||||
import NuxtLink from './components/nuxt-link.<%= features.clientPrefetch ? "client" : "server" %>.js' // should be included after ./index.js
|
import NuxtLink from './components/nuxt-link.<%= features.clientPrefetch ? "client" : "server" %>.js' // should be included after ./index.js
|
||||||
|
<% if (isFullStatic) { %>import './jsonp'<% } %>
|
||||||
|
|
||||||
<% if (features.fetch) { %>
|
<% if (features.fetch) { %>
|
||||||
// Fetch mixin
|
// Fetch mixin
|
||||||
@ -136,7 +137,7 @@ function mapTransitions (toComponents, to, from) {
|
|||||||
return mergedTransitions
|
return mergedTransitions
|
||||||
}
|
}
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if (loading) { %>async <% } %>function loadAsyncComponents (to, from, next) {
|
async function loadAsyncComponents (to, from, next) {
|
||||||
// Check if route changed (this._routeChanged), only if the page is not an error (for validate())
|
// Check if route changed (this._routeChanged), only if the page is not an error (for validate())
|
||||||
this._routeChanged = Boolean(app.nuxt.err) || from.name !== to.name
|
this._routeChanged = Boolean(app.nuxt.err) || from.name !== to.name
|
||||||
this._paramChanged = !this._routeChanged && from.path !== to.path
|
this._paramChanged = !this._routeChanged && from.path !== to.path
|
||||||
@ -150,7 +151,6 @@ function mapTransitions (toComponents, to, from) {
|
|||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
try {
|
try {
|
||||||
<% if (loading) { %>
|
|
||||||
if (this._queryChanged) {
|
if (this._queryChanged) {
|
||||||
const Components = await resolveRouteComponents(
|
const Components = await resolveRouteComponents(
|
||||||
to,
|
to,
|
||||||
@ -170,11 +170,12 @@ function mapTransitions (toComponents, to, from) {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
<% if (loading) { %>
|
||||||
if (startLoader && this.$loading.start && !this.$loading.manual) {
|
if (startLoader && this.$loading.start && !this.$loading.manual) {
|
||||||
this.$loading.start()
|
this.$loading.start()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
<% } %>
|
<% } %>
|
||||||
|
}
|
||||||
// Call next()
|
// Call next()
|
||||||
next()
|
next()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -429,7 +430,7 @@ async function render (to, from, next) {
|
|||||||
<% if (features.asyncData || features.fetch) { %>
|
<% if (features.asyncData || features.fetch) { %>
|
||||||
let instances
|
let instances
|
||||||
// Call asyncData & fetch hooks on components matched by the route.
|
// Call asyncData & fetch hooks on components matched by the route.
|
||||||
await Promise.all(Components.map((Component, i) => {
|
await Promise.all(Components.map(async (Component, i) => {
|
||||||
// Check if only children route changed
|
// Check if only children route changed
|
||||||
Component._path = compile(to.matched[matches[i]].path)(to.params)
|
Component._path = compile(to.matched[matches[i]].path)(to.params)
|
||||||
Component._dataRefresh = false
|
Component._dataRefresh = false
|
||||||
@ -484,8 +485,24 @@ async function render (to, from, next) {
|
|||||||
<% if (features.asyncData) { %>
|
<% if (features.asyncData) { %>
|
||||||
// Call asyncData(context)
|
// Call asyncData(context)
|
||||||
if (hasAsyncData) {
|
if (hasAsyncData) {
|
||||||
|
<% if (isFullStatic) { %>
|
||||||
|
let promise
|
||||||
|
|
||||||
|
if (this.isPreview) {
|
||||||
|
promise = promisify(Component.options.asyncData, app.context)
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const payload = await this.fetchPayload(to.path).then((payloadData) => payloadData.data[i])
|
||||||
|
promise = Promise.resolve(payload)
|
||||||
|
} catch (err) {
|
||||||
|
// fallback
|
||||||
|
promise = promisify(Component.options.asyncData, app.context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<% } else { %>
|
||||||
const promise = promisify(Component.options.asyncData, app.context)
|
const promise = promisify(Component.options.asyncData, app.context)
|
||||||
.then((asyncDataResult) => {
|
<% } %>
|
||||||
|
promise.then((asyncDataResult) => {
|
||||||
applyAsyncData(Component, asyncDataResult)
|
applyAsyncData(Component, asyncDataResult)
|
||||||
<% if (loading) { %>
|
<% if (loading) { %>
|
||||||
if (this.$loading.increase) {
|
if (this.$loading.increase) {
|
||||||
@ -501,6 +518,12 @@ async function render (to, from, next) {
|
|||||||
this.$loading.manual = Component.options.loading === false
|
this.$loading.manual = Component.options.loading === false
|
||||||
|
|
||||||
<% if (features.fetch) { %>
|
<% if (features.fetch) { %>
|
||||||
|
<% if (isFullStatic) { %>
|
||||||
|
if (!this.isPreview) {
|
||||||
|
// Catching the error here for letting the SPA fallback and normal fetch behaviour
|
||||||
|
promises.push(this.fetchPayload(to.path).catch(err => null))
|
||||||
|
}
|
||||||
|
<% } %>
|
||||||
// Call fetch(context)
|
// Call fetch(context)
|
||||||
if (hasFetch) {
|
if (hasFetch) {
|
||||||
let p = Component.options.fetch(app.context)
|
let p = Component.options.fetch(app.context)
|
||||||
@ -768,7 +791,7 @@ function addHotReload ($component, depth) {
|
|||||||
}
|
}
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<% if (features.layouts || features.transitions) { %>async <% } %>function mountApp (__app) {
|
async function mountApp (__app) {
|
||||||
// Set global variables
|
// Set global variables
|
||||||
app = __app.app
|
app = __app.app
|
||||||
router = __app.router
|
router = __app.router
|
||||||
@ -777,6 +800,16 @@ function addHotReload ($component, depth) {
|
|||||||
// Create Vue instance
|
// Create Vue instance
|
||||||
const _app = new Vue(app)
|
const _app = new Vue(app)
|
||||||
|
|
||||||
|
<% if (isFullStatic) { %>
|
||||||
|
// Load page chunk
|
||||||
|
if (!NUXT.data && !NUXT.spa) {
|
||||||
|
try {
|
||||||
|
const payload = await _app.fetchPayload(_app.context.route.path)
|
||||||
|
Object.assign(NUXT, payload)
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
<% } %>
|
||||||
|
|
||||||
<% if (features.layouts && mode !== 'spa') { %>
|
<% if (features.layouts && mode !== 'spa') { %>
|
||||||
// Load layout
|
// Load layout
|
||||||
const layout = NUXT.layout || 'default'
|
const layout = NUXT.layout || 'default'
|
||||||
@ -825,6 +858,10 @@ function addHotReload ($component, depth) {
|
|||||||
router.beforeEach(loadAsyncComponents.bind(_app))
|
router.beforeEach(loadAsyncComponents.bind(_app))
|
||||||
router.beforeEach(render.bind(_app))
|
router.beforeEach(render.bind(_app))
|
||||||
|
|
||||||
|
// Fix in static: remove trailing slash to force hydration
|
||||||
|
if (process.static && NUXT.serverRendered && NUXT.routePath !== '/' && NUXT.routePath.slice(-1) !== '/' && _app.context.route.path.slice(-1) === '/') {
|
||||||
|
_app.context.route.path = _app.context.route.path.replace(/\/+$/, '')
|
||||||
|
}
|
||||||
// If page already is server rendered and it was done on the same route path as client side render
|
// If page already is server rendered and it was done on the same route path as client side render
|
||||||
if (NUXT.serverRendered && NUXT.routePath === _app.context.route.path) {
|
if (NUXT.serverRendered && NUXT.routePath === _app.context.route.path) {
|
||||||
mount()
|
mount()
|
||||||
@ -839,6 +876,8 @@ function addHotReload ($component, depth) {
|
|||||||
mount()
|
mount()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fix: force next tick to avoid having same timestamp when an error happen on spa fallback
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 0))
|
||||||
render.call(_app, router.currentRoute, router.currentRoute, (path) => {
|
render.call(_app, router.currentRoute, router.currentRoute, (path) => {
|
||||||
// If not redirected
|
// If not redirected
|
||||||
if (!path) {
|
if (!path) {
|
||||||
|
@ -43,7 +43,7 @@ export default {
|
|||||||
meta: [
|
meta: [
|
||||||
{
|
{
|
||||||
name: 'viewport',
|
name: 'viewport',
|
||||||
content: 'width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no'
|
content: 'width=device-width,initial-scale=1.0,minimum-scale=1.0'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,12 @@ export default {
|
|||||||
}<% } %>
|
}<% } %>
|
||||||
},
|
},
|
||||||
shouldPrefetch () {
|
shouldPrefetch () {
|
||||||
return this.getPrefetchComponents().length > 0
|
<% if (isFullStatic && router.prefetchPayloads) { %>
|
||||||
|
const ref = this.$router.resolve(this.to, this.$route, this.append)
|
||||||
|
const Components = ref.resolved.matched.map(r => r.components.default)
|
||||||
|
|
||||||
|
return Components.filter(Component => ref.href || (typeof Component === 'function' && !Component.options && !Component.__prefetched)).length
|
||||||
|
<% } else { %>return this.getPrefetchComponents().length > 0<% } %>
|
||||||
},
|
},
|
||||||
canPrefetch () {
|
canPrefetch () {
|
||||||
const conn = navigator.connection
|
const conn = navigator.connection
|
||||||
@ -101,7 +106,15 @@ export default {
|
|||||||
<% if (router.linkPrefetchedClass) { %>promises.push(componentOrPromise)<% } %>
|
<% if (router.linkPrefetchedClass) { %>promises.push(componentOrPromise)<% } %>
|
||||||
}
|
}
|
||||||
Component.__prefetched = true
|
Component.__prefetched = true
|
||||||
}<% if (router.linkPrefetchedClass) { %>
|
}
|
||||||
|
<% if (isFullStatic && router.prefetchPayloads) { %>
|
||||||
|
// Preload the data only if not in preview mode
|
||||||
|
if (!this.$root.isPreview) {
|
||||||
|
const { href } = this.$router.resolve(this.to, this.$route, this.append)
|
||||||
|
this.$nuxt.fetchPayload(href).catch(() => {})
|
||||||
|
}
|
||||||
|
<% } %>
|
||||||
|
<% if (router.linkPrefetchedClass) { %>
|
||||||
return Promise.all(promises).then(() => this.addPrefetchedClass())
|
return Promise.all(promises).then(() => this.addPrefetchedClass())
|
||||||
<% } %>
|
<% } %>
|
||||||
}<% if (router.linkPrefetchedClass) { %>,
|
}<% if (router.linkPrefetchedClass) { %>,
|
||||||
|
@ -210,6 +210,13 @@ async function createApp (ssrContext) {
|
|||||||
}
|
}
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
|
// Add enablePreview(previewData = {}) in context for plugins
|
||||||
|
if (process.static && process.client) {
|
||||||
|
app.context.enablePreview = function (previewData = {}) {
|
||||||
|
app.previewData = Object.assign({}, previewData)
|
||||||
|
inject('preview', previewData)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Plugin execution
|
// Plugin execution
|
||||||
<%= isTest ? '/* eslint-disable camelcase */' : '' %>
|
<%= isTest ? '/* eslint-disable camelcase */' : '' %>
|
||||||
<% plugins.forEach((plugin) => { %>
|
<% plugins.forEach((plugin) => { %>
|
||||||
@ -228,6 +235,12 @@ async function createApp (ssrContext) {
|
|||||||
<% } %>
|
<% } %>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
<%= isTest ? '/* eslint-enable camelcase */' : '' %>
|
<%= isTest ? '/* eslint-enable camelcase */' : '' %>
|
||||||
|
// Lock enablePreview in context
|
||||||
|
if (process.static && process.client) {
|
||||||
|
app.context.enablePreview = function () {
|
||||||
|
console.warn('You cannot call enablePreview() outside a plugin.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If server-side, wait for async component to be resolved first
|
// If server-side, wait for async component to be resolved first
|
||||||
if (process.server && ssrContext && ssrContext.url) {
|
if (process.server && ssrContext && ssrContext.url) {
|
||||||
|
80
packages/vue-app/template/jsonp.js
Normal file
80
packages/vue-app/template/jsonp.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
const chunks = {} // chunkId => exports
|
||||||
|
const chunksInstalling = {} // chunkId => Promise
|
||||||
|
const failedChunks = {}
|
||||||
|
|
||||||
|
function importChunk(chunkId, src) {
|
||||||
|
// Already installed
|
||||||
|
if (chunks[chunkId]) {
|
||||||
|
return Promise.resolve(chunks[chunkId])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failed loading
|
||||||
|
if (failedChunks[chunkId]) {
|
||||||
|
return Promise.reject(failedChunks[chunkId])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Installing
|
||||||
|
if (chunksInstalling[chunkId]) {
|
||||||
|
return chunksInstalling[chunkId]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a promise in chunk cache
|
||||||
|
let resolve, reject
|
||||||
|
const promise = chunksInstalling[chunkId] = new Promise((_resolve, _reject) => {
|
||||||
|
resolve = _resolve
|
||||||
|
reject = _reject
|
||||||
|
})
|
||||||
|
|
||||||
|
// Clear chunk data from cache
|
||||||
|
delete chunks[chunkId]
|
||||||
|
|
||||||
|
// Start chunk loading
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.charset = 'utf-8'
|
||||||
|
script.timeout = 120
|
||||||
|
script.src = src
|
||||||
|
let timeout
|
||||||
|
|
||||||
|
// Create error before stack unwound to get useful stacktrace later
|
||||||
|
const error = new Error()
|
||||||
|
|
||||||
|
// Complete handlers
|
||||||
|
const onScriptComplete = script.onerror = script.onload = (event) => {
|
||||||
|
// Cleanups
|
||||||
|
clearTimeout(timeout)
|
||||||
|
delete chunksInstalling[chunkId]
|
||||||
|
|
||||||
|
// Avoid mem leaks in IE
|
||||||
|
script.onerror = script.onload = null
|
||||||
|
|
||||||
|
// Verify chunk is loaded
|
||||||
|
if (chunks[chunkId]) {
|
||||||
|
return resolve(chunks[chunkId])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Something bad happened
|
||||||
|
const errorType = event && (event.type === 'load' ? 'missing' : event.type)
|
||||||
|
const realSrc = event && event.target && event.target.src
|
||||||
|
error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'
|
||||||
|
error.name = 'ChunkLoadError'
|
||||||
|
error.type = errorType
|
||||||
|
error.request = realSrc
|
||||||
|
failedChunks[chunkId] = error
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
onScriptComplete({ type: 'timeout', target: script })
|
||||||
|
}, 120000)
|
||||||
|
|
||||||
|
// Append script
|
||||||
|
document.head.appendChild(script)
|
||||||
|
|
||||||
|
// Return promise
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__NUXT_JSONP__ = function (chunkId, exports) { chunks[chunkId] = exports }
|
||||||
|
window.__NUXT_JSONP_CACHE__ = chunks
|
||||||
|
window.__NUXT_IMPORT__ = importChunk
|
@ -32,6 +32,7 @@ function beforeMount() {
|
|||||||
|
|
||||||
function created() {
|
function created() {
|
||||||
if (!isSsrHydration(this)) {
|
if (!isSsrHydration(this)) {
|
||||||
|
<% if (isFullStatic) { %>createdFullStatic.call(this)<% } %>
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +53,33 @@ function created() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<% if (isFullStatic) { %>
|
||||||
|
function createdFullStatic() {
|
||||||
|
// Check if component has been fetched on server
|
||||||
|
let fetchedOnServer = this.$options.fetchOnServer !== false
|
||||||
|
if (typeof this.$options.fetchOnServer === 'function') {
|
||||||
|
fetchedOnServer = this.$options.fetchOnServer.call(this) !== false
|
||||||
|
}
|
||||||
|
if (!fetchedOnServer || this.$nuxt.isPreview || !this.$nuxt._pagePayload) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._hydrated = true
|
||||||
|
this._fetchKey = this.$nuxt._payloadFetchIndex++
|
||||||
|
const data = this.$nuxt._pagePayload.fetch[this._fetchKey]
|
||||||
|
|
||||||
|
// If fetch error
|
||||||
|
if (data && data._error) {
|
||||||
|
this.$fetchState.error = data._error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge data
|
||||||
|
for (const key in data) {
|
||||||
|
Vue.set(this.$data, key, data[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<% } %>
|
||||||
|
|
||||||
function $fetch() {
|
function $fetch() {
|
||||||
if (!this._fetchPromise) {
|
if (!this._fetchPromise) {
|
||||||
this._fetchPromise = $_fetch.call(this)
|
this._fetchPromise = $_fetch.call(this)
|
||||||
@ -71,6 +99,9 @@ async function $_fetch() {
|
|||||||
try {
|
try {
|
||||||
await this.$options.fetch.call(this)
|
await this.$options.fetch.call(this)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (process.dev) {
|
||||||
|
console.error('Error in fetch():', err)
|
||||||
|
}
|
||||||
error = normalizeError(err)
|
error = normalizeError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,4 +116,3 @@ async function $_fetch() {
|
|||||||
|
|
||||||
this.$nextTick(() => this.<%= globals.nuxt %>.nbFetching--)
|
this.$nextTick(() => this.<%= globals.nuxt %>.nbFetching--)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,9 @@ async function serverPrefetch() {
|
|||||||
try {
|
try {
|
||||||
await this.$options.fetch.call(this)
|
await this.$options.fetch.call(this)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (process.dev) {
|
||||||
|
console.error('Error in fetch():', err)
|
||||||
|
}
|
||||||
this.$fetchState.error = normalizeError(err)
|
this.$fetchState.error = normalizeError(err)
|
||||||
}
|
}
|
||||||
this.$fetchState.pending = false
|
this.$fetchState.pending = false
|
||||||
@ -27,7 +30,7 @@ async function serverPrefetch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
beforeCreate() {
|
created() {
|
||||||
if (!hasFetch(this)) {
|
if (!hasFetch(this)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
5
packages/vue-app/template/nuxt/config.json
Normal file
5
packages/vue-app/template/nuxt/config.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<%= JSON.stringify({
|
||||||
|
isFullStatic: isFullStatic,
|
||||||
|
ssr: nuxtOptions.render.ssr,
|
||||||
|
target: nuxtOptions.target
|
||||||
|
}, null, 2) %>
|
1
packages/vue-app/template/routes.json
Normal file
1
packages/vue-app/template/routes.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
<%= JSON.stringify(router.routes, null, 2) %>
|
@ -37,9 +37,9 @@ function urlJoin () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createNext = ssrContext => (opts) => {
|
const createNext = ssrContext => (opts) => {
|
||||||
|
// If static target, render on client-side
|
||||||
ssrContext.redirected = opts
|
ssrContext.redirected = opts
|
||||||
// If nuxt generate
|
if (ssrContext.target === 'static' || !ssrContext.res) {
|
||||||
if (!ssrContext.res) {
|
|
||||||
ssrContext.nuxt.serverRendered = false
|
ssrContext.nuxt.serverRendered = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -71,8 +71,13 @@ export default async (ssrContext) => {
|
|||||||
ssrContext.next = createNext(ssrContext)
|
ssrContext.next = createNext(ssrContext)
|
||||||
// Used for beforeNuxtRender({ Components, nuxtState })
|
// Used for beforeNuxtRender({ Components, nuxtState })
|
||||||
ssrContext.beforeRenderFns = []
|
ssrContext.beforeRenderFns = []
|
||||||
// Nuxt object (window{{globals.context}}, defaults to window.__NUXT__)
|
// Nuxt object (window.{{globals.context}}, defaults to window.__NUXT__)
|
||||||
ssrContext.nuxt = { <% if (features.layouts) { %>layout: 'default', <% } %>data: [], <% if (features.fetch) { %>fetch: [], <% } %>error: null<%= (store ? ', state: null' : '') %>, serverRendered: true, routePath: '' }
|
ssrContext.nuxt = { <% if (features.layouts) { %>layout: 'default', <% } %>data: [], <% if (features.fetch) { %>fetch: [], <% } %>error: null<%= (store ? ', state: null' : '') %>, serverRendered: true, routePath: '' }
|
||||||
|
// Remove query from url is static target
|
||||||
|
if (process.static && ssrContext.url) {
|
||||||
|
ssrContext.url = ssrContext.url.split('?')[0]
|
||||||
|
}
|
||||||
|
|
||||||
// Create the app definition and the instance (created for each request)
|
// Create the app definition and the instance (created for each request)
|
||||||
const { app, router<%= (store ? ', store' : '') %> } = await createApp(ssrContext)
|
const { app, router<%= (store ? ', store' : '') %> } = await createApp(ssrContext)
|
||||||
const _app = new Vue(app)
|
const _app = new Vue(app)
|
||||||
@ -100,6 +105,10 @@ export default async (ssrContext) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderErrorPage = async () => {
|
const renderErrorPage = async () => {
|
||||||
|
// Don't server-render the page in static target
|
||||||
|
if (ssrContext.target === 'static') {
|
||||||
|
ssrContext.nuxt.serverRendered = false
|
||||||
|
}
|
||||||
<% if (features.layouts) { %>
|
<% if (features.layouts) { %>
|
||||||
// Load layout for error page
|
// Load layout for error page
|
||||||
const layout = (NuxtError.options || NuxtError).layout
|
const layout = (NuxtError.options || NuxtError).layout
|
||||||
@ -245,10 +254,6 @@ export default async (ssrContext) => {
|
|||||||
|
|
||||||
// ...If .validate() returned false
|
// ...If .validate() returned false
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
// Don't server-render the page in generate mode
|
|
||||||
if (ssrContext._generate) {
|
|
||||||
ssrContext.nuxt.serverRendered = false
|
|
||||||
}
|
|
||||||
// Render a 404 error page
|
// Render a 404 error page
|
||||||
return render404Page()
|
return render404Page()
|
||||||
}
|
}
|
||||||
|
@ -156,10 +156,10 @@ export async function setContext (app, context) {
|
|||||||
env: <%= JSON.stringify(env) %><%= isTest ? '// eslint-disable-line' : '' %>
|
env: <%= JSON.stringify(env) %><%= isTest ? '// eslint-disable-line' : '' %>
|
||||||
}
|
}
|
||||||
// Only set once
|
// Only set once
|
||||||
if (context.req) {
|
if (!process.static && context.req) {
|
||||||
app.context.req = context.req
|
app.context.req = context.req
|
||||||
}
|
}
|
||||||
if (context.res) {
|
if (!process.static && context.res) {
|
||||||
app.context.res = context.res
|
app.context.res = context.res
|
||||||
}
|
}
|
||||||
if (context.ssrContext) {
|
if (context.ssrContext) {
|
||||||
@ -642,3 +642,11 @@ export function addLifecycleHook(vm, hook, fn) {
|
|||||||
vm.$options[hook].push(fn)
|
vm.$options[hook].push(fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const urlJoin = function urlJoin () {
|
||||||
|
return [].slice
|
||||||
|
.call(arguments)
|
||||||
|
.join('/')
|
||||||
|
.replace(/\/+/g, '/')
|
||||||
|
.replace(':/', '://')
|
||||||
|
}
|
||||||
|
@ -274,13 +274,15 @@ export default class VueRenderer {
|
|||||||
|
|
||||||
// Add url to the renderContext
|
// Add url to the renderContext
|
||||||
renderContext.url = url
|
renderContext.url = url
|
||||||
|
// Add target to the renderContext
|
||||||
|
renderContext.target = this.serverContext.nuxt.options.target
|
||||||
|
|
||||||
const { req = {} } = renderContext
|
const { req = {}, res = {} } = renderContext
|
||||||
|
|
||||||
// renderContext.spa
|
// renderContext.spa
|
||||||
if (renderContext.spa === undefined) {
|
if (renderContext.spa === undefined) {
|
||||||
// TODO: Remove reading from renderContext.res in Nuxt3
|
// TODO: Remove reading from renderContext.res in Nuxt3
|
||||||
renderContext.spa = !this.SSR || req.spa || (renderContext.res && renderContext.res.spa)
|
renderContext.spa = !this.SSR || req.spa || res.spa
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderContext.modern
|
// renderContext.modern
|
||||||
|
@ -3,7 +3,7 @@ import cloneDeep from 'lodash/cloneDeep'
|
|||||||
import VueMeta from 'vue-meta'
|
import VueMeta from 'vue-meta'
|
||||||
import { createRenderer } from 'vue-server-renderer'
|
import { createRenderer } from 'vue-server-renderer'
|
||||||
import LRU from 'lru-cache'
|
import LRU from 'lru-cache'
|
||||||
import { isModernRequest } from '@nuxt/utils'
|
import { TARGETS, isModernRequest } from '@nuxt/utils'
|
||||||
import BaseRenderer from './base'
|
import BaseRenderer from './base'
|
||||||
|
|
||||||
export default class SPARenderer extends BaseRenderer {
|
export default class SPARenderer extends BaseRenderer {
|
||||||
@ -27,9 +27,9 @@ export default class SPARenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async render (renderContext) {
|
async render (renderContext) {
|
||||||
const { url = '/', req = {}, _generate } = renderContext
|
const { url = '/', req = {} } = renderContext
|
||||||
const modernMode = this.options.modern
|
const modernMode = this.options.modern
|
||||||
const modern = (modernMode && _generate) || isModernRequest(req, modernMode)
|
const modern = (modernMode && this.options.target === TARGETS.static) || isModernRequest(req, modernMode)
|
||||||
const cacheKey = `${modern ? 'modern:' : 'legacy:'}${url}`
|
const cacheKey = `${modern ? 'modern:' : 'legacy:'}${url}`
|
||||||
let meta = this.cache.get(cacheKey)
|
let meta = this.cache.get(cacheKey)
|
||||||
|
|
||||||
@ -148,7 +148,12 @@ export default class SPARenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const APP = `${meta.BODY_SCRIPTS_PREPEND}<div id="${this.serverContext.globals.id}">${this.serverContext.resources.loadingHTML}</div>${meta.BODY_SCRIPTS}`
|
let APP = `${meta.BODY_SCRIPTS_PREPEND}<div id="${this.serverContext.globals.id}">${this.serverContext.resources.loadingHTML}</div>${meta.BODY_SCRIPTS}`
|
||||||
|
|
||||||
|
if (renderContext.staticAssetsBase) {
|
||||||
|
// Full static, add window.__NUXT_STATIC__
|
||||||
|
APP += `<script>window.__NUXT_STATIC__='${renderContext.staticAssetsBase}';window.${this.serverContext.globals.context}={spa:!0}</script>`
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare template params
|
// Prepare template params
|
||||||
const templateParams = {
|
const templateParams = {
|
||||||
|
@ -3,6 +3,7 @@ import crypto from 'crypto'
|
|||||||
import { format } from 'util'
|
import { format } from 'util'
|
||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
|
import { TARGETS, urlJoin } from '@nuxt/utils'
|
||||||
import devalue from '@nuxt/devalue'
|
import devalue from '@nuxt/devalue'
|
||||||
import { createBundleRenderer } from 'vue-server-renderer'
|
import { createBundleRenderer } from 'vue-server-renderer'
|
||||||
import BaseRenderer from './base'
|
import BaseRenderer from './base'
|
||||||
@ -100,7 +101,8 @@ export default class SSRRenderer extends BaseRenderer {
|
|||||||
APP = `<div id="${this.serverContext.globals.id}"></div>`
|
APP = `<div id="${this.serverContext.globals.id}"></div>`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renderContext.redirected && !renderContext._generate) {
|
// Perf: early returns if server target and redirected
|
||||||
|
if (renderContext.redirected && renderContext.target === TARGETS.server) {
|
||||||
return {
|
return {
|
||||||
html: APP,
|
html: APP,
|
||||||
error: renderContext.nuxt.error,
|
error: renderContext.nuxt.error,
|
||||||
@ -155,26 +157,66 @@ export default class SSRRenderer extends BaseRenderer {
|
|||||||
// Only add the hash if 'unsafe-inline' rule isn't present to avoid conflicts (#5387)
|
// Only add the hash if 'unsafe-inline' rule isn't present to avoid conflicts (#5387)
|
||||||
const containsUnsafeInlineScriptSrc = csp.policies && csp.policies['script-src'] && csp.policies['script-src'].includes('\'unsafe-inline\'')
|
const containsUnsafeInlineScriptSrc = csp.policies && csp.policies['script-src'] && csp.policies['script-src'].includes('\'unsafe-inline\'')
|
||||||
const shouldHashCspScriptSrc = csp && (csp.unsafeInlineCompatibility || !containsUnsafeInlineScriptSrc)
|
const shouldHashCspScriptSrc = csp && (csp.unsafeInlineCompatibility || !containsUnsafeInlineScriptSrc)
|
||||||
let serializedSession = ''
|
const inlineScripts = []
|
||||||
|
|
||||||
|
if (renderContext.staticAssetsBase) {
|
||||||
|
const preloadScripts = []
|
||||||
|
renderContext.staticAssets = []
|
||||||
|
const { staticAssetsBase, url, nuxt, staticAssets } = renderContext
|
||||||
|
const { data, fetch, ...state } = nuxt
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
const nuxtStaticScript = `window.__NUXT_STATIC__='${staticAssetsBase}';`
|
||||||
|
const stateScript = `window.${this.serverContext.globals.context}=${devalue(state)};`
|
||||||
|
|
||||||
|
// Make chunk for initial state > 10 KB
|
||||||
|
const stateScriptKb = (stateScript.length * 4 /* utf8 */) / 100
|
||||||
|
if (stateScriptKb > 10) {
|
||||||
|
const statePath = urlJoin(url, 'state.js')
|
||||||
|
const stateUrl = urlJoin(staticAssetsBase, statePath)
|
||||||
|
staticAssets.push({ path: statePath, src: stateScript })
|
||||||
|
APP += `<script defer>${nuxtStaticScript}</script>`
|
||||||
|
APP += `<script defer src="${staticAssetsBase}${statePath}"></script>`
|
||||||
|
preloadScripts.push(stateUrl)
|
||||||
|
} else {
|
||||||
|
APP += `<script defer>${nuxtStaticScript}${stateScript}</script>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page level payload.js (async loaded for CSR)
|
||||||
|
const payloadPath = urlJoin(url, 'payload.js')
|
||||||
|
const payloadUrl = urlJoin(staticAssetsBase, payloadPath)
|
||||||
|
const payloadScript = `__NUXT_JSONP__("${url}", ${devalue({ data, fetch })});`
|
||||||
|
staticAssets.push({ path: payloadPath, src: payloadScript })
|
||||||
|
preloadScripts.push(payloadUrl)
|
||||||
|
|
||||||
|
// Preload links
|
||||||
|
for (const href of preloadScripts) {
|
||||||
|
HEAD += `<link rel="preload" href="${href}" as="script">`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// Serialize state
|
// Serialize state
|
||||||
|
let serializedSession
|
||||||
if (shouldInjectScripts || shouldHashCspScriptSrc) {
|
if (shouldInjectScripts || shouldHashCspScriptSrc) {
|
||||||
// Only serialized session if need inject scripts or csp hash
|
// Only serialized session if need inject scripts or csp hash
|
||||||
serializedSession = `window.${this.serverContext.globals.context}=${devalue(renderContext.nuxt)};`
|
serializedSession = `window.${this.serverContext.globals.context}=${devalue(renderContext.nuxt)};`
|
||||||
|
inlineScripts.push(serializedSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldInjectScripts) {
|
if (shouldInjectScripts) {
|
||||||
APP += `<script>${serializedSession}</script>`
|
APP += `<script>${serializedSession}</script>`
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate CSP hashes
|
// Calculate CSP hashes
|
||||||
const cspScriptSrcHashes = []
|
const cspScriptSrcHashes = []
|
||||||
if (csp) {
|
if (csp) {
|
||||||
if (shouldHashCspScriptSrc) {
|
if (shouldHashCspScriptSrc) {
|
||||||
|
for (const script of inlineScripts) {
|
||||||
const hash = crypto.createHash(csp.hashAlgorithm)
|
const hash = crypto.createHash(csp.hashAlgorithm)
|
||||||
hash.update(serializedSession)
|
hash.update(script)
|
||||||
cspScriptSrcHashes.push(`'${csp.hashAlgorithm}-${hash.digest('base64')}'`)
|
cspScriptSrcHashes.push(`'${csp.hashAlgorithm}-${hash.digest('base64')}'`)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Call ssr:csp hook
|
// Call ssr:csp hook
|
||||||
await this.serverContext.nuxt.callHook('vue-renderer:ssr:csp', cspScriptSrcHashes)
|
await this.serverContext.nuxt.callHook('vue-renderer:ssr:csp', cspScriptSrcHashes)
|
||||||
|
@ -6,7 +6,7 @@ import webpackDevMiddleware from 'webpack-dev-middleware'
|
|||||||
import webpackHotMiddleware from 'webpack-hot-middleware'
|
import webpackHotMiddleware from 'webpack-hot-middleware'
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
|
|
||||||
import { parallel, sequence, wrapArray, isModernRequest } from '@nuxt/utils'
|
import { TARGETS, parallel, sequence, wrapArray, isModernRequest } from '@nuxt/utils'
|
||||||
import AsyncMFS from './utils/async-mfs'
|
import AsyncMFS from './utils/async-mfs'
|
||||||
|
|
||||||
import * as WebpackConfigs from './config'
|
import * as WebpackConfigs from './config'
|
||||||
@ -244,6 +244,6 @@ export class WebpackBundler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
forGenerate () {
|
forGenerate () {
|
||||||
this.buildContext.isStatic = true
|
this.buildContext.target = TARGETS.static
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,11 @@ import WebpackBar from 'webpackbar'
|
|||||||
import env from 'std-env'
|
import env from 'std-env'
|
||||||
import semver from 'semver'
|
import semver from 'semver'
|
||||||
|
|
||||||
import { isUrl, urlJoin, getPKG } from '@nuxt/utils'
|
import { TARGETS, isUrl, urlJoin, getPKG } from '@nuxt/utils'
|
||||||
|
|
||||||
import PerfLoader from '../utils/perf-loader'
|
import PerfLoader from '../utils/perf-loader'
|
||||||
import StyleLoader from '../utils/style-loader'
|
import StyleLoader from '../utils/style-loader'
|
||||||
import WarningIgnorePlugin from '../plugins/warning-ignore'
|
import WarningIgnorePlugin from '../plugins/warning-ignore'
|
||||||
|
|
||||||
import { reservedVueTags } from '../utils/reserved-tags'
|
import { reservedVueTags } from '../utils/reserved-tags'
|
||||||
|
|
||||||
export default class WebpackBaseConfig {
|
export default class WebpackBaseConfig {
|
||||||
@ -47,6 +46,10 @@ export default class WebpackBaseConfig {
|
|||||||
return this.dev ? 'development' : 'production'
|
return this.dev ? 'development' : 'production'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get target () {
|
||||||
|
return this.buildContext.target
|
||||||
|
}
|
||||||
|
|
||||||
get dev () {
|
get dev () {
|
||||||
return this.buildContext.options.dev
|
return this.buildContext.options.dev
|
||||||
}
|
}
|
||||||
@ -139,7 +142,9 @@ export default class WebpackBaseConfig {
|
|||||||
const env = {
|
const env = {
|
||||||
'process.env.NODE_ENV': JSON.stringify(this.mode),
|
'process.env.NODE_ENV': JSON.stringify(this.mode),
|
||||||
'process.mode': JSON.stringify(this.mode),
|
'process.mode': JSON.stringify(this.mode),
|
||||||
'process.static': this.buildContext.isStatic
|
'process.dev': this.dev,
|
||||||
|
'process.static': this.target === TARGETS.static,
|
||||||
|
'process.target': JSON.stringify(this.target)
|
||||||
}
|
}
|
||||||
if (this.buildContext.buildOptions.aggressiveCodeRemoval) {
|
if (this.buildContext.buildOptions.aggressiveCodeRemoval) {
|
||||||
env['typeof process'] = JSON.stringify(this.isServer ? 'object' : 'undefined')
|
env['typeof process'] = JSON.stringify(this.isServer ? 'object' : 'undefined')
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { loadFixture, Nuxt, Generator } from '../utils'
|
import { loadFixture, Nuxt, Builder, Generator } from '../utils'
|
||||||
|
|
||||||
describe('basic fail generate', () => {
|
describe('basic fail generate', () => {
|
||||||
test('Fail with routes() which throw an error', async () => {
|
test('Fail with routes() which throw an error', async () => {
|
||||||
@ -12,9 +12,11 @@ describe('basic fail generate', () => {
|
|||||||
const nuxt = new Nuxt(options)
|
const nuxt = new Nuxt(options)
|
||||||
await nuxt.ready()
|
await nuxt.ready()
|
||||||
|
|
||||||
const generator = new Generator(nuxt)
|
const builder = new Builder(nuxt)
|
||||||
|
builder.build = jest.fn()
|
||||||
|
const generator = new Generator(nuxt, builder)
|
||||||
|
|
||||||
await generator.generate({ build: false }).catch((e) => {
|
await generator.generate().catch((e) => {
|
||||||
expect(e.message).toBe('Not today!')
|
expect(e.message).toBe('Not today!')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -4,6 +4,7 @@ import { resolve } from 'path'
|
|||||||
import { remove } from 'fs-extra'
|
import { remove } from 'fs-extra'
|
||||||
import serveStatic from 'serve-static'
|
import serveStatic from 'serve-static'
|
||||||
import finalhandler from 'finalhandler'
|
import finalhandler from 'finalhandler'
|
||||||
|
import { TARGETS } from '@nuxt/utils'
|
||||||
import { Builder, Generator, getPort, loadFixture, Nuxt, rp, listPaths, equalOrStartsWith } from '../utils'
|
import { Builder, Generator, getPort, loadFixture, Nuxt, rp, listPaths, equalOrStartsWith } from '../utils'
|
||||||
|
|
||||||
let port
|
let port
|
||||||
@ -19,7 +20,12 @@ let changedFileName
|
|||||||
|
|
||||||
describe('basic generate', () => {
|
describe('basic generate', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const config = await loadFixture('basic', { generate: { dir: '.nuxt-generate' } })
|
const config = await loadFixture('basic', {
|
||||||
|
generate: {
|
||||||
|
static: false,
|
||||||
|
dir: '.nuxt-generate'
|
||||||
|
}
|
||||||
|
})
|
||||||
const nuxt = new Nuxt(config)
|
const nuxt = new Nuxt(config)
|
||||||
await nuxt.ready()
|
await nuxt.ready()
|
||||||
|
|
||||||
@ -47,7 +53,7 @@ describe('basic generate', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Check builder', () => {
|
test('Check builder', () => {
|
||||||
expect(builder.bundleBuilder.buildContext.isStatic).toBe(true)
|
expect(builder.bundleBuilder.buildContext.target).toBe(TARGETS.static)
|
||||||
expect(builder.build).toHaveBeenCalledTimes(1)
|
expect(builder.build).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -167,10 +173,9 @@ describe('basic generate', () => {
|
|||||||
test('/validate should not be server-rendered', async () => {
|
test('/validate should not be server-rendered', async () => {
|
||||||
const { body: html } = await rp(url('/validate'))
|
const { body: html } = await rp(url('/validate'))
|
||||||
expect(html).toContain('<div id="__nuxt"></div>')
|
expect(html).toContain('<div id="__nuxt"></div>')
|
||||||
expect(html).toContain('serverRendered:!1')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/validate -> should display a 404', async () => {
|
test.posix('/validate -> should display a 404', async () => {
|
||||||
const window = await generator.nuxt.server.renderAndGetWindow(url('/validate'))
|
const window = await generator.nuxt.server.renderAndGetWindow(url('/validate'))
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
expect(html).toContain('This page could not be found')
|
expect(html).toContain('This page could not be found')
|
||||||
@ -185,7 +190,6 @@ describe('basic generate', () => {
|
|||||||
test('/redirect should not be server-rendered', async () => {
|
test('/redirect should not be server-rendered', async () => {
|
||||||
const { body: html } = await rp(url('/redirect'))
|
const { body: html } = await rp(url('/redirect'))
|
||||||
expect(html).toContain('<div id="__nuxt"></div>')
|
expect(html).toContain('<div id="__nuxt"></div>')
|
||||||
expect(html).toContain('serverRendered:!1')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/redirect -> check redirected source', async () => {
|
test('/redirect -> check redirected source', async () => {
|
||||||
@ -204,6 +208,21 @@ describe('basic generate', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('nuxt re-generating with no subfolders', async () => {
|
||||||
|
generator.nuxt.options.generate.subFolders = false
|
||||||
|
generator.getAppRoutes = jest.fn(() => [])
|
||||||
|
await expect(generator.generate()).resolves.toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('/users/1.html', async () => {
|
||||||
|
const { body } = await rp(url('/users/1.html'))
|
||||||
|
expect(body).toContain('<h1>User: 1</h1>')
|
||||||
|
expect(existsSync(resolve(distDir, 'users/1.html'))).toBe(true)
|
||||||
|
expect(
|
||||||
|
existsSync(resolve(distDir, 'users/1/index.html'))
|
||||||
|
).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
test('/-ignored', async () => {
|
test('/-ignored', async () => {
|
||||||
await expect(rp(url('/-ignored'))).rejects.toMatchObject({
|
await expect(rp(url('/-ignored'))).rejects.toMatchObject({
|
||||||
response: {
|
response: {
|
||||||
|
@ -11,6 +11,7 @@ describe('generator', () => {
|
|||||||
const nuxt = new Nuxt(config)
|
const nuxt = new Nuxt(config)
|
||||||
await nuxt.ready()
|
await nuxt.ready()
|
||||||
const generator = new Generator(nuxt)
|
const generator = new Generator(nuxt)
|
||||||
|
generator.getAppRoutes = jest.fn(() => [])
|
||||||
const routes = await generator.initRoutes()
|
const routes = await generator.initRoutes()
|
||||||
|
|
||||||
expect(routes.length).toBe(array.length)
|
expect(routes.length).toBe(array.length)
|
||||||
@ -31,6 +32,8 @@ describe('generator', () => {
|
|||||||
const nuxt = new Nuxt(config)
|
const nuxt = new Nuxt(config)
|
||||||
await nuxt.ready()
|
await nuxt.ready()
|
||||||
const generator = new Generator(nuxt)
|
const generator = new Generator(nuxt)
|
||||||
|
generator.getAppRoutes = jest.fn(() => [])
|
||||||
|
|
||||||
const routes = await generator.initRoutes()
|
const routes = await generator.initRoutes()
|
||||||
|
|
||||||
expect(routes.length).toBe(array.length)
|
expect(routes.length).toBe(array.length)
|
||||||
@ -50,6 +53,7 @@ describe('generator', () => {
|
|||||||
const nuxt = new Nuxt(config)
|
const nuxt = new Nuxt(config)
|
||||||
await nuxt.ready()
|
await nuxt.ready()
|
||||||
const generator = new Generator(nuxt)
|
const generator = new Generator(nuxt)
|
||||||
|
generator.getAppRoutes = jest.fn(() => [])
|
||||||
const array = ['/1', '/2', '/3', '/4']
|
const array = ['/1', '/2', '/3', '/4']
|
||||||
const routes = await generator.initRoutes(array)
|
const routes = await generator.initRoutes(array)
|
||||||
|
|
||||||
@ -70,6 +74,7 @@ describe('generator', () => {
|
|||||||
const nuxt = new Nuxt(config)
|
const nuxt = new Nuxt(config)
|
||||||
await nuxt.ready()
|
await nuxt.ready()
|
||||||
const generator = new Generator(nuxt)
|
const generator = new Generator(nuxt)
|
||||||
|
generator.getAppRoutes = jest.fn(() => [])
|
||||||
const array = ['/1', '/2', '/3', '/4']
|
const array = ['/1', '/2', '/3', '/4']
|
||||||
const routes = await generator.initRoutes(...array)
|
const routes = await generator.initRoutes(...array)
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
|
import { MODES } from '@nuxt/utils'
|
||||||
import { Nuxt } from '../utils'
|
import { Nuxt } from '../utils'
|
||||||
|
|
||||||
const NO_BUILD_MSG = /Use either `nuxt build` or `builder\.build\(\)` or start nuxt in development mode/
|
const NO_BUILD_MSG = /Use either `nuxt build` or `builder\.build\(\)` or start nuxt in development mode/
|
||||||
@ -12,7 +13,7 @@ describe('renderer', () => {
|
|||||||
test('detect no-build (Universal)', async () => {
|
test('detect no-build (Universal)', async () => {
|
||||||
const nuxt = new Nuxt({
|
const nuxt = new Nuxt({
|
||||||
_start: true,
|
_start: true,
|
||||||
mode: 'universal',
|
mode: MODES.universal,
|
||||||
dev: false,
|
dev: false,
|
||||||
buildDir: '/path/to/404'
|
buildDir: '/path/to/404'
|
||||||
})
|
})
|
||||||
@ -25,7 +26,7 @@ describe('renderer', () => {
|
|||||||
test('detect no-build (SPA)', async () => {
|
test('detect no-build (SPA)', async () => {
|
||||||
const nuxt = new Nuxt({
|
const nuxt = new Nuxt({
|
||||||
_start: true,
|
_start: true,
|
||||||
mode: 'spa',
|
mode: MODES.spa,
|
||||||
dev: false,
|
dev: false,
|
||||||
buildDir: '/path/to/404'
|
buildDir: '/path/to/404'
|
||||||
})
|
})
|
||||||
@ -37,7 +38,7 @@ describe('renderer', () => {
|
|||||||
test('detect no-modern-build', async () => {
|
test('detect no-modern-build', async () => {
|
||||||
const nuxt = new Nuxt({
|
const nuxt = new Nuxt({
|
||||||
_start: true,
|
_start: true,
|
||||||
mode: 'universal',
|
mode: MODES.universal,
|
||||||
modern: 'client',
|
modern: 'client',
|
||||||
dev: false,
|
dev: false,
|
||||||
buildDir: '/path/to/404'
|
buildDir: '/path/to/404'
|
||||||
|
@ -20,7 +20,7 @@ describe('nuxt minimal vue-app bundle size limit', () => {
|
|||||||
it('should stay within the size limit range', async () => {
|
it('should stay within the size limit range', async () => {
|
||||||
const filter = filename => filename === 'vue-app.nuxt.js'
|
const filter = filename => filename === 'vue-app.nuxt.js'
|
||||||
const legacyResourcesSize = await getResourcesSize(distDir, 'client', { filter })
|
const legacyResourcesSize = await getResourcesSize(distDir, 'client', { filter })
|
||||||
const LEGACY_JS_RESOURCES_KB_SIZE = 15.7
|
const LEGACY_JS_RESOURCES_KB_SIZE = 16.2
|
||||||
expect(legacyResourcesSize.uncompressed).toBeWithinSize(LEGACY_JS_RESOURCES_KB_SIZE)
|
expect(legacyResourcesSize.uncompressed).toBeWithinSize(LEGACY_JS_RESOURCES_KB_SIZE)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -8690,6 +8690,13 @@ node-gyp@^5.0.2:
|
|||||||
tar "^4.4.12"
|
tar "^4.4.12"
|
||||||
which "^1.3.1"
|
which "^1.3.1"
|
||||||
|
|
||||||
|
node-html-parser@^1.2.4:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.2.4.tgz#bff5b403da3c5061d189e922aafb193c8e1f6f92"
|
||||||
|
integrity sha512-qHwPdGyGr9pOZBoSgUOuNPG20QYZVN00lFcxKQgjPUODSxVH7obQeLVVawa3B4cfSNtLIeczSzoy/xYA8XG5WQ==
|
||||||
|
dependencies:
|
||||||
|
he "1.1.1"
|
||||||
|
|
||||||
node-int64@^0.4.0:
|
node-int64@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||||
|
Loading…
Reference in New Issue
Block a user