UNPKG

3.25 kBJavaScriptView Raw
1// This file triggers https://github.com/prettier/prettier/issues/1151
2
3const http = require('http');
4const mime = require('mime');
5const pify = require('pify');
6const EventEmitter = require('events').EventEmitter;
7const liveReload = require('tiny-lr');
8const sep = require('path').sep;
9
10/**
11 * A static file server designed to support documentation.js's --serve
12 * option. It serves from an array of Vinyl File objects (virtual files in
13 * memory) and exposes a `setFiles` method that both replaces the set
14 * of files and notifies any browsers using LiveReload to reload
15 * and display the new content.
16 * @class
17 * @param port server port to serve on.
18 */
19class Server extends EventEmitter {
20 constructor(port, disableLiveReload) {
21 super();
22 if (typeof port !== 'number') {
23 throw new Error('port argument required to initialize a server');
24 }
25 this._port = port;
26 this._files = [];
27 this._disableLiveReload = !!disableLiveReload;
28 }
29
30 /**
31 * Update the set of files exposed by this server and notify LiveReload
32 * clients
33 *
34 * @param files new content. replaces any previously-set content.
35 * @returns {Server} self
36 */
37 setFiles(files) {
38 this._files = files;
39 if (this._lr) {
40 this._lr.changed({ body: { files: '*' } });
41 }
42 return this;
43 }
44
45 /**
46 * Internal handler for server requests. The server serves
47 * very few types of things: html, images, and so on, and it
48 * only handles GET requests.
49 *
50 * @param {http.Request} request content wanted
51 * @param {http.Response} response content returned
52 * @returns {undefined} nothing
53 * @private
54 */
55 handler(request, response) {
56 let path = request.url.substring(1);
57 if (path === '') {
58 path = 'index.html';
59 }
60
61 for (let i = 0; i < this._files.length; i++) {
62 const file = this._files[i];
63 const filePath = file.relative.split(sep).join('/');
64 if (filePath === path) {
65 response.writeHead(200, { 'Content-Type': mime.getType(path) });
66 response.end(file.contents);
67 return;
68 }
69 }
70 response.writeHead(404, { 'Content-Type': 'text/plain' });
71 response.end('Not found');
72 }
73
74 start() {
75 /*
76 * Boot up the server's HTTP & LiveReload endpoints. This method
77 * can be called multiple times.
78 *
79 * @returns {Promise} resolved when server starts
80 */
81 return new Promise(resolve => {
82 // idempotent
83 if (this._http) {
84 return resolve(this);
85 }
86
87 if (!this._disableLiveReload) {
88 this._lr = liveReload();
89 }
90 this._http = http.createServer(this.handler.bind(this));
91
92 return Promise.all([
93 this._lr && pify(this._lr.listen.bind(this._lr))(35729),
94 pify(this._http.listen.bind(this._http))(this._port)
95 ]).then(() => {
96 this.emit('listening');
97 return resolve(this);
98 });
99 });
100 }
101
102 stop() {
103 /*
104 * Shut down the server's HTTP & LiveReload endpoints. This method
105 * can be called multiple times.
106 */
107 return Promise.all([
108 this._http && this._http.close(),
109 this._lr && this._lr.close()
110 ]).then(() => {
111 delete this._http;
112 delete this._lr;
113 return this;
114 });
115 }
116}
117
118module.exports = Server;