From 1abffef98dda2f9580b2ca3052b923780eda765b Mon Sep 17 00:00:00 2001 From: Zach Leatherman Date: Fri, 28 Jun 2024 15:55:18 -0500 Subject: [PATCH] Fixes #3338 --- src/Eleventy.js | 11 +++++++---- src/EleventyWatchTargets.js | 23 ++++++++++++----------- src/Engines/JavaScript.js | 23 ++++++++++++++++------- src/GlobalDependencyMap.js | 23 ++++++++++++++++++++--- src/TemplateConfig.js | 16 ++++++++++++++++ src/Util/EsmResolver.js | 4 +++- test/EleventyTest.js | 4 +++- test/EleventyWatchTargetsTest.js | 4 +++- 8 files changed, 80 insertions(+), 28 deletions(-) diff --git a/src/Eleventy.js b/src/Eleventy.js index e75b04acc..e6dab1218 100644 --- a/src/Eleventy.js +++ b/src/Eleventy.js @@ -139,6 +139,7 @@ class Eleventy { await this.eleventyConfig.setProjectConfigPath(this.options.configPath); } + this.eleventyConfig.setRunMode(this.runMode); this.eleventyConfig.setProjectUsingEsm(this.isEsm); this.eleventyConfig.setLogger(this.logger); this.eleventyConfig.setDirectories(this.directories); @@ -204,9 +205,8 @@ class Eleventy { this.watchManager = new EleventyWatch(); /** @member {Object} - tbd. */ - this.watchTargets = new EleventyWatchTargets(); + this.watchTargets = new EleventyWatchTargets(this.eleventyConfig); this.watchTargets.addAndMakeGlob(this.config.additionalWatchTargets); - this.watchTargets.watchJavaScriptDependencies = this.config.watchJavaScriptDependencies; /** @member {Object} - tbd. */ this.fileSystemSearch = new FileSystemSearch(); @@ -801,7 +801,10 @@ Arguments: // Note: these are sync events! // `templateModified` is an alias for resourceModified but all listeners for this are cleared out when the config is reset. - eventBus.emit("eleventy.templateModified", changedFilePath, usedByDependants); + eventBus.emit("eleventy.templateModified", changedFilePath, { + usedByDependants, + relevantLayouts, + }); eventBus.emit("eleventy.resourceModified", changedFilePath, usedByDependants, { viaConfigReset: isResetConfig, relevantLayouts, @@ -1004,7 +1007,7 @@ Arguments: * @method */ async _initWatchDependencies() { - if (!this.watchTargets.watchJavaScriptDependencies) { + if (!this.eleventyConfig.shouldSpiderJavaScriptDependencies()) { return; } diff --git a/src/EleventyWatchTargets.js b/src/EleventyWatchTargets.js index c090bfee5..aec203693 100644 --- a/src/EleventyWatchTargets.js +++ b/src/EleventyWatchTargets.js @@ -5,22 +5,16 @@ import JavaScriptDependencies from "./Util/JavaScriptDependencies.js"; import eventBus from "./EventBus.js"; class EleventyWatchTargets { - constructor() { + #templateConfig; + + constructor(templateConfig) { this.targets = new Set(); this.dependencies = new Set(); this.newTargets = new Set(); - this._watchJavaScriptDependencies = true; this.isEsm = false; this.graph = new DepGraph(); - } - - set watchJavaScriptDependencies(watch) { - this._watchJavaScriptDependencies = !!watch; - } - - get watchJavaScriptDependencies() { - return this._watchJavaScriptDependencies; + this.#templateConfig = templateConfig; } setProjectUsingEsm(isEsmProject) { @@ -111,7 +105,7 @@ class EleventyWatchTargets { // add only a target’s dependencies async addDependencies(targets, filterCallback) { - if (!this.watchJavaScriptDependencies) { + if (this.#templateConfig && !this.#templateConfig.shouldSpiderJavaScriptDependencies()) { return; } @@ -146,6 +140,13 @@ class EleventyWatchTargets { for (let dep of isImportedInTheChangedFile) { paths.add(dep); } + + // Use GlobalDependencyMap + if (this.#templateConfig) { + for (let dep of this.#templateConfig.usesGraph.getDependantsFor(filePath)) { + paths.add(dep); + } + } } eventBus.emit("eleventy.importCacheReset", paths); diff --git a/src/Engines/JavaScript.js b/src/Engines/JavaScript.js index c43fb7bc1..77eede3bd 100644 --- a/src/Engines/JavaScript.js +++ b/src/Engines/JavaScript.js @@ -17,9 +17,14 @@ class JavaScript extends TemplateEngine { this.instances = {}; this.cacheable = false; - EventBusUtil.soloOn("eleventy.templateModified", (inputPath, usedByDependants = []) => { + EventBusUtil.soloOn("eleventy.templateModified", (inputPath, metadata = {}) => { + let { usedByDependants, relevantLayouts } = metadata; // Remove from cached instances when modified - let instancesToDelete = [TemplatePath.addLeadingDotSlash(inputPath), ...usedByDependants]; + let instancesToDelete = [ + inputPath, + ...(usedByDependants || []), + ...(relevantLayouts || []), + ].map((entry) => TemplatePath.addLeadingDotSlash(entry)); for (let inputPath of instancesToDelete) { if (inputPath in this.instances) { delete this.instances[inputPath]; @@ -74,11 +79,7 @@ class JavaScript extends TemplateEngine { } } - async getInstanceFromInputPath(inputPath) { - if (this.instances[inputPath]) { - return this.instances[inputPath]; - } - + async #getInstanceFromInputPath(inputPath) { let isEsm = this.eleventyConfig.getIsProjectUsingEsm(); const mod = await EleventyImport(inputPath, isEsm ? "esm" : "cjs"); @@ -93,6 +94,14 @@ class JavaScript extends TemplateEngine { return inst; } + async getInstanceFromInputPath(inputPath) { + if (!this.instances[inputPath]) { + this.instances[inputPath] = this.#getInstanceFromInputPath(inputPath); + } + + return this.instances[inputPath]; + } + /** * JavaScript files defer to the module loader rather than read the files to strings * diff --git a/src/GlobalDependencyMap.js b/src/GlobalDependencyMap.js index 76b2cf0dd..2b2f25769 100644 --- a/src/GlobalDependencyMap.js +++ b/src/GlobalDependencyMap.js @@ -2,6 +2,7 @@ import { DepGraph } from "dependency-graph"; import debugUtil from "debug"; import { TemplatePath } from "@11ty/eleventy-utils"; +import JavaScriptDependencies from "./Util/JavaScriptDependencies.js"; import PathNormalizer from "./Util/PathNormalizer.js"; const debug = debugUtil("Eleventy:Dependencies"); @@ -11,10 +12,20 @@ class GlobalDependencyMap { static LAYOUT_KEY = "layout"; static COLLECTION_PREFIX = "__collection:"; + #templateConfig; + reset() { this._map = undefined; } + setIsEsm(isEsm) { + this.isEsm = isEsm; + } + + setTemplateConfig(templateConfig) { + this.#templateConfig = templateConfig; + } + setConfig(config) { if (this.config) { return; @@ -23,8 +34,8 @@ class GlobalDependencyMap { this.config = config; // These have leading dot slashes, but so do the paths from Eleventy - this.config.events.once("eleventy.layouts", (layouts) => { - this.addLayoutsToMap(layouts); + this.config.events.once("eleventy.layouts", async (layouts) => { + await this.addLayoutsToMap(layouts); }); } @@ -60,8 +71,9 @@ class GlobalDependencyMap { } // Eleventy Layouts don’t show up in the dependency graph, so we handle those separately - addLayoutsToMap(layouts) { + async addLayoutsToMap(layouts) { let normalizedLayouts = this.normalizeLayoutsObject(layouts); + // Clear out any previous layout relationships to make way for the new ones this.removeLayoutNodes(normalizedLayouts); @@ -81,6 +93,11 @@ class GlobalDependencyMap { for (let pageTemplate of normalizedLayouts[layout]) { this.addDependency(pageTemplate, [layout]); } + + if (this.#templateConfig?.shouldSpiderJavaScriptDependencies()) { + let deps = await JavaScriptDependencies.getDependencies([layout], this.isEsm); + this.addDependency(layout, deps); + } } } diff --git a/src/TemplateConfig.js b/src/TemplateConfig.js index 7bff2fcc2..dcc76324e 100644 --- a/src/TemplateConfig.js +++ b/src/TemplateConfig.js @@ -50,6 +50,7 @@ class EleventyPluginError extends EleventyBaseError {} */ class TemplateConfig { #templateFormats; + #runMode; constructor(customRootConfig, projectConfigPath) { this.userConfig = new UserConfig(); @@ -119,6 +120,18 @@ class TemplateConfig { return this.directories.input; } + setRunMode(runMode) { + this.#runMode = runMode; + } + + shouldSpiderJavaScriptDependencies() { + // not for a standard build + return ( + (this.#runMode === "watch" || this.#runMode === "serve") && + this.userConfig.watchJavaScriptDependencies + ); + } + /** * Normalises local project config file path. * @@ -143,6 +156,7 @@ class TemplateConfig { setProjectUsingEsm(isEsmProject) { this.isEsm = !!isEsmProject; + this.usesGraph.setIsEsm(isEsmProject); } getIsProjectUsingEsm() { @@ -492,6 +506,8 @@ class TemplateConfig { get usesGraph() { if (!this._usesGraph) { this._usesGraph = new GlobalDependencyMap(); + this._usesGraph.setIsEsm(this.isEsm); + this._usesGraph.setTemplateConfig(this); } return this._usesGraph; } diff --git a/src/Util/EsmResolver.js b/src/Util/EsmResolver.js index a1c88ae8a..d42b88b49 100644 --- a/src/Util/EsmResolver.js +++ b/src/Util/EsmResolver.js @@ -17,7 +17,9 @@ export async function resolve(specifier, context, nextResolve) { // Not a relative import and not a file import // Or from node_modules (perhaps better to check if the specifier is in the project directory instead) if ( - (!specifier.startsWith("./") && !specifier.startsWith("file:")) || + (!specifier.startsWith("../") && + !specifier.startsWith("./") && + !specifier.startsWith("file:")) || context.parentURL.includes("/node_modules/") ) { return nextResolve(specifier); diff --git a/test/EleventyTest.js b/test/EleventyTest.js index 8f234f82e..62747ad23 100644 --- a/test/EleventyTest.js +++ b/test/EleventyTest.js @@ -107,7 +107,9 @@ test("Eleventy process.ENV", async (t) => { }); test("Eleventy file watching", async (t) => { - let elev = new Eleventy("./test/stubs", "./test/stubs/_site"); + let elev = new Eleventy("./test/stubs", "./test/stubs/_site", { + runMode: "watch" // required to spider deps + }); elev.setFormats("njk"); await elev.init(); diff --git a/test/EleventyWatchTargetsTest.js b/test/EleventyWatchTargetsTest.js index 828987385..d76e7321c 100644 --- a/test/EleventyWatchTargetsTest.js +++ b/test/EleventyWatchTargetsTest.js @@ -1,5 +1,6 @@ import test from "ava"; +import TemplateConfig from "../src/TemplateConfig.js"; import EleventyWatchTargets from "../src/EleventyWatchTargets.js"; import JavaScriptDependencies from "../src/Util/JavaScriptDependencies.js"; @@ -89,7 +90,8 @@ test("JavaScript addDependencies (one file has two dependencies)", async (t) => }); test("JavaScript addDependencies (skip JS deps)", async (t) => { - let targets = new EleventyWatchTargets(); + let templateConfig = new TemplateConfig(); + let targets = new EleventyWatchTargets(templateConfig); targets.setProjectUsingEsm(true); targets.watchJavaScriptDependencies = false; await targets.addDependencies("./test/stubs/dependencies/two-deps.11ty.cjs");