From 8c44eb75d5fd8e4ad1cecf8424f18d5ca1d932b0 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Tue, 6 Jun 2023 17:02:15 -0400 Subject: [PATCH] improvements --- .gitignore | 8 +- docs/_utilities/code-previews.cjs | 15 --- docs/_utilities/external-links.cjs | 2 +- docs/_utilities/typography.cjs | 6 +- docs/eleventy.config.cjs | 95 ++++-------------- package.json | 2 +- scripts/build.js | 153 ++++++++++++++++------------- scripts/make-search.js | 109 -------------------- 8 files changed, 109 insertions(+), 281 deletions(-) delete mode 100644 scripts/make-search.js diff --git a/.gitignore b/.gitignore index 14cdf7cf..eab31735 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,7 @@ -.DS_Store -.cache _site -docs/dist -docs/search.json -docs/assets/images/sprite.svg +.cache +.DS_Store dist +docs/assets/images/sprite.svg node_modules src/react diff --git a/docs/_utilities/code-previews.cjs b/docs/_utilities/code-previews.cjs index f1362b20..89840f6b 100644 --- a/docs/_utilities/code-previews.cjs +++ b/docs/_utilities/code-previews.cjs @@ -26,7 +26,6 @@ module.exports = function (doc, options) { const adjacentPre = pre.nextElementSibling?.tagName.toLowerCase() === 'pre' ? pre.nextElementSibling : null; const reactCode = adjacentPre?.querySelector('code[class$="react"]'); const sourceGroupId = `code-preview-source-group-${count}`; - const toggleId = `code-preview-toggle-${count}`; const isExpanded = code.getAttribute('class').includes(':expanded'); const noCodePen = code.getAttribute('class').includes(':no-codepen'); @@ -137,17 +136,3 @@ module.exports = function (doc, options) { return doc; }; - -function getAdjacentExample(name, pre) { - let currentPre = pre.nextElementSibling; - - while (currentPre?.tagName.toLowerCase() === 'pre') { - if (currentPre?.getAttribute('class').indexOf(name) > -1) { - return currentPre; - } - - currentPre = currentPre.nextElementSibling; - } - - return null; -} diff --git a/docs/_utilities/external-links.cjs b/docs/_utilities/external-links.cjs index 4d7c2ef1..36a95898 100644 --- a/docs/_utilities/external-links.cjs +++ b/docs/_utilities/external-links.cjs @@ -9,7 +9,7 @@ module.exports = function (doc, options) { className: 'external-link', // the class name to add to links noopener: true, // sets rel="noopener" noreferrer: true, // sets rel="noreferrer" - ignore: link => false, // callback function to filter links that should be ignored + ignore: () => false, // callback function to filter links that should be ignored within: 'body', // element that contains the target links target: '', // sets the target attribute ...options diff --git a/docs/_utilities/typography.cjs b/docs/_utilities/typography.cjs index c4f17c4d..53fe84b6 100644 --- a/docs/_utilities/typography.cjs +++ b/docs/_utilities/typography.cjs @@ -1,14 +1,14 @@ const smartquotes = require('smartquotes'); -smartquotes.replacements.push([/\-\-\-/g, '\u2014']); // em dash -smartquotes.replacements.push([/\-\-/g, '\u2013']); // en dash +smartquotes.replacements.push([/---/g, '\u2014']); // em dash +smartquotes.replacements.push([/--/g, '\u2013']); // en dash smartquotes.replacements.push([/\.\.\./g, '\u2026']); // ellipsis smartquotes.replacements.push([/\(c\)/gi, '\u00A9']); // copyright smartquotes.replacements.push([/\(r\)/gi, '\u00AE']); // registered trademark smartquotes.replacements.push([/\?!/g, '\u2048']); // ?! smartquotes.replacements.push([/!!/g, '\u203C']); // !! smartquotes.replacements.push([/\?\?/g, '\u2047']); // ?? -smartquotes.replacements.push([/([0-9]\s?)\-(\s?[0-9])/g, '$1\u2013$2']); // number ranges use en dash +smartquotes.replacements.push([/([0-9]\s?)-(\s?[0-9])/g, '$1\u2013$2']); // number ranges use en dash /** * Improves typography by adding smart quotes and similar corrections within the specified element(s). diff --git a/docs/eleventy.config.cjs b/docs/eleventy.config.cjs index 3bfe7412..c07b1710 100644 --- a/docs/eleventy.config.cjs +++ b/docs/eleventy.config.cjs @@ -21,12 +21,6 @@ const assetsDir = 'assets'; const allComponents = getAllComponents(); let hasBuiltSearchIndex = false; -function benchmark (callback) { - const time = performance.now() - callback() - return performance.now() - time -} - module.exports = function (eleventyConfig) { // // Global data @@ -110,19 +104,6 @@ module.exports = function (eleventyConfig) { // // Transforms // - - let transformTimers = { - activeLinks: 0, - anchorHeadings: 0, - tableOfContents: 0, - codePreviews: 0, - externalLinks: 0, - highlightCodeBlock: 0, - scrollingTables: 0, - copyCodeButtons: 0, - typography: 0, - prettier: 0, - } eleventyConfig.addTransform('html-transform', function (content) { // Parse the template and get a Document object const doc = new JSDOM(content, { @@ -132,57 +113,28 @@ module.exports = function (eleventyConfig) { }).window.document; // DOM transforms - transformTimers.activeLinks += benchmark(() => { - activeLinks(doc, { pathname: this.page.url }); - }) - - transformTimers.anchorHeadings += benchmark(() => { - anchorHeadings(doc, { - within: '#content .content__body', - levels: ['h2', 'h3', 'h4', 'h5'] - }); - }) - - transformTimers.tableOfContents += benchmark(() => { - tableOfContents(doc, { - levels: ['h2', 'h3'], - container: '#content .content__toc > ul', - within: '#content .content__body' - }); - }) - - - transformTimers.codePreviews += benchmark(() => { - codePreviews(doc); - }) - - transformTimers.externalLinks += benchmark(() => { - externalLinks(doc, { target: '_blank' }); - }) - - transformTimers.highlightCodeBlock += benchmark(() => { - highlightCodeBlocks(doc); - }) - - transformTimers.scrollingTables += benchmark(() => { - scrollingTables(doc); - }) - - transformTimers.copyCodeButtons += benchmark(() => { - copyCodeButtons(doc); // must be after codePreviews + highlightCodeBlocks - }) - - transformTimers.typography += benchmark(() => { - typography(doc, '#content'); - }) + activeLinks(doc, { pathname: this.page.url }); + anchorHeadings(doc, { + within: '#content .content__body', + levels: ['h2', 'h3', 'h4', 'h5'] + }); + tableOfContents(doc, { + levels: ['h2', 'h3'], + container: '#content .content__toc > ul', + within: '#content .content__body' + }); + codePreviews(doc); + externalLinks(doc, { target: '_blank' }); + highlightCodeBlocks(doc); + scrollingTables(doc); + copyCodeButtons(doc); // must be after codePreviews + highlightCodeBlocks + typography(doc, '#content'); // Serialize the Document object to an HTML string and prepend the doctype content = `\n${doc.documentElement.outerHTML}`; // String transforms - transformTimers.prettier += benchmark(() => { - content = prettier(content); - }) + content = prettier(content); return content; }); @@ -190,7 +142,7 @@ module.exports = function (eleventyConfig) { // // Build a search index // - eleventyConfig.on('eleventy.after', async ({ results }) => { + eleventyConfig.on('eleventy.after', ({ results }) => { // We only want to build the search index on the first run so all pages get indexed. if (hasBuiltSearchIndex) { return; @@ -239,13 +191,6 @@ module.exports = function (eleventyConfig) { fs.writeFileSync(searchIndexFilename, JSON.stringify({ searchIndex, map }), 'utf-8'); hasBuiltSearchIndex = true; - let totalTime = 0 - Object.entries(transformTimers).forEach(([k,v]) => { - const rounded = Math.ceil(v) - console.log(k + ": " + rounded + "ms") - totalTime += rounded - }) - console.log("Total transform time: " + totalTime + "ms") }); // @@ -254,9 +199,7 @@ module.exports = function (eleventyConfig) { eleventyConfig.setServerOptions({ domDiff: false, // disable dom diffing so custom elements don't break on reload, port: 4000, // if port 4000 is taken, 11ty will use the next one available - watch: [ - "dist/**/*.*" - ] // additional files to watch that will trigger server updates (array of paths or globs) + watch: ['dist/**/*'] // additional files to watch that will trigger server updates (array of paths or globs) }); // diff --git a/package.json b/package.json index cecdd5d6..9091fba4 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ }, "scripts": { "start": "node scripts/build.js --bundle --serve", - "build": "node scripts/build.js --bundle --types --copydir \"docs/dist\"", + "build": "node scripts/build.js --bundle --types --copydir \"_site/dist\"", "verify": "npm run prettier:check && npm run lint && npm run build && npm run test", "prepublishOnly": "npm run verify", "prettier": "prettier --write --loglevel warn .", diff --git a/scripts/build.js b/scripts/build.js index 5faeb5a3..f325da0b 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,23 +1,30 @@ -import { spawn, execSync } from 'child_process'; -import getPort, { portNumbers } from 'get-port'; -import { globby } from 'globby'; import { deleteSync } from 'del'; +import { globby } from 'globby'; +import { execSync, spawn } from 'child_process'; import browserSync from 'browser-sync'; import chalk from 'chalk'; +import chokidar from 'chokidar'; import commandLineArgs from 'command-line-args'; +import copy from 'recursive-copy'; import esbuild from 'esbuild'; import fs from 'fs'; -import copy from 'recursive-copy'; +import getPort, { portNumbers } from 'get-port'; const abortController = new AbortController(); const abortSignal = abortController.signal; -function buildTheDocs({ watch = false }) { +function buildTheDocs(watch = false) { + deleteSync('./_site'); + if (!watch) { - return execSync('npx @11ty/eleventy', { stdio: 'inherit', cwd: 'docs' }); + return execSync('npx @11ty/eleventy --quiet', { stdio: 'inherit', cwd: 'docs' }); } - return spawn('npx', ['@11ty/eleventy', '--watch', '--incremental'], { stdio: 'inherit', cwd: 'docs', signal: abortSignal }); + return spawn('npx', ['@11ty/eleventy', '--watch', '--incremental', '--quiet'], { + stdio: 'inherit', + cwd: 'docs', + signal: abortSignal + }); } const { bundle, copydir, dir, serve, types } = commandLineArgs([ @@ -33,7 +40,7 @@ const outdir = dir; deleteSync(outdir); fs.mkdirSync(outdir, { recursive: true }); -;(async () => { +(async () => { try { execSync(`node scripts/make-metadata.js --outdir "${outdir}"`, { stdio: 'inherit' }); execSync(`node scripts/make-react.js --outdir "${outdir}"`, { stdio: 'inherit' }); @@ -104,80 +111,84 @@ fs.mkdirSync(outdir, { recursive: true }); copy(outdir, copydir); } - console.log(chalk.green(`The build has been generated at ${outdir} 📦\n`)); - if (serve) { - // Dev - buildTheDocs({ watch: true }); + // Build it with --watch and --incremental + buildTheDocs(true); - const bs = browserSync.create(); - const port = await getPort({ - port: portNumbers(4000, 4999) - }); + // Wait for the search index to appear before launching the browser. This file is generated during eleventy.after, + // so it's usually the last one to appear. + const watcher = chokidar.watch('./_site', { persistent: true }); + watcher.on('add', async filename => { + if (filename.endsWith('search.json')) { + watcher.close(); - const browserSyncConfig = { - startPath: '/', - port, - logLevel: 'silent', - logPrefix: '[shoelace]', - logFileChanges: true, - notify: false, - single: true, - ghostMode: false, - server: { - baseDir: '_site', - routes: { - '/dist': './dist' - } + const bs = browserSync.create(); + const port = await getPort({ + port: portNumbers(4000, 4999) + }); + + const browserSyncConfig = { + startPath: '/', + port, + logLevel: 'silent', + logPrefix: '[shoelace]', + logFileChanges: true, + notify: false, + single: true, + ghostMode: false, + server: { + baseDir: '_site', + routes: { + '/dist': './dist' + } + } + }; + + // Launch browser sync + bs.init(browserSyncConfig, () => { + const url = `http://localhost:${port}`; + console.log(chalk.cyan(`Launched the Shoelace dev server at ${url} 🥾\n`)); + }); + + // Rebuild and reload when source files change + bs.watch(['src/**/!(*.test).*']).on('change', async filename => { + buildResult + // Rebuild and reload + .rebuild() + .then(() => { + // Rebuild stylesheets when a theme file changes + if (/^src\/themes/.test(filename)) { + execSync(`node scripts/make-themes.js --outdir "${outdir}"`, { stdio: 'inherit' }); + } + }) + .then(() => { + // Skip metadata when styles are changed + if (/(\.css|\.styles\.ts)$/.test(filename)) { + return; + } + + execSync(`node scripts/make-metadata.js --outdir "${outdir}"`, { stdio: 'inherit' }); + }) + .then(() => bs.reload()) + .catch(err => console.error(chalk.red(err))); + }); + + // Reload without rebuilding when the docs change + bs.watch(['_site/**/*.*']).on('change', () => { + bs.reload(); + }); } - }; - - // Launch browser sync - bs.init(browserSyncConfig, () => { - const url = `http://localhost:${port}`; - console.log(chalk.cyan(`Launched the Shoelace dev server at ${url} 🥾\n`)); }); + } - // Rebuild and reload when source files change - bs.watch(['src/**/!(*.test).*']).on('change', async filename => { - console.log(`Source file changed - ${filename}`); - - buildResult - // Rebuild and reload - .rebuild() - .then(() => { - // Rebuild stylesheets when a theme file changes - if (/^src\/themes/.test(filename)) { - execSync(`node scripts/make-themes.js --outdir "${outdir}"`, { stdio: 'inherit' }); - } - }) - .then(() => { - // Skip metadata when styles are changed - if (/(\.css|\.styles\.ts)$/.test(filename)) { - return; - } - - execSync(`node scripts/make-metadata.js --outdir "${outdir}"`, { stdio: 'inherit' }); - }) - .then(() => bs.reload()) - .catch(err => console.error(chalk.red(err))); - }); - - // Reload without rebuilding when the docs change - bs.watch(['_site/**/*.*']).on('change', async (filename) => { - console.log(`File changed - ${filename}`); - - // TODO: I tried writing a debounce here, but it wasnt working -.- - bs.reload() - }); - } else { - // Prod build + // Prod build + if (!serve) { buildTheDocs(); } // Cleanup on exit process.on('SIGTERM', () => { - buildResult.rebuild.dispose() + buildResult.rebuild.dispose(); abortController.abort(); // Stops the child process }); })(); diff --git a/scripts/make-search.js b/scripts/make-search.js deleted file mode 100644 index c5c13d71..00000000 --- a/scripts/make-search.js +++ /dev/null @@ -1,109 +0,0 @@ -import commandLineArgs from 'command-line-args'; -import fs from 'fs'; -import path from 'path'; -import { globby } from 'globby'; -import lunr from 'lunr'; -import { getAllComponents } from './shared.js'; - -const { outdir } = commandLineArgs({ name: 'outdir', type: String }); -const metadata = JSON.parse(fs.readFileSync(path.join(outdir, 'custom-elements.json'), 'utf8')); - -console.log('Generating search index for documentation'); - -;(async () => { - function getHeadings(markdown, maxLevel = 6) { - const headings = []; - const lines = markdown.split('\n'); - - lines.forEach(line => { - if (line.startsWith('#')) { - const level = line.match(/^(#+)/)[0].length; - const content = line.replace(/^#+/, ''); - - if (level <= maxLevel) { - headings.push({ level, content }); - } - } - }); - - return headings; - } - - function getMembers(markdown) { - const members = []; - const headers = markdown.match(/\[component-header:([a-z-]+)\]/g); - - if (!headers) { - return ''; - } - - headers.forEach(header => { - const tagName = header.match(/\[component-header:([a-z-]+)\]/)[1]; - const component = getAllComponents(metadata).find(component => component.tagName === tagName); - - if (component) { - const fields = ['members', 'cssProperties', 'cssParts', 'slots', 'events']; - - fields.forEach(field => { - if (component[field]) { - component[field].forEach(entry => { - if (entry.name) members.push(entry.name); - if (entry.description) members.push(entry.description); - if (entry.attribute) members.push(entry.attribute); - }); - } - }); - } - }); - - return members.join(' '); - } - - const files = await globby('./docs/**/*.md'); - const map = {}; - const searchIndex = lunr(function () { - // The search index uses these field names extensively, so shortening them can save some serious bytes. The initial - // index file went from 468 KB => 401 KB by using single-character names! - this.ref('id'); // id - this.field('t', { boost: 10 }); // title - this.field('h', { boost: 5 }); // headings - this.field('m', { boost: 2 }); // members (props, methods, events, etc.) - this.field('c'); // content - - files.forEach((file, index) => { - const relativePath = path.relative('./docs', file).replace(/\\/g, '/'); - const relativePathNoExtension = relativePath.split('.').slice(0, -1).join('.'); - const url = relativePath.replace(/\.md$/, ''); - const filename = path.basename(file); - // Ignore certain directories and files - if ( - relativePath.startsWith('assets/') || - relativePath.startsWith('dist/') || - filename === '_sidebar.md' || - filename === '404.md' - ) { - return false; - } - - const content = fs.readFileSync(file, 'utf8'); - const allHeadings = getHeadings(content, 4); - const title = allHeadings.find(heading => heading.level === 1)?.content || ''; - const headings = allHeadings - .filter(heading => heading.level > 1) - .map(heading => heading.content) - .concat([relativePathNoExtension]) - .join(' '); - const members = getMembers(content); - - // Remove markdown code fields from search results. This seems to make search results a bit more accurate and - // reduces search.json from ~679 KB to ~455 KB. - const prunedContent = content.replace(/```(.*?)```/gs, ''); - - this.add({ id: index, t: title, h: headings, m: members, c: prunedContent }); - - map[index] = { title, url }; - }); - }); - - fs.writeFileSync('./docs/search.json', JSON.stringify({ searchIndex, map }), 'utf8'); -})();