Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

themes: use content hash in cache key and output file name #10611

Merged
merged 8 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions src/command/render/pandoc-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import {
} from "../../core/pandoc/css.ts";
import { kMinimal } from "../../format/html/format-html-shared.ts";
import { kSassBundles } from "../../config/types.ts";
import { md5HashBytes } from "../../core/hash.ts";
import { InternalError } from "../../core/lib/error.ts";

// The output target for a sass bundle
// (controls the overall style tag that is emitted)
Expand Down Expand Up @@ -92,7 +94,9 @@ export async function resolveSassBundles(
const targets: SassTarget[] = [{
name: `${dependency}.min.css`,
bundles,
attribs: {},
attribs: {
"append-hash": "true",
},
}];
if (hasDark) {
// Note that the other bundle provides light
Expand Down Expand Up @@ -162,12 +166,30 @@ export async function resolveSassBundles(
extraDep.name === dependency
);

let targetName = target.name;
if (target.attribs["append-hash"] === "true") {
const hashFragment = `-${md5HashBytes(Deno.readFileSync(cssPath))}`;
let extension = "";
if (target.name.endsWith(".min.css")) {
extension = ".min.css";
} else if (target.name.endsWith(".css")) {
extension = ".css";
} else {
throw new InternalError("Unexpected target name: " + target.name);
}
targetName =
targetName.slice(0, target.name.length - extension.length) +
hashFragment + extension;
} else {
targetName = target.name;
}

if (existingDependency) {
if (!existingDependency.stylesheets) {
existingDependency.stylesheets = [];
}
existingDependency.stylesheets.push({
name: target.name,
name: targetName,
path: cssPath,
attribs: target.attribs,
});
Expand All @@ -179,7 +201,7 @@ export async function resolveSassBundles(
extraDeps.push({
name: dependency,
stylesheets: [{
name: target.name,
name: targetName,
path: cssPath,
attribs: target.attribs,
}, ...imports],
Expand Down Expand Up @@ -249,7 +271,7 @@ async function resolveQuartoSyntaxHighlighting(
// Generate and inject the text highlighting css
const cssFileName = `quarto-syntax-highlighting${
style === "dark" ? "-dark" : ""
}.css`;
}`;

// Read the highlight style (theme name)
const themeDescriptor = readHighlightingTheme(inputDir, format.pandoc, style);
Expand Down Expand Up @@ -283,7 +305,7 @@ async function resolveQuartoSyntaxHighlighting(
// Compile the scss
const highlightCssPath = await compileSass(
[{
key: cssFileName,
key: cssFileName + ".css",
quarto: {
uses: "",
defaults: "",
Expand Down Expand Up @@ -312,8 +334,9 @@ async function resolveQuartoSyntaxHighlighting(
existingDependency.stylesheets = existingDependency.stylesheets ||
[];

const hash = md5HashBytes(Deno.readFileSync(highlightCssPath));
existingDependency.stylesheets.push({
name: cssFileName,
name: cssFileName + `-${hash}.css`,
path: highlightCssPath,
attribs: mediaAttr,
});
Expand Down Expand Up @@ -448,7 +471,8 @@ function processCssIntoExtras(

if (dirty) {
const cleanedCss = css.replaceAll(kVariablesRegex, "");
const newCssPath = temp.createFile({ suffix: ".css" });
const hash = md5HashBytes(new TextEncoder().encode(cleanedCss));
const newCssPath = temp.createFile({ suffix: `-${hash}.css` });

// Preserve the existing permissions if possible
// See https://github.com/quarto-dev/quarto-cli/issues/660
Expand Down
50 changes: 25 additions & 25 deletions src/command/render/render-contexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,31 +507,31 @@ async function resolveFormats(
(isHtmlOutput(format, true) || isHtmlDashboardOutput(format)) &&
formatHasBootstrap(projFormat) && projectTypeIsWebsite(projType)
) {
if (formatHasBootstrap(inputFormat)) {
if (
inputFormat.metadata[kTheme] !== undefined &&
!ld.isEqual(inputFormat.metadata[kTheme], projFormat.metadata[kTheme])
) {
warnOnce(
`The file ${file.path} contains a theme property which is being ignored. Website projects do not support per document themes since all pages within a website share the website's theme.`,
);
}
delete inputFormat.metadata[kTheme];
}
if (formatHasBootstrap(directoryFormat)) {
if (
directoryFormat.metadata[kTheme] !== undefined &&
!ld.isEqual(
directoryFormat.metadata[kTheme],
projFormat.metadata[kTheme],
)
) {
warnOnce(
`The file ${file.path} contains a theme provided by a metadata file. This theme metadata is being ignored. Website projects do not support per directory themes since all pages within a website share the website's theme.`,
);
}
delete directoryFormat.metadata[kTheme];
}
// if (formatHasBootstrap(inputFormat)) {
// if (
// inputFormat.metadata[kTheme] !== undefined &&
// !ld.isEqual(inputFormat.metadata[kTheme], projFormat.metadata[kTheme])
// ) {
// warnOnce(
// `The file ${file.path} contains a theme property which is being ignored. Website projects do not support per document themes since all pages within a website share the website's theme.`,
// );
// }
// delete inputFormat.metadata[kTheme];
// }
// if (formatHasBootstrap(directoryFormat)) {
// if (
// directoryFormat.metadata[kTheme] !== undefined &&
// !ld.isEqual(
// directoryFormat.metadata[kTheme],
// projFormat.metadata[kTheme],
// )
// ) {
// warnOnce(
// `The file ${file.path} contains a theme provided by a metadata file. This theme metadata is being ignored. Website projects do not support per directory themes since all pages within a website share the website's theme.`,
// );
// }
// delete directoryFormat.metadata[kTheme];
// }
cscheid marked this conversation as resolved.
Show resolved Hide resolved
}

// combine user formats
Expand Down
8 changes: 6 additions & 2 deletions src/core/sass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { dartCompile } from "./dart-sass.ts";
import * as ld from "./lodash.ts";
import { lines } from "./text.ts";
import { sassCache } from "./sass/cache.ts";
import { md5HashBytes } from "./hash.ts";

export interface SassVariable {
name: string;
Expand Down Expand Up @@ -119,10 +120,13 @@ export async function compileSass(
...userRules,
].join("\n\n");

const hash = md5HashBytes(new TextEncoder().encode(scssInput));

// Compile the scss
// Note that you can set this to undefined to bypass the cache entirely
const cacheKey = bundles.map((bundle) => bundle.key).join("|") + "-" +
(minified ? "min" : "nomin");
const cacheKey = hash;
// bundles.map((bundle) => bundle.key).join("|") + "-" +
// (minified ? "min" : "nomin");

return await compileWithCache(
scssInput,
Expand Down
5 changes: 4 additions & 1 deletion src/format/dashboard/format-dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@ export function dashboardFormat() {
const formats: Record<string, Metadata> = format.metadata
.format as Record<string, Metadata>;
const htmlFormat = formats["html"];
if (htmlFormat && htmlFormat[kTheme]) {
const dashboardFormat = formats["dashboard"];
if (dashboardFormat && dashboardFormat[kTheme]) {
format.metadata[kTheme] = dashboardFormat[kTheme];
} else if (htmlFormat && htmlFormat[kTheme]) {
cscheid marked this conversation as resolved.
Show resolved Hide resolved
format.metadata[kTheme] = htmlFormat[kTheme];
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/format/reveal/format-reveal-theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { cssHasDarkModeSentinel } from "../../core/pandoc/css.ts";
import { pandocNativeStr } from "../../core/pandoc/codegen.ts";
import { ProjectContext } from "../../project/types.ts";
import { brandRevealSassBundleLayers } from "../../core/sass/brand.ts";
import { md5HashBytes } from "../../core/hash.ts";

export const kRevealLightThemes = [
"white",
Expand Down Expand Up @@ -185,11 +186,14 @@ export async function revealTheme(

// compile sass
const css = await compileSass([bundleLayers, ...brandLayers], temp);
// convert from string to bytes
const hash = md5HashBytes(Deno.readFileSync(css));
const fileName = `quarto-${hash}`;
copyTo(
css,
join(revealDestDir, "dist", "theme", "quarto.css"),
join(revealDestDir, "dist", "theme", `${fileName}.css`),
);
metadata[kTheme] = "quarto";
metadata[kTheme] = fileName;

const highlightingMode: "light" | "dark" =
cssHasDarkModeSentinel(Deno.readTextFileSync(css)) ? "dark" : "light";
Expand Down
6 changes: 4 additions & 2 deletions tests/docs/smoke-all/2024/05/03/9548.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ _quarto:
tests:
revealjs:
ensureHtmlElements:
- ['head > link[rel="stylesheet"][href$="quarto.css"]']
- ['head > link[rel="stylesheet"][href$="quarto-a0be57805c9fbfb2425d641eb5c8e4cc.css"]']
- ['head > link[rel="stylesheet"][href$="beige.css"]']
---

# Revealjs theme handling

User provided theme should be used to build a `quarto.css` using SASS and the `theme: beige` should internally by overridden to `theme: quarto` so that the later is added in Pandoc's template
User provided theme should be used to build a `quarto-a0be57805c9fbfb2425d641eb5c8e4cc.css` using SASS and the `theme: beige` should internally by overridden to `theme: quarto` so that the later is added in Pandoc's template

2024-08-26: `quarto.css` now carries the MD5 hash of the content to allow different revealjs themes to work on the same website.
Loading