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
|
||||
command: yarn --frozen-lockfile --non-interactive
|
||||
|
||||
# Link
|
||||
- run:
|
||||
name: Link
|
||||
command: yarn lerna link
|
||||
|
||||
# Save cache
|
||||
- save_cache:
|
||||
key: yarn-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- node_modules
|
||||
- distributions/*/node_modules
|
||||
- packages/*/node_modules
|
||||
- distributions/*/node_modules
|
||||
|
||||
# Persist workspace
|
||||
- persist_to_workspace:
|
||||
root: ~/project
|
||||
paths:
|
||||
- node_modules
|
||||
- distributions/*/node_modules
|
||||
- packages/*/node_modules
|
||||
- distributions/*/node_modules
|
||||
- packages/*/dist
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Phase 2: Lint + Audit + Build Nuxt and fixtures
|
||||
@ -76,18 +72,13 @@ jobs:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: ~/project
|
||||
- run:
|
||||
name: Build Nuxt
|
||||
command: yarn build
|
||||
- run:
|
||||
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:
|
||||
root: ~/project
|
||||
paths:
|
||||
- test/fixtures # TODO
|
||||
- distributions/**/dist
|
||||
- packages/**/dist
|
||||
- test/fixtures
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Phase 3: Unit and E2E tests
|
||||
|
@ -1,8 +1,16 @@
|
||||
app
|
||||
!app/store.js
|
||||
# Common
|
||||
node_modules
|
||||
dist
|
||||
.nuxt
|
||||
examples/coffeescript/pages/index.vue
|
||||
!examples/storybook/.storybook
|
||||
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
|
||||
displayName: 'Install dependencies'
|
||||
|
||||
- script: |
|
||||
yarn build
|
||||
displayName: 'Build Nuxt'
|
||||
|
||||
- script: |
|
||||
yarn test:fixtures -w=2
|
||||
displayName: 'Test: Build Fixtures'
|
||||
|
@ -15,20 +15,20 @@ test.before(async () => {
|
||||
}
|
||||
nuxt = new Nuxt(config)
|
||||
await new Builder(nuxt).build()
|
||||
await nuxt.listen(4000, 'localhost')
|
||||
await nuxt.server.listen(4000, 'localhost')
|
||||
}, 30000)
|
||||
|
||||
// Example of testing only generated html
|
||||
test('Route / exits and render HTML', async (t) => {
|
||||
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>'))
|
||||
})
|
||||
|
||||
// Example of testing via dom checking
|
||||
test('Route / exits and render HTML with CSS applied', async (t) => {
|
||||
const context = {}
|
||||
const { html } = await nuxt.renderRoute('/', context)
|
||||
const { html } = await nuxt.server.renderRoute('/', context)
|
||||
const { window } = new JSDOM(html).window
|
||||
const element = window.document.querySelector('.red')
|
||||
t.not(element, null)
|
||||
|
@ -5,8 +5,8 @@ const server = http.createServer(this.nuxt.renderer.app)
|
||||
const io = socketIO(server)
|
||||
|
||||
export default function () {
|
||||
// overwrite nuxt.listen()
|
||||
this.nuxt.listen = (port, host) => new Promise(resolve => server.listen(port || 3000, host || 'localhost', resolve))
|
||||
// overwrite nuxt.server.listen()
|
||||
this.nuxt.server.listen = (port, host) => new Promise(resolve => server.listen(port || 3000, host || 'localhost', resolve))
|
||||
// close this server on 'close' event
|
||||
this.nuxt.hook('close', () => new Promise(server.close))
|
||||
|
||||
|
@ -14,32 +14,29 @@ module.exports = {
|
||||
coverageDirectory: './coverage',
|
||||
|
||||
collectCoverageFrom: [
|
||||
'packages/*/src/**/*.js',
|
||||
'packages/cli/bin/*'
|
||||
'**/packages/*/src/**/*.js'
|
||||
],
|
||||
|
||||
coveragePathIgnorePatterns: [
|
||||
'node_modules',
|
||||
'packages/app',
|
||||
'packages/webpack/plugins/vue'
|
||||
'node_modules/(?!(@nuxt|nuxt))',
|
||||
'packages/webpack/src/config/plugins/vue'
|
||||
],
|
||||
|
||||
testPathIgnorePatterns: [
|
||||
'node_modules',
|
||||
'node_modules/(?!(@nuxt|nuxt))',
|
||||
'test/fixtures/.*/.*?/',
|
||||
'examples/.*'
|
||||
],
|
||||
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(@nuxt|nuxt))'
|
||||
],
|
||||
|
||||
transform: {
|
||||
'^.+\\.js$': 'babel-jest',
|
||||
'.*\\.(vue)$': 'vue-jest'
|
||||
},
|
||||
|
||||
transformIgnorePatterns: [
|
||||
'/node_modules/',
|
||||
'/dist/'
|
||||
],
|
||||
|
||||
moduleFileExtensions: [
|
||||
'js',
|
||||
'json'
|
||||
|
@ -10,7 +10,7 @@
|
||||
"clean:build": "rimraf distributions/*/dist packages/*/dist",
|
||||
"clean:examples": "rimraf examples/*/dist examples/*/.nuxt",
|
||||
"clean:test": "rimraf test/fixtures/*/dist test/fixtures/*/.nuxt*",
|
||||
"dev": "yarn build --watch",
|
||||
"dev": "node -r esm ./scripts/dev",
|
||||
"coverage": "codecov",
|
||||
"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",
|
||||
@ -20,7 +20,7 @@
|
||||
"test:e2e": "jest -i test/e2e",
|
||||
"test:lint": "yarn lint",
|
||||
"test:unit": "jest test/unit",
|
||||
"postinstall": "lerna link && node -r esm ./scripts/dev"
|
||||
"postinstall": "lerna link && yarn dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.2",
|
||||
|
@ -8,8 +8,8 @@
|
||||
],
|
||||
"main": "dist/builder.js",
|
||||
"dependencies": {
|
||||
"@nuxt/app": "^2.2.0",
|
||||
"@nuxt/common": "^2.2.0",
|
||||
"@nuxt/vue-app": "^2.2.0",
|
||||
"@nuxtjs/devalue": "^1.0.1",
|
||||
"chokidar": "^2.0.4",
|
||||
"consola": "^1.4.4",
|
||||
|
@ -20,8 +20,6 @@ import values from 'lodash/values'
|
||||
import devalue from '@nuxtjs/devalue'
|
||||
|
||||
import {
|
||||
Options,
|
||||
BuildContext,
|
||||
r,
|
||||
wp,
|
||||
wChunk,
|
||||
@ -33,6 +31,8 @@ import {
|
||||
isString
|
||||
} from '@nuxt/common'
|
||||
|
||||
import BuildContext from './context'
|
||||
|
||||
const glob = pify(Glob)
|
||||
|
||||
export default class Builder {
|
||||
@ -69,7 +69,7 @@ export default class Builder {
|
||||
}
|
||||
|
||||
// Resolve template
|
||||
this.template = this.options.build.template || '@nuxt/app'
|
||||
this.template = this.options.build.template || '@nuxt/vue-app'
|
||||
if (typeof this.template === 'string') {
|
||||
this.template = this.nuxt.resolver.requireModule(this.template)
|
||||
}
|
||||
@ -476,16 +476,16 @@ export default class Builder {
|
||||
consola.success('Nuxt files generated')
|
||||
}
|
||||
|
||||
// TODO: remove ignore when generateConfig enabled again
|
||||
async generateConfig() /* istanbul ignore next */ {
|
||||
const config = path.resolve(this.options.buildDir, 'build.config.js')
|
||||
const options = omit(this.options, Options.unsafeKeys)
|
||||
await fsExtra.writeFile(
|
||||
config,
|
||||
`export default ${JSON.stringify(options, null, ' ')}`,
|
||||
'utf8'
|
||||
)
|
||||
}
|
||||
// TODO: Uncomment when generateConfig enabled again
|
||||
// async generateConfig() /* istanbul ignore next */ {
|
||||
// const config = path.resolve(this.options.buildDir, 'build.config.js')
|
||||
// const options = omit(this.options, Options.unsafeKeys)
|
||||
// await fsExtra.writeFile(
|
||||
// config,
|
||||
// `export default ${JSON.stringify(options, null, ' ')}`,
|
||||
// 'utf8'
|
||||
// )
|
||||
// }
|
||||
|
||||
watchClient() {
|
||||
const src = this.options.srcDir
|
||||
|
@ -48,9 +48,9 @@ export default {
|
||||
})
|
||||
.then(() => oldInstance && oldInstance.nuxt.close())
|
||||
// Start listening
|
||||
.then(() => nuxt.listen())
|
||||
.then(() => nuxt.server.listen())
|
||||
// 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())
|
||||
// Handle errors
|
||||
.catch(err => errorHandler(err, { builder, nuxt }))
|
||||
|
@ -45,8 +45,8 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
return nuxt.listen().then(() => {
|
||||
nuxt.showReady(false)
|
||||
return nuxt.server.listen().then(() => {
|
||||
nuxt.server.showReady(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import consola from 'consola'
|
||||
import esm from 'esm'
|
||||
import wrapAnsi from 'wrap-ansi'
|
||||
import defaultsDeep from 'lodash/defaultsDeep'
|
||||
import { server as nuxtServerConfig } from '@nuxt/config'
|
||||
import { getDefaultNuxtConfig } from '@nuxt/config'
|
||||
|
||||
const _require = esm(module, {
|
||||
cache: false,
|
||||
@ -58,7 +58,7 @@ export async function loadNuxtConfig(argv) {
|
||||
port: argv.port || undefined,
|
||||
host: argv.hostname || undefined,
|
||||
socket: argv['unix-socket'] || undefined
|
||||
}, options.server || {}, nuxtServerConfig(process.env))
|
||||
}, options.server || {}, getDefaultNuxtConfig().server)
|
||||
|
||||
return options
|
||||
}
|
||||
@ -72,11 +72,11 @@ export function indentLines(string, spaces, firstLineSpaces) {
|
||||
let s = ''
|
||||
if (lines.length) {
|
||||
const i0 = indent(firstLineSpaces === undefined ? spaces : firstLineSpaces)
|
||||
s = i0 + lines.shift()
|
||||
s = i0 + lines.shift().trim()
|
||||
}
|
||||
if (lines.length) {
|
||||
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
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
exports[`cli/command builds help text 1`] = `
|
||||
" 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
|
||||
|
||||
Options:
|
||||
@ -16,7 +16,7 @@ exports[`cli/command builds help text 1`] = `
|
||||
--port, -p Port number on which to start the application
|
||||
--hostname, -H Hostname on which to start the application
|
||||
--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
|
||||
|
||||
"
|
||||
|
@ -22,8 +22,8 @@ describe('dev', () => {
|
||||
expect(consola.error).not.toHaveBeenCalled()
|
||||
|
||||
expect(Builder.prototype.build).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.listen).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.showReady).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.server.listen).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.server.showReady).toHaveBeenCalled()
|
||||
expect(Builder.prototype.watchServer).toHaveBeenCalled()
|
||||
|
||||
jest.clearAllMocks()
|
||||
@ -37,8 +37,8 @@ describe('dev', () => {
|
||||
expect(Builder.prototype.unwatch).toHaveBeenCalled()
|
||||
expect(Builder.prototype.build).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.close).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.listen).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.showReady).not.toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.server.listen).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.server.showReady).not.toHaveBeenCalled()
|
||||
expect(Builder.prototype.watchServer).toHaveBeenCalled()
|
||||
|
||||
expect(consola.error).not.toHaveBeenCalled()
|
||||
@ -97,9 +97,11 @@ describe('dev', () => {
|
||||
|
||||
test('catches error on startDev', async () => {
|
||||
mockNuxt({
|
||||
listen: jest.fn().mockImplementation(() => {
|
||||
throw new Error('Listen Error')
|
||||
})
|
||||
server: {
|
||||
listen: jest.fn().mockImplementation(() => {
|
||||
throw new Error('Listen Error')
|
||||
})
|
||||
}
|
||||
})
|
||||
mockBuilder()
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { server as nuxtServerConfig } from '@nuxt/config'
|
||||
import { getDefaultNuxtConfig } from '@nuxt/config'
|
||||
import { consola } from '../utils'
|
||||
import * as utils from '../../src/utils'
|
||||
|
||||
@ -81,14 +81,14 @@ describe('cli/utils', () => {
|
||||
})
|
||||
|
||||
test('nuxtServerConfig: server env', () => {
|
||||
const options = {
|
||||
server: nuxtServerConfig({
|
||||
const options = getDefaultNuxtConfig({
|
||||
env: {
|
||||
...process.env,
|
||||
HOST: 'env-host',
|
||||
PORT: 3003,
|
||||
UNIX_SOCKET: '/var/run/env.sock'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(options.server.host).toBe('env-host')
|
||||
expect(options.server.port).toBe(3003)
|
||||
|
@ -66,8 +66,10 @@ export const mockGetNuxtStart = (ssr) => {
|
||||
ssr
|
||||
}
|
||||
}, {
|
||||
listen,
|
||||
showReady
|
||||
server: {
|
||||
listen,
|
||||
showReady
|
||||
}
|
||||
})
|
||||
|
||||
return { listen, showReady }
|
||||
@ -89,8 +91,10 @@ export const mockNuxt = (implementation) => {
|
||||
},
|
||||
clearHook: jest.fn(),
|
||||
close: jest.fn(),
|
||||
listen: jest.fn().mockImplementationOnce(() => Promise.resolve()),
|
||||
showReady: jest.fn().mockImplementationOnce(() => Promise.resolve())
|
||||
server: {
|
||||
listen: jest.fn().mockImplementationOnce(() => Promise.resolve()),
|
||||
showReady: jest.fn().mockImplementationOnce(() => Promise.resolve())
|
||||
}
|
||||
}, implementation || {})
|
||||
|
||||
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 BuildContext } from './build/context'
|
||||
export { default as Hookable } from './hookable'
|
||||
export * from './utils'
|
||||
|
@ -15,9 +15,10 @@ export const waitFor = function waitFor(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms || 0))
|
||||
}
|
||||
|
||||
export const isString = function isString(obj) {
|
||||
return typeof obj === 'string' || obj instanceof String
|
||||
}
|
||||
export const isString = obj => 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 startsWithSrcAlias = startsWithAlias(['@', '~'])
|
||||
@ -404,3 +405,34 @@ export const stripWhitespace = function stripWhitespace(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",
|
||||
"dependencies": {
|
||||
"@nuxt/common": "^2.2.0",
|
||||
"consola": "^1.4.4",
|
||||
"lodash": "^4.17.11",
|
||||
"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'
|
||||
|
||||
export default {
|
||||
export default () => ({
|
||||
quiet: Boolean(env.ci || env.test),
|
||||
analyze: false,
|
||||
profile: process.argv.includes('--profile'),
|
||||
@ -109,4 +109,4 @@ export default {
|
||||
/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 router from './router'
|
||||
import messages from './messages'
|
||||
import modes from './modes'
|
||||
import render from './render'
|
||||
import router from './router'
|
||||
import server from './server'
|
||||
|
||||
const nuxtDir = fs.existsSync(path.resolve(__dirname, '..', '..', 'package.js'))
|
||||
? path.resolve(__dirname, '..', '..') // src
|
||||
: path.resolve(__dirname, '..') // dist
|
||||
export function getDefaultNuxtConfig(options = {}) {
|
||||
if (!options.env) {
|
||||
options.env = process.env
|
||||
}
|
||||
|
||||
export default {
|
||||
// Information about running environment
|
||||
dev: Boolean(env.dev),
|
||||
test: Boolean(env.test),
|
||||
debug: undefined, // = dev
|
||||
|
||||
// Mode
|
||||
mode: 'universal',
|
||||
|
||||
// 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
|
||||
return {
|
||||
..._app(options),
|
||||
..._common(options),
|
||||
build: build(options),
|
||||
messages: messages(options),
|
||||
modes: modes(options),
|
||||
render: render(options),
|
||||
router: router(options),
|
||||
server: server(options)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
export default () => ({
|
||||
loading: 'Loading...',
|
||||
error_404: 'This page could not be found',
|
||||
server_error: 'Server error',
|
||||
@ -9,4 +9,4 @@ export default {
|
||||
client_error: 'Error',
|
||||
client_error_details:
|
||||
'An error occurred while rendering the page. Check developer tools console for details.'
|
||||
}
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
export default () => ({
|
||||
universal: {
|
||||
build: {
|
||||
ssr: true
|
||||
@ -15,4 +15,4 @@ export default {
|
||||
ssr: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
export default () => ({
|
||||
bundleRenderer: {
|
||||
shouldPrefetch: () => false
|
||||
},
|
||||
@ -24,4 +24,4 @@ export default {
|
||||
// 1 year in production
|
||||
maxAge: '1y'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
export default () => ({
|
||||
mode: 'history',
|
||||
base: '/',
|
||||
routes: [],
|
||||
@ -10,4 +10,4 @@ export default {
|
||||
parseQuery: false,
|
||||
stringifyQuery: false,
|
||||
fallback: false
|
||||
}
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default env => ({
|
||||
export default ({ env }) => ({
|
||||
https: false,
|
||||
port: env.NUXT_PORT ||
|
||||
env.PORT ||
|
||||
|
@ -1,9 +1,2 @@
|
||||
|
||||
// Export individual bundles for easier access
|
||||
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'
|
||||
export { getDefaultNuxtConfig } from './config'
|
||||
export { getNuxtConfig } from './options'
|
||||
|
@ -5,17 +5,10 @@ import defaults from 'lodash/defaults'
|
||||
import pick from 'lodash/pick'
|
||||
import isObject from 'lodash/isObject'
|
||||
import consola from 'consola'
|
||||
import { NuxtConfig, Modes } from '@nuxt/config'
|
||||
import { isPureObject, isUrl, guardDir, isString } from './utils'
|
||||
import { isPureObject, isUrl, guardDir, isNonEmptyString } from '@nuxt/common'
|
||||
import { getDefaultNuxtConfig } from './config'
|
||||
|
||||
// hasValue utility
|
||||
const hasValue = v => typeof v === 'string' && v
|
||||
|
||||
const Options = {}
|
||||
|
||||
export default Options
|
||||
|
||||
Options.from = function (_options) {
|
||||
export function getNuxtConfig(_options) {
|
||||
// Clone options to prevent unwanted side-effects
|
||||
const options = Object.assign({}, _options)
|
||||
|
||||
@ -43,12 +36,12 @@ Options.from = function (_options) {
|
||||
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()
|
||||
: `nuxt`
|
||||
|
||||
// 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
|
||||
// TODO: Unsafe operation.
|
||||
@ -59,11 +52,13 @@ Options.from = function (_options) {
|
||||
// }
|
||||
|
||||
// Apply defaults
|
||||
defaultsDeep(options, NuxtConfig)
|
||||
const nuxtConfig = getDefaultNuxtConfig()
|
||||
nuxtConfig.build._publicPath = nuxtConfig.build.publicPath
|
||||
defaultsDeep(options, nuxtConfig)
|
||||
|
||||
// Check srcDir and generate.dir excistence
|
||||
const hasSrcDir = hasValue(options.srcDir)
|
||||
const hasGenerateDir = hasValue(options.generate.dir)
|
||||
const hasSrcDir = isNonEmptyString(options.srcDir)
|
||||
const hasGenerateDir = isNonEmptyString(options.generate.dir)
|
||||
|
||||
// Resolve srcDir
|
||||
options.srcDir = hasSrcDir
|
||||
@ -97,7 +92,7 @@ Options.from = function (_options) {
|
||||
// Populate modulesDir
|
||||
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))
|
||||
|
||||
const mandatoryExtensions = ['js', 'mjs']
|
||||
@ -119,7 +114,7 @@ Options.from = function (_options) {
|
||||
// Ignore publicPath on dev
|
||||
/* istanbul ignore if */
|
||||
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
|
||||
@ -218,7 +213,7 @@ Options.from = function (_options) {
|
||||
}
|
||||
|
||||
// Apply mode preset
|
||||
const modePreset = Modes[options.mode || 'universal'] || Modes.universal
|
||||
const modePreset = options.modes[options.mode || 'universal']
|
||||
defaultsDeep(options, modePreset)
|
||||
|
||||
// If no server-side rendering, add appear true transition
|
@ -10,33 +10,17 @@
|
||||
"dependencies": {
|
||||
"@nuxt/common": "^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/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",
|
||||
"debug": "^4.1.0",
|
||||
"esm": "^3.0.84",
|
||||
"etag": "^1.8.1",
|
||||
"fresh": "^0.5.2",
|
||||
"fs-extra": "^7.0.0",
|
||||
"hash-sum": "^1.0.2",
|
||||
"ip": "^1.1.5",
|
||||
"launch-editor-middleware": "^2.2.1",
|
||||
"lodash": "^4.17.11",
|
||||
"lru-cache": "^4.1.3",
|
||||
"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"
|
||||
"std-env": "^2.0.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
@ -1,3 +1,3 @@
|
||||
export { default as Module } from './module'
|
||||
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 consola from 'consola'
|
||||
import chalk from 'chalk'
|
||||
|
||||
import ip from 'ip'
|
||||
|
||||
import { Options, sequence } from '@nuxt/common'
|
||||
import { Hookable, defineAlias } from '@nuxt/common'
|
||||
import { getNuxtConfig } from '@nuxt/config'
|
||||
import { Server } from '@nuxt/server'
|
||||
|
||||
import { version } from '../package.json'
|
||||
import ModuleContainer from './module'
|
||||
import Renderer from './renderer'
|
||||
import Resolver from './resolver'
|
||||
|
||||
export default class Nuxt {
|
||||
export default class Nuxt extends Hookable {
|
||||
constructor(options = {}) {
|
||||
this.options = Options.from(options)
|
||||
super()
|
||||
|
||||
this.readyMessage = null
|
||||
this.initialized = false
|
||||
|
||||
// Hooks
|
||||
this._hooks = {}
|
||||
this.hook = this.hook.bind(this)
|
||||
// Assign options and apply defaults
|
||||
this.options = getNuxtConfig(options)
|
||||
|
||||
// Create instance of core components
|
||||
this.moduleContainer = new ModuleContainer(this)
|
||||
this.renderer = new Renderer(this)
|
||||
this.resolver = new Resolver(this)
|
||||
this.moduleContainer = new ModuleContainer(this)
|
||||
this.server = new Server(this)
|
||||
|
||||
// Backward compatibility
|
||||
this.render = this.renderer.app
|
||||
this.renderRoute = this.renderer.renderRoute.bind(this.renderer)
|
||||
this.renderAndGetWindow = this.renderer.renderAndGetWindow.bind(
|
||||
this.renderer
|
||||
)
|
||||
this.resolveAlias = this.resolver.resolveAlias.bind(this)
|
||||
this.resolvePath = this.resolver.resolvePath.bind(this)
|
||||
// Deprecated hooks
|
||||
this._deprecatedHooks = {
|
||||
'render:context': 'render:routeContext' // #3773
|
||||
}
|
||||
|
||||
// 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) => {
|
||||
consola.fatal(err)
|
||||
})
|
||||
@ -62,8 +59,8 @@ export default class Nuxt {
|
||||
// Await for modules
|
||||
await this.moduleContainer.ready()
|
||||
|
||||
// Await for renderer to be ready
|
||||
await this.renderer.ready()
|
||||
// Await for server to be ready
|
||||
await this.server.ready()
|
||||
|
||||
this.initialized = true
|
||||
|
||||
@ -73,156 +70,6 @@ export default class Nuxt {
|
||||
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) {
|
||||
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
|
||||
const { html } = await this.nuxt.renderRoute('/', { spa: true })
|
||||
const { html } = await this.nuxt.server.renderRoute('/', { spa: true })
|
||||
await fsExtra.writeFile(fallbackPath, html, 'utf8')
|
||||
}
|
||||
|
||||
@ -201,7 +201,7 @@ export default class Generator {
|
||||
const pageErrors = []
|
||||
|
||||
try {
|
||||
const res = await this.nuxt.renderer.renderRoute(route, {
|
||||
const res = await this.nuxt.server.renderRoute(route, {
|
||||
_generate: true,
|
||||
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'
|
||||
|
||||
export default function errorMiddleware(err, req, res, next) {
|
||||
export default ({ resources, options }) => function errorMiddleware(err, req, res, next) {
|
||||
// ensure statusCode, message and name fields
|
||||
err.statusCode = err.statusCode || 500
|
||||
err.message = err.message || 'Nuxt Server Error'
|
||||
@ -35,7 +35,7 @@ export default function errorMiddleware(err, req, res, next) {
|
||||
hasReqHeader('user-agent', 'curl/')
|
||||
|
||||
// Use basic errors when debug mode is disabled
|
||||
if (!this.options.debug) {
|
||||
if (!options.debug) {
|
||||
// Json format is compatible with Youch json responses
|
||||
const json = {
|
||||
status: err.statusCode,
|
||||
@ -46,7 +46,7 @@ export default function errorMiddleware(err, req, res, next) {
|
||||
sendResponse(JSON.stringify(json, undefined, 2), 'text/json')
|
||||
return
|
||||
}
|
||||
const html = this.resources.errorTemplate(json)
|
||||
const html = resources.errorTemplate(json)
|
||||
sendResponse(html)
|
||||
return
|
||||
}
|
||||
@ -55,8 +55,13 @@ export default function errorMiddleware(err, req, res, next) {
|
||||
const youch = new Youch(
|
||||
err,
|
||||
req,
|
||||
readSource.bind(this),
|
||||
this.options.router.base,
|
||||
readSourceFactory({
|
||||
srcDir: options.srcDir,
|
||||
rootDir: options.rootDir,
|
||||
buildDir: options.buildDir,
|
||||
resources
|
||||
}),
|
||||
options.router.base,
|
||||
true
|
||||
)
|
||||
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
|
||||
const sanitizeName = name =>
|
||||
name ? name.replace('webpack:///', '').split('?')[0] : null
|
||||
@ -82,10 +87,10 @@ async function readSource(frame) {
|
||||
|
||||
// Possible paths for file
|
||||
const searchPath = [
|
||||
this.options.srcDir,
|
||||
this.options.rootDir,
|
||||
path.join(this.options.buildDir, 'dist', 'server'),
|
||||
this.options.buildDir,
|
||||
srcDir,
|
||||
rootDir,
|
||||
path.join(buildDir, 'dist', 'server'),
|
||||
buildDir,
|
||||
process.cwd()
|
||||
]
|
||||
|
||||
@ -97,7 +102,7 @@ async function readSource(frame) {
|
||||
frame.contents = source
|
||||
frame.fullPath = fullPath
|
||||
if (path.isAbsolute(frame.fileName)) {
|
||||
frame.fileName = path.relative(this.options.rootDir, fullPath)
|
||||
frame.fileName = path.relative(rootDir, fullPath)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -107,6 +112,6 @@ async function readSource(frame) {
|
||||
// TODO: restore to if after https://github.com/istanbuljs/nyc/issues/595 fixed
|
||||
/* istanbul ignore next */
|
||||
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'
|
||||
|
||||
export default async function nuxtMiddleware(req, res, next) {
|
||||
export default ({ options, nuxt, renderRoute, resources }) => async function nuxtMiddleware(req, res, next) {
|
||||
// Get context
|
||||
const context = getContext(req, res)
|
||||
|
||||
res.statusCode = 200
|
||||
try {
|
||||
const result = await this.renderRoute(req.url, context)
|
||||
await this.nuxt.callHook('render:route', req.url, result, context)
|
||||
const result = await renderRoute(req.url, context)
|
||||
await nuxt.callHook('render:route', req.url, result, context)
|
||||
const {
|
||||
html,
|
||||
cspScriptSrcHashSet,
|
||||
@ -21,7 +21,7 @@ export default async function nuxtMiddleware(req, res, next) {
|
||||
} = result
|
||||
|
||||
if (redirected) {
|
||||
this.nuxt.callHook('render:routeDone', req.url, result, context)
|
||||
nuxt.callHook('render:routeDone', req.url, result, context)
|
||||
return html
|
||||
}
|
||||
if (error) {
|
||||
@ -29,26 +29,29 @@ export default async function nuxtMiddleware(req, res, next) {
|
||||
}
|
||||
|
||||
// Add ETag header
|
||||
if (!error && this.options.render.etag) {
|
||||
const etag = generateETag(html, this.options.render.etag)
|
||||
if (!error && options.render.etag) {
|
||||
const etag = generateETag(html, options.render.etag)
|
||||
if (fresh(req.headers, { etag })) {
|
||||
res.statusCode = 304
|
||||
res.end()
|
||||
this.nuxt.callHook('render:routeDone', req.url, result, context)
|
||||
nuxt.callHook('render:routeDone', req.url, result, context)
|
||||
return
|
||||
}
|
||||
res.setHeader('ETag', etag)
|
||||
}
|
||||
|
||||
// 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
|
||||
// https://w3c.github.io/preload/#server-push-http-2
|
||||
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
|
||||
// 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) {
|
||||
const { allowedSources, policies } = this.options.render.csp
|
||||
const cspHeader = this.options.render.csp.reportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'
|
||||
if (options.render.csp) {
|
||||
const { allowedSources, policies } = options.render.csp
|
||||
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
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
res.setHeader('Content-Length', Buffer.byteLength(html))
|
||||
res.end(html, 'utf8')
|
||||
this.nuxt.callHook('render:routeDone', req.url, result, context)
|
||||
nuxt.callHook('render:routeDone', req.url, result, context)
|
||||
return html
|
||||
} catch (err) {
|
||||
/* 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",
|
||||
"repository": "nuxt/nuxt.js",
|
||||
"license": "MIT",
|
||||
@ -7,7 +7,7 @@
|
||||
"dist",
|
||||
"template"
|
||||
],
|
||||
"main": "dist/app.js",
|
||||
"main": "dist/vue-app.js",
|
||||
"publishConfig": {
|
||||
"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 LRU from 'lru-cache'
|
||||
|
||||
export default class MetaRenderer {
|
||||
constructor(nuxt, renderer) {
|
||||
this.nuxt = nuxt
|
||||
export default class SPAMetaRenderer {
|
||||
constructor(renderer) {
|
||||
this.renderer = renderer
|
||||
this.options = nuxt.options
|
||||
this.options = this.renderer.context.options
|
||||
this.vueRenderer = createRenderer()
|
||||
this.cache = LRU({})
|
||||
|
||||
@ -69,7 +68,7 @@ export default class MetaRenderer {
|
||||
|
||||
meta.resourceHints = ''
|
||||
|
||||
const clientManifest = this.renderer.resources.clientManifest
|
||||
const clientManifest = this.renderer.context.resources.clientManifest
|
||||
|
||||
const shouldPreload = this.options.render.bundleRenderer.shouldPreload || (() => true)
|
||||
const shouldPrefetch = this.options.render.bundleRenderer.shouldPrefetch || (() => true)
|
@ -120,7 +120,7 @@ export class WebpackBuilder {
|
||||
})
|
||||
|
||||
// Reload renderer if available
|
||||
nuxt.renderer.loadResources(this.mfs || fs)
|
||||
nuxt.server.loadResources(this.mfs || fs)
|
||||
|
||||
// Resolve on next tick
|
||||
process.nextTick(resolve)
|
||||
@ -166,7 +166,7 @@ export class WebpackBuilder {
|
||||
webpackDev(compiler) {
|
||||
consola.debug('Adding webpack middleware...')
|
||||
|
||||
const { nuxt: { renderer }, options } = this.context
|
||||
const { nuxt: { server }, options } = this.context
|
||||
|
||||
// Create webpack dev middleware
|
||||
this.webpackDevMiddleware = pify(
|
||||
@ -200,9 +200,9 @@ export class WebpackBuilder {
|
||||
)
|
||||
|
||||
// Inject to renderer instance
|
||||
if (renderer) {
|
||||
renderer.webpackDevMiddleware = this.webpackDevMiddleware
|
||||
renderer.webpackHotMiddleware = this.webpackHotMiddleware
|
||||
if (server) {
|
||||
server.webpackDevMiddleware = this.webpackDevMiddleware
|
||||
server.webpackHotMiddleware = this.webpackHotMiddleware
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,7 +257,7 @@ export default class WebpackBaseConfig {
|
||||
const hasErrors = Object.values(states).some(state => state.stats.hasErrors())
|
||||
|
||||
if (!hasErrors) {
|
||||
this.nuxt.showReady(false)
|
||||
this.nuxt.server.showReady(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,9 @@ export default function rollupConfig({
|
||||
return defaultsDeep({}, options, {
|
||||
input: path.resolve(rootDir, input),
|
||||
output: {
|
||||
format: 'cjs',
|
||||
sourcemap: false,
|
||||
file: `${pkg.name.replace('-edge', '')}.js`,
|
||||
dir: path.resolve(rootDir, 'dist')
|
||||
dir: path.resolve(rootDir, 'dist'),
|
||||
format: 'cjs'
|
||||
},
|
||||
preferConst: true,
|
||||
external: [
|
||||
|
@ -13,7 +13,7 @@ describe('basic browser', () => {
|
||||
const config = await loadFixture('basic')
|
||||
nuxt = new Nuxt(config)
|
||||
port = await getPort()
|
||||
await nuxt.listen(port, 'localhost')
|
||||
await nuxt.server.listen(port, 'localhost')
|
||||
|
||||
await browser.start({
|
||||
// slowMo: 50,
|
||||
|
@ -12,7 +12,7 @@ const startServer = async (type = 'basic') => {
|
||||
const config = await loadFixture(type)
|
||||
nuxt = new Nuxt(config)
|
||||
port = await getPort()
|
||||
await nuxt.listen(port, 'localhost')
|
||||
await nuxt.server.listen(port, 'localhost')
|
||||
|
||||
return nuxt
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ describe('children patch (browser)', () => {
|
||||
const options = await loadFixture('children')
|
||||
nuxt = new Nuxt(options)
|
||||
port = await getPort()
|
||||
await nuxt.listen(port, 'localhost')
|
||||
await nuxt.server.listen(port, 'localhost')
|
||||
})
|
||||
|
||||
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', () => {
|
||||
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)
|
||||
}, 80000)
|
||||
|
@ -8,12 +8,12 @@ describe('basic ssr', () => {
|
||||
const options = await loadFixture('async-config')
|
||||
nuxt = new Nuxt(options)
|
||||
port = await getPort()
|
||||
await nuxt.listen(port, '0.0.0.0')
|
||||
await nuxt.server.listen(port, '0.0.0.0')
|
||||
})
|
||||
|
||||
test('/', async () => {
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { resolve } from 'path'
|
||||
import consola from 'consola'
|
||||
|
||||
import { Nuxt, Options, version } from '../utils'
|
||||
import { Nuxt, getNuxtConfig, version } from '../utils'
|
||||
|
||||
describe('basic config defaults', () => {
|
||||
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', () => {
|
||||
const options = Options.from({})
|
||||
const options = getNuxtConfig({})
|
||||
const currentNodeModulesDir = resolve(__dirname, '..', '..', 'node_modules')
|
||||
expect(options.modulesDir.includes(currentNodeModulesDir)).toBe(true)
|
||||
})
|
||||
|
||||
test('vendor has been deprecated', () => {
|
||||
const options = Options.from({
|
||||
const options = getNuxtConfig({
|
||||
build: { vendor: 'vue' }
|
||||
})
|
||||
expect(options.build.vendor).toBeUndefined()
|
||||
@ -23,18 +23,18 @@ describe('basic config defaults', () => {
|
||||
})
|
||||
|
||||
test('globalName uses nuxt as default if not set', () => {
|
||||
const options = Options.from({})
|
||||
const options = getNuxtConfig({})
|
||||
expect(options.globalName).toEqual('nuxt')
|
||||
})
|
||||
|
||||
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')
|
||||
|
||||
options = Options.from({ globalName: 'foo bar' })
|
||||
options = getNuxtConfig({ globalName: 'foo bar' })
|
||||
expect(options.globalName).toEqual('nuxt')
|
||||
|
||||
options = Options.from({ globalName: 'foo?' })
|
||||
options = getNuxtConfig({ globalName: 'foo?' })
|
||||
expect(options.globalName).toEqual('nuxt')
|
||||
})
|
||||
})
|
||||
|
@ -48,7 +48,7 @@ describe('basic dev', () => {
|
||||
builder = new Builder(nuxt, BundleBuilder)
|
||||
await builder.build()
|
||||
port = await getPort()
|
||||
await nuxt.listen(port, 'localhost')
|
||||
await nuxt.server.listen(port, 'localhost')
|
||||
})
|
||||
|
||||
test('Check build:done hook called', () => {
|
||||
@ -83,7 +83,7 @@ describe('basic dev', () => {
|
||||
})
|
||||
|
||||
test('/stateless', async () => {
|
||||
const window = await nuxt.renderAndGetWindow(url('/stateless'))
|
||||
const window = await nuxt.server.renderAndGetWindow(url('/stateless'))
|
||||
const html = window.document.body.innerHTML
|
||||
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 () => {
|
||||
await expect(nuxt.renderAndGetWindow(url('/error'))).rejects.toMatchObject({
|
||||
await expect(nuxt.server.renderAndGetWindow(url('/error'))).rejects.toMatchObject({
|
||||
statusCode: 500
|
||||
})
|
||||
})
|
||||
@ -126,7 +126,7 @@ describe('basic dev', () => {
|
||||
const sourceMaps = 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
|
||||
})
|
||||
|
||||
|
@ -85,19 +85,19 @@ describe('basic generate', () => {
|
||||
})
|
||||
|
||||
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
|
||||
expect(html.includes('<h1>My component!</h1>')).toBe(true)
|
||||
})
|
||||
|
||||
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
|
||||
expect(html.includes('<h1>mutated</h1>')).toBe(true)
|
||||
})
|
||||
|
||||
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
|
||||
expect(headHtml.includes('.red{color:red')).toBe(true)
|
||||
@ -110,13 +110,13 @@ describe('basic generate', () => {
|
||||
})
|
||||
|
||||
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
|
||||
expect(html.includes('<div><p>The answer is 42</p></div>')).toBe(true)
|
||||
})
|
||||
|
||||
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 metas = window.document.getElementsByTagName('meta')
|
||||
expect(window.document.title).toBe('My title - Nuxt.js')
|
||||
@ -125,7 +125,7 @@ describe('basic generate', () => {
|
||||
})
|
||||
|
||||
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
|
||||
expect(html.includes('<p>Nuxt.js</p>')).toBe(true)
|
||||
})
|
||||
@ -165,13 +165,13 @@ describe('basic generate', () => {
|
||||
})
|
||||
|
||||
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
|
||||
expect(html.includes('This page could not be found')).toBe(true)
|
||||
})
|
||||
|
||||
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
|
||||
expect(html.includes('I am valid</h1>')).toBe(true)
|
||||
})
|
||||
@ -183,7 +183,7 @@ describe('basic generate', () => {
|
||||
})
|
||||
|
||||
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
|
||||
expect(html.includes('<h1>Index page</h1>')).toBe(true)
|
||||
})
|
||||
|
@ -10,11 +10,11 @@ describe('with-config', () => {
|
||||
const config = await loadFixture('basic')
|
||||
nuxt = new Nuxt(config)
|
||||
port = await getPort()
|
||||
await nuxt.listen(port, 'localhost')
|
||||
await nuxt.server.listen(port, 'localhost')
|
||||
})
|
||||
|
||||
test('/', async () => {
|
||||
const window = await nuxt.renderAndGetWindow(url('/'))
|
||||
const window = await nuxt.server.renderAndGetWindow(url('/'))
|
||||
expect(window.__test_plugin).toBe(true)
|
||||
})
|
||||
|
||||
|
@ -10,7 +10,7 @@ const startCspServer = async (csp, isProduction = true) => {
|
||||
})
|
||||
const nuxt = new Nuxt(options)
|
||||
port = await getPort()
|
||||
await nuxt.listen(port, '0.0.0.0')
|
||||
await nuxt.server.listen(port, '0.0.0.0')
|
||||
return nuxt
|
||||
}
|
||||
|
||||
|
@ -11,16 +11,16 @@ describe('basic ssr', () => {
|
||||
const options = await loadFixture('basic')
|
||||
nuxt = new Nuxt(options)
|
||||
port = await getPort()
|
||||
await nuxt.listen(port, '0.0.0.0')
|
||||
await nuxt.server.listen(port, '0.0.0.0')
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
@ -28,7 +28,7 @@ describe('basic ssr', () => {
|
||||
** Example of testing via dom checking
|
||||
*/
|
||||
test('/css', async () => {
|
||||
const window = await nuxt.renderAndGetWindow(url('/css'))
|
||||
const window = await nuxt.server.renderAndGetWindow(url('/css'))
|
||||
|
||||
const headHtml = window.document.head.innerHTML
|
||||
expect(headHtml.includes('color:red')).toBe(true)
|
||||
@ -41,7 +41,7 @@ describe('basic ssr', () => {
|
||||
})
|
||||
|
||||
test('/postcss', async () => {
|
||||
const window = await nuxt.renderAndGetWindow(url('/css'))
|
||||
const window = await nuxt.server.renderAndGetWindow(url('/css'))
|
||||
|
||||
const headHtml = window.document.head.innerHTML
|
||||
expect(headHtml.includes('background-color:#00f')).toBe(true)
|
||||
@ -51,18 +51,18 @@ describe('basic ssr', () => {
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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('<p>1</p>')).toBe(true)
|
||||
})
|
||||
|
||||
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')
|
||||
|
||||
const html = window.document.body.innerHTML
|
||||
@ -77,64 +77,64 @@ describe('basic ssr', () => {
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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(html.includes('Custom Error')).toBe(true)
|
||||
})
|
||||
|
||||
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(html.includes('Custom Error')).toBe(true)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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(redirected.path === '/').toBe(true)
|
||||
expect(redirected.status === 302).toBe(true)
|
||||
@ -142,14 +142,14 @@ describe('basic ssr', () => {
|
||||
|
||||
test('/redirect -> check redirected source', async () => {
|
||||
// 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
|
||||
expect(html.includes('<h1>Index page</h1>')).toBe(true)
|
||||
})
|
||||
|
||||
test('/redirect -> external link', async () => {
|
||||
let _headers, _status
|
||||
const { html } = await nuxt.renderRoute('/redirect-external', {
|
||||
const { html } = await nuxt.server.renderRoute('/redirect-external', {
|
||||
res: {
|
||||
writeHead(status, headers) {
|
||||
_status = status
|
||||
@ -164,13 +164,13 @@ describe('basic ssr', () => {
|
||||
})
|
||||
|
||||
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.__NUXT__.test).toBe(true)
|
||||
})
|
||||
|
||||
test('/error', async () => {
|
||||
await expect(nuxt.renderRoute('/error', { req: {}, res: {} }))
|
||||
await expect(nuxt.server.renderRoute('/error', { req: {}, res: {} }))
|
||||
.rejects.toThrow('Error mouahahah')
|
||||
})
|
||||
|
||||
@ -198,7 +198,7 @@ describe('basic ssr', () => {
|
||||
})
|
||||
|
||||
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(error.message.includes('Custom error')).toBe(true)
|
||||
expect(error.statusCode === undefined).toBe(true)
|
||||
@ -220,21 +220,21 @@ describe('basic ssr', () => {
|
||||
})
|
||||
|
||||
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(redirected.path === '/stateless').toBe(true)
|
||||
expect(redirected.status === 302).toBe(true)
|
||||
})
|
||||
|
||||
test('/no-ssr', async () => {
|
||||
const { html } = await nuxt.renderRoute('/no-ssr')
|
||||
const { html } = await nuxt.server.renderRoute('/no-ssr')
|
||||
expect(html.includes(
|
||||
'<p class="no-ssr-placeholder">Loading...</p>'
|
||||
)).toBe(true)
|
||||
})
|
||||
|
||||
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
|
||||
expect(html.includes('Displayed only on client-side</h1>')).toBe(true)
|
||||
})
|
||||
@ -259,7 +259,7 @@ describe('basic ssr', () => {
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
@ -269,28 +269,28 @@ describe('basic ssr', () => {
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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('Router Guard')).toBe(false)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
|
@ -10,39 +10,39 @@ describe('children', () => {
|
||||
const options = await loadFixture('children')
|
||||
nuxt = new Nuxt(options)
|
||||
port = await getPort()
|
||||
await nuxt.listen(port, 'localhost')
|
||||
await nuxt.server.listen(port, 'localhost')
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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('<h2>I am the child</h2>')).toBe(true)
|
||||
})
|
||||
|
||||
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('<h2>Id=</h2>')).toBe(true)
|
||||
})
|
||||
|
||||
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('<h2>Id=1</h2>')).toBe(true)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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('<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