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 `${alt}`; + } +} 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 @@