This commit is contained in:
konnorrogers
2025-07-14 18:59:36 -04:00
parent 86cc174b1e
commit e35d8d6e2c
8 changed files with 120 additions and 129 deletions

View File

@@ -1,14 +1,14 @@
import { parse as HTMLParse } from 'node-html-parser';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { parse as HTMLParse } from 'node-html-parser';
import { anchorHeadingsPlugin } from './_utils/anchor-headings.js';
import { SimulateWebAwesomeApp } from './_utils/simulate-webawesome-app.js'
import { codeExamplesPlugin } from './_utils/code-examples.js';
import { copyCodePlugin } from './_utils/copy-code.js';
import { currentLink } from './_utils/current-link.js';
import { highlightCodePlugin } from './_utils/highlight-code.js';
import { getComponents } from './_utils/manifest.js';
import { markdown } from './_utils/markdown.js';
import { SimulateWebAwesomeApp } from './_utils/simulate-webawesome-app.js';
// import { formatCodePlugin } from './_utils/format-code.js';
// import litPlugin from '@lit-labs/eleventy-plugin-lit';
import { readFile } from 'fs/promises';
@@ -127,11 +127,11 @@ export default async function (eleventyConfig) {
eleventyConfig.setLibrary('md', markdown);
// Add anchors to headings
eleventyConfig.addTransform("doc-transforms", function (content) {
eleventyConfig.addTransform('doc-transforms', function (content) {
let doc = HTMLParse(content, { blockTextElements: { code: true } });
const plugins = [
anchorHeadingsPlugin({ container: "#content" }),
anchorHeadingsPlugin({ container: '#content' }),
outlinePlugin({
container: '#content',
target: '.outline-links',
@@ -145,14 +145,14 @@ export default async function (eleventyConfig) {
codeExamplesPlugin(),
highlightCodePlugin(),
copyCodePlugin(),
]
];
for (const plugin of plugins) {
plugin.call(this, doc)
plugin.call(this, doc);
}
return doc.toString()
})
return doc.toString();
});
eleventyConfig.addPlugin(
replaceTextPlugin([
@@ -175,8 +175,8 @@ export default async function (eleventyConfig) {
replace: /\[discuss:([0-9]+)\]/gs,
replaceWith: '<a href="https://github.com/shoelace-style/webawesome/discussions/$1" target="_blank">#$1</a>',
},
])
)
]),
);
// Build the search index
eleventyConfig.addPlugin(
@@ -203,7 +203,6 @@ export default async function (eleventyConfig) {
eleventyConfig.addPassthroughCopy(glob);
}
// // SSR plugin
// if (!isDev) {
// //
@@ -241,7 +240,7 @@ export default async function (eleventyConfig) {
}
/** This largely mimics what an app would do and just stubs out what we don't care about. */
return SimulateWebAwesomeApp(content)
return SimulateWebAwesomeApp(content);
});
}
}

View File

