/** * Welcome to Cloudflare Workers! This is your first worker. * * - Run `wrangler dev src/index.ts` in your terminal to start a development server * - Open a browser tab at http://localhost:8787/ to see your worker in action * - Run `wrangler publish src/index.ts --name my-worker` to publish your worker * * Learn more at https://developers.cloudflare.com/workers/ */ export interface Env { // Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/ // MY_KV_NAMESPACE: KVNamespace; // // Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/ // MY_DURABLE_OBJECT: DurableObjectNamespace; // // Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/ // MY_BUCKET: R2Bucket; } // clear previous cache ;(async (cache) => { for (const key of await cache.keys()) { await cache.delete(key) } })((caches as CacheStorage & { readonly default: Cache }).default) const CDN_ORIGIN = 'https://cdn.jsdelivr.net' const DEFAULT_VERSION = 'latest' const WELL_KNOWN_PRESETS: Record = { ext: '@twind/preset-ext', 'line-clamp': '@twind/preset-line-clamp', 'tailwind-forms': '@twind/preset-tailwind-forms', // compat for Tailwind CSS Play CDN forms: '@twind/preset-tailwind-forms', typography: '@twind/preset-typography', } export default { async fetch(request: Request, env: Env, context: ExecutionContext) { // Only GET requests work with this proxy. if (request.method !== 'GET') return MethodNotAllowed(request) const url = new URL(request.url) let targetURL: string | undefined let { pathname } = url // Cache API respects Cache-Control headers. // jsdelivr sends something like: `public, max-age=604800, s-maxage=43200` // we may need to adjust the value if a tag like latest, next or canary is used // other version are very specific let cacheControl: string | undefined if (pathname === '/favicon.ico') { targetURL = 'https://twind.style/favicon.ico' } else if (pathname.startsWith('/sm/') && pathname.endsWith('.map')) { // forward source map requests // /sm/6a8adc00c50122db980fa39db2d4346cd3483a7ac903c53b1246d9b9a1427158.map // sourcemap urls are very specific -> use cache-control from jsdelivr response targetURL = CDN_ORIGIN + pathname } else { // default version // / // /?presets=forms,typography,line-clamp // /forms,typography,line-clamp // explicit version // /@canary // /@canary?presets=forms,typography,line-clamp // /@canary/forms,typography,line-clamp const match = pathname.match( /^\/(?:@(latest|next|canary|dev|(?:0|[1-9]\d{0,9}?)(?:\.(?:0|[1-9]\d{0,9})){2}(?:-[^/]+)?)(?=$|\/))/, ) let version = DEFAULT_VERSION if (match) { // versioned version = match[1] pathname = pathname.slice(match[0].length) } let hasTagVersion = !/^\d/.test(version) const modules = [ `@twind/cdn${version && version !== 'latest' ? '@' + version : ''}`, pathname.slice(1), url.searchParams.get('presets') || '', ] .filter(Boolean) .join(',') .split(',') .map((specifier) => { const match = specifier.match(/^((?:@[^/]+\/)?[^/@]+)(?:@([^/]+))?(\/.+)?$/) if (match) { const { 1: id, 2: version = DEFAULT_VERSION, 3: path } = match hasTagVersion ||= !/^\d/.test(version) const preset = WELL_KNOWN_PRESETS[id] if (preset) { return ( preset + (version && version !== 'latest' ? '@' + version : '') + (path ? '/' + path : '') ) } } else { // no match just be sure to not cache forever hasTagVersion = true } return specifier }) // -> https://cdn.jsdelivr.net/npm/twind // -> https://cdn.jsdelivr.net/combine/npm/@twind/cdn,npm/@twind/preset-ext targetURL = CDN_ORIGIN + (modules.length > 1 ? '/combine/' : '/') + modules.map((module) => `npm/${module}`).join(',') if (hasTagVersion) { // clients can cache for 4 hours, shared caches for 1 hour cacheControl = 'public, max-age=14400, s-maxage=3600' } } if (!targetURL) return NotFound(request) // Construct the target request const target = new Request(targetURL, request) const cache = (caches as CacheStorage & { readonly default: Cache }).default // Check whether the value is already available in the cache // if not, you will need to fetch it from origin, and store it in the cache // for future access let response = await cache.match(target) if (!response) { console.log(`Cache miss for ${request.url} - fetching from ${target.url}`) // If not in cache, get it from origin response = await fetch(target) if (cacheControl) { // Must use Response constructor to inherit all of response's fields response = new Response(response.body, response) // Any changes made to the response here will be reflected in the cached value response.headers.set('Cache-Control', cacheControl) } // Store the fetched response // Use waitUntil so you can return the response without blocking on // writing to cache context.waitUntil(cache.put(target, response.clone())) } else { console.log(`Cache hit for: ${request.url} - fetched from ${target.url}`) } return response }, } function MethodNotAllowed(request: Request) { return new Response(`Method ${request.method} not allowed.`, { status: 405, headers: { Allow: 'GET', }, }) } function NotFound(request: Request) { return new Response(`Path ${new URL(request.url).pathname} not found.`, { status: 400, }) }