Skip to content

Commit

Permalink
Implement Eleventy-based build system (#3917)
Browse files Browse the repository at this point in the history
Re-implements the Ant/XSLT-based build system,
with no changes to source HTML files.
See PR for full list of fixes and a few behavioral changes,
and 11ty/README.md for running instructions.
  • Loading branch information
kfranqueiro committed Jul 8, 2024
1 parent ba81e62 commit f8efc41
Show file tree
Hide file tree
Showing 48 changed files with 5,156 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .eleventyignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
*.*
11ty/
acknowledgements/
conformance-challenges/
guidelines/
lib/
requirements/
script/
wcag20/
# working-examples is directly copied; it should not be processed as templates
working-examples/
xslt/

# These files under understanding don't end up in output in the old build
understanding/*/accessibility-support-documenting.html
understanding/*/identify-changes.html
understanding/*/interruptions-minimum.html
understanding/*/seizures.html

# Ignore templates used for creating new documents
**/*-template.html

# HTML files under img will be passthrough-copied
**/img/*
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ pip-log.txt

build.properties

#######
## Node
#######

node_modules/

#############
## Output
#############
Expand All @@ -238,3 +244,4 @@ build.properties
/guidelines/wcag.xml
/guidelines/versions.xml
/guidelines/index-flat.html
/_site/
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.*
!*.ts
!*.11tydata.js
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"printWidth": 100,
"trailingComma": "es5"
}
561 changes: 561 additions & 0 deletions 11ty/CustomLiquid.ts

Large diffs are not rendered by default.

70 changes: 70 additions & 0 deletions 11ty/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Eleventy Infrastructure for WCAG Techniques and Understanding

This subdirectory contains ES Modules re-implementing pieces of the
XSLT-based build process using Eleventy.

## Usage

Make sure you have Node.js installed. This has primarily been tested with v20,
the current LTS at time of writing.

If you use [fnm](https://github.com/Schniz/fnm) or [nvm](https://github.com/nvm-sh/nvm) to manage multiple Node.js versions,
you can switch to the recommended version by typing `fnm use` or `nvm use`
(with no additional arguments) while in the repository directory.

Otherwise, you can download an installer from [nodejs.org](https://nodejs.org/).

First, run `npm i` in the root directory of the repository to install dependencies.

Common tasks:

- `npm run build` runs a one-time build
- `npm start` runs a local server with hot-reloading to preview changes as you make them:
- http://localhost:8080/techniques
- http://localhost:8080/understanding

Maintenance tasks (for working with Eleventy config and supporting files under this subdirectory):

- `npm run check` checks for TypeScript errors
- `npm run fmt` formats all TypeScript files

## Environment Variables

### `WCAG_CVSDIR`

**Usage context:** `publish-w3c` script only

Indicates top-level path of W3C CVS checkout, for WAI site updates (via `publish-w3c` script).

**Default:** `../../../w3ccvs` (same as in Ant/XSLT build process)

### `WCAG_VERSION`

**Usage context:** `publish-w3c` script only;
this should currently not be changed, pending future improvements to `21` support.

Indicates WCAG version being built, in `XY` format (i.e. no `.`)

**Default:** `22`

### `WCAG_MODE`

**Usage context:** should not need to be used manually except in specific testing scenarios

Influences base URLs for links to guidelines, techniques, and understanding pages.
Typically set by specific npm scripts or CI processes.

Possible values:

- Unset **(default)** - Sets base URLs appropriate for local testing
- `editors` - Sets base URLs appropriate for `gh-pages` publishing; used by deploy action
- `publication` - Sets base URLs appropriate for WAI site publishing; used by `publish-w3c` script

## Other points of interest

- The main configuration can be found in top-level `eleventy.config.ts`
- Build commands are defined in top-level `package.json` under `scripts`,
and can be run via `npm run <name>`
- If you see files named `*.11tydata.js`, these contribute data to the Eleventy build
(see Template and Directory Data files under
[Sources of Data](https://www.11ty.dev/docs/data/#sources-of-data))
64 changes: 64 additions & 0 deletions 11ty/cheerio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { load, type CheerioOptions } from "cheerio";
import { readFileSync } from "fs";
import { readFile } from "fs/promises";
import { dirname, resolve } from "path";

export { load } from "cheerio";

/** Convenience function that combines readFile and load. */
export const loadFromFile = async (
inputPath: string,
options?: CheerioOptions | null,
isDocument?: boolean
) => load(await readFile(inputPath, "utf8"), options, isDocument);

/**
* Retrieves content for a data-include, either from _includes,
* or relative to the input file.
* Operates synchronously for simplicity of use within Cheerio callbacks.
*
* @param includePath A data-include attribute value
* @param inputPath Path (relative to repo root) to file containing the directive
* @returns
*/
function readInclude(includePath: string, inputPath: string) {
const relativePath = resolve(dirname(inputPath), includePath);
if (includePath.startsWith("..")) return readFileSync(relativePath, "utf8");

try {
// Prioritize any match under _includes (e.g. over local toc.html built via XSLT)
return readFileSync(resolve("_includes", includePath), "utf8");
} catch (error) {
return readFileSync(relativePath, "utf8");
}
}

/**
* Resolves data-include directives in the given file, a la flatten-document.xslt.
* This is a lower-level version for use in Eleventy configuration;
* you'd probably rather use flattenDomFromFile in other cases.
*
* @param content String containing HTML to process
* @param inputPath Path (relative to repo root) to file containing the HTML
* (needed for data-include resolution)
* @returns Cheerio instance containing "flattened" DOM
*/
export function flattenDom(content: string, inputPath: string) {
const $ = load(content);

$("body [data-include]").each((_, el) => {
const replacement = readInclude(el.attribs["data-include"], inputPath);
// Replace entire element or children, depending on data-include-replace
if (el.attribs["data-include-replace"]) $(el).replaceWith(replacement);
else $(el).removeAttr("data-include").html(replacement);
});

return $;
}

/**
* Convenience version of flattenDom that requires only inputPath to be passed.
* @see flattenDom
*/
export const flattenDomFromFile = async (inputPath: string) =>
flattenDom(await readFile(inputPath, "utf8"), inputPath);
30 changes: 30 additions & 0 deletions 11ty/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/** @fileoverview Common functions used by multiple parts of the build process */

import type { Guideline, Principle, SuccessCriterion } from "./guidelines";

/** Generates an ID for heading permalinks. Equivalent to wcag:generate-id in base.xslt. */
export function generateId(title: string) {
if (title === "Parsing (Obsolete and removed)") return "parsing";
return title
.replace(/\s+/g, "-")
.replace(/[,\():]+/g, "")
.toLowerCase();
}

/** Given a string "xy", returns "x.y" */
export const resolveDecimalVersion = (version: `${number}`) => version.split("").join(".");

/** Sort function for ordering WCAG principle/guideline/SC numbers ascending */
export function wcagSort(
a: Principle | Guideline | SuccessCriterion,
b: Principle | Guideline | SuccessCriterion
) {
const aParts = a.num.split(".").map((n) => +n);
const bParts = b.num.split(".").map((n) => +n);

for (let i = 0; i < 3; i++) {
if (aParts[i] > bParts[i] || (aParts[i] && !bParts[i])) return 1;
if (aParts[i] < bParts[i] || (bParts[i] && !aParts[i])) return -1;
}
return 0;
}
50 changes: 50 additions & 0 deletions 11ty/cp-cvs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/** @fileoverview script to copy already-built output to CVS subfolders */

import { copyFile, unlink } from "fs/promises";
import { glob } from "glob";
import { mkdirp } from "mkdirp";

import { dirname, join } from "path";

const outputBase = "_site";
const cvsBase = process.env.WCAG_CVSDIR || "../../../w3ccvs";
const wcagVersion = process.env.WCAG_VERSION || "22";
const wcagBase = `${cvsBase}/WWW/WAI/WCAG${wcagVersion}`;

// Map (git) sources to (CVS) destinations, since some don't match case-sensitively
const dirs = {
techniques: "Techniques",
understanding: "Understanding",
"working-examples": "working-examples",
};

for (const [srcDir, destDir] of Object.entries(dirs)) {
const cleanPaths = await glob(`**`, {
cwd: join(wcagBase, destDir),
ignore: ["**/CVS/**"],
nodir: true,
});

for (const path of cleanPaths) await unlink(join(wcagBase, destDir, path));

const indexPaths = await glob(`**/index.html`, { cwd: join(outputBase, srcDir) });
const nonIndexPaths = await glob(`**`, {
cwd: join(outputBase, srcDir),
ignore: ["**/index.html"],
nodir: true,
});

for (const path of indexPaths) {
const srcPath = join(outputBase, srcDir, path);
const destPath = join(wcagBase, destDir, path.replace(/index\.html$/, "Overview.html"));
await mkdirp(dirname(destPath));
await copyFile(srcPath, destPath);
}

for (const path of nonIndexPaths) {
const srcPath = join(outputBase, srcDir, path);
const destPath = join(wcagBase, destDir, path);
await mkdirp(dirname(destPath));
await copyFile(srcPath, destPath);
}
}
Loading

0 comments on commit f8efc41

Please sign in to comment.