-
Notifications
You must be signed in to change notification settings - Fork 903
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
build(catalog): implement the eleventy config
PiperOrigin-RevId: 534959163
- Loading branch information
1 parent
c87d732
commit a7a061f
Showing
7 changed files
with
436 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/** | ||
* @license | ||
* Copyright 2023 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
/** | ||
* A filter that sorts and filters an array based on truthyness and sorts the | ||
* filtered array. | ||
* | ||
* This filter takes the following arguments: | ||
* - arr: (required) The array to filter-sort. | ||
* - attr: (required) The attribute to filter and sort by. | ||
* | ||
* @example | ||
* ```html | ||
* <!-- | ||
* Will generate an array of anchor tags based on the array of entries in the | ||
* "component" 11ty collection. The anchor tags are sorted alphabetically by | ||
* `data.name` and will not be rendered if `data.name` is not defined. | ||
* --> | ||
* {% for component in collections.component|filtersort('data.name') %} | ||
* <a href={{ component.url }}>{{ component.data.name }}</a> | ||
* {% endfor %} | ||
* ``` | ||
* | ||
* @param eleventyConfig The 11ty config in which to attach this filter. | ||
*/ | ||
function filterSort (eleventyConfig) { | ||
eleventyConfig.addFilter("filtersort", function(arr, attr) { | ||
// get the parts of the attribute to look up | ||
const attrParts = attr.split("."); | ||
|
||
const array = arr.filter(item => { | ||
let value = item; | ||
|
||
// get the deep attribute | ||
for (const part of attrParts) { | ||
value = value[part]; | ||
} | ||
|
||
return !!value; | ||
}); | ||
|
||
array.sort((a, b) => { | ||
let aVal = a; | ||
let bVal = b; | ||
|
||
// get the deep attributes of each a and b | ||
for (const part of attrParts) { | ||
aVal = aVal[part]; | ||
bVal = bVal[part]; | ||
} | ||
|
||
if (aVal < bVal) { | ||
return -1; | ||
} else if (aVal > bVal) { | ||
return 1; | ||
} | ||
|
||
return 0; | ||
}); | ||
|
||
return array; | ||
}); | ||
}; | ||
|
||
module.exports = filterSort; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/** | ||
* @license | ||
* Copyright 2023 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
const markdownIt = require('markdown-it'); | ||
const markdownItAnchor = require('markdown-it-anchor'); | ||
const slugifyLib = require('slugify'); | ||
|
||
/** | ||
* An 11ty plugin that integrates `markdown-it-anchor` to 11ty's markdown | ||
* engine. This allows us to inject an <a> around our <h*> elements. | ||
* | ||
* @param eleventyConfig The 11ty config in which to attach this plugin. | ||
*/ | ||
function permalinks(eleventyConfig) { | ||
// Use the same slugify as 11ty for markdownItAnchor. | ||
const slugify = (s) => slugifyLib(s, { lower: true }); | ||
|
||
const linkAfterHeaderBase = markdownItAnchor.permalink.linkAfterHeader({ | ||
style: 'visually-hidden', | ||
class: 'anchor', | ||
visuallyHiddenClass: 'offscreen', | ||
assistiveText: (title) => `Link to “${title}”`, | ||
}); | ||
|
||
/** | ||
* Wraps the link with a div so that it's more accessible. Implementation | ||
* taken from lit.dev | ||
* | ||
* https://github.com/lit/lit.dev/blob/18d86901c2814913a35b201d78e95ba8735c42e7/packages/lit-dev-content/.eleventy.js#L105-L134 | ||
*/ | ||
const linkAfterHeaderWithWrapper = (slug, opts, state, idx) => { | ||
const headingTag = state.tokens[idx].tag; | ||
if (!headingTag.match(/^h[123456]$/)) { | ||
throw new Error(`Expected token to be a h1-6: ${headingTag}`); | ||
} | ||
|
||
// Using markdownit's token system to inject a div wrapper so that we can | ||
// have: | ||
// <div class="heading h2"> | ||
// <h2 id="interactive-demo">Interactive Demo<h2> | ||
// <a class="anchor" href="#interactive-demo"> | ||
// <span class="offscreen">Permalink to "Interactive Demo"</span> | ||
// </a> | ||
// </div> | ||
state.tokens.splice( | ||
idx, | ||
0, | ||
Object.assign(new state.Token('div_open', 'div', 1), { | ||
attrs: [['class', `heading ${headingTag}`]], | ||
block: true, | ||
}) | ||
); | ||
state.tokens.splice( | ||
idx + 4, | ||
0, | ||
Object.assign(new state.Token('div_close', 'div', -1), { | ||
block: true, | ||
}) | ||
); | ||
linkAfterHeaderBase(slug, opts, state, idx + 1); | ||
}; | ||
|
||
// Apply the anchor plugin to markdownit | ||
const md = markdownIt({ | ||
html: true, | ||
breaks: false, // 2 newlines for paragraph break instead of 1 | ||
linkify: true, | ||
}).use(markdownItAnchor, { | ||
slugify, | ||
permalink: linkAfterHeaderWithWrapper, | ||
permalinkClass: 'anchor', | ||
permalinkSymbol: '#', | ||
level: [2, 3, 4], // only apply to h2 h3 and h4 | ||
}); | ||
eleventyConfig.setLibrary('md', md); | ||
} | ||
|
||
module.exports = permalinks; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/** | ||
* @license | ||
* Copyright 2023 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
const CleanCSS = require('clean-css'); | ||
|
||
/** | ||
* Bundle, minify, and inline a CSS file. Path is relative to ./site/css/. | ||
* | ||
* In dev mode, instead import the CSS file directly. | ||
* | ||
* This filter takes the following arguments: | ||
* - path: (required) The path of the file to minify and inject relative to | ||
* /site/css | ||
* | ||
* @example | ||
* ```html | ||
* <!-- | ||
* In prod will minify and inline the file at site/css/global.css into the | ||
* page to prevent a new network request. In dev will inject a <link> tag for | ||
* a faster build. | ||
* --> | ||
* <head> | ||
* {% inlinecss "global.css" %} | ||
* </head> | ||
* ``` | ||
* | ||
* @param eleventyConfig The 11ty config in which to attach this shortcode. | ||
* @param isDev {boolean} Whether or not the build is in development mode. | ||
*/ | ||
function inlineCSS(eleventyConfig, isDev) { | ||
eleventyConfig.addShortcode('inlinecss', (path) => { | ||
if (isDev) { | ||
return `<link rel="stylesheet" href="/css/${path}">`; | ||
} | ||
const result = new CleanCSS({ inline: ['local'] }).minify([ | ||
`./site/css/${path}`, | ||
]); | ||
if (result.errors.length > 0 || result.warnings.length > 0) { | ||
throw new Error( | ||
`CleanCSS errors/warnings on file ${path}:\n\n${[ | ||
...result.errors, | ||
...result.warnings, | ||
].join('\n')}` | ||
); | ||
} | ||
return `<style>${result.styles}</style>`; | ||
}); | ||
} | ||
|
||
module.exports = inlineCSS; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/** | ||
* @license | ||
* Copyright 2023 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
const fsSync = require('fs'); | ||
|
||
/** | ||
* Inline the Rollup-bundled version of a JavaScript module. Path is relative | ||
* to ./lib or ./build aliased to /js by 11ty | ||
* | ||
* In dev mode, instead directly import the module in a | ||
* script[type=module][src=/js/...], which has already been symlinked directly | ||
* to the 11ty JS output directory. | ||
* | ||
* This filter takes the following arguments: | ||
* - path: (required) The path of the file to minify and inject relative to | ||
* ./lib, ./build, or ./js folders depending on dev mode. | ||
* | ||
* @example | ||
* ```html | ||
* <!-- | ||
* In prod will inline the file at /build/ssr-utils/dsd-polyfill in a | ||
* synchronous script tag. In dev it will externally load the file in a | ||
* module script for faster build. | ||
* --> | ||
* <body dsd-pending> | ||
* {% inlinejs "ssr-utils/dsd-polyfill.js" %} | ||
* </body> | ||
* ``` | ||
* | ||
* @param eleventyConfig The 11ty config in which to attach this shortcode. | ||
* @param isDev {boolean} Whether or not the build is in development mode. | ||
* @param config {{jsdir: string}} Configuration options to set the JS directory | ||
*/ | ||
function inlineJS(eleventyConfig, isDev, {jsDir}) { | ||
eleventyConfig.addShortcode('inlinejs', (path) => { | ||
// script type module | ||
if (isDev) { | ||
return `<script type="module" src="/js/${path}"></script>`; | ||
} | ||
const script = fsSync.readFileSync(`${jsDir}/${path}`, 'utf8').trim(); | ||
return `<script>${script}</script>`; | ||
}); | ||
} | ||
|
||
module.exports = inlineJS; |
75 changes: 75 additions & 0 deletions
75
catalog/eleventy-helpers/shortcodes/playground-example.cjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/** | ||
* @license | ||
* Copyright 2023 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
/** | ||
* Will render a playground example with a project.json in the | ||
* `/catalog/stories/${dirname}/` directory. | ||
* | ||
* This shorcode takes the following arguments: | ||
* - dirname: (required) The name of the directory where the project.json is | ||
* located | ||
* - id: (optional) The id of the project. This is used to identify the project on pages | ||
* with multiple playground examples. | ||
* - previewHeight: (optional) The height of the preview window. Defaults to `400`. | ||
* - editorHeight: (optional) The height of the editor window. Defaults to `500`. | ||
* | ||
* @example | ||
* ```html | ||
* <!-- | ||
* Will generate a playground example located at | ||
* /catalog/stories/checkbox/project.json | ||
* and give the project the id "example1" | ||
* --> | ||
* {% playgroundexample dirname="checkbox", id="example2", previewHeight="400", editorHeight="500" %} | ||
* ``` | ||
* | ||
* @param eleventyConfig The 11ty config in which to attach this shortcode. | ||
*/ | ||
function playgroundExample(eleventyConfig) { | ||
eleventyConfig.addShortcode('playgroundexample', (config) => { | ||
let { id, dirname } = config; | ||
if (!dirname) { | ||
throw new Error('No dirname provided to playgroundexample shortcode'); | ||
} | ||
|
||
id ||= 'project'; | ||
|
||
const previewHeight = config.previewHeight | ||
? `height: ${config.previewHeight}px` | ||
: 'height: 400px;'; | ||
const editorHeight = config.editorHeight | ||
? `height: ${config.editorHeight}px` | ||
: 'height: 500px;'; | ||
|
||
return ` | ||
<details> | ||
<summary> | ||
<md-outlined-icon-button toggle tabindex="-1" aria-hidden="true"> | ||
<md-icon aria-hidden="true">expand_more</md-icon> | ||
<md-icon aria-hidden="true" slot="selectedIcon">expand_less</md-icon> | ||
</md-outlined-icon-button> | ||
Expand interactive demo. | ||
</summary> | ||
<lit-island on:visible import="/material-web/js/hydration-entrypoints/playground-elements.js" class="example" aria-hidden="true"> | ||
<playground-project | ||
id="${id}" project-src="/material-web/assets/stories/${dirname}/project.json"> | ||
<playground-preview | ||
style="${previewHeight}" | ||
project="${id}" | ||
><md-circular-progress indeterminate></md-circular-progress></playground-preview> | ||
<playground-file-editor | ||
style="${editorHeight}" | ||
project="${id}" | ||
filename="stories.ts" | ||
line-numbers | ||
><md-circular-progress indeterminate></md-circular-progress></playground-file-editor> | ||
</lit-island> | ||
</details> | ||
`; | ||
}); | ||
} | ||
|
||
module.exports = playgroundExample; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/** | ||
* @license | ||
* Copyright 2023 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
const htmlMinifier = require('html-minifier'); | ||
|
||
/** | ||
* Minifies HTML in production mode. Does nothing in dev mode for a faster build | ||
* and debuggability | ||
* | ||
* @param eleventyConfig The 11ty config in which to attach this transform. | ||
* @param isDev {boolean} Whether or not the build is in development mode. | ||
*/ | ||
function minifyHTML(eleventyConfig, isDev) { | ||
eleventyConfig.addTransform('htmlMinify', function (content, outputPath) { | ||
// return the normal content in dev moe. | ||
if (isDev || !outputPath.endsWith('.html')) { | ||
return content; | ||
} | ||
// minify the html in Prod mode | ||
const minified = htmlMinifier.minify(content, { | ||
useShortDoctype: true, | ||
removeComments: true, | ||
collapseWhitespace: true, | ||
}); | ||
return minified; | ||
}); | ||
} | ||
|
||
module.exports = minifyHTML; |
Oops, something went wrong.