Skip to content

Commit

Permalink
Fixes #8
Browse files Browse the repository at this point in the history
  • Loading branch information
zachleat committed Sep 11, 2024
1 parent e956f2c commit 24145ef
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 18 deletions.
4 changes: 3 additions & 1 deletion eleventy.bundle.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createRequire } from "node:module";
import bundleManagersPlugin from "./src/eleventy.bundleManagers.js";
import pruneEmptyBundlesPlugin from "./src/eleventy.pruneEmptyBundles.js";
import shortcodesPlugin from "./src/eleventy.shortcodes.js";
import debugUtil from "debug";

Expand Down Expand Up @@ -34,10 +35,11 @@ function eleventyBundlePlugin(eleventyConfig, pluginOptions = {}) {
bundleManagersPlugin(eleventyConfig, pluginOptions);
}

pruneEmptyBundlesPlugin(eleventyConfig, pluginOptions);

// should this be unique too?
shortcodesPlugin(eleventyConfig, pluginOptions);


if(Array.isArray(pluginOptions.bundles)) {
debug("Adding bundles via `addPlugin`: %o", pluginOptions.bundles)
pluginOptions.bundles.forEach(name => {
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,14 @@
]
},
"devDependencies": {
"@11ty/eleventy": "3.0.0-alpha.19",
"@11ty/eleventy": "3.0.0-alpha.20",
"ava": "^5.3.1",
"postcss": "^8.4.31",
"postcss-nested": "^6.0.1",
"sass": "^1.69.5"
},
"dependencies": {
"debug": "^4.3.4"
"debug": "^4.3.4",
"posthtml-match-helper": "^2.0.2"
}
}
88 changes: 88 additions & 0 deletions src/eleventy.pruneEmptyBundles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import matchHelper from "posthtml-match-helper";
import debugUtil from "debug";

const debug = debugUtil("Eleventy:Bundle");

const ATTRS = {
keep: "eleventy:keep"
}

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"]`;

