/*!
|
* @nuxt/server v2.15.8 (c) 2016-2021
|
* Released under the MIT License
|
* Repository: https://github.com/nuxt/nuxt.js
|
* Website: https://nuxtjs.org
|
*/
|
'use strict';
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
const path = require('path');
|
const consola = require('consola');
|
const launchMiddleware = require('launch-editor-middleware');
|
const serveStatic = require('serve-static');
|
const servePlaceholder = require('serve-placeholder');
|
const connect = require('connect');
|
const compression = require('compression');
|
const utils = require('@nuxt/utils');
|
const vueRenderer = require('@nuxt/vue-renderer');
|
const generateETag = require('etag');
|
const fresh = require('fresh');
|
const ufo = require('ufo');
|
const fs = require('fs-extra');
|
const Youch = require('@nuxtjs/youch');
|
const http = require('http');
|
const https = require('https');
|
const enableDestroy = require('server-destroy');
|
const ip = require('ip');
|
const pify = require('pify');
|
const onHeaders = require('on-headers');
|
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
function _interopNamespace(e) {
|
if (e && e.__esModule) return e;
|
var n = Object.create(null);
|
if (e) {
|
Object.keys(e).forEach(function (k) {
|
if (k !== 'default') {
|
var d = Object.getOwnPropertyDescriptor(e, k);
|
Object.defineProperty(n, k, d.get ? d : {
|
enumerable: true,
|
get: function () {
|
return e[k];
|
}
|
});
|
}
|
});
|
}
|
n['default'] = e;
|
return Object.freeze(n);
|
}
|
|
const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
|
const launchMiddleware__default = /*#__PURE__*/_interopDefaultLegacy(launchMiddleware);
|
const serveStatic__default = /*#__PURE__*/_interopDefaultLegacy(serveStatic);
|
const servePlaceholder__default = /*#__PURE__*/_interopDefaultLegacy(servePlaceholder);
|
const connect__default = /*#__PURE__*/_interopDefaultLegacy(connect);
|
const compression__default = /*#__PURE__*/_interopDefaultLegacy(compression);
|
const generateETag__default = /*#__PURE__*/_interopDefaultLegacy(generateETag);
|
const fresh__default = /*#__PURE__*/_interopDefaultLegacy(fresh);
|
const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
const Youch__default = /*#__PURE__*/_interopDefaultLegacy(Youch);
|
const http__default = /*#__PURE__*/_interopDefaultLegacy(http);
|
const https__default = /*#__PURE__*/_interopDefaultLegacy(https);
|
const enableDestroy__default = /*#__PURE__*/_interopDefaultLegacy(enableDestroy);
|
const ip__default = /*#__PURE__*/_interopDefaultLegacy(ip);
|
const pify__default = /*#__PURE__*/_interopDefaultLegacy(pify);
|
const onHeaders__default = /*#__PURE__*/_interopDefaultLegacy(onHeaders);
|
|
class ServerContext {
|
constructor (server) {
|
this.nuxt = server.nuxt;
|
this.globals = server.globals;
|
this.options = server.options;
|
this.resources = server.resources;
|
}
|
}
|
|
async function renderAndGetWindow (
|
url = 'http://localhost:3000',
|
jsdomOpts = {},
|
{
|
loadedCallback,
|
loadingTimeout = 2000,
|
globals
|
} = {}
|
) {
|
const jsdom = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('jsdom')); })
|
.then(m => m.default || m)
|
.catch((e) => {
|
consola__default['default'].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__default['default']);
|
}
|
// Throw error when window creation failed
|
options.virtualConsole.on('jsdomError', jsdomErrHandler);
|
} else {
|
// If we get the virtualConsole option as `false` we should delete for don't pass it to `jsdom.JSDOM.fromURL`
|
delete options.virtualConsole;
|
}
|
|
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(`id="${globals.id}"`);
|
|
if (!nuxtExists) {
|
const error = new Error('Could not load the nuxt app');
|
error.body = window.document.body.innerHTML;
|
window.close();
|
throw error
|
}
|
|
// Used by Nuxt to say when the components are loaded and the app ready
|
await utils.timeout(new Promise((resolve) => {
|
window[loadedCallback] = () => resolve(window);
|
}), loadingTimeout, `Components loading in renderAndGetWindow was not completed in ${loadingTimeout / 1000}s`);
|
|
if (options.virtualConsole) {
|
// After window initialized successfully
|
options.virtualConsole.removeListener('jsdomError', jsdomErrHandler);
|
}
|
|
// Send back window object
|
return window
|
}
|
|
const nuxtMiddleware = ({ options, nuxt, renderRoute, resources }) => async function nuxtMiddleware (req, res, next) {
|
// Get context
|
const context = utils.getContext(req, res);
|
|
try {
|
const url = ufo.normalizeURL(req.url);
|
res.statusCode = 200;
|
const result = await renderRoute(url, context);
|
|
// If result is falsy, call renderLoading
|
if (!result) {
|
await nuxt.callHook('server:nuxt:renderLoading', req, res);
|
return
|
}
|
|
await nuxt.callHook('render:route', url, result, context);
|
const {
|
html,
|
cspScriptSrcHashes,
|
error,
|
redirected,
|
preloadFiles
|
} = result;
|
|
if (redirected && context.target !== utils.TARGETS.static) {
|
await nuxt.callHook('render:routeDone', url, result, context);
|
return html
|
}
|
if (error) {
|
res.statusCode = context.nuxt.error.statusCode || 500;
|
}
|
|
if (options.render.csp && cspScriptSrcHashes) {
|
const { allowedSources, policies } = options.render.csp;
|
const isReportOnly = !!options.render.csp.reportOnly;
|
const cspHeader = isReportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy';
|
|
res.setHeader(cspHeader, getCspString({ cspScriptSrcHashes, allowedSources, policies, isReportOnly }));
|
}
|
|
// Add ETag header
|
if (!error && options.render.etag) {
|
const { hash } = options.render.etag;
|
const etag = hash ? hash(html, options.render.etag) : generateETag__default['default'](html, options.render.etag);
|
if (fresh__default['default'](req.headers, { etag })) {
|
res.statusCode = 304;
|
await nuxt.callHook('render:beforeResponse', url, result, context);
|
res.end();
|
await nuxt.callHook('render:routeDone', url, result, context);
|
return
|
}
|
res.setHeader('ETag', etag);
|
}
|
|
// HTTP2 push headers for preload assets
|
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 { shouldPush, pushAssets } = options.render.http2;
|
const { publicPath } = resources.clientManifest;
|
|
const links = pushAssets
|
? pushAssets(req, res, publicPath, preloadFiles)
|
: defaultPushAssets(preloadFiles, shouldPush, publicPath, options);
|
|
// Pass with single Link header
|
// https://blog.cloudflare.com/http-2-server-push-with-multiple-assets-per-link-header
|
// https://www.w3.org/Protocols/9707-link-header.html
|
if (links.length > 0) {
|
res.setHeader('Link', links.join(', '));
|
}
|
}
|
|
// Send response
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
res.setHeader('Accept-Ranges', 'none'); // #3870
|
res.setHeader('Content-Length', Buffer.byteLength(html));
|
await nuxt.callHook('render:beforeResponse', url, result, context);
|
res.end(html, 'utf8');
|
await nuxt.callHook('render:routeDone', url, result, context);
|
return html
|
} catch (err) {
|
if (context && context.redirected) {
|
consola__default['default'].error(err);
|
return err
|
}
|
|
if (err.name === 'URIError') {
|
err.statusCode = 400;
|
}
|
next(err);
|
}
|
};
|
|
const defaultPushAssets = (preloadFiles, shouldPush, publicPath, options) => {
|
if (shouldPush && options.dev) {
|
consola__default['default'].warn('http2.shouldPush is deprecated. Use http2.pushAssets function');
|
}
|
|
const links = [];
|
preloadFiles.forEach(({ file, asType, fileWithoutQuery, modern }) => {
|
// By default, we only preload scripts or css
|
if (!shouldPush && asType !== 'script' && asType !== 'style') {
|
return
|
}
|
|
// User wants to explicitly control what to preload
|
if (shouldPush && !shouldPush(fileWithoutQuery, asType)) {
|
return
|
}
|
|
const { crossorigin } = options.render;
|
const cors = `${crossorigin ? ` crossorigin=${crossorigin};` : ''}`;
|
// `modulepreload` rel attribute only supports script-like `as` value
|
// https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload
|
const rel = modern && asType === 'script' ? 'modulepreload' : 'preload';
|
|
links.push(`<${publicPath}${file}>; rel=${rel};${cors} as=${asType}`);
|
});
|
return links
|
};
|
|
const getCspString = ({ cspScriptSrcHashes, allowedSources, policies, isReportOnly }) => {
|
const joinedHashes = cspScriptSrcHashes.join(' ');
|
const baseCspStr = `script-src 'self' ${joinedHashes}`;
|
const policyObjectAvailable = typeof policies === 'object' && policies !== null && !Array.isArray(policies);
|
|
if (Array.isArray(allowedSources) && allowedSources.length) {
|
return isReportOnly && policyObjectAvailable && !!policies['report-uri'] ? `${baseCspStr} ${allowedSources.join(' ')}; report-uri ${policies['report-uri']};` : `${baseCspStr} ${allowedSources.join(' ')}`
|
}
|
|
if (policyObjectAvailable) {
|
const transformedPolicyObject = transformPolicyObject(policies, cspScriptSrcHashes);
|
return Object.entries(transformedPolicyObject).map(([k, v]) => `${k} ${Array.isArray(v) ? v.join(' ') : v}`).join('; ')
|
}
|
|
return baseCspStr
|
};
|
|
const transformPolicyObject = (policies, cspScriptSrcHashes) => {
|
const userHasDefinedScriptSrc = policies['script-src'] && Array.isArray(policies['script-src']);
|
|
const additionalPolicies = userHasDefinedScriptSrc ? policies['script-src'] : [];
|
|
// Self is always needed for inline-scripts, so add it, no matter if the user specified script-src himself.
|
const hashAndPolicyList = cspScriptSrcHashes.concat('\'self\'', additionalPolicies);
|
|
return { ...policies, 'script-src': hashAndPolicyList }
|
};
|
|
const errorMiddleware = ({ resources, options }) => async function errorMiddleware (_error, req, res, next) {
|
// Normalize error
|
const error = normalizeError(_error, options);
|
|
const sendResponse = (content, type = 'text/html') => {
|
// Set Headers
|
res.statusCode = error.statusCode;
|
res.statusMessage = 'RuntimeError';
|
res.setHeader('Content-Type', type + '; charset=utf-8');
|
res.setHeader('Content-Length', Buffer.byteLength(content));
|
res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate');
|
|
// Error headers
|
if (error.headers) {
|
for (const name in error.headers) {
|
res.setHeader(name, error.headers[name]);
|
}
|
}
|
|
// Send Response
|
res.end(content, 'utf-8');
|
};
|
|
// Check if request accepts JSON
|
const hasReqHeader = (header, includes) =>
|
req.headers[header] && req.headers[header].toLowerCase().includes(includes);
|
const isJson =
|
hasReqHeader('accept', 'application/json') ||
|
hasReqHeader('user-agent', 'curl/');
|
|
// Use basic errors when debug mode is disabled
|
if (!options.debug) {
|
// We hide actual errors from end users, so show them on server logs
|
if (error.statusCode !== 404) {
|
consola__default['default'].error(error);
|
}
|
|
// Json format is compatible with Youch json responses
|
const json = {
|
status: error.statusCode,
|
message: error.message,
|
name: error.name
|
};
|
if (isJson) {
|
sendResponse(JSON.stringify(json, undefined, 2), 'text/json');
|
return
|
}
|
const html = resources.errorTemplate(json);
|
sendResponse(html);
|
return
|
}
|
|
// Show stack trace
|
const youch = new Youch__default['default'](
|
error,
|
req,
|
readSource,
|
options.router.base,
|
true
|
);
|
if (isJson) {
|
const json = await youch.toJSON();
|
sendResponse(JSON.stringify(json, undefined, 2), 'text/json');
|
return
|
}
|
|
const html = await youch.toHTML();
|
sendResponse(html);
|
};
|
|
const sanitizeName = name => name ? name.replace('webpack:///', '').split('?')[0] : null;
|
|
const normalizeError = (_error, { srcDir, rootDir, buildDir }) => {
|
if (typeof _error === 'string') {
|
_error = { message: _error };
|
} else if (!_error) {
|
_error = { message: '<empty>' };
|
}
|
|
const error = new Error(_error.message);
|
error.name = _error.name;
|
error.statusCode = _error.statusCode || 500;
|
error.headers = _error.headers;
|
|
const searchPath = [
|
srcDir,
|
rootDir,
|
path__default['default'].join(buildDir, 'dist', 'server'),
|
buildDir,
|
process.cwd()
|
];
|
|
const findInPaths = (fileName) => {
|
for (const dir of searchPath) {
|
const fullPath = path__default['default'].resolve(dir, fileName);
|
if (fs__default['default'].existsSync(fullPath)) {
|
return fullPath
|
}
|
}
|
return fileName
|
};
|
|
error.stack = (_error.stack || '')
|
.split('\n')
|
.map((line) => {
|
const match = line.match(/\(([^)]+)\)|([^\s]+\.[^\s]+):/);
|
if (!match) {
|
return line
|
}
|
const src = match[1] || match[2] || '';
|
return line.replace(src, findInPaths(sanitizeName(src)))
|
})
|
.join('\n');
|
|
return error
|
};
|
|
async function readSource (frame) {
|
if (fs__default['default'].existsSync(frame.fileName)) {
|
frame.fullPath = frame.fileName; // Youch BW compat
|
frame.contents = await fs__default['default'].readFile(frame.fileName, 'utf-8');
|
}
|
}
|
|
let RANDOM_PORT = '0';
|
|
class Listener {
|
constructor ({ port, host, socket, https, app, dev, baseURL }) {
|
// Options
|
this.port = port;
|
this.host = host;
|
this.socket = socket;
|
this.https = https;
|
this.app = app;
|
this.dev = dev;
|
this.baseURL = baseURL;
|
|
// After listen
|
this.listening = false;
|
this._server = null;
|
this.server = null;
|
this.address = null;
|
this.url = null;
|
}
|
|
async close () {
|
// Destroy server by forcing every connection to be closed
|
if (this.server && this.server.listening) {
|
await this.server.destroy();
|
consola__default['default'].debug('server closed');
|
}
|
|
// Delete references
|
this.listening = false;
|
this._server = null;
|
this.server = null;
|
this.address = null;
|
this.url = null;
|
}
|
|
computeURL () {
|
const address = this.server.address();
|
if (!this.socket) {
|
switch (address.address) {
|
case '127.0.0.1': this.host = 'localhost'; break
|
case '0.0.0.0': this.host = ip__default['default'].address(); break
|
}
|
this.port = address.port;
|
this.url = `http${this.https ? 's' : ''}://${this.host}:${this.port}${this.baseURL}`;
|
this.url = decodeURI(this.url);
|
return
|
}
|
this.url = `unix+http://${address}`;
|
}
|
|
async listen () {
|
// Prevent multi calls
|
if (this.listening) {
|
return
|
}
|
|
// Initialize underlying http(s) server
|
const protocol = this.https ? https__default['default'] : http__default['default'];
|
const protocolOpts = this.https ? [this.https] : [];
|
this._server = protocol.createServer.apply(protocol, protocolOpts.concat(this.app));
|
|
// Call server.listen
|
// Prepare listenArgs
|
const listenArgs = this.socket ? { path: this.socket } : { host: this.host, port: this.port };
|
listenArgs.exclusive = false;
|
|
// Call server.listen
|
try {
|
this.server = await new Promise((resolve, reject) => {
|
this._server.on('error', error => reject(error));
|
const s = this._server.listen(listenArgs, error => error ? reject(error) : resolve(s));
|
});
|
} catch (error) {
|
return this.serverErrorHandler(error)
|
}
|
|
// Enable destroy support
|
enableDestroy__default['default'](this.server);
|
pify__default['default'](this.server.destroy);
|
|
// Compute listen URL
|
this.computeURL();
|
|
// Set this.listening to true
|
this.listening = true;
|
}
|
|
async serverErrorHandler (error) {
|
// Detect if port is not available
|
const addressInUse = error.code === 'EADDRINUSE';
|
|
// Use better error message
|
if (addressInUse) {
|
const address = this.socket || `${this.host}:${this.port}`;
|
error.message = `Address \`${address}\` is already in use.`;
|
|
// Listen to a random port on dev as a fallback
|
if (this.dev && !this.socket && this.port !== RANDOM_PORT) {
|
consola__default['default'].warn(error.message);
|
consola__default['default'].info('Trying a random port...');
|
this.port = RANDOM_PORT;
|
await this.close();
|
await this.listen();
|
RANDOM_PORT = this.port;
|
return
|
}
|
}
|
|
// Throw error
|
throw error
|
}
|
}
|
|
const createTimingMiddleware = options => (req, res, next) => {
|
if (res.timing) {
|
consola__default['default'].warn('server-timing is already registered.');
|
}
|
res.timing = new ServerTiming();
|
|
if (options && options.total) {
|
res.timing.start('total', 'Nuxt Server Time');
|
}
|
|
onHeaders__default['default'](res, () => {
|
res.timing.end('total');
|
|
if (res.timing.headers.length > 0) {
|
res.setHeader(
|
'Server-Timing',
|
[]
|
.concat(res.getHeader('Server-Timing') || [])
|
.concat(res.timing.headers)
|
.join(', ')
|
);
|
}
|
res.timing.clear();
|
});
|
|
next();
|
};
|
|
class ServerTiming extends utils.Timer {
|
constructor (...args) {
|
super(...args);
|
this.headers = [];
|
}
|
|
end (...args) {
|
const time = super.end(...args);
|
if (time) {
|
this.headers.push(this.formatHeader(time));
|
}
|
return time
|
}
|
|
clear () {
|
super.clear();
|
this.headers.length = 0;
|
}
|
|
formatHeader (time) {
|
const desc = time.description ? `;desc="${time.description}"` : '';
|
return `${time.name};dur=${time.duration}${desc}`
|
}
|
}
|
|
class Server {
|
constructor (nuxt) {
|
this.nuxt = nuxt;
|
this.options = nuxt.options;
|
|
this.globals = utils.determineGlobals(nuxt.options.globalName, nuxt.options.globals);
|
|
this.publicPath = utils.isUrl(this.options.build.publicPath)
|
? this.options.build._publicPath
|
: this.options.build.publicPath.replace(/^\.+\//, '/');
|
|
// Runtime shared resources
|
this.resources = {};
|
|
// Will be set after listen
|
this.listeners = [];
|
|
// Create new connect instance
|
this.app = connect__default['default']();
|
|
// Close hook
|
this.nuxt.hook('close', () => this.close());
|
|
// devMiddleware placeholder
|
if (this.options.dev) {
|
this.nuxt.hook('server:devMiddleware', (devMiddleware) => {
|
this.devMiddleware = devMiddleware;
|
});
|
}
|
}
|
|
async ready () {
|
if (this._readyCalled) {
|
return this
|
}
|
this._readyCalled = true;
|
|
await this.nuxt.callHook('render:before', this, this.options.render);
|
|
// Initialize vue-renderer
|
this.serverContext = new ServerContext(this);
|
this.renderer = new vueRenderer.VueRenderer(this.serverContext);
|
await this.renderer.ready();
|
|
// Setup nuxt middleware
|
await this.setupMiddleware();
|
|
// Call done hook
|
await this.nuxt.callHook('render:done', this);
|
|
return 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;
|
if (typeof compressor === 'object') {
|
// If only setting for `compression` are provided, require the module and insert
|
this.useMiddleware(compression__default['default'](compressor));
|
} else if (compressor) {
|
// Else, require own compression middleware if compressor is actually truthy
|
this.useMiddleware(compressor);
|
}
|
}
|
|
if (this.options.server.timing) {
|
this.useMiddleware(createTimingMiddleware(this.options.server.timing));
|
}
|
|
// For serving static/ files to /
|
const staticMiddleware = serveStatic__default['default'](
|
path__default['default'].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__default['default'].resolve(this.options.buildDir, 'dist', 'client');
|
this.useMiddleware({
|
path: this.publicPath,
|
handler: serveStatic__default['default'](
|
distDir,
|
this.options.render.dist
|
)
|
});
|
}
|
|
// Dev middleware
|
if (this.options.dev) {
|
this.useMiddleware((req, res, next) => {
|
if (!this.devMiddleware) {
|
return next()
|
}
|
// Safari over-caches JS (breaking HMR) and the seemingly only way to turn
|
// this off in dev mode is to set Vary: * header
|
// #3828, #9034
|
if (req.url.startsWith(this.publicPath) && req.url.endsWith('.js')) {
|
res.setHeader('Vary', '*');
|
}
|
this.devMiddleware(req, res, next);
|
});
|
|
// open in editor for debug mode only
|
if (this.options.debug) {
|
this.useMiddleware({
|
path: '__open-in-editor',
|
handler: launchMiddleware__default['default'](this.options.editor)
|
});
|
}
|
}
|
|
// Add user provided middleware
|
for (const m of this.options.serverMiddleware) {
|
this.useMiddleware(m);
|
}
|
|
// Graceful 404 error handler
|
const { fallback } = this.options.render;
|
if (fallback) {
|
// Dist files
|
if (fallback.dist) {
|
this.useMiddleware({
|
path: this.publicPath,
|
handler: servePlaceholder__default['default'](fallback.dist)
|
});
|
}
|
|
// Other paths
|
if (fallback.static) {
|
this.useMiddleware({
|
path: '/',
|
handler: servePlaceholder__default['default'](fallback.static)
|
});
|
}
|
}
|
|
// Finally use nuxtMiddleware
|
this.useMiddleware(nuxtMiddleware({
|
options: this.options,
|
nuxt: this.nuxt,
|
renderRoute: this.renderRoute.bind(this),
|
resources: this.resources
|
}));
|
|
// DX: redirect if router.base in development
|
const routerBase = this.nuxt.options.router.base;
|
if (this.options.dev && routerBase !== '/') {
|
this.useMiddleware({
|
prefix: false,
|
handler: (req, res, next) => {
|
if (decodeURI(req.url).startsWith(decodeURI(routerBase))) {
|
return next()
|
}
|
const to = utils.urlJoin(routerBase, req.url);
|
consola__default['default'].info(`[Development] Redirecting from \`${decodeURI(req.url)}\` to \`${decodeURI(to)}\` (router.base specified)`);
|
res.writeHead(302, {
|
Location: to
|
});
|
res.end();
|
}
|
});
|
}
|
|
// Apply errorMiddleware from modules first
|
await this.nuxt.callHook('render:errorMiddleware', this.app);
|
|
// Error middleware for errors that occurred in middleware that declared above
|
this.useMiddleware(errorMiddleware({
|
resources: this.resources,
|
options: this.options
|
}));
|
}
|
|
_normalizeMiddleware (middleware) {
|
// Normalize plain function
|
if (typeof middleware === 'function') {
|
middleware = { handle: middleware };
|
}
|
|
// If a plain string provided as path to middleware
|
if (typeof middleware === 'string') {
|
middleware = this._requireMiddleware(middleware);
|
}
|
|
// #8584
|
// shallow clone the middleware before any change is made,
|
// in case any following mutation breaks when applied repeatedly.
|
middleware = Object.assign({}, middleware);
|
|
// Normalize handler to handle (backward compatibility)
|
if (middleware.handler && !middleware.handle) {
|
middleware.handle = middleware.handler;
|
delete middleware.handler;
|
}
|
|
// Normalize path to route (backward compatibility)
|
if (middleware.path && !middleware.route) {
|
middleware.route = middleware.path;
|
delete middleware.path;
|
}
|
|
// If handle is a string pointing to path
|
if (typeof middleware.handle === 'string') {
|
Object.assign(middleware, this._requireMiddleware(middleware.handle));
|
}
|
|
// No handle
|
if (!middleware.handle) {
|
middleware.handle = (req, res, next) => {
|
next(new Error('ServerMiddleware should expose a handle: ' + middleware.entry));
|
};
|
}
|
|
// Prefix on handle (proxy-module)
|
if (middleware.handle.prefix !== undefined && middleware.prefix === undefined) {
|
middleware.prefix = middleware.handle.prefix;
|
}
|
|
// sub-app (express)
|
if (typeof middleware.handle.handle === 'function') {
|
const server = middleware.handle;
|
middleware.handle = server.handle.bind(server);
|
}
|
|
return middleware
|
}
|
|
_requireMiddleware (entry) {
|
// Resolve entry
|
entry = this.nuxt.resolver.resolvePath(entry);
|
|
// Require middleware
|
let middleware;
|
try {
|
middleware = this.nuxt.resolver.requireModule(entry);
|
} catch (error) {
|
// Show full error
|
consola__default['default'].error('ServerMiddleware Error:', error);
|
|
// Placeholder for error
|
middleware = (req, res, next) => { next(error); };
|
}
|
|
// Normalize
|
middleware = this._normalizeMiddleware(middleware);
|
|
// Set entry
|
middleware.entry = entry;
|
|
return middleware
|
}
|
|
resolveMiddleware (middleware, fallbackRoute = '/') {
|
// Ensure middleware is normalized
|
middleware = this._normalizeMiddleware(middleware);
|
|
// Fallback route
|
if (!middleware.route) {
|
middleware.route = fallbackRoute;
|
}
|
|
// #8584
|
// save the original route before applying defaults
|
middleware._originalRoute = middleware.route;
|
|
// Resolve final route
|
middleware.route = (
|
(middleware.prefix !== false ? this.options.router.base : '') +
|
(typeof middleware.route === 'string' ? middleware.route : '')
|
).replace(/\/\//g, '/');
|
|
// Strip trailing slash
|
if (middleware.route.endsWith('/')) {
|
middleware.route = middleware.route.slice(0, -1);
|
}
|
|
// Assign _middleware to handle to make accessible from app.stack
|
middleware.handle._middleware = middleware;
|
|
return middleware
|
}
|
|
useMiddleware (middleware) {
|
const { route, handle } = this.resolveMiddleware(middleware);
|
this.app.use(route, handle);
|
}
|
|
replaceMiddleware (query, middleware) {
|
let serverStackItem;
|
|
if (typeof query === 'string') {
|
// Search by entry
|
serverStackItem = this.app.stack.find(({ handle }) => handle._middleware && handle._middleware.entry === query);
|
} else {
|
// Search by reference
|
serverStackItem = this.app.stack.find(({ handle }) => handle === query);
|
}
|
|
// Stop if item not found
|
if (!serverStackItem) {
|
return
|
}
|
|
// unload middleware
|
this.unloadMiddleware(serverStackItem);
|
|
// Resolve middleware
|
const { route, handle } = this.resolveMiddleware(
|
middleware,
|
// #8584 pass the original route as fallback
|
serverStackItem.handle._middleware
|
? serverStackItem.handle._middleware._originalRoute
|
: serverStackItem.route
|
);
|
|
// Update serverStackItem
|
serverStackItem.handle = handle;
|
|
// Error State
|
serverStackItem.route = route;
|
|
// Return updated item
|
return serverStackItem
|
}
|
|
unloadMiddleware ({ handle }) {
|
if (handle._middleware && typeof handle._middleware.unload === 'function') {
|
handle._middleware.unload();
|
}
|
}
|
|
serverMiddlewarePaths () {
|
return this.app.stack.map(({ handle }) => handle._middleware && handle._middleware.entry).filter(Boolean)
|
}
|
|
renderRoute () {
|
return this.renderer.renderRoute.apply(this.renderer, arguments)
|
}
|
|
loadResources () {
|
return this.renderer.loadResources.apply(this.renderer, arguments)
|
}
|
|
renderAndGetWindow (url, opts = {}, {
|
loadingTimeout = 2000,
|
loadedCallback = this.globals.loadedCallback,
|
globals = this.globals
|
} = {}) {
|
return renderAndGetWindow(url, opts, {
|
loadingTimeout,
|
loadedCallback,
|
globals
|
})
|
}
|
|
async listen (port, host, socket) {
|
// Ensure nuxt is ready
|
await this.nuxt.ready();
|
|
// Create a new listener
|
const listener = new Listener({
|
port: isNaN(parseInt(port)) ? this.options.server.port : port,
|
host: host || this.options.server.host,
|
socket: socket || this.options.server.socket,
|
https: this.options.server.https,
|
app: this.app,
|
dev: this.options.dev,
|
baseURL: this.options.router.base
|
});
|
|
// Listen
|
await listener.listen();
|
|
// Push listener to this.listeners
|
this.listeners.push(listener);
|
|
await this.nuxt.callHook('listen', listener.server, listener);
|
|
return listener
|
}
|
|
async close () {
|
if (this.__closed) {
|
return
|
}
|
this.__closed = true;
|
|
await Promise.all(this.listeners.map(l => l.close()));
|
|
this.listeners = [];
|
|
if (typeof this.renderer.close === 'function') {
|
await this.renderer.close();
|
}
|
|
this.app.stack.forEach(this.unloadMiddleware);
|
this.app.removeAllListeners();
|
this.app = null;
|
|
for (const key in this.resources) {
|
delete this.resources[key];
|
}
|
}
|
}
|
|
exports.Listener = Listener;
|
exports.Server = Server;
|