Initial commit: 11ty website with Fire/Frost branding

This commit is contained in:
The Trinity
2026-04-02 18:39:00 -05:00
commit 40b45dff2e
1646 changed files with 329080 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
import fs from "node:fs";
import path from "node:path";
import debugUtil from "debug";
import { createHash } from "@11ty/eleventy-utils";
const debug = debugUtil("Eleventy:Bundle");
const hashCache = {};
const directoryExistsCache = {};
const writingCache = new Set();
class BundleFileOutput {
constructor(outputDirectory, bundleDirectory) {
this.outputDirectory = outputDirectory;
this.bundleDirectory = bundleDirectory || "";
this.hashLength = 10;
this.fileExtension = undefined;
}
setFileExtension(ext) {
this.fileExtension = ext;
}
async getFilenameHash(content) {
if(hashCache[content]) {
return hashCache[content];
}
let base64hash = await createHash(content);
let filenameHash = base64hash.substring(0, this.hashLength);
hashCache[content] = filenameHash;
return filenameHash;
}
getFilename(filename, extension) {
return filename + (extension && !extension.startsWith(".") ? `.${extension}` : "");
}
modifyPathToUrl(dir, filename) {
return "/" + path.join(dir, filename).split(path.sep).join("/");
}
async writeBundle(content, type, writeToFileSystem) {
// do not write a bundle, do not return a file name is content is empty
if(!content) {
return;
}
let dir = path.join(this.outputDirectory, this.bundleDirectory);
let filenameHash = await this.getFilenameHash(content);
let filename = this.getFilename(filenameHash, this.fileExtension || type);
if(writeToFileSystem) {
let fullPath = path.join(dir, filename);
// no duplicate writes, this may be improved with a fs exists check, but it would only save the first write
if(!writingCache.has(fullPath)) {
writingCache.add(fullPath);
if(!directoryExistsCache[dir]) {
fs.mkdirSync(dir, { recursive: true });
directoryExistsCache[dir] = true;
}
debug("Writing bundle %o", fullPath);
fs.writeFileSync(fullPath, content);
}
}
return this.modifyPathToUrl(this.bundleDirectory, filename);
}
}
export { BundleFileOutput };

View File

