diff --git a/README.md b/README.md index bdee6aad..03540397 100644 --- a/README.md +++ b/README.md @@ -10,41 +10,55 @@ https://unpkg.com/:package@:version/:file Where `:package` is the package name, `:version` is the version range, and `:file` is the path to the file in the package. -You can learn more about UNPKG [on the website](https://unpkg.com). +You can [learn more about UNPKG on the website](https://unpkg.com). ## Development -This repository contains the source for the unpkg.com production server. The source is divided into two [Cloudflare Workers](https://workers.cloudflare.com/): +This repository contains the production source for UNPKG. There are 4 packages: -- [`unpkg-www-worker`](./workers/www/) serves all files on npm and the main homepage -- [`unpkg-app-worker`](./workers/app/) serves the package detail pages +- [`unpkg-app`](./packages/unpkg-app/) is the UNPKG web app (file browser) +- [`unpkg-files`](./packages/unpkg-files/) is the file server backend that fetches tarballs from npm and extracts their contents +- [`unpkg-worker`](./packages/unpkg-worker/) is a shared set of utilites between the web apps (Cloudflare workers) +- [`unpkg-www`](./packages/unpkg-www/) is the main UNPKG app -To run everything locally, you'll first need to do an install: +We use [Bun](https://bun.sh/) in development, as well as [pnpm](https://pnpm.io/). Install these first. + +Next, install all dependencies and run the tests: ```sh pnpm install +pnpm test ``` -Then start each worker along with its assets server (you'll need 4 terminal tabs): +Then start the file server and each worker along with its assets server (you'll need 5 terminal tabs): ```sh -cd workers/www && pnpm dev -cd workers/www && pnpm dev:assets -cd workers/app && pnpm dev -cd workers/app && pnpm dev:assets +cd packages/unpkg-files && pnpm dev +cd packages/unpkg-www && pnpm dev +cd packages/unpkg-www && pnpm dev:assets +cd packages/unpkg-app && pnpm dev +cd packages/unpkg-app && pnpm dev:assets ``` -The dev server will be listening on `http://localhost:3000`. [Wrangler](https://developers.cloudflare.com/workers/cli-wrangler) allows the workers to find and communicate with each other in dev. +The dev server will be listening on `http://localhost:3000`. ## Deploying -To deploy the workers, you'll need to have a [Cloudflare](https://cloudflare.com) account. You will also need to edit the `wrangler.json` file in each worker and update its [`routes`](https://developers.cloudflare.com/workers/wrangler/configuration/) to your own domain(s). You'll also need to adjust each worker's environment `vars` (in `wrangler.json`) so they can find one another in production. +The `unpkg-files` backend is deployed on [Fly.io](https://fly.io). You'll need an account. + +Next, adjust the Fly config in `packages/unpkg-files/fly.json` (you'll need your own app `name`) and deploy: + +```sh +cd packages/unpkg-files && pnpm run deploy +``` + +To deploy the workers, you'll need a [Cloudflare](https://cloudflare.com) account. You will also need to (1) edit the `wrangler.json` file in each worker and update its [`routes`](https://developers.cloudflare.com/workers/wrangler/configuration/) to your own domain(s) and (2) adjust each worker's environment `vars` (in `wrangler.json`) so they can find one another in production. Once you've done that, you can deploy each worker with: ```sh -cd workers/www && pnpm run deploy -cd workers/app && pnpm run deploy +cd packages/unpkg-www && pnpm run deploy +cd packages/unpkg-app && pnpm run deploy ``` ## License diff --git a/package.json b/package.json index b297f689..ee8b3a75 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "scripts": { "build": "pnpm -r run build", "clean": "git clean -fdX .", + "pretest": "pnpm run build", "test": "pnpm -r test" } } diff --git a/packages/unpkg-app/.gitignore b/packages/unpkg-app/.gitignore index 03fbfc53..3602c1b1 100644 --- a/packages/unpkg-app/.gitignore +++ b/packages/unpkg-app/.gitignore @@ -1,2 +1,3 @@ +.wrangler/ assets-manifest.json public/_assets/* \ No newline at end of file diff --git a/packages/unpkg-app/README.md b/packages/unpkg-app/README.md new file mode 100644 index 00000000..4ec8fc6b --- /dev/null +++ b/packages/unpkg-app/README.md @@ -0,0 +1,27 @@ +# unpkg-app + +This packages is the UNPKG web app. It is built and deployed as a [Cloudflare Worker](https://workers.cloudflare.com/). + +## Development + +Install dependencies and run the tests: + +``` +pnpm install +pnpm test +``` + +Boot the dev server and assets server (two separate tabs): + +``` +pnpm dev +pnpm dev:assets +``` + +## Deploying + +Edit the worker configuration in `wrangler.json`, then: + +``` +pnpm run deploy +``` diff --git a/packages/unpkg-app/fly.json b/packages/unpkg-app/fly.json deleted file mode 100644 index 93282047..00000000 --- a/packages/unpkg-app/fly.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "app": "unpkg-app", - "primary_region": "lax", - "build": { - "dockerfile": "Dockerfile", - "args": { - "MODE": "production" - } - }, - "http_service": { - "internal_port": 3000, - "auto_start_machines": true, - "min_machines_running": 2, - "processes": ["app"], - "checks": [ - { - "grace_period": "10s", - "interval": "1m0s", - "method": "get", - "path": "/_health", - "timeout": "5s" - } - ] - }, - "vm": [{ "size": "shared-cpu-8x" }] -} diff --git a/packages/unpkg-app/fly.staging.json b/packages/unpkg-app/fly.staging.json deleted file mode 100644 index 073b64c4..00000000 --- a/packages/unpkg-app/fly.staging.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "app": "unpkg-app-staging", - "primary_region": "lax", - "build": { - "dockerfile": "Dockerfile", - "args": { - "MODE": "staging" - } - }, - "http_service": { - "internal_port": 3000, - "auto_start_machines": true, - "min_machines_running": 1, - "processes": ["app"], - "checks": [ - { - "grace_period": "10s", - "interval": "1m0s", - "method": "get", - "path": "/_health", - "timeout": "5s" - } - ] - }, - "vm": [{ "size": "shared-cpu-8x" }] -} diff --git a/packages/unpkg-app/package.json b/packages/unpkg-app/package.json index 9644cef8..da4f6182 100644 --- a/packages/unpkg-app/package.json +++ b/packages/unpkg-app/package.json @@ -4,37 +4,31 @@ "private": true, "type": "module", "dependencies": { - "chalk": "^5.4.1", "highlight.js": "^11.11.1", - "mrmime": "^2.0.0", "preact": "^10.25.2", "preact-render-to-string": "^6.5.12", "pretty-bytes": "^6.1.1", "semver": "^7.6.3", - "unpkg-tools": "workspace:^" + "unpkg-worker": "workspace:*" }, "devDependencies": { + "@cloudflare/workers-types": "^4.20250320.0", "@ryanto/esbuild-plugin-tailwind": "^0.0.1", "@types/bun": "^1.2.8", "@types/node": "^22.10.2", "@types/semver": "^7.5.8", "esbuild": "^0.24.2", "tailwindcss": "4.0.0-beta.9", - "typescript": "^5.7.2" + "wrangler": "^4.3.0" }, "scripts": { - "build": "pnpm run build:assets && pnpm run build:server", + "build": "wrangler deploy --dry-run --outdir=dist", "build:assets": "node --disable-warning=ExperimentalWarning ../../scripts/build-assets.ts", - "build:server": "tsc --project ./tsconfig.build.json", - "dev": "MODE=development bun --port=3001 ./src/server.ts", + "dev": "wrangler dev --env dev --port 3001", "dev:assets": "bun ../../scripts/serve-assets.ts", "test": "bun --preload=./test/setup.ts test", - "deploy": "cd ../.. && fly deploy . -c ./packages/unpkg-app/fly.json", - "deploy:staging": "cd ../.. && fly deploy . -c ./packages/unpkg-app/fly.staging.json" - }, - "files": [ - "assets-manifest.json", - "dist", - "public" - ] + "predeploy": "pnpm run build:assets", + "deploy": "wrangler deploy", + "deploy:staging": "wrangler deploy --env staging" + } } diff --git a/packages/unpkg-app/src/assets-manifest.ts b/packages/unpkg-app/src/assets-manifest.ts index 3312b215..f27f067b 100644 --- a/packages/unpkg-app/src/assets-manifest.ts +++ b/packages/unpkg-app/src/assets-manifest.ts @@ -1,46 +1,33 @@ -import * as path from "node:path"; -import * as fsp from "node:fs/promises"; - -import { env } from "./env.ts"; - -const __dirname = path.dirname(new URL(import.meta.url).pathname); -const rootDir = path.resolve(__dirname, ".."); +import type { Env } from "./env.ts"; /** * A map of entry points to their URLs. */ export type AssetsManifest = Map; -export async function loadAssetsManifest(): Promise { - let mod: Record; +export async function loadAssetsManifest(env: Env): Promise { + let mod: { default: Record }; switch (env.MODE) { case "development": case "test": - mod = await loadJson(path.join(rootDir, "assets-manifest.dev.json")); + mod = await import("../assets-manifest.dev.json"); break; case "production": case "staging": try { - mod = await loadJson(path.join(rootDir, "assets-manifest.json")); + // @ts-ignore - This file is generated at build time + mod = await import("../assets-manifest.json"); } catch (error) { - throw new Error("Failed to load assets-manifest.json. Did you run `pnpm run build:assets`?"); + throw new Error("Failed to load assets-manifest.json. Did you run `pnpm build:assets`?"); } break; } let manifest: AssetsManifest = new Map(); - for (let [entryPoint, path] of Object.entries(mod)) { - if (env.ASSETS_ORIGIN) { - manifest.set(entryPoint, new URL(path, env.ASSETS_ORIGIN).href); - } else { - manifest.set(entryPoint, path); - } + for (let [entryPoint, path] of Object.entries(mod.default)) { + manifest.set(entryPoint, new URL(path, env.ASSETS_ORIGIN).href); } return manifest; } - -async function loadJson(file: string): Promise> { - return JSON.parse(await fsp.readFile(file, "utf-8")); -} diff --git a/packages/unpkg-app/src/components/document.tsx b/packages/unpkg-app/src/components/document.tsx index 4d0cabda..5d53bc17 100644 --- a/packages/unpkg-app/src/components/document.tsx +++ b/packages/unpkg-app/src/components/document.tsx @@ -3,25 +3,27 @@ import { type VNode } from "preact"; import { useAsset } from "../hooks.ts"; import { type ImportMap } from "../import-map.ts"; -const importMap: ImportMap = { - imports: { - preact: "https://unpkg.com/preact@10.25.4/dist/preact.module.js", - "preact/hooks": "https://unpkg.com/preact@10.25.4/hooks/dist/hooks.module.js", - "preact/jsx-runtime": "https://unpkg.com/preact@10.25.4/jsx-runtime/dist/jsxRuntime.module.js", - }, -}; - export function Document({ children, description = "The CDN for everything on npm", title = "UNPKG", subtitle, + wwwOrigin = "https://unpkg.com", }: { children?: VNode | VNode[]; description?: string; title?: string; subtitle?: string; + wwwOrigin?: string; }): VNode { + let importMap: ImportMap = { + imports: { + preact: new URL("/preact@10.25.4/dist/preact.module.js", wwwOrigin).href, + "preact/hooks": new URL("/preact@10.25.4/hooks/dist/hooks.module.js", wwwOrigin).href, + "preact/jsx-runtime": new URL("/preact@10.25.4/jsx-runtime/dist/jsxRuntime.module.js", wwwOrigin).href, + }, + }; + return ( @@ -36,7 +38,7 @@ export function Document({ - {subtitle == null ? title : `UNPKG • ${subtitle}`} + {subtitle == null ? title : `${title} • ${subtitle}`}