website/plugins/css_bundler/src/builder.ts

107 lines
2.9 KiB
TypeScript

import { join, parse, resolve, toFileUrl } from '$std/path/mod.ts'
import { bundleAsync } from 'lightningcss'
import { Logger, cssImports, uInt8ArrayConcat } from './helpers.ts'
export async function builder(
{ filename, dev, assetDir, remote }: {
filename: string
dev: boolean
assetDir: string
remote?: string
},
) {
const { code, map } = await bundleAsync({
filename,
minify: true,
sourceMap: dev,
resolver: {
async read(pathOrUrl) {
if (pathOrUrl.startsWith('https%3A%2F%2F')) {
pathOrUrl = decodeURIComponent(pathOrUrl)
}
if (pathOrUrl.startsWith('https://')) {
//use cache for remote
if (cssImports.has(pathOrUrl)) {
Logger.info('using cache for', `${pathOrUrl}`)
const filepath = cssImports.get(pathOrUrl)!
return Deno.readTextFile(filepath)
}
//update cache for new remote
Logger.info('fetching and caching', `${pathOrUrl}`)
const response = await fetch(pathOrUrl)
const file = await response.arrayBuffer()
const filename = encodeURIComponent(pathOrUrl.toString())
const filepath = join(assetDir, `${filename}.css`)
await Deno.writeFile(filepath, new Uint8Array(file))
setTimeout(() => {}, 3_000)
const { code, map } = await builder({
filename: filepath,
dev,
assetDir,
remote: pathOrUrl,
})
if (map) {
const sourceMappingURL = new TextEncoder().encode(
`\n/*# sourceMappingURL=${filepath}.map.css */`,
)
await Deno.writeFile(
filepath,
uInt8ArrayConcat(code, sourceMappingURL),
)
await Deno.writeFile(`${filepath}.map.css`, map)
} else {
await Deno.writeFile(filepath, code)
}
cssImports.set(pathOrUrl, filepath)
return new TextDecoder().decode(file)
}
return Deno.readTextFile(pathOrUrl)
},
resolve(specifier, from) {
if (remote) {
const url = new URL(specifier, remote)
Logger.info('resolve remote imports', url.toString())
return url.toString()
}
//resolve local files normally
if (!specifier.startsWith('https://') && !from.startsWith('https://')) {
Logger.info('resolve local file', specifier)
return resolve(parse(from).dir, specifier)
}
//construct asset url
const baseUrl = from.startsWith('https://') ? from : toFileUrl(from)
const url = new URL(specifier, baseUrl)
Logger.info('resolve remote file', url.toString())
return url.toString()
},
},
visitor: {
Url({ loc, url }) {
if (remote && !url.startsWith('data:')) {
cache(url, remote, assetDir)
}
return { loc, url }
}
}
})
return { code, map }
}
async function cache(url: string, base: string, assetDir: string) {
const fullUrl = new URL(url, base)
const response = await fetch(fullUrl)
const file = await response.arrayBuffer()
const filepath = join(assetDir, url.split('?')[0])
await Deno.writeFile(filepath, new Uint8Array(file))
}