From 39b558f59c0ecb5aa40bf709ca5dff83a151b20b Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 31 Oct 2018 00:12:53 +0330 Subject: [PATCH] refactor core into sub-packages (#4202) --- .circleci/config.yml | 19 +- .eslintignore | 16 +- azure-pipelines.yml | 4 - examples/with-ava/test/index.test.js | 6 +- examples/with-sockets/io/index.js | 4 +- jest.config.js | 19 +- package.json | 4 +- packages/builder/package.json | 2 +- packages/builder/src/builder.js | 26 +- .../src/build => builder/src}/context.js | 0 packages/cli/src/commands/dev.js | 4 +- packages/cli/src/commands/start.js | 4 +- packages/cli/src/utils.js | 8 +- .../unit/__snapshots__/command.test.js.snap | 4 +- packages/cli/test/unit/dev.test.js | 16 +- packages/cli/test/unit/utils.test.js | 10 +- packages/cli/test/utils/mocking.js | 12 +- packages/common/src/hookable.js | 67 +++ packages/common/src/index.js | 3 +- packages/common/src/utils.js | 38 +- packages/config/package.json | 1 + packages/config/src/config/_app.js | 52 ++ packages/config/src/config/_common.js | 80 +++ packages/config/src/config/build.js | 4 +- packages/config/src/config/index.js | 143 +----- packages/config/src/config/messages.js | 4 +- packages/config/src/{ => config}/modes.js | 4 +- packages/config/src/config/render.js | 4 +- packages/config/src/config/router.js | 4 +- packages/config/src/config/server.js | 2 +- packages/config/src/index.js | 11 +- packages/{common => config}/src/options.js | 31 +- packages/core/package.json | 22 +- packages/core/src/index.js | 2 +- packages/core/src/nuxt.js | 201 +------- packages/core/src/renderer.js | 484 ------------------ packages/generator/src/generator.js | 4 +- packages/{app => server}/package.js | 0 packages/server/package.json | 29 ++ packages/server/src/context.js | 8 + packages/server/src/index.js | 1 + packages/server/src/jsdom.js | 75 +++ .../{core => server}/src/middleware/error.js | 29 +- .../{core => server}/src/middleware/nuxt.js | 35 +- packages/server/src/server.js | 276 ++++++++++ packages/vue-app/package.js | 3 + packages/{app => vue-app}/package.json | 4 +- packages/{app => vue-app}/src/index.js | 0 .../{app => vue-app}/template/.eslintignore | 0 packages/{app => vue-app}/template/App.js | 0 packages/{app => vue-app}/template/client.js | 0 .../template/components/no-ssr.js | 0 .../template/components/nuxt-child.js | 0 .../template/components/nuxt-error.vue | 0 .../template/components/nuxt-link.js | 0 .../template/components/nuxt-loading.vue | 0 .../template/components/nuxt.js | 0 packages/{app => vue-app}/template/empty.js | 0 packages/{app => vue-app}/template/index.js | 0 .../template/layouts/default.vue | 0 .../{app => vue-app}/template/middleware.js | 0 .../{app => vue-app}/template/pages/index.vue | 0 packages/{app => vue-app}/template/router.js | 0 packages/{app => vue-app}/template/server.js | 0 packages/{app => vue-app}/template/store.js | 0 packages/{app => vue-app}/template/utils.js | 0 .../template/views/app.template.html | 0 .../template/views/error.html | 0 .../template/views/loading/chasing-dots.html | 0 .../template/views/loading/circle.html | 0 .../template/views/loading/cube-grid.html | 0 .../template/views/loading/default.html | 0 .../template/views/loading/fading-circle.html | 0 .../template/views/loading/folding-cube.html | 0 .../template/views/loading/nuxt.html | 0 .../template/views/loading/pulse.html | 0 .../views/loading/rectangle-bounce.html | 0 .../views/loading/rotating-plane.html | 0 .../template/views/loading/three-bounce.html | 0 .../views/loading/wandering-cubes.html | 0 packages/vue-renderer/package.js | 3 + packages/vue-renderer/package.json | 27 + packages/vue-renderer/src/index.js | 1 + packages/vue-renderer/src/renderer.js | 297 +++++++++++ .../meta.js => vue-renderer/src/spa-meta.js} | 9 +- packages/webpack/src/builder.js | 10 +- packages/webpack/src/config/base.js | 2 +- scripts/rollup.config.js | 5 +- test/e2e/basic.browser.test.js | 2 +- test/e2e/basic.vue-config.test.js | 2 +- test/e2e/children.patch.browser.test.js | 2 +- test/fixtures/cli/cli.build.test.js | 2 +- test/unit/async-config.test.js | 4 +- test/unit/basic.config.defaults.test.js | 14 +- test/unit/basic.dev.test.js | 8 +- test/unit/basic.generate.test.js | 18 +- test/unit/basic.plugins.test.js | 4 +- test/unit/basic.ssr.csp.test.js | 2 +- test/unit/basic.ssr.test.js | 68 +-- test/unit/children.test.js | 14 +- test/unit/custom-app-template.test.js | 4 +- test/unit/custom-dirs.test.js | 8 +- test/unit/dist-options.test.js | 2 +- test/unit/error.test.js | 8 +- test/unit/extract-css.test.js | 4 +- test/unit/fallback.generate.test.js | 4 +- test/unit/https.test.js | 4 +- test/unit/module.test.js | 10 +- test/unit/nuxt.test.js | 4 +- test/unit/sockets.test.js | 4 +- test/unit/spa.test.js | 4 +- test/unit/ssr.test.js | 6 +- test/unit/with-config.test.js | 44 +- test/utils/index.js | 4 +- 114 files changed, 1286 insertions(+), 1097 deletions(-) rename packages/{common/src/build => builder/src}/context.js (100%) create mode 100644 packages/common/src/hookable.js create mode 100644 packages/config/src/config/_app.js create mode 100644 packages/config/src/config/_common.js rename packages/config/src/{ => config}/modes.js (86%) rename packages/{common => config}/src/options.js (90%) delete mode 100644 packages/core/src/renderer.js rename packages/{app => server}/package.js (100%) create mode 100644 packages/server/package.json create mode 100644 packages/server/src/context.js create mode 100644 packages/server/src/index.js create mode 100644 packages/server/src/jsdom.js rename packages/{core => server}/src/middleware/error.js (80%) rename packages/{core => server}/src/middleware/nuxt.js (75%) create mode 100644 packages/server/src/server.js create mode 100644 packages/vue-app/package.js rename packages/{app => vue-app}/package.json (75%) rename packages/{app => vue-app}/src/index.js (100%) rename packages/{app => vue-app}/template/.eslintignore (100%) rename packages/{app => vue-app}/template/App.js (100%) rename packages/{app => vue-app}/template/client.js (100%) rename packages/{app => vue-app}/template/components/no-ssr.js (100%) rename packages/{app => vue-app}/template/components/nuxt-child.js (100%) rename packages/{app => vue-app}/template/components/nuxt-error.vue (100%) rename packages/{app => vue-app}/template/components/nuxt-link.js (100%) rename packages/{app => vue-app}/template/components/nuxt-loading.vue (100%) rename packages/{app => vue-app}/template/components/nuxt.js (100%) rename packages/{app => vue-app}/template/empty.js (100%) rename packages/{app => vue-app}/template/index.js (100%) rename packages/{app => vue-app}/template/layouts/default.vue (100%) rename packages/{app => vue-app}/template/middleware.js (100%) rename packages/{app => vue-app}/template/pages/index.vue (100%) rename packages/{app => vue-app}/template/router.js (100%) rename packages/{app => vue-app}/template/server.js (100%) rename packages/{app => vue-app}/template/store.js (100%) rename packages/{app => vue-app}/template/utils.js (100%) rename packages/{app => vue-app}/template/views/app.template.html (100%) rename packages/{app => vue-app}/template/views/error.html (100%) rename packages/{app => vue-app}/template/views/loading/chasing-dots.html (100%) rename packages/{app => vue-app}/template/views/loading/circle.html (100%) rename packages/{app => vue-app}/template/views/loading/cube-grid.html (100%) rename packages/{app => vue-app}/template/views/loading/default.html (100%) rename packages/{app => vue-app}/template/views/loading/fading-circle.html (100%) rename packages/{app => vue-app}/template/views/loading/folding-cube.html (100%) rename packages/{app => vue-app}/template/views/loading/nuxt.html (100%) rename packages/{app => vue-app}/template/views/loading/pulse.html (100%) rename packages/{app => vue-app}/template/views/loading/rectangle-bounce.html (100%) rename packages/{app => vue-app}/template/views/loading/rotating-plane.html (100%) rename packages/{app => vue-app}/template/views/loading/three-bounce.html (100%) rename packages/{app => vue-app}/template/views/loading/wandering-cubes.html (100%) create mode 100644 packages/vue-renderer/package.js create mode 100644 packages/vue-renderer/package.json create mode 100644 packages/vue-renderer/src/index.js create mode 100644 packages/vue-renderer/src/renderer.js rename packages/{core/src/meta.js => vue-renderer/src/spa-meta.js} (93%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 693d36d2c8..50781d8ed4 100755 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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 diff --git a/.eslintignore b/.eslintignore index afbcb62ebe..48de14efb0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -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 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1d82aa01ef..4fa4d6940d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -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' diff --git a/examples/with-ava/test/index.test.js b/examples/with-ava/test/index.test.js index 7e1e414d11..fb25e5ec5f 100755 --- a/examples/with-ava/test/index.test.js +++ b/examples/with-ava/test/index.test.js @@ -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('

Hello world!

')) }) // 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) diff --git a/examples/with-sockets/io/index.js b/examples/with-sockets/io/index.js index bcf53592de..941f7b2761 100644 --- a/examples/with-sockets/io/index.js +++ b/examples/with-sockets/io/index.js @@ -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)) diff --git a/jest.config.js b/jest.config.js index f4706457af..c2546d018f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -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' diff --git a/package.json b/package.json index 947fc51cb0..6b8bac1477 100644 --- a/package.json +++ b/package.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", diff --git a/packages/builder/package.json b/packages/builder/package.json index 9cfacbd631..6b2fb0e120 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -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", diff --git a/packages/builder/src/builder.js b/packages/builder/src/builder.js index 15bc0865e2..224cea73d6 100644 --- a/packages/builder/src/builder.js +++ b/packages/builder/src/builder.js @@ -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 diff --git a/packages/common/src/build/context.js b/packages/builder/src/context.js similarity index 100% rename from packages/common/src/build/context.js rename to packages/builder/src/context.js diff --git a/packages/cli/src/commands/dev.js b/packages/cli/src/commands/dev.js index c6c81594d9..0afd2b49b7 100644 --- a/packages/cli/src/commands/dev.js +++ b/packages/cli/src/commands/dev.js @@ -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 })) diff --git a/packages/cli/src/commands/start.js b/packages/cli/src/commands/start.js index e08e08ffcc..506f0fe47f 100644 --- a/packages/cli/src/commands/start.js +++ b/packages/cli/src/commands/start.js @@ -45,8 +45,8 @@ export default { } } - return nuxt.listen().then(() => { - nuxt.showReady(false) + return nuxt.server.listen().then(() => { + nuxt.server.showReady(false) }) } } diff --git a/packages/cli/src/utils.js b/packages/cli/src/utils.js index 6b609005bf..fd9e138230 100644 --- a/packages/cli/src/utils.js +++ b/packages/cli/src/utils.js @@ -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 } diff --git a/packages/cli/test/unit/__snapshots__/command.test.js.snap b/packages/cli/test/unit/__snapshots__/command.test.js.snap index 20ef9648a3..be77274aa6 100644 --- a/packages/cli/test/unit/__snapshots__/command.test.js.snap +++ b/packages/cli/test/unit/__snapshots__/command.test.js.snap @@ -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 " diff --git a/packages/cli/test/unit/dev.test.js b/packages/cli/test/unit/dev.test.js index dad9e2011b..f975d967a9 100644 --- a/packages/cli/test/unit/dev.test.js +++ b/packages/cli/test/unit/dev.test.js @@ -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() diff --git a/packages/cli/test/unit/utils.test.js b/packages/cli/test/unit/utils.test.js index b1a1bf00c5..5eb385101b 100644 --- a/packages/cli/test/unit/utils.test.js +++ b/packages/cli/test/unit/utils.test.js @@ -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) diff --git a/packages/cli/test/utils/mocking.js b/packages/cli/test/utils/mocking.js index 63a83e3976..da4a9ba7d4 100644 --- a/packages/cli/test/utils/mocking.js +++ b/packages/cli/test/utils/mocking.js @@ -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 })) diff --git a/packages/common/src/hookable.js b/packages/common/src/hookable.js new file mode 100644 index 0000000000..658f25791b --- /dev/null +++ b/packages/common/src/hookable.js @@ -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)) + }) + } +} diff --git a/packages/common/src/index.js b/packages/common/src/index.js index 67718e1adf..6d33677a6a 100644 --- a/packages/common/src/index.js +++ b/packages/common/src/index.js @@ -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' diff --git a/packages/common/src/utils.js b/packages/common/src/utils.js index 7efdc33450..58fd1df740 100644 --- a/packages/common/src/utils.js +++ b/packages/common/src/utils.js @@ -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 + } + }) +} diff --git a/packages/config/package.json b/packages/config/package.json index 045596e9ef..7cefff1e78 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -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" diff --git a/packages/config/src/config/_app.js b/packages/config/src/config/_app.js new file mode 100644 index 0000000000..c591728e9c --- /dev/null +++ b/packages/config/src/config/_app.js @@ -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' + } +}) diff --git a/packages/config/src/config/_common.js b/packages/config/src/config/_common.js new file mode 100644 index 0000000000..9b8dec7742 --- /dev/null +++ b/packages/config/src/config/_common.js @@ -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 +}) diff --git a/packages/config/src/config/build.js b/packages/config/src/config/build.js index 70846ea9ed..af2037ab13 100644 --- a/packages/config/src/config/build.js +++ b/packages/config/src/config/build.js @@ -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/ ] } -} +}) diff --git a/packages/config/src/config/index.js b/packages/config/src/config/index.js index 8a16689662..daf607ef6c 100644 --- a/packages/config/src/config/index.js +++ b/packages/config/src/config/index.js @@ -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) + } } diff --git a/packages/config/src/config/messages.js b/packages/config/src/config/messages.js index 34a2dda4bf..0c3694c2b5 100644 --- a/packages/config/src/config/messages.js +++ b/packages/config/src/config/messages.js @@ -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.' -} +}) diff --git a/packages/config/src/modes.js b/packages/config/src/config/modes.js similarity index 86% rename from packages/config/src/modes.js rename to packages/config/src/config/modes.js index 676aea8b6d..b73a08fa62 100644 --- a/packages/config/src/modes.js +++ b/packages/config/src/config/modes.js @@ -1,4 +1,4 @@ -export default { +export default () => ({ universal: { build: { ssr: true @@ -15,4 +15,4 @@ export default { ssr: false } } -} +}) diff --git a/packages/config/src/config/render.js b/packages/config/src/config/render.js index da9dd2c4d0..28d96ca802 100644 --- a/packages/config/src/config/render.js +++ b/packages/config/src/config/render.js @@ -1,4 +1,4 @@ -export default { +export default () => ({ bundleRenderer: { shouldPrefetch: () => false }, @@ -24,4 +24,4 @@ export default { // 1 year in production maxAge: '1y' } -} +}) diff --git a/packages/config/src/config/router.js b/packages/config/src/config/router.js index 8d3c8fcb53..28bbf404cd 100644 --- a/packages/config/src/config/router.js +++ b/packages/config/src/config/router.js @@ -1,4 +1,4 @@ -export default { +export default () => ({ mode: 'history', base: '/', routes: [], @@ -10,4 +10,4 @@ export default { parseQuery: false, stringifyQuery: false, fallback: false -} +}) diff --git a/packages/config/src/config/server.js b/packages/config/src/config/server.js index 9bdbaef1a9..a037ae80ea 100644 --- a/packages/config/src/config/server.js +++ b/packages/config/src/config/server.js @@ -1,4 +1,4 @@ -export default env => ({ +export default ({ env }) => ({ https: false, port: env.NUXT_PORT || env.PORT || diff --git a/packages/config/src/index.js b/packages/config/src/index.js index e632c83881..41a04bd804 100644 --- a/packages/config/src/index.js +++ b/packages/config/src/index.js @@ -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' diff --git a/packages/common/src/options.js b/packages/config/src/options.js similarity index 90% rename from packages/common/src/options.js rename to packages/config/src/options.js index 795cf6c666..491eecebe7 100644 --- a/packages/common/src/options.js +++ b/packages/config/src/options.js @@ -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 diff --git a/packages/core/package.json b/packages/core/package.json index 3f55bf33c2..fee32c3f1d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -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" diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 332498907e..38ef78e5cd 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -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' diff --git a/packages/core/src/nuxt.js b/packages/core/src/nuxt.js index cdc9876833..e1570f075a 100644 --- a/packages/core/src/nuxt.js +++ b/packages/core/src/nuxt.js @@ -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) diff --git a/packages/core/src/renderer.js b/packages/core/src/renderer.js deleted file mode 100644 index 7726209da6..0000000000 --- a/packages/core/src/renderer.js +++ /dev/null @@ -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 = - `
${this.resources.loadingHTML}
` + 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 = `
` - } - 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 += `` - } - - 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 += `` - 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}` : `
` - ) - /* 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 - } -] diff --git a/packages/generator/src/generator.js b/packages/generator/src/generator.js index 2ca16d1c17..39ba8a2180 100644 --- a/packages/generator/src/generator.js +++ b/packages/generator/src/generator.js @@ -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 }) diff --git a/packages/app/package.js b/packages/server/package.js similarity index 100% rename from packages/app/package.js rename to packages/server/package.js diff --git a/packages/server/package.json b/packages/server/package.json new file mode 100644 index 0000000000..6c58be7c18 --- /dev/null +++ b/packages/server/package.json @@ -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" + } +} diff --git a/packages/server/src/context.js b/packages/server/src/context.js new file mode 100644 index 0000000000..dd8a0c3844 --- /dev/null +++ b/packages/server/src/context.js @@ -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 + } +} diff --git a/packages/server/src/index.js b/packages/server/src/index.js new file mode 100644 index 0000000000..fcf1442ba3 --- /dev/null +++ b/packages/server/src/index.js @@ -0,0 +1 @@ +export { default as Server } from './server' diff --git a/packages/server/src/jsdom.js b/packages/server/src/jsdom.js new file mode 100644 index 0000000000..41410325eb --- /dev/null +++ b/packages/server/src/jsdom.js @@ -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}` : `
` + ) + + /* 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 +} diff --git a/packages/core/src/middleware/error.js b/packages/server/src/middleware/error.js similarity index 80% rename from packages/core/src/middleware/error.js rename to packages/server/src/middleware/error.js index bec1bb45c2..01cf3a2b88 100644 --- a/packages/core/src/middleware/error.js +++ b/packages/server/src/middleware/error.js @@ -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] } } diff --git a/packages/core/src/middleware/nuxt.js b/packages/server/src/middleware/nuxt.js similarity index 75% rename from packages/core/src/middleware/nuxt.js rename to packages/server/src/middleware/nuxt.js index 3136354756..c11250c210 100644 --- a/packages/core/src/middleware/nuxt.js +++ b/packages/server/src/middleware/nuxt.js @@ -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 */ diff --git a/packages/server/src/server.js b/packages/server/src/server.js new file mode 100644 index 0000000000..986234439d --- /dev/null +++ b/packages/server/src/server.js @@ -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) + }) + } +} diff --git a/packages/vue-app/package.js b/packages/vue-app/package.js new file mode 100644 index 0000000000..19d0ef6e2d --- /dev/null +++ b/packages/vue-app/package.js @@ -0,0 +1,3 @@ +export default { + build: true +} diff --git a/packages/app/package.json b/packages/vue-app/package.json similarity index 75% rename from packages/app/package.json rename to packages/vue-app/package.json index 1c121aeb0f..edf38a3c03 100644 --- a/packages/app/package.json +++ b/packages/vue-app/package.json @@ -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" } diff --git a/packages/app/src/index.js b/packages/vue-app/src/index.js similarity index 100% rename from packages/app/src/index.js rename to packages/vue-app/src/index.js diff --git a/packages/app/template/.eslintignore b/packages/vue-app/template/.eslintignore similarity index 100% rename from packages/app/template/.eslintignore rename to packages/vue-app/template/.eslintignore diff --git a/packages/app/template/App.js b/packages/vue-app/template/App.js similarity index 100% rename from packages/app/template/App.js rename to packages/vue-app/template/App.js diff --git a/packages/app/template/client.js b/packages/vue-app/template/client.js similarity index 100% rename from packages/app/template/client.js rename to packages/vue-app/template/client.js diff --git a/packages/app/template/components/no-ssr.js b/packages/vue-app/template/components/no-ssr.js similarity index 100% rename from packages/app/template/components/no-ssr.js rename to packages/vue-app/template/components/no-ssr.js diff --git a/packages/app/template/components/nuxt-child.js b/packages/vue-app/template/components/nuxt-child.js similarity index 100% rename from packages/app/template/components/nuxt-child.js rename to packages/vue-app/template/components/nuxt-child.js diff --git a/packages/app/template/components/nuxt-error.vue b/packages/vue-app/template/components/nuxt-error.vue similarity index 100% rename from packages/app/template/components/nuxt-error.vue rename to packages/vue-app/template/components/nuxt-error.vue diff --git a/packages/app/template/components/nuxt-link.js b/packages/vue-app/template/components/nuxt-link.js similarity index 100% rename from packages/app/template/components/nuxt-link.js rename to packages/vue-app/template/components/nuxt-link.js diff --git a/packages/app/template/components/nuxt-loading.vue b/packages/vue-app/template/components/nuxt-loading.vue similarity index 100% rename from packages/app/template/components/nuxt-loading.vue rename to packages/vue-app/template/components/nuxt-loading.vue diff --git a/packages/app/template/components/nuxt.js b/packages/vue-app/template/components/nuxt.js similarity index 100% rename from packages/app/template/components/nuxt.js rename to packages/vue-app/template/components/nuxt.js diff --git a/packages/app/template/empty.js b/packages/vue-app/template/empty.js similarity index 100% rename from packages/app/template/empty.js rename to packages/vue-app/template/empty.js diff --git a/packages/app/template/index.js b/packages/vue-app/template/index.js similarity index 100% rename from packages/app/template/index.js rename to packages/vue-app/template/index.js diff --git a/packages/app/template/layouts/default.vue b/packages/vue-app/template/layouts/default.vue similarity index 100% rename from packages/app/template/layouts/default.vue rename to packages/vue-app/template/layouts/default.vue diff --git a/packages/app/template/middleware.js b/packages/vue-app/template/middleware.js similarity index 100% rename from packages/app/template/middleware.js rename to packages/vue-app/template/middleware.js diff --git a/packages/app/template/pages/index.vue b/packages/vue-app/template/pages/index.vue similarity index 100% rename from packages/app/template/pages/index.vue rename to packages/vue-app/template/pages/index.vue diff --git a/packages/app/template/router.js b/packages/vue-app/template/router.js similarity index 100% rename from packages/app/template/router.js rename to packages/vue-app/template/router.js diff --git a/packages/app/template/server.js b/packages/vue-app/template/server.js similarity index 100% rename from packages/app/template/server.js rename to packages/vue-app/template/server.js diff --git a/packages/app/template/store.js b/packages/vue-app/template/store.js similarity index 100% rename from packages/app/template/store.js rename to packages/vue-app/template/store.js diff --git a/packages/app/template/utils.js b/packages/vue-app/template/utils.js similarity index 100% rename from packages/app/template/utils.js rename to packages/vue-app/template/utils.js diff --git a/packages/app/template/views/app.template.html b/packages/vue-app/template/views/app.template.html similarity index 100% rename from packages/app/template/views/app.template.html rename to packages/vue-app/template/views/app.template.html diff --git a/packages/app/template/views/error.html b/packages/vue-app/template/views/error.html similarity index 100% rename from packages/app/template/views/error.html rename to packages/vue-app/template/views/error.html diff --git a/packages/app/template/views/loading/chasing-dots.html b/packages/vue-app/template/views/loading/chasing-dots.html similarity index 100% rename from packages/app/template/views/loading/chasing-dots.html rename to packages/vue-app/template/views/loading/chasing-dots.html diff --git a/packages/app/template/views/loading/circle.html b/packages/vue-app/template/views/loading/circle.html similarity index 100% rename from packages/app/template/views/loading/circle.html rename to packages/vue-app/template/views/loading/circle.html diff --git a/packages/app/template/views/loading/cube-grid.html b/packages/vue-app/template/views/loading/cube-grid.html similarity index 100% rename from packages/app/template/views/loading/cube-grid.html rename to packages/vue-app/template/views/loading/cube-grid.html diff --git a/packages/app/template/views/loading/default.html b/packages/vue-app/template/views/loading/default.html similarity index 100% rename from packages/app/template/views/loading/default.html rename to packages/vue-app/template/views/loading/default.html diff --git a/packages/app/template/views/loading/fading-circle.html b/packages/vue-app/template/views/loading/fading-circle.html similarity index 100% rename from packages/app/template/views/loading/fading-circle.html rename to packages/vue-app/template/views/loading/fading-circle.html diff --git a/packages/app/template/views/loading/folding-cube.html b/packages/vue-app/template/views/loading/folding-cube.html similarity index 100% rename from packages/app/template/views/loading/folding-cube.html rename to packages/vue-app/template/views/loading/folding-cube.html diff --git a/packages/app/template/views/loading/nuxt.html b/packages/vue-app/template/views/loading/nuxt.html similarity index 100% rename from packages/app/template/views/loading/nuxt.html rename to packages/vue-app/template/views/loading/nuxt.html diff --git a/packages/app/template/views/loading/pulse.html b/packages/vue-app/template/views/loading/pulse.html similarity index 100% rename from packages/app/template/views/loading/pulse.html rename to packages/vue-app/template/views/loading/pulse.html diff --git a/packages/app/template/views/loading/rectangle-bounce.html b/packages/vue-app/template/views/loading/rectangle-bounce.html similarity index 100% rename from packages/app/template/views/loading/rectangle-bounce.html rename to packages/vue-app/template/views/loading/rectangle-bounce.html diff --git a/packages/app/template/views/loading/rotating-plane.html b/packages/vue-app/template/views/loading/rotating-plane.html similarity index 100% rename from packages/app/template/views/loading/rotating-plane.html rename to packages/vue-app/template/views/loading/rotating-plane.html diff --git a/packages/app/template/views/loading/three-bounce.html b/packages/vue-app/template/views/loading/three-bounce.html similarity index 100% rename from packages/app/template/views/loading/three-bounce.html rename to packages/vue-app/template/views/loading/three-bounce.html diff --git a/packages/app/template/views/loading/wandering-cubes.html b/packages/vue-app/template/views/loading/wandering-cubes.html similarity index 100% rename from packages/app/template/views/loading/wandering-cubes.html rename to packages/vue-app/template/views/loading/wandering-cubes.html diff --git a/packages/vue-renderer/package.js b/packages/vue-renderer/package.js new file mode 100644 index 0000000000..19d0ef6e2d --- /dev/null +++ b/packages/vue-renderer/package.js @@ -0,0 +1,3 @@ +export default { + build: true +} diff --git a/packages/vue-renderer/package.json b/packages/vue-renderer/package.json new file mode 100644 index 0000000000..fa9cea0f98 --- /dev/null +++ b/packages/vue-renderer/package.json @@ -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" + } +} diff --git a/packages/vue-renderer/src/index.js b/packages/vue-renderer/src/index.js new file mode 100644 index 0000000000..0567e6025f --- /dev/null +++ b/packages/vue-renderer/src/index.js @@ -0,0 +1 @@ +export { default as VueRenderer } from './renderer' diff --git a/packages/vue-renderer/src/renderer.js b/packages/vue-renderer/src/renderer.js new file mode 100644 index 0000000000..832dda2dad --- /dev/null +++ b/packages/vue-renderer/src/renderer.js @@ -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 = + `
${this.context.resources.loadingHTML}
` + 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 = `
` + } + 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 += `` + } + + 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 += `` + 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 + } + ] + } +} diff --git a/packages/core/src/meta.js b/packages/vue-renderer/src/spa-meta.js similarity index 93% rename from packages/core/src/meta.js rename to packages/vue-renderer/src/spa-meta.js index 510431a86d..4940477085 100644 --- a/packages/core/src/meta.js +++ b/packages/vue-renderer/src/spa-meta.js @@ -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) diff --git a/packages/webpack/src/builder.js b/packages/webpack/src/builder.js index 5c1bcf4458..118ef2b393 100644 --- a/packages/webpack/src/builder.js +++ b/packages/webpack/src/builder.js @@ -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 } } diff --git a/packages/webpack/src/config/base.js b/packages/webpack/src/config/base.js index 106ce56374..50ac2140b7 100644 --- a/packages/webpack/src/config/base.js +++ b/packages/webpack/src/config/base.js @@ -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) } } } diff --git a/scripts/rollup.config.js b/scripts/rollup.config.js index 2c5790ee54..fbd31474c2 100644 --- a/scripts/rollup.config.js +++ b/scripts/rollup.config.js @@ -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: [ diff --git a/test/e2e/basic.browser.test.js b/test/e2e/basic.browser.test.js index 86cdc222d3..c215a4f6fa 100644 --- a/test/e2e/basic.browser.test.js +++ b/test/e2e/basic.browser.test.js @@ -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, diff --git a/test/e2e/basic.vue-config.test.js b/test/e2e/basic.vue-config.test.js index 90e563f08e..61386f1e0e 100644 --- a/test/e2e/basic.vue-config.test.js +++ b/test/e2e/basic.vue-config.test.js @@ -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 } diff --git a/test/e2e/children.patch.browser.test.js b/test/e2e/children.patch.browser.test.js index 6a189bf370..02ad78da15 100644 --- a/test/e2e/children.patch.browser.test.js +++ b/test/e2e/children.patch.browser.test.js @@ -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 () => { diff --git a/test/fixtures/cli/cli.build.test.js b/test/fixtures/cli/cli.build.test.js index 32aa75caee..109891c06d 100644 --- a/test/fixtures/cli/cli.build.test.js +++ b/test/fixtures/cli/cli.build.test.js @@ -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) diff --git a/test/unit/async-config.test.js b/test/unit/async-config.test.js index be54f33f2b..5e85754c3c 100644 --- a/test/unit/async-config.test.js +++ b/test/unit/async-config.test.js @@ -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('

I am ALIVE!

')).toBe(true) }) }) diff --git a/test/unit/basic.config.defaults.test.js b/test/unit/basic.config.defaults.test.js index 8695fa0d2c..f0de55e406 100644 --- a/test/unit/basic.config.defaults.test.js +++ b/test/unit/basic.config.defaults.test.js @@ -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') }) }) diff --git a/test/unit/basic.dev.test.js b/test/unit/basic.dev.test.js index cc9ab034e0..158edb964d 100644 --- a/test/unit/basic.dev.test.js +++ b/test/unit/basic.dev.test.js @@ -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('

My component!

')).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 }) diff --git a/test/unit/basic.generate.test.js b/test/unit/basic.generate.test.js index 5a274a7d8b..c4fb052451 100644 --- a/test/unit/basic.generate.test.js +++ b/test/unit/basic.generate.test.js @@ -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('

My component!

')).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('

mutated

')).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('

The answer is 42

')).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('

Nuxt.js

')).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')).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('

Index page

')).toBe(true) }) diff --git a/test/unit/basic.plugins.test.js b/test/unit/basic.plugins.test.js index 2399d54a95..4d1e470f1f 100644 --- a/test/unit/basic.plugins.test.js +++ b/test/unit/basic.plugins.test.js @@ -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) }) diff --git a/test/unit/basic.ssr.csp.test.js b/test/unit/basic.ssr.csp.test.js index b3c7177f06..4048a640de 100644 --- a/test/unit/basic.ssr.csp.test.js +++ b/test/unit/basic.ssr.csp.test.js @@ -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 } diff --git a/test/unit/basic.ssr.test.js b/test/unit/basic.ssr.test.js index a4fee24087..78b550d7fb 100644 --- a/test/unit/basic.ssr.test.js +++ b/test/unit/basic.ssr.test.js @@ -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('

My component!

')).toBe(true) }) test('/store-module', async () => { - const { html } = await nuxt.renderRoute('/store-module') + const { html } = await nuxt.server.renderRoute('/store-module') expect(html.includes('

mutated

')).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('

The answer is 42

')).toBe(true) }) test('/store', async () => { - const { html } = await nuxt.renderRoute('/store') + const { html } = await nuxt.server.renderRoute('/store') expect(html.includes('

Vuex Nested Modules

')).toBe(true) expect(html.includes('

1

')).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('

Nuxt.js

')).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('

Await Nuxt.js

')).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('

Callback Nuxt.js

')).toBe(true) }) test('/users/1', async () => { - const { html } = await nuxt.renderRoute('/users/1') + const { html } = await nuxt.server.renderRoute('/users/1') expect(html.includes('

User: 1

')).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('

I am valid

')).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('

I am valid

')).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('

Index page

')).toBe(true) }) test('/redirect', async () => { - const { html, redirected } = await nuxt.renderRoute('/redirect') + const { html, redirected } = await nuxt.server.renderRoute('/redirect') expect(html.includes('
')).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('

Index page

')).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('
')).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( '

Loading...

' )).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')).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(/
.*"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('

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('

Nuxt.js

')).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('

JSX Page

')).toBe(true) }) test('/jsx-link', async () => { - const { html } = await nuxt.renderRoute('/jsx-link') + const { html } = await nuxt.server.renderRoute('/jsx-link') expect(html.includes('

JSX Link Page

')).toBe(true) }) test('/js-link', async () => { - const { html } = await nuxt.renderRoute('/js-link') + const { html } = await nuxt.server.renderRoute('/js-link') expect(html.includes('

vue file is first-class

')).toBe(true) }) diff --git a/test/unit/children.test.js b/test/unit/children.test.js index 166b4ef518..4da2dcc8f9 100644 --- a/test/unit/children.test.js +++ b/test/unit/children.test.js @@ -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('

I am the parent

')).toBe(true) }) test('/parent/child', async () => { - const { html } = await nuxt.renderRoute('/parent/child') + const { html } = await nuxt.server.renderRoute('/parent/child') expect(html.includes('

I am the parent

')).toBe(true) expect(html.includes('

I am the child

')).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('

I am the parent

')).toBe(true) expect(html.includes('

Id=

')).toBe(true) }) test('/parent/1', async () => { - const { html } = await nuxt.renderRoute('/parent/1') + const { html } = await nuxt.server.renderRoute('/parent/1') expect(html.includes('

I am the parent

')).toBe(true) expect(html.includes('

Id=1

')).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('

I am the parent

')).toBe(true) expect(html.includes('

Child valid

')).toBe(true) }) diff --git a/test/unit/custom-app-template.test.js b/test/unit/custom-app-template.test.js index cd6dfe458d..22bbdb1174 100644 --- a/test/unit/custom-app-template.test.js +++ b/test/unit/custom-app-template.test.js @@ -8,10 +8,10 @@ describe('custom-app-template', () => { const options = await loadFixture('custom-app-template') 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 () => { - const { html } = await nuxt.renderRoute('/') + const { html } = await nuxt.server.renderRoute('/') expect(html.includes('

My Template

')).toBe(true) expect(html.includes('

Custom!

')).toBe(true) }) diff --git a/test/unit/custom-dirs.test.js b/test/unit/custom-dirs.test.js index 8f351db726..d5a6c31e71 100644 --- a/test/unit/custom-dirs.test.js +++ b/test/unit/custom-dirs.test.js @@ -13,7 +13,7 @@ describe('custom-dirs', () => { const config = await loadFixture('custom-dirs') nuxt = new Nuxt(config) port = await getPort() - await nuxt.listen(port, 'localhost') + await nuxt.server.listen(port, 'localhost') }) test('custom assets directory', async () => { @@ -26,18 +26,18 @@ describe('custom-dirs', () => { }) test('custom layouts directory', async () => { - const { html } = await nuxt.renderRoute('/') + const { html } = await nuxt.server.renderRoute('/') expect(html.includes('

I have custom layouts directory

')).toBe(true) }) test('custom middleware directory', async () => { - const window = await nuxt.renderAndGetWindow(url('/user-agent')) + const window = await nuxt.server.renderAndGetWindow(url('/user-agent')) const html = window.document.body.innerHTML expect(html.includes('
Mozilla')).toBe(true)
   })
 
   test('custom pages directory', async () => {
-    const { html } = await nuxt.renderRoute('/')
+    const { html } = await nuxt.server.renderRoute('/')
     expect(html.includes('

I have custom pages directory

')).toBe(true) }) diff --git a/test/unit/dist-options.test.js b/test/unit/dist-options.test.js index 2c78c1bfcb..fc0700796b 100644 --- a/test/unit/dist-options.test.js +++ b/test/unit/dist-options.test.js @@ -10,7 +10,7 @@ describe('dist options', () => { const options = await loadFixture('basic') nuxt = new Nuxt(Object.assign(options, { dev: false })) port = await getPort() - await nuxt.listen(port, '0.0.0.0') + await nuxt.server.listen(port, '0.0.0.0') }) test('Specify maxAge/index in render.dist options', async () => { diff --git a/test/unit/error.test.js b/test/unit/error.test.js index 97c2487cd5..474687d689 100644 --- a/test/unit/error.test.js +++ b/test/unit/error.test.js @@ -13,22 +13,22 @@ describe('error', () => { const config = await loadFixture('error') nuxt = new Nuxt(config) port = await getPort() - await nuxt.listen(port, 'localhost') + await nuxt.server.listen(port, 'localhost') }) test('/ should display an error', async () => { - await expect(nuxt.renderRoute('/')).rejects.toMatchObject({ + await expect(nuxt.server.renderRoute('/')).rejects.toMatchObject({ message: expect.stringContaining('not_defined is not defined') }) }) test('/404 should display an error too', async () => { - const { error } = await nuxt.renderRoute('/404') + const { error } = await nuxt.server.renderRoute('/404') expect(error.message.includes('This page could not be found')).toBe(true) }) test('/ with renderAndGetWindow()', async () => { - await expect(nuxt.renderAndGetWindow(url('/'))).rejects.toMatchObject({ + await expect(nuxt.server.renderAndGetWindow(url('/'))).rejects.toMatchObject({ statusCode: 500 }) }) diff --git a/test/unit/extract-css.test.js b/test/unit/extract-css.test.js index fd1b7c39a8..86bbd95ce2 100644 --- a/test/unit/extract-css.test.js +++ b/test/unit/extract-css.test.js @@ -11,7 +11,7 @@ describe('extract css', () => { beforeAll(async () => { const options = await loadFixture('extract-css') nuxt = new Nuxt(options) - await nuxt.listen(await getPort(), '0.0.0.0') + await nuxt.server.listen(await getPort(), '0.0.0.0') }) test.skip('Verify global.css has been extracted and minified', async () => { @@ -27,7 +27,7 @@ describe('extract css', () => { }) test('/about should contain module style', async () => { - const { html } = await nuxt.renderRoute('/about') + const { html } = await nuxt.server.renderRoute('/about') expect(html).toMatch(/

I'm BLUE<\/h1>/) }) }) diff --git a/test/unit/fallback.generate.test.js b/test/unit/fallback.generate.test.js index 1d52b306cf..2afec697e6 100644 --- a/test/unit/fallback.generate.test.js +++ b/test/unit/fallback.generate.test.js @@ -3,7 +3,7 @@ import { existsSync } from 'fs' import { resolve } from 'path' import serveStatic from 'serve-static' import finalhandler from 'finalhandler' -import { loadFixture, getPort, Nuxt, Generator, Options, rp } from '../utils' +import { loadFixture, getPort, Nuxt, Generator, getNuxtConfig, rp } from '../utils' let port const url = route => 'http://localhost:' + port + route @@ -76,7 +76,7 @@ describe('fallback generate', () => { test('generate.fallback = true is transformed to /404.html', () => { nuxt.options.generate.fallback = true - const options = Options.from(nuxt.options) + const options = getNuxtConfig(nuxt.options) expect(options.generate.fallback).toBe('404.html') }) diff --git a/test/unit/https.test.js b/test/unit/https.test.js index 1168a920d3..6539a0ad90 100644 --- a/test/unit/https.test.js +++ b/test/unit/https.test.js @@ -7,11 +7,11 @@ describe('basic https', () => { const options = await loadFixture('https') nuxt = new Nuxt(options) const port = await getPort() - await nuxt.listen(port, '0.0.0.0') + await nuxt.server.listen(port, '0.0.0.0') }) test('/', async () => { - const { html } = await nuxt.renderRoute('/') + const { html } = await nuxt.server.renderRoute('/') expect(html.includes('

Served over HTTPS!

')).toBe(true) }) diff --git a/test/unit/module.test.js b/test/unit/module.test.js index f53ff3859a..0fd337d84d 100644 --- a/test/unit/module.test.js +++ b/test/unit/module.test.js @@ -13,26 +13,26 @@ describe('module', () => { const config = await loadFixture('module') nuxt = new Nuxt(config) port = await getPort() - await nuxt.listen(port, 'localhost') + await nuxt.server.listen(port, 'localhost') }) test('Plugin', async () => { expect(normalize(nuxt.options.plugins[0].src).includes( normalize('fixtures/module/.nuxt/basic.reverse.') )).toBe(true) - const { html } = await nuxt.renderRoute('/') + const { html } = await nuxt.server.renderRoute('/') expect(html.includes('

TXUN

')).toBe(true) }) test('Layout', async () => { expect(nuxt.options.layouts.layout.includes('layout')).toBe(true) - const { html } = await nuxt.renderRoute('/layout') + const { html } = await nuxt.server.renderRoute('/layout') expect(html.includes('

Module Layouts

')).toBe(true) }) test('/404 should display the module error layout', async () => { - const { html } = await nuxt.renderRoute('/404') + const { html } = await nuxt.server.renderRoute('/404') expect(html).toContain('You should see the error in a different Vue!') }) @@ -60,7 +60,7 @@ describe('module', () => { }) test('Hooks - render context', async () => { - await nuxt.renderRoute('/render-context') + await nuxt.server.renderRoute('/render-context') expect(nuxt.__render_context).toBeTruthy() }) diff --git a/test/unit/nuxt.test.js b/test/unit/nuxt.test.js index 0108e7c2e2..fb3396947b 100644 --- a/test/unit/nuxt.test.js +++ b/test/unit/nuxt.test.js @@ -36,9 +36,9 @@ describe('nuxt', () => { const nuxt = new Nuxt() new Builder(nuxt).build() const port = await getPort() - await nuxt.listen(port, 'localhost') + await nuxt.server.listen(port, 'localhost') - const { html } = await nuxt.renderRoute('/') + const { html } = await nuxt.server.renderRoute('/') expect(html.includes('Universal Vue.js Applications')).toBe(true) expect(/Landscape__Page__Explanation/.test(html)).toBe(true) diff --git a/test/unit/sockets.test.js b/test/unit/sockets.test.js index a617b3af04..85be8752b0 100644 --- a/test/unit/sockets.test.js +++ b/test/unit/sockets.test.js @@ -6,11 +6,11 @@ describe.skip.win('basic sockets', () => { beforeAll(async () => { const options = await loadFixture('sockets') nuxt = new Nuxt(options) - await nuxt.listen() + await nuxt.server.listen() }) test('/', async () => { - const { html } = await nuxt.renderRoute('/') + const { html } = await nuxt.server.renderRoute('/') expect(html.includes('

Served over sockets!

')).toBe(true) }) diff --git a/test/unit/spa.test.js b/test/unit/spa.test.js index f631535657..01d60ad17d 100644 --- a/test/unit/spa.test.js +++ b/test/unit/spa.test.js @@ -5,7 +5,7 @@ let nuxt, port const url = route => 'http://localhost:' + port + route const renderRoute = async (_url) => { - const window = await nuxt.renderAndGetWindow(url(_url)) + const window = await nuxt.server.renderAndGetWindow(url(_url)) const head = window.document.head.innerHTML const html = window.document.body.innerHTML return { window, head, html } @@ -16,7 +16,7 @@ describe('spa', () => { const config = await loadFixture('spa') nuxt = new Nuxt(config) port = await getPort() - await nuxt.listen(port, 'localhost') + await nuxt.server.listen(port, 'localhost') }) test('/ (basic spa)', async () => { diff --git a/test/unit/ssr.test.js b/test/unit/ssr.test.js index 5656e86f6f..5e09eb6a8e 100644 --- a/test/unit/ssr.test.js +++ b/test/unit/ssr.test.js @@ -23,7 +23,7 @@ const uniqueTest = async (url) => { const results = [] await parallel(range(5), async () => { - const { html } = await nuxt.renderRoute(url) + const { html } = await nuxt.server.renderRoute(url) const foobar = match(FOOBAR_REGEX, html) results.push(parseInt(foobar)) }) @@ -67,7 +67,7 @@ describe('ssr', () => { const config = await loadFixture('ssr') nuxt = new Nuxt(config) port = await getPort() - await nuxt.listen(port, 'localhost') + await nuxt.server.listen(port, 'localhost') }) test('unique responses with data()', async () => { @@ -99,7 +99,7 @@ describe('ssr', () => { }) test('store undefined variable response', async () => { - const window = await nuxt.renderAndGetWindow(url('/store')) + const window = await nuxt.server.renderAndGetWindow(url('/store')) expect('idUndefined' in window.__NUXT__.state).toBe(true) expect(window.__NUXT__.state.idUndefined).toEqual(undefined) }) diff --git a/test/unit/with-config.test.js b/test/unit/with-config.test.js index 09bea055fc..453281c03e 100644 --- a/test/unit/with-config.test.js +++ b/test/unit/with-config.test.js @@ -11,53 +11,53 @@ describe('with-config', () => { const config = await loadFixture('with-config') nuxt = new Nuxt(config) port = await getPort() - await nuxt.listen(port, 'localhost') + await nuxt.server.listen(port, 'localhost') }) test('/', async () => { - const { html } = await nuxt.renderRoute('/') + const { html } = await nuxt.server.renderRoute('/') expect(html.includes('

I have custom configurations

')).toBe(true) }) test('/ (asset name for analyze mode)', async () => { - const { html } = await nuxt.renderRoute('/') + const { html } = await nuxt.server.renderRoute('/') expect(html).toContain('