Skip to content

hoist css external @imports #467

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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
36 changes: 23 additions & 13 deletions src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ export interface BuildEffects {
writeFile(outputPath: string, contents: Buffer | string): Promise<void>;
}

function hoistCssImports(style) {
const hoisted = style.match(/^((?:@import .*?;\n)*)/)?.[0] ?? "";
return {hoisted, code: style.slice(hoisted.length)};
}

export async function build(
{config, addPublic = true, clientEntry = "./src/client/index.js"}: BuildOptions,
effects: BuildEffects = new FileBuildEffects(config.output)
Expand All @@ -72,7 +77,9 @@ export async function build(
// Render .md files, building a list of file attachments as we go.
const files: string[] = [];
const imports: string[] = [];
const styles: Style[] = [];

// TODO InternSet?
const styles: {style: Style; path: string; code: string}[] = [];
for await (const sourceFile of visitMarkdownFiles(root)) {
const sourcePath = join(root, sourceFile);
const outputPath = join(dirname(sourceFile), basename(sourceFile, ".md") + ".html");
Expand All @@ -82,12 +89,20 @@ export async function build(
const resolveFile = ({name}) => resolvePath(sourceFile, name);
files.push(...render.files.map(resolveFile));
imports.push(...render.imports.filter((i) => i.type === "local").map(resolveFile));
await effects.writeFile(outputPath, render.html);
const style = mergeStyle(path, render.data?.style, render.data?.theme, config.style);
if (style) {
if ("path" in style) style.path = resolvePath(sourceFile, style.path);
if (!styles.some((s) => styleEquals(s, style))) styles.push(style);
if (!styles.some((s) => styleEquals(s.style, style))) {
styles.push({
style,
path: "path" in style ? style.path : `theme-${style.theme}.css`,
...hoistCssImports(
await bundleStyles("path" in style ? {path: join(root, style.path)} : {theme: style.theme})
)
});
}
}
await effects.writeFile(outputPath, render.html);
}

// Generate the client bundles.
Expand All @@ -99,18 +114,13 @@ export async function build(
const code = await rollupClient(clientPath, {minify: true});
await effects.writeFile(outputPath, code);
}
for (const style of styles) {
for (const {style, code, path} of styles) {
if ("path" in style) {
const outputPath = join("_import", style.path);
const sourcePath = join(root, style.path);
effects.output.write(`${faint("bundle")} ${sourcePath} ${faint("→")} `);
const code = await bundleStyles({path: sourcePath});
await effects.writeFile(outputPath, code);
effects.output.write(`${faint("bundle")} ${join(root, path)} ${faint("→")} `);
await effects.writeFile(join("_import", path), code);
} else {
const outputPath = join("_observablehq", `theme-${style.theme}.css`);
effects.output.write(`${faint("bundle")} theme-${style.theme}.css ${faint("→")} `);
const code = await bundleStyles({theme: style.theme});
await effects.writeFile(outputPath, code);
effects.output.write(`${faint("bundle")} ${path} ${faint("→")} `);
await effects.writeFile(join("_observablehq", path), code);
}
}
}
Expand Down
10 changes: 7 additions & 3 deletions src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {addImplicitSpecifiers, addImplicitStylesheets} from "./libraries.js";
import {type ParseResult, parseMarkdown} from "./markdown.js";
import {type PageLink, findLink, normalizePath} from "./pager.js";
import {getPreviewStylesheet} from "./preview.js";
import {getClientPath, rollupClient} from "./rollup.js";
import {getClientPath, hoistStyleImport, rollupClient} from "./rollup.js";
import {relativeUrl} from "./url.js";

export interface Render {
Expand Down Expand Up @@ -180,7 +180,7 @@ async function renderLinks(
path: string,
resolver: ImportResolver
): Promise<Html> {
const stylesheets = new Set<string>(["https://fonts.googleapis.com/css2?family=Source+Serif+Pro:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap"]); // prettier-ignore
const stylesheets = new Set<string>();
const style = getPreviewStylesheet(path, parseResult.data, options.style);
if (style) stylesheets.add(style);
const specifiers = new Set<string>(["npm:@observablehq/runtime", "npm:@observablehq/stdlib"]);
Expand All @@ -191,7 +191,11 @@ async function renderLinks(
const preloads = new Set<string>([relativeUrl(path, "/_observablehq/client.js")]);
for (const specifier of specifiers) preloads.add(await resolver(path, specifier));
await resolveModulePreloads(preloads);
return html`<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>${
// <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
// "https://fonts.googleapis.com/css2?family=Source+Serif+Pro:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap"
return html`
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
${
Array.from(stylesheets).sort().map(renderStylesheetPreload) // <link rel=preload as=style>
}${
Array.from(stylesheets).sort().map(renderStylesheet) // <link rel=stylesheet>
Expand Down
7 changes: 7 additions & 0 deletions src/rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,16 @@ export async function bundleStyles({path, theme}: {path?: string; theme?: string
write: false,
alias: STYLE_MODULES
});
console.warn(result.outputFiles[0].text.slice(0, 100));
return result.outputFiles[0].text;
}

export async function hoistStyleImport(cf): Promise<string[]> {
const styles = Array.from((await bundleStyles(cf)).matchAll(/^@import .*/), ([match]) => match);
console.warn({styles});
return styles;
}

export async function rollupClient(clientPath: string, {minify = false} = {}): Promise<string> {
const bundle = await rollup({
input: clientPath,
Expand Down
1 change: 1 addition & 0 deletions src/style/default.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@import url("https://fonts.googleapis.com/css2?family=Source+Serif+Pro:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap");
@import url("./global.css");
@import url("./layout.css");
@import url("./grid.css");
Expand Down