ci: replace asset build script by fresh plugin
This commit is contained in:
parent
863c02f4ae
commit
e008e0f929
56
build.ts
56
build.ts
|
@ -1,56 +0,0 @@
|
||||||
import { bundleAsync } from 'lightningcss'
|
|
||||||
import { ensureDir } from '$std/fs/mod.ts'
|
|
||||||
import { parse, resolve } from '$std/path/mod.ts'
|
|
||||||
|
|
||||||
console.log('building styles starts')
|
|
||||||
await ensureDir('_fresh')
|
|
||||||
|
|
||||||
try {
|
|
||||||
//prevent Deno from exiting before bundle
|
|
||||||
setTimeout(() => {}, 2_000)
|
|
||||||
|
|
||||||
const cssImports = new Map()
|
|
||||||
|
|
||||||
const { code, map } = await bundleAsync({
|
|
||||||
filename: './src/stylesheets/main.css',
|
|
||||||
minify: true,
|
|
||||||
sourceMap: true,
|
|
||||||
resolver: {
|
|
||||||
read(path) {
|
|
||||||
return Deno.readTextFile(path)
|
|
||||||
},
|
|
||||||
async resolve(specifier, from) {
|
|
||||||
//resolve local files normally
|
|
||||||
if (!specifier.startsWith('https://')) {
|
|
||||||
return resolve(parse(from).dir, specifier)
|
|
||||||
}
|
|
||||||
//use cache for remote
|
|
||||||
if (cssImports.has(specifier)) return cssImports.get(specifier)
|
|
||||||
|
|
||||||
//update cache for new remote
|
|
||||||
const response = await fetch(specifier)
|
|
||||||
const file = await response.arrayBuffer()
|
|
||||||
const hash = new Uint8Array(await crypto.subtle.digest('SHA-256', file))
|
|
||||||
const filename = [...hash].map(value => value.toString(16).padStart(2, '0')).join('')
|
|
||||||
|
|
||||||
const filepath = `_fresh/${filename}`
|
|
||||||
await Deno.writeFile(filepath, new Uint8Array(file))
|
|
||||||
cssImports.set(specifier, filepath)
|
|
||||||
return filepath
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await Deno.writeTextFile(
|
|
||||||
'./static/dev/styles.css',
|
|
||||||
new TextDecoder().decode(code),
|
|
||||||
)
|
|
||||||
await Deno.writeTextFile(
|
|
||||||
'./static/dev/styles.map.css',
|
|
||||||
new TextDecoder().decode(map ?? new Uint8Array()),
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('building styles finish')
|
|
|
@ -8,8 +8,7 @@
|
||||||
"build": "deno run -A dev.ts build",
|
"build": "deno run -A dev.ts build",
|
||||||
"preview": "deno run -A main.ts",
|
"preview": "deno run -A main.ts",
|
||||||
"update": "deno run -A -r https://fresh.deno.dev/update .",
|
"update": "deno run -A -r https://fresh.deno.dev/update .",
|
||||||
"assets:watch": "deno run -A --watch=src/ build.ts",
|
"prod": "deno task build && export PORT=80 && deno task preview"
|
||||||
"assets": "deno run -A build.ts"
|
|
||||||
},
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
import { defineConfig } from '$fresh/server.ts'
|
import { defineConfig } from '$fresh/server.ts'
|
||||||
|
import { cssBundler } from './plugins/css_bundler/plugin.ts'
|
||||||
|
|
||||||
export default defineConfig({})
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
cssBundler(import.meta.resolve('./src/stylesheets')),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
29
plugins/css_bundler/plugin.ts
Normal file
29
plugins/css_bundler/plugin.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { Plugin } from '$fresh/server.ts'
|
||||||
|
import { fromFileUrl } from '$std/path/mod.ts'
|
||||||
|
import { bundleCss } from './src/bundler.ts'
|
||||||
|
import { cssHandler } from './src/middleware.ts'
|
||||||
|
|
||||||
|
export function cssBundler(sourceDir: string, pattern = /main.css/): Plugin {
|
||||||
|
return {
|
||||||
|
name: 'css_bundler',
|
||||||
|
middlewares: [{
|
||||||
|
middleware: { handler: cssHandler(sourceDir) },
|
||||||
|
path: '/',
|
||||||
|
}],
|
||||||
|
async buildStart(config) {
|
||||||
|
//Get fresh build directory
|
||||||
|
const { outDir } = config.build
|
||||||
|
const tasks: Promise<void>[] = []
|
||||||
|
|
||||||
|
//Get all source stylesheets
|
||||||
|
for await (const entry of Deno.readDir(fromFileUrl(sourceDir))) {
|
||||||
|
if (entry.isFile && entry.name.match(pattern)) {
|
||||||
|
tasks.push(bundleCss(sourceDir, outDir, entry.name, config.dev))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Await for all bundle to finish
|
||||||
|
await Promise.all(tasks)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
59
plugins/css_bundler/src/builder.ts
Normal file
59
plugins/css_bundler/src/builder.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import { join, parse, resolve, toFileUrl } from '$std/path/mod.ts'
|
||||||
|
import { bundleAsync } from 'lightningcss'
|
||||||
|
import { cssImports, hashFile, Logger } from './helpers.ts'
|
||||||
|
|
||||||
|
export async function builder(
|
||||||
|
{ filename, dev, assetDir }: {
|
||||||
|
filename: string
|
||||||
|
dev: boolean
|
||||||
|
assetDir: string
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const { code, map } = await bundleAsync({
|
||||||
|
filename,
|
||||||
|
minify: true,
|
||||||
|
sourceMap: dev,
|
||||||
|
resolver: {
|
||||||
|
read(path) {
|
||||||
|
return Deno.readTextFile(path)
|
||||||
|
},
|
||||||
|
async resolve(specifier, from) {
|
||||||
|
//resolve local files normally
|
||||||
|
if (!specifier.startsWith('https://') && !from.startsWith('https://')) {
|
||||||
|
Logger.info('resolve local file', specifier)
|
||||||
|
return resolve(parse(from).dir, specifier)
|
||||||
|
}
|
||||||
|
//use cache for remote
|
||||||
|
if (cssImports.has(`${from}/${specifier}`)) {
|
||||||
|
Logger.info('using cache for', `${from}/${specifier}`)
|
||||||
|
return cssImports.get(`${from}/${specifier}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
//update cache for new remote
|
||||||
|
Logger.info('fetching and caching', `${from}/${specifier}`)
|
||||||
|
|
||||||
|
//construct asset url
|
||||||
|
const baseUrl = from.startsWith('https://') ? from : toFileUrl(from)
|
||||||
|
const url = new URL(specifier, baseUrl)
|
||||||
|
|
||||||
|
//fetch asset
|
||||||
|
const response = await fetch(url)
|
||||||
|
const file = await response.arrayBuffer()
|
||||||
|
const filename = await hashFile(file)
|
||||||
|
|
||||||
|
//!TODO recursive bundle to cache all remote imports
|
||||||
|
// bundleAsync({ minify: true, sourceMap: true, '' })
|
||||||
|
// const { code, map } = transform({ minify: true, sourceMap: dev, code: new Uint8Array(file), filename: url.toString() })
|
||||||
|
|
||||||
|
// const { code, map } = await builder({ filename: url.toString(), assetDir, dev })
|
||||||
|
|
||||||
|
const filepath = join(assetDir, filename)
|
||||||
|
await Deno.writeFile(filepath, new Uint8Array(file))
|
||||||
|
cssImports.set(`${from}/${specifier}`, filepath)
|
||||||
|
return filepath
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return { code, map }
|
||||||
|
}
|
31
plugins/css_bundler/src/bundler.ts
Normal file
31
plugins/css_bundler/src/bundler.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { fromFileUrl, join } from '$std/path/mod.ts'
|
||||||
|
import { builder } from './builder.ts'
|
||||||
|
import { cssImports, Logger } from './helpers.ts'
|
||||||
|
|
||||||
|
export async function bundleCss(
|
||||||
|
sourceDir: string,
|
||||||
|
assetDir: string,
|
||||||
|
pathname: string,
|
||||||
|
dev: boolean,
|
||||||
|
) {
|
||||||
|
const filename = fromFileUrl(join(sourceDir, pathname))
|
||||||
|
Logger.info('bundling', filename)
|
||||||
|
|
||||||
|
//prevent Deno from exiting before bundle
|
||||||
|
setTimeout(() => {}, 3_000)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { code, map } = await builder({ filename, dev, assetDir })
|
||||||
|
|
||||||
|
await Deno.writeFile(join(assetDir, pathname), code)
|
||||||
|
if (map) {
|
||||||
|
await Deno.writeFile(
|
||||||
|
join(assetDir, pathname.replace('.css', '.map.css')),
|
||||||
|
map,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error('error during bundle, cleaning cache', error)
|
||||||
|
cssImports.clear()
|
||||||
|
}
|
||||||
|
}
|
28
plugins/css_bundler/src/helpers.ts
Normal file
28
plugins/css_bundler/src/helpers.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
export class Logger {
|
||||||
|
static #name = 'bundle_css'
|
||||||
|
|
||||||
|
static info(message: string, path?: string) {
|
||||||
|
console.log(
|
||||||
|
`%c[${this.#name}]%c ${message} %c${path ?? ''}`,
|
||||||
|
'color: blue; font-weight: bold',
|
||||||
|
'',
|
||||||
|
'color: green',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static error(message: string, error?: Error) {
|
||||||
|
console.error(
|
||||||
|
`%c[${this.#name}]%c ${message} %c${error?.toString() ?? ''}`,
|
||||||
|
'color: red; font-weight: bold',
|
||||||
|
'',
|
||||||
|
'color: yellow',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function hashFile(file: ArrayBuffer): Promise<string> {
|
||||||
|
const hash = new Uint8Array(await crypto.subtle.digest('SHA-256', file))
|
||||||
|
return [...hash].map((value) => value.toString(16).padStart(2, '0')).join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cssImports = new Map()
|
27
plugins/css_bundler/src/middleware.ts
Normal file
27
plugins/css_bundler/src/middleware.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { MiddlewareHandler } from '$fresh/server.ts'
|
||||||
|
import { ensureDir } from '$std/fs/ensure_dir.ts'
|
||||||
|
import { join } from '$std/path/mod.ts'
|
||||||
|
import { bundleCss } from './bundler.ts'
|
||||||
|
|
||||||
|
export function cssHandler(sourceDir: string): MiddlewareHandler {
|
||||||
|
return async (_, ctx) => {
|
||||||
|
const assetDir = join(ctx.config.build.outDir, '/static')
|
||||||
|
await ensureDir(assetDir)
|
||||||
|
|
||||||
|
if (ctx.config.dev) {
|
||||||
|
if (
|
||||||
|
ctx.url.pathname.startsWith('/') && ctx.url.pathname.endsWith('.css')
|
||||||
|
) {
|
||||||
|
bundleCss(sourceDir, assetDir, ctx.url.pathname, ctx.config.dev)
|
||||||
|
const file = await Deno.readFile(join(assetDir, ctx.url.pathname))
|
||||||
|
return new Response(file, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/css; charset=utf-8',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const resp = await ctx.next()
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue