Skip to content

Commit

Permalink
refactor(reference): generate HTML files with the deno/doc jsr packag…
Browse files Browse the repository at this point in the history
…e instead of CLI (#1170)

Co-authored-by: Josh Collinsworth <joshuajcollinsworth@gmail.com>
  • Loading branch information
crowlKats and josh-collinsworth authored Nov 21, 2024
1 parent afe9ad6 commit 1c2fae3
Show file tree
Hide file tree
Showing 23 changed files with 866 additions and 138 deletions.
8 changes: 1 addition & 7 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@ jobs:
- name: Set up Deno
uses: denoland/setup-deno@v2

- name: Set up Deno
uses: denoland/setup-deno@v2
with:
deno-version: 2.0.5
deno-binary-name: "deno_doc"

- name: "Reference: install"
working-directory: "reference_gen"
run: deno install
Expand All @@ -36,7 +30,7 @@ jobs:

- name: "Reference: generate docs"
working-directory: "reference_gen"
run: deno_doc task doc
run: deno task doc

- name: Build
env:
Expand Down
18 changes: 4 additions & 14 deletions _config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,14 @@ import checkUrls from "lume/plugins/check_urls.ts";
import esbuild from "lume/plugins/esbuild.ts";
import jsx from "lume/plugins/jsx_preact.ts";
import postcss from "lume/plugins/postcss.ts";
import prism from "lume/plugins/prism.ts";
import redirects from "lume/plugins/redirects.ts";
import search from "lume/plugins/search.ts";
import sitemap from "lume/plugins/sitemap.ts";

import tw from "tailwindcss";
import tailwindConfig from "./tailwind.config.js";

import Prism from "npm:prismjs@1.29.0";
import "npm:prismjs@1.29.0/components/prism-bash.js";
import "npm:prismjs@1.29.0/components/prism-diff.js";
import "npm:prismjs@1.29.0/components/prism-json.js";
import "npm:prismjs@1.29.0/components/prism-json5.js";
import "npm:prismjs@1.29.0/components/prism-typescript.js";

Prism.languages.jsonc = Prism.languages.json5;
import Prism from "./prism.ts";

import title from "https://deno.land/x/lume_markdown_plugins@v0.7.0/title.ts";
import toc from "https://deno.land/x/lume_markdown_plugins@v0.7.0/toc.ts";
Expand Down Expand Up @@ -152,14 +144,12 @@ site.process([".html"], (pages) => {
const document = page.document!;
if (!document.querySelector(".ddoc")) {
document.body.classList.add("apply-prism");
document.querySelectorAll("body.apply-prism pre code").forEach((
element,
) => Prism.highlightElement(element));
}
}
});
site.use(
prism({
cssSelector: "body.apply-prism pre code",
}),
);

site.use(toc({ anchor: false }));
site.use(title());
Expand Down
16 changes: 10 additions & 6 deletions overrides.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
@apply max-w-[75ch] !important;
}

.ddoc .markdown_summary {
.ddoc .markdown-summary {
@apply text-foreground-secondary !important;
}

Expand All @@ -29,11 +29,11 @@
@apply px-4 !important;
}

