feat: split and hash inlined assets
This commit is contained in:
@@ -1,7 +1,12 @@
|
|||||||
import { render } from "preact-render-to-string"
|
import { render } from "preact-render-to-string"
|
||||||
import { QuartzComponent, QuartzComponentProps } from "./types"
|
import { QuartzComponent, QuartzComponentProps } from "./types"
|
||||||
import BodyConstructor from "./Body"
|
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 { FullSlug, RelativeURL, joinSegments, normalizeHastElement } from "../util/path"
|
||||||
import { clone } from "../util/clone"
|
import { clone } from "../util/clone"
|
||||||
import { visit } from "unist-util-visit"
|
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 contentIndexPath = joinSegments(baseDir, "static/contentIndex.json")
|
||||||
const contentIndexScript = `const fetchData = fetch("${contentIndexPath}").then(data => data.json())`
|
const contentIndexScript = `const fetchData = fetch("${contentIndexPath}").then(data => data.json())`
|
||||||
|
|
||||||
@@ -55,7 +81,7 @@ export function pageResources(
|
|||||||
content: joinSegments(baseDir, cssFile),
|
content: joinSegments(baseDir, cssFile),
|
||||||
},
|
},
|
||||||
...componentCssResources,
|
...componentCssResources,
|
||||||
...staticResources.css,
|
...resolvedCss,
|
||||||
],
|
],
|
||||||
js: [
|
js: [
|
||||||
{
|
{
|
||||||
@@ -69,7 +95,7 @@ export function pageResources(
|
|||||||
spaPreserve: true,
|
spaPreserve: true,
|
||||||
script: contentIndexScript,
|
script: contentIndexScript,
|
||||||
},
|
},
|
||||||
...staticResources.js,
|
...resolvedJs,
|
||||||
],
|
],
|
||||||
additionalHead: staticResources.additionalHead,
|
additionalHead: staticResources.additionalHead,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentReso
|
|||||||
export const ComponentResources: QuartzEmitterPlugin = () => {
|
export const ComponentResources: QuartzEmitterPlugin = () => {
|
||||||
return {
|
return {
|
||||||
name: "ComponentResources",
|
name: "ComponentResources",
|
||||||
async *emit(ctx, _content, _resources) {
|
async *emit(ctx, _content, resources) {
|
||||||
const cfg = ctx.cfg.configuration
|
const cfg = ctx.cfg.configuration
|
||||||
// component specific scripts and styles
|
// component specific scripts and styles
|
||||||
const componentResources = getComponentResources(ctx)
|
const componentResources = getComponentResources(ctx)
|
||||||
@@ -395,6 +395,59 @@ export const ComponentResources: QuartzEmitterPlugin = () => {
|
|||||||
|
|
||||||
ctx.componentCssMap = cssStringToFilename
|
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 cssHash = useHashing ? hashContent(cssContent) : null
|
||||||
const prescriptHash = useHashing ? hashContent(prescript) : null
|
const prescriptHash = useHashing ? hashContent(prescript) : null
|
||||||
const postscriptHash = useHashing ? hashContent(postscript) : null
|
const postscriptHash = useHashing ? hashContent(postscript) : null
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ export interface BuildCtx {
|
|||||||
hashedResourceNames?: HashedResourceNames
|
hashedResourceNames?: HashedResourceNames
|
||||||
/** Maps CSS content strings to their emitted hashed filenames. Populated by ComponentResources. */
|
/** Maps CSS content strings to their emitted hashed filenames. Populated by ComponentResources. */
|
||||||
componentCssMap?: Map<string, string>
|
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> {
|
export function trieFromAllFiles(allFiles: QuartzPluginData[]): FileTrieNode<BuildTimeTrieData> {
|
||||||
|
|||||||
Reference in New Issue
Block a user