From 802e1b3dccb38bc6ec4e4f28226d86bd1228559d Mon Sep 17 00:00:00 2001 From: Steven Date: Sat, 20 Jan 2018 17:55:01 -0500 Subject: [PATCH 1/4] Add strict tsconfig options --- tsconfig.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 7db27bff..4fee4a19 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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" From ed9a2e2efae9998ef4fbff8624bb0a00076fa441 Mon Sep 17 00:00:00 2001 From: Steven Date: Sat, 20 Jan 2018 17:55:24 -0500 Subject: [PATCH 2/4] Add cache control and stream pipe --- src/cache-control.ts | 7 ++++++ src/file.ts | 4 ---- src/mime-types.ts | 19 +++++++++++++++ src/server.tsx | 55 ++++++++++++++++++++++++++------------------ 4 files changed, 58 insertions(+), 27 deletions(-) create mode 100644 src/cache-control.ts delete mode 100644 src/file.ts create mode 100644 src/mime-types.ts diff --git a/src/cache-control.ts b/src/cache-control.ts new file mode 100644 index 00000000..60dd2ab2 --- /dev/null +++ b/src/cache-control.ts @@ -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}`; +} diff --git a/src/file.ts b/src/file.ts deleted file mode 100644 index f7d1bb7e..00000000 --- a/src/file.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { readFile } from 'fs'; -import { promisify } from 'util'; - -export const readFileAsync = promisify(readFile); diff --git a/src/mime-types.ts b/src/mime-types.ts new file mode 100644 index 00000000..5fd1d77d --- /dev/null +++ b/src/mime-types.ts @@ -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; +} diff --git a/src/server.tsx b/src/server.tsx index a1c322a5..47a53088 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -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, @@ -13,7 +16,6 @@ import { propsUrl, containerId, } from './constants'; -import { readFileAsync } from './file'; console.log('Server booting...'); const isProd = process.env.NODE_ENV === 'production'; @@ -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(` @@ -50,34 +56,37 @@ createServer(async (req, res) => { `); }); } 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, { end: true }); } 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, { end: true }); } 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, { end: true }); } 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'); } From 8ea7569dc5ff51d054fb600616b29fad06548cf7 Mon Sep 17 00:00:00 2001 From: Steven Date: Sat, 20 Jan 2018 17:58:16 -0500 Subject: [PATCH 3/4] Remove default pipe end option --- src/server.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server.tsx b/src/server.tsx index 47a53088..195f6f37 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -64,17 +64,17 @@ createServer(async (req, res) => { res.setHeader('Cache-Control', control(isProd, 7)); const name = url.replace('.js', ''); const file = `./node_modules${name}/umd${name}${suffix}`; - createReadStream(file).pipe(res, { end: true }); + createReadStream(file).pipe(res); } else if (url === stylesUrl) { res.setHeader('Content-Type', lookup(url)); res.setHeader('Cache-Control', control(isProd, 7)); const file = `./src/${url}`; - createReadStream(file).pipe(res, { end: true }); + createReadStream(file).pipe(res); } else if (url === browserUrl || url === browserMapUrl) { res.setHeader('Content-Type', lookup(url)); res.setHeader('Cache-Control', control(isProd, 7)); const file = `./dist${url}`; - createReadStream(file).pipe(res, { end: true }); + createReadStream(file).pipe(res); } else { url = 'notfound.txt'; res.setHeader('Content-Type', lookup(url)); From 257327307032de19e87c0f4c55ba78960c23eb2d Mon Sep 17 00:00:00 2001 From: Steven Date: Sat, 20 Jan 2018 18:14:13 -0500 Subject: [PATCH 4/4] Add mime-types-test --- tests/mime-types-test.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/mime-types-test.ts diff --git a/tests/mime-types-test.ts b/tests/mime-types-test.ts new file mode 100644 index 00000000..324302db --- /dev/null +++ b/tests/mime-types-test.ts @@ -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'); +}); \ No newline at end of file