.ddoc .markdown .highlight {
.ddoc .markdown-body .highlight {
@apply -mx-4 !important;
}

.ddoc .markdown pre > code:first-child {
.ddoc .markdown-body pre > code:first-child {
@apply px-4 !important;
}
}
Expand Down Expand Up @@ -69,14 +69,18 @@
.ddoc .usageContent {
@apply bg-primary/5 border-primary/25 max-w-[75ch] py-4 mt-6 relative
!important;

> div {
color: inherit !important;
}
}

/* Adjust spacing for ref pages that don't have a header above usage */
.ddoc .usageContent:first-of-type {
@apply mt-1 !important;
}

.ddoc .usageContent > div.markdown > pre.highlight {
.ddoc .usageContent > div.markdown-body > pre.highlight {
@apply border-primary/25 !important;
}

Expand All @@ -90,7 +94,7 @@
color: inherit;
}

.ddoc .usageContent > .markdown {
.ddoc .usageContent > .markdown-body {
@apply max-w-full !important;
}

Expand All @@ -107,6 +111,6 @@
@apply mt-0 !important;
}

.ddoc .space-y-7 > .section .markdown {
.ddoc .space-y-7 > .section .markdown-body {
@apply mb-6 !important;
}
10 changes: 10 additions & 0 deletions prism.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Prism from "npm:prismjs@1.29.0";
import "npm:prismjs@1.29.0/components/prism-typescript.js";
import "npm:prismjs@1.29.0/components/prism-diff.js";
import "npm:prismjs@1.29.0/components/prism-json.js";
import "npm:prismjs@1.29.0/components/prism-bash.js";
import "npm:prismjs@1.29.0/components/prism-json5.js";

Prism.languages.jsonc = Prism.languages.json5;

export default Prism;
2 changes: 1 addition & 1 deletion reference.page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import entityList from "@std/html/named-entity-list.json" with { type: "json" };
export const layout = "raw.tsx";

const resetRegexp =
/<link id="ddocResetStylesheet" rel="stylesheet" href=".*?reset\.css">\s+/;
/<link id="ddocResetStylesheet" rel="stylesheet" href=".*?reset\.css">\s*/;
const titleRegexp = /<title>(.+?)<\/title>\s*/s;

export default function* () {
Expand Down
198 changes: 198 additions & 0 deletions reference_gen/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import type { HrefResolver, ShortPath } from "@deno/doc";
import { dirname, join } from "@std/path";
import markdownit from "markdown-it";
import Prism from "../prism.ts";

import admonitionPlugin from "../markdown-it/admonition.ts";
import codeblockCopyPlugin from "../markdown-it/codeblock-copy.ts";

const titleOnlyAllowedTypes = new Set([
"inline",
"paragraph_open",
"paragraph_close",
"heading_open",
"heading_close",
"text",
"code_inline",
"html_inline",
"em_open",
"em_close",
"strong_open",
"strong_close",
"s_open",
"s_close",
"sup_open",
"sup_close",
"link_open",
"link_close",
"math_inline",
"softbreak",
"underline_open",
"underline_close",
]);

function walkTitleTokens(tokens) {
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];

if (titleOnlyAllowedTypes.has(token.type)) {
if (token.children) {
walkTitleTokens(token.children);
}
} else {
tokens.splice(i, 1);
}
}
}

function titleOnlyPlugin(md) {
md.core.ruler.push("titleOnly", function titleOnly(state) {
walkTitleTokens(state.tokens);

const paragraphEnd = state.tokens.findIndex((token) =>
token.type === "paragraph_close"
);

if (paragraphEnd !== -1) {
state.tokens.splice(paragraphEnd);
}

if (
state.tokens.length === 1 && state.tokens[0].type === "paragraph_open"
) {
state.tokens = [];
}
});
}

function createAnchorizePlugin(
anchorizer: (content: string, depthLevel: number) => string,
) {
return function anchorizePlugin(md) {
md.core.ruler.push("anchorize", function anchorize(state) {
const tokens = state.tokens;

for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];

if (token.type === "heading_open") {
const level = parseInt(token.tag.substring(1));

const nextToken = tokens[i + 1];

if (nextToken && nextToken.type === "inline") {
const content = nextToken.children
.filter((t) => t.type === "text" || t.type === "code_inline")
.map((t) => t.content)
.join("");

const id = anchorizer(content, level);
token.attrSet("id", id);
}
}
}
});
};
}

function createRenderer(
anchorizer: (content: string, depthLevel: number) => string,
) {
return markdownit({
html: true,
linkify: true,
langPrefix: "highlight notranslate language-",
highlight(content: string, lang: string) {
if (Prism.languages[lang]) {
return Prism.highlight(content, Prism.languages[lang], lang);
} else {
return "";
}
},
})
.disable("code")
.use(admonitionPlugin)
.use(codeblockCopyPlugin)
.use(createAnchorizePlugin(anchorizer));
}

