Skip to content

Commit

Permalink
Improvements for upgrade helper plugin #3358 Notably adds a better er…
Browse files Browse the repository at this point in the history
…ror message for requiring Eleventy in a CommonJS project and will work as expected when this support is added natively too.
  • Loading branch information
zachleat committed Jul 24, 2024
1 parent 7a04092 commit c505822
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 25 deletions.
26 changes: 14 additions & 12 deletions cmd.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ require("please-upgrade-node")(pkg, {
},
});

const minimist = require("minimist");
const debug = require("debug")("Eleventy:cmd");

(async function () {
const { default: EleventyErrorHandler } = await import("./src/Errors/EleventyErrorHandler.js");
const { EleventyErrorHandler } = await import("./src/Errors/EleventyErrorHandler.js");

try {
let errorHandler = new EleventyErrorHandler();
const argv = require("minimist")(process.argv.slice(2), {
const argv = minimist(process.argv.slice(2), {
string: ["input", "output", "formats", "config", "pathprefix", "port", "to", "incremental"],
boolean: [
"quiet",
Expand All @@ -48,14 +48,16 @@ const debug = require("debug")("Eleventy:cmd");
debug("command: eleventy %o", argv);
const { Eleventy } = await import("./src/Eleventy.js");

let ErrorHandler = new EleventyErrorHandler();

process.on("unhandledRejection", (error, promise) => {
errorHandler.fatal(error, "Unhandled rejection in promise");
ErrorHandler.fatal(error, "Unhandled rejection in promise");
});
process.on("uncaughtException", (error) => {
errorHandler.fatal(error, "Uncaught exception");
ErrorHandler.fatal(error, "Uncaught exception");
});
process.on("rejectionHandled", (promise) => {
errorHandler.warn(promise, "A promise rejection was handled asynchronously");
ErrorHandler.warn(promise, "A promise rejection was handled asynchronously");
});

if (argv.version) {
Expand All @@ -74,7 +76,7 @@ const debug = require("debug")("Eleventy:cmd");
});

// reuse ErrorHandler instance in Eleventy
errorHandler = elev.errorHandler;
ErrorHandler = elev.errorHandler;

// Before init
elev.setFormats(argv.formats);
Expand Down Expand Up @@ -112,7 +114,7 @@ const debug = require("debug")("Eleventy:cmd");
// Build failed but error message already displayed.
shouldStartServer = false;
// A build error occurred and we aren’t going to --serve
errorHandler.fatal(e, "Eleventy CLI Error");
ErrorHandler.fatal(e, "Eleventy CLI Error");
});

process.on("SIGINT", async () => {
Expand All @@ -138,12 +140,12 @@ const debug = require("debug")("Eleventy:cmd");
}
}
} catch (e) {
errorHandler.fatal(e, "Eleventy CLI Error");
ErrorHandler.fatal(e, "Eleventy CLI Error");
}
}, errorHandler.fatal.bind(errorHandler));
}, ErrorHandler.fatal.bind(ErrorHandler));
}
} catch (e) {
let errorHandler = new EleventyErrorHandler();
errorHandler.fatal(e, "Eleventy CLI Fatal Error");
let ErrorHandler = new EleventyErrorHandler();
ErrorHandler.fatal(e, "Eleventy CLI Fatal Error");
}
})();
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
"publishConfig": {
"access": "public"
},
"main": "src/Eleventy.js",
"type": "module",
"main": "src/Eleventy.js",
"exports": {
"import": "./src/Eleventy.js",
"require": "./src/EleventyCommonJsRequireError.cjs"
},
"bin": {
"eleventy": "cmd.cjs"
},
Expand Down
21 changes: 20 additions & 1 deletion src/Eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { filesize } from "filesize";
import TemplateData from "./Data/TemplateData.js";
import TemplateWriter from "./TemplateWriter.js";
import EleventyExtensionMap from "./EleventyExtensionMap.js";
import EleventyErrorHandler from "./Errors/EleventyErrorHandler.js";
import { EleventyErrorHandler } from "./Errors/EleventyErrorHandler.js";
import EleventyBaseError from "./Errors/EleventyBaseError.js";
import EleventyServe from "./EleventyServe.js";
import EleventyWatch from "./EleventyWatch.js";
Expand Down Expand Up @@ -1378,10 +1378,29 @@ Object.assign(RenderPlugin, RenderPluginExtras);
Object.assign(I18nPlugin, I18nPluginExtras);
Object.assign(HtmlBasePlugin, HtmlBasePluginExtras);

// Removed plugins

const EleventyServerlessBundlerPlugin = function () {
throw new Error(
"Following feedback from our Community Survey, low interest in this plugin prompted its removal from Eleventy core in 3.0 as we refocus on static sites. Learn more: https://www.11ty.dev/docs/plugins/serverless/",
);
};

const EleventyEdgePlugin = function () {
throw new Error(
"Following feedback from our Community Survey, low interest in this plugin prompted its removal from Eleventy core in 3.0 as we refocus on static sites. Learn more: https://www.11ty.dev/docs/plugins/edge/",
);
};

