Skip to content

Commit bc3eaf8

Browse files
committed
Finish unpkg-www worker
1 parent 9a47b88 commit bc3eaf8

17 files changed

Lines changed: 220 additions & 230 deletions

packages/unpkg-files/src/lib/request-handler.test.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ describe("handleRequest", () => {
1818
beforeAll(() => {
1919
globalFetch = globalThis.fetch;
2020

21-
// Does not implement Bun's non-spec fetch.preconnect API - https://bun.sh/docs/api/fetch#preconnect-to-a-host
22-
// @ts-expect-error
23-
globalThis.fetch = async (input: RequestInfo | URL) => {
24-
let url = input instanceof Request ? input.url : input;
21+
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
22+
let request = input instanceof Request ? input : new Request(input, init);
23+
let url = new URL(request.url);
2524

26-
switch (url.toString()) {
25+
switch (url.href) {
2726
case "https://registry.npmjs.org/@ffmpeg/core/-/core-0.12.6.tgz":
2827
return fileResponse(packageTarballs["@ffmpeg/core"]["0.12.6"]);
2928
case "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz":
@@ -35,7 +34,7 @@ describe("handleRequest", () => {
3534
default:
3635
throw new Error(`Unexpected URL: ${url}`);
3736
}
38-
};
37+
}) as unknown as typeof fetch;
3938
});
4039

4140
afterAll(() => {

packages/unpkg-www/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
.wrangler/
12
assets-manifest.json
23
public/_assets/*

packages/unpkg-www/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# unpkg-www
2+
3+
This is the main home page and file server for UNPKG. It is built and deployed as a Cloudflare Worker.
4+
5+
## Development
6+
7+
Install dependencies and run the tests:
8+
9+
```
10+
pnpm install
11+
pnpm test
12+
```
13+
14+
Boot the dev server and assets server (two separate tabs):
15+
16+
```
17+
pnpm dev
18+
pnpm dev:assets
19+
```
20+
21+
## Deploying
22+
23+
Edit the worker configuration in `wrangler.json`, then:
24+
25+
```
26+
pnpm run deploy
27+
```

packages/unpkg-www/package.json

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,32 @@
11
{
22
"name": "unpkg-www",
3-
"description": "The UNPKG home page and file server",
3+
"description": "The UNPKG home page and file server as a Cloudflare Worker",
44
"private": true,
55
"type": "module",
66
"dependencies": {
7-
"chalk": "^5.4.1",
87
"highlight.js": "^11.11.1",
98
"preact": "^10.25.2",
109
"preact-render-to-string": "^6.5.12",
11-
"unpkg-tools": "workspace:^"
10+
"unpkg-worker": "workspace:*"
1211
},
1312
"devDependencies": {
13+
"@cloudflare/workers-types": "^4.20250320.0",
1414
"@ryanto/esbuild-plugin-tailwind": "^0.0.1",
1515
"@types/bun": "^1.2.8",
1616
"@types/node": "^22.10.2",
1717
"esbuild": "^0.24.2",
18+
"miniflare": "^3.20241205.0",
1819
"tailwindcss": "4.0.0-beta.9",
19-
"typescript": "^5.7.2"
20+
"unpkg-files": "workspace:*",
21+
"wrangler": "^4.3.0"
2022
},
2123
"scripts": {
22-
"build": "pnpm run build:assets && pnpm run build:server",
24+
"build": "wrangler deploy --dry-run --outdir=dist",
2325
"build:assets": "node --disable-warning=ExperimentalWarning ../../scripts/build-assets.ts",
24-
"build:server": "tsc --project ./tsconfig.build.json",
25-
"dev": "MODE=development bun ./src/server.ts",
26+
"dev": "wrangler dev --env dev --port 3000",
2627
"dev:assets": "bun ../../scripts/serve-assets.ts",
27-
"test": "bun --preload=./test/setup.ts test"
28-
},
29-
"files": [
30-
"assets-manifest.json",
31-
"dist",
32-
"public"
33-
]
28+
"test": "bun --preload=./test/setup.ts test",
29+
"predeploy": "pnpm run build:assets",
30+
"deploy": "wrangler deploy"
31+
}
3432
}
Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,33 @@
1-
import * as path from "node:path";
2-
import * as fsp from "node:fs/promises";
3-
4-
import { env } from "./env.ts";
5-
6-
const __dirname = path.dirname(new URL(import.meta.url).pathname);
7-
const rootDir = path.resolve(__dirname, "..");
1+
import type { Env } from "./env.ts";
82

93
/**
104
* A map of entry points to their URLs.
115
*/
126
export type AssetsManifest = Map<string, string>;
137

14-
export async function loadAssetsManifest(): Promise<AssetsManifest> {
15-
let json: Record<string, string>;
8+
export async function loadAssetsManifest(env: Env): Promise<AssetsManifest> {
9+
let mod: { default: Record<string, string> };
1610
switch (env.MODE) {
1711
case "development":
1812
case "test":
19-
json = await loadJson(path.join(rootDir, "assets-manifest.dev.json"));
13+
mod = await import("../assets-manifest.dev.json");
2014
break;
2115
case "production":
2216
case "staging":
2317
try {
24-
json = await loadJson(path.join(rootDir, "assets-manifest.json"));
18+
// @ts-ignore - This file is generated at build time
19+
mod = await import("../assets-manifest.json");
2520
} catch (error) {
26-
throw new Error("Failed to load assets-manifest.json. Did you run `pnpm run build:assets`?");
21+
throw new Error("Failed to load assets-manifest.json. Did you run `pnpm build:assets`?");
2722
}
2823
break;
2924
}
3025

3126
let manifest: AssetsManifest = new Map();
3227

33-
for (let [entryPoint, path] of Object.entries(json)) {
34-
if (env.ASSETS_ORIGIN) {
35-
manifest.set(entryPoint, new URL(path, env.ASSETS_ORIGIN).href);
36-
} else {
37-
manifest.set(entryPoint, path);
38-
}
28+
for (let [entryPoint, path] of Object.entries(mod.default)) {
29+
manifest.set(entryPoint, new URL(path, env.ASSETS_ORIGIN).href);
3930
}
4031

4132
return manifest;
4233
}
43-
44-
async function loadJson(file: string): Promise<Record<string, string>> {
45-
return JSON.parse(await fsp.readFile(file, "utf-8"));
46-
}

packages/unpkg-www/src/env.ts

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,8 @@
11
export interface Env {
22
APP_ORIGIN: string;
33
ASSETS_ORIGIN: string;
4-
DEBUG: boolean;
54
DEV: boolean;
5+
FILES_ORIGIN: string;
66
MODE: "development" | "production" | "staging" | "test";
7-
WWW_ORIGIN: string;
7+
ORIGIN: string;
88
}
9-
10-
const envs: Record<Env["MODE"], Env> = {
11-
development: {
12-
APP_ORIGIN: "http://localhost:3001",
13-
ASSETS_ORIGIN: "http://localhost:8000",
14-
DEBUG: true,
15-
DEV: true,
16-
MODE: "development",
17-
WWW_ORIGIN: "http://localhost:3000",
18-
},
19-
production: {
20-
APP_ORIGIN: "https://app.unpkg.com",
21-
ASSETS_ORIGIN: "",
22-
DEBUG: !!(process.env.DEBUG ?? false),
23-
DEV: false,
24-
MODE: "production",
25-
WWW_ORIGIN: "https://unpkg.com",
26-
},
27-
staging: {
28-
APP_ORIGIN: "https://app.unpkg.dev",
29-
ASSETS_ORIGIN: "",
30-
DEBUG: !!(process.env.DEBUG ?? false),
31-
DEV: false,
32-
MODE: "staging",
33-
WWW_ORIGIN: "https://unpkg.dev",
34-
},
35-
test: {
36-
APP_ORIGIN: "https://app.unpkg.com",
37-
ASSETS_ORIGIN: "",
38-
DEBUG: false,
39-
DEV: false,
40-
MODE: "test",
41-
WWW_ORIGIN: "https://unpkg.com",
42-
},
43-
};
44-
45-
let mode = (process.env.MODE ?? "development") as Env["MODE"];
46-
if (!(mode in envs)) {
47-
throw new Error(`Invalid MODE: ${mode}`);
48-
}
49-
50-
export const env = envs[mode];

packages/unpkg-www/src/hooks.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import { AssetsContext } from "./assets-context.ts";
44

55
export function useAsset(entryPoint: string): string {
66
let manifest = useContext(AssetsContext);
7-
87
let asset = manifest.get(entryPoint);
98
if (asset) return asset;
10-
119
throw new Error(`Asset not found: ${entryPoint}`);
1210
}

packages/unpkg-www/src/hrefs.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

packages/unpkg-www/src/public-assets.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

packages/unpkg-www/src/request-handler.test.ts

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,63 @@
11
import { expect, describe, it, beforeAll, afterAll } from "bun:test";
2+
import type { ExecutionContext } from "@cloudflare/workers-types";
3+
import { handleRequest as handleFilesRequest } from "unpkg-files";
24

35
import { packageInfo, packageTarballs } from "../test/fixtures.ts";
4-
6+
import type { Env } from "./env.ts";
57
import { handleRequest } from "./request-handler.tsx";
68

9+
const env: Env = {
10+
APP_ORIGIN: "https://app.unpkg.com",
11+
ASSETS_ORIGIN: "https://unpkg.com",
12+
DEV: false,
13+
FILES_ORIGIN: "https://files.unpkg.com",
14+
MODE: "test",
15+
ORIGIN: "https://unpkg.com",
16+
};
17+
18+
const context: ExecutionContext = {
19+
waitUntil() {},
20+
} as unknown as ExecutionContext;
21+
722
function dispatchFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
823
let request = input instanceof Request ? input : new Request(input, init);
9-
return handleRequest(request);
24+
return handleRequest(request, env, context);
25+
}
26+
27+
function fileResponse(path: string): Response {
28+
return new Response(Bun.file(path));
1029
}
1130

1231
describe("handleRequest", () => {
32+
let globalCaches: CacheStorage | undefined;
1333
let globalFetch: typeof fetch | undefined;
1434

15-
function fileResponse(path: string): Response {
16-
return new Response(Bun.file(path));
17-
}
18-
1935
beforeAll(() => {
36+
globalCaches = globalThis.caches;
2037
globalFetch = globalThis.fetch;
2138

22-
// Does not implement Bun's non-spec fetch.preconnect API - https://bun.sh/docs/api/fetch#preconnect-to-a-host
23-
// @ts-expect-error
24-
globalThis.fetch = async (input: RequestInfo | URL) => {
25-
let url = input instanceof Request ? input.url : input;
39+
globalThis.caches = {
40+
async open() {
41+
return {
42+
async match() {
43+
return null;
44+
},
45+
async put() {},
46+
};
47+
},
48+
} as unknown as CacheStorage;
49+
50+
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
51+
let request = input instanceof Request ? input : new Request(input, init);
52+
let url = new URL(request.url);
53+
54+
if (url.origin === env.FILES_ORIGIN) {
55+
// Run the request through the file server. This allows us to write integration tests
56+
// that run without booting the file server.
57+
return handleFilesRequest(request);
58+
}
2659

27-
switch (url.toString()) {
60+
switch (url.href) {
2861
case "https://registry.npmjs.org/lodash":
2962
return fileResponse(packageInfo.lodash);
3063
case "https://registry.npmjs.org/preact":
@@ -44,13 +77,12 @@ describe("handleRequest", () => {
4477
default:
4578
throw new Error(`Unexpected URL: ${url}`);
4679
}
47-
};
80+
}) as unknown as typeof fetch;
4881
});
4982

5083
afterAll(() => {
51-
if (globalFetch) {
52-
globalThis.fetch = globalFetch;
53-
}
84+
if (globalCaches) globalThis.caches = globalCaches;
85+
if (globalFetch) globalThis.fetch = globalFetch;
5486
});
5587

5688
describe("file requests", () => {

0 commit comments

Comments
 (0)