@@ -0,0 +1,231 @@
import { BundleFileOutput } from "./BundleFileOutput.js";
import debugUtil from "debug";
const debug = debugUtil("Eleventy:Bundle");
const DEBUG_LOG_TRUNCATION_SIZE = 200;
class CodeManager {
// code is placed in this bucket by default
static DEFAULT_BUCKET_NAME = "default";
// code is hoisted to this bucket when necessary
static HOISTED_BUCKET_NAME = "default";
constructor(name) {
this.name = name;
this.trimOnAdd = true;
// TODO unindent on add
this.reset();
this.transforms = [];
this.isHoisting = true;
this.fileExtension = undefined;
this.toFileDirectory = undefined;
this.bundleExportKey = "bundle";
this.runsAfterHtmlTransformer = false;
this.pluckedSelector = undefined;
}
setDelayed(isDelayed) {
this.runsAfterHtmlTransformer = Boolean(isDelayed);
}
isDelayed() {
return this.runsAfterHtmlTransformer;
}
// posthtml-match-selector friendly
setPluckedSelector(selector) {
this.pluckedSelector = selector;
}
getPluckedSelector() {
return this.pluckedSelector;
}
setFileExtension(ext) {
this.fileExtension = ext;
}
setHoisting(enabled) {
this.isHoisting = !!enabled;
}
setBundleDirectory(dir) {
this.toFileDirectory = dir;
}
setBundleExportKey(key) {
this.bundleExportKey = key;
}
getBundleExportKey() {
return this.bundleExportKey;
}
reset() {
this.pages = {};
}
static normalizeBuckets(bucket) {
if(Array.isArray(bucket)) {
return bucket;
} else if(typeof bucket === "string") {
return bucket.split(",");
}
return [CodeManager.DEFAULT_BUCKET_NAME];
}
setTransforms(transforms) {
if(!Array.isArray(transforms)) {
throw new Error("Array expected to setTransforms");
}
this.transforms = transforms;
}
_initBucket(pageUrl, bucket) {
if(!this.pages[pageUrl][bucket]) {
this.pages[pageUrl][bucket] = new Set();
}
}
addToPage(pageUrl, code = [], bucket) {
if(!Array.isArray(code) && code) {
code = [code];
}
if(code.length === 0) {
return;
}
if(!this.pages[pageUrl]) {
this.pages[pageUrl] = {};
}
let buckets = CodeManager.normalizeBuckets(bucket);
let codeContent = code.map(entry => {
if(this.trimOnAdd) {
return entry.trim();
}
return entry;
});
for(let b of buckets) {
this._initBucket(pageUrl, b);
for(let content of codeContent) {
if(content) {
if(!this.pages[pageUrl][b].has(content)) {
debug("Adding code to bundle %o for %o (bucket: %o, size: %o): %o", this.name, pageUrl, b, content.length, content.length > DEBUG_LOG_TRUNCATION_SIZE ? content.slice(0, DEBUG_LOG_TRUNCATION_SIZE) + "…" : content);
this.pages[pageUrl][b].add(content);
}
}
}
}
}
async runTransforms(str, pageData, buckets) {
for (let callback of this.transforms) {
str = await callback.call(
{
page: pageData,
type: this.name,
buckets: buckets
},
str
);
}
return str;
}
getBucketsForPage(pageData) {
let pageUrl = pageData.url;
if(!this.pages[pageUrl]) {
return [];
}
return Object.keys(this.pages[pageUrl]);
}
getRawForPage(pageData, buckets = undefined) {
let url = pageData.url;
if(!this.pages[url]) {
debug("No bundle code found for %o on %o, %O", this.name, url, this.pages);
return new Set();
}
buckets = CodeManager.normalizeBuckets(buckets);
let set = new Set();
let size = 0;
for(let b of buckets) {
if(!this.pages[url][b]) {
// Just continue, if you retrieve code from a bucket that doesnt exist or has no code, it will return an empty set
continue;
}
for(let entry of this.pages[url][b]) {
size += entry.length;
set.add(entry);
}
}
debug("Retrieving %o for %o (buckets: %o, entries: %o, size: %o)", this.name, url, buckets, set.size, size);
return set;
}
async getForPage(pageData, buckets = undefined) {
let set = this.getRawForPage(pageData, buckets);
let bundleContent = Array.from(set).join("\n");
// returns promise
return this.runTransforms(bundleContent, pageData, buckets);
}
async writeBundle(pageData, buckets, options = {}) {
let url = pageData.url;
if(!this.pages[url]) {
debug("No bundle code found for %o on %o, %O", this.name, url, this.pages);
return "";
}
let { output, write } = options;
buckets = CodeManager.normalizeBuckets(buckets);
// TODO the bundle output URL might be useful in the transforms for sourcemaps
let content = await this.getForPage(pageData, buckets);
let writer = new BundleFileOutput(output, this.toFileDirectory);
writer.setFileExtension(this.fileExtension);
return writer.writeBundle(content, this.name, write);
}
// Used when a bucket is output multiple times on a page and needs to be hoisted
hoistBucket(pageData, bucketName) {
let newTargetBucketName = CodeManager.HOISTED_BUCKET_NAME;
if(!this.isHoisting || bucketName === newTargetBucketName) {
return;
}
let url = pageData.url;
if(!this.pages[url] || !this.pages[url][bucketName]) {
debug("No bundle code found for %o on %o, %O", this.name, url, this.pages);
return;
}
debug("Code in bucket (%o) is being hoisted to a new bucket (%o)", bucketName, newTargetBucketName);
this._initBucket(url, newTargetBucketName);
for(let codeEntry of this.pages[url][bucketName]) {
this.pages[url][bucketName].delete(codeEntry);
this.pages[url][newTargetBucketName].add(codeEntry);
}
// delete the bucket
delete this.pages[url][bucketName];
}
}
export { CodeManager };