export {
Eleventy,
EleventyImport as ImportFile,

// Error messages for removed plugins
EleventyServerlessBundlerPlugin as EleventyServerless,
EleventyServerlessBundlerPlugin,
EleventyEdgePlugin,

/**
* @type {module:11ty/eleventy/Plugins/RenderPlugin}
*/
Expand Down
32 changes: 32 additions & 0 deletions src/EleventyCommonJsRequireError.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
function canRequireModules() {
// via --experimental-require-module or newer than Node 22 support when this flag is no longer necessary
try {
require("./Util/Objects/SampleModule.mjs");
return true;
} catch(e) {
// Do nothing
}

return false;
}

if(!canRequireModules()) {
let error = new Error(`Eleventy cannot be loaded via \`require("@11ty/eleventy")\` in 3.0 and newer. Instead, you have a few options:
1. (Easiest) Change the \`require\` to use a dynamic import inside of an asynchronous CommonJS configuration callback, for example:
module.exports = async function {
const {EleventyRenderPlugin, EleventyI18nPlugin, EleventyHtmlBasePlugin} = await import("@11ty/eleventy");
}
2. (Easier) Update the JavaScript syntax in your configuration file from CommonJS to ESM (change \`require\` to \`import\` and rename the file to have an .mjs file extension).
3. (More work) Change your project to use ESM-first by adding \`"type": "module"\` to your package.json. Any \`.js\` will need to be ported to use ESM syntax (or renamed to \`.cjs\`.)
4. (Short term workaround) Use the --experimental-require-module flag to enable this behavior. Read more: https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require It is possible that the newest version of Node has this enabled by default—you can try upgrading your version of Node.js.`);

error.skipOriginalStack = true;

throw error;
}

module.exports = (async function() {
return await import("@11ty/eleventy");
})();
25 changes: 17 additions & 8 deletions src/Errors/EleventyErrorHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class EleventyErrorHandler {
debug("Full error object: %o", util.inspect(e, { showHidden: false, depth: null }));
}

let showStack = true;
let totalErrorCount = EleventyErrorHandler.getTotalErrorCount(e);
let ref = e;
let index = 1;
Expand All @@ -82,10 +83,15 @@ class EleventyErrorHandler {
if (!nextRef && EleventyErrorUtil.hasEmbeddedError(ref.message)) {
nextRef = EleventyErrorUtil.deconvertErrorToObject(ref);
}

if (nextRef?.skipOriginalStack) {
showStack = false;
}

this.logger.message(
`${totalErrorCount > 1 ? `${index}. ` : ""}${(
EleventyErrorUtil.cleanMessage(ref.message) || "(No error message provided)"
).trim()} (via ${ref.name})`,
).trim()}${ref.name !== "Error" ? ` (via ${ref.name})` : ""}`,
type,
chalkColor,
forceToConsole,
Expand All @@ -104,12 +110,15 @@ class EleventyErrorHandler {
"(Repeated output has been truncated…)",
);
}
this.logger.message(
"\nOriginal error stack trace: " + stackStr,
type,
chalkColor,
forceToConsole,
);

if (showStack) {
this.logger.message(
"\nOriginal error stack trace: " + stackStr,
type,
chalkColor,
forceToConsole,
);
}
}
ref = nextRef;
index++;
Expand All @@ -128,4 +137,4 @@ class EleventyErrorHandler {
}
}

export default EleventyErrorHandler;
export { EleventyErrorHandler };
2 changes: 1 addition & 1 deletion src/TemplateWriter.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import TemplateMap from "./TemplateMap.js";
import EleventyFiles from "./EleventyFiles.js";
import EleventyExtensionMap from "./EleventyExtensionMap.js";
import EleventyBaseError from "./Errors/EleventyBaseError.js";
import EleventyErrorHandler from "./Errors/EleventyErrorHandler.js";
import { EleventyErrorHandler } from "./Errors/EleventyErrorHandler.js";
import EleventyErrorUtil from "./Errors/EleventyErrorUtil.js";
import FileSystemSearch from "./FileSystemSearch.js";
import ConsoleLogger from "./Util/ConsoleLogger.js";
Expand Down
1 change: 1 addition & 0 deletions src/Util/Objects/SampleModule.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default {};
4 changes: 4 additions & 0 deletions src/Util/ProjectTemplateFormats.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class ProjectTemplateFormats {
return final;
}

isWildcard() {
return this.#isUseAll.cli || this.#isUseAll.config || false;
}

#isUseAll(rawFormats) {
if (rawFormats === "") {
return false;
Expand Down
4 changes: 2 additions & 2 deletions test/EleventyErrorHandlerTest.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import test from "ava";
import EleventyErrorHandler from "../src/Errors/EleventyErrorHandler.js";
import { EleventyErrorHandler } from "../src/Errors/EleventyErrorHandler.js";

test("Log a warning, warning", (t) => {
let errorHandler = new EleventyErrorHandler();
Expand Down Expand Up @@ -46,7 +46,7 @@ test("Log a warning, error", (t) => {
errorHandler.error(new Error("Test error"), "It’s me");

let expected = `It’s me: (more in DEBUG output)
Test error (via Error)
Test error
Original error stack trace: Error: Test error`;
t.is(output.join("\n").slice(0, expected.length), expected);
Expand Down

0 comments on commit c505822

Please sign in to comment.