Skip to content

Instantly share code, notes, and snippets.

@jamesbibby
Last active March 28, 2019 17:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesbibby/c4c311887d6b392729fc13fb441cb33d to your computer and use it in GitHub Desktop.
Save jamesbibby/c4c311887d6b392729fc13fb441cb33d to your computer and use it in GitHub Desktop.
Cloudflare Typescript Worker
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;
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))
});
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)
})
})
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()
})
})
// 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 };
{
"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__"
]
}
declare var global: any
interface CloudflareWorker extends Window {
listeners: Map<string, any>
trigger(key: string, value: any): any
}
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