View File

@@ -0,0 +1,158 @@
import debugUtil from "debug";
const debug = debugUtil("Eleventy:Bundle");
/* This class defers any `bundleGet` calls to a post-build transform step,
* to allow `getBundle` to be called before all of the `css` additions have been processed
*/
class OutOfOrderRender {
static SPLIT_REGEX = /(\/\*__EleventyBundle:[^:]*:[^:]*:[^:]*:EleventyBundle__\*\/)/;
static SEPARATOR = ":";
constructor(content) {
this.content = content;
this.managers = {};
}
// type if `get` (return string) or `file` (bundle writes to file, returns file url)
static getAssetKey(type, name, bucket) {
if(Array.isArray(bucket)) {
bucket = bucket.join(",");
} else if(typeof bucket === "string") {
} else {
bucket = "";
}
return `/*__EleventyBundle:${type}:${name}:${bucket || "default"}:EleventyBundle__*/`
}
static parseAssetKey(str) {
if(str.startsWith("/*__EleventyBundle:")) {
let [prefix, type, name, bucket, suffix] = str.split(OutOfOrderRender.SEPARATOR);
return { type, name, bucket };
}
return false;
}
setAssetManager(name, assetManager) {
this.managers[name] = assetManager;
}
setOutputDirectory(dir) {
this.outputDirectory = dir;
}
normalizeMatch(match) {
let ret = OutOfOrderRender.parseAssetKey(match)
return ret || match;
}
findAll() {
let matches = this.content.split(OutOfOrderRender.SPLIT_REGEX);
let ret = [];
for(let match of matches) {
ret.push(this.normalizeMatch(match));
}
return ret;
}
setWriteToFileSystem(isWrite) {
this.writeToFileSystem = isWrite;
}
getAllBucketsForPage(pageData) {
let availableBucketsForPage = new Set();
for(let name in this.managers) {
for(let bucket of this.managers[name].getBucketsForPage(pageData)) {
availableBucketsForPage.add(`${name}::${bucket}`);
}
}
return availableBucketsForPage;
}
getManager(name) {
if(!this.managers[name]) {
throw new Error(`No asset manager found for ${name}. Known names: ${Object.keys(this.managers)}`);
}
return this.managers[name];
}
async replaceAll(pageData, stage = 0) {
let matches = this.findAll();
let availableBucketsForPage = this.getAllBucketsForPage(pageData);
let usedBucketsOnPage = new Set();
let bucketsOutputStringCount = {};
let bucketsFileCount = {};
for(let match of matches) {
if(typeof match === "string") {
continue;
}
// type is `file` or `get`
let {type, name, bucket} = match;
let key = `${name}::${bucket}`;
if(!usedBucketsOnPage.has(key)) {
usedBucketsOnPage.add(key);
}
if(type === "get") {
if(!bucketsOutputStringCount[key]) {
bucketsOutputStringCount[key] = 0;
}
bucketsOutputStringCount[key]++;
} else if(type === "file") {
if(!bucketsFileCount[key]) {
bucketsFileCount[key] = 0;
}
bucketsFileCount[key]++;
}
}
// Hoist code in non-default buckets that are output multiple times
// Only hoist if 2+ `get` OR 1+ `get` and 1+ `file`
for(let bucketInfo in bucketsOutputStringCount) {
let stringOutputCount = bucketsOutputStringCount[bucketInfo];
if(stringOutputCount > 1 || stringOutputCount === 1 && bucketsFileCount[bucketInfo] > 0) {
let [name, bucketName] = bucketInfo.split("::");
this.getManager(name).hoistBucket(pageData, bucketName);
}
}
let content = await Promise.all(matches.map(match => {
if(typeof match === "string") {
return match;
}
let {type, name, bucket} = match;
let manager = this.getManager(name);
// Quit early if in stage 0, run delayed replacements if in stage 1+
if(typeof manager.isDelayed === "function" && manager.isDelayed() && stage === 0) {
return OutOfOrderRender.getAssetKey(type, name, bucket);
}
if(type === "get") {
// returns promise
return manager.getForPage(pageData, bucket);
} else if(type === "file") {
// returns promise
return manager.writeBundle(pageData, bucket, {
output: this.outputDirectory,
write: this.writeToFileSystem,
});
}
return "";
}));
for(let bucketInfo of availableBucketsForPage) {
if(!usedBucketsOnPage.has(bucketInfo)) {
let [name, bucketName] = bucketInfo.split("::");
debug(`WARNING! \`${pageData.inputPath}\` has unbundled \`${name}\` assets (in the '${bucketName}' bucket) that were not written to or used on the page. You might want to add a call to \`getBundle('${name}', '${bucketName}')\` to your content! Learn more: https://github.com/11ty/eleventy-plugin-bundle#asset-bucketing`);
}
}
return content.join("");
}
}
export { OutOfOrderRender };

