feat: split and hash inlined assets
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import { render } from "preact-render-to-string"
|
||||
import { QuartzComponent, QuartzComponentProps } from "./types"
|
||||
import BodyConstructor from "./Body"
|
||||
import { CSSResource, JSResourceToScriptElement, StaticResources } from "../util/resources"
|
||||
import {
|
||||
CSSResource,
|
||||
JSResource,
|
||||
JSResourceToScriptElement,
|
||||
StaticResources,
|
||||
} from "../util/resources"
|
||||
import { FullSlug, RelativeURL, joinSegments, normalizeHastElement } from "../util/path"
|
||||
import { clone } from "../util/clone"
|
||||
import { visit } from "unist-util-visit"
|
||||
@@ -46,6 +51,27 @@ export function pageResources(
|
||||
}
|
||||
}
|
||||
|
||||
const extracted = ctx?.extractedInlineResources
|
||||
const resolvedCss: CSSResource[] = staticResources.css.map((resource) => {
|
||||
if (!(resource.inline ?? false) || !extracted) return resource
|
||||
const filename = extracted.get(resource.content)
|
||||
if (!filename) return resource
|
||||
return { content: joinSegments(baseDir, filename) }
|
||||
})
|
||||
|
||||
const resolvedJs: JSResource[] = staticResources.js.map((resource) => {
|
||||
if (resource.contentType !== "inline" || !extracted) return resource
|
||||
const filename = extracted.get(resource.script)
|
||||
if (!filename) return resource
|
||||
return {
|
||||
src: joinSegments(baseDir, filename),
|
||||
loadTime: resource.loadTime,
|
||||
contentType: "external" as const,
|
||||
moduleType: resource.moduleType,
|
||||
spaPreserve: resource.spaPreserve,
|
||||
}
|
||||
})
|
||||
|
||||
const contentIndexPath = joinSegments(baseDir, "static/contentIndex.json")
|
||||
const contentIndexScript = `const fetchData = fetch("${contentIndexPath}").then(data => data.json())`
|
||||
|
||||
@@ -55,7 +81,7 @@ export function pageResources(
|
||||
content: joinSegments(baseDir, cssFile),
|
||||
},
|
||||
...componentCssResources,
|
||||
...staticResources.css,
|
||||
...resolvedCss,
|
||||
],
|
||||
js: [
|
||||
{
|
||||
@@ -69,7 +95,7 @@ export function pageResources(
|
||||
spaPreserve: true,
|
||||
script: contentIndexScript,
|
||||
},
|
||||
...staticResources.js,
|
||||
...resolvedJs,
|
||||
],
|
||||
additionalHead: staticResources.additionalHead,
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentReso
|
||||
export const ComponentResources: QuartzEmitterPlugin = () => {
|
||||
return {
|
||||
name: "ComponentResources",
|
||||
async *emit(ctx, _content, _resources) {
|
||||
async *emit(ctx, _content, resources) {
|
||||
const cfg = ctx.cfg.configuration
|
||||
// component specific scripts and styles
|
||||
const componentResources = getComponentResources(ctx)
|
||||
@@ -395,6 +395,59 @@ export const ComponentResources: QuartzEmitterPlugin = () => {
|
||||
|
||||
ctx.componentCssMap = cssStringToFilename
|
||||
|
||||
// Extract inline CSS/JS from plugin externalResources() into external files.
|
||||
// This prevents large inline payloads (e.g. theme CSS) from being duplicated
|
||||
// into every HTML page's <head>.
|
||||
const extractedInlineResources = new Map<string, string>()
|
||||
for (const cssResource of resources.css) {
|
||||
if (!(cssResource.inline ?? false)) continue
|
||||
|
||||
let output: string
|
||||
try {
|
||||
output = transform({
|
||||
filename: "plugin-resource.css",
|
||||
code: Buffer.from(cssResource.content),
|
||||
minify: true,
|
||||
targets: lightningTargets,
|
||||
include: Features.MediaQueries,
|
||||
}).code.toString()
|
||||
} catch {
|
||||
output = cssResource.content
|
||||
}
|
||||
|
||||
const hash = hashContent(output)
|
||||
const slug = `static/resource-style-${hash}`
|
||||
const filename = `${slug}.css`
|
||||
extractedInlineResources.set(cssResource.content, filename)
|
||||
|
||||
yield write({
|
||||
ctx,
|
||||
slug: slug as FullSlug,
|
||||
ext: ".css",
|
||||
content: output,
|
||||
})
|
||||
}
|
||||
|
||||
for (const jsResource of resources.js) {
|
||||
if (jsResource.contentType !== "inline") continue
|
||||
|
||||
const minified = await joinScripts([jsResource.script])
|
||||
const hash = hashContent(minified)
|
||||
const loadTimePrefix = jsResource.loadTime === "beforeDOMReady" ? "before" : "after"
|
||||
const slug = `static/resource-${loadTimePrefix}-${hash}`
|
||||
const filename = `${slug}.js`
|
||||
extractedInlineResources.set(jsResource.script, filename)
|
||||
|
||||
yield write({
|
||||
ctx,
|
||||
slug: slug as FullSlug,
|
||||
ext: ".js",
|
||||
content: minified,
|
||||
})
|
||||
}
|
||||
|
||||
ctx.extractedInlineResources = extractedInlineResources
|
||||
|
||||
const cssHash = useHashing ? hashContent(cssContent) : null
|
||||
const prescriptHash = useHashing ? hashContent(prescript) : null
|
||||
const postscriptHash = useHashing ? hashContent(postscript) : null
|
||||
|
||||
@@ -42,6 +42,8 @@ export interface BuildCtx {
|
||||
hashedResourceNames?: HashedResourceNames
|
||||
/** Maps CSS content strings to their emitted hashed filenames. Populated by ComponentResources. */
|
||||
componentCssMap?: Map<string, string>
|
||||
/** Maps inline CSS/JS content to extracted external file paths. Populated by ComponentResources. */
|
||||
extractedInlineResources?: Map<string, string>
|
||||
}
|
||||
|
||||
export function trieFromAllFiles(allFiles: QuartzPluginData[]): FileTrieNode<BuildTimeTrieData> {
|
||||
|
||||
Reference in New Issue
Block a user