export function renderMarkdown(
md: string,
titleOnly: boolean,
_filePath: ShortPath | undefined,
anchorizer: (content: string, depthLevel: number) => string,
): string | undefined {
const renderer = createRenderer(anchorizer);
if (titleOnly) {
const titleOnlyRenderer = renderer.use(titleOnlyPlugin);
const parsed = titleOnlyRenderer.parse(md, {});
if (parsed.length === 0) {
return undefined;
}

return `<div data-color-mode="dark" data-light-theme="light" data-dark-theme="dark" class="markdown-body markdown-summary">${
titleOnlyRenderer.renderer.render(parsed, titleOnlyRenderer.options, {})
}</div>`;
} else {
return `<div data-color-mode="dark" data-light-theme="light" data-dark-theme="dark" class="markdown-body">${
renderer.render(md)
}</div>`;
}
}

function strip(tokens) {
let out = "";

for (const token of tokens) {
if (token.type === "text" || token.type === "code_inline") {
out += token.content;
} else if (token.type === "fence") {
out += token.content + "\n";
} else if (token.type === "softbreak") {
out += "\n";
} else if (
token.type === "heading_close" || token.type === "paragraph_close"
) {
out += "\n\n";
} else if (token.children && token.children.length) {
out += strip(token.children);
}
}

return out;
}

export function stripMarkdown(md: string): string {
const renderer = createRenderer(() => "");
const tokens = renderer.parse(md, {});
return strip(tokens);
}

export const hrefResolver: HrefResolver = {
resolvePath(_current, _target, defaultResolve) {
let path = defaultResolve();

if (path.endsWith("index.html")) {
path = path.slice(0, -"index.html".length);
} else if (path.endsWith(".html")) {
path = path.slice(0, -".html".length);
}

return path;
},
};

export async function writeFiles(root: string, files: Record<string, string>) {
await Deno.remove(root, { recursive: true });

await Promise.all(
Object.entries(files).map(async ([path, content]) => {
const joined = join(root, path);

await Deno.mkdir(dirname(joined), { recursive: true });
await Deno.writeTextFile(joined, content);
}),
);

console.log(`Written ${Object.keys(files).length} files`);
}
2 changes: 1 addition & 1 deletion reference_gen/deno-categories.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"Runtime": "System-related functionality, process management, and observability. \n\nEg {@linkcode Deno.mainModule}, {@linkcode Deno.exit}, {@linkcode Deno.cwd}",
"Sub Process": "Spawn and manage child processes, execute commands, and collect output. Useful for executing external programs from Deno.\n\nEg {@linkcode Deno.Command}",
"Testing": "Robust testing and benchmarking capabilities to ensure code quality and performance.\n\nEg {@linkcode Deno.test}, {@linkcode Deno.bench}",
"Web Sockets": "Enable real-time communication between clients and servers using WebSockets. Tools to create interactive and dynamic applications.\n\nEg {@linkcode WebSocket}"
"WebSockets": "Enable real-time communication between clients and servers using WebSockets. Tools to create interactive and dynamic applications.\n\nEg {@linkcode WebSocket}"
}
33 changes: 33 additions & 0 deletions reference_gen/deno-doc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { doc, generateHtml } from "@deno/doc";
import {
hrefResolver,
renderMarkdown,
stripMarkdown,
writeFiles,
} from "./common.ts";
import categoryDocs from "./deno-categories.json" with { type: "json" };

const url = import.meta.resolve("./types/deno.d.ts");

console.log("Generating doc nodes...");

const nodes = await doc(url, { includeAll: true });

console.log("Generating html files...");

const files = await generateHtml({ [url]: nodes }, {
packageName: "Deno",
categoryDocs,
disableSearch: true,
hrefResolver,
usageComposer: {
singleMode: true,
compose(_currentResolve, _usageToMd) {
return new Map();
},
},
markdownRenderer: renderMarkdown,
markdownStripper: stripMarkdown,
});

await writeFiles("./gen/deno", files);
File renamed without changes.
Loading

0 comments on commit 1c2fae3

Please sign in to comment.