Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/cache-control.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function control(isProd: boolean, days: number): string {
if (days === 0 || !isProd) {
return 'public, no-cache, no-store, must-revalidate';
}
const sec = days * 24 * 60 * 60;
return `public, max-age=${sec}`;
}
4 changes: 0 additions & 4 deletions src/file.ts

This file was deleted.

19 changes: 19 additions & 0 deletions src/mime-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { extname } from 'path';

const data: { [ext: string]: string } = {
'.js': 'application/javascript',
'.map': 'application/javascript',
'.json': 'application/json',
'.css': 'text/css',
'.html': 'text/html',
'.txt': 'text/plain',
};

export function lookup(path: string): string {
const ext = extname(path);
const mime = data[ext];
if (!mime) {
throw new Error(`No mime type for file: ${path}`);
}
return mime;
}
55 changes: 32 additions & 23 deletions src/server.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { createServer, IncomingMessage, ServerResponse } from 'http';
import { createFactory } from 'react';
import { renderToNodeStream } from 'react-dom/server';
import { createReadStream } from 'fs';
import App from './components/app';
import { fetchProps } from './props';
import { lookup } from './mime-types';
import { control } from './cache-control';
import {
faviconUrl,
stylesUrl,
Expand All @@ -13,7 +16,6 @@ import {
propsUrl,
containerId,
} from './constants';
import { readFileAsync } from './file';

console.log('Server booting...');
const isProd = process.env.NODE_ENV === 'production';
Expand All @@ -23,11 +25,15 @@ const PORT = process.env.PORT || 3007;
const suffix = isProd ? '.production.min.js' : '.development.js';

createServer(async (req, res) => {
const { httpVersion, method, url } = req;
let { httpVersion, method, url } = req;
console.log(`${httpVersion} ${method} ${url}`);
if (!url || url === '/') {
url = 'index.html';
}
try {
if (url === '/') {
res.setHeader('Content-Type', 'text/html');
if (url === 'index.html') {
res.setHeader('Content-Type', lookup(url));
res.setHeader('Cache-Control', control(isProd, 1));
res.write(`<!DOCTYPE html>
<html>
<head>
Expand All @@ -50,34 +56,37 @@ createServer(async (req, res) => {
</html>`);
});
} else if (url === propsUrl) {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Type', lookup(url));
res.setHeader('Cache-Control', control(isProd, 0));
res.end(JSON.stringify(fetchProps()));
} else if (url === reactUrl) {
res.setHeader('Content-Type', 'text/javascript');
res.setHeader('Cache-Control', 'public, max-age=86400');
const data = await readFileAsync(`./node_modules/react/umd/react${suffix}`);
res.end(data);
} else if (url === reactDomUrl) {
res.setHeader('Content-Type', 'text/javascript');
res.setHeader('Cache-Control', 'public, max-age=86400');
const data = await readFileAsync(`./node_modules/react-dom/umd/react-dom${suffix}`);
res.end(data);
} else if (url === reactUrl || url === reactDomUrl) {
res.setHeader('Content-Type', lookup(url));
res.setHeader('Cache-Control', control(isProd, 7));
const name = url.replace('.js', '');
const file = `./node_modules${name}/umd${name}${suffix}`;
createReadStream(file).pipe(res);
} else if (url === stylesUrl) {
res.setHeader('Content-Type', 'text/css');
const data = await readFileAsync(`./src/${url}`);
res.end(data);
res.setHeader('Content-Type', lookup(url));
res.setHeader('Cache-Control', control(isProd, 7));
const file = `./src/${url}`;
createReadStream(file).pipe(res);
} else if (url === browserUrl || url === browserMapUrl) {
res.setHeader('Content-Type', 'text/javascript');
const data = await readFileAsync(`./dist${url}`);
res.end(data);
res.setHeader('Content-Type', lookup(url));
res.setHeader('Cache-Control', control(isProd, 7));
const file = `./dist${url}`;
createReadStream(file).pipe(res);
} else {
res.setHeader('Content-Type', 'text/plain');
url = 'notfound.txt';
res.setHeader('Content-Type', lookup(url));
res.setHeader('Cache-Control', control(isProd, 0));
res.statusCode = 404;
res.end('404 Not Found');
}
} catch (e) {
console.error(e);
res.setHeader('Content-Type', 'text/plain');
url = 'notfound.txt';
res.setHeader('Content-Type', lookup(url));
res.setHeader('Cache-Control', control(isProd, 0));
res.statusCode = 500;
res.end('500 Internal Error');
}
Expand Down
38 changes: 38 additions & 0 deletions tests/mime-types-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as test from 'tape';
import { lookup } from '../src/mime-types';

test('lookup .js', t => {
t.plan(1);
const mime = lookup('file.js');
t.equal(mime, 'application/javascript');
});

test('lookup .js.map', t => {
t.plan(1);
const mime = lookup('file.js.map');
t.equal(mime, 'application/javascript');
});

test('lookup .json', t => {
t.plan(1);
const mime = lookup('file.json');
t.equal(mime, 'application/json');
});

test('lookup .css', t => {
t.plan(1);
const mime = lookup('file.css');
t.equal(mime, 'text/css');
});

test('lookup .html', t => {
t.plan(1);
const mime = lookup('file.html');
t.equal(mime, 'text/html');
});

test('lookup .txt', t => {
t.plan(1);
const mime = lookup('/some/file.txt');
t.equal(mime, 'text/plain');
});
9 changes: 6 additions & 3 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
{
"compilerOptions": {
"rootDir": ".",
"outDir": "./dist/",
"module": "commonjs",
"target": "ES2017",
"moduleResolution": "node",
"strict": true,
"rootDir": ".",
"jsx": "react",
"sourceMap": true
"sourceMap": true,
"strict": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noEmitOnError": true
},
"exclude": [
"node_modules"
Expand Down