server.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  1. /*!
  2. * @nuxt/server v2.15.8 (c) 2016-2021
  3. * Released under the MIT License
  4. * Repository: https://github.com/nuxt/nuxt.js
  5. * Website: https://nuxtjs.org
  6. */
  7. 'use strict';
  8. Object.defineProperty(exports, '__esModule', { value: true });
  9. const path = require('path');
  10. const consola = require('consola');
  11. const launchMiddleware = require('launch-editor-middleware');
  12. const serveStatic = require('serve-static');
  13. const servePlaceholder = require('serve-placeholder');
  14. const connect = require('connect');
  15. const compression = require('compression');
  16. const utils = require('@nuxt/utils');
  17. const vueRenderer = require('@nuxt/vue-renderer');
  18. const generateETag = require('etag');
  19. const fresh = require('fresh');
  20. const ufo = require('ufo');
  21. const fs = require('fs-extra');
  22. const Youch = require('@nuxtjs/youch');
  23. const http = require('http');
  24. const https = require('https');
  25. const enableDestroy = require('server-destroy');
  26. const ip = require('ip');
  27. const pify = require('pify');
  28. const onHeaders = require('on-headers');
  29. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
  30. function _interopNamespace(e) {
  31. if (e && e.__esModule) return e;
  32. var n = Object.create(null);
  33. if (e) {
  34. Object.keys(e).forEach(function (k) {
  35. if (k !== 'default') {
  36. var d = Object.getOwnPropertyDescriptor(e, k);
  37. Object.defineProperty(n, k, d.get ? d : {
  38. enumerable: true,
  39. get: function () {
  40. return e[k];
  41. }
  42. });
  43. }
  44. });
  45. }
  46. n['default'] = e;
  47. return Object.freeze(n);
  48. }
  49. const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
  50. const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
  51. const launchMiddleware__default = /*#__PURE__*/_interopDefaultLegacy(launchMiddleware);
  52. const serveStatic__default = /*#__PURE__*/_interopDefaultLegacy(serveStatic);
  53. const servePlaceholder__default = /*#__PURE__*/_interopDefaultLegacy(servePlaceholder);
  54. const connect__default = /*#__PURE__*/_interopDefaultLegacy(connect);
  55. const compression__default = /*#__PURE__*/_interopDefaultLegacy(compression);
  56. const generateETag__default = /*#__PURE__*/_interopDefaultLegacy(generateETag);
  57. const fresh__default = /*#__PURE__*/_interopDefaultLegacy(fresh);
  58. const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
  59. const Youch__default = /*#__PURE__*/_interopDefaultLegacy(Youch);
  60. const http__default = /*#__PURE__*/_interopDefaultLegacy(http);
  61. const https__default = /*#__PURE__*/_interopDefaultLegacy(https);
  62. const enableDestroy__default = /*#__PURE__*/_interopDefaultLegacy(enableDestroy);
  63. const ip__default = /*#__PURE__*/_interopDefaultLegacy(ip);
  64. const pify__default = /*#__PURE__*/_interopDefaultLegacy(pify);
  65. const onHeaders__default = /*#__PURE__*/_interopDefaultLegacy(onHeaders);
  66. class ServerContext {
  67. constructor (server) {
  68. this.nuxt = server.nuxt;
  69. this.globals = server.globals;
  70. this.options = server.options;
  71. this.resources = server.resources;
  72. }
  73. }
  74. async function renderAndGetWindow (
  75. url = 'http://localhost:3000',
  76. jsdomOpts = {},
  77. {
  78. loadedCallback,
  79. loadingTimeout = 2000,
  80. globals
  81. } = {}
  82. ) {
  83. const jsdom = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('jsdom')); })
  84. .then(m => m.default || m)
  85. .catch((e) => {
  86. consola__default['default'].error(`
  87. jsdom is not installed. Please install jsdom with:
  88. $ yarn add --dev jsdom
  89. OR
  90. $ npm install --dev jsdom
  91. `);
  92. throw e
  93. });
  94. const options = Object.assign({
  95. // Load subresources (https://github.com/tmpvar/jsdom#loading-subresources)
  96. resources: 'usable',
  97. runScripts: 'dangerously',
  98. virtualConsole: true,
  99. beforeParse (window) {
  100. // Mock window.scrollTo
  101. window.scrollTo = () => {};
  102. }
  103. }, jsdomOpts);
  104. const jsdomErrHandler = (err) => {
  105. throw err
  106. };
  107. if (options.virtualConsole) {
  108. if (options.virtualConsole === true) {
  109. options.virtualConsole = new jsdom.VirtualConsole().sendTo(consola__default['default']);
  110. }
  111. // Throw error when window creation failed
  112. options.virtualConsole.on('jsdomError', jsdomErrHandler);
  113. } else {
  114. // If we get the virtualConsole option as `false` we should delete for don't pass it to `jsdom.JSDOM.fromURL`
  115. delete options.virtualConsole;
  116. }
  117. const { window } = await jsdom.JSDOM.fromURL(url, options);
  118. // If Nuxt could not be loaded (error from the server-side)
  119. const nuxtExists = window.document.body.innerHTML.includes(`id="${globals.id}"`);
  120. if (!nuxtExists) {
  121. const error = new Error('Could not load the nuxt app');
  122. error.body = window.document.body.innerHTML;
  123. window.close();
  124. throw error
  125. }
  126. // Used by Nuxt to say when the components are loaded and the app ready
  127. await utils.timeout(new Promise((resolve) => {
  128. window[loadedCallback] = () => resolve(window);
  129. }), loadingTimeout, `Components loading in renderAndGetWindow was not completed in ${loadingTimeout / 1000}s`);
  130. if (options.virtualConsole) {
  131. // After window initialized successfully
  132. options.virtualConsole.removeListener('jsdomError', jsdomErrHandler);
  133. }
  134. // Send back window object
  135. return window
  136. }
  137. const nuxtMiddleware = ({ options, nuxt, renderRoute, resources }) => async function nuxtMiddleware (req, res, next) {
  138. // Get context
  139. const context = utils.getContext(req, res);
  140. try {
  141. const url = ufo.normalizeURL(req.url);
  142. res.statusCode = 200;
  143. const result = await renderRoute(url, context);
  144. // If result is falsy, call renderLoading
  145. if (!result) {
  146. await nuxt.callHook('server:nuxt:renderLoading', req, res);
  147. return
  148. }
  149. await nuxt.callHook('render:route', url, result, context);
  150. const {
  151. html,
  152. cspScriptSrcHashes,
  153. error,
  154. redirected,
  155. preloadFiles
  156. } = result;
  157. if (redirected && context.target !== utils.TARGETS.static) {
  158. await nuxt.callHook('render:routeDone', url, result, context);
  159. return html
  160. }
  161. if (error) {
  162. res.statusCode = context.nuxt.error.statusCode || 500;
  163. }
  164. if (options.render.csp && cspScriptSrcHashes) {
  165. const { allowedSources, policies } = options.render.csp;
  166. const isReportOnly = !!options.render.csp.reportOnly;
  167. const cspHeader = isReportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy';
  168. res.setHeader(cspHeader, getCspString({ cspScriptSrcHashes, allowedSources, policies, isReportOnly }));
  169. }
  170. // Add ETag header
  171. if (!error && options.render.etag) {
  172. const { hash } = options.render.etag;
  173. const etag = hash ? hash(html, options.render.etag) : generateETag__default['default'](html, options.render.etag);
  174. if (fresh__default['default'](req.headers, { etag })) {
  175. res.statusCode = 304;
  176. await nuxt.callHook('render:beforeResponse', url, result, context);
  177. res.end();
  178. await nuxt.callHook('render:routeDone', url, result, context);
  179. return
  180. }
  181. res.setHeader('ETag', etag);
  182. }
  183. // HTTP2 push headers for preload assets
  184. if (!error && options.render.http2.push) {
  185. // Parse resourceHints to extract HTTP.2 prefetch/push headers
  186. // https://w3c.github.io/preload/#server-push-http-2
  187. const { shouldPush, pushAssets } = options.render.http2;
  188. const { publicPath } = resources.clientManifest;
  189. const links = pushAssets
  190. ? pushAssets(req, res, publicPath, preloadFiles)
  191. : defaultPushAssets(preloadFiles, shouldPush, publicPath, options);
  192. // Pass with single Link header
  193. // https://blog.cloudflare.com/http-2-server-push-with-multiple-assets-per-link-header
  194. // https://www.w3.org/Protocols/9707-link-header.html
  195. if (links.length > 0) {
  196. res.setHeader('Link', links.join(', '));
  197. }
  198. }
  199. // Send response
  200. res.setHeader('Content-Type', 'text/html; charset=utf-8');
  201. res.setHeader('Accept-Ranges', 'none'); // #3870
  202. res.setHeader('Content-Length', Buffer.byteLength(html));
  203. await nuxt.callHook('render:beforeResponse', url, result, context);
  204. res.end(html, 'utf8');
  205. await nuxt.callHook('render:routeDone', url, result, context);
  206. return html
  207. } catch (err) {
  208. if (context && context.redirected) {
  209. consola__default['default'].error(err);
  210. return err
  211. }
  212. if (err.name === 'URIError') {
  213. err.statusCode = 400;
  214. }
  215. next(err);
  216. }
  217. };
  218. const defaultPushAssets = (preloadFiles, shouldPush, publicPath, options) => {
  219. if (shouldPush && options.dev) {
  220. consola__default['default'].warn('http2.shouldPush is deprecated. Use http2.pushAssets function');
  221. }
  222. const links = [];
  223. preloadFiles.forEach(({ file, asType, fileWithoutQuery, modern }) => {
  224. // By default, we only preload scripts or css
  225. if (!shouldPush && asType !== 'script' && asType !== 'style') {
  226. return
  227. }
  228. // User wants to explicitly control what to preload
  229. if (shouldPush && !shouldPush(fileWithoutQuery, asType)) {
  230. return
  231. }
  232. const { crossorigin } = options.render;
  233. const cors = `${crossorigin ? ` crossorigin=${crossorigin};` : ''}`;
  234. // `modulepreload` rel attribute only supports script-like `as` value
  235. // https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload
  236. const rel = modern && asType === 'script' ? 'modulepreload' : 'preload';
  237. links.push(`<${publicPath}${file}>; rel=${rel};${cors} as=${asType}`);
  238. });
  239. return links
  240. };
  241. const getCspString = ({ cspScriptSrcHashes, allowedSources, policies, isReportOnly }) => {
  242. const joinedHashes = cspScriptSrcHashes.join(' ');
  243. const baseCspStr = `script-src 'self' ${joinedHashes}`;
  244. const policyObjectAvailable = typeof policies === 'object' && policies !== null && !Array.isArray(policies);
  245. if (Array.isArray(allowedSources) && allowedSources.length) {
  246. return isReportOnly && policyObjectAvailable && !!policies['report-uri'] ? `${baseCspStr} ${allowedSources.join(' ')}; report-uri ${policies['report-uri']};` : `${baseCspStr} ${allowedSources.join(' ')}`
  247. }
  248. if (policyObjectAvailable) {
  249. const transformedPolicyObject = transformPolicyObject(policies, cspScriptSrcHashes);
  250. return Object.entries(transformedPolicyObject).map(([k, v]) => `${k} ${Array.isArray(v) ? v.join(' ') : v}`).join('; ')
  251. }
  252. return baseCspStr
  253. };
  254. const transformPolicyObject = (policies, cspScriptSrcHashes) => {
  255. const userHasDefinedScriptSrc = policies['script-src'] && Array.isArray(policies['script-src']);
  256. const additionalPolicies = userHasDefinedScriptSrc ? policies['script-src'] : [];
  257. // Self is always needed for inline-scripts, so add it, no matter if the user specified script-src himself.
  258. const hashAndPolicyList = cspScriptSrcHashes.concat('\'self\'', additionalPolicies);
  259. return { ...policies, 'script-src': hashAndPolicyList }
  260. };
  261. const errorMiddleware = ({ resources, options }) => async function errorMiddleware (_error, req, res, next) {
  262. // Normalize error
  263. const error = normalizeError(_error, options);
  264. const sendResponse = (content, type = 'text/html') => {
  265. // Set Headers
  266. res.statusCode = error.statusCode;
  267. res.statusMessage = 'RuntimeError';
  268. res.setHeader('Content-Type', type + '; charset=utf-8');
  269. res.setHeader('Content-Length', Buffer.byteLength(content));
  270. res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate');
  271. // Error headers
  272. if (error.headers) {
  273. for (const name in error.headers) {
  274. res.setHeader(name, error.headers[name]);
  275. }
  276. }
  277. // Send Response
  278. res.end(content, 'utf-8');
  279. };
  280. // Check if request accepts JSON
  281. const hasReqHeader = (header, includes) =>
  282. req.headers[header] && req.headers[header].toLowerCase().includes(includes);
  283. const isJson =
  284. hasReqHeader('accept', 'application/json') ||
  285. hasReqHeader('user-agent', 'curl/');
  286. // Use basic errors when debug mode is disabled
  287. if (!options.debug) {
  288. // We hide actual errors from end users, so show them on server logs
  289. if (error.statusCode !== 404) {
  290. consola__default['default'].error(error);
  291. }
  292. // Json format is compatible with Youch json responses
  293. const json = {
  294. status: error.statusCode,
  295. message: error.message,
  296. name: error.name
  297. };
  298. if (isJson) {
  299. sendResponse(JSON.stringify(json, undefined, 2), 'text/json');
  300. return
  301. }
  302. const html = resources.errorTemplate(json);
  303. sendResponse(html);
  304. return
  305. }
  306. // Show stack trace
  307. const youch = new Youch__default['default'](
  308. error,
  309. req,
  310. readSource,
  311. options.router.base,
  312. true
  313. );
  314. if (isJson) {
  315. const json = await youch.toJSON();
  316. sendResponse(JSON.stringify(json, undefined, 2), 'text/json');
  317. return
  318. }
  319. const html = await youch.toHTML();
  320. sendResponse(html);
  321. };
  322. const sanitizeName = name => name ? name.replace('webpack:///', '').split('?')[0] : null;
  323. const normalizeError = (_error, { srcDir, rootDir, buildDir }) => {
  324. if (typeof _error === 'string') {
  325. _error = { message: _error };
  326. } else if (!_error) {
  327. _error = { message: '<empty>' };
  328. }
  329. const error = new Error(_error.message);
  330. error.name = _error.name;
  331. error.statusCode = _error.statusCode || 500;
  332. error.headers = _error.headers;
  333. const searchPath = [
  334. srcDir,
  335. rootDir,
  336. path__default['default'].join(buildDir, 'dist', 'server'),
  337. buildDir,
  338. process.cwd()
  339. ];
  340. const findInPaths = (fileName) => {
  341. for (const dir of searchPath) {
  342. const fullPath = path__default['default'].resolve(dir, fileName);
  343. if (fs__default['default'].existsSync(fullPath)) {
  344. return fullPath
  345. }
  346. }
  347. return fileName
  348. };
  349. error.stack = (_error.stack || '')
  350. .split('\n')
  351. .map((line) => {
  352. const match = line.match(/\(([^)]+)\)|([^\s]+\.[^\s]+):/);
  353. if (!match) {
  354. return line
  355. }
  356. const src = match[1] || match[2] || '';
  357. return line.replace(src, findInPaths(sanitizeName(src)))
  358. })
  359. .join('\n');
  360. return error
  361. };
  362. async function readSource (frame) {
  363. if (fs__default['default'].existsSync(frame.fileName)) {
  364. frame.fullPath = frame.fileName; // Youch BW compat
  365. frame.contents = await fs__default['default'].readFile(frame.fileName, 'utf-8');
  366. }
  367. }
  368. let RANDOM_PORT = '0';
  369. class Listener {
  370. constructor ({ port, host, socket, https, app, dev, baseURL }) {
  371. // Options
  372. this.port = port;
  373. this.host = host;
  374. this.socket = socket;
  375. this.https = https;
  376. this.app = app;
  377. this.dev = dev;
  378. this.baseURL = baseURL;
  379. // After listen
  380. this.listening = false;
  381. this._server = null;
  382. this.server = null;
  383. this.address = null;
  384. this.url = null;
  385. }
  386. async close () {
  387. // Destroy server by forcing every connection to be closed
  388. if (this.server && this.server.listening) {
  389. await this.server.destroy();
  390. consola__default['default'].debug('server closed');
  391. }
  392. // Delete references
  393. this.listening = false;
  394. this._server = null;
  395. this.server = null;
  396. this.address = null;
  397. this.url = null;
  398. }
  399. computeURL () {
  400. const address = this.server.address();
  401. if (!this.socket) {
  402. switch (address.address) {
  403. case '127.0.0.1': this.host = 'localhost'; break
  404. case '0.0.0.0': this.host = ip__default['default'].address(); break
  405. }
  406. this.port = address.port;
  407. this.url = `http${this.https ? 's' : ''}://${this.host}:${this.port}${this.baseURL}`;
  408. this.url = decodeURI(this.url);
  409. return
  410. }
  411. this.url = `unix+http://${address}`;
  412. }
  413. async listen () {
  414. // Prevent multi calls
  415. if (this.listening) {
  416. return
  417. }
  418. // Initialize underlying http(s) server
  419. const protocol = this.https ? https__default['default'] : http__default['default'];
  420. const protocolOpts = this.https ? [this.https] : [];
  421. this._server = protocol.createServer.apply(protocol, protocolOpts.concat(this.app));
  422. // Call server.listen
  423. // Prepare listenArgs
  424. const listenArgs = this.socket ? { path: this.socket } : { host: this.host, port: this.port };
  425. listenArgs.exclusive = false;
  426. // Call server.listen
  427. try {
  428. this.server = await new Promise((resolve, reject) => {
  429. this._server.on('error', error => reject(error));
  430. const s = this._server.listen(listenArgs, error => error ? reject(error) : resolve(s));
  431. });
  432. } catch (error) {
  433. return this.serverErrorHandler(error)
  434. }
  435. // Enable destroy support
  436. enableDestroy__default['default'](this.server);
  437. pify__default['default'](this.server.destroy);
  438. // Compute listen URL
  439. this.computeURL();
  440. // Set this.listening to true
  441. this.listening = true;
  442. }
  443. async serverErrorHandler (error) {
  444. // Detect if port is not available
  445. const addressInUse = error.code === 'EADDRINUSE';
  446. // Use better error message
  447. if (addressInUse) {
  448. const address = this.socket || `${this.host}:${this.port}`;
  449. error.message = `Address \`${address}\` is already in use.`;
  450. // Listen to a random port on dev as a fallback
  451. if (this.dev && !this.socket && this.port !== RANDOM_PORT) {
  452. consola__default['default'].warn(error.message);
  453. consola__default['default'].info('Trying a random port...');
  454. this.port = RANDOM_PORT;
  455. await this.close();
  456. await this.listen();
  457. RANDOM_PORT = this.port;
  458. return
  459. }
  460. }
  461. // Throw error
  462. throw error
  463. }
  464. }
  465. const createTimingMiddleware = options => (req, res, next) => {
  466. if (res.timing) {
  467. consola__default['default'].warn('server-timing is already registered.');
  468. }
  469. res.timing = new ServerTiming();
  470. if (options && options.total) {
  471. res.timing.start('total', 'Nuxt Server Time');
  472. }
  473. onHeaders__default['default'](res, () => {
  474. res.timing.end('total');
  475. if (res.timing.headers.length > 0) {
  476. res.setHeader(
  477. 'Server-Timing',
  478. []
  479. .concat(res.getHeader('Server-Timing') || [])
  480. .concat(res.timing.headers)
  481. .join(', ')
  482. );
  483. }
  484. res.timing.clear();
  485. });
  486. next();
  487. };
  488. class ServerTiming extends utils.Timer {
  489. constructor (...args) {
  490. super(...args);
  491. this.headers = [];
  492. }
  493. end (...args) {
  494. const time = super.end(...args);
  495. if (time) {
  496. this.headers.push(this.formatHeader(time));
  497. }
  498. return time
  499. }
  500. clear () {
  501. super.clear();
  502. this.headers.length = 0;
  503. }
  504. formatHeader (time) {
  505. const desc = time.description ? `;desc="${time.description}"` : '';
  506. return `${time.name};dur=${time.duration}${desc}`
  507. }
  508. }
  509. class Server {
  510. constructor (nuxt) {
  511. this.nuxt = nuxt;
  512. this.options = nuxt.options;
  513. this.globals = utils.determineGlobals(nuxt.options.globalName, nuxt.options.globals);
  514. this.publicPath = utils.isUrl(this.options.build.publicPath)
  515. ? this.options.build._publicPath
  516. : this.options.build.publicPath.replace(/^\.+\//, '/');
  517. // Runtime shared resources
  518. this.resources = {};
  519. // Will be set after listen
  520. this.listeners = [];
  521. // Create new connect instance
  522. this.app = connect__default['default']();
  523. // Close hook
  524. this.nuxt.hook('close', () => this.close());
  525. // devMiddleware placeholder
  526. if (this.options.dev) {
  527. this.nuxt.hook('server:devMiddleware', (devMiddleware) => {
  528. this.devMiddleware = devMiddleware;
  529. });
  530. }
  531. }
  532. async ready () {
  533. if (this._readyCalled) {
  534. return this
  535. }
  536. this._readyCalled = true;
  537. await this.nuxt.callHook('render:before', this, this.options.render);
  538. // Initialize vue-renderer
  539. this.serverContext = new ServerContext(this);
  540. this.renderer = new vueRenderer.VueRenderer(this.serverContext);
  541. await this.renderer.ready();
  542. // Setup nuxt middleware
  543. await this.setupMiddleware();
  544. // Call done hook
  545. await this.nuxt.callHook('render:done', this);
  546. return this
  547. }
  548. async setupMiddleware () {
  549. // Apply setupMiddleware from modules first
  550. await this.nuxt.callHook('render:setupMiddleware', this.app);
  551. // Compression middleware for production
  552. if (!this.options.dev) {
  553. const { compressor } = this.options.render;
  554. if (typeof compressor === 'object') {
  555. // If only setting for `compression` are provided, require the module and insert
  556. this.useMiddleware(compression__default['default'](compressor));
  557. } else if (compressor) {
  558. // Else, require own compression middleware if compressor is actually truthy
  559. this.useMiddleware(compressor);
  560. }
  561. }
  562. if (this.options.server.timing) {
  563. this.useMiddleware(createTimingMiddleware(this.options.server.timing));
  564. }
  565. // For serving static/ files to /
  566. const staticMiddleware = serveStatic__default['default'](
  567. path__default['default'].resolve(this.options.srcDir, this.options.dir.static),
  568. this.options.render.static
  569. );
  570. staticMiddleware.prefix = this.options.render.static.prefix;
  571. this.useMiddleware(staticMiddleware);
  572. // Serve .nuxt/dist/client files only for production
  573. // For dev they will be served with devMiddleware
  574. if (!this.options.dev) {
  575. const distDir = path__default['default'].resolve(this.options.buildDir, 'dist', 'client');
  576. this.useMiddleware({
  577. path: this.publicPath,
  578. handler: serveStatic__default['default'](
  579. distDir,
  580. this.options.render.dist
  581. )
  582. });
  583. }
  584. // Dev middleware
  585. if (this.options.dev) {
  586. this.useMiddleware((req, res, next) => {
  587. if (!this.devMiddleware) {
  588. return next()
  589. }
  590. // Safari over-caches JS (breaking HMR) and the seemingly only way to turn
  591. // this off in dev mode is to set Vary: * header
  592. // #3828, #9034
  593. if (req.url.startsWith(this.publicPath) && req.url.endsWith('.js')) {
  594. res.setHeader('Vary', '*');
  595. }
  596. this.devMiddleware(req, res, next);
  597. });
  598. // open in editor for debug mode only
  599. if (this.options.debug) {
  600. this.useMiddleware({
  601. path: '__open-in-editor',
  602. handler: launchMiddleware__default['default'](this.options.editor)
  603. });
  604. }
  605. }
  606. // Add user provided middleware
  607. for (const m of this.options.serverMiddleware) {
  608. this.useMiddleware(m);
  609. }
  610. // Graceful 404 error handler
  611. const { fallback } = this.options.render;
  612. if (fallback) {
  613. // Dist files
  614. if (fallback.dist) {
  615. this.useMiddleware({
  616. path: this.publicPath,
  617. handler: servePlaceholder__default['default'](fallback.dist)
  618. });
  619. }
  620. // Other paths
  621. if (fallback.static) {
  622. this.useMiddleware({
  623. path: '/',
  624. handler: servePlaceholder__default['default'](fallback.static)
  625. });
  626. }
  627. }
  628. // Finally use nuxtMiddleware
  629. this.useMiddleware(nuxtMiddleware({
  630. options: this.options,
  631. nuxt: this.nuxt,
  632. renderRoute: this.renderRoute.bind(this),
  633. resources: this.resources
  634. }));
  635. // DX: redirect if router.base in development
  636. const routerBase = this.nuxt.options.router.base;
  637. if (this.options.dev && routerBase !== '/') {
  638. this.useMiddleware({
  639. prefix: false,
  640. handler: (req, res, next) => {
  641. if (decodeURI(req.url).startsWith(decodeURI(routerBase))) {
  642. return next()
  643. }
  644. const to = utils.urlJoin(routerBase, req.url);
  645. consola__default['default'].info(`[Development] Redirecting from \`${decodeURI(req.url)}\` to \`${decodeURI(to)}\` (router.base specified)`);
  646. res.writeHead(302, {
  647. Location: to
  648. });
  649. res.end();
  650. }
  651. });
  652. }
  653. // Apply errorMiddleware from modules first
  654. await this.nuxt.callHook('render:errorMiddleware', this.app);
  655. // Error middleware for errors that occurred in middleware that declared above
  656. this.useMiddleware(errorMiddleware({
  657. resources: this.resources,
  658. options: this.options
  659. }));
  660. }
  661. _normalizeMiddleware (middleware) {
  662. // Normalize plain function
  663. if (typeof middleware === 'function') {
  664. middleware = { handle: middleware };
  665. }
  666. // If a plain string provided as path to middleware
  667. if (typeof middleware === 'string') {
  668. middleware = this._requireMiddleware(middleware);
  669. }
  670. // #8584
  671. // shallow clone the middleware before any change is made,
  672. // in case any following mutation breaks when applied repeatedly.
  673. middleware = Object.assign({}, middleware);
  674. // Normalize handler to handle (backward compatibility)
  675. if (middleware.handler && !middleware.handle) {
  676. middleware.handle = middleware.handler;
  677. delete middleware.handler;
  678. }
  679. // Normalize path to route (backward compatibility)
  680. if (middleware.path && !middleware.route) {
  681. middleware.route = middleware.path;
  682. delete middleware.path;
  683. }
  684. // If handle is a string pointing to path
  685. if (typeof middleware.handle === 'string') {
  686. Object.assign(middleware, this._requireMiddleware(middleware.handle));
  687. }
  688. // No handle
  689. if (!middleware.handle) {
  690. middleware.handle = (req, res, next) => {
  691. next(new Error('ServerMiddleware should expose a handle: ' + middleware.entry));
  692. };
  693. }
  694. // Prefix on handle (proxy-module)
  695. if (middleware.handle.prefix !== undefined && middleware.prefix === undefined) {
  696. middleware.prefix = middleware.handle.prefix;
  697. }
  698. // sub-app (express)
  699. if (typeof middleware.handle.handle === 'function') {
  700. const server = middleware.handle;
  701. middleware.handle = server.handle.bind(server);
  702. }
  703. return middleware
  704. }
  705. _requireMiddleware (entry) {
  706. // Resolve entry
  707. entry = this.nuxt.resolver.resolvePath(entry);
  708. // Require middleware
  709. let middleware;
  710. try {
  711. middleware = this.nuxt.resolver.requireModule(entry);
  712. } catch (error) {
  713. // Show full error
  714. consola__default['default'].error('ServerMiddleware Error:', error);
  715. // Placeholder for error
  716. middleware = (req, res, next) => { next(error); };
  717. }
  718. // Normalize
  719. middleware = this._normalizeMiddleware(middleware);
  720. // Set entry
  721. middleware.entry = entry;
  722. return middleware
  723. }
  724. resolveMiddleware (middleware, fallbackRoute = '/') {
  725. // Ensure middleware is normalized
  726. middleware = this._normalizeMiddleware(middleware);
  727. // Fallback route
  728. if (!middleware.route) {
  729. middleware.route = fallbackRoute;
  730. }
  731. // #8584
  732. // save the original route before applying defaults
  733. middleware._originalRoute = middleware.route;
  734. // Resolve final route
  735. middleware.route = (
  736. (middleware.prefix !== false ? this.options.router.base : '') +
  737. (typeof middleware.route === 'string' ? middleware.route : '')
  738. ).replace(/\/\//g, '/');
  739. // Strip trailing slash
  740. if (middleware.route.endsWith('/')) {
  741. middleware.route = middleware.route.slice(0, -1);
  742. }
  743. // Assign _middleware to handle to make accessible from app.stack
  744. middleware.handle._middleware = middleware;
  745. return middleware
  746. }
  747. useMiddleware (middleware) {
  748. const { route, handle } = this.resolveMiddleware(middleware);
  749. this.app.use(route, handle);
  750. }
  751. replaceMiddleware (query, middleware) {
  752. let serverStackItem;
  753. if (typeof query === 'string') {
  754. // Search by entry
  755. serverStackItem = this.app.stack.find(({ handle }) => handle._middleware && handle._middleware.entry === query);
  756. } else {
  757. // Search by reference
  758. serverStackItem = this.app.stack.find(({ handle }) => handle === query);
  759. }
  760. // Stop if item not found
  761. if (!serverStackItem) {
  762. return
  763. }
  764. // unload middleware
  765. this.unloadMiddleware(serverStackItem);
  766. // Resolve middleware
  767. const { route, handle } = this.resolveMiddleware(
  768. middleware,
  769. // #8584 pass the original route as fallback
  770. serverStackItem.handle._middleware
  771. ? serverStackItem.handle._middleware._originalRoute
  772. : serverStackItem.route
  773. );
  774. // Update serverStackItem
  775. serverStackItem.handle = handle;
  776. // Error State
  777. serverStackItem.route = route;
  778. // Return updated item
  779. return serverStackItem
  780. }
  781. unloadMiddleware ({ handle }) {
  782. if (handle._middleware && typeof handle._middleware.unload === 'function') {
  783. handle._middleware.unload();
  784. }
  785. }
  786. serverMiddlewarePaths () {
  787. return this.app.stack.map(({ handle }) => handle._middleware && handle._middleware.entry).filter(Boolean)
  788. }
  789. renderRoute () {
  790. return this.renderer.renderRoute.apply(this.renderer, arguments)
  791. }
  792. loadResources () {
  793. return this.renderer.loadResources.apply(this.renderer, arguments)
  794. }
  795. renderAndGetWindow (url, opts = {}, {
  796. loadingTimeout = 2000,
  797. loadedCallback = this.globals.loadedCallback,
  798. globals = this.globals
  799. } = {}) {
  800. return renderAndGetWindow(url, opts, {
  801. loadingTimeout,
  802. loadedCallback,
  803. globals
  804. })
  805. }
  806. async listen (port, host, socket) {
  807. // Ensure nuxt is ready
  808. await this.nuxt.ready();
  809. // Create a new listener
  810. const listener = new Listener({
  811. port: isNaN(parseInt(port)) ? this.options.server.port : port,
  812. host: host || this.options.server.host,
  813. socket: socket || this.options.server.socket,
  814. https: this.options.server.https,
  815. app: this.app,
  816. dev: this.options.dev,
  817. baseURL: this.options.router.base
  818. });
  819. // Listen
  820. await listener.listen();
  821. // Push listener to this.listeners
  822. this.listeners.push(listener);
  823. await this.nuxt.callHook('listen', listener.server, listener);
  824. return listener
  825. }
  826. async close () {
  827. if (this.__closed) {
  828. return
  829. }
  830. this.__closed = true;
  831. await Promise.all(this.listeners.map(l => l.close()));
  832. this.listeners = [];
  833. if (typeof this.renderer.close === 'function') {
  834. await this.renderer.close();
  835. }
  836. this.app.stack.forEach(this.unloadMiddleware);
  837. this.app.removeAllListeners();
  838. this.app = null;
  839. for (const key in this.resources) {
  840. delete this.resources[key];
  841. }
  842. }
  843. }
  844. exports.Listener = Listener;
  845. exports.Server = Server;