Skip to content

Commit 8be2eea

Browse files
authored
Merge branch 'next' into card-image-rounding
2 parents c328671 + c9e6895 commit 8be2eea

24 files changed

+717
-406
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/webawesome/docs/.eleventy.js

Lines changed: 91 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,63 @@
1+
import { parse as HTMLParse } from 'node-html-parser';
12
import * as fs from 'node:fs';
23
import * as path from 'node:path';
3-
import { parse } from 'path';
4-
import { anchorHeadingsPlugin } from './_utils/anchor-headings.js';
5-
import { codeExamplesPlugin } from './_utils/code-examples.js';
6-
import { copyCodePlugin } from './_utils/copy-code.js';
7-
import { currentLink } from './_utils/current-link.js';
8-
import { highlightCodePlugin } from './_utils/highlight-code.js';
4+
import { anchorHeadingsTransformer } from './_transformers/anchor-headings.js';
5+
import { codeExamplesTransformer } from './_transformers/code-examples.js';
6+
import { copyCodeTransformer } from './_transformers/copy-code.js';
7+
import { currentLinkTransformer } from './_transformers/current-link.js';
8+
import { highlightCodeTransformer } from './_transformers/highlight-code.js';
9+
import { outlineTransformer } from './_transformers/outline.js';
910
import { getComponents } from './_utils/manifest.js';
1011
import { markdown } from './_utils/markdown.js';
11-
// import { formatCodePlugin } from './_utils/format-code.js';
12+
import { SimulateWebAwesomeApp } from './_utils/simulate-webawesome-app.js';
13+
// import { formatCodePlugin } from './_plugins/format-code.js';
1214
// import litPlugin from '@lit-labs/eleventy-plugin-lit';
1315
import { readFile } from 'fs/promises';
14-
import nunjucks from 'nunjucks';
1516
import process from 'process';
1617
import * as url from 'url';
17-
import { outlinePlugin } from './_utils/outline.js';
18-
import { replaceTextPlugin } from './_utils/replace-text.js';
19-
import { searchPlugin } from './_utils/search.js';
18+
import { replaceTextPlugin } from './_plugins/replace-text.js';
19+
import { searchPlugin } from './_plugins/search.js';
2020
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
2121
const isDev = process.argv.includes('--develop');
2222
const passThroughExtensions = ['js', 'css', 'png', 'svg', 'jpg', 'mp4'];
2323

24+
async function getPackageData() {
25+
return JSON.parse(await readFile(path.join(__dirname, '..', 'package.json'), 'utf-8'));
26+
}
27+
2428
export default async function (eleventyConfig) {
25-
const packageData = JSON.parse(await readFile(path.join(__dirname, '..', 'package.json'), 'utf-8'));
2629
const docsDir = path.join(process.env.BASE_DIR || '.', 'docs');
27-
const allComponents = getComponents();
30+
let packageData = await getPackageData();
31+
let allComponents = getComponents();
32+
33+
const distDir = process.env.UNBUNDLED_DIST_DIRECTORY || path.resolve(__dirname, '../dist');
34+
const customElementsManifest = path.join(distDir, 'custom-elements.json');
35+
const stylesheets = path.join(distDir, 'styles');
36+
37+
eleventyConfig.addWatchTarget(customElementsManifest);
38+
eleventyConfig.setWatchThrottleWaitTime(10); // in milliseconds
39+
40+
eleventyConfig.on('eleventy.beforeWatch', async function (changedFiles) {
41+
let updatePackageData = false;
42+
let updateComponentData = false;
43+
changedFiles.forEach(file => {
44+
if (file.includes('package.json')) {
45+
updatePackageData = true;
46+
}
47+
48+
if (file.includes('custom-elements.json')) {
49+
updateComponentData = true;
50+
}
51+
});
52+
53+
if (updatePackageData) {
54+
packageData = await getPackageData();
55+
}
56+
57+
if (updateComponentData) {
58+
allComponents = getComponents();
59+
}
60+
});
2861

2962
/**
3063
* If you plan to add or remove any of these extensions, make sure to let either Konnor or Cory know as these
@@ -52,7 +85,7 @@ export default async function (eleventyConfig) {
5285
// Template filters - {{ content | filter }}
5386
eleventyConfig.addFilter('inlineMarkdown', content => markdown.renderInline(content || ''));
5487
eleventyConfig.addFilter('markdown', content => markdown.render(content || ''));
55-
eleventyConfig.addFilter('stripExtension', string => parse(string + '').name);
88+
eleventyConfig.addFilter('stripExtension', string => path.parse(string + '').name);
5689
eleventyConfig.addFilter('stripPrefix', content => content.replace(/^wa-/, ''));
5790
// Trims whitespace and pipes from the start and end of a string. Useful for CEM types, which can be pipe-delimited.
5891
// With Prettier 3, this means a leading pipe will exist be present when the line wraps.
@@ -109,31 +142,6 @@ export default async function (eleventyConfig) {
109142
return '';
110143
});
111144

112-
eleventyConfig.addTransform('second-nunjucks-transform', function NunjucksTransform(content) {
113-
// For a server build, we expect a server to run the second transform.
114-
if (serverBuild) {
115-
return content;
116-
}
117-
118-
// Only run the transform on files nunjucks would transform.
119-
if (!this.page.inputPath.match(/.(md|html|njk)$/)) {
120-
return content;
121-
}
122-
123-
/** This largely mimics what an app would do and just stubs out what we don't care about. */
124-
return nunjucks.renderString(content, {
125-
// Stub the server EJS shortcodes.
126-
currentUser: {
127-
hasPro: false,
128-
},
129-
server: {
130-
head: '',
131-
loginOrAvatar: '',
132-
flashes: '',
133-
},
134-
});
135-
});
136-
137145
// Paired shortcodes - {% shortCode %}content{% endShortCode %}
138146
eleventyConfig.addPairedShortcode('markdown', content => markdown.render(content || ''));
139147

