mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 06:05:11 +00:00
refactor core into sub-packages (#4202)
This commit is contained in:
parent
4503d42d54
commit
39b558f59c
@ -26,26 +26,22 @@ jobs:
|
|||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn --frozen-lockfile --non-interactive
|
command: yarn --frozen-lockfile --non-interactive
|
||||||
|
|
||||||
# Link
|
|
||||||
- run:
|
|
||||||
name: Link
|
|
||||||
command: yarn lerna link
|
|
||||||
|
|
||||||
# Save cache
|
# Save cache
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: yarn-{{ checksum "yarn.lock" }}
|
key: yarn-{{ checksum "yarn.lock" }}
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- node_modules
|
||||||
- distributions/*/node_modules
|
|
||||||
- packages/*/node_modules
|
- packages/*/node_modules
|
||||||
|
- distributions/*/node_modules
|
||||||
|
|
||||||
# Persist workspace
|
# Persist workspace
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: ~/project
|
root: ~/project
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- node_modules
|
||||||
- distributions/*/node_modules
|
|
||||||
- packages/*/node_modules
|
- packages/*/node_modules
|
||||||
|
- distributions/*/node_modules
|
||||||
|
- packages/*/dist
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Phase 2: Lint + Audit + Build Nuxt and fixtures
|
# Phase 2: Lint + Audit + Build Nuxt and fixtures
|
||||||
@ -76,18 +72,13 @@ jobs:
|
|||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: ~/project
|
at: ~/project
|
||||||
- run:
|
|
||||||
name: Build Nuxt
|
|
||||||
command: yarn build
|
|
||||||
- run:
|
- run:
|
||||||
name: Build Fixtures
|
name: Build Fixtures
|
||||||
command: yarn build && yarn test:fixtures -w=4 --coverage && yarn coverage
|
command: yarn test:fixtures -w=4 --coverage && yarn coverage
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: ~/project
|
root: ~/project
|
||||||
paths:
|
paths:
|
||||||
- test/fixtures # TODO
|
- test/fixtures
|
||||||
- distributions/**/dist
|
|
||||||
- packages/**/dist
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Phase 3: Unit and E2E tests
|
# Phase 3: Unit and E2E tests
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
app
|
# Common
|
||||||
!app/store.js
|
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
.nuxt
|
.nuxt
|
||||||
examples/coffeescript/pages/index.vue
|
|
||||||
!examples/storybook/.storybook
|
|
||||||
coverage
|
coverage
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
## cofeescript
|
||||||
|
examples/coffeescript/pages/index.vue
|
||||||
|
|
||||||
|
# Packages
|
||||||
|
|
||||||
|
# vue-app
|
||||||
|
packages/vue-app/template
|
||||||
|
!packages/vue-app/template/store.js
|
||||||
|
@ -19,10 +19,6 @@ steps:
|
|||||||
yarn
|
yarn
|
||||||
displayName: 'Install dependencies'
|
displayName: 'Install dependencies'
|
||||||
|
|
||||||
- script: |
|
|
||||||
yarn build
|
|
||||||
displayName: 'Build Nuxt'
|
|
||||||
|
|
||||||
- script: |
|
- script: |
|
||||||
yarn test:fixtures -w=2
|
yarn test:fixtures -w=2
|
||||||
displayName: 'Test: Build Fixtures'
|
displayName: 'Test: Build Fixtures'
|
||||||
|
@ -15,20 +15,20 @@ test.before(async () => {
|
|||||||
}
|
}
|
||||||
nuxt = new Nuxt(config)
|
nuxt = new Nuxt(config)
|
||||||
await new Builder(nuxt).build()
|
await new Builder(nuxt).build()
|
||||||
await nuxt.listen(4000, 'localhost')
|
await nuxt.server.listen(4000, 'localhost')
|
||||||
}, 30000)
|
}, 30000)
|
||||||
|
|
||||||
// Example of testing only generated html
|
// Example of testing only generated html
|
||||||
test('Route / exits and render HTML', async (t) => {
|
test('Route / exits and render HTML', async (t) => {
|
||||||
const context = {}
|
const context = {}
|
||||||
const { html } = await nuxt.renderRoute('/', context)
|
const { html } = await nuxt.server.renderRoute('/', context)
|
||||||
t.true(html.includes('<h1 class="red">Hello world!</h1>'))
|
t.true(html.includes('<h1 class="red">Hello world!</h1>'))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Example of testing via dom checking
|
// Example of testing via dom checking
|
||||||
test('Route / exits and render HTML with CSS applied', async (t) => {
|
test('Route / exits and render HTML with CSS applied', async (t) => {
|
||||||
const context = {}
|
const context = {}
|
||||||
const { html } = await nuxt.renderRoute('/', context)
|
const { html } = await nuxt.server.renderRoute('/', context)
|
||||||
const { window } = new JSDOM(html).window
|
const { window } = new JSDOM(html).window
|
||||||
const element = window.document.querySelector('.red')
|
const element = window.document.querySelector('.red')
|
||||||
t.not(element, null)
|
t.not(element, null)
|
||||||
|
@ -5,8 +5,8 @@ const server = http.createServer(this.nuxt.renderer.app)
|
|||||||
const io = socketIO(server)
|
const io = socketIO(server)
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
// overwrite nuxt.listen()
|
// overwrite nuxt.server.listen()
|
||||||
this.nuxt.listen = (port, host) => new Promise(resolve => server.listen(port || 3000, host || 'localhost', resolve))
|
this.nuxt.server.listen = (port, host) => new Promise(resolve => server.listen(port || 3000, host || 'localhost', resolve))
|
||||||
// close this server on 'close' event
|
// close this server on 'close' event
|
||||||
this.nuxt.hook('close', () => new Promise(server.close))
|
this.nuxt.hook('close', () => new Promise(server.close))
|
||||||
|
|
||||||
|
@ -14,32 +14,29 @@ module.exports = {
|
|||||||
coverageDirectory: './coverage',
|
coverageDirectory: './coverage',
|
||||||
|
|
||||||
collectCoverageFrom: [
|
collectCoverageFrom: [
|
||||||
'packages/*/src/**/*.js',
|
'**/packages/*/src/**/*.js'
|
||||||
'packages/cli/bin/*'
|
|
||||||
],
|
],
|
||||||
|
|
||||||
coveragePathIgnorePatterns: [
|
coveragePathIgnorePatterns: [
|
||||||
'node_modules',
|
'node_modules/(?!(@nuxt|nuxt))',
|
||||||
'packages/app',
|
'packages/webpack/src/config/plugins/vue'
|
||||||
'packages/webpack/plugins/vue'
|
|
||||||
],
|
],
|
||||||
|
|
||||||
testPathIgnorePatterns: [
|
testPathIgnorePatterns: [
|
||||||
'node_modules',
|
'node_modules/(?!(@nuxt|nuxt))',
|
||||||
'test/fixtures/.*/.*?/',
|
'test/fixtures/.*/.*?/',
|
||||||
'examples/.*'
|
'examples/.*'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
transformIgnorePatterns: [
|
||||||
|
'node_modules/(?!(@nuxt|nuxt))'
|
||||||
|
],
|
||||||
|
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.js$': 'babel-jest',
|
'^.+\\.js$': 'babel-jest',
|
||||||
'.*\\.(vue)$': 'vue-jest'
|
'.*\\.(vue)$': 'vue-jest'
|
||||||
},
|
},
|
||||||
|
|
||||||
transformIgnorePatterns: [
|
|
||||||
'/node_modules/',
|
|
||||||
'/dist/'
|
|
||||||
],
|
|
||||||
|
|
||||||
moduleFileExtensions: [
|
moduleFileExtensions: [
|
||||||
'js',
|
'js',
|
||||||
'json'
|
'json'
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"clean:build": "rimraf distributions/*/dist packages/*/dist",
|
"clean:build": "rimraf distributions/*/dist packages/*/dist",
|
||||||
"clean:examples": "rimraf examples/*/dist examples/*/.nuxt",
|
"clean:examples": "rimraf examples/*/dist examples/*/.nuxt",
|
||||||
"clean:test": "rimraf test/fixtures/*/dist test/fixtures/*/.nuxt*",
|
"clean:test": "rimraf test/fixtures/*/dist test/fixtures/*/.nuxt*",
|
||||||
"dev": "yarn build --watch",
|
"dev": "node -r esm ./scripts/dev",
|
||||||
"coverage": "codecov",
|
"coverage": "codecov",
|
||||||
"lint": "eslint --ext .js,.mjs,.vue .",
|
"lint": "eslint --ext .js,.mjs,.vue .",
|
||||||
"lint:app": "eslint-multiplexer eslint --ignore-path packages/app/template/.eslintignore 'test/fixtures/!(missing-plugin)/.nuxt!(-dev)/**' | eslint-multiplexer -b",
|
"lint:app": "eslint-multiplexer eslint --ignore-path packages/app/template/.eslintignore 'test/fixtures/!(missing-plugin)/.nuxt!(-dev)/**' | eslint-multiplexer -b",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"test:e2e": "jest -i test/e2e",
|
"test:e2e": "jest -i test/e2e",
|
||||||
"test:lint": "yarn lint",
|
"test:lint": "yarn lint",
|
||||||
"test:unit": "jest test/unit",
|
"test:unit": "jest test/unit",
|
||||||
"postinstall": "lerna link && node -r esm ./scripts/dev"
|
"postinstall": "lerna link && yarn dev"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.1.2",
|
"@babel/core": "^7.1.2",
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
],
|
],
|
||||||
"main": "dist/builder.js",
|
"main": "dist/builder.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/app": "^2.2.0",
|
|
||||||
"@nuxt/common": "^2.2.0",
|
"@nuxt/common": "^2.2.0",
|
||||||
|
"@nuxt/vue-app": "^2.2.0",
|
||||||
"@nuxtjs/devalue": "^1.0.1",
|
"@nuxtjs/devalue": "^1.0.1",
|
||||||
"chokidar": "^2.0.4",
|
"chokidar": "^2.0.4",
|
||||||
"consola": "^1.4.4",
|
"consola": "^1.4.4",
|
||||||
|
@ -20,8 +20,6 @@ import values from 'lodash/values'
|
|||||||
import devalue from '@nuxtjs/devalue'
|
import devalue from '@nuxtjs/devalue'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Options,
|
|
||||||
BuildContext,
|
|
||||||
r,
|
r,
|
||||||
wp,
|
wp,
|
||||||
wChunk,
|
wChunk,
|
||||||
@ -33,6 +31,8 @@ import {
|
|||||||
isString
|
isString
|
||||||
} from '@nuxt/common'
|
} from '@nuxt/common'
|
||||||
|
|
||||||
|
import BuildContext from './context'
|
||||||
|
|
||||||
const glob = pify(Glob)
|
const glob = pify(Glob)
|
||||||
|
|
||||||
export default class Builder {
|
export default class Builder {
|
||||||
@ -69,7 +69,7 @@ export default class Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve template
|
// Resolve template
|
||||||
this.template = this.options.build.template || '@nuxt/app'
|
this.template = this.options.build.template || '@nuxt/vue-app'
|
||||||
if (typeof this.template === 'string') {
|
if (typeof this.template === 'string') {
|
||||||
this.template = this.nuxt.resolver.requireModule(this.template)
|
this.template = this.nuxt.resolver.requireModule(this.template)
|
||||||
}
|
}
|
||||||
@ -476,16 +476,16 @@ export default class Builder {
|
|||||||
consola.success('Nuxt files generated')
|
consola.success('Nuxt files generated')
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove ignore when generateConfig enabled again
|
// TODO: Uncomment when generateConfig enabled again
|
||||||
async generateConfig() /* istanbul ignore next */ {
|
// async generateConfig() /* istanbul ignore next */ {
|
||||||
const config = path.resolve(this.options.buildDir, 'build.config.js')
|
// const config = path.resolve(this.options.buildDir, 'build.config.js')
|
||||||
const options = omit(this.options, Options.unsafeKeys)
|
// const options = omit(this.options, Options.unsafeKeys)
|
||||||
await fsExtra.writeFile(
|
// await fsExtra.writeFile(
|
||||||
config,
|
// config,
|
||||||
`export default ${JSON.stringify(options, null, ' ')}`,
|
// `export default ${JSON.stringify(options, null, ' ')}`,
|
||||||
'utf8'
|
// 'utf8'
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
watchClient() {
|
watchClient() {
|
||||||
const src = this.options.srcDir
|
const src = this.options.srcDir
|
||||||
|
@ -48,9 +48,9 @@ export default {
|
|||||||
})
|
})
|
||||||
.then(() => oldInstance && oldInstance.nuxt.close())
|
.then(() => oldInstance && oldInstance.nuxt.close())
|
||||||
// Start listening
|
// Start listening
|
||||||
.then(() => nuxt.listen())
|
.then(() => nuxt.server.listen())
|
||||||
// Show ready message first time, others will be shown through WebpackBar
|
// Show ready message first time, others will be shown through WebpackBar
|
||||||
.then(() => !oldInstance && nuxt.showReady(false))
|
.then(() => !oldInstance && nuxt.server.showReady(false))
|
||||||
.then(() => builder.watchServer())
|
.then(() => builder.watchServer())
|
||||||
// Handle errors
|
// Handle errors
|
||||||
.catch(err => errorHandler(err, { builder, nuxt }))
|
.catch(err => errorHandler(err, { builder, nuxt }))
|
||||||
|
@ -45,8 +45,8 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nuxt.listen().then(() => {
|
return nuxt.server.listen().then(() => {
|
||||||
nuxt.showReady(false)
|
nuxt.server.showReady(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import consola from 'consola'
|
|||||||
import esm from 'esm'
|
import esm from 'esm'
|
||||||
import wrapAnsi from 'wrap-ansi'
|
import wrapAnsi from 'wrap-ansi'
|
||||||
import defaultsDeep from 'lodash/defaultsDeep'
|
import defaultsDeep from 'lodash/defaultsDeep'
|
||||||
import { server as nuxtServerConfig } from '@nuxt/config'
|
import { getDefaultNuxtConfig } from '@nuxt/config'
|
||||||
|
|
||||||
const _require = esm(module, {
|
const _require = esm(module, {
|
||||||
cache: false,
|
cache: false,
|
||||||
@ -58,7 +58,7 @@ export async function loadNuxtConfig(argv) {
|
|||||||
port: argv.port || undefined,
|
port: argv.port || undefined,
|
||||||
host: argv.hostname || undefined,
|
host: argv.hostname || undefined,
|
||||||
socket: argv['unix-socket'] || undefined
|
socket: argv['unix-socket'] || undefined
|
||||||
}, options.server || {}, nuxtServerConfig(process.env))
|
}, options.server || {}, getDefaultNuxtConfig().server)
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
@ -72,11 +72,11 @@ export function indentLines(string, spaces, firstLineSpaces) {
|
|||||||
let s = ''
|
let s = ''
|
||||||
if (lines.length) {
|
if (lines.length) {
|
||||||
const i0 = indent(firstLineSpaces === undefined ? spaces : firstLineSpaces)
|
const i0 = indent(firstLineSpaces === undefined ? spaces : firstLineSpaces)
|
||||||
s = i0 + lines.shift()
|
s = i0 + lines.shift().trim()
|
||||||
}
|
}
|
||||||
if (lines.length) {
|
if (lines.length) {
|
||||||
const i = indent(spaces)
|
const i = indent(spaces)
|
||||||
s += '\n' + lines.map(l => i + l).join('\n')
|
s += '\n' + lines.map(l => i + l.trim()).join('\n')
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
exports[`cli/command builds help text 1`] = `
|
exports[`cli/command builds help text 1`] = `
|
||||||
" Usage: nuxt this is how you do it [options]
|
" Usage: nuxt this is how you do it [options]
|
||||||
|
|
||||||
a very long description that is longer than 80 chars and should wrap to the next
|
a very long description that is longer than 80 chars and should wrap to the next
|
||||||
line while keeping indentation
|
line while keeping indentation
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@ -16,7 +16,7 @@ exports[`cli/command builds help text 1`] = `
|
|||||||
--port, -p Port number on which to start the application
|
--port, -p Port number on which to start the application
|
||||||
--hostname, -H Hostname on which to start the application
|
--hostname, -H Hostname on which to start the application
|
||||||
--unix-socket, -n Path to a UNIX socket
|
--unix-socket, -n Path to a UNIX socket
|
||||||
--foo very long option that is longer than 80 chars and should wrap
|
--foo very long option that is longer than 80 chars and should wrap
|
||||||
to the next line while keeping indentation
|
to the next line while keeping indentation
|
||||||
|
|
||||||
"
|
"
|
||||||
|
@ -22,8 +22,8 @@ describe('dev', () => {
|
|||||||
expect(consola.error).not.toHaveBeenCalled()
|
expect(consola.error).not.toHaveBeenCalled()
|
||||||
|
|
||||||
expect(Builder.prototype.build).toHaveBeenCalled()
|
expect(Builder.prototype.build).toHaveBeenCalled()
|
||||||
expect(Nuxt.prototype.listen).toHaveBeenCalled()
|
expect(Nuxt.prototype.server.listen).toHaveBeenCalled()
|
||||||
expect(Nuxt.prototype.showReady).toHaveBeenCalled()
|
expect(Nuxt.prototype.server.showReady).toHaveBeenCalled()
|
||||||
expect(Builder.prototype.watchServer).toHaveBeenCalled()
|
expect(Builder.prototype.watchServer).toHaveBeenCalled()
|
||||||
|
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
@ -37,8 +37,8 @@ describe('dev', () => {
|
|||||||
expect(Builder.prototype.unwatch).toHaveBeenCalled()
|
expect(Builder.prototype.unwatch).toHaveBeenCalled()
|
||||||
expect(Builder.prototype.build).toHaveBeenCalled()
|
expect(Builder.prototype.build).toHaveBeenCalled()
|
||||||
expect(Nuxt.prototype.close).toHaveBeenCalled()
|
expect(Nuxt.prototype.close).toHaveBeenCalled()
|
||||||
expect(Nuxt.prototype.listen).toHaveBeenCalled()
|
expect(Nuxt.prototype.server.listen).toHaveBeenCalled()
|
||||||
expect(Nuxt.prototype.showReady).not.toHaveBeenCalled()
|
expect(Nuxt.prototype.server.showReady).not.toHaveBeenCalled()
|
||||||
expect(Builder.prototype.watchServer).toHaveBeenCalled()
|
expect(Builder.prototype.watchServer).toHaveBeenCalled()
|
||||||
|
|
||||||
expect(consola.error).not.toHaveBeenCalled()
|
expect(consola.error).not.toHaveBeenCalled()
|
||||||
@ -97,9 +97,11 @@ describe('dev', () => {
|
|||||||
|
|
||||||
test('catches error on startDev', async () => {
|
test('catches error on startDev', async () => {
|
||||||
mockNuxt({
|
mockNuxt({
|
||||||
listen: jest.fn().mockImplementation(() => {
|
server: {
|
||||||
throw new Error('Listen Error')
|
listen: jest.fn().mockImplementation(() => {
|
||||||
})
|
throw new Error('Listen Error')
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
mockBuilder()
|
mockBuilder()
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { server as nuxtServerConfig } from '@nuxt/config'
|
import { getDefaultNuxtConfig } from '@nuxt/config'
|
||||||
import { consola } from '../utils'
|
import { consola } from '../utils'
|
||||||
import * as utils from '../../src/utils'
|
import * as utils from '../../src/utils'
|
||||||
|
|
||||||
@ -81,14 +81,14 @@ describe('cli/utils', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('nuxtServerConfig: server env', () => {
|
test('nuxtServerConfig: server env', () => {
|
||||||
const options = {
|
const options = getDefaultNuxtConfig({
|
||||||
server: nuxtServerConfig({
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
HOST: 'env-host',
|
HOST: 'env-host',
|
||||||
PORT: 3003,
|
PORT: 3003,
|
||||||
UNIX_SOCKET: '/var/run/env.sock'
|
UNIX_SOCKET: '/var/run/env.sock'
|
||||||
})
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
expect(options.server.host).toBe('env-host')
|
expect(options.server.host).toBe('env-host')
|
||||||
expect(options.server.port).toBe(3003)
|
expect(options.server.port).toBe(3003)
|
||||||
|
@ -66,8 +66,10 @@ export const mockGetNuxtStart = (ssr) => {
|
|||||||
ssr
|
ssr
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
listen,
|
server: {
|
||||||
showReady
|
listen,
|
||||||
|
showReady
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return { listen, showReady }
|
return { listen, showReady }
|
||||||
@ -89,8 +91,10 @@ export const mockNuxt = (implementation) => {
|
|||||||
},
|
},
|
||||||
clearHook: jest.fn(),
|
clearHook: jest.fn(),
|
||||||
close: jest.fn(),
|
close: jest.fn(),
|
||||||
listen: jest.fn().mockImplementationOnce(() => Promise.resolve()),
|
server: {
|
||||||
showReady: jest.fn().mockImplementationOnce(() => Promise.resolve())
|
listen: jest.fn().mockImplementationOnce(() => Promise.resolve()),
|
||||||
|
showReady: jest.fn().mockImplementationOnce(() => Promise.resolve())
|
||||||
|
}
|
||||||
}, implementation || {})
|
}, implementation || {})
|
||||||
|
|
||||||
imports.core.mockImplementation(() => ({ Nuxt }))
|
imports.core.mockImplementation(() => ({ Nuxt }))
|
||||||
|
67
packages/common/src/hookable.js
Normal file
67
packages/common/src/hookable.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
|
||||||
|
import consola from 'consola'
|
||||||
|
|
||||||
|
import { sequence } from './utils'
|
||||||
|
|
||||||
|
export default class Hookable {
|
||||||
|
constructor() {
|
||||||
|
this._hooks = {}
|
||||||
|
this._deprecatedHooks = {}
|
||||||
|
|
||||||
|
this.hook = this.hook.bind(this)
|
||||||
|
this.callHook = this.callHook.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
hook(name, fn) {
|
||||||
|
if (!name || typeof fn !== 'function') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._deprecatedHooks[name]) {
|
||||||
|
consola.warn(`${name} hook has been deprecated, please use ${this._deprecatedHooks[name]}`)
|
||||||
|
name = this._deprecatedHooks[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
this._hooks[name] = this._hooks[name] || []
|
||||||
|
this._hooks[name].push(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
async callHook(name, ...args) {
|
||||||
|
if (!this._hooks[name]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
consola.debug(`Call ${name} hooks (${this._hooks[name].length})`)
|
||||||
|
try {
|
||||||
|
await sequence(this._hooks[name], fn => fn(...args))
|
||||||
|
} catch (err) {
|
||||||
|
consola.error(err)
|
||||||
|
this.callHook('error', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearHook(name) {
|
||||||
|
if (name) {
|
||||||
|
delete this._hooks[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flatHooks(configHooks, hooks = {}, parentName) {
|
||||||
|
Object.keys(configHooks).forEach((key) => {
|
||||||
|
const subHook = configHooks[key]
|
||||||
|
const name = parentName ? `${parentName}:${key}` : key
|
||||||
|
if (typeof subHook === 'object' && subHook !== null) {
|
||||||
|
this.flatHooks(subHook, hooks, name)
|
||||||
|
} else {
|
||||||
|
hooks[name] = subHook
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return hooks
|
||||||
|
}
|
||||||
|
|
||||||
|
addHooks(configHooks) {
|
||||||
|
const hooks = this.flatHooks(configHooks)
|
||||||
|
Object.keys(hooks).filter(Boolean).forEach((key) => {
|
||||||
|
[].concat(hooks[key]).forEach(h => this.hook(key, h))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,2 @@
|
|||||||
export { default as Options } from './options'
|
export { default as Hookable } from './hookable'
|
||||||
export { default as BuildContext } from './build/context'
|
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
|
@ -15,9 +15,10 @@ export const waitFor = function waitFor(ms) {
|
|||||||
return new Promise(resolve => setTimeout(resolve, ms || 0))
|
return new Promise(resolve => setTimeout(resolve, ms || 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isString = function isString(obj) {
|
export const isString = obj => typeof obj === 'string' || obj instanceof String
|
||||||
return typeof obj === 'string' || obj instanceof String
|
|
||||||
}
|
export const isNonEmptyString = obj => obj && isString(obj)
|
||||||
|
|
||||||
export const startsWithAlias = aliasArray => str => aliasArray.some(c => str.startsWith(c))
|
export const startsWithAlias = aliasArray => str => aliasArray.some(c => str.startsWith(c))
|
||||||
|
|
||||||
export const startsWithSrcAlias = startsWithAlias(['@', '~'])
|
export const startsWithSrcAlias = startsWithAlias(['@', '~'])
|
||||||
@ -404,3 +405,34 @@ export const stripWhitespace = function stripWhitespace(string) {
|
|||||||
})
|
})
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function defineAlias(src, target, prop, opts = {}) {
|
||||||
|
const { bind = true, warn = false } = opts
|
||||||
|
|
||||||
|
if (Array.isArray(prop)) {
|
||||||
|
for (const p of prop) {
|
||||||
|
defineAlias(src, target, p, opts)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetVal = target[prop]
|
||||||
|
if (bind && typeof targetVal === 'function') {
|
||||||
|
targetVal = targetVal.bind(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
let warned = false
|
||||||
|
|
||||||
|
Object.defineProperty(src, prop, {
|
||||||
|
get: () => {
|
||||||
|
if (warn && !warned) {
|
||||||
|
warned = true
|
||||||
|
consola.warn({
|
||||||
|
message: `'${prop}' is deprecated'`,
|
||||||
|
additional: new Error().stack.split('\n').splice(2).join('\n')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return targetVal
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
],
|
],
|
||||||
"main": "dist/config.js",
|
"main": "dist/config.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nuxt/common": "^2.2.0",
|
||||||
"consola": "^1.4.4",
|
"consola": "^1.4.4",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"std-env": "^2.0.2"
|
"std-env": "^2.0.2"
|
||||||
|
52
packages/config/src/config/_app.js
Normal file
52
packages/config/src/config/_app.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
export default () => ({
|
||||||
|
vue: {
|
||||||
|
config: {
|
||||||
|
silent: undefined, // = !dev
|
||||||
|
performance: undefined // = dev
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
head: {
|
||||||
|
meta: [],
|
||||||
|
link: [],
|
||||||
|
style: [],
|
||||||
|
script: []
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [],
|
||||||
|
|
||||||
|
css: [],
|
||||||
|
|
||||||
|
modules: [],
|
||||||
|
|
||||||
|
layouts: {},
|
||||||
|
|
||||||
|
ErrorPage: null,
|
||||||
|
|
||||||
|
loading: {
|
||||||
|
color: 'black',
|
||||||
|
failedColor: 'red',
|
||||||
|
height: '2px',
|
||||||
|
throttle: 200,
|
||||||
|
duration: 5000,
|
||||||
|
continuous: false,
|
||||||
|
rtl: false,
|
||||||
|
css: true
|
||||||
|
},
|
||||||
|
|
||||||
|
loadingIndicator: 'default',
|
||||||
|
|
||||||
|
transition: {
|
||||||
|
name: 'page',
|
||||||
|
mode: 'out-in',
|
||||||
|
appear: false,
|
||||||
|
appearClass: 'appear',
|
||||||
|
appearActiveClass: 'appear-active',
|
||||||
|
appearToClass: 'appear-to'
|
||||||
|
},
|
||||||
|
|
||||||
|
layoutTransition: {
|
||||||
|
name: 'layout',
|
||||||
|
mode: 'out-in'
|
||||||
|
}
|
||||||
|
})
|
80
packages/config/src/config/_common.js
Normal file
80
packages/config/src/config/_common.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import capitalize from 'lodash/capitalize'
|
||||||
|
import env from 'std-env'
|
||||||
|
|
||||||
|
export default () => ({
|
||||||
|
// Env
|
||||||
|
dev: Boolean(env.dev),
|
||||||
|
test: Boolean(env.test),
|
||||||
|
debug: undefined, // = dev
|
||||||
|
env: {},
|
||||||
|
|
||||||
|
// Mode
|
||||||
|
mode: 'universal',
|
||||||
|
|
||||||
|
// Globals
|
||||||
|
globalName: `nuxt`,
|
||||||
|
globals: {
|
||||||
|
id: globalName => `__${globalName}`,
|
||||||
|
nuxt: globalName => `$${globalName}`,
|
||||||
|
context: globalName => `__${globalName.toUpperCase()}__`,
|
||||||
|
pluginPrefix: globalName => globalName,
|
||||||
|
readyCallback: globalName => `on${capitalize(globalName)}Ready`,
|
||||||
|
loadedCallback: globalName => `_on${capitalize(globalName)}Loaded`
|
||||||
|
},
|
||||||
|
|
||||||
|
// Server
|
||||||
|
serverMiddleware: [],
|
||||||
|
|
||||||
|
// Dirs and extensions
|
||||||
|
srcDir: undefined,
|
||||||
|
buildDir: '.nuxt',
|
||||||
|
nuxtDir: fs.existsSync(path.resolve(__dirname, '..', '..', 'package.js'))
|
||||||
|
? path.resolve(__dirname, '..', '..') // src
|
||||||
|
: path.resolve(__dirname, '..'), // dist
|
||||||
|
modulesDir: [
|
||||||
|
'node_modules'
|
||||||
|
],
|
||||||
|
dir: {
|
||||||
|
assets: 'assets',
|
||||||
|
layouts: 'layouts',
|
||||||
|
middleware: 'middleware',
|
||||||
|
pages: 'pages',
|
||||||
|
static: 'static',
|
||||||
|
store: 'store'
|
||||||
|
},
|
||||||
|
extensions: [],
|
||||||
|
|
||||||
|
// Ignores
|
||||||
|
ignorePrefix: '-',
|
||||||
|
ignore: [
|
||||||
|
'**/*.test.*',
|
||||||
|
'**/*.spec.*'
|
||||||
|
],
|
||||||
|
|
||||||
|
// Generate
|
||||||
|
generate: {
|
||||||
|
dir: 'dist',
|
||||||
|
routes: [],
|
||||||
|
concurrency: 500,
|
||||||
|
interval: 0,
|
||||||
|
subFolders: true,
|
||||||
|
fallback: '200.html'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Watch
|
||||||
|
watch: [],
|
||||||
|
watchers: {
|
||||||
|
webpack: {},
|
||||||
|
chokidar: {
|
||||||
|
ignoreInitial: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Editor
|
||||||
|
editor: undefined,
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
hooks: null
|
||||||
|
})
|
@ -1,6 +1,6 @@
|
|||||||
import env from 'std-env'
|
import env from 'std-env'
|
||||||
|
|
||||||
export default {
|
export default () => ({
|
||||||
quiet: Boolean(env.ci || env.test),
|
quiet: Boolean(env.ci || env.test),
|
||||||
analyze: false,
|
analyze: false,
|
||||||
profile: process.argv.includes('--profile'),
|
profile: process.argv.includes('--profile'),
|
||||||
@ -109,4 +109,4 @@ export default {
|
|||||||
/vue-ssr-client-manifest.json/
|
/vue-ssr-client-manifest.json/
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
@ -1,130 +1,27 @@
|
|||||||
import path from 'path'
|
|
||||||
import fs from 'fs'
|
|
||||||
import capitalize from 'lodash/capitalize'
|
|
||||||
import env from 'std-env'
|
|
||||||
|
|
||||||
import render from './render'
|
import _app from './_app'
|
||||||
|
import _common from './_common'
|
||||||
|
|
||||||
import build from './build'
|
import build from './build'
|
||||||
import router from './router'
|
|
||||||
import messages from './messages'
|
import messages from './messages'
|
||||||
|
import modes from './modes'
|
||||||
|
import render from './render'
|
||||||
|
import router from './router'
|
||||||
import server from './server'
|
import server from './server'
|
||||||
|
|
||||||
const nuxtDir = fs.existsSync(path.resolve(__dirname, '..', '..', 'package.js'))
|
export function getDefaultNuxtConfig(options = {}) {
|
||||||
? path.resolve(__dirname, '..', '..') // src
|
if (!options.env) {
|
||||||
: path.resolve(__dirname, '..') // dist
|
options.env = process.env
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
return {
|
||||||
// Information about running environment
|
..._app(options),
|
||||||
dev: Boolean(env.dev),
|
..._common(options),
|
||||||
test: Boolean(env.test),
|
build: build(options),
|
||||||
debug: undefined, // = dev
|
messages: messages(options),
|
||||||
|
modes: modes(options),
|
||||||
// Mode
|
render: render(options),
|
||||||
mode: 'universal',
|
router: router(options),
|
||||||
|
server: server(options)
|
||||||
// Global name
|
}
|
||||||
globalName: `nuxt`,
|
|
||||||
globals: {
|
|
||||||
id: globalName => `__${globalName}`,
|
|
||||||
nuxt: globalName => `$${globalName}`,
|
|
||||||
context: globalName => `__${globalName.toUpperCase()}__`,
|
|
||||||
pluginPrefix: globalName => globalName,
|
|
||||||
readyCallback: globalName => `on${capitalize(globalName)}Ready`,
|
|
||||||
loadedCallback: globalName => `_on${capitalize(globalName)}Loaded`
|
|
||||||
},
|
|
||||||
|
|
||||||
render,
|
|
||||||
build,
|
|
||||||
router,
|
|
||||||
messages,
|
|
||||||
|
|
||||||
// Server options
|
|
||||||
server: server(process.env),
|
|
||||||
|
|
||||||
// Dirs
|
|
||||||
srcDir: undefined,
|
|
||||||
buildDir: '.nuxt',
|
|
||||||
nuxtDir,
|
|
||||||
modulesDir: [
|
|
||||||
'node_modules'
|
|
||||||
],
|
|
||||||
|
|
||||||
// Ignore
|
|
||||||
ignorePrefix: '-',
|
|
||||||
ignore: [
|
|
||||||
'**/*.test.*',
|
|
||||||
'**/*.spec.*'
|
|
||||||
],
|
|
||||||
|
|
||||||
extensions: [],
|
|
||||||
|
|
||||||
generate: {
|
|
||||||
dir: 'dist',
|
|
||||||
routes: [],
|
|
||||||
concurrency: 500,
|
|
||||||
interval: 0,
|
|
||||||
subFolders: true,
|
|
||||||
fallback: '200.html'
|
|
||||||
},
|
|
||||||
env: {},
|
|
||||||
head: {
|
|
||||||
meta: [],
|
|
||||||
link: [],
|
|
||||||
style: [],
|
|
||||||
script: []
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
css: [],
|
|
||||||
modules: [],
|
|
||||||
layouts: {},
|
|
||||||
serverMiddleware: [],
|
|
||||||
ErrorPage: null,
|
|
||||||
loading: {
|
|
||||||
color: 'black',
|
|
||||||
failedColor: 'red',
|
|
||||||
height: '2px',
|
|
||||||
throttle: 200,
|
|
||||||
duration: 5000,
|
|
||||||
continuous: false,
|
|
||||||
rtl: false,
|
|
||||||
css: true
|
|
||||||
},
|
|
||||||
loadingIndicator: 'default',
|
|
||||||
transition: {
|
|
||||||
name: 'page',
|
|
||||||
mode: 'out-in',
|
|
||||||
appear: false,
|
|
||||||
appearClass: 'appear',
|
|
||||||
appearActiveClass: 'appear-active',
|
|
||||||
appearToClass: 'appear-to'
|
|
||||||
},
|
|
||||||
layoutTransition: {
|
|
||||||
name: 'layout',
|
|
||||||
mode: 'out-in'
|
|
||||||
},
|
|
||||||
dir: {
|
|
||||||
assets: 'assets',
|
|
||||||
layouts: 'layouts',
|
|
||||||
middleware: 'middleware',
|
|
||||||
pages: 'pages',
|
|
||||||
static: 'static',
|
|
||||||
store: 'store'
|
|
||||||
},
|
|
||||||
vue: {
|
|
||||||
config: {
|
|
||||||
silent: undefined, // = !dev
|
|
||||||
performance: undefined // = dev
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// User-defined changes
|
|
||||||
watch: [],
|
|
||||||
watchers: {
|
|
||||||
webpack: {},
|
|
||||||
chokidar: {
|
|
||||||
ignoreInitial: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
editor: undefined,
|
|
||||||
hooks: null
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export default {
|
export default () => ({
|
||||||
loading: 'Loading...',
|
loading: 'Loading...',
|
||||||
error_404: 'This page could not be found',
|
error_404: 'This page could not be found',
|
||||||
server_error: 'Server error',
|
server_error: 'Server error',
|
||||||
@ -9,4 +9,4 @@ export default {
|
|||||||
client_error: 'Error',
|
client_error: 'Error',
|
||||||
client_error_details:
|
client_error_details:
|
||||||
'An error occurred while rendering the page. Check developer tools console for details.'
|
'An error occurred while rendering the page. Check developer tools console for details.'
|
||||||
}
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export default {
|
export default () => ({
|
||||||
universal: {
|
universal: {
|
||||||
build: {
|
build: {
|
||||||
ssr: true
|
ssr: true
|
||||||
@ -15,4 +15,4 @@ export default {
|
|||||||
ssr: false
|
ssr: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
@ -1,4 +1,4 @@
|
|||||||
export default {
|
export default () => ({
|
||||||
bundleRenderer: {
|
bundleRenderer: {
|
||||||
shouldPrefetch: () => false
|
shouldPrefetch: () => false
|
||||||
},
|
},
|
||||||
@ -24,4 +24,4 @@ export default {
|
|||||||
// 1 year in production
|
// 1 year in production
|
||||||
maxAge: '1y'
|
maxAge: '1y'
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export default {
|
export default () => ({
|
||||||
mode: 'history',
|
mode: 'history',
|
||||||
base: '/',
|
base: '/',
|
||||||
routes: [],
|
routes: [],
|
||||||
@ -10,4 +10,4 @@ export default {
|
|||||||
parseQuery: false,
|
parseQuery: false,
|
||||||
stringifyQuery: false,
|
stringifyQuery: false,
|
||||||
fallback: false
|
fallback: false
|
||||||
}
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export default env => ({
|
export default ({ env }) => ({
|
||||||
https: false,
|
https: false,
|
||||||
port: env.NUXT_PORT ||
|
port: env.NUXT_PORT ||
|
||||||
env.PORT ||
|
env.PORT ||
|
||||||
|
@ -1,9 +1,2 @@
|
|||||||
|
export { getDefaultNuxtConfig } from './config'
|
||||||
// Export individual bundles for easier access
|
export { getNuxtConfig } from './options'
|
||||||
export { default as Modes } from './modes'
|
|
||||||
export { default as build } from './config/build'
|
|
||||||
export { default as messages } from './config/messages'
|
|
||||||
export { default as render } from './config/render'
|
|
||||||
export { default as router } from './config/router'
|
|
||||||
export { default as server } from './config/server'
|
|
||||||
export { default as NuxtConfig } from './config'
|
|
||||||
|
@ -5,17 +5,10 @@ import defaults from 'lodash/defaults'
|
|||||||
import pick from 'lodash/pick'
|
import pick from 'lodash/pick'
|
||||||
import isObject from 'lodash/isObject'
|
import isObject from 'lodash/isObject'
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
import { NuxtConfig, Modes } from '@nuxt/config'
|
import { isPureObject, isUrl, guardDir, isNonEmptyString } from '@nuxt/common'
|
||||||
import { isPureObject, isUrl, guardDir, isString } from './utils'
|
import { getDefaultNuxtConfig } from './config'
|
||||||
|
|
||||||
// hasValue utility
|
export function getNuxtConfig(_options) {
|
||||||
const hasValue = v => typeof v === 'string' && v
|
|
||||||
|
|
||||||
const Options = {}
|
|
||||||
|
|
||||||
export default Options
|
|
||||||
|
|
||||||
Options.from = function (_options) {
|
|
||||||
// Clone options to prevent unwanted side-effects
|
// Clone options to prevent unwanted side-effects
|
||||||
const options = Object.assign({}, _options)
|
const options = Object.assign({}, _options)
|
||||||
|
|
||||||
@ -43,12 +36,12 @@ Options.from = function (_options) {
|
|||||||
options.extensions = [options.extensions]
|
options.extensions = [options.extensions]
|
||||||
}
|
}
|
||||||
|
|
||||||
options.globalName = (isString(options.globalName) && /^[a-zA-Z]+$/.test(options.globalName))
|
options.globalName = (isNonEmptyString(options.globalName) && /^[a-zA-Z]+$/.test(options.globalName))
|
||||||
? options.globalName.toLowerCase()
|
? options.globalName.toLowerCase()
|
||||||
: `nuxt`
|
: `nuxt`
|
||||||
|
|
||||||
// Resolve rootDir
|
// Resolve rootDir
|
||||||
options.rootDir = hasValue(options.rootDir) ? path.resolve(options.rootDir) : process.cwd()
|
options.rootDir = isNonEmptyString(options.rootDir) ? path.resolve(options.rootDir) : process.cwd()
|
||||||
|
|
||||||
// Apply defaults by ${buildDir}/dist/build.config.js
|
// Apply defaults by ${buildDir}/dist/build.config.js
|
||||||
// TODO: Unsafe operation.
|
// TODO: Unsafe operation.
|
||||||
@ -59,11 +52,13 @@ Options.from = function (_options) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// Apply defaults
|
// Apply defaults
|
||||||
defaultsDeep(options, NuxtConfig)
|
const nuxtConfig = getDefaultNuxtConfig()
|
||||||
|
nuxtConfig.build._publicPath = nuxtConfig.build.publicPath
|
||||||
|
defaultsDeep(options, nuxtConfig)
|
||||||
|
|
||||||
// Check srcDir and generate.dir excistence
|
// Check srcDir and generate.dir excistence
|
||||||
const hasSrcDir = hasValue(options.srcDir)
|
const hasSrcDir = isNonEmptyString(options.srcDir)
|
||||||
const hasGenerateDir = hasValue(options.generate.dir)
|
const hasGenerateDir = isNonEmptyString(options.generate.dir)
|
||||||
|
|
||||||
// Resolve srcDir
|
// Resolve srcDir
|
||||||
options.srcDir = hasSrcDir
|
options.srcDir = hasSrcDir
|
||||||
@ -97,7 +92,7 @@ Options.from = function (_options) {
|
|||||||
// Populate modulesDir
|
// Populate modulesDir
|
||||||
options.modulesDir = []
|
options.modulesDir = []
|
||||||
.concat(options.modulesDir)
|
.concat(options.modulesDir)
|
||||||
.concat(path.join(options.nuxtDir, 'node_modules')).filter(hasValue)
|
.concat(path.join(options.nuxtDir, 'node_modules')).filter(isNonEmptyString)
|
||||||
.map(dir => path.resolve(options.rootDir, dir))
|
.map(dir => path.resolve(options.rootDir, dir))
|
||||||
|
|
||||||
const mandatoryExtensions = ['js', 'mjs']
|
const mandatoryExtensions = ['js', 'mjs']
|
||||||
@ -119,7 +114,7 @@ Options.from = function (_options) {
|
|||||||
// Ignore publicPath on dev
|
// Ignore publicPath on dev
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
||||||
if (options.dev && isUrl(options.build.publicPath)) {
|
if (options.dev && isUrl(options.build.publicPath)) {
|
||||||
options.build.publicPath = NuxtConfig.build.publicPath
|
options.build.publicPath = options.build._publicPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// If store defined, update store options to true unless explicitly disabled
|
// If store defined, update store options to true unless explicitly disabled
|
||||||
@ -218,7 +213,7 @@ Options.from = function (_options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply mode preset
|
// Apply mode preset
|
||||||
const modePreset = Modes[options.mode || 'universal'] || Modes.universal
|
const modePreset = options.modes[options.mode || 'universal']
|
||||||
defaultsDeep(options, modePreset)
|
defaultsDeep(options, modePreset)
|
||||||
|
|
||||||
// If no server-side rendering, add appear true transition
|
// If no server-side rendering, add appear true transition
|
@ -10,33 +10,17 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/common": "^2.2.0",
|
"@nuxt/common": "^2.2.0",
|
||||||
"@nuxt/config": "^2.2.0",
|
"@nuxt/config": "^2.2.0",
|
||||||
|
"@nuxt/server": "^2.2.0",
|
||||||
|
"@nuxt/vue-renderer": "^2.2.0",
|
||||||
"@nuxtjs/devalue": "^1.0.1",
|
"@nuxtjs/devalue": "^1.0.1",
|
||||||
"@nuxtjs/opencollective": "^0.1.0",
|
"@nuxtjs/opencollective": "^0.1.0",
|
||||||
"@nuxtjs/youch": "^4.2.3",
|
|
||||||
"chalk": "^2.4.1",
|
|
||||||
"compression": "^1.7.3",
|
|
||||||
"connect": "^3.6.6",
|
|
||||||
"consola": "^1.4.4",
|
"consola": "^1.4.4",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"esm": "^3.0.84",
|
"esm": "^3.0.84",
|
||||||
"etag": "^1.8.1",
|
|
||||||
"fresh": "^0.5.2",
|
|
||||||
"fs-extra": "^7.0.0",
|
"fs-extra": "^7.0.0",
|
||||||
"hash-sum": "^1.0.2",
|
"hash-sum": "^1.0.2",
|
||||||
"ip": "^1.1.5",
|
|
||||||
"launch-editor-middleware": "^2.2.1",
|
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"lru-cache": "^4.1.3",
|
"std-env": "^2.0.2"
|
||||||
"serve-static": "^1.13.2",
|
|
||||||
"server-destroy": "^1.0.1",
|
|
||||||
"std-env": "^2.0.2",
|
|
||||||
"vue": "^2.5.17",
|
|
||||||
"vue-meta": "^1.5.5",
|
|
||||||
"vue-no-ssr": "^1.0.0",
|
|
||||||
"vue-router": "^3.0.1",
|
|
||||||
"vue-server-renderer": "^2.5.17",
|
|
||||||
"vue-template-compiler": "^2.5.17",
|
|
||||||
"vuex": "^3.0.1"
|
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export { default as Module } from './module'
|
export { default as Module } from './module'
|
||||||
export { default as Nuxt } from './nuxt'
|
export { default as Nuxt } from './nuxt'
|
||||||
export { default as Renderer } from './renderer'
|
export { default as Resolver } from './resolver'
|
||||||
|
@ -1,43 +1,40 @@
|
|||||||
import https from 'https'
|
|
||||||
import enableDestroy from 'server-destroy'
|
|
||||||
import isPlainObject from 'lodash/isPlainObject'
|
import isPlainObject from 'lodash/isPlainObject'
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
import chalk from 'chalk'
|
|
||||||
|
|
||||||
import ip from 'ip'
|
import { Hookable, defineAlias } from '@nuxt/common'
|
||||||
|
import { getNuxtConfig } from '@nuxt/config'
|
||||||
import { Options, sequence } from '@nuxt/common'
|
import { Server } from '@nuxt/server'
|
||||||
|
|
||||||
import { version } from '../package.json'
|
import { version } from '../package.json'
|
||||||
import ModuleContainer from './module'
|
import ModuleContainer from './module'
|
||||||
import Renderer from './renderer'
|
|
||||||
import Resolver from './resolver'
|
import Resolver from './resolver'
|
||||||
|
|
||||||
export default class Nuxt {
|
export default class Nuxt extends Hookable {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
this.options = Options.from(options)
|
super()
|
||||||
|
|
||||||
this.readyMessage = null
|
// Assign options and apply defaults
|
||||||
this.initialized = false
|
this.options = getNuxtConfig(options)
|
||||||
|
|
||||||
// Hooks
|
|
||||||
this._hooks = {}
|
|
||||||
this.hook = this.hook.bind(this)
|
|
||||||
|
|
||||||
// Create instance of core components
|
// Create instance of core components
|
||||||
this.moduleContainer = new ModuleContainer(this)
|
|
||||||
this.renderer = new Renderer(this)
|
|
||||||
this.resolver = new Resolver(this)
|
this.resolver = new Resolver(this)
|
||||||
|
this.moduleContainer = new ModuleContainer(this)
|
||||||
|
this.server = new Server(this)
|
||||||
|
|
||||||
// Backward compatibility
|
// Deprecated hooks
|
||||||
this.render = this.renderer.app
|
this._deprecatedHooks = {
|
||||||
this.renderRoute = this.renderer.renderRoute.bind(this.renderer)
|
'render:context': 'render:routeContext' // #3773
|
||||||
this.renderAndGetWindow = this.renderer.renderAndGetWindow.bind(
|
}
|
||||||
this.renderer
|
|
||||||
)
|
|
||||||
this.resolveAlias = this.resolver.resolveAlias.bind(this)
|
|
||||||
this.resolvePath = this.resolver.resolvePath.bind(this)
|
|
||||||
|
|
||||||
|
// Add Legacy aliases
|
||||||
|
this.renderer = this.server
|
||||||
|
this.render = this.server.app
|
||||||
|
defineAlias(this, this.server, [ 'renderRoute', 'renderAndGetWindow', 'showReady', 'listen' ])
|
||||||
|
defineAlias(this, this.resolver, [ 'resolveAlias', 'resolvePath' ])
|
||||||
|
|
||||||
|
// Wait for Nuxt to be ready
|
||||||
|
this.initialized = false
|
||||||
this._ready = this.ready().catch((err) => {
|
this._ready = this.ready().catch((err) => {
|
||||||
consola.fatal(err)
|
consola.fatal(err)
|
||||||
})
|
})
|
||||||
@ -62,8 +59,8 @@ export default class Nuxt {
|
|||||||
// Await for modules
|
// Await for modules
|
||||||
await this.moduleContainer.ready()
|
await this.moduleContainer.ready()
|
||||||
|
|
||||||
// Await for renderer to be ready
|
// Await for server to be ready
|
||||||
await this.renderer.ready()
|
await this.server.ready()
|
||||||
|
|
||||||
this.initialized = true
|
this.initialized = true
|
||||||
|
|
||||||
@ -73,156 +70,6 @@ export default class Nuxt {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
hook(name, fn) {
|
|
||||||
if (!name || typeof fn !== 'function') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (name === 'render:context') {
|
|
||||||
name = 'render:routeContext'
|
|
||||||
consola.warn('render:context hook has been deprecated, please use render:routeContext')
|
|
||||||
}
|
|
||||||
this._hooks[name] = this._hooks[name] || []
|
|
||||||
this._hooks[name].push(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
async callHook(name, ...args) {
|
|
||||||
if (!this._hooks[name]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
consola.debug(`Call ${name} hooks (${this._hooks[name].length})`)
|
|
||||||
try {
|
|
||||||
await sequence(this._hooks[name], fn => fn(...args))
|
|
||||||
} catch (err) {
|
|
||||||
consola.error(err)
|
|
||||||
this.callHook('error', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clearHook(name) {
|
|
||||||
if (name) {
|
|
||||||
delete this._hooks[name]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
flatHooks(configHooks, hooks = {}, parentName) {
|
|
||||||
Object.keys(configHooks).forEach((key) => {
|
|
||||||
const subHook = configHooks[key]
|
|
||||||
const name = parentName ? `${parentName}:${key}` : key
|
|
||||||
if (typeof subHook === 'object' && subHook !== null) {
|
|
||||||
this.flatHooks(subHook, hooks, name)
|
|
||||||
} else {
|
|
||||||
hooks[name] = subHook
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return hooks
|
|
||||||
}
|
|
||||||
|
|
||||||
addHooks(configHooks) {
|
|
||||||
const hooks = this.flatHooks(configHooks)
|
|
||||||
Object.keys(hooks).filter(Boolean).forEach((key) => {
|
|
||||||
[].concat(hooks[key]).forEach(h => this.hook(key, h))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
showReady(clear = true) {
|
|
||||||
if (!this.readyMessage) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
consola.ready({
|
|
||||||
message: this.readyMessage,
|
|
||||||
badge: true,
|
|
||||||
clear
|
|
||||||
})
|
|
||||||
this.readyMessage = null
|
|
||||||
}
|
|
||||||
|
|
||||||
listen(port, host, socket) {
|
|
||||||
return this.ready().then(() => new Promise((resolve, reject) => {
|
|
||||||
if (!socket && typeof this.options.server.socket === 'string') {
|
|
||||||
socket = this.options.server.socket
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = { exclusive: false }
|
|
||||||
|
|
||||||
if (socket) {
|
|
||||||
args.path = socket
|
|
||||||
} else {
|
|
||||||
args.port = port || this.options.server.port
|
|
||||||
args.host = host || this.options.server.host
|
|
||||||
}
|
|
||||||
|
|
||||||
let appServer
|
|
||||||
const isHttps = Boolean(this.options.server.https)
|
|
||||||
|
|
||||||
if (isHttps) {
|
|
||||||
let httpsOptions
|
|
||||||
|
|
||||||
if (this.options.server.https === true) {
|
|
||||||
httpsOptions = {}
|
|
||||||
} else {
|
|
||||||
httpsOptions = this.options.server.https
|
|
||||||
}
|
|
||||||
|
|
||||||
appServer = https.createServer(httpsOptions, this.renderer.app)
|
|
||||||
} else {
|
|
||||||
appServer = this.renderer.app
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = appServer.listen(
|
|
||||||
args,
|
|
||||||
(err) => {
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
let listenURL
|
|
||||||
|
|
||||||
if (!socket) {
|
|
||||||
({ address: host, port } = server.address())
|
|
||||||
if (host === '127.0.0.1') {
|
|
||||||
host = 'localhost'
|
|
||||||
} else if (host === '0.0.0.0') {
|
|
||||||
host = ip.address()
|
|
||||||
}
|
|
||||||
|
|
||||||
listenURL = chalk.underline.blue(`http${isHttps ? 's' : ''}://${host}:${port}`)
|
|
||||||
this.readyMessage = `Listening on ${listenURL}`
|
|
||||||
} else {
|
|
||||||
listenURL = chalk.underline.blue(`unix+http://${socket}`)
|
|
||||||
this.readyMessage = `Listening on ${listenURL}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close server on nuxt close
|
|
||||||
this.hook(
|
|
||||||
'close',
|
|
||||||
() =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
// Destroy server by forcing every connection to be closed
|
|
||||||
server.listening && server.destroy((err) => {
|
|
||||||
consola.debug('server closed')
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (err) {
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
if (socket) {
|
|
||||||
this.callHook('listen', server, { path: socket }).then(resolve)
|
|
||||||
} else {
|
|
||||||
this.callHook('listen', server, { port, host }).then(resolve)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Add server.destroy(cb) method
|
|
||||||
enableDestroy(server)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
async close(callback) {
|
async close(callback) {
|
||||||
await this.callHook('close', this)
|
await this.callHook('close', this)
|
||||||
|
|
||||||
|
@ -1,484 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
import crypto from 'crypto'
|
|
||||||
import devalue from '@nuxtjs/devalue'
|
|
||||||
import serveStatic from 'serve-static'
|
|
||||||
import template from 'lodash/template'
|
|
||||||
import fs from 'fs-extra'
|
|
||||||
import { createBundleRenderer } from 'vue-server-renderer'
|
|
||||||
import connect from 'connect'
|
|
||||||
import launchMiddleware from 'launch-editor-middleware'
|
|
||||||
import consola from 'consola'
|
|
||||||
|
|
||||||
import { isUrl, timeout, waitFor, determineGlobals } from '@nuxt/common'
|
|
||||||
import { NuxtConfig } from '@nuxt/config'
|
|
||||||
|
|
||||||
import MetaRenderer from './meta'
|
|
||||||
import errorMiddleware from './middleware/error'
|
|
||||||
import nuxtMiddleware from './middleware/nuxt'
|
|
||||||
|
|
||||||
let jsdom = null
|
|
||||||
|
|
||||||
export default class Renderer {
|
|
||||||
constructor(nuxt) {
|
|
||||||
this.nuxt = nuxt
|
|
||||||
this.options = nuxt.options
|
|
||||||
this.globals = determineGlobals(nuxt.options.globalName, nuxt.options.globals)
|
|
||||||
|
|
||||||
// Will be set by createRenderer
|
|
||||||
this.bundleRenderer = null
|
|
||||||
this.metaRenderer = null
|
|
||||||
|
|
||||||
// Will be available on dev
|
|
||||||
this.webpackDevMiddleware = null
|
|
||||||
this.webpackHotMiddleware = null
|
|
||||||
|
|
||||||
// Create new connect instance
|
|
||||||
this.app = connect()
|
|
||||||
|
|
||||||
// Renderer runtime resources
|
|
||||||
this.resources = {
|
|
||||||
clientManifest: null,
|
|
||||||
serverBundle: null,
|
|
||||||
ssrTemplate: null,
|
|
||||||
spaTemplate: null,
|
|
||||||
errorTemplate: parseTemplate('Nuxt.js Internal Server Error')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async ready() {
|
|
||||||
await this.nuxt.callHook('render:before', this, this.options.render)
|
|
||||||
// Setup nuxt middleware
|
|
||||||
await this.setupMiddleware()
|
|
||||||
|
|
||||||
// Production: Load SSR resources from fs
|
|
||||||
if (!this.options.dev) {
|
|
||||||
await this.loadResources()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call done hook
|
|
||||||
await this.nuxt.callHook('render:done', this)
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadResources(_fs = fs) {
|
|
||||||
const distPath = path.resolve(this.options.buildDir, 'dist', 'server')
|
|
||||||
const updated = []
|
|
||||||
|
|
||||||
resourceMap.forEach(({ key, fileName, transform }) => {
|
|
||||||
const rawKey = '$$' + key
|
|
||||||
const _path = path.join(distPath, fileName)
|
|
||||||
|
|
||||||
if (!_fs.existsSync(_path)) {
|
|
||||||
return // Resource not exists
|
|
||||||
}
|
|
||||||
const rawData = _fs.readFileSync(_path, 'utf8')
|
|
||||||
if (!rawData || rawData === this.resources[rawKey]) {
|
|
||||||
return // No changes
|
|
||||||
}
|
|
||||||
this.resources[rawKey] = rawData
|
|
||||||
const data = transform(rawData)
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (!data) {
|
|
||||||
return // Invalid data ?
|
|
||||||
}
|
|
||||||
this.resources[key] = data
|
|
||||||
updated.push(key)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Reload error template
|
|
||||||
const errorTemplatePath = path.resolve(this.options.buildDir, 'views/error.html')
|
|
||||||
if (fs.existsSync(errorTemplatePath)) {
|
|
||||||
this.resources.errorTemplate = parseTemplate(
|
|
||||||
fs.readFileSync(errorTemplatePath, 'utf8')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load loading template
|
|
||||||
const loadingHTMLPath = path.resolve(this.options.buildDir, 'loading.html')
|
|
||||||
if (fs.existsSync(loadingHTMLPath)) {
|
|
||||||
this.resources.loadingHTML = fs.readFileSync(loadingHTMLPath, 'utf8')
|
|
||||||
this.resources.loadingHTML = this.resources.loadingHTML
|
|
||||||
.replace(/\r|\n|[\t\s]{3,}/g, '')
|
|
||||||
} else {
|
|
||||||
this.resources.loadingHTML = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call resourcesLoaded plugin
|
|
||||||
await this.nuxt.callHook('render:resourcesLoaded', this.resources)
|
|
||||||
|
|
||||||
if (updated.length > 0) {
|
|
||||||
this.createRenderer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get noSSR() {
|
|
||||||
return this.options.render.ssr === false
|
|
||||||
}
|
|
||||||
|
|
||||||
get isReady() {
|
|
||||||
if (this.noSSR) {
|
|
||||||
return Boolean(this.resources.spaTemplate)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Boolean(this.bundleRenderer && this.resources.ssrTemplate)
|
|
||||||
}
|
|
||||||
|
|
||||||
get isResourcesAvailable() {
|
|
||||||
// Required for both
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (!this.resources.clientManifest) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required for SPA rendering
|
|
||||||
if (this.noSSR) {
|
|
||||||
return Boolean(this.resources.spaTemplate)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required for bundle renderer
|
|
||||||
return Boolean(this.resources.ssrTemplate && this.resources.serverBundle)
|
|
||||||
}
|
|
||||||
|
|
||||||
createRenderer() {
|
|
||||||
// Ensure resources are available
|
|
||||||
if (!this.isResourcesAvailable) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Meta Renderer
|
|
||||||
this.metaRenderer = new MetaRenderer(this.nuxt, this)
|
|
||||||
|
|
||||||
// Skip following steps if noSSR mode
|
|
||||||
if (this.noSSR) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasModules = fs.existsSync(path.resolve(this.options.rootDir, 'node_modules'))
|
|
||||||
// Create bundle renderer for SSR
|
|
||||||
this.bundleRenderer = createBundleRenderer(
|
|
||||||
this.resources.serverBundle,
|
|
||||||
Object.assign(
|
|
||||||
{
|
|
||||||
clientManifest: this.resources.clientManifest,
|
|
||||||
runInNewContext: false,
|
|
||||||
// for globally installed nuxt command, search dependencies in global dir
|
|
||||||
basedir: hasModules ? this.options.rootDir : __dirname
|
|
||||||
},
|
|
||||||
this.options.render.bundleRenderer
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
useMiddleware(m) {
|
|
||||||
// Resolve
|
|
||||||
const $m = m
|
|
||||||
if (typeof m === 'string') {
|
|
||||||
m = this.nuxt.resolver.requireModule(m)
|
|
||||||
}
|
|
||||||
if (typeof m.handler === 'string') {
|
|
||||||
m.handler = this.nuxt.resolver.requireModule(m.handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handler = m.handler || m
|
|
||||||
const path = (
|
|
||||||
(m.prefix !== false ? this.options.router.base : '') +
|
|
||||||
(typeof m.path === 'string' ? m.path : '')
|
|
||||||
).replace(/\/\//g, '/')
|
|
||||||
|
|
||||||
handler.$m = $m
|
|
||||||
|
|
||||||
// Use middleware
|
|
||||||
this.app.use(path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
get publicPath() {
|
|
||||||
return isUrl(this.options.build.publicPath)
|
|
||||||
? NuxtConfig.build.publicPath
|
|
||||||
: this.options.build.publicPath
|
|
||||||
}
|
|
||||||
|
|
||||||
async setupMiddleware() {
|
|
||||||
// Apply setupMiddleware from modules first
|
|
||||||
await this.nuxt.callHook('render:setupMiddleware', this.app)
|
|
||||||
|
|
||||||
// Compression middleware for production
|
|
||||||
if (!this.options.dev) {
|
|
||||||
const compressor = this.options.render.compressor
|
|
||||||
if (typeof compressor === 'object') {
|
|
||||||
// If only setting for `compression` are provided, require the module and insert
|
|
||||||
// Prefer require instead of requireModule to keep dependency in nuxt-start
|
|
||||||
const compression = require('compression')
|
|
||||||
this.useMiddleware(compression(compressor))
|
|
||||||
} else {
|
|
||||||
// Else, require own compression middleware
|
|
||||||
this.useMiddleware(compressor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add webpack middleware only for development
|
|
||||||
if (this.options.dev) {
|
|
||||||
this.useMiddleware(async (req, res, next) => {
|
|
||||||
if (this.webpackDevMiddleware) {
|
|
||||||
await this.webpackDevMiddleware(req, res)
|
|
||||||
}
|
|
||||||
if (this.webpackHotMiddleware) {
|
|
||||||
await this.webpackHotMiddleware(req, res)
|
|
||||||
}
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// open in editor for debug mode only
|
|
||||||
if (this.options.debug && this.options.dev) {
|
|
||||||
this.useMiddleware({
|
|
||||||
path: '__open-in-editor',
|
|
||||||
handler: launchMiddleware(this.options.editor)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// For serving static/ files to /
|
|
||||||
const staticMiddleware = serveStatic(
|
|
||||||
path.resolve(this.options.srcDir, this.options.dir.static),
|
|
||||||
this.options.render.static
|
|
||||||
)
|
|
||||||
staticMiddleware.prefix = this.options.render.static.prefix
|
|
||||||
this.useMiddleware(staticMiddleware)
|
|
||||||
|
|
||||||
// Serve .nuxt/dist/ files only for production
|
|
||||||
// For dev they will be served with devMiddleware
|
|
||||||
if (!this.options.dev) {
|
|
||||||
const distDir = path.resolve(this.options.buildDir, 'dist', 'client')
|
|
||||||
this.useMiddleware({
|
|
||||||
path: this.publicPath,
|
|
||||||
handler: serveStatic(
|
|
||||||
distDir,
|
|
||||||
this.options.render.dist
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add User provided middleware
|
|
||||||
this.options.serverMiddleware.forEach((m) => {
|
|
||||||
this.useMiddleware(m)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Finally use nuxtMiddleware
|
|
||||||
this.useMiddleware(nuxtMiddleware.bind(this))
|
|
||||||
|
|
||||||
// Error middleware for errors that occurred in middleware that declared above
|
|
||||||
// Middleware should exactly take 4 arguments
|
|
||||||
// https://github.com/senchalabs/connect#error-middleware
|
|
||||||
|
|
||||||
// Apply errorMiddleware from modules first
|
|
||||||
await this.nuxt.callHook('render:errorMiddleware', this.app)
|
|
||||||
|
|
||||||
// Apply errorMiddleware from Nuxt
|
|
||||||
this.useMiddleware(errorMiddleware.bind(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTemplate(ssr, opts) {
|
|
||||||
// Fix problem with HTMLPlugin's minify option (#3392)
|
|
||||||
opts.html_attrs = opts.HTML_ATTRS
|
|
||||||
opts.body_attrs = opts.BODY_ATTRS
|
|
||||||
|
|
||||||
const fn = ssr ? this.resources.ssrTemplate : this.resources.spaTemplate
|
|
||||||
|
|
||||||
return fn(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
async renderRoute(url, context = {}) {
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (!this.isReady) {
|
|
||||||
await waitFor(1000)
|
|
||||||
return this.renderRoute(url, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log rendered url
|
|
||||||
consola.debug(`Rendering url ${url}`)
|
|
||||||
|
|
||||||
// Add url and isSever to the context
|
|
||||||
context.url = url
|
|
||||||
|
|
||||||
// Basic response if SSR is disabled or spa data provided
|
|
||||||
const spa = context.spa || (context.res && context.res.spa)
|
|
||||||
const ENV = this.options.env
|
|
||||||
|
|
||||||
if (this.noSSR || spa) {
|
|
||||||
const {
|
|
||||||
HTML_ATTRS,
|
|
||||||
BODY_ATTRS,
|
|
||||||
HEAD,
|
|
||||||
BODY_SCRIPTS,
|
|
||||||
getPreloadFiles
|
|
||||||
} = await this.metaRenderer.render(context)
|
|
||||||
const APP =
|
|
||||||
`<div id="${this.globals.id}">${this.resources.loadingHTML}</div>` + BODY_SCRIPTS
|
|
||||||
|
|
||||||
// Detect 404 errors
|
|
||||||
if (
|
|
||||||
url.includes(this.options.build.publicPath) ||
|
|
||||||
url.includes('__webpack')
|
|
||||||
) {
|
|
||||||
const err = {
|
|
||||||
statusCode: 404,
|
|
||||||
message: this.options.messages.error_404,
|
|
||||||
name: 'ResourceNotFound'
|
|
||||||
}
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = this.renderTemplate(false, {
|
|
||||||
HTML_ATTRS,
|
|
||||||
BODY_ATTRS,
|
|
||||||
HEAD,
|
|
||||||
APP,
|
|
||||||
ENV
|
|
||||||
})
|
|
||||||
|
|
||||||
return { html, getPreloadFiles }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call renderToString from the bundleRenderer and generate the HTML (will update the context as well)
|
|
||||||
let APP = await this.bundleRenderer.renderToString(context)
|
|
||||||
|
|
||||||
if (!context.nuxt.serverRendered) {
|
|
||||||
APP = `<div id="${this.globals.id}"></div>`
|
|
||||||
}
|
|
||||||
const m = context.meta.inject()
|
|
||||||
let HEAD =
|
|
||||||
m.title.text() +
|
|
||||||
m.meta.text() +
|
|
||||||
m.link.text() +
|
|
||||||
m.style.text() +
|
|
||||||
m.script.text() +
|
|
||||||
m.noscript.text()
|
|
||||||
if (this.options._routerBaseSpecified) {
|
|
||||||
HEAD += `<base href="${this.options.router.base}">`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.render.resourceHints) {
|
|
||||||
HEAD += context.renderResourceHints()
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.nuxt.callHook('render:routeContext', context.nuxt)
|
|
||||||
|
|
||||||
const serializedSession = `window.${this.globals.context}=${devalue(context.nuxt)};`
|
|
||||||
|
|
||||||
const cspScriptSrcHashSet = new Set()
|
|
||||||
if (this.options.render.csp) {
|
|
||||||
const { hashAlgorithm } = this.options.render.csp
|
|
||||||
const hash = crypto.createHash(hashAlgorithm)
|
|
||||||
hash.update(serializedSession)
|
|
||||||
cspScriptSrcHashSet.add(`'${hashAlgorithm}-${hash.digest('base64')}'`)
|
|
||||||
}
|
|
||||||
|
|
||||||
APP += `<script>${serializedSession}</script>`
|
|
||||||
APP += context.renderScripts()
|
|
||||||
APP += m.script.text({ body: true })
|
|
||||||
APP += m.noscript.text({ body: true })
|
|
||||||
|
|
||||||
HEAD += context.renderStyles()
|
|
||||||
|
|
||||||
const html = this.renderTemplate(true, {
|
|
||||||
HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(),
|
|
||||||
BODY_ATTRS: m.bodyAttrs.text(),
|
|
||||||
HEAD,
|
|
||||||
APP,
|
|
||||||
ENV
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
html,
|
|
||||||
cspScriptSrcHashSet,
|
|
||||||
getPreloadFiles: context.getPreloadFiles,
|
|
||||||
error: context.nuxt.error,
|
|
||||||
redirected: context.redirected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async renderAndGetWindow(url, opts = {}) {
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (!jsdom) {
|
|
||||||
try {
|
|
||||||
jsdom = require('jsdom')
|
|
||||||
} catch (e) /* istanbul ignore next */ {
|
|
||||||
consola.error(`
|
|
||||||
Fail when calling nuxt.renderAndGetWindow(url)
|
|
||||||
jsdom module is not installed
|
|
||||||
Please install jsdom with: npm install --save-dev jsdom
|
|
||||||
`)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const options = Object.assign({
|
|
||||||
resources: 'usable', // load subresources (https://github.com/tmpvar/jsdom#loading-subresources)
|
|
||||||
runScripts: 'dangerously',
|
|
||||||
virtualConsole: true,
|
|
||||||
beforeParse(window) {
|
|
||||||
// Mock window.scrollTo
|
|
||||||
window.scrollTo = () => {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, opts)
|
|
||||||
const jsdomErrHandler = (err) => {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
if (options.virtualConsole) {
|
|
||||||
if (options.virtualConsole === true) {
|
|
||||||
options.virtualConsole = new jsdom.VirtualConsole().sendTo(consola)
|
|
||||||
}
|
|
||||||
// throw error when window creation failed
|
|
||||||
options.virtualConsole.on('jsdomError', jsdomErrHandler)
|
|
||||||
}
|
|
||||||
url = url || 'http://localhost:3000'
|
|
||||||
const { window } = await jsdom.JSDOM.fromURL(url, options)
|
|
||||||
// If Nuxt could not be loaded (error from the server-side)
|
|
||||||
const nuxtExists = window.document.body.innerHTML.includes(
|
|
||||||
this.options.render.ssr ? `window.${this.globals.context}` : `<div id="${this.globals.id}">`
|
|
||||||
)
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (!nuxtExists) {
|
|
||||||
const error = new Error('Could not load the nuxt app')
|
|
||||||
error.body = window.document.body.innerHTML
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
// Used by nuxt.js to say when the components are loaded and the app ready
|
|
||||||
const onNuxtLoaded = this.globals.loadedCallback
|
|
||||||
await timeout(new Promise((resolve) => {
|
|
||||||
window[onNuxtLoaded] = () => resolve(window)
|
|
||||||
}), 20000, 'Components loading in renderAndGetWindow was not completed in 20s')
|
|
||||||
if (options.virtualConsole) {
|
|
||||||
// after window initialized successfully
|
|
||||||
options.virtualConsole.removeListener('jsdomError', jsdomErrHandler)
|
|
||||||
}
|
|
||||||
// Send back window object
|
|
||||||
return window
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseTemplate = templateStr =>
|
|
||||||
template(templateStr, {
|
|
||||||
interpolate: /{{([\s\S]+?)}}/g
|
|
||||||
})
|
|
||||||
|
|
||||||
export const resourceMap = [
|
|
||||||
{
|
|
||||||
key: 'clientManifest',
|
|
||||||
fileName: 'vue-ssr-client-manifest.json',
|
|
||||||
transform: JSON.parse
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'serverBundle',
|
|
||||||
fileName: 'server-bundle.json',
|
|
||||||
transform: JSON.parse
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'ssrTemplate',
|
|
||||||
fileName: 'index.ssr.html',
|
|
||||||
transform: parseTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'spaTemplate',
|
|
||||||
fileName: 'index.spa.html',
|
|
||||||
transform: parseTemplate
|
|
||||||
}
|
|
||||||
]
|
|
@ -153,7 +153,7 @@ export default class Generator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render and write the SPA template to the fallback path
|
// Render and write the SPA template to the fallback path
|
||||||
const { html } = await this.nuxt.renderRoute('/', { spa: true })
|
const { html } = await this.nuxt.server.renderRoute('/', { spa: true })
|
||||||
await fsExtra.writeFile(fallbackPath, html, 'utf8')
|
await fsExtra.writeFile(fallbackPath, html, 'utf8')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +201,7 @@ export default class Generator {
|
|||||||
const pageErrors = []
|
const pageErrors = []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await this.nuxt.renderer.renderRoute(route, {
|
const res = await this.nuxt.server.renderRoute(route, {
|
||||||
_generate: true,
|
_generate: true,
|
||||||
payload
|
payload
|
||||||
})
|
})
|
||||||
|
29
packages/server/package.json
Normal file
29
packages/server/package.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "@nuxt/server",
|
||||||
|
"version": "2.2.0",
|
||||||
|
"repository": "nuxt/nuxt.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"main": "dist/server.js",
|
||||||
|
"dependencies": {
|
||||||
|
"@nuxt/common": "^2.2.0",
|
||||||
|
"@nuxt/config": "^2.2.0",
|
||||||
|
"@nuxtjs/youch": "^4.2.3",
|
||||||
|
"chalk": "^2.4.1",
|
||||||
|
"compression": "^1.7.3",
|
||||||
|
"connect": "^3.6.6",
|
||||||
|
"consola": "^1.4.4",
|
||||||
|
"etag": "^1.8.1",
|
||||||
|
"fresh": "^0.5.2",
|
||||||
|
"fs-extra": "^7.0.0",
|
||||||
|
"ip": "^1.1.5",
|
||||||
|
"launch-editor-middleware": "^2.2.1",
|
||||||
|
"serve-static": "^1.13.2",
|
||||||
|
"server-destroy": "^1.0.1"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
8
packages/server/src/context.js
Normal file
8
packages/server/src/context.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default class ServerContext {
|
||||||
|
constructor(server) {
|
||||||
|
this.nuxt = server.nuxt
|
||||||
|
this.globals = server.globals
|
||||||
|
this.options = server.options
|
||||||
|
this.resources = server.resources
|
||||||
|
}
|
||||||
|
}
|
1
packages/server/src/index.js
Normal file
1
packages/server/src/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as Server } from './server'
|
75
packages/server/src/jsdom.js
Normal file
75
packages/server/src/jsdom.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import consola from 'consola'
|
||||||
|
import { timeout } from '@nuxt/common'
|
||||||
|
|
||||||
|
export default async function renderAndGetWindow(
|
||||||
|
url = 'http://localhost:3000',
|
||||||
|
jsdomOpts = {},
|
||||||
|
{
|
||||||
|
loadedCallback,
|
||||||
|
loadingTimeout = 2000,
|
||||||
|
ssr,
|
||||||
|
globals
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
const jsdom = await import('jsdom')
|
||||||
|
.then(m => m.default || m)
|
||||||
|
.catch((e) => {
|
||||||
|
consola.error(`
|
||||||
|
jsdom is not installed. Please install jsdom with:
|
||||||
|
$ yarn add --dev jsdom
|
||||||
|
OR
|
||||||
|
$ npm install --dev jsdom
|
||||||
|
`)
|
||||||
|
throw e
|
||||||
|
})
|
||||||
|
|
||||||
|
const options = Object.assign({
|
||||||
|
// Load subresources (https://github.com/tmpvar/jsdom#loading-subresources)
|
||||||
|
resources: 'usable',
|
||||||
|
runScripts: 'dangerously',
|
||||||
|
virtualConsole: true,
|
||||||
|
beforeParse(window) {
|
||||||
|
// Mock window.scrollTo
|
||||||
|
window.scrollTo = () => {}
|
||||||
|
}
|
||||||
|
}, jsdomOpts)
|
||||||
|
|
||||||
|
const jsdomErrHandler = (err) => {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.virtualConsole) {
|
||||||
|
if (options.virtualConsole === true) {
|
||||||
|
options.virtualConsole = new jsdom.VirtualConsole().sendTo(consola)
|
||||||
|
}
|
||||||
|
// Throw error when window creation failed
|
||||||
|
options.virtualConsole.on('jsdomError', jsdomErrHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { window } = await jsdom.JSDOM.fromURL(url, options)
|
||||||
|
|
||||||
|
// If Nuxt could not be loaded (error from the server-side)
|
||||||
|
const nuxtExists = window.document.body.innerHTML.includes(
|
||||||
|
ssr ? `window.${globals.context}` : `<div id="${globals.id}">`
|
||||||
|
)
|
||||||
|
|
||||||
|
/* istanbul ignore if */
|
||||||
|
if (!nuxtExists) {
|
||||||
|
const error = new Error('Could not load the nuxt app')
|
||||||
|
error.body = window.document.body.innerHTML
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used by Nuxt.js to say when the components are loaded and the app ready
|
||||||
|
await timeout(new Promise((resolve) => {
|
||||||
|
window[loadedCallback] = () => resolve(window)
|
||||||
|
}), loadingTimeout, `Components loading in renderAndGetWindow was not completed in ${timeout / 1000}s`)
|
||||||
|
|
||||||
|
if (options.virtualConsole) {
|
||||||
|
// After window initialized successfully
|
||||||
|
options.virtualConsole.removeListener('jsdomError', jsdomErrHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send back window object
|
||||||
|
return window
|
||||||
|
}
|
@ -4,7 +4,7 @@ import consola from 'consola'
|
|||||||
|
|
||||||
import Youch from '@nuxtjs/youch'
|
import Youch from '@nuxtjs/youch'
|
||||||
|
|
||||||
export default function errorMiddleware(err, req, res, next) {
|
export default ({ resources, options }) => function errorMiddleware(err, req, res, next) {
|
||||||
// ensure statusCode, message and name fields
|
// ensure statusCode, message and name fields
|
||||||
err.statusCode = err.statusCode || 500
|
err.statusCode = err.statusCode || 500
|
||||||
err.message = err.message || 'Nuxt Server Error'
|
err.message = err.message || 'Nuxt Server Error'
|
||||||
@ -35,7 +35,7 @@ export default function errorMiddleware(err, req, res, next) {
|
|||||||
hasReqHeader('user-agent', 'curl/')
|
hasReqHeader('user-agent', 'curl/')
|
||||||
|
|
||||||
// Use basic errors when debug mode is disabled
|
// Use basic errors when debug mode is disabled
|
||||||
if (!this.options.debug) {
|
if (!options.debug) {
|
||||||
// Json format is compatible with Youch json responses
|
// Json format is compatible with Youch json responses
|
||||||
const json = {
|
const json = {
|
||||||
status: err.statusCode,
|
status: err.statusCode,
|
||||||
@ -46,7 +46,7 @@ export default function errorMiddleware(err, req, res, next) {
|
|||||||
sendResponse(JSON.stringify(json, undefined, 2), 'text/json')
|
sendResponse(JSON.stringify(json, undefined, 2), 'text/json')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const html = this.resources.errorTemplate(json)
|
const html = resources.errorTemplate(json)
|
||||||
sendResponse(html)
|
sendResponse(html)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -55,8 +55,13 @@ export default function errorMiddleware(err, req, res, next) {
|
|||||||
const youch = new Youch(
|
const youch = new Youch(
|
||||||
err,
|
err,
|
||||||
req,
|
req,
|
||||||
readSource.bind(this),
|
readSourceFactory({
|
||||||
this.options.router.base,
|
srcDir: options.srcDir,
|
||||||
|
rootDir: options.rootDir,
|
||||||
|
buildDir: options.buildDir,
|
||||||
|
resources
|
||||||
|
}),
|
||||||
|
options.router.base,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
if (isJson) {
|
if (isJson) {
|
||||||
@ -68,7 +73,7 @@ export default function errorMiddleware(err, req, res, next) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readSource(frame) {
|
const readSourceFactory = ({ srcDir, rootDir, buildDir, resources }) => async function readSource(frame) {
|
||||||
// Remove webpack:/// & query string from the end
|
// Remove webpack:/// & query string from the end
|
||||||
const sanitizeName = name =>
|
const sanitizeName = name =>
|
||||||
name ? name.replace('webpack:///', '').split('?')[0] : null
|
name ? name.replace('webpack:///', '').split('?')[0] : null
|
||||||
@ -82,10 +87,10 @@ async function readSource(frame) {
|
|||||||
|
|
||||||
// Possible paths for file
|
// Possible paths for file
|
||||||
const searchPath = [
|
const searchPath = [
|
||||||
this.options.srcDir,
|
srcDir,
|
||||||
this.options.rootDir,
|
rootDir,
|
||||||
path.join(this.options.buildDir, 'dist', 'server'),
|
path.join(buildDir, 'dist', 'server'),
|
||||||
this.options.buildDir,
|
buildDir,
|
||||||
process.cwd()
|
process.cwd()
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -97,7 +102,7 @@ async function readSource(frame) {
|
|||||||
frame.contents = source
|
frame.contents = source
|
||||||
frame.fullPath = fullPath
|
frame.fullPath = fullPath
|
||||||
if (path.isAbsolute(frame.fileName)) {
|
if (path.isAbsolute(frame.fileName)) {
|
||||||
frame.fileName = path.relative(this.options.rootDir, fullPath)
|
frame.fileName = path.relative(rootDir, fullPath)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -107,6 +112,6 @@ async function readSource(frame) {
|
|||||||
// TODO: restore to if after https://github.com/istanbuljs/nyc/issues/595 fixed
|
// TODO: restore to if after https://github.com/istanbuljs/nyc/issues/595 fixed
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (!frame.contents) {
|
if (!frame.contents) {
|
||||||
frame.contents = this.resources.serverBundle.files[frame.fileName]
|
frame.contents = resources.serverBundle.files[frame.fileName]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,14 +4,14 @@ import consola from 'consola'
|
|||||||
|
|
||||||
import { getContext } from '@nuxt/common'
|
import { getContext } from '@nuxt/common'
|
||||||
|
|
||||||
export default async function nuxtMiddleware(req, res, next) {
|
export default ({ options, nuxt, renderRoute, resources }) => async function nuxtMiddleware(req, res, next) {
|
||||||
// Get context
|
// Get context
|
||||||
const context = getContext(req, res)
|
const context = getContext(req, res)
|
||||||
|
|
||||||
res.statusCode = 200
|
res.statusCode = 200
|
||||||
try {
|
try {
|
||||||
const result = await this.renderRoute(req.url, context)
|
const result = await renderRoute(req.url, context)
|
||||||
await this.nuxt.callHook('render:route', req.url, result, context)
|
await nuxt.callHook('render:route', req.url, result, context)
|
||||||
const {
|
const {
|
||||||
html,
|
html,
|
||||||
cspScriptSrcHashSet,
|
cspScriptSrcHashSet,
|
||||||
@ -21,7 +21,7 @@ export default async function nuxtMiddleware(req, res, next) {
|
|||||||
} = result
|
} = result
|
||||||
|
|
||||||
if (redirected) {
|
if (redirected) {
|
||||||
this.nuxt.callHook('render:routeDone', req.url, result, context)
|
nuxt.callHook('render:routeDone', req.url, result, context)
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -29,26 +29,29 @@ export default async function nuxtMiddleware(req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add ETag header
|
// Add ETag header
|
||||||
if (!error && this.options.render.etag) {
|
if (!error && options.render.etag) {
|
||||||
const etag = generateETag(html, this.options.render.etag)
|
const etag = generateETag(html, options.render.etag)
|
||||||
if (fresh(req.headers, { etag })) {
|
if (fresh(req.headers, { etag })) {
|
||||||
res.statusCode = 304
|
res.statusCode = 304
|
||||||
res.end()
|
res.end()
|
||||||
this.nuxt.callHook('render:routeDone', req.url, result, context)
|
nuxt.callHook('render:routeDone', req.url, result, context)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res.setHeader('ETag', etag)
|
res.setHeader('ETag', etag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP2 push headers for preload assets
|
// HTTP2 push headers for preload assets
|
||||||
if (!error && this.options.render.http2.push) {
|
if (!error && options.render.http2.push) {
|
||||||
// Parse resourceHints to extract HTTP.2 prefetch/push headers
|
// Parse resourceHints to extract HTTP.2 prefetch/push headers
|
||||||
// https://w3c.github.io/preload/#server-push-http-2
|
// https://w3c.github.io/preload/#server-push-http-2
|
||||||
const preloadFiles = getPreloadFiles()
|
const preloadFiles = getPreloadFiles()
|
||||||
const { shouldPush, pushAssets } = this.options.render.http2
|
|
||||||
const { publicPath } = this.resources.clientManifest
|
|
||||||
|
|
||||||
const links = pushAssets ? pushAssets(req, res, publicPath, preloadFiles) : defaultPushAssets(preloadFiles, shouldPush, publicPath, this.options.dev)
|
const { shouldPush, pushAssets } = options.render.http2
|
||||||
|
const { publicPath } = resources.clientManifest
|
||||||
|
|
||||||
|
const links = pushAssets
|
||||||
|
? pushAssets(req, res, publicPath, preloadFiles)
|
||||||
|
: defaultPushAssets(preloadFiles, shouldPush, publicPath, options.dev)
|
||||||
|
|
||||||
// Pass with single Link header
|
// Pass with single Link header
|
||||||
// https://blog.cloudflare.com/http-2-server-push-with-multiple-assets-per-link-header
|
// https://blog.cloudflare.com/http-2-server-push-with-multiple-assets-per-link-header
|
||||||
@ -58,18 +61,18 @@ export default async function nuxtMiddleware(req, res, next) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.render.csp) {
|
if (options.render.csp) {
|
||||||
const { allowedSources, policies } = this.options.render.csp
|
const { allowedSources, policies } = options.render.csp
|
||||||
const cspHeader = this.options.render.csp.reportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'
|
const cspHeader = options.render.csp.reportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'
|
||||||
|
|
||||||
res.setHeader(cspHeader, getCspString({ cspScriptSrcHashSet, allowedSources, policies, isDev: this.options.dev }))
|
res.setHeader(cspHeader, getCspString({ cspScriptSrcHashSet, allowedSources, policies, isDev: options.dev }))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send response
|
// Send response
|
||||||
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||||
res.setHeader('Content-Length', Buffer.byteLength(html))
|
res.setHeader('Content-Length', Buffer.byteLength(html))
|
||||||
res.end(html, 'utf8')
|
res.end(html, 'utf8')
|
||||||
this.nuxt.callHook('render:routeDone', req.url, result, context)
|
nuxt.callHook('render:routeDone', req.url, result, context)
|
||||||
return html
|
return html
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
276
packages/server/src/server.js
Normal file
276
packages/server/src/server.js
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
import https from 'https'
|
||||||
|
import path from 'path'
|
||||||
|
import enableDestroy from 'server-destroy'
|
||||||
|
import launchMiddleware from 'launch-editor-middleware'
|
||||||
|
import serveStatic from 'serve-static'
|
||||||
|
import chalk from 'chalk'
|
||||||
|
import ip from 'ip'
|
||||||
|
import consola from 'consola'
|
||||||
|
import connect from 'connect'
|
||||||
|
import { determineGlobals, isUrl } from '@nuxt/common'
|
||||||
|
|
||||||
|
import ServerContext from './context'
|
||||||
|
import renderAndGetWindow from './jsdom'
|
||||||
|
import nuxtMiddleware from './middleware/nuxt'
|
||||||
|
import errorMiddleware from './middleware/error'
|
||||||
|
|
||||||
|
export default class Server {
|
||||||
|
constructor(nuxt) {
|
||||||
|
this.nuxt = nuxt
|
||||||
|
this.options = nuxt.options
|
||||||
|
|
||||||
|
this.globals = determineGlobals(nuxt.options.globalName, nuxt.options.globals)
|
||||||
|
|
||||||
|
this.publicPath = isUrl(this.options.build.publicPath)
|
||||||
|
? this.options.build._publicPath
|
||||||
|
: this.options.build.publicPath
|
||||||
|
|
||||||
|
// Runtime shared resources
|
||||||
|
this.resources = {}
|
||||||
|
|
||||||
|
// Will be available on dev
|
||||||
|
this.webpackDevMiddleware = null
|
||||||
|
this.webpackHotMiddleware = null
|
||||||
|
|
||||||
|
// Create new connect instance
|
||||||
|
this.app = connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
async ready() {
|
||||||
|
await this.nuxt.callHook('render:before', this, this.options.render)
|
||||||
|
|
||||||
|
// Initialize vue-renderer
|
||||||
|
const { VueRenderer } = await import('@nuxt/vue-renderer')
|
||||||
|
|
||||||
|
const context = new ServerContext(this)
|
||||||
|
this.renderer = new VueRenderer(context)
|
||||||
|
await this.renderer.ready()
|
||||||
|
|
||||||
|
// Setup nuxt middleware
|
||||||
|
await this.setupMiddleware()
|
||||||
|
|
||||||
|
// Call done hook
|
||||||
|
await this.nuxt.callHook('render:done', this)
|
||||||
|
}
|
||||||
|
|
||||||
|
async setupMiddleware() {
|
||||||
|
// Apply setupMiddleware from modules first
|
||||||
|
await this.nuxt.callHook('render:setupMiddleware', this.app)
|
||||||
|
|
||||||
|
// Compression middleware for production
|
||||||
|
if (!this.options.dev) {
|
||||||
|
const compressor = this.options.render.compressor
|
||||||
|
if (typeof compressor === 'object') {
|
||||||
|
// If only setting for `compression` are provided, require the module and insert
|
||||||
|
const compression = this.nuxt.resolver.requireModule('compression')
|
||||||
|
this.useMiddleware(compression(compressor))
|
||||||
|
} else {
|
||||||
|
// Else, require own compression middleware
|
||||||
|
this.useMiddleware(compressor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add webpack middleware support only for development
|
||||||
|
if (this.options.dev) {
|
||||||
|
this.useMiddleware(async (req, res, next) => {
|
||||||
|
if (this.webpackDevMiddleware) {
|
||||||
|
await this.webpackDevMiddleware(req, res)
|
||||||
|
}
|
||||||
|
if (this.webpackHotMiddleware) {
|
||||||
|
await this.webpackHotMiddleware(req, res)
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// open in editor for debug mode only
|
||||||
|
if (this.options.debug && this.options.dev) {
|
||||||
|
this.useMiddleware({
|
||||||
|
path: '__open-in-editor',
|
||||||
|
handler: launchMiddleware(this.options.editor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// For serving static/ files to /
|
||||||
|
const staticMiddleware = serveStatic(
|
||||||
|
path.resolve(this.options.srcDir, this.options.dir.static),
|
||||||
|
this.options.render.static
|
||||||
|
)
|
||||||
|
staticMiddleware.prefix = this.options.render.static.prefix
|
||||||
|
this.useMiddleware(staticMiddleware)
|
||||||
|
|
||||||
|
// Serve .nuxt/dist/client files only for production
|
||||||
|
// For dev they will be served with devMiddleware
|
||||||
|
if (!this.options.dev) {
|
||||||
|
const distDir = path.resolve(this.options.buildDir, 'dist', 'client')
|
||||||
|
this.useMiddleware({
|
||||||
|
path: this.publicPath,
|
||||||
|
handler: serveStatic(
|
||||||
|
distDir,
|
||||||
|
this.options.render.dist
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add User provided middleware
|
||||||
|
this.options.serverMiddleware.forEach((m) => {
|
||||||
|
this.useMiddleware(m)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Finally use nuxtMiddleware
|
||||||
|
this.useMiddleware(nuxtMiddleware({
|
||||||
|
options: this.options,
|
||||||
|
nuxt: this.nuxt,
|
||||||
|
renderRoute: this.renderRoute.bind(this),
|
||||||
|
resources: this.resources
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Error middleware for errors that occurred in middleware that declared above
|
||||||
|
// Middleware should exactly take 4 arguments
|
||||||
|
// https://github.com/senchalabs/connect#error-middleware
|
||||||
|
|
||||||
|
// Apply errorMiddleware from modules first
|
||||||
|
await this.nuxt.callHook('render:errorMiddleware', this.app)
|
||||||
|
|
||||||
|
// Apply errorMiddleware from Nuxt
|
||||||
|
this.useMiddleware(errorMiddleware({
|
||||||
|
resources: this.resources,
|
||||||
|
options: this.options
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
useMiddleware(middleware) {
|
||||||
|
// Resolve middleware
|
||||||
|
if (typeof middleware === 'string') {
|
||||||
|
middleware = this.nuxt.resolver.requireModule(middleware)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve handler
|
||||||
|
if (typeof middleware.handler === 'string') {
|
||||||
|
middleware.handler = this.nuxt.resolver.requireModule(middleware.handler)
|
||||||
|
}
|
||||||
|
const handler = middleware.handler || middleware
|
||||||
|
|
||||||
|
// Resolve path
|
||||||
|
const path = (
|
||||||
|
(middleware.prefix !== false ? this.options.router.base : '') +
|
||||||
|
(typeof middleware.path === 'string' ? middleware.path : '')
|
||||||
|
).replace(/\/\//g, '/')
|
||||||
|
|
||||||
|
// Use middleware
|
||||||
|
this.app.use(path, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRoute() {
|
||||||
|
return this.renderer.renderRoute.apply(this.renderer, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadResources() {
|
||||||
|
return this.renderer.loadResources.apply(this.renderer, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAndGetWindow(url, opts = {}) {
|
||||||
|
return renderAndGetWindow(url, opts, {
|
||||||
|
loadedCallback: this.globals.loadedCallback,
|
||||||
|
ssr: this.options.render.ssr,
|
||||||
|
globals: this.globals
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
showReady(clear = true) {
|
||||||
|
if (this.readyMessage) {
|
||||||
|
consola.ready({
|
||||||
|
message: this.readyMessage,
|
||||||
|
badge: true,
|
||||||
|
clear
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(port, host, socket) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!socket && typeof this.options.server.socket === 'string') {
|
||||||
|
socket = this.options.server.socket
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = { exclusive: false }
|
||||||
|
|
||||||
|
if (socket) {
|
||||||
|
args.path = socket
|
||||||
|
} else {
|
||||||
|
args.port = port || this.options.server.port
|
||||||
|
args.host = host || this.options.server.host
|
||||||
|
}
|
||||||
|
|
||||||
|
let appServer
|
||||||
|
const isHttps = Boolean(this.options.server.https)
|
||||||
|
|
||||||
|
if (isHttps) {
|
||||||
|
let httpsOptions
|
||||||
|
|
||||||
|
if (this.options.server.https === true) {
|
||||||
|
httpsOptions = {}
|
||||||
|
} else {
|
||||||
|
httpsOptions = this.options.server.https
|
||||||
|
}
|
||||||
|
|
||||||
|
appServer = https.createServer(httpsOptions, this.app)
|
||||||
|
} else {
|
||||||
|
appServer = this.app
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = appServer.listen(
|
||||||
|
args,
|
||||||
|
(err) => {
|
||||||
|
/* istanbul ignore if */
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
let listenURL
|
||||||
|
|
||||||
|
if (!socket) {
|
||||||
|
({ address: host, port } = server.address())
|
||||||
|
if (host === '127.0.0.1') {
|
||||||
|
host = 'localhost'
|
||||||
|
} else if (host === '0.0.0.0') {
|
||||||
|
host = ip.address()
|
||||||
|
}
|
||||||
|
|
||||||
|
listenURL = chalk.underline.blue(`http${isHttps ? 's' : ''}://${host}:${port}`)
|
||||||
|
this.readyMessage = `Listening on ${listenURL}`
|
||||||
|
} else {
|
||||||
|
listenURL = chalk.underline.blue(`unix+http://${socket}`)
|
||||||
|
this.readyMessage = `Listening on ${listenURL}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close server on nuxt close
|
||||||
|
this.nuxt.hook(
|
||||||
|
'close',
|
||||||
|
() =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
// Destroy server by forcing every connection to be closed
|
||||||
|
server.listening && server.destroy((err) => {
|
||||||
|
consola.debug('server closed')
|
||||||
|
/* istanbul ignore if */
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
if (socket) {
|
||||||
|
this.nuxt.callHook('listen', server, { path: socket }).then(resolve)
|
||||||
|
} else {
|
||||||
|
this.nuxt.callHook('listen', server, { port, host }).then(resolve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add server.destroy(cb) method
|
||||||
|
enableDestroy(server)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
3
packages/vue-app/package.js
Normal file
3
packages/vue-app/package.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
build: true
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@nuxt/app",
|
"name": "@nuxt/vue-app",
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"repository": "nuxt/nuxt.js",
|
"repository": "nuxt/nuxt.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -7,7 +7,7 @@
|
|||||||
"dist",
|
"dist",
|
||||||
"template"
|
"template"
|
||||||
],
|
],
|
||||||
"main": "dist/app.js",
|
"main": "dist/vue-app.js",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
}
|
}
|
3
packages/vue-renderer/package.js
Normal file
3
packages/vue-renderer/package.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
build: true
|
||||||
|
}
|
27
packages/vue-renderer/package.json
Normal file
27
packages/vue-renderer/package.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "@nuxt/vue-renderer",
|
||||||
|
"version": "2.2.0",
|
||||||
|
"repository": "nuxt/nuxt.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"main": "dist/vue-renderer.js",
|
||||||
|
"dependencies": {
|
||||||
|
"@nuxt/common": "^2.2.0",
|
||||||
|
"@nuxtjs/devalue": "^1.0.1",
|
||||||
|
"consola": "^1.4.4",
|
||||||
|
"fs-extra": "^7.0.0",
|
||||||
|
"lru-cache": "^4.1.3",
|
||||||
|
"vue": "^2.5.17",
|
||||||
|
"vue-meta": "^1.5.5",
|
||||||
|
"vue-no-ssr": "^1.0.0",
|
||||||
|
"vue-router": "^3.0.1",
|
||||||
|
"vue-server-renderer": "^2.5.17",
|
||||||
|
"vue-template-compiler": "^2.5.17",
|
||||||
|
"vuex": "^3.0.1"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
1
packages/vue-renderer/src/index.js
Normal file
1
packages/vue-renderer/src/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as VueRenderer } from './renderer'
|
297
packages/vue-renderer/src/renderer.js
Normal file
297
packages/vue-renderer/src/renderer.js
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import devalue from '@nuxtjs/devalue'
|
||||||
|
import template from 'lodash/template'
|
||||||
|
import fs from 'fs-extra'
|
||||||
|
import { createBundleRenderer } from 'vue-server-renderer'
|
||||||
|
import consola from 'consola'
|
||||||
|
|
||||||
|
import { waitFor } from '@nuxt/common'
|
||||||
|
|
||||||
|
import SPAMetaRenderer from './spa-meta'
|
||||||
|
|
||||||
|
export default class VueRenderer {
|
||||||
|
constructor(context) {
|
||||||
|
this.context = context
|
||||||
|
|
||||||
|
// Will be set by createRenderer
|
||||||
|
this.bundleRenderer = null
|
||||||
|
this.spaMetaRenderer = null
|
||||||
|
|
||||||
|
// Renderer runtime resources
|
||||||
|
Object.assign(this.context.resources, {
|
||||||
|
clientManifest: null,
|
||||||
|
serverBundle: null,
|
||||||
|
ssrTemplate: null,
|
||||||
|
spaTemplate: null,
|
||||||
|
errorTemplate: this.constructor.parseTemplate('Nuxt.js Internal Server Error')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async ready() {
|
||||||
|
// Production: Load SSR resources from fs
|
||||||
|
if (!this.context.options.dev) {
|
||||||
|
await this.loadResources()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadResources(_fs = fs) {
|
||||||
|
const distPath = path.resolve(this.context.options.buildDir, 'dist', 'server')
|
||||||
|
const updated = []
|
||||||
|
|
||||||
|
this.constructor.resourceMap.forEach(({ key, fileName, transform }) => {
|
||||||
|
const rawKey = '$$' + key
|
||||||
|
const _path = path.join(distPath, fileName)
|
||||||
|
|
||||||
|
if (!_fs.existsSync(_path)) {
|
||||||
|
return // Resource not exists
|
||||||
|
}
|
||||||
|
const rawData = _fs.readFileSync(_path, 'utf8')
|
||||||
|
if (!rawData || rawData === this.context.resources[rawKey]) {
|
||||||
|
return // No changes
|
||||||
|
}
|
||||||
|
this.context.resources[rawKey] = rawData
|
||||||
|
const data = transform(rawData)
|
||||||
|
/* istanbul ignore if */
|
||||||
|
if (!data) {
|
||||||
|
return // Invalid data ?
|
||||||
|
}
|
||||||
|
this.context.resources[key] = data
|
||||||
|
updated.push(key)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Reload error template
|
||||||
|
const errorTemplatePath = path.resolve(this.context.options.buildDir, 'views/error.html')
|
||||||
|
if (fs.existsSync(errorTemplatePath)) {
|
||||||
|
this.context.resources.errorTemplate = this.constructor.parseTemplate(
|
||||||
|
fs.readFileSync(errorTemplatePath, 'utf8')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loading template
|
||||||
|
const loadingHTMLPath = path.resolve(this.context.options.buildDir, 'loading.html')
|
||||||
|
if (fs.existsSync(loadingHTMLPath)) {
|
||||||
|
this.context.resources.loadingHTML = fs.readFileSync(loadingHTMLPath, 'utf8')
|
||||||
|
this.context.resources.loadingHTML = this.context.resources.loadingHTML
|
||||||
|
.replace(/\r|\n|[\t\s]{3,}/g, '')
|
||||||
|
} else {
|
||||||
|
this.context.resources.loadingHTML = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call resourcesLoaded plugin
|
||||||
|
await this.context.nuxt.callHook('render:resourcesLoaded', this.context.resources)
|
||||||
|
|
||||||
|
if (updated.length > 0) {
|
||||||
|
this.createRenderer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get noSSR() {
|
||||||
|
return this.context.options.render.ssr === false
|
||||||
|
}
|
||||||
|
|
||||||
|
get isReady() {
|
||||||
|
if (this.noSSR) {
|
||||||
|
return Boolean(this.context.resources.spaTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Boolean(this.bundleRenderer && this.context.resources.ssrTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
get isResourcesAvailable() {
|
||||||
|
// Required for both
|
||||||
|
/* istanbul ignore if */
|
||||||
|
if (!this.context.resources.clientManifest) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required for SPA rendering
|
||||||
|
if (this.noSSR) {
|
||||||
|
return Boolean(this.context.resources.spaTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required for bundle renderer
|
||||||
|
return Boolean(this.context.resources.ssrTemplate && this.context.resources.serverBundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
createRenderer() {
|
||||||
|
// Ensure resources are available
|
||||||
|
if (!this.isResourcesAvailable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Meta Renderer
|
||||||
|
this.spaMetaRenderer = new SPAMetaRenderer(this)
|
||||||
|
|
||||||
|
// Skip following steps if noSSR mode
|
||||||
|
if (this.noSSR) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasModules = fs.existsSync(path.resolve(this.context.options.rootDir, 'node_modules'))
|
||||||
|
// Create bundle renderer for SSR
|
||||||
|
this.bundleRenderer = createBundleRenderer(
|
||||||
|
this.context.resources.serverBundle,
|
||||||
|
Object.assign(
|
||||||
|
{
|
||||||
|
clientManifest: this.context.resources.clientManifest,
|
||||||
|
runInNewContext: false,
|
||||||
|
// for globally installed nuxt command, search dependencies in global dir
|
||||||
|
basedir: hasModules ? this.context.options.rootDir : __dirname
|
||||||
|
},
|
||||||
|
this.context.options.render.bundleRenderer
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTemplate(ssr, opts) {
|
||||||
|
// Fix problem with HTMLPlugin's minify option (#3392)
|
||||||
|
opts.html_attrs = opts.HTML_ATTRS
|
||||||
|
opts.body_attrs = opts.BODY_ATTRS
|
||||||
|
|
||||||
|
const fn = ssr ? this.context.resources.ssrTemplate : this.context.resources.spaTemplate
|
||||||
|
|
||||||
|
return fn(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
async renderRoute(url, context = {}) {
|
||||||
|
/* istanbul ignore if */
|
||||||
|
if (!this.isReady) {
|
||||||
|
await waitFor(1000)
|
||||||
|
return this.renderRoute(url, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log rendered url
|
||||||
|
consola.debug(`Rendering url ${url}`)
|
||||||
|
|
||||||
|
// Add url and isSever to the context
|
||||||
|
context.url = url
|
||||||
|
|
||||||
|
// Basic response if SSR is disabled or spa data provided
|
||||||
|
const spa = context.spa || (context.res && context.res.spa)
|
||||||
|
const ENV = this.context.options.env
|
||||||
|
|
||||||
|
if (this.noSSR || spa) {
|
||||||
|
const {
|
||||||
|
HTML_ATTRS,
|
||||||
|
BODY_ATTRS,
|
||||||
|
HEAD,
|
||||||
|
BODY_SCRIPTS,
|
||||||
|
getPreloadFiles
|
||||||
|
} = await this.spaMetaRenderer.render(context)
|
||||||
|
const APP =
|
||||||
|
`<div id="${this.context.globals.id}">${this.context.resources.loadingHTML}</div>` + BODY_SCRIPTS
|
||||||
|
|
||||||
|
// Detect 404 errors
|
||||||
|
if (
|
||||||
|
url.includes(this.context.options.build.publicPath) ||
|
||||||
|
url.includes('__webpack')
|
||||||
|
) {
|
||||||
|
const err = {
|
||||||
|
statusCode: 404,
|
||||||
|
message: this.context.options.messages.error_404,
|
||||||
|
name: 'ResourceNotFound'
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = this.renderTemplate(false, {
|
||||||
|
HTML_ATTRS,
|
||||||
|
BODY_ATTRS,
|
||||||
|
HEAD,
|
||||||
|
APP,
|
||||||
|
ENV
|
||||||
|
})
|
||||||
|
|
||||||
|
return { html, getPreloadFiles }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call renderToString from the bundleRenderer and generate the HTML (will update the context as well)
|
||||||
|
let APP = await this.bundleRenderer.renderToString(context)
|
||||||
|
|
||||||
|
if (!context.nuxt.serverRendered) {
|
||||||
|
APP = `<div id="${this.context.globals.id}"></div>`
|
||||||
|
}
|
||||||
|
const m = context.meta.inject()
|
||||||
|
let HEAD =
|
||||||
|
m.title.text() +
|
||||||
|
m.meta.text() +
|
||||||
|
m.link.text() +
|
||||||
|
m.style.text() +
|
||||||
|
m.script.text() +
|
||||||
|
m.noscript.text()
|
||||||
|
if (this.context.options._routerBaseSpecified) {
|
||||||
|
HEAD += `<base href="${this.context.options.router.base}">`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.context.options.render.resourceHints) {
|
||||||
|
HEAD += context.renderResourceHints()
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.context.nuxt.callHook('render:routeContext', context.nuxt)
|
||||||
|
|
||||||
|
const serializedSession = `window.${this.context.globals.context}=${devalue(context.nuxt)};`
|
||||||
|
|
||||||
|
const cspScriptSrcHashSet = new Set()
|
||||||
|
if (this.context.options.render.csp) {
|
||||||
|
const { hashAlgorithm } = this.context.options.render.csp
|
||||||
|
const hash = crypto.createHash(hashAlgorithm)
|
||||||
|
hash.update(serializedSession)
|
||||||
|
cspScriptSrcHashSet.add(`'${hashAlgorithm}-${hash.digest('base64')}'`)
|
||||||
|
}
|
||||||
|
|
||||||
|
APP += `<script>${serializedSession}</script>`
|
||||||
|
APP += context.renderScripts()
|
||||||
|
APP += m.script.text({ body: true })
|
||||||
|
APP += m.noscript.text({ body: true })
|
||||||
|
|
||||||
|
HEAD += context.renderStyles()
|
||||||
|
|
||||||
|
const html = this.renderTemplate(true, {
|
||||||
|
HTML_ATTRS: 'data-n-head-ssr ' + m.htmlAttrs.text(),
|
||||||
|
BODY_ATTRS: m.bodyAttrs.text(),
|
||||||
|
HEAD,
|
||||||
|
APP,
|
||||||
|
ENV
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
html,
|
||||||
|
cspScriptSrcHashSet,
|
||||||
|
getPreloadFiles: context.getPreloadFiles,
|
||||||
|
error: context.nuxt.error,
|
||||||
|
redirected: context.redirected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static parseTemplate(templateStr) {
|
||||||
|
return template(templateStr, {
|
||||||
|
interpolate: /{{([\s\S]+?)}}/g
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static get resourceMap() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'clientManifest',
|
||||||
|
fileName: 'vue-ssr-client-manifest.json',
|
||||||
|
transform: JSON.parse
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'serverBundle',
|
||||||
|
fileName: 'server-bundle.json',
|
||||||
|
transform: JSON.parse
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ssrTemplate',
|
||||||
|
fileName: 'index.ssr.html',
|
||||||
|
transform: this.parseTemplate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'spaTemplate',
|
||||||
|
fileName: 'index.spa.html',
|
||||||
|
transform: this.parseTemplate
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -3,11 +3,10 @@ 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'
|
||||||
|
|
||||||
export default class MetaRenderer {
|
export default class SPAMetaRenderer {
|
||||||
constructor(nuxt, renderer) {
|
constructor(renderer) {
|
||||||
this.nuxt = nuxt
|
|
||||||
this.renderer = renderer
|
this.renderer = renderer
|
||||||
this.options = nuxt.options
|
this.options = this.renderer.context.options
|
||||||
this.vueRenderer = createRenderer()
|
this.vueRenderer = createRenderer()
|
||||||
this.cache = LRU({})
|
this.cache = LRU({})
|
||||||
|
|
||||||
@ -69,7 +68,7 @@ export default class MetaRenderer {
|
|||||||
|
|
||||||
meta.resourceHints = ''
|
meta.resourceHints = ''
|
||||||
|
|
||||||
const clientManifest = this.renderer.resources.clientManifest
|
const clientManifest = this.renderer.context.resources.clientManifest
|
||||||
|
|
||||||
const shouldPreload = this.options.render.bundleRenderer.shouldPreload || (() => true)
|
const shouldPreload = this.options.render.bundleRenderer.shouldPreload || (() => true)
|
||||||
const shouldPrefetch = this.options.render.bundleRenderer.shouldPrefetch || (() => true)
|
const shouldPrefetch = this.options.render.bundleRenderer.shouldPrefetch || (() => true)
|
@ -120,7 +120,7 @@ export class WebpackBuilder {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Reload renderer if available
|
// Reload renderer if available
|
||||||
nuxt.renderer.loadResources(this.mfs || fs)
|
nuxt.server.loadResources(this.mfs || fs)
|
||||||
|
|
||||||
// Resolve on next tick
|
// Resolve on next tick
|
||||||
process.nextTick(resolve)
|
process.nextTick(resolve)
|
||||||
@ -166,7 +166,7 @@ export class WebpackBuilder {
|
|||||||
webpackDev(compiler) {
|
webpackDev(compiler) {
|
||||||
consola.debug('Adding webpack middleware...')
|
consola.debug('Adding webpack middleware...')
|
||||||
|
|
||||||
const { nuxt: { renderer }, options } = this.context
|
const { nuxt: { server }, options } = this.context
|
||||||
|
|
||||||
// Create webpack dev middleware
|
// Create webpack dev middleware
|
||||||
this.webpackDevMiddleware = pify(
|
this.webpackDevMiddleware = pify(
|
||||||
@ -200,9 +200,9 @@ export class WebpackBuilder {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Inject to renderer instance
|
// Inject to renderer instance
|
||||||
if (renderer) {
|
if (server) {
|
||||||
renderer.webpackDevMiddleware = this.webpackDevMiddleware
|
server.webpackDevMiddleware = this.webpackDevMiddleware
|
||||||
renderer.webpackHotMiddleware = this.webpackHotMiddleware
|
server.webpackHotMiddleware = this.webpackHotMiddleware
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +257,7 @@ export default class WebpackBaseConfig {
|
|||||||
const hasErrors = Object.values(states).some(state => state.stats.hasErrors())
|
const hasErrors = Object.values(states).some(state => state.stats.hasErrors())
|
||||||
|
|
||||||
if (!hasErrors) {
|
if (!hasErrors) {
|
||||||
this.nuxt.showReady(false)
|
this.nuxt.server.showReady(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,9 @@ export default function rollupConfig({
|
|||||||
return defaultsDeep({}, options, {
|
return defaultsDeep({}, options, {
|
||||||
input: path.resolve(rootDir, input),
|
input: path.resolve(rootDir, input),
|
||||||
output: {
|
output: {
|
||||||
format: 'cjs',
|
|
||||||
sourcemap: false,
|
|
||||||
file: `${pkg.name.replace('-edge', '')}.js`,
|
file: `${pkg.name.replace('-edge', '')}.js`,
|
||||||
dir: path.resolve(rootDir, 'dist')
|
dir: path.resolve(rootDir, 'dist'),
|
||||||
|
format: 'cjs'
|
||||||
},
|
},
|
||||||
preferConst: true,
|
preferConst: true,
|
||||||
external: [
|
external: [
|
||||||
|
@ -13,7 +13,7 @@ describe('basic browser', () => {
|
|||||||
const config = await loadFixture('basic')
|
const config = await loadFixture('basic')
|
||||||
nuxt = new Nuxt(config)
|
nuxt = new Nuxt(config)
|
||||||
port = await getPort()
|
port = await getPort()
|
||||||
await nuxt.listen(port, 'localhost')
|
await nuxt.server.listen(port, 'localhost')
|
||||||
|
|
||||||
await browser.start({
|
await browser.start({
|
||||||
// slowMo: 50,
|
// slowMo: 50,
|
||||||
|
@ -12,7 +12,7 @@ const startServer = async (type = 'basic') => {
|
|||||||
const config = await loadFixture(type)
|
const config = await loadFixture(type)
|
||||||
nuxt = new Nuxt(config)
|
nuxt = new Nuxt(config)
|
||||||
port = await getPort()
|
port = await getPort()
|
||||||
await nuxt.listen(port, 'localhost')
|
await nuxt.server.listen(port, 'localhost')
|
||||||
|
|
||||||
return nuxt
|
return nuxt
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ describe('children patch (browser)', () => {
|
|||||||
const options = await loadFixture('children')
|
const options = await loadFixture('children')
|
||||||
nuxt = new Nuxt(options)
|
nuxt = new Nuxt(options)
|
||||||
port = await getPort()
|
port = await getPort()
|
||||||
await nuxt.listen(port, 'localhost')
|
await nuxt.server.listen(port, 'localhost')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Start browser', async () => {
|
test('Start browser', async () => {
|
||||||
|
2
test/fixtures/cli/cli.build.test.js
vendored
2
test/fixtures/cli/cli.build.test.js
vendored
@ -8,7 +8,7 @@ const nuxtBin = resolve(__dirname, '../../../packages/cli/bin/nuxt.js')
|
|||||||
|
|
||||||
describe('cli build', () => {
|
describe('cli build', () => {
|
||||||
test('nuxt build', async () => {
|
test('nuxt build', async () => {
|
||||||
const { stdout } = await execify(`node ${nuxtBin} build ${rootDir} -c cli.build.config.js`)
|
const { stdout } = await execify(`node -r esm ${nuxtBin} build ${rootDir} -c cli.build.config.js`)
|
||||||
|
|
||||||
expect(stdout.includes('Compiled successfully')).toBe(true)
|
expect(stdout.includes('Compiled successfully')).toBe(true)
|
||||||
}, 80000)
|
}, 80000)
|
||||||
|
@ -8,12 +8,12 @@ describe('basic ssr', () => {
|
|||||||
const options = await loadFixture('async-config')
|
const options = await loadFixture('async-config')
|
||||||
nuxt = new Nuxt(options)
|
nuxt = new Nuxt(options)
|
||||||
port = await getPort()
|
port = await getPort()
|
||||||
await nuxt.listen(port, '0.0.0.0')
|
await nuxt.server.listen(port, '0.0.0.0')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/', async () => {
|
test('/', async () => {
|
||||||
expect(nuxt.options.head.title).toBe('Async Config!')
|
expect(nuxt.options.head.title).toBe('Async Config!')
|
||||||
const { html } = await nuxt.renderRoute('/')
|
const { html } = await nuxt.server.renderRoute('/')
|
||||||
expect(html.includes('<h1>I am ALIVE!</h1>')).toBe(true)
|
expect(html.includes('<h1>I am ALIVE!</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
|
|
||||||
import { Nuxt, Options, version } from '../utils'
|
import { Nuxt, getNuxtConfig, version } from '../utils'
|
||||||
|
|
||||||
describe('basic config defaults', () => {
|
describe('basic config defaults', () => {
|
||||||
test('Nuxt.version is same as package', () => {
|
test('Nuxt.version is same as package', () => {
|
||||||
@ -9,13 +9,13 @@ describe('basic config defaults', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('modulesDir uses /node_modules as default if not set', () => {
|
test('modulesDir uses /node_modules as default if not set', () => {
|
||||||
const options = Options.from({})
|
const options = getNuxtConfig({})
|
||||||
const currentNodeModulesDir = resolve(__dirname, '..', '..', 'node_modules')
|
const currentNodeModulesDir = resolve(__dirname, '..', '..', 'node_modules')
|
||||||
expect(options.modulesDir.includes(currentNodeModulesDir)).toBe(true)
|
expect(options.modulesDir.includes(currentNodeModulesDir)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('vendor has been deprecated', () => {
|
test('vendor has been deprecated', () => {
|
||||||
const options = Options.from({
|
const options = getNuxtConfig({
|
||||||
build: { vendor: 'vue' }
|
build: { vendor: 'vue' }
|
||||||
})
|
})
|
||||||
expect(options.build.vendor).toBeUndefined()
|
expect(options.build.vendor).toBeUndefined()
|
||||||
@ -23,18 +23,18 @@ describe('basic config defaults', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('globalName uses nuxt as default if not set', () => {
|
test('globalName uses nuxt as default if not set', () => {
|
||||||
const options = Options.from({})
|
const options = getNuxtConfig({})
|
||||||
expect(options.globalName).toEqual('nuxt')
|
expect(options.globalName).toEqual('nuxt')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('globalName uses nuxt as default if set to something other than only letters', () => {
|
test('globalName uses nuxt as default if set to something other than only letters', () => {
|
||||||
let options = Options.from({ globalName: '12foo4' })
|
let options = getNuxtConfig({ globalName: '12foo4' })
|
||||||
expect(options.globalName).toEqual('nuxt')
|
expect(options.globalName).toEqual('nuxt')
|
||||||
|
|
||||||
options = Options.from({ globalName: 'foo bar' })
|
options = getNuxtConfig({ globalName: 'foo bar' })
|
||||||
expect(options.globalName).toEqual('nuxt')
|
expect(options.globalName).toEqual('nuxt')
|
||||||
|
|
||||||
options = Options.from({ globalName: 'foo?' })
|
options = getNuxtConfig({ globalName: 'foo?' })
|
||||||
expect(options.globalName).toEqual('nuxt')
|
expect(options.globalName).toEqual('nuxt')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -48,7 +48,7 @@ describe('basic dev', () => {
|
|||||||
builder = new Builder(nuxt, BundleBuilder)
|
builder = new Builder(nuxt, BundleBuilder)
|
||||||
await builder.build()
|
await builder.build()
|
||||||
port = await getPort()
|
port = await getPort()
|
||||||
await nuxt.listen(port, 'localhost')
|
await nuxt.server.listen(port, 'localhost')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Check build:done hook called', () => {
|
test('Check build:done hook called', () => {
|
||||||
@ -83,7 +83,7 @@ describe('basic dev', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/stateless', async () => {
|
test('/stateless', async () => {
|
||||||
const window = await nuxt.renderAndGetWindow(url('/stateless'))
|
const window = await nuxt.server.renderAndGetWindow(url('/stateless'))
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
expect(html.includes('<h1>My component!</h1>')).toBe(true)
|
expect(html.includes('<h1>My component!</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
@ -117,7 +117,7 @@ describe('basic dev', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/error should return error stack trace (Youch)', async () => {
|
test('/error should return error stack trace (Youch)', async () => {
|
||||||
await expect(nuxt.renderAndGetWindow(url('/error'))).rejects.toMatchObject({
|
await expect(nuxt.server.renderAndGetWindow(url('/error'))).rejects.toMatchObject({
|
||||||
statusCode: 500
|
statusCode: 500
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -126,7 +126,7 @@ describe('basic dev', () => {
|
|||||||
const sourceMaps = nuxt.renderer.resources.serverBundle.maps
|
const sourceMaps = nuxt.renderer.resources.serverBundle.maps
|
||||||
nuxt.renderer.resources.serverBundle.maps = {}
|
nuxt.renderer.resources.serverBundle.maps = {}
|
||||||
|
|
||||||
await expect(nuxt.renderAndGetWindow(url('/error'))).rejects.toMatchObject({
|
await expect(nuxt.server.renderAndGetWindow(url('/error'))).rejects.toMatchObject({
|
||||||
statusCode: 500
|
statusCode: 500
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -85,19 +85,19 @@ describe('basic generate', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/stateless', async () => {
|
test('/stateless', async () => {
|
||||||
const window = await generator.nuxt.renderAndGetWindow(url('/stateless'))
|
const window = await generator.nuxt.server.renderAndGetWindow(url('/stateless'))
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
expect(html.includes('<h1>My component!</h1>')).toBe(true)
|
expect(html.includes('<h1>My component!</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/store-module', async () => {
|
test('/store-module', async () => {
|
||||||
const window = await generator.nuxt.renderAndGetWindow(url('/store-module'))
|
const window = await generator.nuxt.server.renderAndGetWindow(url('/store-module'))
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
expect(html.includes('<h1>mutated</h1>')).toBe(true)
|
expect(html.includes('<h1>mutated</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/css', async () => {
|
test('/css', async () => {
|
||||||
const window = await generator.nuxt.renderAndGetWindow(url('/css'))
|
const window = await generator.nuxt.server.renderAndGetWindow(url('/css'))
|
||||||
|
|
||||||
const headHtml = window.document.head.innerHTML
|
const headHtml = window.document.head.innerHTML
|
||||||
expect(headHtml.includes('.red{color:red')).toBe(true)
|
expect(headHtml.includes('.red{color:red')).toBe(true)
|
||||||
@ -110,13 +110,13 @@ describe('basic generate', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/stateful', async () => {
|
test('/stateful', async () => {
|
||||||
const window = await generator.nuxt.renderAndGetWindow(url('/stateful'))
|
const window = await generator.nuxt.server.renderAndGetWindow(url('/stateful'))
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
expect(html.includes('<div><p>The answer is 42</p></div>')).toBe(true)
|
expect(html.includes('<div><p>The answer is 42</p></div>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/head', async () => {
|
test('/head', async () => {
|
||||||
const window = await generator.nuxt.renderAndGetWindow(url('/head'))
|
const window = await generator.nuxt.server.renderAndGetWindow(url('/head'))
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
const metas = window.document.getElementsByTagName('meta')
|
const metas = window.document.getElementsByTagName('meta')
|
||||||
expect(window.document.title).toBe('My title - Nuxt.js')
|
expect(window.document.title).toBe('My title - Nuxt.js')
|
||||||
@ -125,7 +125,7 @@ describe('basic generate', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/async-data', async () => {
|
test('/async-data', async () => {
|
||||||
const window = await generator.nuxt.renderAndGetWindow(url('/async-data'))
|
const window = await generator.nuxt.server.renderAndGetWindow(url('/async-data'))
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
expect(html.includes('<p>Nuxt.js</p>')).toBe(true)
|
expect(html.includes('<p>Nuxt.js</p>')).toBe(true)
|
||||||
})
|
})
|
||||||
@ -165,13 +165,13 @@ describe('basic generate', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/validate -> should display a 404', async () => {
|
test('/validate -> should display a 404', async () => {
|
||||||
const window = await generator.nuxt.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.includes('This page could not be found')).toBe(true)
|
expect(html.includes('This page could not be found')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/validate?valid=true', async () => {
|
test('/validate?valid=true', async () => {
|
||||||
const window = await generator.nuxt.renderAndGetWindow(url('/validate?valid=true'))
|
const window = await generator.nuxt.server.renderAndGetWindow(url('/validate?valid=true'))
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
expect(html.includes('I am valid</h1>')).toBe(true)
|
expect(html.includes('I am valid</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
@ -183,7 +183,7 @@ describe('basic generate', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/redirect -> check redirected source', async () => {
|
test('/redirect -> check redirected source', async () => {
|
||||||
const window = await generator.nuxt.renderAndGetWindow(url('/redirect'))
|
const window = await generator.nuxt.server.renderAndGetWindow(url('/redirect'))
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
expect(html.includes('<h1>Index page</h1>')).toBe(true)
|
expect(html.includes('<h1>Index page</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
@ -10,11 +10,11 @@ describe('with-config', () => {
|
|||||||
const config = await loadFixture('basic')
|
const config = await loadFixture('basic')
|
||||||
nuxt = new Nuxt(config)
|
nuxt = new Nuxt(config)
|
||||||
port = await getPort()
|
port = await getPort()
|
||||||
await nuxt.listen(port, 'localhost')
|
await nuxt.server.listen(port, 'localhost')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/', async () => {
|
test('/', async () => {
|
||||||
const window = await nuxt.renderAndGetWindow(url('/'))
|
const window = await nuxt.server.renderAndGetWindow(url('/'))
|
||||||
expect(window.__test_plugin).toBe(true)
|
expect(window.__test_plugin).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ const startCspServer = async (csp, isProduction = true) => {
|
|||||||
})
|
})
|
||||||
const nuxt = new Nuxt(options)
|
const nuxt = new Nuxt(options)
|
||||||
port = await getPort()
|
port = await getPort()
|
||||||
await nuxt.listen(port, '0.0.0.0')
|
await nuxt.server.listen(port, '0.0.0.0')
|
||||||
return nuxt
|
return nuxt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,16 +11,16 @@ describe('basic ssr', () => {
|
|||||||
const options = await loadFixture('basic')
|
const options = await loadFixture('basic')
|
||||||
nuxt = new Nuxt(options)
|
nuxt = new Nuxt(options)
|
||||||
port = await getPort()
|
port = await getPort()
|
||||||
await nuxt.listen(port, '0.0.0.0')
|
await nuxt.server.listen(port, '0.0.0.0')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/stateless', async () => {
|
test('/stateless', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/stateless')
|
const { html } = await nuxt.server.renderRoute('/stateless')
|
||||||
expect(html.includes('<h1>My component!</h1>')).toBe(true)
|
expect(html.includes('<h1>My component!</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/store-module', async () => {
|
test('/store-module', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/store-module')
|
const { html } = await nuxt.server.renderRoute('/store-module')
|
||||||
expect(html.includes('<h1>mutated</h1>')).toBe(true)
|
expect(html.includes('<h1>mutated</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ describe('basic ssr', () => {
|
|||||||
** Example of testing via dom checking
|
** Example of testing via dom checking
|
||||||
*/
|
*/
|
||||||
test('/css', async () => {
|
test('/css', async () => {
|
||||||
const window = await nuxt.renderAndGetWindow(url('/css'))
|
const window = await nuxt.server.renderAndGetWindow(url('/css'))
|
||||||
|
|
||||||
const headHtml = window.document.head.innerHTML
|
const headHtml = window.document.head.innerHTML
|
||||||
expect(headHtml.includes('color:red')).toBe(true)
|
expect(headHtml.includes('color:red')).toBe(true)
|
||||||
@ -41,7 +41,7 @@ describe('basic ssr', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/postcss', async () => {
|
test('/postcss', async () => {
|
||||||
const window = await nuxt.renderAndGetWindow(url('/css'))
|
const window = await nuxt.server.renderAndGetWindow(url('/css'))
|
||||||
|
|
||||||
const headHtml = window.document.head.innerHTML
|
const headHtml = window.document.head.innerHTML
|
||||||
expect(headHtml.includes('background-color:#00f')).toBe(true)
|
expect(headHtml.includes('background-color:#00f')).toBe(true)
|
||||||
@ -51,18 +51,18 @@ describe('basic ssr', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/stateful', async () => {
|
test('/stateful', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/stateful')
|
const { html } = await nuxt.server.renderRoute('/stateful')
|
||||||
expect(html.includes('<div><p>The answer is 42</p></div>')).toBe(true)
|
expect(html.includes('<div><p>The answer is 42</p></div>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/store', async () => {
|
test('/store', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/store')
|
const { html } = await nuxt.server.renderRoute('/store')
|
||||||
expect(html.includes('<h1>Vuex Nested Modules</h1>')).toBe(true)
|
expect(html.includes('<h1>Vuex Nested Modules</h1>')).toBe(true)
|
||||||
expect(html.includes('<p>1</p>')).toBe(true)
|
expect(html.includes('<p>1</p>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/head', async () => {
|
test('/head', async () => {
|
||||||
const window = await nuxt.renderAndGetWindow(url('/head'))
|
const window = await nuxt.server.renderAndGetWindow(url('/head'))
|
||||||
expect(window.document.title).toBe('My title - Nuxt.js')
|
expect(window.document.title).toBe('My title - Nuxt.js')
|
||||||
|
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
@ -77,64 +77,64 @@ describe('basic ssr', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/async-data', async () => {
|
test('/async-data', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/async-data')
|
const { html } = await nuxt.server.renderRoute('/async-data')
|
||||||
expect(html.includes('<p>Nuxt.js</p>')).toBe(true)
|
expect(html.includes('<p>Nuxt.js</p>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/await-async-data', async () => {
|
test('/await-async-data', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/await-async-data')
|
const { html } = await nuxt.server.renderRoute('/await-async-data')
|
||||||
expect(html.includes('<p>Await Nuxt.js</p>')).toBe(true)
|
expect(html.includes('<p>Await Nuxt.js</p>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/callback-async-data', async () => {
|
test('/callback-async-data', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/callback-async-data')
|
const { html } = await nuxt.server.renderRoute('/callback-async-data')
|
||||||
expect(html.includes('<p>Callback Nuxt.js</p>')).toBe(true)
|
expect(html.includes('<p>Callback Nuxt.js</p>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/users/1', async () => {
|
test('/users/1', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/users/1')
|
const { html } = await nuxt.server.renderRoute('/users/1')
|
||||||
expect(html.includes('<h1>User: 1</h1>')).toBe(true)
|
expect(html.includes('<h1>User: 1</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/validate should display a 404', async () => {
|
test('/validate should display a 404', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/validate')
|
const { html } = await nuxt.server.renderRoute('/validate')
|
||||||
expect(html.includes('This page could not be found')).toBe(true)
|
expect(html.includes('This page could not be found')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/validate-async should display a 404', async () => {
|
test('/validate-async should display a 404', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/validate-async')
|
const { html } = await nuxt.server.renderRoute('/validate-async')
|
||||||
expect(html.includes('This page could not be found')).toBe(true)
|
expect(html.includes('This page could not be found')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/validate?valid=true', async () => {
|
test('/validate?valid=true', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/validate?valid=true')
|
const { html } = await nuxt.server.renderRoute('/validate?valid=true')
|
||||||
expect(html.includes('<h1>I am valid</h1>')).toBe(true)
|
expect(html.includes('<h1>I am valid</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/validate-async?valid=true', async () => {
|
test('/validate-async?valid=true', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/validate-async?valid=true')
|
const { html } = await nuxt.server.renderRoute('/validate-async?valid=true')
|
||||||
expect(html.includes('<h1>I am valid</h1>')).toBe(true)
|
expect(html.includes('<h1>I am valid</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/validate?error=403', async () => {
|
test('/validate?error=403', async () => {
|
||||||
const { html, error } = await nuxt.renderRoute('/validate?error=403')
|
const { html, error } = await nuxt.server.renderRoute('/validate?error=403')
|
||||||
expect(error).toMatchObject({ statusCode: 403, message: 'Custom Error' })
|
expect(error).toMatchObject({ statusCode: 403, message: 'Custom Error' })
|
||||||
expect(html.includes('Custom Error')).toBe(true)
|
expect(html.includes('Custom Error')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/validate-async?error=503', async () => {
|
test('/validate-async?error=503', async () => {
|
||||||
const { html, error } = await nuxt.renderRoute('/validate-async?error=503')
|
const { html, error } = await nuxt.server.renderRoute('/validate-async?error=503')
|
||||||
expect(error).toMatchObject({ statusCode: 503, message: 'Custom Error' })
|
expect(error).toMatchObject({ statusCode: 503, message: 'Custom Error' })
|
||||||
expect(html.includes('Custom Error')).toBe(true)
|
expect(html.includes('Custom Error')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/before-enter', async () => {
|
test('/before-enter', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/before-enter')
|
const { html } = await nuxt.server.renderRoute('/before-enter')
|
||||||
expect(html.includes('<h1>Index page</h1>')).toBe(true)
|
expect(html.includes('<h1>Index page</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/redirect', async () => {
|
test('/redirect', async () => {
|
||||||
const { html, redirected } = await nuxt.renderRoute('/redirect')
|
const { html, redirected } = await nuxt.server.renderRoute('/redirect')
|
||||||
expect(html.includes('<div id="__nuxt"></div>')).toBe(true)
|
expect(html.includes('<div id="__nuxt"></div>')).toBe(true)
|
||||||
expect(redirected.path === '/').toBe(true)
|
expect(redirected.path === '/').toBe(true)
|
||||||
expect(redirected.status === 302).toBe(true)
|
expect(redirected.status === 302).toBe(true)
|
||||||
@ -142,14 +142,14 @@ describe('basic ssr', () => {
|
|||||||
|
|
||||||
test('/redirect -> check redirected source', async () => {
|
test('/redirect -> check redirected source', async () => {
|
||||||
// there are no transition properties in jsdom, ignore the error log
|
// there are no transition properties in jsdom, ignore the error log
|
||||||
const window = await nuxt.renderAndGetWindow(url('/redirect'))
|
const window = await nuxt.server.renderAndGetWindow(url('/redirect'))
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
expect(html.includes('<h1>Index page</h1>')).toBe(true)
|
expect(html.includes('<h1>Index page</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/redirect -> external link', async () => {
|
test('/redirect -> external link', async () => {
|
||||||
let _headers, _status
|
let _headers, _status
|
||||||
const { html } = await nuxt.renderRoute('/redirect-external', {
|
const { html } = await nuxt.server.renderRoute('/redirect-external', {
|
||||||
res: {
|
res: {
|
||||||
writeHead(status, headers) {
|
writeHead(status, headers) {
|
||||||
_status = status
|
_status = status
|
||||||
@ -164,13 +164,13 @@ describe('basic ssr', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/special-state -> check window.__NUXT__.test = true', async () => {
|
test('/special-state -> check window.__NUXT__.test = true', async () => {
|
||||||
const window = await nuxt.renderAndGetWindow(url('/special-state'))
|
const window = await nuxt.server.renderAndGetWindow(url('/special-state'))
|
||||||
expect(window.document.title).toBe('Nuxt.js')
|
expect(window.document.title).toBe('Nuxt.js')
|
||||||
expect(window.__NUXT__.test).toBe(true)
|
expect(window.__NUXT__.test).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/error', async () => {
|
test('/error', async () => {
|
||||||
await expect(nuxt.renderRoute('/error', { req: {}, res: {} }))
|
await expect(nuxt.server.renderRoute('/error', { req: {}, res: {} }))
|
||||||
.rejects.toThrow('Error mouahahah')
|
.rejects.toThrow('Error mouahahah')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ describe('basic ssr', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/error2', async () => {
|
test('/error2', async () => {
|
||||||
const { html, error } = await nuxt.renderRoute('/error2')
|
const { html, error } = await nuxt.server.renderRoute('/error2')
|
||||||
expect(html.includes('Custom error')).toBe(true)
|
expect(html.includes('Custom error')).toBe(true)
|
||||||
expect(error.message.includes('Custom error')).toBe(true)
|
expect(error.message.includes('Custom error')).toBe(true)
|
||||||
expect(error.statusCode === undefined).toBe(true)
|
expect(error.statusCode === undefined).toBe(true)
|
||||||
@ -220,21 +220,21 @@ describe('basic ssr', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/redirect-name', async () => {
|
test('/redirect-name', async () => {
|
||||||
const { html, redirected } = await nuxt.renderRoute('/redirect-name')
|
const { html, redirected } = await nuxt.server.renderRoute('/redirect-name')
|
||||||
expect(html.includes('<div id="__nuxt"></div>')).toBe(true)
|
expect(html.includes('<div id="__nuxt"></div>')).toBe(true)
|
||||||
expect(redirected.path === '/stateless').toBe(true)
|
expect(redirected.path === '/stateless').toBe(true)
|
||||||
expect(redirected.status === 302).toBe(true)
|
expect(redirected.status === 302).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/no-ssr', async () => {
|
test('/no-ssr', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/no-ssr')
|
const { html } = await nuxt.server.renderRoute('/no-ssr')
|
||||||
expect(html.includes(
|
expect(html.includes(
|
||||||
'<p class="no-ssr-placeholder">Loading...</p>'
|
'<p class="no-ssr-placeholder">Loading...</p>'
|
||||||
)).toBe(true)
|
)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/no-ssr (client-side)', async () => {
|
test('/no-ssr (client-side)', async () => {
|
||||||
const window = await nuxt.renderAndGetWindow(url('/no-ssr'))
|
const window = await nuxt.server.renderAndGetWindow(url('/no-ssr'))
|
||||||
const html = window.document.body.innerHTML
|
const html = window.document.body.innerHTML
|
||||||
expect(html.includes('Displayed only on client-side</h1>')).toBe(true)
|
expect(html.includes('Displayed only on client-side</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
@ -259,7 +259,7 @@ describe('basic ssr', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/meta', async () => {
|
test('/meta', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/meta')
|
const { html } = await nuxt.server.renderRoute('/meta')
|
||||||
expect(/<pre>.*"works": true.*<\/pre>/s.test(html)).toBe(true)
|
expect(/<pre>.*"works": true.*<\/pre>/s.test(html)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -269,28 +269,28 @@ describe('basic ssr', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('/fn-midd?please=true', async () => {
|
test('/fn-midd?please=true', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/fn-midd?please=true')
|
const { html } = await nuxt.server.renderRoute('/fn-midd?please=true')
|
||||||
expect(html.includes('<h1>Date:')).toBe(true)
|
expect(html.includes('<h1>Date:')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/router-guard', async () => {
|
test('/router-guard', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/router-guard')
|
const { html } = await nuxt.server.renderRoute('/router-guard')
|
||||||
expect(html.includes('<p>Nuxt.js</p>')).toBe(true)
|
expect(html.includes('<p>Nuxt.js</p>')).toBe(true)
|
||||||
expect(html.includes('Router Guard')).toBe(false)
|
expect(html.includes('Router Guard')).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/jsx', async () => {
|
test('/jsx', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/jsx')
|
const { html } = await nuxt.server.renderRoute('/jsx')
|
||||||
expect(html.includes('<h1>JSX Page</h1>')).toBe(true)
|
expect(html.includes('<h1>JSX Page</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/jsx-link', async () => {
|
test('/jsx-link', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/jsx-link')
|
const { html } = await nuxt.server.renderRoute('/jsx-link')
|
||||||
expect(html.includes('<h1>JSX Link Page</h1>')).toBe(true)
|
expect(html.includes('<h1>JSX Link Page</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/js-link', async () => {
|
test('/js-link', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/js-link')
|
const { html } = await nuxt.server.renderRoute('/js-link')
|
||||||
expect(html.includes('<h1>vue file is first-class</h1>')).toBe(true)
|
expect(html.includes('<h1>vue file is first-class</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -10,39 +10,39 @@ describe('children', () => {
|
|||||||
const options = await loadFixture('children')
|
const options = await loadFixture('children')
|
||||||
nuxt = new Nuxt(options)
|
nuxt = new Nuxt(options)
|
||||||
port = await getPort()
|
port = await getPort()
|
||||||
await nuxt.listen(port, 'localhost')
|
await nuxt.server.listen(port, 'localhost')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/parent', async () => {
|
test('/parent', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/parent')
|
const { html } = await nuxt.server.renderRoute('/parent')
|
||||||
expect(html.includes('<h1>I am the parent</h1>')).toBe(true)
|
expect(html.includes('<h1>I am the parent</h1>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/parent/child', async () => {
|
test('/parent/child', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/parent/child')
|
const { html } = await nuxt.server.renderRoute('/parent/child')
|
||||||
expect(html.includes('<h1>I am the parent</h1>')).toBe(true)
|
expect(html.includes('<h1>I am the parent</h1>')).toBe(true)
|
||||||
expect(html.includes('<h2>I am the child</h2>')).toBe(true)
|
expect(html.includes('<h2>I am the child</h2>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/parent should call _id.vue', async () => {
|
test('/parent should call _id.vue', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/parent')
|
const { html } = await nuxt.server.renderRoute('/parent')
|
||||||
expect(html.includes('<h1>I am the parent</h1>')).toBe(true)
|
expect(html.includes('<h1>I am the parent</h1>')).toBe(true)
|
||||||
expect(html.includes('<h2>Id=</h2>')).toBe(true)
|
expect(html.includes('<h2>Id=</h2>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/parent/1', async () => {
|
test('/parent/1', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/parent/1')
|
const { html } = await nuxt.server.renderRoute('/parent/1')
|
||||||
expect(html.includes('<h1>I am the parent</h1>')).toBe(true)
|
expect(html.includes('<h1>I am the parent</h1>')).toBe(true)
|
||||||
expect(html.includes('<h2>Id=1</h2>')).toBe(true)
|
expect(html.includes('<h2>Id=1</h2>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/parent/validate-child should display 404', async () => {
|
test('/parent/validate-child should display 404', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/parent/validate-child')
|
const { html } = await nuxt.server.renderRoute('/parent/validate-child')
|
||||||
expect(html.includes('This page could not be found')).toBe(true)
|
expect(html.includes('This page could not be found')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('/parent/validate-child?key=12345', async () => {
|
test('/parent/validate-child?key=12345', async () => {
|
||||||
const { html } = await nuxt.renderRoute('/parent/validate-child?key=12345')
|
const { html } = await nuxt.server.renderRoute('/parent/validate-child?key=12345')
|
||||||
expect(html.includes('<h1>I am the parent</h1>')).toBe(true)
|
expect(html.includes('<h1>I am the parent</h1>')).toBe(true)
|
||||||
expect(html.includes('<h2>Child valid</h2>')).toBe(true)
|
expect(html.includes('<h2>Child valid</h2>')).toBe(true)
|
||||||
})
|
})
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user