View File

@@ -0,0 +1,69 @@
import debugUtil from "debug";
import matchHelper from "posthtml-match-helper";
const debug = debugUtil("Eleventy:Bundle");
const ATTRS = {
ignore: "eleventy:ignore",
bucket: "eleventy:bucket",
};
const POSTHTML_PLUGIN_NAME = "11ty/eleventy/html-bundle-plucker";
function hasAttribute(node, name) {
return node?.attrs?.[name] !== undefined;
}
function addHtmlPlucker(eleventyConfig, bundleManager) {
let matchSelector = bundleManager.getPluckedSelector();
if(!matchSelector) {
throw new Error("Internal error: missing plucked selector on bundle manager.");
}
eleventyConfig.htmlTransformer.addPosthtmlPlugin(
"html",
function (context = {}) {
let pageUrl = context?.url;
if(!pageUrl) {
throw new Error("Internal error: missing `url` property from context.");
}
return function (tree, ...args) {
tree.match(matchHelper(matchSelector), function (node) {
try {
// ignore
if(hasAttribute(node, ATTRS.ignore)) {
delete node.attrs[ATTRS.ignore];
return node;
}
if(Array.isArray(node?.content) && node.content.length > 0) {
// TODO make this better decoupled
if(node?.content.find(entry => entry.includes(`/*__EleventyBundle:`))) {
// preserve {% getBundle %} calls as-is
return node;
}
let bucketName = node?.attrs?.[ATTRS.bucket];
bundleManager.addToPage(pageUrl, [ ...node.content ], bucketName);
return { attrs: [], content: [], tag: false };
}
} catch(e) {
debug(`Bundle plucker: error adding content to bundle in HTML Assets: %o`, e);
return node;
}
return node;
});
};
},
{
// pluginOptions
name: POSTHTML_PLUGIN_NAME,
},
);
}
export { addHtmlPlucker };

View File

