rollup-plugin-serve-proxy
Version:
Serve your rolled up bundle
218 lines (186 loc) • 7.41 kB
JavaScript
import { readFile } from 'fs';
import https, { createServer } from 'https';
import http, { createServer as createServer$1 } from 'http';
import { resolve } from 'path';
import mime from 'mime';
import opener from 'opener';
var server;
/**
* Serve your rolled up bundle like webpack-dev-server
* @param {ServeOptions|string|string[]} options
*/
function serve (options) {
if ( options === void 0 ) options = { contentBase: '' };
if (Array.isArray(options) || typeof options === 'string') {
options = { contentBase: options };
}
options.contentBase = Array.isArray(options.contentBase) ? options.contentBase : [options.contentBase];
options.host = options.host || 'localhost';
options.port = options.port || 10001;
options.headers = options.headers || {};
options.https = options.https || false;
options.openPage = options.openPage || '';
options.proxy = options.proxy || {};
mime.default_type = 'text/plain';
// Use http or https as needed
var httpS = options.https ? https : http;
var proxies = Object.keys(options.proxy).map(function (proxy) { return ({
proxy: proxy,
destination: options.proxy[proxy],
test: new RegExp(("/" + proxy))
}); });
var requestListener = function (request, response) {
// Remove querystring
var urlPath = decodeURI(request.url.split('?')[0]);
Object.keys(options.headers).forEach(function (key) {
response.setHeader(key, options.headers[key]);
});
// Find the appropriate proxy for the request if one exists
var proxy = proxies.find(function (ref) {
var test = ref.test;
return request.url.match(test);
});
// If a proxy exists, forward the request to the appropriate server
if (proxy && proxy.destination) {
var destination = proxy.destination;
var newDestination = "" + destination + (request.url);
var headers = request.headers;
var method = request.method;
// Get the request contents
var body = '';
request.on('data', function (chunk) { body += chunk; });
// Forward the request
request.on('end', function () {
var proxyRequest = httpS
.request(newDestination, { headers: headers, method: method }, function (proxyResponse) {
var data = '';
proxyResponse.on('data', function (chunk) { data += chunk; });
proxyResponse.on('end', function () {
Object.keys(proxyResponse.headers).forEach(function (key) { return response.setHeader(key, proxyResponse.headers[key]); });
foundProxy(response, proxyResponse.statusCode, data);
});
})
.end(body, 'utf-8');
proxyRequest.on('error', function (err) { return console.error(("There was a problem with the request for " + (request.url) + ": " + err)); });
proxyRequest.end();
});
} else {
readFileFromContentBase(options.contentBase, urlPath, function (error, content, filePath) {
if (!error) {
return found(response, filePath, content)
}
if (error.code !== 'ENOENT') {
response.writeHead(500);
response.end('500 Internal Server Error' +
'\n\n' + filePath +
'\n\n' + Object.values(error).join('\n') +
'\n\n(rollup-plugin-serve)', 'utf-8');
return
}
if (options.historyApiFallback) {
var fallbackPath = typeof options.historyApiFallback === 'string' ? options.historyApiFallback : '/index.html';
readFileFromContentBase(options.contentBase, fallbackPath, function (error, content, filePath) {
if (error) {
notFound(response, filePath);
} else {
found(response, filePath, content);
}
});
} else {
notFound(response, filePath);
}
});
}
};
// release previous server instance if rollup is reloading configuration in watch mode
if (server) {
server.close();
}
// If HTTPS options are available, create an HTTPS server
if (options.https) {
server = createServer(options.https, requestListener).listen(options.port, options.host);
} else {
server = createServer$1(requestListener).listen(options.port, options.host);
}
closeServerOnTermination(server);
var running = options.verbose === false;
return {
name: 'serve',
generateBundle: function generateBundle () {
if (!running) {
running = true;
// Log which url to visit
var url = (options.https ? 'https' : 'http') + '://' + options.host + ':' + options.port;
options.contentBase.forEach(function (base) {
console.log(green(url) + ' -> ' + resolve(base));
});
// Open browser
if (options.open) {
opener(url + options.openPage);
}
}
}
}
}
function readFileFromContentBase (contentBase, urlPath, callback) {
var filePath = resolve(contentBase[0] || '.', '.' + urlPath);
// Load index.html in directories
if (urlPath.endsWith('/')) {
filePath = resolve(filePath, 'index.html');
}
readFile(filePath, function (error, content) {
if (error && contentBase.length > 1) {
// Try to read from next contentBase
readFileFromContentBase(contentBase.slice(1), urlPath, callback);
} else {
// We know enough
callback(error, content, filePath);
}
});
}
function notFound (response, filePath) {
response.writeHead(404);
response.end('404 Not Found' +
'\n\n' + filePath +
'\n\n(rollup-plugin-serve)', 'utf-8');
}
function found (response, filePath, content) {
response.writeHead(200, { 'Content-Type': mime.getType(filePath) });
response.end(content, 'utf-8');
}
function foundProxy (response, status, content) {
response.writeHead(status);
response.end(content, 'utf-8');
}
function green (text) {
return '\u001b[1m\u001b[32m' + text + '\u001b[39m\u001b[22m'
}
function closeServerOnTermination (server) {
var terminationSignals = ['SIGINT', 'SIGTERM'];
terminationSignals.forEach(function (signal) {
process.on(signal, function () {
server.close();
process.exit();
});
});
}
/**
* @typedef {Object} ServeOptions
* @property {boolean} [open=false] Launch in browser (default: `false`)
* @property {string} [openPage=''] Page to navigate to when opening the browser. Will not do anything if `open` is `false`. Remember to start with a slash e.g. `'/different/page'`
* @property {boolean} [verbose=true] Show server address in console (default: `true`)
* @property {string|string[]} [contentBase=''] Folder(s) to serve files from
* @property {string|boolean} [historyApiFallback] Path to fallback page. Set to `true` to return index.html (200) instead of error page (404)
* @property {string} [host='localhost'] Server host (default: `'localhost'`)
* @property {number} [port=10001] Server port (default: `10001`)
* @property {ServeOptionsHttps} [https=false] By default server will be served over HTTP (https: `false`). It can optionally be served over HTTPS
* @property {{[header:string]: string}} [headers] Set headers
*/
/**
* @typedef {Object} ServeOptionsHttps
* @property {string|Buffer|Buffer[]|Object[]} key
* @property {string|Buffer|Array<string|Buffer>} cert
* @property {string|Buffer|Array<string|Buffer>} ca
* @see https.ServerOptions
*/
export default serve;