aliaset
Version:
twind monorepo
183 lines (152 loc) • 6.05 kB
text/typescript
/**
* 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<string, string> = {
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,
})
}