123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008 |
- /*!
- * @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;
|