@@ -0,0 +1,85 @@
import debugUtil from "debug";
import { CodeManager } from "./CodeManager.js";
import { addHtmlPlucker } from "./bundlePlucker.js"
const debug = debugUtil("Eleventy:Bundle");
function eleventyBundleManagers(eleventyConfig, pluginOptions = {}) {
if(pluginOptions.force) {
// no errors
} else if(("getBundleManagers" in eleventyConfig || "addBundle" in eleventyConfig)) {
throw new Error("Duplicate addPlugin calls for @11ty/eleventy-plugin-bundle");
}
let managers = {};
function addBundle(name, bundleOptions = {}) {
if(name in managers) {
// note: shortcode must still be added
debug("Bundle exists %o, skipping.", name);
} else {
debug("Creating new bundle %o", name);
managers[name] = new CodeManager(name);
if(bundleOptions.delayed !== undefined) {
managers[name].setDelayed(bundleOptions.delayed);
}
if(bundleOptions.hoist !== undefined) {
managers[name].setHoisting(bundleOptions.hoist);
}
if(bundleOptions.bundleHtmlContentFromSelector !== undefined) {
managers[name].setPluckedSelector(bundleOptions.bundleHtmlContentFromSelector);
managers[name].setDelayed(true); // must override `delayed` above
addHtmlPlucker(eleventyConfig, managers[name]);
}
if(bundleOptions.bundleExportKey !== undefined) {
managers[name].setBundleExportKey(bundleOptions.bundleExportKey);
}
if(bundleOptions.outputFileExtension) {
managers[name].setFileExtension(bundleOptions.outputFileExtension);
}
if(bundleOptions.toFileDirectory) {
managers[name].setBundleDirectory(bundleOptions.toFileDirectory);
}
if(bundleOptions.transforms) {
managers[name].setTransforms(bundleOptions.transforms);
}
}
// if undefined, defaults to `name`
if(bundleOptions.shortcodeName !== false) {
let shortcodeName = bundleOptions.shortcodeName || name;
// e.g. `css` shortcode to add code to page bundle
// These shortcode names are not configurable on purpose (for wider plugin compatibility)
eleventyConfig.addPairedShortcode(shortcodeName, function addContent(content, bucket, explicitUrl) {
let url = explicitUrl || this.page?.url;
if(url) { // dont add if a file doesnt have an output URL
managers[name].addToPage(url, content, bucket);
}
return "";
});
}
};
eleventyConfig.addBundle = addBundle;
eleventyConfig.getBundleManagers = function() {
return managers;
};
eleventyConfig.on("eleventy.before", async () => {
for(let key in managers) {
managers[key].reset();
}
});
};
export default eleventyBundleManagers;

View File

@@ -0,0 +1,105 @@
import matchHelper from "posthtml-match-helper";
import debugUtil from "debug";
const debug = debugUtil("Eleventy:Bundle");
const ATTRS = {
keep: "eleventy:keep"
};
const POSTHTML_PLUGIN_NAME = "11ty/eleventy-bundle/prune-empty";
function getTextNodeContent(node) {
if (!node.content) {
return "";
}
return node.content
.map((entry) => {
if (typeof entry === "string") {
return entry;
}
if (Array.isArray(entry.content)) {
return getTextNodeContent(entry);
}
return "";
})
.join("");
}
function eleventyPruneEmptyBundles(eleventyConfig, options = {}) {
// Right now script[src],link[rel="stylesheet"] nodes are removed if the final bundles are empty.
// `false` to disable
options.pruneEmptySelector = options.pruneEmptySelector ?? `style,script,link[rel="stylesheet"]`;
// Subsequent call can remove a previously added `addPosthtmlPlugin` entry
// htmlTransformer.remove is v3.0.1-alpha.4+
if(typeof eleventyConfig.htmlTransformer.remove === "function") {
eleventyConfig.htmlTransformer.remove("html", entry => {
if(entry.name === POSTHTML_PLUGIN_NAME) {
return true;
}
// Temporary workaround for missing `name` property.
let fnStr = entry.fn.toString();
return !entry.name && fnStr.startsWith("function (pluginOptions = {}) {") && fnStr.includes(`tree.match(matchHelper(options.pruneEmptySelector), function (node)`);
});
}
// `false` disables this plugin
if(options.pruneEmptySelector === false) {
return;
}
if(!eleventyConfig.htmlTransformer || !eleventyConfig.htmlTransformer?.constructor?.SUPPORTS_PLUGINS_ENABLED_CALLBACK) {
debug("You will need to upgrade your version of Eleventy core to remove empty bundle tags automatically (v3 or newer).");
return;
}
eleventyConfig.htmlTransformer.addPosthtmlPlugin(
"html",
function bundlePruneEmptyPosthtmlPlugin(pluginOptions = {}) {
return function (tree) {
tree.match(matchHelper(options.pruneEmptySelector), function (node) {
if(node.attrs && node.attrs[ATTRS.keep] !== undefined) {
delete node.attrs[ATTRS.keep];
return node;
}
// <link rel="stylesheet" href="">
if(node.tag === "link") {
if(node.attrs?.rel === "stylesheet" && (node.attrs?.href || "").trim().length === 0) {
return false;
}
} else {
let content = getTextNodeContent(node);
if(!content) {
// <script></script> or <script src=""></script>
if(node.tag === "script" && (node.attrs?.src || "").trim().length === 0) {
return false;
}
// <style></style>
if(node.tag === "style") {
return false;
}
}
}
return node;
});
};
},
{
name: POSTHTML_PLUGIN_NAME,
// the `enabled` callback for plugins is available on v3.0.0-alpha.20+ and v3.0.0-beta.2+
enabled: () => {
return Object.keys(eleventyConfig.getBundleManagers()).length > 0;
}
}
);
}
export default eleventyPruneEmptyBundles;

View File

@@ -0,0 +1,83 @@
import { OutOfOrderRender } from "./OutOfOrderRender.js";
import debugUtil from "debug";
const debug = debugUtil("Eleventy:Bundle");
export default function(eleventyConfig, pluginOptions = {}) {
let managers = eleventyConfig.getBundleManagers();
let writeToFileSystem = true;
function bundleTransform(content, stage = 0) {
// Only run if content is string
// Only run if managers are in play
if(typeof content !== "string" || Object.keys(managers).length === 0) {
return content;
}
debug("Processing %o", this.page.url);
let render = new OutOfOrderRender(content);
for(let key in managers) {
render.setAssetManager(key, managers[key]);
}
render.setOutputDirectory(eleventyConfig.directories.output);
render.setWriteToFileSystem(writeToFileSystem);
return render.replaceAll(this.page, stage);
}
eleventyConfig.on("eleventy.before", async ({ outputMode }) => {
if(Object.keys(managers).length === 0) {
return;
}
if(outputMode !== "fs") {
writeToFileSystem = false;
debug("Skipping writing to the file system due to output mode: %o", outputMode);
}
});
// e.g. `getBundle` shortcode to get code in current page bundle
// bucket can be an array
// This shortcode name is not configurable on purpose (for wider plugin compatibility)
eleventyConfig.addShortcode("getBundle", function getContent(type, bucket, explicitUrl) {
if(!type || !(type in managers) || Object.keys(managers).length === 0) {
throw new Error(`Invalid bundle type: ${type}. Available options: ${Object.keys(managers)}`);
}
return OutOfOrderRender.getAssetKey("get", type, bucket);
});
// write a bundle to the file system
// This shortcode name is not configurable on purpose (for wider plugin compatibility)
eleventyConfig.addShortcode("getBundleFileUrl", function(type, bucket, explicitUrl) {
if(!type || !(type in managers) || Object.keys(managers).length === 0) {
throw new Error(`Invalid bundle type: ${type}. Available options: ${Object.keys(managers)}`);
}
return OutOfOrderRender.getAssetKey("file", type, bucket);
});
eleventyConfig.addTransform("@11ty/eleventy-bundle", function (content) {
let hasNonDelayedManagers = Boolean(Object.values(eleventyConfig.getBundleManagers()).find(manager => {
return typeof manager.isDelayed !== "function" || !manager.isDelayed();
}));
if(hasNonDelayedManagers) {
return bundleTransform.call(this, content, 0);
}
return content;
});
eleventyConfig.addPlugin((eleventyConfig) => {
// Delayed bundles *MUST* not alter URLs
eleventyConfig.addTransform("@11ty/eleventy-bundle/delayed", function (content) {
let hasDelayedManagers = Boolean(Object.values(eleventyConfig.getBundleManagers()).find(manager => {
return typeof manager.isDelayed === "function" && manager.isDelayed();
}));
if(hasDelayedManagers) {
return bundleTransform.call(this, content, 1);
}
return content;
});
});
};