@@ -1,8 +1,8 @@
import { parse } from 'node-html-parser';
import { v4 as uuid } from 'uuid';
import { markdown } from '../_utils/markdown.js';
import { highlightCode } from './highlight-code.js';
import { copyCode, copyCodePlugin } from './copy-code.js';
import { highlightCode } from './highlight-code.js';
/**
* Eleventy plugin to turn `<code class="example">` blocks into live examples.
@@ -14,35 +14,34 @@ export function codeExamplesPlugin(options = {}) {
};
return function (doc) {
const container = doc.querySelector(options.container);
const container = doc.querySelector(options.container);
if (!container) {
return;
}
if (!container) {
return;
}
// Look for external links
container.querySelectorAll('code.example').forEach(code => {
let pre = code.closest('pre');
const hasButtons = !code.classList.contains('no-buttons');
const isOpen = code.classList.contains('open') || !hasButtons;
const noEdit = code.classList.contains('no-edit');
const id = `code-example-${uuid().slice(-12)}`;
let preview = pre.textContent;
// Look for external links
container.querySelectorAll('code.example').forEach(code => {
let pre = code.closest('pre');
const hasButtons = !code.classList.contains('no-buttons');
const isOpen = code.classList.contains('open') || !hasButtons;
const noEdit = code.classList.contains('no-edit');
const id = `code-example-${uuid().slice(-12)}`;
let preview = pre.textContent;
const langClass = [...code.classList.values()].find(val => val.startsWith('language-'));
const lang = langClass ? langClass.replace(/^language-/, '') : 'plain';
const langClass = [...code.classList.values()].find(val => val.startsWith('language-'));
const lang = langClass ? langClass.replace(/^language-/, '') : 'plain';
code.innerHTML = highlightCode(code.textContent ?? '', lang);
code.innerHTML = highlightCode(code.textContent ?? '', lang);
// Run preview scripts as modules to prevent collisions
const root = parse(preview, { blockTextElements: { script: true } });
root.querySelectorAll('script').forEach(script => script.setAttribute('type', 'module'));
preview = root.toString();
// Run preview scripts as modules to prevent collisions
const root = parse(preview, { blockTextElements: { script: true } });
root.querySelectorAll('script').forEach(script => script.setAttribute('type', 'module'));
preview = root.toString();
copyCode(code);
copyCode(code)
const codeExample = parse(`
const codeExample = parse(`
<div class="code-example ${isOpen ? 'open' : ''}">
<div class="code-example-preview">
${preview}
@@ -85,7 +84,7 @@ export function codeExamplesPlugin(options = {}) {
</div>
`);
pre.replaceWith(codeExample);
});
}
pre.replaceWith(codeExample);
});
};
}

View File

@@ -1,4 +1,4 @@
export function copyCode (code) {
export function copyCode(code) {
const pre = code.closest('pre');
let preId = pre.getAttribute('id') || `code-block-${crypto.randomUUID()}`;
let codeId = code.getAttribute('id') || `${preId}-inner`;
@@ -24,7 +24,7 @@ export function copyCodePlugin(options = {}) {
};
return function (doc) {
const container = doc.querySelector(options.container)
const container = doc.querySelector(options.container);
if (!container) {
return;
@@ -32,7 +32,7 @@ export function copyCodePlugin(options = {}) {
// Look for code blocks
container.querySelectorAll('pre > code').forEach(code => {
copyCode(code)
copyCode(code);
});
}
};
}

View File

@@ -19,46 +19,46 @@ export function outlinePlugin(options = {}) {
};
return function (doc) {
const container = doc.querySelector(options.container);
const ul = parse('<ul></ul>');
let numLinks = 0;
const container = doc.querySelector(options.container);
const ul = parse('<ul></ul>');
let numLinks = 0;
if (!container) {
if (!container) {
return;
}
container.querySelectorAll(options.selector).forEach(heading => {
const id = heading.getAttribute('id');
const level = heading.tagName.slice(1);
const clone = parse(heading.outerHTML);
if (heading.closest('[data-no-outline]')) {
return;
}
container.querySelectorAll(options.selector).forEach(heading => {
const id = heading.getAttribute('id');
const level = heading.tagName.slice(1);
const clone = parse(heading.outerHTML);
// Create a clone of the heading so we can remove links and [data-no-outline] elements from the text content
clone.querySelectorAll('.wa-visually-hidden, [hidden], [aria-hidden="true"]').forEach(el => el.remove());
clone.querySelectorAll('[data-no-outline]').forEach(el => el.remove());
if (heading.closest('[data-no-outline]')) {
return;
}
// Generate the link
const li = parse(`<li data-level="${level}"><a></a></li>`);
const a = li.querySelector('a');
a.setAttribute('href', `#${encodeURIComponent(id)}`);
a.textContent = clone.textContent.trim().replace(/#$/, '');
// Create a clone of the heading so we can remove links and [data-no-outline] elements from the text content
clone.querySelectorAll('.wa-visually-hidden, [hidden], [aria-hidden="true"]').forEach(el => el.remove());
clone.querySelectorAll('[data-no-outline]').forEach(el => el.remove());
// Add it to the list
ul.firstChild.appendChild(li);
numLinks++;
});
// Generate the link
const li = parse(`<li data-level="${level}"><a></a></li>`);
const a = li.querySelector('a');
a.setAttribute('href', `#${encodeURIComponent(id)}`);
a.textContent = clone.textContent.trim().replace(/#$/, '');
// Add it to the list
ul.firstChild.appendChild(li);
numLinks++;
if (numLinks > 0) {
// Append the list to all matching targets
doc.querySelectorAll(options.target).forEach(target => {
target.appendChild(parse(ul.outerHTML));
});
if (numLinks > 0) {
// Append the list to all matching targets
doc.querySelectorAll(options.target).forEach(target => {
target.appendChild(parse(ul.outerHTML));
});
} else {
// Remove if empty
options.ifEmpty(doc);
}
} else {
// Remove if empty
options.ifEmpty(doc);
}
};
}

View File

@@ -31,7 +31,7 @@ export function searchPlugin(options = {}) {
eleventyConfig.addPreprocessor('exclude-unlisted-from-search', '*', function (data, content) {
if (data.unlisted) {
// no-op
pagesToIndex.delete(data.page.inputPath)
pagesToIndex.delete(data.page.inputPath);
} else {
pagesToIndex.set(data.page.inputPath, true);
}
@@ -39,7 +39,6 @@ export function searchPlugin(options = {}) {
return content;
});
// With incremental builds we need this to be last in case stuff was added from metadata. _BUT_ in incremental builds, not every page is added to the "transform".
eleventyConfig.addTransform('search', function (content) {
if (!pagesToIndex.has(this.page.inputPath)) {
@@ -77,24 +76,24 @@ export function searchPlugin(options = {}) {
const outputFilename = path.resolve(join(output, 'search.json'));
const cachedPages = path.resolve(join(output, 'cached_pages.json'));
function getCachedPages () {
let content = {pages: []}
function getCachedPages() {
let content = { pages: [] };
try {
content = JSON.parse(readFileSync(cachedPages))
content = JSON.parse(readFileSync(cachedPages));
} catch (e) {}
const cachedPagesMap = new Map(content.pages)
const cachedPagesMap = new Map(content.pages);
for (const [key, value] of cachedPagesMap.entries()) {
// A page uses a cached value if `true` and it didnt get its value set in the "transform" hook. This is to get around the limitation of incremental builds not going over every file in transform.
if (pagesToIndex.get(key) === true) {
pagesToIndex.set(key, value)
pagesToIndex.set(key, value);
}
}
}
const map = [];
getCachedPages()
getCachedPages();
const searchIndex = lunr(function () {
let index = 0;
@@ -112,7 +111,7 @@ export function searchPlugin(options = {}) {
await mkdir(dirname(outputFilename), { recursive: true });
await writeFile(outputFilename, JSON.stringify({ searchIndex, map }), 'utf-8');
await writeFile(cachedPages, JSON.stringify({ pages: [...pagesToIndex.entries()] }, null, 2))
await writeFile(cachedPages, JSON.stringify({ pages: [...pagesToIndex.entries()] }, null, 2));
});
};
}

View File

@@ -1,6 +1,6 @@
import nunjucks from 'nunjucks';
export function SimulateWebAwesomeApp (str) {
export function SimulateWebAwesomeApp(str) {
return nunjucks.renderString(str, {
// Stub the server EJS shortcodes.
currentUser: {

View File

@@ -5,17 +5,17 @@ import { deleteAsync } from 'del';
import esbuild from 'esbuild';
import { replace } from 'esbuild-plugin-replace';
import Eleventy from '@11ty/eleventy';
import { mkdir, readFile } from 'fs/promises';
import getPort, { portNumbers } from 'get-port';
import { globby } from 'globby';
import { dirname, join, relative, extname } from 'node:path';
import { dirname, extname, join, relative } from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import ora from 'ora';
import copy from 'recursive-copy';
import { getCdnDir, getDistDir, getDocsDir, getRootDir, getSiteDir, getEleventyConfigPath } from './utils.js';
import Eleventy from '@11ty/eleventy';
import { SimulateWebAwesomeApp } from '../docs/_utils/simulate-webawesome-app.js';
import { getCdnDir, getDistDir, getDocsDir, getEleventyConfigPath, getRootDir, getSiteDir } from './utils.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const isDeveloping = process.argv.includes('--develop');
@@ -27,41 +27,41 @@ let buildContexts = {
unbundledContext: {},
};
const debugPerf = process.env.DEBUG_PERFORMANCE === "1"
const debugPerf = process.env.DEBUG_PERFORMANCE === '1';
const isIncremental = process.argv.includes('--incremental')
const isIncremental = process.argv.includes('--incremental');
// 11ty
async function createEleventy () {
async function createEleventy() {
const eleventy = new Eleventy(getDocsDir(), getSiteDir(), {
quietMode: true,
configPath: getEleventyConfigPath(),
config: (eleventyConfig) => {
config: eleventyConfig => {
if (isDeveloping || isIncremental) {
eleventyConfig.setUseTemplateCache(false)
eleventyConfig.setUseTemplateCache(false);
}
},
source: "script",
source: 'script',
runMode: isIncremental ? 'watch' : 'build',
});
eleventy.setIncrementalBuild(isIncremental)
eleventy.setIncrementalBuild(isIncremental);
await eleventy.init()
await eleventy.init();
if (isIncremental) {
await eleventy.watch();
await eleventy.watch();
process.on("SIGINT", async () => {
await eleventy.stopWatch();
process.exitCode = 0;
});
process.on('SIGINT', async () => {
await eleventy.stopWatch();
process.exitCode = 0;
});
}
return eleventy
return eleventy;
}
// We can't initialize 11ty here because we need to wait for the `/dist` build to execute so we can read the custom-elements.json.
let eleventy = null
let eleventy = null;
/**
* @typedef {Object} BuildOptions
@@ -82,8 +82,7 @@ export async function build(options = {}) {
options.watchedDocsDirectories = [getDocsDir()];
}
function measureStep () {
}
function measureStep() {}
/**
* Runs the full build.
@@ -92,22 +91,16 @@ export async function build(options = {}) {
const start = Date.now();
try {
const steps = [
cleanup,
generateManifest,
generateReactWrappers,
generateTypes,
generateStyles
]
const steps = [cleanup, generateManifest, generateReactWrappers, generateTypes, generateStyles];
for (const step of steps) {
if (debugPerf) {
const stepStart = Date.now()
await step()
const stepStart = Date.now();
await step();
const elapsedTime = (Date.now() - stepStart) / 1000 + 's';
spinner.succeed(`${step.name}: ${elapsedTime}`)
spinner.succeed(`${step.name}: ${elapsedTime}`);
} else {
await step()
await step();
}
}
@@ -326,9 +319,9 @@ export async function build(options = {}) {
spinner.start('Writing the docs');
if (isIncremental) {
eleventy ||= await createEleventy()
eleventy ||= await createEleventy();
} else {
eleventy = await createEleventy()
eleventy = await createEleventy();
}
try {
@@ -408,18 +401,20 @@ export async function build(options = {}) {
const finalString = [];
const encoding = 'utf-8';
if (!next) { return }
if (!req.url) {
next()
return
if (!next) {
return;
}
const extension = extname(req.url)
if (extension !== "" && extension !== '.html') {
if (!req.url) {
next();
return;
}
const extension = extname(req.url);
if (extension !== '' && extension !== '.html') {
// Assume its something like .svg / .png / .css etc. that we don't want to transform.
next()
return
next();
return;
}
const _write = res.write;
@@ -431,7 +426,7 @@ export async function build(options = {}) {
const _end = res.end;
res.end = function (...args) {
const transformedStr = SimulateWebAwesomeApp(finalString.join(""))
const transformedStr = SimulateWebAwesomeApp(finalString.join(''));
_write.call(res, transformedStr, encoding);
_end.call(res, ...args);
};

View File

@@ -73,7 +73,6 @@
}
}
/* Bounce attention */
:host([attention='bounce']) {
animation: bounce 1s cubic-bezier(0.28, 0.84, 0.42, 1) infinite;