Last active
March 28, 2019 17:55
-
-
Save jamesbibby/c4c311887d6b392729fc13fb441cb33d to your computer and use it in GitHub Desktop.
Cloudflare Typescript Worker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Title |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react'; | |
import logo from './logo.svg'; | |
import './App.css'; | |
class App extends Component { | |
render() { | |
return ( | |
<div className="App"> | |
<header className="App-header"> | |
<img src={logo} className="App-logo" alt="logo" /> | |
<p> | |
Bibs.Codes React App - Served By the SPARouter Cloudflare Worker | |
</p> | |
<a | |
className="App-link" | |
href="https://bibs.codes/posts/typescript-cloudflare-worker" | |
target="_blank" | |
rel="noopener noreferrer" | |
> | |
See the post here | |
</a> | |
</header> | |
</div> | |
); | |
} | |
} | |
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { SPARouter } from './spaRouter'; | |
const helloRouter = new SPARouter( | |
'https://hello-world-react.bibs.codes', | |
[ | |
'/asset-manifest.json', | |
'/favicon.ico', | |
'/manifest.json', | |
'/precache-manifest.', | |
'/serviceWorker.js', | |
'/static/' | |
] | |
); | |
self.addEventListener('fetch', (evt: Event) => { | |
const event = evt as FetchEvent | |
event.respondWith(helloRouter.handleRequest(event.request)) | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import makeServiceWorkerEnv from 'service-worker-mock'; | |
const filesUnderTest = ['../src/index.ts', '../src/spaRouter.ts']; | |
const baseURL = 'https://hello-world-react.bibs.codes' | |
describe('Service worker', () => { | |
// setup the service worker environment before each test | |
beforeEach(() => { | |
// mock the fetch global object | |
Object.assign(global, makeServiceWorkerEnv(), { fetch: jest.fn() }) | |
jest.resetModules() | |
filesUnderTest.forEach((f) => require(f)); | |
}) | |
it('should add listeners', () => { | |
expect((self as CloudflareWorker).listeners.get('fetch')).toBeDefined() | |
}) | |
it('should return index.html for a non asset url', async () => { | |
// fetch is a jest mock, setup the response | |
(fetch as jest.Mock).mockReturnValue(Promise.resolve(new Response('index', { status: 200 }))) | |
// create a request and issue the fetch against the mock | |
const request = new Request('/home') | |
const response = await (self as CloudflareWorker).trigger('fetch', request) | |
// check that our mock was called with the correct URL | |
expect(fetch).toBeCalledWith(`${baseURL}/index.html`, request) | |
// check that our response was the expected one | |
expect(response[0].status).toEqual(200) | |
await expect(response[0].text()).resolves.toEqual('index') | |
}) | |
it('should return a static asset', async () => { | |
(fetch as jest.Mock).mockReturnValue(new Response('img', { status: 200 })) | |
const request = new Request('/static/img/home.jpg') | |
const response = await (self as CloudflareWorker).trigger('fetch', request) | |
expect(fetch).toBeCalledWith(`${baseURL}/static/img/home.jpg`, request); | |
expect(response[0].status).toEqual(200) | |
await expect(response[0].text()).resolves.toEqual('img') | |
}) | |
it('should return a root level asset', async () => { | |
(fetch as jest.Mock).mockReturnValue(new Response('favicon', { status: 200 })) | |
const request = new Request('/favicon.ico') | |
const response = await (self as CloudflareWorker).trigger('fetch', request) | |
expect(fetch).toBeCalledWith(`${baseURL}/favicon.ico`, request); | |
expect(response[0].status).toEqual(200) | |
await expect(response[0].text()).resolves.toEqual('favicon') | |
}) | |
it('should handle an asset 404 properly', async () => { | |
(fetch as jest.Mock).mockReturnValue(new Response('not found', { status: 404 })) | |
const request = new Request('/static/js/does_not_exist.js') | |
const response = await (self as CloudflareWorker).trigger('fetch', request) | |
expect(fetch).toBeCalledWith(`${baseURL}/static/js/does_not_exist.js`, request); | |
expect(response[0].status).toEqual(404) | |
}) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import makeServiceWorkerEnv from 'service-worker-mock'; | |
const filesUnderTest = ['../src/index.ts', '../src/spaRouter.ts']; | |
const baseURL = 'https://hello-world-react.bibs.codes' | |
describe('Service worker', () => { | |
// setup the service worker environment before each test | |
beforeEach(() => { | |
// mock the fetch global object | |
Object.assign(global, makeServiceWorkerEnv(), { fetch: jest.fn() }) | |
jest.resetModules() | |
filesUnderTest.forEach((f) => require(f)); | |
}) | |
it('should add listeners', () => { | |
expect((self as CloudflareWorker).listeners.get('fetch')).toBeDefined() | |
}) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// simple interface for handling a request | |
class SPARouter { | |
assetPaths: Array<string> | |
baseURL: string | |
// configure a new SPA router for a given baseurl and set of paths | |
constructor(baseURL: string, assetPaths: Array<string>) { | |
this.assetPaths = assetPaths; | |
this.baseURL = baseURL; | |
} | |
handleRequest(request: Request): Promise<Response> { | |
// lets parse the URL into a proper URL object | |
const url = new URL(request.url) | |
// if this is in the asset path, fetch that asset | |
// if this is not in the asset paths, return index.html | |
const path = this.assetPaths.some((path: string) => url.pathname.startsWith(path)) | |
? url.pathname | |
: '/index.html' | |
// fetch the path from S3, include the original response | |
// so that headers, auth, etc aren't lost | |
return fetch( | |
`${this.baseURL}${path}${url.search}`, | |
request | |
) | |
} | |
} | |
export { SPARouter }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
{ | |
"compilerOptions": { | |
"outDir": "./dist/", | |
"removeComments": true, | |
"sourceMap": true, | |
"module": "commonjs", | |
"target": "esnext", | |
"lib": [ | |
"esnext", | |
"webworker", | |
], | |
"strict": true, | |
"moduleResolution": "node", | |
"noImplicitAny": true, | |
"noImplicitThis": true, | |
"strictPropertyInitialization": true, | |
"strictNullChecks": true, | |
"alwaysStrict": true, | |
"noUnusedLocals": true, | |
"noUnusedParameters": true, | |
"noImplicitReturns": true, | |
"noFallthroughCasesInSwitch": true, | |
"esModuleInterop": true | |
}, | |
"exclude": [ | |
"./__tests__" | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare var global: any | |
interface CloudflareWorker extends Window { | |
listeners: Map<string, any> | |
trigger(key: string, value: any): any | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const path = require('path'); | |
module.exports = { | |
target: 'webworker', | |
mode: 'production', | |
entry: './src/index.ts', | |
devtool: 'none', | |
module: { | |
rules: [ | |
{ | |
test: /\.ts$/, | |
use: 'ts-loader', | |
exclude: /node_modules/ | |
} | |
] | |
}, | |
resolve: { | |
extensions: ['.ts', '.js'] | |
}, | |
output: { | |
filename: 'worker.js', | |
path: path.resolve(__dirname, 'dist') | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment