diff --git a/eleventy.config.js b/eleventy.config.js
index 588beaf..fe67cfe 100644
--- a/eleventy.config.js
+++ b/eleventy.config.js
@@ -9,42 +9,43 @@
* @returns {import("@11ty/eleventy").EleventyConfig} - Returns Eleventy's configuration options
*/
-const addWorkflow = require("./src/_flightdeck/workflow");
-const addFilters = require("./src/_flightdeck/filters");
-const addTransforms = require("./src/_flightdeck/transforms");
-const addShortcodes = require("./src/_flightdeck/shortcodes");
-const addPlugins = require("./src/_flightdeck/plugins");
-
-module.exports = (config) => {
-
- /** @type {{useImageDirTransform: boolean}} */
- const options = {
- useImageDirTransform: false,
- };
-
- // Configure 11ty development server, layout aliases, watch, passthrough copy
- addWorkflow(config, options);
-
- // Custom plugins that integrate esbuild, scss, image optimization
- addTransforms(config, options);
-
- // Add eleventy plugins and configurations
- addPlugins(config);
-
- // Custom shortcodes for Nunjucks/Liquid template - ui components go here
- addShortcodes(config);
-
- // Custom universal filters for Nunjucks/Liquid templates
- addFilters(config);
-
- // 11ty configuration options
- return {
- dir: {
- input: "src",
- output: "dist",
- data: "_includes/data",
- },
- htmlTemplateEngine: "njk",
- markdownTemplateEngine: "njk",
- };
-};
+import addWorkflow from "./src/_flightdeck/workflow.js";
+import addFilters from "./src/_flightdeck/filters.js";
+import addTransforms from "./src/_flightdeck/transforms.js";
+import addShortcodes from "./src/_flightdeck/shortcodes.js";
+import addPlugins from "./src/_flightdeck/plugins.js";
+
+export default function(config) {
+ /** @type {{useImageDirTransform: boolean}} */
+ const options = {
+ useImageDirTransform: false
+ };
+
+ // Configure development workflow (server, watch, passthrough)
+ addWorkflow(config, options);
+
+ // Add transforms (esbuild, lightningcss, image optimization)
+ addTransforms(config, options);
+
+ // Add eleventy plugins
+ addPlugins(config);
+
+ // Add shortcodes for templates
+ addShortcodes(config);
+
+ // Add universal filters
+ addFilters(config);
+
+ return {
+ dir: {
+ input: "src",
+ output: "dist",
+ data: "_includes/data",
+ includes: "_includes",
+ layouts: "_includes/layouts"
+ },
+ htmlTemplateEngine: "njk",
+ markdownTemplateEngine: "njk",
+ templateFormats: ["md", "njk", "html"],
+ };
+}
diff --git a/package.json b/package.json
index ecc321a..1d4eab6 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"author": "Ed Heltzel",
"description": "An opinionated Jamstack starter project for Eleventy.",
"license": "WTFPL",
+ "type": "module",
"devDependencies": {
"@11ty/eleventy": "^3.0.0",
"@11ty/eleventy-img": "^5.0.0",
diff --git a/src/_flightdeck/filters.js b/src/_flightdeck/filters.js
index 8c5ec06..a204df8 100644
--- a/src/_flightdeck/filters.js
+++ b/src/_flightdeck/filters.js
@@ -8,16 +8,16 @@
* @param {import("@11ty/eleventy").UserConfig} config - The Eleventy config object to which the filters will be added.
*/
-const baseUrl = require("./filters/baseUrl");
-const limit = require("./filters/postLimit");
-const strip = require("./filters/stripFileExtension");
-const date = require("./filters/dates");
-const excerpt = require("./filters/excerpt");
+import baseUrl from "./filters/baseUrl.js";
+import { postLimit } from "./filters/postLimit.js";
+import { stripFileExtension } from "./filters/stripFileExtension.js";
+import * as date from "./filters/dates.js";
+import excerpt from "./filters/excerpt.js";
-module.exports = (config) => {
+export default (config) => {
config.addFilter("excerpt", excerpt);
- config.addFilter("postLimit", limit.postLimit);
- config.addFilter("removeExt", strip.stripFileExtension);
+ config.addFilter("postLimit", postLimit);
+ config.addFilter("removeExt", stripFileExtension);
config.addFilter("baseUrl", baseUrl);
config.addFilter("postDate", date.postDate);
config.addFilter("postDateTime", date.postDateTime);
diff --git a/src/_flightdeck/filters/baseUrl.js b/src/_flightdeck/filters/baseUrl.js
index f986904..e06c8db 100644
--- a/src/_flightdeck/filters/baseUrl.js
+++ b/src/_flightdeck/filters/baseUrl.js
@@ -9,8 +9,8 @@
* // outputs: https://example.com/about/
*/
-const baseUrl = require("../../_includes/data/site").baseUrl;
+import { baseUrl } from "../../_includes/data/site.js";
-module.exports = (url) => {
+export default (url) => {
return `${baseUrl}${url}`;
};
diff --git a/src/_flightdeck/filters/dates.js b/src/_flightdeck/filters/dates.js
index 16d8d6b..9a2182b 100644
--- a/src/_flightdeck/filters/dates.js
+++ b/src/_flightdeck/filters/dates.js
@@ -1,23 +1,19 @@
-const { DateTime } = require("luxon"); //bundled with 11ty
+import { DateTime } from 'luxon'; //bundled with 11ty
/**
- * Human readable date format for date
- * @param {string} postDate
- * @returns {string} May 20, 1982
- * @example {{ page.date | postDate }}
+ * Format a date using Luxon's DateTime
+ * @param {Date} date - The date to format
+ * @returns {string} Formatted date string
*/
-const postDate = (date) => {
- return DateTime.fromJSDate(date).toLocaleString(DateTime.DATE_MED);
+export const postDate = (date) => {
+ return DateTime.fromJSDate(date).toLocaleString(DateTime.DATE_FULL);
};
/**
- * Human readable format for date with time
- * @param {string} postDateTime
- * @returns {string} May 20, 1982, 5:30 PM EDT
- * @example {{ page.date | postDateTime }}
+ * Format a date for use in HTML datetime attributes
+ * @param {Date} date - The date to format
+ * @returns {string} ISO date string
*/
-const postDateTime = (date) => {
- return DateTime.fromJSDate(date).toLocaleString(DateTime.DATETIME_MED);
+export const postDateTime = (date) => {
+ return DateTime.fromJSDate(date).toFormat('yyyy-LL-dd');
};
-
-module.exports = { postDate, postDateTime };
diff --git a/src/_flightdeck/filters/excerpt.js b/src/_flightdeck/filters/excerpt.js
index 84c74d4..b4ff153 100644
--- a/src/_flightdeck/filters/excerpt.js
+++ b/src/_flightdeck/filters/excerpt.js
@@ -1,9 +1,10 @@
/**
* Returns the first 200 characters as the excerpt
+ * @param {string} content - The content to create an excerpt from
+ * @returns {string} The excerpt with ellipsis
* @usage {{ post.templateContent | excerpt | safe }}
*/
-
-module.exports = (content) => {
+export default (content) => {
// Remove HTML tags
const text = content.replace(/<[^>]+>/g, "");
diff --git a/src/_flightdeck/filters/postLimit.js b/src/_flightdeck/filters/postLimit.js
index 5d60160..35a9597 100644
--- a/src/_flightdeck/filters/postLimit.js
+++ b/src/_flightdeck/filters/postLimit.js
@@ -6,8 +6,6 @@
* @returns {Array} The subset of the array up to the limit
* @usage {{ for item in collections.all | postLimit(3) }}
*/
-const postLimit = (arr, limit) => {
+export const postLimit = (arr, limit) => {
return arr.slice(0, limit);
};
-
-module.exports = { postLimit };
diff --git a/src/_flightdeck/filters/stripFileExtension.js b/src/_flightdeck/filters/stripFileExtension.js
index a1ad2d0..0e2eae1 100644
--- a/src/_flightdeck/filters/stripFileExtension.js
+++ b/src/_flightdeck/filters/stripFileExtension.js
@@ -6,8 +6,6 @@
* @usage
* useful for creating css classes based on layouts
*/
-const stripFileExtension = (file) => {
+export const stripFileExtension = (file) => {
return file.replace(/\.[^/.]+$/, "");
};
-
-module.exports = { stripFileExtension };
diff --git a/src/_flightdeck/plugins.js b/src/_flightdeck/plugins.js
index 6461b6d..8ea56e0 100644
--- a/src/_flightdeck/plugins.js
+++ b/src/_flightdeck/plugins.js
@@ -8,11 +8,11 @@
* @param {import("@11ty/eleventy").UserConfig} config - The Eleventy config object to which the plugins will be added.
*/
-const embedEverything = require("eleventy-plugin-embed-everything");
-const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
-const navigation = require("@11ty/eleventy-navigation");
+import embedEverything from "eleventy-plugin-embed-everything";
+import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
+import navigation from "@11ty/eleventy-navigation";
-module.exports = (config) => {
+export default (config) => {
config.addPlugin(embedEverything);
config.addPlugin(syntaxHighlight);
config.addPlugin(navigation);
diff --git a/src/_flightdeck/shortcodes.js b/src/_flightdeck/shortcodes.js
index ed46693..163fe77 100644
--- a/src/_flightdeck/shortcodes.js
+++ b/src/_flightdeck/shortcodes.js
@@ -7,20 +7,20 @@
*
* @param {import("@11ty/eleventy").UserConfig} config - The Eleventy config object to which the shortcodes will be added.
*/
-const blockquote = require("./shortcodes/blockquote");
-const { button, buttonLink } = require("./shortcodes/buttons");
-const codepen = require("./shortcodes/codepen");
-const copyright = require("./shortcodes/copyright");
-const image = require("./shortcodes/image");
-const version = require("./shortcodes/flightdeck-version");
-const email = require("./shortcodes/email");
+import blockquote from "./shortcodes/blockquote.js";
+import { button, buttonLink } from "./shortcodes/buttons.js";
+import codepen from "./shortcodes/codepen.js";
+import copyright from "./shortcodes/copyright.js";
+import image from "./shortcodes/image.js";
+import version from "./shortcodes/flightdeck-version.js";
+import email from "./shortcodes/email.js";
-module.exports = (config) => {
+export default (config) => {
config.addShortcode("blockquote", blockquote);
config.addShortcode("button", button);
config.addShortcode("link", buttonLink);
config.addShortcode("codepen", codepen);
- config.addShortcode("copyright", copyright);
+ config.addShortcode("year", copyright);
config.addShortcode("image", image);
config.addShortcode("version", version);
config.addShortcode("email", email);
diff --git a/src/_flightdeck/shortcodes/blockquote.js b/src/_flightdeck/shortcodes/blockquote.js
index 365d9af..d375fdb 100644
--- a/src/_flightdeck/shortcodes/blockquote.js
+++ b/src/_flightdeck/shortcodes/blockquote.js
@@ -4,10 +4,10 @@
* @param {string} [params.text] - The text to display in the blockquote
* @param {string} [params.source] - The source of the blockquote or Author of the quote
* @param {string} [params.classes] - Additional CSS classes to apply to the blockquote
+ * @returns {string} HTML string for the blockquote
* @example {% blockquote text="First, solve the problem. Then, write the code.", source="John Johnson", classes="text-lg italic" %}
*/
-
-module.exports = (params = {}) => {
+export default (params = {}) => {
const { text = '', source = '', classes = '' } = params;
return `
@@ -15,4 +15,4 @@ module.exports = (params = {}) => {
${source ? `` : ''}
`;
-};
+}
diff --git a/src/_flightdeck/shortcodes/buttons.js b/src/_flightdeck/shortcodes/buttons.js
index 1bcd790..5852455 100644
--- a/src/_flightdeck/shortcodes/buttons.js
+++ b/src/_flightdeck/shortcodes/buttons.js
@@ -5,9 +5,10 @@
* @param {string} [params.type='button'] - The type of button (submit/reset/button)
* @param {string} [params.text='Button'] - The text to display in the button
* @param {string} [params.classes='btn'] - The classes to apply to the button (e.g., Tailwind classes)
+ * @returns {string} HTML string for the button
* @example {% button type="submit", text="Click Me", classes="btn btn-primary" %}
*/
-const button = (params = {}) => {
+export const button = (params = {}) => {
const { type = 'button', text = 'Button', classes = 'btn' } = params;
return ``;
};
@@ -18,14 +19,12 @@ const button = (params = {}) => {
* @param {Object} [params] - The parameters for the link button (all optional)
* @param {string} [params.url='/'] - The link to a page or external URL
* @param {string} [params.text='Button'] - The text to display in the link
- * @param {string} [params.target='_self'] - The target for the link
- * @param {string} [params.classes='btn'] - The classes to apply to the button (e.g., Tailwind classes)
- * @param {string} [params.role='button'] - The role purpose, used mainly for accessibility
- * @example {% link role="button", url="https://google.com", text="Click Me", target="_blank", classes="btn btn-link" %}
+ * @param {string} [params.classes='btn'] - The classes to apply to the link (e.g., Tailwind classes)
+ * @param {string} [params.target='_self'] - The target attribute for the link (_blank/_self)
+ * @returns {string} HTML string for the link button
+ * @example {% link url="https://example.com", text="Visit Site", classes="btn btn-primary", target="_blank" %}
*/
-const buttonLink = (params = {}) => {
- const { url = '/', text = 'Button', target = '_self', role = 'button', classes = 'btn btn-primary' } = params;
- return `${text}`;
+export const buttonLink = (params = {}) => {
+ const { url = '/', text = 'Button', classes = 'btn', target = '_self' } = params;
+ return `${text}`;
};
-
-module.exports = { button, buttonLink };
diff --git a/src/_flightdeck/shortcodes/codepen.js b/src/_flightdeck/shortcodes/codepen.js
index 88abba6..7c82e3c 100644
--- a/src/_flightdeck/shortcodes/codepen.js
+++ b/src/_flightdeck/shortcodes/codepen.js
@@ -5,10 +5,10 @@
* @param {number} [params.height=300] - Height of the embed in pixels
* @param {string} [params.tabs='result'] - Tabs to show (e.g., 'html', 'html,result', 'css', 'css,result', 'js', 'js,result')
* @param {string} [params.theme=''] - Theme ID ('light', 'dark', or custom theme ID for pro users)
+ * @returns {string} HTML string for the Codepen embed
* @example {% codepen penUrl="https://codepen.io/jacobberglund/pen/bwrGvx", height=900, tabs="css,result", theme="178" %}
*/
-
-module.exports = (params) => {
+export default (params) => {
const {
penUrl,
height = 300,
@@ -20,25 +20,25 @@ module.exports = (params) => {
throw new Error("penUrl is required for the Codepen embed");
}
- const splitUrl = penUrl.split("/");
- const splitProfileUrl = splitUrl.slice(0, -2);
- const userProfile = splitProfileUrl.join("/");
- const slugHash = splitUrl[splitUrl.length - 1];
- const userName = splitProfileUrl[splitProfileUrl.length - 1];
+ // Extract pen ID from URL
+ const penId = penUrl.split("/").pop();
+
+ // Extract username from URL
+ const username = penUrl.split("/").slice(-3)[0];
return `
-
-
- See the pen
- (@${userName})
- on CodePen.
-
-
- `;
+
+
+
+ `;
};
diff --git a/src/_flightdeck/shortcodes/copyright.js b/src/_flightdeck/shortcodes/copyright.js
index 012ce36..d398eff 100644
--- a/src/_flightdeck/shortcodes/copyright.js
+++ b/src/_flightdeck/shortcodes/copyright.js
@@ -1,7 +1,8 @@
/**
* Get the current year - copyright
+ * @returns {string} HTML copyright symbol with current year
* @usage {% year %}
*/
-module.exports = (copyright) => {
+export default () => {
return `© ${new Date().getFullYear()}`;
};
diff --git a/src/_flightdeck/shortcodes/email.js b/src/_flightdeck/shortcodes/email.js
index aa8f0c9..391f8bb 100644
--- a/src/_flightdeck/shortcodes/email.js
+++ b/src/_flightdeck/shortcodes/email.js
@@ -15,7 +15,7 @@
* @type {(params?: EmailParams) => string}
* @see src/assets/styles/_autopilot/_utilities/text.css
*/
-module.exports = (params = {}) => {
+export default (params = {}) => {
const { address, honeypot = 'honeypot' } = params;
return `${address}${honeypot}.com`;
};
diff --git a/src/_flightdeck/shortcodes/flightdeck-version.js b/src/_flightdeck/shortcodes/flightdeck-version.js
index a5e0d1f..fd93fbd 100644
--- a/src/_flightdeck/shortcodes/flightdeck-version.js
+++ b/src/_flightdeck/shortcodes/flightdeck-version.js
@@ -1,9 +1,16 @@
/**
* Get the current package version - version
+ * @returns {string} Current package version prefixed with 'v'
* @example {% version %}
*/
-const fdVersion = require("../../../package.json").version;
+import { readFileSync } from 'node:fs';
+import { resolve, dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
-module.exports = (version) => {
- return `v${fdVersion}`;
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const packagePath = resolve(__dirname, '../../../package.json');
+const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
+
+export default () => {
+ return `v${packageJson.version}`;
};
diff --git a/src/_flightdeck/shortcodes/image.js b/src/_flightdeck/shortcodes/image.js
index db7ef96..5839c8c 100644
--- a/src/_flightdeck/shortcodes/image.js
+++ b/src/_flightdeck/shortcodes/image.js
@@ -8,11 +8,13 @@
* @example {% image src="/assets/images/moon.jpg", alt="A picture of the moon", sizes="(max-width: 600px) 100vw, 50vw" %}
*/
-// Import Image library
-const Image = require("@11ty/eleventy-img");
+import Image from "@11ty/eleventy-img";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
-// Shortcode function
-module.exports = async (params) => {
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+export default async function imageShortcode(params) {
const {
src,
alt,
@@ -20,32 +22,41 @@ module.exports = async (params) => {
} = params;
if (!src) {
- throw new Error("src is required for the image");
+ throw new Error("src is required for the image shortcode");
}
if (!alt) {
- throw new Error("alt is required for the image");
+ throw new Error("alt is required for the image shortcode");
}
- // Image paths
- const rootPath = `./src${src}`;
- const outputDir = "./dist/assets/images/";
- const urlPath = "/assets/images/";
-
- // Generate metadata
- const metadata = await Image(rootPath, {
- widths: [400, 800, 1600],
- formats: ["webp", "jpeg", "png"],
- outputDir: outputDir,
- urlPath: urlPath,
- svgShortCircuit: "size",
- });
-
- // Generate HTML
- return Image.generateHTML(metadata, {
- alt,
- sizes,
- loading: "lazy",
- decoding: "async",
- });
-};
+ // Determine if src is absolute or relative
+ const inputPath = src.startsWith("/")
+ ? path.join(process.cwd(), "src", src)
+ : path.join(process.cwd(), src);
+
+ const options = {
+ widths: [300, 600, 900, 1200],
+ formats: ["avif", "webp", "jpeg"],
+ outputDir: path.join(process.cwd(), "dist", "assets", "images"),
+ urlPath: "/assets/images",
+ sharpOptions: {
+ animated: true,
+ quality: 80,
+ progressive: true
+ }
+ };
+
+ try {
+ const metadata = await Image(inputPath, options);
+
+ return Image.generateHTML(metadata, {
+ alt,
+ sizes,
+ loading: "lazy",
+ decoding: "async",
+ });
+ } catch (error) {
+ console.error("Image processing error:", error);
+ return ``;
+ }
+}
diff --git a/src/_flightdeck/transforms.js b/src/_flightdeck/transforms.js
index c0e2d2b..3a30480 100644
--- a/src/_flightdeck/transforms.js
+++ b/src/_flightdeck/transforms.js
@@ -6,26 +6,33 @@
*
* @param {import("@11ty/eleventy").UserConfig} config - The Eleventy config object to which the transformations will be added.
* @param {{useImageDirTransform: boolean}} options - Custom options for configuring transforms.
- *
*/
+import markdownIt from "./transforms/markdownIt.js";
+import imageTransform from "./transforms/allimages.js";
+import minifyHtml from "./transforms/minifyHtml.js";
+import esbuildTransform from "./transforms/esBuild.js";
+import lightningTransform from "./transforms/lightning.js";
+
const isProd = process.env.ENV === "prod";
-const { markdownIt } = require("./transforms/markdownIt"); // markdown-it plugins
-const { transformImages } = require("./transforms/allimages"); // optimize all images in src/assets/images
-const minifyHtml = require("./transforms/minifyHtml");
-const { transformJs } = require("./transforms/esBuild"); // js bundling
-const lightningCss = require("./transforms/lightning"); // css bundling
-module.exports = (config, options) => {
- config.setLibrary("md", markdownIt);
- config.addPlugin(transformJs);
- config.addPlugin(lightningCss);
+export default (config, options) => {
+ // Set up markdown processing
+ config.setLibrary("md", markdownIt());
- if (options.useImageDirTransform) {
- config.addPlugin(transformImages);
+ // Add transforms
+ minifyHtml(config);
+ esbuildTransform(config);
+ lightningTransform(config);
+
+ // Add image optimization if enabled
+ if (options?.useImageDirTransform) {
+ imageTransform(config);
}
+
// production build only
if (isProd) {
- config.addPlugin(minifyHtml);
+ // No minifyHtml config.addPlugin equivalent, so leaving this commented out
+ // config.addPlugin(minifyHtml);
}
};
diff --git a/src/_flightdeck/transforms/_legacyEsBuild.js b/src/_flightdeck/transforms/_legacyEsBuild.js
deleted file mode 100644
index dcc69de..0000000
--- a/src/_flightdeck/transforms/_legacyEsBuild.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// @ts-check
-
-/**
- * ESBuild Transform function
- * @module _legacyEsBuild
- * @requires esbuild
- */
-
-const isProd = process.env.ENV === "prod";
-const esbuild = require("esbuild");
-
-/**
- * @typedef {import('@11ty/eleventy').UserConfig} EleventyConfig
- */
-
-/**
- * Adds the ESBuild transform to the Eleventy config.
- * @param {EleventyConfig} config - The Eleventy configuration object.
- */
-const transformJs = (config) => {
- config.on("eleventy.after", async () => {
- await esbuild.build({
- entryPoints: { "assets/js/app": "./src/assets/js/app.js" },
- bundle: true,
- outdir: "dist",
- minify: isProd,
- sourcemap: !isProd,
- splitting: true,
- format: "esm",
- logLevel: "warning",
- metafile: true,
- });
- });
-};
-
-module.exports = { transformJs };
diff --git a/src/_flightdeck/transforms/_scss.js b/src/_flightdeck/transforms/_scss.js
deleted file mode 100644
index 11e5b4f..0000000
--- a/src/_flightdeck/transforms/_scss.js
+++ /dev/null
@@ -1,52 +0,0 @@
- // @ts-check
-
-/**
- * Process Scss
- * @description If you want to use Sass/Scss in your project, you can add this module to the transforms.js
-*
-* @module scss
-* @requires eleventy-sass
-* @requires postcss
-* @requires autoprefixer
-* @requires css-declaration-sorter
-* @example const { transformScss } = require("./transforms/_scss");
-* @see {@link https://github.com/kentaroi/eleventy-sass/blob/main/docs/sass-options.md}
- */
-
-const scss = require("eleventy-sass");
-const postcss = require("postcss");
-const autoprefixer = require("autoprefixer");
-const cssDeclarationSorter = require("css-declaration-sorter");
-
-/**
- * @typedef {import('@11ty/eleventy').UserConfig} EleventyConfig
- */
-
-/**
- * Adds the SCSS transform to the Eleventy config.
- * @param {EleventyConfig} config - The Eleventy configuration object.
- */
-const transformScss = (config) => {
- config.addPlugin(scss, [
- {
- sass: {
- style: "expanded",
- sourceMap: false,
- loadPaths: ["node_modules/@picocss/pico/scss", "node_modules/@picocss/pico/scss/themes/default"],
- },
- },
- {
- sass: {
- style: "compressed",
- sourceMap: false,
- loadPaths: ["node_modules/@picocss/pico/scss", "node_modules/@picocss/pico/scss/themes/default"],
- },
- postcss: postcss([autoprefixer, cssDeclarationSorter({ order: "concentric-css" })]),
- when: [{ ENV: "prod" }],
- },
- ]);
-};
-
-module.exports = {
- transformScss,
-};
diff --git a/src/_flightdeck/transforms/allimages.js b/src/_flightdeck/transforms/allimages.js
index 976d857..5db73ad 100644
--- a/src/_flightdeck/transforms/allimages.js
+++ b/src/_flightdeck/transforms/allimages.js
@@ -8,9 +8,9 @@
* @requires fast-glob
*/
-const Image = require("@11ty/eleventy-img");
-const path = require("node:path");
-const glob = require("fast-glob");
+import Image from "@11ty/eleventy-img";
+import path from "node:path";
+import glob from "fast-glob";
/**
* Optimizes all images in the specified base directory.
@@ -19,38 +19,36 @@ const glob = require("fast-glob");
const optimizeImages = async () => {
const baseDirectory = "./src/assets/images";
const outputDirectory = "./dist/assets/images";
+ const imageFormats = ["jpg", "jpeg", "png", "gif", "webp", "avif"];
- // Get all image files
- const imageFiles = await glob(`${baseDirectory}/**/*.{png,jpg,jpeg,webp}`, {
- onlyFiles: true, // only get files not directories
- });
+ try {
+ // Find all image files
+ const imageFiles = await glob(`${baseDirectory}/**/*.{${imageFormats.join(',')}}`);
- // Optimize all images in parallel
- await Promise.all(
- imageFiles.map(async (imageFile) => {
- const relativePath = path.relative(baseDirectory, imageFile);
- const outputPath = path.join(outputDirectory, path.dirname(relativePath));
- const outputUrlPath = path.join("/assets/images", path.dirname(relativePath));
+ // Process each image
+ for (const imagePath of imageFiles) {
+ const outputPath = path.join(
+ outputDirectory,
+ path.relative(baseDirectory, imagePath)
+ );
- await Image(imageFile, {
- formats: ["auto"],
- urlPath: outputUrlPath,
- widths: [1600],
- outputDir: outputPath,
- filenameFormat: (id, src, width, format, options) => {
- const name = path.basename(src, path.extname(src));
- return `${name}.${format}`;
+ await Image(imagePath, {
+ formats: ["avif", "webp", "jpeg"],
+ outputDir: path.dirname(outputPath),
+ filenameFormat: (id, src, width, format) => {
+ const ext = path.extname(src);
+ const name = path.basename(src, ext);
+ return `${name}-${width}w.${format}`;
},
+ widths: [400, 800, 1200],
sharpOptions: {
- quality: 80,
- compressionLevel: 9,
- progressive: true,
- optimizeScans: true,
- withMetadata: false,
- },
+ animated: true
+ }
});
- })
- );
+ }
+ } catch (error) {
+ console.error("Image optimization error:", error);
+ }
};
/**
@@ -61,10 +59,7 @@ const optimizeImages = async () => {
* Adds the image optimization transform to the Eleventy config.
* @param {EleventyConfig} config - The Eleventy configuration object.
*/
-const transformImages = (config) => {
- config.on("eleventy.after", async () => {
- await optimizeImages();
- });
+export default (config) => {
+ // Run image optimization during build
+ config.on("eleventy.after", optimizeImages);
};
-
-module.exports = { transformImages };
diff --git a/src/_flightdeck/transforms/esBuild.js b/src/_flightdeck/transforms/esBuild.js
index 678cf6b..944dd28 100644
--- a/src/_flightdeck/transforms/esBuild.js
+++ b/src/_flightdeck/transforms/esBuild.js
@@ -1,58 +1,47 @@
-// @ts-check
-
-/**
- * ESBuild Transform function
- * @module esbuild
- * @requires esbuild
- * @requires path
- */
-
-const isProd = process.env.ENV === "prod";
-const esbuild = require("esbuild");
-const path = require("node:path");
-
-/**
- * @typedef {import('@11ty/eleventy').UserConfig} EleventyConfig
- */
-
-/**
- * Adds the ESBuild transform to the Eleventy config.
- * @param {EleventyConfig} config - The Eleventy configuration object.
- */
-const transformJs = (config) => {
- config.addTemplateFormats("js");
- config.addExtension("js", {
- outputFileExtension: "js",
+/* ----------------------------------------------------------------------------
+process JS files with esbuild
+---------------------------------------------------------------------------- */
+import esbuild from 'esbuild';
+import path from 'node:path';
+
+export default (config) => {
+ config.addTemplateFormats('js');
+ config.addExtension('js', {
+ outputFileExtension: 'js',
async compile(inputContent, inputPath) {
- // Check if any directory in the path starts with an underscore
- if (inputPath.split(path.sep).some(component => component.startsWith('_'))) {
+ const baseDir = path.basename(path.dirname(inputPath));
+ if (baseDir.startsWith('_')) {
return undefined;
}
+ // Only bundle browser-side JavaScript (in assets/js)
+ if (!inputPath.includes('assets/js')) {
+ return () => inputContent;
+ }
+
const result = await esbuild.build({
entryPoints: [inputPath],
bundle: true,
- minify: isProd,
- sourcemap: !isProd,
- splitting: true,
- format: "esm",
- logLevel: "warning",
- outdir: "dist/assets/js",
- outbase: "src/assets/js",
+ minify: true,
+ sourcemap: true,
+ format: 'esm',
+ platform: 'browser',
+ logLevel: 'warning',
+ outdir: 'dist/assets/js',
+ outbase: 'src/assets/js',
metafile: true,
});
- const files = new Set();
- for (const { imports } of Object.values(result.metafile.inputs)) {
- for (const { path: filePath } of imports) {
- files.add(filePath);
+ const files = [];
+ const inputs = Object.values(result.metafile.inputs);
+ inputs.forEach((input) => {
+ const { imports } = input;
+ if (imports.length) {
+ imports.forEach((file) => files.push(file.path));
}
- }
-
- this.addDependencies(inputPath, Array.from(files));
+ });
+ this.addDependencies(inputPath, files);
return () => result.js;
},
});
-};
-
-module.exports = { transformJs };
+}
diff --git a/src/_flightdeck/transforms/lightning.js b/src/_flightdeck/transforms/lightning.js
index 315f557..5f7a36a 100644
--- a/src/_flightdeck/transforms/lightning.js
+++ b/src/_flightdeck/transforms/lightning.js
@@ -1,77 +1,37 @@
-// @ts-check
-/**
- * Configures LightningCSS for FlightDeck.
- *
- * This module sets up LightningCSS integration with the FlightDeck build process:
- * - Registers "css" as a template format.
- * - Adds a "css" extension with custom compilation logic.
- *
- * The custom compilation process:
- * - Excludes files in directories starting with "_".
- * - Bundles CSS using LightningCSS with the following features:
- * - Minification
- * - Source map generation
- * - Draft CSS features enabled (custom media queries and nesting)
- * - Resolves and tracks CSS import dependencies
- * - Returns compiled CSS code as a string
- * @module lightningCssConfig
- * @requires lightningcss
- * @requires node:path
- * @see {@link https://github.com/11ty/eleventy-plugin-bundle|eleventy-plugin-bundle} - FlightDeck's bundling plugin
- * @see {@link https://lightningcss.dev/ | LightningCSS documentation}
- * @see {@link https://11ty.rocks/posts/process-css-with-lightningcss/ | Tutorial on using LightningCSS with Eleventy}
- *
- * @param {Object} config - The Eleventy configuration object
- */
-
-
-const css = require("lightningcss");
-const path = require("node:path");
-
-module.exports = (config) => {
- // Add "css" as a template format
- config.addTemplateFormats("css");
-
- // Add a custom extension for CSS files
- config.addExtension("css", {
- outputFileExtension: "css",
+/* ----------------------------------------------------------------------------
+process CSS with LightningCSS
+---------------------------------------------------------------------------- */
+import { bundleAsync, Features } from 'lightningcss';
+import path from 'node:path';
+
+export default (config) => {
+ config.addTemplateFormats('css');
+ config.addExtension('css', {
+ outputFileExtension: 'css',
async compile(inputContent, inputPath) {
- // Exclude files in directories starting with "_"
- if (inputPath.split(path.sep).some(component => component.startsWith('_'))) {
+ const baseDir = path.basename(path.dirname(inputPath));
+ if (baseDir.startsWith('_')) {
return undefined;
}
-
- // Store imported file paths
- const files = new Set();
-
- // Enable draft syntaxes for LightningCSS
- const targets = { future: 1 };
-
- // Bundle the CSS using LightningCSS
- const result = await css.bundleAsync({
+ const files = [];
+ const targets = { future: (1) }; // enables draft syntaxes
+ const result = await bundleAsync({
filename: inputPath,
minify: true,
sourceMap: true,
- projectRoot: "../../assets/styles",
- drafts: {
- customMedia: true,
- nesting: true,
- },
+ include: Features.Nesting,
+ drafts: { customMedia: true },
resolver: {
resolve(specifier, from) {
const importPath = path.resolve(path.dirname(from), specifier);
- files.add(importPath);
- return importPath;
+ files.push(importPath);
+ return path.resolve(path.dirname(from), specifier);
},
},
targets,
});
-
- // Add imported files as dependencies
- this.addDependencies(inputPath, Array.from(files));
-
- // Return the compiled CSS code
+ this.addDependencies(inputPath, files);
return () => result.code.toString();
},
});
-};
+}
diff --git a/src/_flightdeck/transforms/markdownIt.js b/src/_flightdeck/transforms/markdownIt.js
index 7b7f456..0e0ba2b 100644
--- a/src/_flightdeck/transforms/markdownIt.js
+++ b/src/_flightdeck/transforms/markdownIt.js
@@ -5,31 +5,40 @@
* @module markdownIt
* @requires markdown-it
* @requires markdown-it-attrs
- * @requires markdown-it-bra
+ * @requires markdown-it-bracketed-spans
*
* @see {@link https://github.com/markdown-it/markdown-it}
* @see {@link https://github.com/arve0/markdown-it-attrs}
* @see {@link https://github.com/mb21/markdown-it-bracketed-spans}
*/
-const mdIt = require("markdown-it");
-const mdItAttrs = require("markdown-it-attrs");
-const mdItBracketedSpans = require("markdown-it-bracketed-spans");
+import mdIt from "markdown-it";
+import mdItAttrs from "markdown-it-attrs";
+import mdItBracketedSpans from "markdown-it-bracketed-spans";
+
/**
* @typedef {Object} MarkdownItOptions
* @property {boolean} html - Enable HTML tags in source
* @property {boolean} breaks - Convert '\n' in paragraphs into
* @property {boolean} linkify - Autoconvert URL-like text to links
- * @see {@link https://markdown-it.github.io/markdown-it/#MarkdownIt.new} for more options.
+ * @property {boolean} typographer - Enable smartquotes and other typographic replacements
*/
-/** @type {MarkdownItOptions} */
-const markdownItOptions = {
- html: true,
- breaks: true,
- linkify: true,
-};
+/**
+ * Creates a configured markdown-it instance
+ * @returns {import('markdown-it')} Configured markdown-it instance
+ */
+export default () => {
+ const options = {
+ html: true,
+ breaks: true,
+ linkify: true,
+ typographer: true
+ };
-const markdownIt = mdIt(markdownItOptions).use(mdItAttrs).use(mdItBracketedSpans);
+ const md = mdIt(options)
+ .use(mdItAttrs)
+ .use(mdItBracketedSpans);
-exports.markdownIt = markdownIt;
+ return md;
+};
diff --git a/src/_flightdeck/transforms/minifyHtml.js b/src/_flightdeck/transforms/minifyHtml.js
index acb03de..ddb08fb 100644
--- a/src/_flightdeck/transforms/minifyHtml.js
+++ b/src/_flightdeck/transforms/minifyHtml.js
@@ -6,7 +6,7 @@
* @requires html-minifier
*/
-const htmlmin = require("html-minifier");
+import htmlmin from "html-minifier";
/**
* @typedef {import('@11ty/eleventy').UserConfig} EleventyConfig
@@ -16,17 +16,20 @@ const htmlmin = require("html-minifier");
* Adds HTML minification transform to Eleventy.
* @param {EleventyConfig} config - The Eleventy configuration object.
*/
-module.exports = (config) => {
+export default (config) => {
config.addTransform("htmlMin", async (content, outputPath) => {
if (outputPath?.endsWith(".html")) {
const minified = htmlmin.minify(content, {
+ useShortDoctype: true,
+ removeComments: true,
collapseWhitespace: true,
minifyCSS: true,
- removeComments: true,
- useShortDoctype: true,
+ minifyJS: true,
});
+
return minified;
}
+
return content;
});
};
diff --git a/src/_flightdeck/workflow.js b/src/_flightdeck/workflow.js
index b0c8497..afd7ac0 100644
--- a/src/_flightdeck/workflow.js
+++ b/src/_flightdeck/workflow.js
@@ -8,7 +8,7 @@
* @see https://www.11ty.dev/docs/dev-server/
*/
-module.exports = (config, options) => {
+export default (config, options) => {
config.setQuietMode(true); // reduce console
config.setServerOptions({
port: 54321, // like Astro
diff --git a/src/_includes/data/site.js b/src/_includes/data/site.js
index f622dc1..4055832 100644
--- a/src/_includes/data/site.js
+++ b/src/_includes/data/site.js
@@ -1,6 +1,7 @@
const isDev = process.env.ENV === "development";
-const baseUrl = isDev ? "localhost:8080" : "https://github.com"; // your website url goes here
+export const baseUrl = isDev ? "localhost:8080" : "https://github.com"; // your website url goes here
+
const site = {
baseUrl,
title: "flightdeck",
@@ -25,4 +26,4 @@ const site = {
env: process.env.ENV,
};
-module.exports = site;
+export default site;
diff --git a/src/_includes/partials/footer.njk b/src/_includes/partials/footer.njk
index 77115da..2adbda3 100644
--- a/src/_includes/partials/footer.njk
+++ b/src/_includes/partials/footer.njk
@@ -1,6 +1,6 @@