Skip to content

Commit

Permalink
Prepare to bundle with 3.0 core.
Browse files Browse the repository at this point in the history
+ Add `bundles: false` option to skip built-in bundles.
+ Throws an error on incompatible eleventy versions.
+ Adds `addBundle` and `getBundleManagers` methods to eleventyConfig.
+ Add option to set file extension separate from key #22
+ Handle multiple addPlugin calls.
+ Skip transform if no bundles in play or if page being processed did not fetch bundles.
+ Changes tests to use ESM and 3.0+
  • Loading branch information
zachleat committed Apr 30, 2024
1 parent a4ddd45 commit a28cf3a
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 86 deletions.
7 changes: 6 additions & 1 deletion BundleFileOutput.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ class BundleFileOutput {
this.outputDirectory = outputDirectory;
this.bundleDirectory = bundleDirectory;
this.hashLength = 10;
this.fileExtension = undefined;
}

setFileExtension(ext) {
this.fileExtension = ext;
}

getFilenameHash(content) {
Expand Down Expand Up @@ -44,7 +49,7 @@ class BundleFileOutput {

let dir = path.join(this.outputDirectory, this.bundleDirectory);
let filenameHash = this.getFilenameHash(content);
let filename = this.getFilename(filenameHash, type);
let filename = this.getFilename(filenameHash, this.fileExtension || type);

if(writeToFileSystem) {
let fullPath = path.join(dir, filename);
Expand Down
11 changes: 10 additions & 1 deletion codeManager.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const BundleFileOutput = require("./BundleFileOutput");
const debug = require("debug")("Eleventy:Bundle");

const DEBUG_LOG_TRUNCATION_SIZE = 200;

class CodeManager {
// code is placed in this bucket by default
static DEFAULT_BUCKET_NAME = "default";
Expand All @@ -14,6 +16,11 @@ class CodeManager {
this.reset();
this.transforms = [];
this.isHoisting = true;
this.fileExtension = undefined;
}

setFileExtension(ext) {
this.fileExtension = ext;
}

setHoisting(enabled) {
Expand Down Expand Up @@ -72,7 +79,8 @@ class CodeManager {
for(let b of buckets) {
this._initBucket(pageUrl, b);

debug("Adding code to bundle %o for %o (bucket: %o): %o", this.name, pageUrl, b, codeContent);
let debugLoggedContent = codeContent.join("\n");
debug("Adding code to bundle %o for %o (bucket: %o, size: %o): %o", this.name, pageUrl, b, debugLoggedContent.length, debugLoggedContent.length > DEBUG_LOG_TRUNCATION_SIZE ? debugLoggedContent.slice(0, DEBUG_LOG_TRUNCATION_SIZE) + "…" : debugLoggedContent);
for(let content of codeContent) {
this.pages[pageUrl][b].add(content);
}
Expand Down Expand Up @@ -144,6 +152,7 @@ class CodeManager {
// 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, bundle);
writer.setFileExtension(this.fileExtension);
return writer.writeBundle(content, this.name, write);
}

Expand Down
36 changes: 27 additions & 9 deletions eleventy.bundle.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,49 @@
const pkg = require("./package.json");
const bundleManagersPlugin = require("./eleventy.bundleManagers.js");
const shortcodesPlugin = require("./eleventy.shortcodes.js");
const debug = require("debug")("Eleventy:Bundle");

function normalizeOptions(options = {}) {
options = Object.assign({
// Plugin defaults
bundles: [], // extra bundles: css, js, and html are guaranteed
bundles: [], // extra bundles: css, js, and html are guaranteed unless `bundles: false`
toFileDirectory: "bundle",
// post-process
transforms: [],
hoistDuplicateBundlesFor: [],
}, options);

options.bundles = Array.from(new Set(["css", "js", "html", ...(options.bundles || [])]));
if(options.bundles !== false) {
options.bundles = Array.from(new Set(["css", "js", "html", ...(options.bundles || [])]));
}

return options;
}

function eleventyBundlePlugin(eleventyConfig, options = {}) {
try {
eleventyConfig.versionCheck(pkg["11ty"].compatibility);
} catch(e) {
console.log( `WARN: Eleventy Plugin (${pkg.name}) Compatibility: ${e.message}` );
function eleventyBundlePlugin(eleventyConfig, pluginOptions = {}) {
eleventyConfig.versionCheck(pkg["11ty"].compatibility);

pluginOptions = normalizeOptions(pluginOptions);

if(!("getBundleManagers" in eleventyConfig) && !("addBundle" in eleventyConfig)) {
bundleManagersPlugin(eleventyConfig, pluginOptions);
}

options = normalizeOptions(options);
shortcodesPlugin(eleventyConfig, pluginOptions);

shortcodesPlugin(eleventyConfig, options);
if(Array.isArray(pluginOptions.bundles)) {
debug("Adding bundles via `addPlugin`: %o", pluginOptions.bundles)
pluginOptions.bundles.forEach(name => {
let hoist = Array.isArray(pluginOptions.hoistDuplicateBundlesFor) && pluginOptions.hoistDuplicateBundlesFor.includes(name);

eleventyConfig.addBundle(name, {
hoist,
outputFileExtension: name, // default as `name`
shortcodeName: name, // `false` will skip shortcode
transforms: pluginOptions.transforms,
});
});
}
};

// This is used to find the package name for this plugin (used in eleventy-plugin-webc to prevent dupes)
Expand Down
59 changes: 59 additions & 0 deletions eleventy.bundleManagers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

const pkg = require("./package.json");
const CodeManager = require("./codeManager.js");
const debug = require("debug")("Eleventy:Bundle");

module.exports = function(eleventyConfig, pluginOptions = {}) {
if("getBundleManagers" in eleventyConfig || "addBundle" in eleventyConfig) {
throw new Error("Duplicate plugin calls for " + pkg.name);
}

let managers = {};

function addBundle(name, bundleOptions = {}) {
if(name in managers) {
debug("Bundle exists %o, skipping.", name);
// note: shortcode must still be added
} else {
debug("Creating new bundle %o", name);
managers[name] = new CodeManager(name);

if(bundleOptions.hoist !== undefined) {
managers[name].setHoisting(bundleOptions.hoist);
}

if(bundleOptions.outputFileExtension) {
managers[name].setFileExtension(bundleOptions.outputFileExtension);
}

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, urlOverride) {
let url = urlOverride || this.page.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();
}
});
};
64 changes: 30 additions & 34 deletions eleventy.shortcodes.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,18 @@
const CodeManager = require("./codeManager.js");
const OutOfOrderRender = require("./outOfOrderRender.js");
const debug = require("debug")("Eleventy:Bundle");

module.exports = function(eleventyConfig, options = {}) {
// TODO throw an error if addPlugin is called more than once per build here.

let managers = {};

options.bundles.forEach(name => {
managers[name] = new CodeManager(name);

if(Array.isArray(options.hoistDuplicateBundlesFor) && options.hoistDuplicateBundlesFor.includes(name)) {
managers[name].setHoisting(true);
} else {
managers[name].setHoisting(false);
}

managers[name].setTransforms(options.transforms);

// e.g. `css` shortcode to add code to page bundle
// These shortcode names are not configurable on purpose (for wider plugin compatibility)
eleventyConfig.addPairedShortcode(name, function addContent(content, bucket, urlOverride) {
let url = urlOverride || this.page.url;
managers[name].addToPage(url, content, bucket);
return "";
});
});

module.exports = function(eleventyConfig, pluginOptions = {}) {
let managers = eleventyConfig.getBundleManagers();
let writeToFileSystem = true;
let pagesUsingBundles = {};

eleventyConfig.on("eleventy.before", async ({ outputMode }) => {
for(let key in managers) {
managers[key].reset();
if(Object.keys(managers).length === 0) {
return;
}

pagesUsingBundles = {};

if(outputMode !== "fs") {
writeToFileSystem = false;
debug("Skipping writing to the file system due to output mode: %o", outputMode);
Expand All @@ -43,8 +23,12 @@ module.exports = function(eleventyConfig, options = {}) {
// bucket can be an array
// This shortcode name is not configurable on purpose (for wider plugin compatibility)
eleventyConfig.addShortcode("getBundle", function getContent(type, bucket) {
if(!type || !(type in managers)) {
throw new Error("Invalid bundle type: " + type);
if(!type || !(type in managers) || Object.keys(managers).length === 0) {
throw new Error(`Invalid bundle type: ${type}. Available options: ${Object.keys(managers)}`);
}

if(this.page.url) {
pagesUsingBundles[this.page.url] = true;
}

return OutOfOrderRender.getAssetKey("get", type, bucket);
Expand All @@ -53,28 +37,40 @@ module.exports = function(eleventyConfig, options = {}) {
// 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) {
if(!type || !(type in managers)) {
throw new Error("Invalid bundle type: " + type);
if(!type || !(type in managers) || Object.keys(managers).length === 0) {
throw new Error(`Invalid bundle type: ${type}. Available options: ${Object.keys(managers)}`);
}

if(this.page.url) {
pagesUsingBundles[this.page.url] = true;
}

return OutOfOrderRender.getAssetKey("file", type, bucket);
});

eleventyConfig.addTransform("@11ty/eleventy-bundle", async function(content) {
// `page.outputPath` is required to perform bundle transform, unless
// we're running in an Eleventy Serverless context.
// we're running in Eleventy Serverless.
let missingOutputPath = !this.page.outputPath && process.env.ELEVENTY_SERVERLESS !== "true";
if(missingOutputPath || typeof content !== "string") {
return content;
}

// Only run if managers are in play
// Only run on pages that have fetched bundles via `getBundle` or `getBundleFileUrl`
if(Object.keys(managers).length === 0 || this.page.url && !pagesUsingBundles[this.page.url]) {
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.dir.output);
render.setBundleDirectory(options.toFileDirectory);
render.setBundleDirectory(pluginOptions.toFileDirectory);
render.setWriteToFileSystem(writeToFileSystem);

return render.replaceAll(this.page);
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Little bundles of code, little bundles of joy.",
"main": "eleventy.bundle.js",
"scripts": {
"sample": "DEBUG=Eleventy:Bundle npx @11ty/eleventy --config=sample/sample-config.js --input=sample",
"sample": "DEBUG=Eleventy:Bundle npx @11ty/eleventy --config=sample/sample-config.js --input=sample --serve",
"test": "npx ava"
},
"publishConfig": {
Expand All @@ -15,7 +15,7 @@
"node": ">=14"
},
"11ty": {
"compatibility": ">=2.0.0-beta.1"
"compatibility": ">=3.0.0-alpha"
},
"funding": {
"type": "opencollective",
Expand All @@ -39,15 +39,16 @@
"ava": {
"failFast": true,
"files": [
"test/*.js"
"test/*.js",
"test/*.mjs"
],
"ignoredByWatcher": [
"**/_site/**",
".cache"
]
},
"devDependencies": {
"@11ty/eleventy": "^2.0.0",
"@11ty/eleventy": "3.0.0-alpha.9",
"ava": "^5.3.1",
"postcss": "^8.4.31",
"postcss-nested": "^6.0.1",
Expand Down
25 changes: 24 additions & 1 deletion sample/sample-config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
const bundlePlugin = require("../");

module.exports = function(eleventyConfig) {
// This call is what Eleventy will do in the default config in 3.0.0-alpha.10
eleventyConfig.addPlugin(bundlePlugin, {
bundles: false,
immediate: true
});

// adds html, css, js (maintain existing API)
eleventyConfig.addPlugin(bundlePlugin);
eleventyConfig.addPlugin(bundlePlugin);

// ignored, already exists
eleventyConfig.addBundle("css");
// ignored, already exists
eleventyConfig.addBundle("css");
// ignored, already exists
eleventyConfig.addBundle("css");
// ignored, already exists
eleventyConfig.addBundle("html");

// new!
eleventyConfig.addBundle("stylesheet", {
outputFileExtension: "css",
shortcodeName: "stylesheet",
transforms: [],
// hoist: true,
});
};
4 changes: 3 additions & 1 deletion sample/test.njk
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
{%- css %}* { color: blue; }{% endcss %}
{%- css %}* { color: red; }{% endcss %}
<style>{% getBundle "css" %}</style>
{%- stylesheet %}/* lololololol sdlkfjkdlsfsldkjflksd sdlfkj */{% endstylesheet %}

<svg width="0" height="0" aria-hidden="true" style="position: absolute;">
<defs>{%- getBundle "html", "svg" %}</defs>
Expand All @@ -21,4 +22,5 @@ And now you can use `icon-close` in as many SVG instances as you’d like (witho
<svg><use xlink:href="#icon-close"></use></svg>
<svg><use xlink:href="#icon-close"></use></svg>
<svg><use xlink:href="#icon-close"></use></svg>
<svg><use xlink:href="#icon-close"></use></svg>
<svg><use xlink:href="#icon-close"></use></svg>
<style>{% getBundle "stylesheet" %}</style>
Loading

0 comments on commit a28cf3a

Please sign in to comment.