@@ -152,33 +160,33 @@ export default async function (eleventyConfig) {
152160
eleventyConfig.setLibrary('md', markdown);
153161

154162
// Add anchors to headings
155-
eleventyConfig.addPlugin(anchorHeadingsPlugin({ container: '#content' }));
156-
157-
// Add an outline to the page
158-
eleventyConfig.addPlugin(
159-
outlinePlugin({
160-
container: '#content',
161-
target: '.outline-links',
162-
selector: 'h2, h3',
163-
ifEmpty: doc => {
164-
doc.querySelector('#outline')?.remove();
165-
},
166-
}),
167-
);
168-
169-
// Add current link classes
170-
eleventyConfig.addPlugin(currentLink());
171-
172-
// Add code examples for `<code class="example">` blocks
173-
eleventyConfig.addPlugin(codeExamplesPlugin());
174-
175-
// Highlight code blocks with Prism
176-
eleventyConfig.addPlugin(highlightCodePlugin());
163+
eleventyConfig.addTransform('doc-transforms', function (content) {
164+
let doc = HTMLParse(content, { blockTextElements: { code: true } });
165+
166+
const transformers = [
167+
anchorHeadingsTransformer({ container: '#content' }),
168+
outlineTransformer({
169+
container: '#content',
170+
target: '.outline-links',
171+
selector: 'h2, h3',
172+
ifEmpty: doc => {
173+
doc.querySelector('#outline')?.remove();
174+
},
175+
}),
176+
// Add current link classes
177+
currentLinkTransformer(),
178+
codeExamplesTransformer(),
179+
highlightCodeTransformer(),
180+
copyCodeTransformer(),
181+
];
182+
183+
for (const transformer of transformers) {
184+
transformer.call(this, doc);
185+
}
177186

178-
// Add copy code buttons to code blocks
179-
eleventyConfig.addPlugin(copyCodePlugin);
187+
return doc.toString();
188+
});
180189

181-
// Various text replacements
182190
eleventyConfig.addPlugin(
183191
replaceTextPlugin([
184192
{
@@ -222,15 +230,14 @@ export default async function (eleventyConfig) {
222230
// }
223231

224232
let assetsDir = path.join(process.env.BASE_DIR || 'docs', 'assets');
225-
fs.cpSync(assetsDir, path.join(eleventyConfig.directories.output, 'assets'), { recursive: true });
233+
const siteAssetsDir = path.join(eleventyConfig.directories.output, 'assets');
234+
fs.cpSync(assetsDir, siteAssetsDir, { recursive: true });
226235

227236
for (let glob of passThrough) {
228237
eleventyConfig.addPassthroughCopy(glob);
229238
}
230239

231240
// // SSR plugin
232-
// // Make sure this is the last thing, we don't want to run the risk of accidentally transforming shadow roots with
233-
// // the nunjucks 2nd transform.
234241
// if (!isDev) {
235242
// //
236243
// // Problematic components in SSR land:
@@ -253,6 +260,23 @@ export default async function (eleventyConfig) {
253260
// componentModules,
254261
// });
255262
// }
263+
264+
if (!isDev) {
265+
eleventyConfig.addTransform('simulate-webawesome-app', function (content) {
266+
// For a server build, we expect a server to run the second transform.
267+
if (serverBuild) {
268+
return content;
269+
}
270+
271+
// Only run the transform on files nunjucks would transform.
272+
if (!this.page.inputPath.match(/.(md|html|njk)$/)) {
273+
return content;
274+
}
275+
276+
/** This largely mimics what an app would do and just stubs out what we don't care about. */
277+
return SimulateWebAwesomeApp(content);
278+
});
279+
}
256280
}
257281

258282
export const config = {

packages/webawesome/docs/_utils/search.js renamed to packages/webawesome/docs/_plugins/search.js

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* eslint-disable no-invalid-this */
2+
import { readFileSync } from 'fs';
23
import { mkdir, writeFile } from 'fs/promises';
34
import lunr from 'lunr';
45
import { parse } from 'node-html-parser';
@@ -23,19 +24,22 @@ export function searchPlugin(options = {}) {
2324
...options,
2425
};
2526

27+
// Hoist above so that it can "cache" properly for incremental builds.
2628
return function (eleventyConfig) {
27-
const pagesToIndex = new Map();
29+
let pagesToIndex = new Map();
2830

2931
eleventyConfig.addPreprocessor('exclude-unlisted-from-search', '*', function (data, content) {
3032
if (data.unlisted) {
3133
// no-op
34+
pagesToIndex.delete(data.page.inputPath);
3235
} else {
33-
pagesToIndex.set(data.page.inputPath, {});
36+
pagesToIndex.set(data.page.inputPath, true);
3437
}
3538

3639
return content;
3740
});
3841

42+
// 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".
3943
eleventyConfig.addTransform('search', function (content) {
4044
if (!pagesToIndex.has(this.page.inputPath)) {
4145
return content;
@@ -67,11 +71,30 @@ export function searchPlugin(options = {}) {
6771
return content;
6872
});
6973

70-
eleventyConfig.on('eleventy.after', ({ directories }) => {
74+
eleventyConfig.on('eleventy.after', async ({ directories }) => {
7175
const { output } = directories;
7276
const outputFilename = path.resolve(join(output, 'search.json'));
77+
const cachedPages = path.resolve(join(output, 'cached_pages.json'));
78+
79+
function getCachedPages() {
80+
let content = { pages: [] };
81+
try {
82+
content = JSON.parse(readFileSync(cachedPages));
83+
} catch (e) {}
84+
85+
const cachedPagesMap = new Map(content.pages);
86+
for (const [key, value] of cachedPagesMap.entries()) {
87+
// 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.
88+
if (pagesToIndex.get(key) === true) {
89+
pagesToIndex.set(key, value);
90+
}
91+
}
92+
}
93+
7394
const map = [];
74-
const searchIndex = lunr(async function () {
95+
96+
getCachedPages();
97+
const searchIndex = lunr(function () {
7598
let index = 0;
7699

77100
this.ref('id');
@@ -84,9 +107,11 @@ export function searchPlugin(options = {}) {
84107
map[index] = { title: page.title, description: page.description, url: page.url };
85108
index++;
86109
}
87-
await mkdir(dirname(outputFilename), { recursive: true });
88-
await writeFile(outputFilename, JSON.stringify({ searchIndex, map }), 'utf-8');
89110
});
111+
112+
await mkdir(dirname(outputFilename), { recursive: true });
113+
await writeFile(outputFilename, JSON.stringify({ searchIndex, map }), 'utf-8');
114+
await writeFile(cachedPages, JSON.stringify({ pages: [...pagesToIndex.entries()] }, null, 2));
90115
});
91116
};
92117
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { parse } from 'node-html-parser';
2+
import slugify from 'slugify';
3+
import { v4 as uuid } from 'uuid';
4+
5+
function createId(text) {
6+
let slug = slugify(String(text), {
7+
remove: /[^\w|\s]/g,
8+
lower: true,
9+
});
10+
11+
// ids must start with a letter
12+
if (!/^[a-z]/i.test(slug)) {
13+
slug = `wa_${slug}`;
14+
}
15+
16+
return slug;
17+
}
18+
19+
/**
20+
* Eleventy plugin to add anchors to headings to content.
21+
*/
22+
export function anchorHeadingsTransformer(options = {}) {
23+
options = {
24+
container: 'body',
25+
headingSelector: 'h2, h3, h4, h5, h6',
26+
anchorLabel: 'Jump to heading',
27+
...options,
28+
};
29+
30+
/** doc is a parsed HTML document */
31+
return function (doc) {
32+
const container = doc.querySelector(options.container);
33+
34+
if (!container) {
35+
return doc;
36+
}
37+
38+
// Look for headings
39+
let selector = `:is(${options.headingSelector}):not([data-no-anchor], [data-no-anchor] *)`;
40+
container.querySelectorAll(selector).forEach(heading => {
41+
const hasAnchor = heading.querySelector('a');
42+
const existingId = heading.getAttribute('id');
43+
const clone = parse(heading.outerHTML);
44+
45+
// Create a clone of the heading so we can remove [data-no-anchor] elements from the text content
46+
clone.querySelectorAll('[data-no-anchor]').forEach(el => el.remove());
47+
48+
if (hasAnchor) {
49+
return;
50+
}
51+
52+
let id = existingId;
53+
if (!id) {
54+
const slug = createId(clone.textContent ?? '') ?? uuid().slice(-12);
55+
id = slug;
56+
let suffix = 1;
57+
58+
// Make sure the slug is unique in the document
59+
while (doc.getElementById(id) !== null) {
60+
id = `${slug}-${++suffix}`;
61+
}
62+
}
63+
64+
// Create the anchor
65+
const anchor = parse(`
66+
<a href="#${encodeURIComponent(id)}">
67+
<span class="wa-visually-hidden"></span>
68+
<span aria-hidden="true">#</span>
69+
</a>
70+
`);
71+
anchor.querySelector('.wa-visually-hidden').textContent = options.anchorLabel;
72+
73+
// Update the heading
74+
if (!existingId) {
75+
heading.setAttribute('id', id);
76+
}
77+
heading.classList.add('anchor-heading');
78+
heading.appendChild(anchor);
79+
});
80+
};
81+
}

0 commit comments

Comments
 (0)