1566 lines
42 KiB
JavaScript
1566 lines
42 KiB
JavaScript
|
|
import chalk from "kleur";
|
|||
|
|
import { performance } from "node:perf_hooks";
|
|||
|
|
import debugUtil from "debug";
|
|||
|
|
import { filesize } from "filesize";
|
|||
|
|
import path from "node:path";
|
|||
|
|
|
|||
|
|
/* Eleventy Deps */
|
|||
|
|
import { TemplatePath } from "@11ty/eleventy-utils";
|
|||
|
|
import BundlePlugin from "@11ty/eleventy-plugin-bundle";
|
|||
|
|
|
|||
|
|
import TemplateData from "./Data/TemplateData.js";
|
|||
|
|
import TemplateWriter from "./TemplateWriter.js";
|
|||
|
|
import EleventyExtensionMap from "./EleventyExtensionMap.js";
|
|||
|
|
import { EleventyErrorHandler } from "./Errors/EleventyErrorHandler.js";
|
|||
|
|
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
|||
|
|
import EleventyServe from "./EleventyServe.js";
|
|||
|
|
import EleventyWatch from "./EleventyWatch.js";
|
|||
|
|
import EleventyWatchTargets from "./EleventyWatchTargets.js";
|
|||
|
|
import EleventyFiles from "./EleventyFiles.js";
|
|||
|
|
import TemplatePassthroughManager from "./TemplatePassthroughManager.js";
|
|||
|
|
import TemplateConfig from "./TemplateConfig.js";
|
|||
|
|
import FileSystemSearch from "./FileSystemSearch.js";
|
|||
|
|
import TemplateEngineManager from "./Engines/TemplateEngineManager.js";
|
|||
|
|
|
|||
|
|
/* Utils */
|
|||
|
|
import ConsoleLogger from "./Util/ConsoleLogger.js";
|
|||
|
|
import PathPrefixer from "./Util/PathPrefixer.js";
|
|||
|
|
import ProjectDirectories from "./Util/ProjectDirectories.js";
|
|||
|
|
import PathNormalizer from "./Util/PathNormalizer.js";
|
|||
|
|
import { isGlobMatch } from "./Util/GlobMatcher.js";
|
|||
|
|
import simplePlural from "./Util/Pluralize.js";
|
|||
|
|
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
|
|||
|
|
import eventBus from "./EventBus.js";
|
|||
|
|
import {
|
|||
|
|
getEleventyPackageJson,
|
|||
|
|
importJsonSync,
|
|||
|
|
getWorkingProjectPackageJsonPath,
|
|||
|
|
} from "./Util/ImportJsonSync.js";
|
|||
|
|
import { EleventyImport } from "./Util/Require.js";
|
|||
|
|
import ProjectTemplateFormats from "./Util/ProjectTemplateFormats.js";
|
|||
|
|
import { withResolvers } from "./Util/PromiseUtil.js";
|
|||
|
|
|
|||
|
|
/* Plugins */
|
|||
|
|
import RenderPlugin, * as RenderPluginExtras from "./Plugins/RenderPlugin.js";
|
|||
|
|
import I18nPlugin, * as I18nPluginExtras from "./Plugins/I18nPlugin.js";
|
|||
|
|
import HtmlBasePlugin, * as HtmlBasePluginExtras from "./Plugins/HtmlBasePlugin.js";
|
|||
|
|
import { TransformPlugin as InputPathToUrlTransformPlugin } from "./Plugins/InputPathToUrl.js";
|
|||
|
|
import { IdAttributePlugin } from "./Plugins/IdAttributePlugin.js";
|
|||
|
|
import FileSystemRemap from "./Util/GlobRemap.js";
|
|||
|
|
|
|||
|
|
const pkg = getEleventyPackageJson();
|
|||
|
|
const debug = debugUtil("Eleventy");
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Eleventy’s programmatic API
|
|||
|
|
* @module 11ty/eleventy/Eleventy
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
class Eleventy {
|
|||
|
|
/**
|
|||
|
|
* Userspace package.json file contents
|
|||
|
|
* @type {object|undefined}
|
|||
|
|
*/
|
|||
|
|
#projectPackageJson;
|
|||
|
|
/** @type {string} */
|
|||
|
|
#projectPackageJsonPath;
|
|||
|
|
/** @type {ProjectTemplateFormats|undefined} */
|
|||
|
|
#templateFormats;
|
|||
|
|
/** @type {ConsoleLogger|undefined} */
|
|||
|
|
#logger;
|
|||
|
|
/** @type {ProjectDirectories|undefined} */
|
|||
|
|
#directories;
|
|||
|
|
/** @type {boolean|undefined} */
|
|||
|
|
#verboseOverride;
|
|||
|
|
/** @type {boolean} */
|
|||
|
|
#isVerboseMode = true;
|
|||
|
|
/** @type {boolean|undefined} */
|
|||
|
|
#preInitVerbose;
|
|||
|
|
/** @type {boolean} */
|
|||
|
|
#hasConfigInitialized = false;
|
|||
|
|
/** @type {boolean} */
|
|||
|
|
#needsInit = true;
|
|||
|
|
/** @type {Promise|undefined} */
|
|||
|
|
#initPromise;
|
|||
|
|
/** @type {EleventyErrorHandler|undefined} */
|
|||
|
|
#errorHandler;
|
|||
|
|
/** @type {Map} */
|
|||
|
|
#privateCaches = new Map();
|
|||
|
|
/** @type {boolean} */
|
|||
|
|
#isStopping = false;
|
|||
|
|
/** @type {boolean|undefined} */
|
|||
|
|
#isEsm;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @typedef {object} EleventyOptions
|
|||
|
|
* @property {'cli'|'script'=} source
|
|||
|
|
* @property {'build'|'serve'|'watch'=} runMode
|
|||
|
|
* @property {boolean=} dryRun
|
|||
|
|
* @property {string=} configPath
|
|||
|
|
* @property {string=} pathPrefix
|
|||
|
|
* @property {boolean=} quietMode
|
|||
|
|
* @property {Function=} config
|
|||
|
|
* @property {string=} inputDir
|
|||
|
|
|
|||
|
|
* @param {string} [input] - Directory or filename for input/sources files.
|
|||
|
|
* @param {string} [output] - Directory serving as the target for writing the output files.
|
|||
|
|
* @param {EleventyOptions} [options={}]
|
|||
|
|
* @param {TemplateConfig} [eleventyConfig]
|
|||
|
|
*/
|
|||
|
|
constructor(input, output, options = {}, eleventyConfig = null) {
|
|||
|
|
/**
|
|||
|
|
* @type {string|undefined}
|
|||
|
|
* @description Holds the path to the input (might be a file or folder)
|
|||
|
|
*/
|
|||
|
|
this.rawInput = input || undefined;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {string|undefined}
|
|||
|
|
* @description holds the path to the output directory
|
|||
|
|
*/
|
|||
|
|
this.rawOutput = output || undefined;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {module:11ty/eleventy/TemplateConfig}
|
|||
|
|
* @description Override the config instance (for centralized config re-use)
|
|||
|
|
*/
|
|||
|
|
this.eleventyConfig = eleventyConfig;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {EleventyOptions}
|
|||
|
|
* @description Options object passed to the Eleventy constructor
|
|||
|
|
* @default {}
|
|||
|
|
*/
|
|||
|
|
this.options = options;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {'cli'|'script'}
|
|||
|
|
* @description Called via CLI (`cli`) or Programmatically (`script`)
|
|||
|
|
* @default "script"
|
|||
|
|
*/
|
|||
|
|
this.source = options.source || "script";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {string}
|
|||
|
|
* @description One of build, serve, or watch
|
|||
|
|
* @default "build"
|
|||
|
|
*/
|
|||
|
|
this.runMode = options.runMode || "build";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {boolean}
|
|||
|
|
* @description Is Eleventy running in dry mode?
|
|||
|
|
* @default false
|
|||
|
|
*/
|
|||
|
|
this.isDryRun = options.dryRun ?? false;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {boolean}
|
|||
|
|
* @description Is this an incremental build? (only operates on a subset of input files)
|
|||
|
|
* @default false
|
|||
|
|
*/
|
|||
|
|
this.isIncremental = false;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {string|undefined}
|
|||
|
|
* @description If an incremental build, this is the file we’re operating on.
|
|||
|
|
* @default null
|
|||
|
|
*/
|
|||
|
|
this.programmaticApiIncrementalFile = undefined;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {boolean}
|
|||
|
|
* @description Should we process files on first run? (The --ignore-initial feature)
|
|||
|
|
* @default true
|
|||
|
|
*/
|
|||
|
|
this.isRunInitialBuild = true;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {Number}
|
|||
|
|
* @description Number of builds run on this instance.
|
|||
|
|
* @default 0
|
|||
|
|
*/
|
|||
|
|
this.buildCount = 0;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @member {String} - Force ESM or CJS mode instead of detecting from package.json. Either cjs, esm, or auto.
|
|||
|
|
* @default "auto"
|
|||
|
|
*/
|
|||
|
|
this.loader = this.options.loader ?? "auto";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {Number}
|
|||
|
|
* @description The timestamp of Eleventy start.
|
|||
|
|
*/
|
|||
|
|
this.start = this.getNewTimestamp();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {string|undefined}
|
|||
|
|
* @description An override of Eleventy's default config file paths
|
|||
|
|
* @default undefined
|
|||
|
|
*/
|
|||
|
|
get configPath() {
|
|||
|
|
return this.options.configPath;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {string}
|
|||
|
|
* @description The top level directory the site pretends to reside in
|
|||
|
|
* @default "/"
|
|||
|
|
*/
|
|||
|
|
get pathPrefix() {
|
|||
|
|
return this.options.pathPrefix || "/";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async initializeConfig(initOverrides) {
|
|||
|
|
if (!this.eleventyConfig) {
|
|||
|
|
this.eleventyConfig = new TemplateConfig(null, this.configPath);
|
|||
|
|
} else if (this.configPath) {
|
|||
|
|
await this.eleventyConfig.setProjectConfigPath(this.configPath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.eleventyConfig.setRunMode(this.runMode);
|
|||
|
|
this.eleventyConfig.setProjectUsingEsm(this.isEsm);
|
|||
|
|
this.eleventyConfig.setLogger(this.logger);
|
|||
|
|
this.eleventyConfig.setDirectories(this.directories);
|
|||
|
|
this.eleventyConfig.setTemplateFormats(this.templateFormats);
|
|||
|
|
|
|||
|
|
if (this.pathPrefix || this.pathPrefix === "") {
|
|||
|
|
this.eleventyConfig.setPathPrefix(this.pathPrefix);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Debug mode should always run quiet (all output goes to debug logger)
|
|||
|
|
if (process.env.DEBUG) {
|
|||
|
|
this.#verboseOverride = false;
|
|||
|
|
} else if (this.options.quietMode === true || this.options.quietMode === false) {
|
|||
|
|
this.#verboseOverride = !this.options.quietMode;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Moved before config merges: https://github.com/11ty/eleventy/issues/3316
|
|||
|
|
if (this.#verboseOverride === true || this.#verboseOverride === false) {
|
|||
|
|
this.eleventyConfig.userConfig._setQuietModeOverride(!this.#verboseOverride);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.eleventyConfig.userConfig.directories = this.directories;
|
|||
|
|
|
|||
|
|
/* Programmatic API config */
|
|||
|
|
if (this.options.config && typeof this.options.config === "function") {
|
|||
|
|
debug("Running options.config configuration callback (passed to Eleventy constructor)");
|
|||
|
|
// TODO use return object here?
|
|||
|
|
await this.options.config(this.eleventyConfig.userConfig);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {object}
|
|||
|
|
* @description Initialize Eleventy environment variables
|
|||
|
|
* @default null
|
|||
|
|
*/
|
|||
|
|
// this.runMode need to be set before this
|
|||
|
|
this.env = this.getEnvironmentVariableValues();
|
|||
|
|
this.initializeEnvironmentVariables(this.env);
|
|||
|
|
|
|||
|
|
// Async initialization of configuration
|
|||
|
|
await this.eleventyConfig.init(initOverrides);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {object}
|
|||
|
|
* @description Initialize Eleventy’s configuration, including the user config file
|
|||
|
|
*/
|
|||
|
|
this.config = this.eleventyConfig.getConfig();
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {object}
|
|||
|
|
* @description Singleton BenchmarkManager instance
|
|||
|
|
*/
|
|||
|
|
this.bench = this.config.benchmarkManager;
|
|||
|
|
|
|||
|
|
if (performance) {
|
|||
|
|
debug("Eleventy warm up time: %o (ms)", performance.now());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Careful to make sure the previous server closes on SIGINT, issue #3873
|
|||
|
|
if (!this.eleventyServe) {
|
|||
|
|
/** @type {object} */
|
|||
|
|
this.eleventyServe = new EleventyServe();
|
|||
|
|
}
|
|||
|
|
this.eleventyServe.eleventyConfig = this.eleventyConfig;
|
|||
|
|
|
|||
|
|
/** @type {object} */
|
|||
|
|
this.watchManager = new EleventyWatch();
|
|||
|
|
|
|||
|
|
/** @type {object} */
|
|||
|
|
this.watchTargets = new EleventyWatchTargets(this.eleventyConfig);
|
|||
|
|
this.watchTargets.addAndMakeGlob(this.config.additionalWatchTargets);
|
|||
|
|
|
|||
|
|
/** @type {object} */
|
|||
|
|
this.fileSystemSearch = new FileSystemSearch();
|
|||
|
|
|
|||
|
|
this.#hasConfigInitialized = true;
|
|||
|
|
|
|||
|
|
// after #hasConfigInitialized above
|
|||
|
|
this.setIsVerbose(this.#preInitVerbose ?? !this.config.quietMode);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getNewTimestamp() {
|
|||
|
|
if (performance) {
|
|||
|
|
return performance.now();
|
|||
|
|
}
|
|||
|
|
return new Date().getTime();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** @type {ProjectDirectories} */
|
|||
|
|
get directories() {
|
|||
|
|
if (!this.#directories) {
|
|||
|
|
this.#directories = new ProjectDirectories();
|
|||
|
|
this.#directories.setInput(this.rawInput, this.options.inputDir);
|
|||
|
|
this.#directories.setOutput(this.rawOutput);
|
|||
|
|
|
|||
|
|
if (this.source == "cli" && (this.rawInput !== undefined || this.rawOutput !== undefined)) {
|
|||
|
|
this.#directories.freeze();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return this.#directories;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** @type {string} */
|
|||
|
|
get input() {
|
|||
|
|
return this.directories.inputFile || this.directories.input || this.config.dir.input;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** @type {string} */
|
|||
|
|
get inputFile() {
|
|||
|
|
return this.directories.inputFile;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** @type {string} */
|
|||
|
|
get inputDir() {
|
|||
|
|
return this.directories.input;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Not used internally, removed in 3.0.
|
|||
|
|
setInputDir() {
|
|||
|
|
throw new Error(
|
|||
|
|
"Eleventy->setInputDir was removed in 3.0. Use the inputDir option to the constructor",
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** @type {string} */
|
|||
|
|
get outputDir() {
|
|||
|
|
return this.directories.output || this.config.dir.output;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Updates the dry-run mode of Eleventy.
|
|||
|
|
*
|
|||
|
|
* @param {boolean} isDryRun - Shall Eleventy run in dry mode?
|
|||
|
|
*/
|
|||
|
|
setDryRun(isDryRun) {
|
|||
|
|
this.isDryRun = !!isDryRun;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Sets the incremental build mode.
|
|||
|
|
*
|
|||
|
|
* @param {boolean} isIncremental - Shall Eleventy run in incremental build mode and only write the files that trigger watch updates
|
|||
|
|
*/
|
|||
|
|
setIncrementalBuild(isIncremental) {
|
|||
|
|
this.isIncremental = !!isIncremental;
|
|||
|
|
|
|||
|
|
if (this.watchManager) {
|
|||
|
|
this.watchManager.incremental = !!isIncremental;
|
|||
|
|
}
|
|||
|
|
if (this.writer) {
|
|||
|
|
this.writer.setIncrementalBuild(this.isIncremental);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Set whether or not to do an initial build
|
|||
|
|
*
|
|||
|
|
* @param {boolean} ignoreInitialBuild - Shall Eleventy ignore the default initial build before watching in watch/serve mode?
|
|||
|
|
* @default true
|
|||
|
|
*/
|
|||
|
|
setIgnoreInitial(ignoreInitialBuild) {
|
|||
|
|
this.isRunInitialBuild = !ignoreInitialBuild;
|
|||
|
|
|
|||
|
|
if (this.writer) {
|
|||
|
|
this.writer.setRunInitialBuild(this.isRunInitialBuild);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Updates the path prefix used in the config.
|
|||
|
|
*
|
|||
|
|
* @param {string} pathPrefix - The new path prefix.
|
|||
|
|
*/
|
|||
|
|
setPathPrefix(pathPrefix) {
|
|||
|
|
if (pathPrefix || pathPrefix === "") {
|
|||
|
|
this.eleventyConfig.setPathPrefix(pathPrefix);
|
|||
|
|
// TODO reset config
|
|||
|
|
// this.config = this.eleventyConfig.getConfig();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Restarts Eleventy.
|
|||
|
|
*/
|
|||
|
|
async restart() {
|
|||
|
|
debug("Restarting.");
|
|||
|
|
this.start = this.getNewTimestamp();
|
|||
|
|
|
|||
|
|
this.extensionMap.reset();
|
|||
|
|
this.bench.reset();
|
|||
|
|
this.passthroughManager.reset();
|
|||
|
|
this.eleventyFiles.restart();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Logs some statistics after a complete run of Eleventy.
|
|||
|
|
*
|
|||
|
|
* @returns {string} ret - The log message.
|
|||
|
|
*/
|
|||
|
|
logFinished() {
|
|||
|
|
if (!this.writer) {
|
|||
|
|
throw new Error(
|
|||
|
|
"Did you call Eleventy.init to create the TemplateWriter instance? Hint: you probably didn’t.",
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let ret = [];
|
|||
|
|
|
|||
|
|
let {
|
|||
|
|
copyCount,
|
|||
|
|
copySize,
|
|||
|
|
skipCount,
|
|||
|
|
writeCount,
|
|||
|
|
// renderCount, // files that render (costly) but may not write to disk
|
|||
|
|
} = this.writer.getMetadata();
|
|||
|
|
|
|||
|
|
let slashRet = [];
|
|||
|
|
|
|||
|
|
if (copyCount) {
|
|||
|
|
debug("Total passthrough copy aggregate size: %o", filesize(copySize));
|
|||
|
|
slashRet.push(`Copied ${chalk.bold(copyCount)}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
slashRet.push(
|
|||
|
|
`Wrote ${chalk.bold(writeCount)} ${simplePlural(writeCount, "file", "files")}${
|
|||
|
|
skipCount ? ` (skipped ${skipCount})` : ""
|
|||
|
|
}`,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// slashRet.push(
|
|||
|
|
// `${renderCount} rendered`
|
|||
|
|
// )
|
|||
|
|
|
|||
|
|
if (slashRet.length) {
|
|||
|
|
ret.push(slashRet.join(" "));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let time = (this.getNewTimestamp() - this.start) / 1000;
|
|||
|
|
ret.push(
|
|||
|
|
`in ${chalk.bold(time.toFixed(2))} ${simplePlural(time.toFixed(2), "second", "seconds")}`,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// More than 1 second total, show estimate of per-template time
|
|||
|
|
if (time >= 1 && writeCount > 1) {
|
|||
|
|
ret.push(`(${((time * 1000) / writeCount).toFixed(1)}ms each, v${pkg.version})`);
|
|||
|
|
} else {
|
|||
|
|
ret.push(`(v${pkg.version})`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return ret.join(" ");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#cache(key, inst) {
|
|||
|
|
if (!("caches" in inst)) {
|
|||
|
|
throw new Error("To use #cache you need a `caches` getter object");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Restore from cache
|
|||
|
|
if (this.#privateCaches.has(key)) {
|
|||
|
|
let c = this.#privateCaches.get(key);
|
|||
|
|
for (let cacheKey in c) {
|
|||
|
|
inst[cacheKey] = c[cacheKey];
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// Set cache
|
|||
|
|
let c = {};
|
|||
|
|
for (let cacheKey of inst.caches || []) {
|
|||
|
|
c[cacheKey] = inst[cacheKey];
|
|||
|
|
}
|
|||
|
|
this.#privateCaches.set(key, c);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Starts Eleventy.
|
|||
|
|
*/
|
|||
|
|
async init(options = {}) {
|
|||
|
|
let { viaConfigReset } = Object.assign({ viaConfigReset: false }, options);
|
|||
|
|
if (!this.#hasConfigInitialized) {
|
|||
|
|
await this.initializeConfig();
|
|||
|
|
} else {
|
|||
|
|
// Note: Global event bus is different from user config event bus
|
|||
|
|
this.config.events.reset();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await this.config.events.emit("eleventy.config", this.eleventyConfig);
|
|||
|
|
|
|||
|
|
if (this.env) {
|
|||
|
|
await this.config.events.emit("eleventy.env", this.env);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let formats = this.templateFormats.getTemplateFormats();
|
|||
|
|
let engineManager = new TemplateEngineManager(this.eleventyConfig);
|
|||
|
|
this.extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
|||
|
|
this.extensionMap.setFormats(formats);
|
|||
|
|
this.extensionMap.engineManager = engineManager;
|
|||
|
|
await this.config.events.emit("eleventy.extensionmap", this.extensionMap);
|
|||
|
|
|
|||
|
|
// eleventyServe is always available, even when not in --serve mode
|
|||
|
|
// TODO directorynorm
|
|||
|
|
this.eleventyServe.setOutputDir(this.outputDir);
|
|||
|
|
|
|||
|
|
// TODO
|
|||
|
|
// this.eleventyServe.setWatcherOptions(this.getChokidarConfig());
|
|||
|
|
|
|||
|
|
this.templateData = new TemplateData(this.eleventyConfig);
|
|||
|
|
this.templateData.setProjectUsingEsm(this.isEsm);
|
|||
|
|
this.templateData.extensionMap = this.extensionMap;
|
|||
|
|
if (this.env) {
|
|||
|
|
this.templateData.environmentVariables = this.env;
|
|||
|
|
}
|
|||
|
|
this.templateData.setFileSystemSearch(this.fileSystemSearch);
|
|||
|
|
|
|||
|
|
this.passthroughManager = new TemplatePassthroughManager(this.eleventyConfig);
|
|||
|
|
this.passthroughManager.setRunMode(this.runMode);
|
|||
|
|
this.passthroughManager.setDryRun(this.isDryRun);
|
|||
|
|
this.passthroughManager.extensionMap = this.extensionMap;
|
|||
|
|
this.passthroughManager.setFileSystemSearch(this.fileSystemSearch);
|
|||
|
|
|
|||
|
|
this.eleventyFiles = new EleventyFiles(formats, this.eleventyConfig);
|
|||
|
|
this.eleventyFiles.setPassthroughManager(this.passthroughManager);
|
|||
|
|
this.eleventyFiles.setFileSystemSearch(this.fileSystemSearch);
|
|||
|
|
this.eleventyFiles.setRunMode(this.runMode);
|
|||
|
|
this.eleventyFiles.extensionMap = this.extensionMap;
|
|||
|
|
// This needs to be set before init or it’ll construct a new one
|
|||
|
|
this.eleventyFiles.templateData = this.templateData;
|
|||
|
|
this.eleventyFiles.init();
|
|||
|
|
|
|||
|
|
if (checkPassthroughCopyBehavior(this.config, this.runMode)) {
|
|||
|
|
this.eleventyServe.watchPassthroughCopy(
|
|||
|
|
this.eleventyFiles.getGlobWatcherFilesForPassthroughCopy(),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Note these directories are all project root relative
|
|||
|
|
this.config.events.emit("eleventy.directories", this.directories.getUserspaceInstance());
|
|||
|
|
|
|||
|
|
this.writer = new TemplateWriter(formats, this.templateData, this.eleventyConfig);
|
|||
|
|
|
|||
|
|
if (!viaConfigReset) {
|
|||
|
|
// set or restore cache
|
|||
|
|
this.#cache("TemplateWriter", this.writer);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.writer.logger = this.logger;
|
|||
|
|
this.writer.extensionMap = this.extensionMap;
|
|||
|
|
this.writer.setEleventyFiles(this.eleventyFiles);
|
|||
|
|
this.writer.setPassthroughManager(this.passthroughManager);
|
|||
|
|
this.writer.setRunInitialBuild(this.isRunInitialBuild);
|
|||
|
|
this.writer.setIncrementalBuild(this.isIncremental);
|
|||
|
|
|
|||
|
|
let debugStr = `Directories:
|
|||
|
|
Input:
|
|||
|
|
Directory: ${this.directories.input}
|
|||
|
|
File: ${this.directories.inputFile || false}
|
|||
|
|
Glob: ${this.directories.inputGlob || false}
|
|||
|
|
Data: ${this.directories.data}
|
|||
|
|
Includes: ${this.directories.includes}
|
|||
|
|
Layouts: ${this.directories.layouts || false}
|
|||
|
|
Output: ${this.directories.output}
|
|||
|
|
Template Formats: ${formats.join(",")}
|
|||
|
|
Verbose Output: ${this.verboseMode}`;
|
|||
|
|
debug(debugStr);
|
|||
|
|
|
|||
|
|
this.writer.setVerboseOutput(this.verboseMode);
|
|||
|
|
this.writer.setDryRun(this.isDryRun);
|
|||
|
|
|
|||
|
|
this.#needsInit = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// These are all set as initial global data under eleventy.env.* (see TemplateData->environmentVariables)
|
|||
|
|
getEnvironmentVariableValues() {
|
|||
|
|
let values = {
|
|||
|
|
source: this.source,
|
|||
|
|
runMode: this.runMode,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let configPath = this.eleventyConfig.getLocalProjectConfigFile();
|
|||
|
|
if (configPath) {
|
|||
|
|
values.config = TemplatePath.absolutePath(configPath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Fixed: instead of configuration directory, explicit root or working directory
|
|||
|
|
values.root = TemplatePath.getWorkingDir();
|
|||
|
|
|
|||
|
|
values.source = this.source;
|
|||
|
|
|
|||
|
|
// Backwards compatibility
|
|||
|
|
Object.defineProperty(values, "isServerless", {
|
|||
|
|
enumerable: false,
|
|||
|
|
value: false,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return values;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Set process.ENV variables for use in Eleventy projects
|
|||
|
|
*
|
|||
|
|
* @method
|
|||
|
|
*/
|
|||
|
|
initializeEnvironmentVariables(env) {
|
|||
|
|
// Recognize that global data `eleventy.version` is coerced to remove prerelease tags
|
|||
|
|
// and this is the raw version (3.0.0 versus 3.0.0-alpha.6).
|
|||
|
|
// `eleventy.env.version` does not yet exist (unnecessary)
|
|||
|
|
process.env.ELEVENTY_VERSION = Eleventy.getVersion();
|
|||
|
|
|
|||
|
|
process.env.ELEVENTY_ROOT = env.root;
|
|||
|
|
debug("Setting process.env.ELEVENTY_ROOT: %o", env.root);
|
|||
|
|
|
|||
|
|
process.env.ELEVENTY_SOURCE = env.source;
|
|||
|
|
process.env.ELEVENTY_RUN_MODE = env.runMode;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** @param {boolean} value */
|
|||
|
|
set verboseMode(value) {
|
|||
|
|
this.setIsVerbose(value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** @type {boolean} */
|
|||
|
|
get verboseMode() {
|
|||
|
|
return this.#isVerboseMode;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** @type {ConsoleLogger} */
|
|||
|
|
get logger() {
|
|||
|
|
if (!this.#logger) {
|
|||
|
|
this.#logger = new ConsoleLogger();
|
|||
|
|
this.#logger.isVerbose = this.verboseMode;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return this.#logger;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** @param {ConsoleLogger} logger */
|
|||
|
|
set logger(logger) {
|
|||
|
|
this.eleventyConfig.setLogger(logger);
|
|||
|
|
this.#logger = logger;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
disableLogger() {
|
|||
|
|
this.logger.overrideLogger(false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** @type {EleventyErrorHandler} */
|
|||
|
|
get errorHandler() {
|
|||
|
|
if (!this.#errorHandler) {
|
|||
|
|
this.#errorHandler = new EleventyErrorHandler();
|
|||
|
|
this.#errorHandler.isVerbose = this.verboseMode;
|
|||
|
|
this.#errorHandler.logger = this.logger;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return this.#errorHandler;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Updates the verbose mode of Eleventy.
|
|||
|
|
*
|
|||
|
|
* @method
|
|||
|
|
* @param {boolean} isVerbose - Shall Eleventy run in verbose mode?
|
|||
|
|
*/
|
|||
|
|
setIsVerbose(isVerbose) {
|
|||
|
|
if (!this.#hasConfigInitialized) {
|
|||
|
|
this.#preInitVerbose = !!isVerbose;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// always defer to --quiet if override happened
|
|||
|
|
isVerbose = this.#verboseOverride ?? !!isVerbose;
|
|||
|
|
|
|||
|
|
this.#isVerboseMode = isVerbose;
|
|||
|
|
|
|||
|
|
if (this.logger) {
|
|||
|
|
this.logger.isVerbose = isVerbose;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.bench.setVerboseOutput(isVerbose);
|
|||
|
|
|
|||
|
|
if (this.writer) {
|
|||
|
|
this.writer.setVerboseOutput(isVerbose);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (this.errorHandler) {
|
|||
|
|
this.errorHandler.isVerbose = isVerbose;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Set verbose mode in config file
|
|||
|
|
this.eleventyConfig.verbose = isVerbose;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
get templateFormats() {
|
|||
|
|
if (!this.#templateFormats) {
|
|||
|
|
let tf = new ProjectTemplateFormats();
|
|||
|
|
this.#templateFormats = tf;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return this.#templateFormats;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Updates the template formats of Eleventy.
|
|||
|
|
*
|
|||
|
|
* @method
|
|||
|
|
* @param {string} formats - The new template formats.
|
|||
|
|
*/
|
|||
|
|
setFormats(formats) {
|
|||
|
|
this.templateFormats.setViaCommandLine(formats);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Updates the run mode of Eleventy.
|
|||
|
|
*
|
|||
|
|
* @method
|
|||
|
|
* @param {string} runMode - One of "build", "watch", or "serve"
|
|||
|
|
*/
|
|||
|
|
setRunMode(runMode) {
|
|||
|
|
this.runMode = runMode;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Set the file that needs to be rendered/compiled/written for an incremental build.
|
|||
|
|
* This method is also wired up to the CLI --incremental=incrementalFile
|
|||
|
|
*
|
|||
|
|
* @method
|
|||
|
|
* @param {string} incrementalFile - File path (added or modified in a project)
|
|||
|
|
*/
|
|||
|
|
setIncrementalFile(incrementalFile) {
|
|||
|
|
if (incrementalFile) {
|
|||
|
|
// This used to also setIgnoreInitial(true) but was changed in 3.0.0-alpha.14
|
|||
|
|
this.setIncrementalBuild(true);
|
|||
|
|
|
|||
|
|
this.programmaticApiIncrementalFile = TemplatePath.addLeadingDotSlash(incrementalFile);
|
|||
|
|
|
|||
|
|
this.eleventyConfig.setPreviousBuildModifiedFile(incrementalFile);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
unsetIncrementalFile() {
|
|||
|
|
// only applies to initial build, no re-runs (--watch/--serve)
|
|||
|
|
if (this.programmaticApiIncrementalFile) {
|
|||
|
|
// this.setIgnoreInitial(false);
|
|||
|
|
this.programmaticApiIncrementalFile = undefined;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// reset back to false
|
|||
|
|
this.setIgnoreInitial(false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Reads the version of Eleventy.
|
|||
|
|
*
|
|||
|
|
* @static
|
|||
|
|
* @returns {string} - The version of Eleventy.
|
|||
|
|
*/
|
|||
|
|
static getVersion() {
|
|||
|
|
return pkg.version;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @deprecated since 1.0.1, use static Eleventy.getVersion()
|
|||
|
|
*/
|
|||
|
|
getVersion() {
|
|||
|
|
return Eleventy.getVersion();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Shows a help message including usage.
|
|||
|
|
*
|
|||
|
|
* @static
|
|||
|
|
* @returns {string} - The help message.
|
|||
|
|
*/
|
|||
|
|
static getHelp() {
|
|||
|
|
return `Usage: eleventy
|
|||
|
|
eleventy --input=. --output=./_site
|
|||
|
|
eleventy --serve
|
|||
|
|
|
|||
|
|
Arguments:
|
|||
|
|
|
|||
|
|
--version
|
|||
|
|
|
|||
|
|
--input=.
|
|||
|
|
Input template files (default: \`.\`)
|
|||
|
|
|
|||
|
|
--output=_site
|
|||
|
|
Write HTML output to this folder (default: \`_site\`)
|
|||
|
|
|
|||
|
|
--serve
|
|||
|
|
Run web server on --port (default 8080) and watch them too
|
|||
|
|
|
|||
|
|
--port
|
|||
|
|
Run the --serve web server on this port (default 8080)
|
|||
|
|
|
|||
|
|
--watch
|
|||
|
|
Wait for files to change and automatically rewrite (no web server)
|
|||
|
|
|
|||
|
|
--incremental
|
|||
|
|
Only build the files that have changed. Best with watch/serve.
|
|||
|
|
|
|||
|
|
--incremental=filename.md
|
|||
|
|
Does not require watch/serve. Run an incremental build targeting a single file.
|
|||
|
|
|
|||
|
|
--ignore-initial
|
|||
|
|
Start without a build; build when files change. Works best with watch/serve/incremental.
|
|||
|
|
|
|||
|
|
--formats=liquid,md
|
|||
|
|
Allow only certain template types (default: \`*\`)
|
|||
|
|
|
|||
|
|
--quiet
|
|||
|
|
Don’t print all written files (off by default)
|
|||
|
|
|
|||
|
|
--config=filename.js
|
|||
|
|
Override the eleventy config file path (default: \`.eleventy.js\`)
|
|||
|
|
|
|||
|
|
--pathprefix='/'
|
|||
|
|
Change all url template filters to use this subdirectory.
|
|||
|
|
|
|||
|
|
--dryrun
|
|||
|
|
Don’t write any files. Useful in DEBUG mode, for example: \`DEBUG=Eleventy* npx @11ty/eleventy --dryrun\`
|
|||
|
|
|
|||
|
|
--loader
|
|||
|
|
Set to "esm" to force ESM mode, "cjs" to force CommonJS mode, or "auto" (default) to infer it from package.json.
|
|||
|
|
|
|||
|
|
--to=json
|
|||
|
|
--to=ndjson
|
|||
|
|
Change the output to JSON or NDJSON (default: \`fs\`)
|
|||
|
|
|
|||
|
|
--help`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @deprecated since 1.0.1, use static Eleventy.getHelp()
|
|||
|
|
*/
|
|||
|
|
getHelp() {
|
|||
|
|
return Eleventy.getHelp();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Resets the config of Eleventy.
|
|||
|
|
*
|
|||
|
|
* @method
|
|||
|
|
*/
|
|||
|
|
resetConfig() {
|
|||
|
|
delete this.eleventyConfig;
|
|||
|
|
|
|||
|
|
// ensures `initializeConfig()` will run when `init()` is called next
|
|||
|
|
this.#hasConfigInitialized = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param {string} changedFilePath - File that triggered a re-run (added or modified)
|
|||
|
|
* @param {boolean} [isResetConfig] - are we doing a config reset
|
|||
|
|
*/
|
|||
|
|
async #addFileToWatchQueue(changedFilePath, isResetConfig) {
|
|||
|
|
// Currently this is only for 11ty.js deps but should be extended with usesGraph
|
|||
|
|
let usedByDependants = [];
|
|||
|
|
if (this.watchTargets) {
|
|||
|
|
usedByDependants = this.watchTargets.getDependantsOf(
|
|||
|
|
TemplatePath.addLeadingDotSlash(changedFilePath),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let relevantLayouts = this.eleventyConfig.usesGraph.getLayoutsUsedBy(changedFilePath);
|
|||
|
|
|
|||
|
|
// `eleventy.templateModified` is no longer used internally, remove in a future major version.
|
|||
|
|
eventBus.emit("eleventy.templateModified", changedFilePath, {
|
|||
|
|
usedByDependants,
|
|||
|
|
relevantLayouts,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// These listeners are *global*, not cleared even on config reset
|
|||
|
|
eventBus.emit("eleventy.resourceModified", changedFilePath, usedByDependants, {
|
|||
|
|
viaConfigReset: isResetConfig,
|
|||
|
|
relevantLayouts,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
this.config.events.emit("eleventy#templateModified", changedFilePath);
|
|||
|
|
|
|||
|
|
this.watchManager.addToPendingQueue(changedFilePath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
shouldTriggerConfigReset(changedFiles) {
|
|||
|
|
let configFilePaths = new Set(this.eleventyConfig.getLocalProjectConfigFiles());
|
|||
|
|
let resetConfigGlobs = EleventyWatchTargets.normalizeToGlobs(
|
|||
|
|
Array.from(this.eleventyConfig.userConfig.watchTargetsConfigReset),
|
|||
|
|
);
|
|||
|
|
for (let filePath of changedFiles) {
|
|||
|
|
if (configFilePaths.has(filePath)) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
if (isGlobMatch(filePath, resetConfigGlobs)) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (const configFilePath of configFilePaths) {
|
|||
|
|
// Any dependencies of the config file changed
|
|||
|
|
let configFileDependencies = new Set(this.watchTargets.getDependenciesOf(configFilePath));
|
|||
|
|
|
|||
|
|
for (let filePath of changedFiles) {
|
|||
|
|
if (configFileDependencies.has(filePath)) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Checks the build queue to see if any configuration related files have changed
|
|||
|
|
#shouldResetConfig(activeQueue = []) {
|
|||
|
|
if (!activeQueue.length) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return this.shouldTriggerConfigReset(
|
|||
|
|
activeQueue.map((path) => {
|
|||
|
|
return PathNormalizer.normalizeSeperator(TemplatePath.addLeadingDotSlash(path));
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async #watch(isResetConfig = false) {
|
|||
|
|
if (this.watchManager.isBuildRunning()) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.watchManager.setBuildRunning();
|
|||
|
|
|
|||
|
|
let queue = this.watchManager.getActiveQueue();
|
|||
|
|
|
|||
|
|
await this.config.events.emit("beforeWatch", queue);
|
|||
|
|
await this.config.events.emit("eleventy.beforeWatch", queue);
|
|||
|
|
|
|||
|
|
// Clear `import` cache for all files that triggered the rebuild (sync event)
|
|||
|
|
this.watchTargets.clearImportCacheFor(queue);
|
|||
|
|
|
|||
|
|
// reset and reload global configuration
|
|||
|
|
if (isResetConfig) {
|
|||
|
|
// important: run this before config resets otherwise the handlers will disappear.
|
|||
|
|
await this.config.events.emit("eleventy.reset");
|
|||
|
|
this.resetConfig();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await this.restart();
|
|||
|
|
await this.init({ viaConfigReset: isResetConfig });
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
let [passthroughCopyResults, templateResults] = await this.write();
|
|||
|
|
|
|||
|
|
this.watchTargets.reset();
|
|||
|
|
|
|||
|
|
await this.#initWatchDependencies();
|
|||
|
|
|
|||
|
|
// Add new deps to chokidar
|
|||
|
|
this.watcher.add(this.watchTargets.getNewTargetsSinceLastReset());
|
|||
|
|
|
|||
|
|
// Is a CSS input file and is not in the includes folder
|
|||
|
|
// TODO check output path file extension of this template (not input path)
|
|||
|
|
// TODO add additional API for this, maybe a config callback?
|
|||
|
|
let onlyCssChanges = this.watchManager.hasAllQueueFiles((path) => {
|
|||
|
|
return (
|
|||
|
|
path.endsWith(".css") &&
|
|||
|
|
// TODO how to make this work with relative includes?
|
|||
|
|
!TemplatePath.startsWithSubPath(path, this.eleventyFiles.getIncludesDir())
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
let files = this.watchManager.getActiveQueue();
|
|||
|
|
|
|||
|
|
// Maps passthrough copy files to output URLs for CSS live reload
|
|||
|
|
let stylesheetUrls = new Set();
|
|||
|
|
for (let entry of passthroughCopyResults) {
|
|||
|
|
for (let filepath in entry.map) {
|
|||
|
|
if (
|
|||
|
|
filepath.endsWith(".css") &&
|
|||
|
|
files.includes(TemplatePath.addLeadingDotSlash(filepath))
|
|||
|
|
) {
|
|||
|
|
stylesheetUrls.add(
|
|||
|
|
"/" + TemplatePath.stripLeadingSubPath(entry.map[filepath], this.outputDir),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let normalizedPathPrefix = PathPrefixer.normalizePathPrefix(this.config.pathPrefix);
|
|||
|
|
let matchingTemplates = templateResults
|
|||
|
|
.flat()
|
|||
|
|
.filter((entry) => Boolean(entry))
|
|||
|
|
.map((entry) => {
|
|||
|
|
// only `url`, `inputPath`, and `content` are used: https://github.com/11ty/eleventy-dev-server/blob/1c658605f75224fdc76f68aebe7a412eeb4f1bc9/client/reload-client.js#L140
|
|||
|
|
entry.url = PathPrefixer.joinUrlParts(normalizedPathPrefix, entry.url);
|
|||
|
|
delete entry.rawInput; // Issue #3481
|
|||
|
|
return entry;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await this.eleventyServe.reload({
|
|||
|
|
files,
|
|||
|
|
subtype: onlyCssChanges ? "css" : undefined,
|
|||
|
|
build: {
|
|||
|
|
stylesheets: Array.from(stylesheetUrls),
|
|||
|
|
templates: matchingTemplates,
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
this.eleventyServe.sendError({
|
|||
|
|
error,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.watchManager.setBuildFinished();
|
|||
|
|
|
|||
|
|
let queueSize = this.watchManager.getPendingQueueSize();
|
|||
|
|
if (queueSize > 0) {
|
|||
|
|
this.logger.log(
|
|||
|
|
`You saved while Eleventy was running, let’s run again. (${queueSize} change${
|
|||
|
|
queueSize !== 1 ? "s" : ""
|
|||
|
|
})`,
|
|||
|
|
);
|
|||
|
|
await this.#watch();
|
|||
|
|
} else {
|
|||
|
|
this.logger.log("Watching…");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @returns {module:11ty/eleventy/src/Benchmark/BenchmarkGroup~BenchmarkGroup}
|
|||
|
|
*/
|
|||
|
|
get watcherBench() {
|
|||
|
|
return this.bench.get("Watcher");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Set up watchers and benchmarks.
|
|||
|
|
*
|
|||
|
|
* @async
|
|||
|
|
* @method
|
|||
|
|
*/
|
|||
|
|
async initWatch() {
|
|||
|
|
this.watchManager = new EleventyWatch();
|
|||
|
|
this.watchManager.incremental = this.isIncremental;
|
|||
|
|
|
|||
|
|
if (this.projectPackageJsonPath) {
|
|||
|
|
this.watchTargets.add([
|
|||
|
|
path.relative(TemplatePath.getWorkingDir(), this.projectPackageJsonPath),
|
|||
|
|
]);
|
|||
|
|
}
|
|||
|
|
this.watchTargets.add(this.eleventyFiles.getGlobWatcherFiles());
|
|||
|
|
this.watchTargets.add(this.eleventyFiles.getIgnoreFiles());
|
|||
|
|
|
|||
|
|
// Watch the local project config file
|
|||
|
|
this.watchTargets.add(this.eleventyConfig.getLocalProjectConfigFiles());
|
|||
|
|
|
|||
|
|
// Template and Directory Data Files
|
|||
|
|
this.watchTargets.add(await this.eleventyFiles.getGlobWatcherTemplateDataFiles());
|
|||
|
|
|
|||
|
|
let benchmark = this.watcherBench.get(
|
|||
|
|
"Watching JavaScript Dependencies (disable with `eleventyConfig.setWatchJavaScriptDependencies(false)`)",
|
|||
|
|
);
|
|||
|
|
benchmark.before();
|
|||
|
|
await this.#initWatchDependencies();
|
|||
|
|
benchmark.after();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// fetch from project’s package.json
|
|||
|
|
get projectPackageJsonPath() {
|
|||
|
|
if (this.#projectPackageJsonPath === undefined) {
|
|||
|
|
this.#projectPackageJsonPath = getWorkingProjectPackageJsonPath() || false;
|
|||
|
|
}
|
|||
|
|
return this.#projectPackageJsonPath;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
get projectPackageJson() {
|
|||
|
|
if (!this.#projectPackageJson) {
|
|||
|
|
let p = this.projectPackageJsonPath;
|
|||
|
|
this.#projectPackageJson = p ? importJsonSync(p) : {};
|
|||
|
|
}
|
|||
|
|
return this.#projectPackageJson;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
get isEsm() {
|
|||
|
|
if (this.#isEsm !== undefined) {
|
|||
|
|
return this.#isEsm;
|
|||
|
|
}
|
|||
|
|
if (this.loader == "esm") {
|
|||
|
|
this.#isEsm = true;
|
|||
|
|
} else if (this.loader == "cjs") {
|
|||
|
|
this.#isEsm = false;
|
|||
|
|
} else if (this.loader == "auto") {
|
|||
|
|
this.#isEsm = this.projectPackageJson?.type === "module";
|
|||
|
|
} else {
|
|||
|
|
throw new Error("The 'loader' option must be one of 'esm', 'cjs', or 'auto'");
|
|||
|
|
}
|
|||
|
|
return this.#isEsm;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Starts watching dependencies.
|
|||
|
|
*/
|
|||
|
|
async #initWatchDependencies() {
|
|||
|
|
if (!this.eleventyConfig.shouldSpiderJavaScriptDependencies()) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TODO use DirContains
|
|||
|
|
let dataDir = TemplatePath.stripLeadingDotSlash(this.templateData.getDataDir());
|
|||
|
|
function filterOutGlobalDataFiles(path) {
|
|||
|
|
return !dataDir || !TemplatePath.stripLeadingDotSlash(path).startsWith(dataDir);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Lazy resolve isEsm only for --watch
|
|||
|
|
this.watchTargets.setProjectUsingEsm(this.isEsm);
|
|||
|
|
|
|||
|
|
// Template files .11ty.js
|
|||
|
|
let templateFiles = await this.eleventyFiles.getWatchPathCache();
|
|||
|
|
await this.watchTargets.addDependencies(templateFiles);
|
|||
|
|
|
|||
|
|
// Config file dependencies
|
|||
|
|
await this.watchTargets.addDependencies(
|
|||
|
|
this.eleventyConfig.getLocalProjectConfigFiles(),
|
|||
|
|
filterOutGlobalDataFiles,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Deps from Global Data (that aren’t in the global data directory, everything is watched there)
|
|||
|
|
let globalDataDeps = this.templateData.getWatchPathCache();
|
|||
|
|
await this.watchTargets.addDependencies(globalDataDeps, filterOutGlobalDataFiles);
|
|||
|
|
|
|||
|
|
await this.watchTargets.addDependencies(
|
|||
|
|
await this.eleventyFiles.getWatcherTemplateJavaScriptDataFiles(),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Returns all watched files.
|
|||
|
|
*
|
|||
|
|
* @async
|
|||
|
|
* @method
|
|||
|
|
* @returns {Promise<Array>} targets - The watched files.
|
|||
|
|
*/
|
|||
|
|
async getWatchedFiles() {
|
|||
|
|
return this.watchTargets.getTargets();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getChokidarConfig() {
|
|||
|
|
let ignores = this.eleventyFiles.getGlobWatcherIgnores();
|
|||
|
|
debug("Ignoring watcher changes to: %o", ignores);
|
|||
|
|
|
|||
|
|
let configOptions = this.config.chokidarConfig;
|
|||
|
|
|
|||
|
|
// can’t override these yet
|
|||
|
|
// TODO maybe if array, merge the array?
|
|||
|
|
delete configOptions.ignored;
|
|||
|
|
|
|||
|
|
return Object.assign(
|
|||
|
|
{
|
|||
|
|
ignored: ignores,
|
|||
|
|
ignoreInitial: true,
|
|||
|
|
awaitWriteFinish: {
|
|||
|
|
stabilityThreshold: 150,
|
|||
|
|
pollInterval: 25,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
configOptions,
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Start the watching of files
|
|||
|
|
*
|
|||
|
|
* @async
|
|||
|
|
* @method
|
|||
|
|
*/
|
|||
|
|
async watch() {
|
|||
|
|
this.watcherBench.setMinimumThresholdMs(500);
|
|||
|
|
this.watcherBench.reset();
|
|||
|
|
|
|||
|
|
// We use a string module name and try/catch here to hide this from the zisi and esbuild serverless bundlers
|
|||
|
|
let chokidar;
|
|||
|
|
// eslint-disable-next-line no-useless-catch
|
|||
|
|
try {
|
|||
|
|
let moduleName = "chokidar";
|
|||
|
|
let chokidarImport = await import(moduleName);
|
|||
|
|
chokidar = chokidarImport.default;
|
|||
|
|
} catch (e) {
|
|||
|
|
throw e;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Note that watching indirectly depends on this for fetching dependencies from JS files
|
|||
|
|
// See: TemplateWriter:pathCache and EleventyWatchTargets
|
|||
|
|
await this.write();
|
|||
|
|
|
|||
|
|
let initWatchBench = this.watcherBench.get("Start up --watch");
|
|||
|
|
initWatchBench.before();
|
|||
|
|
|
|||
|
|
await this.initWatch();
|
|||
|
|
|
|||
|
|
// TODO improve unwatching if JS dependencies are removed (or files are deleted)
|
|||
|
|
let rawFiles = await this.getWatchedFiles();
|
|||
|
|
debug("Watching for changes to: %o", rawFiles);
|
|||
|
|
|
|||
|
|
let options = this.getChokidarConfig();
|
|||
|
|
|
|||
|
|
// Remap all paths to `cwd` if in play (Issue #3854)
|
|||
|
|
let remapper = new FileSystemRemap(rawFiles);
|
|||
|
|
let cwd = remapper.getCwd();
|
|||
|
|
|
|||
|
|
if (cwd) {
|
|||
|
|
options.cwd = cwd;
|
|||
|
|
|
|||
|
|
rawFiles = remapper.getInput().map((entry) => {
|
|||
|
|
return TemplatePath.stripLeadingDotSlash(entry);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
options.ignored = remapper.getRemapped(options.ignored || []).map((entry) => {
|
|||
|
|
return TemplatePath.stripLeadingDotSlash(entry);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let watcher = chokidar.watch(rawFiles, options);
|
|||
|
|
|
|||
|
|
initWatchBench.after();
|
|||
|
|
|
|||
|
|
this.watcherBench.finish("Watch");
|
|||
|
|
|
|||
|
|
this.logger.forceLog("Watching…");
|
|||
|
|
|
|||
|
|
this.watcher = watcher;
|
|||
|
|
|
|||
|
|
let watchDelay;
|
|||
|
|
let watchRun = async (path) => {
|
|||
|
|
path = TemplatePath.normalize(path);
|
|||
|
|
try {
|
|||
|
|
let isResetConfig = this.#shouldResetConfig([path]);
|
|||
|
|
this.#addFileToWatchQueue(path, isResetConfig);
|
|||
|
|
|
|||
|
|
clearTimeout(watchDelay);
|
|||
|
|
|
|||
|
|
let { promise, resolve, reject } = withResolvers();
|
|||
|
|
|
|||
|
|
watchDelay = setTimeout(async () => {
|
|||
|
|
this.#watch(isResetConfig).then(resolve, reject);
|
|||
|
|
}, this.config.watchThrottleWaitTime);
|
|||
|
|
|
|||
|
|
await promise;
|
|||
|
|
} catch (e) {
|
|||
|
|
if (e instanceof EleventyBaseError) {
|
|||
|
|
this.errorHandler.error(e, "Eleventy watch error");
|
|||
|
|
this.watchManager.setBuildFinished();
|
|||
|
|
} else {
|
|||
|
|
this.errorHandler.fatal(e, "Eleventy fatal watch error");
|
|||
|
|
await this.stopWatch();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.config.events.emit("eleventy.afterwatch");
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
watcher.on("change", async (path) => {
|
|||
|
|
// Emulated passthrough copy logs from the server
|
|||
|
|
if (!this.eleventyServe.isEmulatedPassthroughCopyMatch(path)) {
|
|||
|
|
this.logger.forceLog(`File changed: ${TemplatePath.standardizeFilePath(path)}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await watchRun(path);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
watcher.on("add", async (path) => {
|
|||
|
|
// Emulated passthrough copy logs from the server
|
|||
|
|
if (!this.eleventyServe.isEmulatedPassthroughCopyMatch(path)) {
|
|||
|
|
this.logger.forceLog(`File added: ${TemplatePath.standardizeFilePath(path)}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.fileSystemSearch.add(path);
|
|||
|
|
await watchRun(path);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
watcher.on("unlink", (path) => {
|
|||
|
|
this.logger.forceLog(`File deleted: ${TemplatePath.standardizeFilePath(path)}`);
|
|||
|
|
this.fileSystemSearch.delete(path);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// wait for chokidar to be ready.
|
|||
|
|
await new Promise((resolve) => {
|
|||
|
|
watcher.on("ready", () => resolve());
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Returns for testability
|
|||
|
|
return watchRun;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async stopWatch() {
|
|||
|
|
// Prevent multiple invocations.
|
|||
|
|
if (this.#isStopping) {
|
|||
|
|
return this.#isStopping;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
debug("Cleaning up chokidar and server instances, if they exist.");
|
|||
|
|
this.#isStopping = Promise.all([this.eleventyServe.close(), this.watcher?.close()]).then(() => {
|
|||
|
|
this.#isStopping = false;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return this.#isStopping;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Serve Eleventy on this port.
|
|||
|
|
*
|
|||
|
|
* @param {Number} port - The HTTP port to serve Eleventy from.
|
|||
|
|
*/
|
|||
|
|
async serve(port) {
|
|||
|
|
// Port is optional and in this case likely via --port on the command line
|
|||
|
|
// May defer to configuration API options `port` property
|
|||
|
|
return this.eleventyServe.serve(port);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Writes templates to the file system.
|
|||
|
|
*
|
|||
|
|
* @async
|
|||
|
|
* @method
|
|||
|
|
* @returns {Promise<{Array}>}
|
|||
|
|
*/
|
|||
|
|
async write() {
|
|||
|
|
return this.executeBuild("fs");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Renders templates to a JSON object.
|
|||
|
|
*
|
|||
|
|
* @async
|
|||
|
|
* @method
|
|||
|
|
* @returns {Promise<{Array}>}
|
|||
|
|
*/
|
|||
|
|
async toJSON() {
|
|||
|
|
return this.executeBuild("json");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Returns a stream of new line delimited (NDJSON) objects
|
|||
|
|
*
|
|||
|
|
* @async
|
|||
|
|
* @method
|
|||
|
|
* @returns {Promise<{ReadableStream}>}
|
|||
|
|
*/
|
|||
|
|
async toNDJSON() {
|
|||
|
|
return this.executeBuild("ndjson");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* tbd.
|
|||
|
|
*
|
|||
|
|
* @async
|
|||
|
|
* @method
|
|||
|
|
* @returns {Promise<{Array,ReadableStream}>} ret - tbd.
|
|||
|
|
*/
|
|||
|
|
async executeBuild(to = "fs") {
|
|||
|
|
if (this.#needsInit) {
|
|||
|
|
if (!this.#initPromise) {
|
|||
|
|
this.#initPromise = this.init();
|
|||
|
|
}
|
|||
|
|
await this.#initPromise.then(() => {
|
|||
|
|
// #needsInit also set to false at the end of `init()`
|
|||
|
|
this.#needsInit = false;
|
|||
|
|
this.#initPromise = undefined;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!this.writer) {
|
|||
|
|
throw new Error(
|
|||
|
|
"Internal error: Eleventy didn’t run init() properly and wasn’t able to create a TemplateWriter.",
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let incrementalFile =
|
|||
|
|
this.programmaticApiIncrementalFile || this.watchManager?.getIncrementalFile();
|
|||
|
|
if (incrementalFile) {
|
|||
|
|
this.writer.setIncrementalFile(incrementalFile);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let returnObj;
|
|||
|
|
let hasError = false;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
let directories = this.directories.getUserspaceInstance();
|
|||
|
|
let eventsArg = {
|
|||
|
|
directories,
|
|||
|
|
|
|||
|
|
// v3.0.0-alpha.6, changed to use `directories` instead (this was only used by serverless plugin)
|
|||
|
|
inputDir: directories.input,
|
|||
|
|
|
|||
|
|
// Deprecated (not normalized) use `directories` instead.
|
|||
|
|
dir: this.config.dir,
|
|||
|
|
|
|||
|
|
runMode: this.runMode,
|
|||
|
|
outputMode: to,
|
|||
|
|
incremental: this.isIncremental,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
await this.config.events.emit("beforeBuild", eventsArg);
|
|||
|
|
await this.config.events.emit("eleventy.before", eventsArg);
|
|||
|
|
|
|||
|
|
let promise;
|
|||
|
|
if (to === "fs") {
|
|||
|
|
promise = this.writer.write();
|
|||
|
|
} else if (to === "json") {
|
|||
|
|
promise = this.writer.getJSON("json");
|
|||
|
|
} else if (to === "ndjson") {
|
|||
|
|
promise = this.writer.getJSON("ndjson");
|
|||
|
|
} else {
|
|||
|
|
throw new Error(
|
|||
|
|
`Invalid argument for \`Eleventy->executeBuild(${to})\`, expected "json", "ndjson", or "fs".`,
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let resolved = await promise;
|
|||
|
|
|
|||
|
|
// Passing the processed output to the eleventy.after event (2.0+)
|
|||
|
|
eventsArg.results = resolved.templates;
|
|||
|
|
|
|||
|
|
if (to === "ndjson") {
|
|||
|
|
// return a stream
|
|||
|
|
// TODO this outputs all ndjson rows after all the templates have been written to the stream
|
|||
|
|
returnObj = this.logger.closeStream();
|
|||
|
|
} else if (to === "json") {
|
|||
|
|
// Backwards compat
|
|||
|
|
returnObj = resolved.templates;
|
|||
|
|
} else {
|
|||
|
|
// Backwards compat
|
|||
|
|
returnObj = [resolved.passthroughCopy, resolved.templates];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.unsetIncrementalFile();
|
|||
|
|
this.writer.resetIncrementalFile();
|
|||
|
|
|
|||
|
|
eventsArg.uses = this.eleventyConfig.usesGraph.map;
|
|||
|
|
await this.config.events.emit("afterBuild", eventsArg);
|
|||
|
|
await this.config.events.emit("eleventy.after", eventsArg);
|
|||
|
|
|
|||
|
|
this.buildCount++;
|
|||
|
|
} catch (error) {
|
|||
|
|
hasError = true;
|
|||
|
|
|
|||
|
|
// Issue #2405: Don’t change the exitCode for programmatic scripts
|
|||
|
|
let errorSeverity = this.source === "script" ? "error" : "fatal";
|
|||
|
|
this.errorHandler.once(errorSeverity, error, "Problem writing Eleventy templates");
|
|||
|
|
|
|||
|
|
// TODO ndjson should stream the error but https://github.com/11ty/eleventy/issues/3382
|
|||
|
|
throw error;
|
|||
|
|
} finally {
|
|||
|
|
this.bench.finish();
|
|||
|
|
|
|||
|
|
if (to === "fs") {
|
|||
|
|
this.logger.logWithOptions({
|
|||
|
|
message: this.logFinished(),
|
|||
|
|
color: hasError ? "red" : "green",
|
|||
|
|
force: true,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
debug("Finished.");
|
|||
|
|
|
|||
|
|
debug(`
|
|||
|
|
Have a suggestion/feature request/feedback? Feeling frustrated? I want to hear it!
|
|||
|
|
Open an issue: https://github.com/11ty/eleventy/issues/new`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return returnObj;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default Eleventy;
|
|||
|
|
|
|||
|
|
// extend for exporting to CJS
|
|||
|
|
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://v3.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://v3.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}
|
|||
|
|
*/
|
|||
|
|
RenderPlugin as EleventyRenderPlugin, // legacy name
|
|||
|
|
/**
|
|||
|
|
* @type {module:11ty/eleventy/Plugins/RenderPlugin}
|
|||
|
|
*/
|
|||
|
|
RenderPlugin,
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {module:11ty/eleventy/Plugins/I18nPlugin}
|
|||
|
|
*/
|
|||
|
|
I18nPlugin as EleventyI18nPlugin, // legacy name
|
|||
|
|
/**
|
|||
|
|
* @type {module:11ty/eleventy/Plugins/I18nPlugin}
|
|||
|
|
*/
|
|||
|
|
I18nPlugin,
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {module:11ty/eleventy/Plugins/HtmlBasePlugin}
|
|||
|
|
*/
|
|||
|
|
HtmlBasePlugin as EleventyHtmlBasePlugin, // legacy name
|
|||
|
|
/**
|
|||
|
|
* @type {module:11ty/eleventy/Plugins/HtmlBasePlugin}
|
|||
|
|
*/
|
|||
|
|
HtmlBasePlugin,
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {module:11ty/eleventy/Plugins/InputPathToUrlTransformPlugin}
|
|||
|
|
*/
|
|||
|
|
InputPathToUrlTransformPlugin,
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {module:11ty/eleventy-plugin-bundle}
|
|||
|
|
*/
|
|||
|
|
BundlePlugin,
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @type {module:11ty/eleventy/Plugins/IdAttributePlugin}
|
|||
|
|
*/
|
|||
|
|
IdAttributePlugin,
|
|||
|
|
};
|