// `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 (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;
});
};
},
{
// 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;
107 changes: 92 additions & 15 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import test from "ava";
import fs from "fs";
import Eleventy, { RenderPlugin } from "@11ty/eleventy";
import * as sass from "sass";
import bundlePlugin from "../eleventy.bundle.js";

function normalize(str) {
if(typeof str !== "string") {
Expand Down Expand Up @@ -56,7 +57,7 @@ test("SVG", async t => {
let elev = new Eleventy("test/stubs/nunjucks-svg/", "_site", { configPath: "eleventy.bundle.js" });
let results = await elev.toJSON();
t.deepEqual(normalize(results[0].content), `<svg width="0" height="0" aria-hidden="true" style="position: absolute;">
<defs><g id="icon-close-legacy"><path d="M8,15 C4.13400675,15 1,11.8659932 1,8 C1,4.13400675 4.13400675,1 8,1 C11.8659932,1 15,4.13400675 15,8 C15,11.8659932 11.8659932,15 8,15 Z M10.44352,10.7233105 L10.4528296,10.7326201 L10.7326201,10.4528296 C11.0310632,10.1543865 11.0314986,9.66985171 10.7335912,9.37194437 L9.36507937,8.0034325 L10.7360526,6.63245928 C11.0344957,6.33401613 11.0349311,5.84948135 10.7370237,5.55157401 L10.448426,5.26297627 C10.1505186,4.96506892 9.66598387,4.96550426 9.36754072,5.26394741 L8.00589385,6.62559428 L6.63738198,5.25708241 C6.33947464,4.95917507 5.85493986,4.95961041 5.55649671,5.25805356 L5.26737991,5.54717036 C4.96893676,5.84561351 4.96850142,6.33014829 5.26640876,6.62805563 L6.62561103,7.9872579 L5.25463781,9.35823112 C4.95619466,9.65667427 4.95575932,10.141209 5.25366666,10.4391164 L5.5422644,10.7277141 C5.84017175,11.0256215 6.32470652,11.0251861 6.62314967,10.726743 L7.99412289,9.35576976 L9.36263476,10.7242816 C9.66054211,11.022189 10.1450769,11.0217536 10.44352,10.7233105 Z" /></g></defs>
<defs><g id="icon-close-legacy"><path d="M8,15 C4.13400675,15 1,11.8659932 1,8 C1,4.13400675 4.13400675,1 8,1 C11.8659932,1 15,4.13400675 15,8 C15,11.8659932 11.8659932,15 8,15 Z M10.44352,10.7233105 L10.4528296,10.7326201 L10.7326201,10.4528296 C11.0310632,10.1543865 11.0314986,9.66985171 10.7335912,9.37194437 L9.36507937,8.0034325 L10.7360526,6.63245928 C11.0344957,6.33401613 11.0349311,5.84948135 10.7370237,5.55157401 L10.448426,5.26297627 C10.1505186,4.96506892 9.66598387,4.96550426 9.36754072,5.26394741 L8.00589385,6.62559428 L6.63738198,5.25708241 C6.33947464,4.95917507 5.85493986,4.95961041 5.55649671,5.25805356 L5.26737991,5.54717036 C4.96893676,5.84561351 4.96850142,6.33014829 5.26640876,6.62805563 L6.62561103,7.9872579 L5.25463781,9.35823112 C4.95619466,9.65667427 4.95575932,10.141209 5.25366666,10.4391164 L5.5422644,10.7277141 C5.84017175,11.0256215 6.32470652,11.0251861 6.62314967,10.726743 L7.99412289,9.35576976 L9.36263476,10.7242816 C9.66054211,11.022189 10.1450769,11.0217536 10.44352,10.7233105 Z"></path></g></defs>
</svg>`)
});

Expand Down Expand Up @@ -90,8 +91,7 @@ test("CSS, two buckets, explicit `default`", async t => {
t.deepEqual(normalize(results[0].content), `<style>* { color: blue; }
* { color: orange; }</style>
<style>* { color: blue; }
* { color: red; }</style>
<style></style>`)
* { color: red; }</style>`)
});

test("CSS, get two buckets at once", async t => {
Expand Down Expand Up @@ -185,7 +185,7 @@ test("toFile Filter (write files, out of order)", async t => {
test("Bundle in Layout file", async t => {
let elev = new Eleventy("test/stubs/bundle-in-layout/", "_site", { configPath: "eleventy.bundle.js" });
let results = await elev.toJSON();
t.deepEqual(normalize(results[0].content), `<!doctype html><html><head><link href="https://v1.opengraph.11ty.dev" rel="preconnect" crossorigin></head></html>`);
t.deepEqual(normalize(results[0].content), `<!doctype html><html><head><link href="https://v1.opengraph.11ty.dev" rel="preconnect" crossorigin=""></head></html>`);
});

test("Bundle with render plugin", async t => {
Expand Down Expand Up @@ -275,9 +275,7 @@ test("Output `defer` bucket multiple times (does hoisting)", async t => {

let results = await elev.toJSON();
t.deepEqual(normalize(results[0].content), `<style>* { color: blue; }
* { color: red; }</style>
<style></style>
<style></style>`);
* { color: red; }</style>`);
});

test("Output `default` bucket multiple times (no hoisting)", async t => {
Expand All @@ -303,17 +301,12 @@ test("`defer` hoisting", async t => {
return -1;
})

t.deepEqual(normalize(results[0].content), `<style></style>
<link rel="stylesheet" href="/bundle/S_XQgJiZBL.css">
t.deepEqual(normalize(results[0].content), `<link rel="stylesheet" href="/bundle/S_XQgJiZBL.css">
<link rel="stylesheet" href="/bundle/S_XQgJiZBL.css">`);

t.deepEqual(normalize(results[1].content), `<style>* { color: blue; }</style>
<style></style>
<link rel="stylesheet" href="">`);
t.deepEqual(normalize(results[1].content), `<style>* { color: blue; }</style>`);

t.deepEqual(normalize(results[2].content), `<style>* { color: blue; }</style>
<style></style>
<style></style>`);
t.deepEqual(normalize(results[2].content), `<style>* { color: blue; }</style>`);
});

test("Bundle export key as string (11ty.js)", async t => {
Expand All @@ -333,3 +326,87 @@ test("Bundle export key as string, using separate bundleExportKey’s (11ty.js)"
let results = await elev.toJSON();
t.deepEqual(normalize(results[0].content), `<style>/* CSS */</style><script>/* JS */</script>`)
});

test("Empty CSS bundle (trimmed) removes empty <style> tag", async t => {
let elev = new Eleventy("test/stubs-virtual/", "_site", {
config: function(eleventyConfig) {
eleventyConfig.addPlugin(bundlePlugin);

eleventyConfig.addTemplate('test.njk', `<div></div><style>{% getBundle "css" %}</style>
{%- css %} {% endcss %}`)
}
});
let results = await elev.toJSON();
t.deepEqual(normalize(results[0].content), `<div></div>`)
});

test("Empty JS bundle (trimmed) removes empty <script> tag", async t => {
let elev = new Eleventy("test/stubs-virtual/", "_site", {
config: function(eleventyConfig) {
eleventyConfig.addPlugin(bundlePlugin);

eleventyConfig.addTemplate('test.njk', `<div></div><script>{% getBundle "css" %}</script>
{%- js %} {% endjs %}`)
}
});
let results = await elev.toJSON();
t.deepEqual(normalize(results[0].content), `<div></div>`)
});

test("Empty CSS bundle (trimmed) removes empty <link rel=stylesheet> tag", async t => {
let elev = new Eleventy("test/stubs-virtual/", "_site", {
config: function(eleventyConfig) {
eleventyConfig.addPlugin(bundlePlugin);

eleventyConfig.addTemplate('test.njk', `<div></div><link rel="stylesheet" href="{% getBundleFileUrl "css" %}">
{%- css %} {% endcss %}`)
}
});
let results = await elev.toJSON();
t.deepEqual(normalize(results[0].content), `<div></div>`)
});

test("Empty CSS bundle (trimmed) does *not* remove empty <style> tag", async t => {
let elev = new Eleventy("test/stubs-virtual/", "_site", {
config: function(eleventyConfig) {
eleventyConfig.addPlugin(bundlePlugin, {
pruneEmptySelector: false,
});

eleventyConfig.addTemplate('test.njk', `<div></div><style>{% getBundle "css" %}</style>
{%- css %} {% endcss %}`)
}
});
let results = await elev.toJSON();
t.deepEqual(normalize(results[0].content), `<div></div><style></style>`)
});

test("Empty CSS bundle (trimmed) does *not* remove empty <style eleventy:keep> tag", async t => {
let elev = new Eleventy("test/stubs-virtual/", "_site", {
config: function(eleventyConfig) {
eleventyConfig.addPlugin(bundlePlugin);

eleventyConfig.addTemplate('test.njk', `<div></div><style eleventy:keep>{% getBundle "css" %}</style>
{%- css %} {% endcss %}`)
}
});

let results = await elev.toJSON();
t.deepEqual(normalize(results[0].content), `<div></div><style></style>`)
});

// This one requires a new Eleventy v3.0.0-alpha.20 or -beta.2 release, per the `enabled` option on plugins to HtmlTransformer
test("<style> is left as-is (not removed) because there are *no* bundles", async t => {
let elev = new Eleventy("test/stubs-virtual/", "_site", {
config: function(eleventyConfig) {
eleventyConfig.addPlugin(bundlePlugin, {
bundles: false,
});

eleventyConfig.addTemplate('test.njk', `<div></div><style></style>`)
}
});

let results = await elev.toJSON();
t.deepEqual(normalize(results[0].content), `<div></div><style></style>`)
});

0 comments on commit 24145ef

Please sign in to comment.