Initial commit
This commit is contained in:
48
node_modules/@11ty/eleventy/CODE_OF_CONDUCT.md
generated
vendored
Normal file
48
node_modules/@11ty/eleventy/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# Eleventy Community Code of Conduct
|
||||
|
||||
View the [Code of Conduct](https://www.11ty.dev/docs/code-of-conduct/) on 11ty.dev
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, chat messages, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at eleventy@zachleat.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
21
node_modules/@11ty/eleventy/LICENSE
generated
vendored
Normal file
21
node_modules/@11ty/eleventy/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017–2024 Zach Leatherman @zachleat
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
47
node_modules/@11ty/eleventy/README.md
generated
vendored
Normal file
47
node_modules/@11ty/eleventy/README.md
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
<p align="center"><img src="https://www.11ty.dev/img/logo-github.svg" width="200" height="200" alt="eleventy Logo"></p>
|
||||
|
||||
# eleventy 🕚⚡️🎈🐀
|
||||
|
||||
A simpler static site generator. An alternative to Jekyll. Written in JavaScript. Transforms a directory of templates (of varying types) into HTML.
|
||||
|
||||
Works with HTML, Markdown, JavaScript, Liquid, Nunjucks, with addons for WebC, Sass, Vue, Svelte, TypeScript, JSX, and many others!
|
||||
|
||||
## ➡ [Documentation](https://www.11ty.dev/docs/)
|
||||
|
||||
- Please star [this repo on GitHub](https://github.com/11ty/eleventy/)!
|
||||
- Follow us on Mastodon [@eleventy@fosstodon.org](https://fosstodon.org/@eleventy) or Twitter [@eleven_ty](https://twitter.com/eleven_ty)
|
||||
- Join us on [Discord](https://www.11ty.dev/blog/discord/)
|
||||
- Support [11ty on Open Collective](https://opencollective.com/11ty)
|
||||
- [11ty on npm](https://www.npmjs.com/org/11ty)
|
||||
- [11ty on GitHub](https://github.com/11ty)
|
||||
|
||||
[](https://www.npmjs.com/package/@11ty/eleventy) [](https://github.com/11ty/eleventy/issues) [](https://github.com/prettier/prettier) [](https://www.npmjs.com/package/@11ty/eleventy)
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install @11ty/eleventy --save-dev
|
||||
```
|
||||
|
||||
Read our [Getting Started guide](https://www.11ty.dev/docs/getting-started/).
|
||||
|
||||
## Tests
|
||||
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
|
||||
- We use the [ava JavaScript test runner](https://github.com/avajs/ava) ([Assertions documentation](https://github.com/avajs/ava/blob/master/docs/03-assertions.md))
|
||||
- ℹ️ To keep tests fast, thou shalt try to avoid writing files in tests.
|
||||
- [Continuous Integration on GitHub Actions](https://github.com/11ty/eleventy/actions/workflows/ci.yml)
|
||||
- [Code Coverage Statistics](https://github.com/11ty/eleventy/blob/master/docs/coverage.md)
|
||||
- [Benchmark for Performance Regressions](https://github.com/11ty/eleventy-benchmark)
|
||||
|
||||
## Community Roadmap
|
||||
|
||||
- [Top Feature Requests](https://github.com/11ty/eleventy/issues?q=label%3Aneeds-votes+sort%3Areactions-%2B1-desc+label%3Aenhancement) (Add your own votes using the 👍 reaction)
|
||||
- [Top Bugs 😱](https://github.com/11ty/eleventy/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions) (Add your own votes using the 👍 reaction)
|
||||
|
||||
## Plugins
|
||||
|
||||
See the [official docs on plugins](https://www.11ty.dev/docs/plugins/).
|
||||
9
node_modules/@11ty/eleventy/SECURITY.md
generated
vendored
Normal file
9
node_modules/@11ty/eleventy/SECURITY.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Privately report a security issue by navigating to https://github.com/11ty/eleventy/security and using the “Report a vulnerability” button.
|
||||
|
||||
Read more at: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability
|
||||
|
||||
Alternatively, you may report security issues via an email to `security@11ty.dev`.
|
||||
165
node_modules/@11ty/eleventy/cmd.cjs
generated
vendored
Executable file
165
node_modules/@11ty/eleventy/cmd.cjs
generated
vendored
Executable file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// This file intentionally uses older code conventions to be as friendly
|
||||
// as possible with error messaging to folks on older runtimes.
|
||||
|
||||
const pkg = require("./package.json");
|
||||
require("please-upgrade-node")(pkg, {
|
||||
message: function (requiredVersion) {
|
||||
return (
|
||||
"Eleventy " +
|
||||
pkg.version +
|
||||
" requires Node " +
|
||||
requiredVersion +
|
||||
". You will need to upgrade Node to use Eleventy!"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const minimist = require("minimist");
|
||||
const debug = require("debug")("Eleventy:cmd");
|
||||
|
||||
(async function () {
|
||||
const { EleventyErrorHandler } = await import("./src/Errors/EleventyErrorHandler.js");
|
||||
|
||||
class SimpleError extends Error {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.skipOriginalStack = true;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const argv = minimist(process.argv.slice(2), {
|
||||
string: ["input", "output", "formats", "config", "pathprefix", "port", "to", "incremental", "loader"],
|
||||
boolean: [
|
||||
"quiet",
|
||||
"version",
|
||||
"watch",
|
||||
"dryrun",
|
||||
"help",
|
||||
"serve",
|
||||
"ignore-initial",
|
||||
],
|
||||
default: {
|
||||
quiet: null,
|
||||
"ignore-initial": false,
|
||||
"to": "fs",
|
||||
},
|
||||
unknown: function (unknownArgument) {
|
||||
throw new Error(
|
||||
`We don’t know what '${unknownArgument}' is. Use --help to see the list of supported commands.`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
process.on("uncaughtException", (error) => {
|
||||
ErrorHandler.fatal(error, "Uncaught exception");
|
||||
});
|
||||
process.on("rejectionHandled", (promise) => {
|
||||
ErrorHandler.warn(promise, "A promise rejection was handled asynchronously");
|
||||
});
|
||||
|
||||
if (argv.version) {
|
||||
console.log(Eleventy.getVersion());
|
||||
return;
|
||||
} else if (argv.help) {
|
||||
console.log(Eleventy.getHelp());
|
||||
return;
|
||||
}
|
||||
|
||||
let elev = new Eleventy(argv.input, argv.output, {
|
||||
source: "cli",
|
||||
// --quiet and --quiet=true both resolve to true
|
||||
quietMode: argv.quiet,
|
||||
configPath: argv.config,
|
||||
pathPrefix: argv.pathprefix,
|
||||
runMode: argv.serve ? "serve" : argv.watch ? "watch" : "build",
|
||||
dryRun: argv.dryrun,
|
||||
loader: argv.loader,
|
||||
});
|
||||
|
||||
// reuse ErrorHandler instance in Eleventy
|
||||
ErrorHandler = elev.errorHandler;
|
||||
|
||||
// Before init
|
||||
elev.setFormats(argv.formats);
|
||||
|
||||
// careful, we can’t use async/await here to error properly
|
||||
// with old node versions in `please-upgrade-node` above.
|
||||
elev
|
||||
.init()
|
||||
.then(() => {
|
||||
if (argv.to === "json" || argv.to === "ndjson") {
|
||||
// override logging output
|
||||
elev.setIsVerbose(false);
|
||||
}
|
||||
|
||||
// Only relevant for watch/serve
|
||||
elev.setIgnoreInitial(argv["ignore-initial"]);
|
||||
|
||||
if(argv.incremental) {
|
||||
elev.setIncrementalFile(argv.incremental);
|
||||
} else if(argv.incremental !== undefined) {
|
||||
elev.setIncrementalBuild(argv.incremental === "" || argv.incremental);
|
||||
}
|
||||
|
||||
if (argv.serve || argv.watch) {
|
||||
if(argv.to === "json" || argv.to === "ndjson") {
|
||||
throw new SimpleError("--to=json and --to=ndjson are not compatible with --serve or --watch.");
|
||||
}
|
||||
|
||||
elev
|
||||
.watch()
|
||||
.then(() => {
|
||||
if (argv.serve) {
|
||||
elev.serve(argv.port);
|
||||
}
|
||||
}, error => {
|
||||
// A build error occurred and we aren’t going to --serve
|
||||
ErrorHandler.once("error", error, "Eleventy Error (Watch CLI)");
|
||||
});
|
||||
|
||||
process.on("SIGINT", async () => {
|
||||
await elev.stopWatch();
|
||||
process.exitCode = 0;
|
||||
});
|
||||
} else {
|
||||
if (!argv.to || argv.to === "fs") {
|
||||
elev.write().catch(error => {
|
||||
ErrorHandler.once("fatal", error, "Eleventy Error (FS CLI)");
|
||||
});
|
||||
} else if (argv.to === "json") {
|
||||
elev.toJSON().then(function (result) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
}, error => {
|
||||
ErrorHandler.once("fatal", error, "Eleventy Error (JSON CLI)");
|
||||
});
|
||||
} else if (argv.to === "ndjson") {
|
||||
elev.toNDJSON().then(function (stream) {
|
||||
stream.pipe(process.stdout);
|
||||
}, error => {
|
||||
ErrorHandler.once("fatal", error, "Eleventy Error (JSON CLI)");
|
||||
});
|
||||
} else {
|
||||
throw new SimpleError(
|
||||
`Invalid --to value: ${argv.to}. Supported values: \`fs\` (default), \`json\`, and \`ndjson\`.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
ErrorHandler.fatal(error, "Eleventy Error (CLI)");
|
||||
});
|
||||
} catch (error) {
|
||||
let ErrorHandler = new EleventyErrorHandler();
|
||||
ErrorHandler.fatal(error, "Eleventy Fatal Error (CLI)");
|
||||
}
|
||||
})();
|
||||
155
node_modules/@11ty/eleventy/package.json
generated
vendored
Normal file
155
node_modules/@11ty/eleventy/package.json
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"name": "@11ty/eleventy",
|
||||
"version": "3.0.0",
|
||||
"description": "A simpler static site generator.",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"type": "module",
|
||||
"main": "./src/Eleventy.js",
|
||||
"exports": {
|
||||
"import": "./src/Eleventy.js",
|
||||
"require": "./src/EleventyCommonJs.cjs"
|
||||
},
|
||||
"bin": {
|
||||
"eleventy": "cmd.cjs"
|
||||
},
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/11ty"
|
||||
},
|
||||
"keywords": [
|
||||
"static-site-generator",
|
||||
"static-site",
|
||||
"ssg",
|
||||
"documentation",
|
||||
"website",
|
||||
"jekyll",
|
||||
"blog",
|
||||
"templates",
|
||||
"generator",
|
||||
"framework",
|
||||
"eleventy",
|
||||
"11ty",
|
||||
"html",
|
||||
"markdown",
|
||||
"liquid",
|
||||
"nunjucks"
|
||||
],
|
||||
"scripts": {
|
||||
"default": "npm run test",
|
||||
"test": "npm run test:node && npm run test:ava",
|
||||
"test:ava": "ava --verbose --timeout 20s",
|
||||
"test:node": "node --test test_node/tests.js",
|
||||
"format": "prettier . --write",
|
||||
"check": "eslint src",
|
||||
"check-types": "tsc",
|
||||
"lint-staged": "lint-staged",
|
||||
"coverage": "npx c8 ava && npx c8 report --reporter=json-summary && cp coverage/coverage-summary.json docs/_data/coverage.json && node cmd.cjs --config=docs/eleventy.coverage.js",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"author": "Zach Leatherman <zachleatherman@gmail.com> (https://zachleat.com/)",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/11ty/eleventy.git"
|
||||
},
|
||||
"bugs": "https://github.com/11ty/eleventy/issues",
|
||||
"homepage": "https://www.11ty.dev/",
|
||||
"ava": {
|
||||
"environmentVariables": {},
|
||||
"failFast": true,
|
||||
"files": [
|
||||
"./test/*.js",
|
||||
"./test/_issues/**/*test.js"
|
||||
],
|
||||
"watchMode": {
|
||||
"ignoreChanges": [
|
||||
"./test/stubs*/**/*",
|
||||
".cache"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,css,md}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy-img": "5.0.0-beta.10",
|
||||
"@11ty/eleventy-plugin-rss": "^2.0.2",
|
||||
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@mdx-js/node-loader": "^3.0.1",
|
||||
"@types/node": "^22.7.4",
|
||||
"@vue/server-renderer": "^3.5.10",
|
||||
"@zachleat/noop": "^1.0.4",
|
||||
"ava": "^6.1.3",
|
||||
"c8": "^10.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"globals": "^15.10.0",
|
||||
"husky": "^9.1.6",
|
||||
"lint-staged": "^15.2.10",
|
||||
"markdown-it-emoji": "^3.0.0",
|
||||
"marked": "^14.1.2",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty": "^2.0.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"sass": "^1.79.4",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "^5.6.2",
|
||||
"vue": "^3.5.10",
|
||||
"zod": "^3.23.8",
|
||||
"zod-validation-error": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@11ty/dependency-tree": "^3.0.1",
|
||||
"@11ty/dependency-tree-esm": "^1.0.0",
|
||||
"@11ty/eleventy-dev-server": "^2.0.4",
|
||||
"@11ty/eleventy-plugin-bundle": "^3.0.0",
|
||||
"@11ty/eleventy-utils": "^1.0.3",
|
||||
"@11ty/lodash-custom": "^4.17.21",
|
||||
"@11ty/posthtml-urls": "^1.0.0",
|
||||
"@11ty/recursive-copy": "^3.0.0",
|
||||
"@sindresorhus/slugify": "^2.2.1",
|
||||
"bcp-47-normalize": "^2.3.0",
|
||||
"chardet": "^2.0.0",
|
||||
"chokidar": "^3.6.0",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"debug": "^4.3.7",
|
||||
"dependency-graph": "^1.0.0",
|
||||
"entities": "^5.0.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"filesize": "^10.1.6",
|
||||
"graceful-fs": "^4.2.11",
|
||||
"gray-matter": "^4.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"iso-639-1": "^3.1.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"kleur": "^4.1.5",
|
||||
"liquidjs": "^10.17.0",
|
||||
"luxon": "^3.5.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"micromatch": "^4.0.8",
|
||||
"minimist": "^1.2.8",
|
||||
"moo": "^0.5.2",
|
||||
"node-retrieve-globals": "^6.0.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"nunjucks": "^3.2.4",
|
||||
"please-upgrade-node": "^3.2.0",
|
||||
"posthtml": "^0.16.6",
|
||||
"posthtml-match-helper": "^2.0.2",
|
||||
"semver": "^7.6.3",
|
||||
"slugify": "^1.6.6"
|
||||
}
|
||||
}
|
||||
55
node_modules/@11ty/eleventy/src/Benchmark/Benchmark.js
generated
vendored
Normal file
55
node_modules/@11ty/eleventy/src/Benchmark/Benchmark.js
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import { performance } from "node:perf_hooks";
|
||||
|
||||
class Benchmark {
|
||||
constructor() {
|
||||
// TypeScript slop
|
||||
this.timeSpent = 0;
|
||||
this.timesCalled = 0;
|
||||
this.beforeTimers = [];
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.timeSpent = 0;
|
||||
this.timesCalled = 0;
|
||||
this.beforeTimers = [];
|
||||
}
|
||||
|
||||
getNewTimestamp() {
|
||||
if (performance) {
|
||||
return performance.now();
|
||||
}
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
incrementCount() {
|
||||
this.timesCalled++;
|
||||
}
|
||||
|
||||
// TODO(slightlyoff):
|
||||
// disable all of these hrtime requests when not benchmarking
|
||||
before() {
|
||||
this.timesCalled++;
|
||||
this.beforeTimers.push(this.getNewTimestamp());
|
||||
}
|
||||
|
||||
after() {
|
||||
if (!this.beforeTimers.length) {
|
||||
throw new Error("You called Benchmark after() without a before().");
|
||||
}
|
||||
|
||||
let before = this.beforeTimers.pop();
|
||||
if (!this.beforeTimers.length) {
|
||||
this.timeSpent += this.getNewTimestamp() - before;
|
||||
}
|
||||
}
|
||||
|
||||
getTimesCalled() {
|
||||
return this.timesCalled;
|
||||
}
|
||||
|
||||
getTotal() {
|
||||
return this.timeSpent;
|
||||
}
|
||||
}
|
||||
|
||||
export default Benchmark;
|
||||
135
node_modules/@11ty/eleventy/src/Benchmark/BenchmarkGroup.js
generated
vendored
Normal file
135
node_modules/@11ty/eleventy/src/Benchmark/BenchmarkGroup.js
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
import debugUtil from "debug";
|
||||
|
||||
import ConsoleLogger from "../Util/ConsoleLogger.js";
|
||||
import isAsyncFunction from "../Util/IsAsyncFunction.js";
|
||||
import Benchmark from "./Benchmark.js";
|
||||
|
||||
const debugBenchmark = debugUtil("Eleventy:Benchmark");
|
||||
|
||||
class BenchmarkGroup {
|
||||
constructor() {
|
||||
this.benchmarks = {};
|
||||
// Warning: aggregate benchmarks automatically default to false via BenchmarkManager->getBenchmarkGroup
|
||||
this.isVerbose = true;
|
||||
this.logger = new ConsoleLogger();
|
||||
this.minimumThresholdMs = 50;
|
||||
this.minimumThresholdPercent = 8;
|
||||
}
|
||||
|
||||
setIsVerbose(isVerbose) {
|
||||
this.isVerbose = isVerbose;
|
||||
this.logger.isVerbose = isVerbose;
|
||||
}
|
||||
|
||||
reset() {
|
||||
for (var type in this.benchmarks) {
|
||||
this.benchmarks[type].reset();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO use addAsync everywhere instead
|
||||
add(type, callback) {
|
||||
let benchmark = (this.benchmarks[type] = new Benchmark());
|
||||
|
||||
/** @this {any} */
|
||||
let fn = function (...args) {
|
||||
benchmark.before();
|
||||
let ret = callback.call(this, ...args);
|
||||
benchmark.after();
|
||||
return ret;
|
||||
};
|
||||
|
||||
Object.defineProperty(fn, "__eleventyInternal", {
|
||||
value: {
|
||||
type: isAsyncFunction(callback) ? "async" : "sync",
|
||||
callback,
|
||||
},
|
||||
});
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
// callback must return a promise
|
||||
// async addAsync(type, callback) {
|
||||
// let benchmark = (this.benchmarks[type] = new Benchmark());
|
||||
|
||||
// benchmark.before();
|
||||
// // don’t await here.
|
||||
// let promise = callback.call(this);
|
||||
// promise.then(function() {
|
||||
// benchmark.after();
|
||||
// });
|
||||
// return promise;
|
||||
// }
|
||||
|
||||
setMinimumThresholdMs(minimumThresholdMs) {
|
||||
let val = parseInt(minimumThresholdMs, 10);
|
||||
if (isNaN(val)) {
|
||||
throw new Error("`setMinimumThresholdMs` expects a number argument.");
|
||||
}
|
||||
this.minimumThresholdMs = val;
|
||||
}
|
||||
|
||||
setMinimumThresholdPercent(minimumThresholdPercent) {
|
||||
let val = parseInt(minimumThresholdPercent, 10);
|
||||
if (isNaN(val)) {
|
||||
throw new Error("`setMinimumThresholdPercent` expects a number argument.");
|
||||
}
|
||||
this.minimumThresholdPercent = val;
|
||||
}
|
||||
|
||||
has(type) {
|
||||
return !!this.benchmarks[type];
|
||||
}
|
||||
|
||||
get(type) {
|
||||
if (!this.benchmarks[type]) {
|
||||
this.benchmarks[type] = new Benchmark();
|
||||
}
|
||||
return this.benchmarks[type];
|
||||
}
|
||||
|
||||
padNumber(num, length) {
|
||||
if (("" + num).length >= length) {
|
||||
return num;
|
||||
}
|
||||
|
||||
let prefix = new Array(length + 1).join(" ");
|
||||
return (prefix + num).slice(-1 * length);
|
||||
}
|
||||
|
||||
finish(label, totalTimeSpent) {
|
||||
for (var type in this.benchmarks) {
|
||||
let bench = this.benchmarks[type];
|
||||
let isAbsoluteMinimumComparison = this.minimumThresholdMs > 0;
|
||||
let totalForBenchmark = bench.getTotal();
|
||||
let percent = Math.round((totalForBenchmark * 100) / totalTimeSpent);
|
||||
let callCount = bench.getTimesCalled();
|
||||
|
||||
let output = {
|
||||
ms: this.padNumber(totalForBenchmark.toFixed(0), 6),
|
||||
percent: this.padNumber(percent, 3),
|
||||
calls: this.padNumber(callCount, 5),
|
||||
};
|
||||
let str = `Benchmark ${output.ms}ms ${output.percent}% ${output.calls}× (${label}) ${type}`;
|
||||
|
||||
if (
|
||||
isAbsoluteMinimumComparison &&
|
||||
totalForBenchmark >= this.minimumThresholdMs &&
|
||||
percent > this.minimumThresholdPercent
|
||||
) {
|
||||
this.logger.warn(str);
|
||||
}
|
||||
|
||||
// Opt out of logging if low count (1× or 2×) or 0ms / 1%
|
||||
if (
|
||||
callCount > 1 || // called more than once
|
||||
Math.round(totalForBenchmark) > 0 // more than 0.5ms
|
||||
) {
|
||||
debugBenchmark(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BenchmarkGroup;
|
||||
73
node_modules/@11ty/eleventy/src/Benchmark/BenchmarkManager.js
generated
vendored
Normal file
73
node_modules/@11ty/eleventy/src/Benchmark/BenchmarkManager.js
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
import { performance } from "node:perf_hooks";
|
||||
|
||||
import BenchmarkGroup from "./BenchmarkGroup.js";
|
||||
|
||||
// TODO this should not be a singleton, it belongs in the config or somewhere on the Eleventy instance.
|
||||
|
||||
class BenchmarkManager {
|
||||
constructor() {
|
||||
this.benchmarkGroups = {};
|
||||
this.isVerbose = true;
|
||||
this.start = this.getNewTimestamp();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.start = this.getNewTimestamp();
|
||||
|
||||
for (var j in this.benchmarkGroups) {
|
||||
this.benchmarkGroups[j].reset();
|
||||
}
|
||||
}
|
||||
|
||||
getNewTimestamp() {
|
||||
if (performance) {
|
||||
return performance.now();
|
||||
}
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
setVerboseOutput(isVerbose) {
|
||||
this.isVerbose = !!isVerbose;
|
||||
}
|
||||
|
||||
hasBenchmarkGroup(name) {
|
||||
return name in this.benchmarkGroups;
|
||||
}
|
||||
|
||||
getBenchmarkGroup(name) {
|
||||
if (!this.benchmarkGroups[name]) {
|
||||
this.benchmarkGroups[name] = new BenchmarkGroup();
|
||||
|
||||
// Special behavior for aggregate benchmarks
|
||||
// so they don’t console.log every time
|
||||
if (name === "Aggregate") {
|
||||
this.benchmarkGroups[name].setIsVerbose(false);
|
||||
} else {
|
||||
this.benchmarkGroups[name].setIsVerbose(this.isVerbose);
|
||||
}
|
||||
}
|
||||
|
||||
return this.benchmarkGroups[name];
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return this.benchmarkGroups;
|
||||
}
|
||||
|
||||
get(name) {
|
||||
if (name) {
|
||||
return this.getBenchmarkGroup(name);
|
||||
}
|
||||
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
finish() {
|
||||
let totalTimeSpentBenchmarking = this.getNewTimestamp() - this.start;
|
||||
for (var j in this.benchmarkGroups) {
|
||||
this.benchmarkGroups[j].finish(j, totalTimeSpentBenchmarking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BenchmarkManager;
|
||||
121
node_modules/@11ty/eleventy/src/Data/ComputedData.js
generated
vendored
Normal file
121
node_modules/@11ty/eleventy/src/Data/ComputedData.js
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import ComputedDataQueue from "./ComputedDataQueue.js";
|
||||
import ComputedDataTemplateString from "./ComputedDataTemplateString.js";
|
||||
import ComputedDataProxy from "./ComputedDataProxy.js";
|
||||
|
||||
const { set: lodashSet, get: lodashGet } = lodash;
|
||||
const debug = debugUtil("Eleventy:ComputedData");
|
||||
|
||||
class ComputedData {
|
||||
constructor(config) {
|
||||
this.computed = {};
|
||||
this.symbolParseFunctions = {};
|
||||
this.templateStringKeyLookup = {};
|
||||
this.computedKeys = new Set();
|
||||
this.declaredDependencies = {};
|
||||
this.queue = new ComputedDataQueue();
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
add(key, renderFn, declaredDependencies = [], symbolParseFn, templateInstance) {
|
||||
this.computedKeys.add(key);
|
||||
this.declaredDependencies[key] = declaredDependencies;
|
||||
|
||||
// bind config filters/JS functions
|
||||
if (typeof renderFn === "function") {
|
||||
let fns = {};
|
||||
// TODO bug? no access to non-universal config things?
|
||||
if (this.config) {
|
||||
fns = this.config.javascriptFunctions;
|
||||
}
|
||||
renderFn = renderFn.bind({
|
||||
...fns,
|
||||
tmpl: templateInstance,
|
||||
});
|
||||
}
|
||||
|
||||
lodashSet(this.computed, key, renderFn);
|
||||
|
||||
if (symbolParseFn) {
|
||||
lodashSet(this.symbolParseFunctions, key, symbolParseFn);
|
||||
}
|
||||
}
|
||||
|
||||
addTemplateString(key, renderFn, declaredDependencies = [], symbolParseFn, templateInstance) {
|
||||
this.add(key, renderFn, declaredDependencies, symbolParseFn, templateInstance);
|
||||
this.templateStringKeyLookup[key] = true;
|
||||
}
|
||||
|
||||
async resolveVarOrder(data) {
|
||||
let proxyByTemplateString = new ComputedDataTemplateString(this.computedKeys);
|
||||
let proxyByProxy = new ComputedDataProxy(this.computedKeys);
|
||||
|
||||
for (let key of this.computedKeys) {
|
||||
let computed = lodashGet(this.computed, key);
|
||||
|
||||
if (typeof computed !== "function") {
|
||||
// add nodes for non functions (primitives like booleans, etc)
|
||||
// This will not handle template strings, as they are normalized to functions
|
||||
this.queue.addNode(key);
|
||||
} else {
|
||||
this.queue.uses(key, this.declaredDependencies[key]);
|
||||
|
||||
let symbolParseFn = lodashGet(this.symbolParseFunctions, key);
|
||||
let varsUsed = [];
|
||||
if (symbolParseFn) {
|
||||
// use the parseForSymbols function in the TemplateEngine
|
||||
varsUsed = symbolParseFn();
|
||||
} else if (symbolParseFn !== false) {
|
||||
// skip resolution is this is false (just use declaredDependencies)
|
||||
let isTemplateString = !!this.templateStringKeyLookup[key];
|
||||
let proxy = isTemplateString ? proxyByTemplateString : proxyByProxy;
|
||||
varsUsed = await proxy.findVarsUsed(computed, data);
|
||||
}
|
||||
|
||||
debug("%o accesses %o variables", key, varsUsed);
|
||||
let filteredVarsUsed = varsUsed.filter((varUsed) => {
|
||||
return (
|
||||
(varUsed !== key && this.computedKeys.has(varUsed)) ||
|
||||
varUsed.startsWith("collections.")
|
||||
);
|
||||
});
|
||||
this.queue.uses(key, filteredVarsUsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _setupDataEntry(data, order) {
|
||||
debug("Computed data order of execution: %o", order);
|
||||
for (let key of order) {
|
||||
let computed = lodashGet(this.computed, key);
|
||||
|
||||
if (typeof computed === "function") {
|
||||
let ret = await computed(data);
|
||||
lodashSet(data, key, ret);
|
||||
} else if (computed !== undefined) {
|
||||
lodashSet(data, key, computed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async setupData(data, orderFilter) {
|
||||
await this.resolveVarOrder(data);
|
||||
|
||||
await this.processRemainingData(data, orderFilter);
|
||||
}
|
||||
|
||||
async processRemainingData(data, orderFilter) {
|
||||
// process all variables
|
||||
let order = this.queue.getOrder();
|
||||
if (orderFilter && typeof orderFilter === "function") {
|
||||
order = order.filter(orderFilter.bind(this.queue));
|
||||
}
|
||||
|
||||
await this._setupDataEntry(data, order);
|
||||
this.queue.markComputed(order);
|
||||
}
|
||||
}
|
||||
|
||||
export default ComputedData;
|
||||
131
node_modules/@11ty/eleventy/src/Data/ComputedDataProxy.js
generated
vendored
Normal file
131
node_modules/@11ty/eleventy/src/Data/ComputedDataProxy.js
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
import { isPlainObject } from "@11ty/eleventy-utils";
|
||||
|
||||
const { set: lodashSet, get: lodashGet } = lodash;
|
||||
|
||||
/* Calculates computed data using Proxies */
|
||||
class ComputedDataProxy {
|
||||
constructor(computedKeys) {
|
||||
if (Array.isArray(computedKeys)) {
|
||||
this.computedKeys = new Set(computedKeys);
|
||||
} else {
|
||||
this.computedKeys = computedKeys;
|
||||
}
|
||||
}
|
||||
|
||||
isArrayOrPlainObject(data) {
|
||||
return Array.isArray(data) || isPlainObject(data);
|
||||
}
|
||||
|
||||
getProxyData(data, keyRef) {
|
||||
// WARNING: SIDE EFFECTS
|
||||
// Set defaults for keys not already set on parent data
|
||||
|
||||
// TODO should make another effort to get rid of this,
|
||||
// See the ProxyWrap util for more proxy handlers that will likely fix this
|
||||
let undefinedValue = "__11TY_UNDEFINED__";
|
||||
if (this.computedKeys) {
|
||||
for (let key of this.computedKeys) {
|
||||
if (lodashGet(data, key, undefinedValue) === undefinedValue) {
|
||||
lodashSet(data, key, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let proxyData = this._getProxyData(data, keyRef);
|
||||
return proxyData;
|
||||
}
|
||||
|
||||
_getProxyForObject(dataObj, keyRef, parentKey = "") {
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (obj, key) => {
|
||||
if (typeof key !== "string") {
|
||||
return obj[key];
|
||||
}
|
||||
|
||||
let newKey = `${parentKey ? `${parentKey}.` : ""}${key}`;
|
||||
|
||||
// Issue #1137
|
||||
// Special case for Collections, always return an Array for collection keys
|
||||
// so they it works fine with Array methods like `filter`, `map`, etc
|
||||
if (newKey === "collections") {
|
||||
keyRef.add(newKey);
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, key) => {
|
||||
if (typeof key === "string") {
|
||||
keyRef.add(`collections.${key}`);
|
||||
return [];
|
||||
}
|
||||
return target[key];
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let newData = this._getProxyData(dataObj[key], keyRef, newKey);
|
||||
if (!this.isArrayOrPlainObject(newData)) {
|
||||
keyRef.add(newKey);
|
||||
}
|
||||
return newData;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_getProxyForArray(dataArr, keyRef, parentKey = "") {
|
||||
return new Proxy(new Array(dataArr.length), {
|
||||
get: (obj, key) => {
|
||||
if (Array.prototype.hasOwnProperty(key)) {
|
||||
// remove `filter`, `constructor`, `map`, etc
|
||||
keyRef.add(parentKey);
|
||||
return obj[key];
|
||||
}
|
||||
|
||||
// Hm, this needs to be better
|
||||
if (key === "then") {
|
||||
keyRef.add(parentKey);
|
||||
return;
|
||||
}
|
||||
|
||||
let newKey = `${parentKey}[${key}]`;
|
||||
let newData = this._getProxyData(dataArr[key], keyRef, newKey);
|
||||
if (!this.isArrayOrPlainObject(newData)) {
|
||||
keyRef.add(newKey);
|
||||
}
|
||||
return newData;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_getProxyData(data, keyRef, parentKey = "") {
|
||||
if (isPlainObject(data)) {
|
||||
return this._getProxyForObject(data, keyRef, parentKey);
|
||||
} else if (Array.isArray(data)) {
|
||||
return this._getProxyForArray(data, keyRef, parentKey);
|
||||
}
|
||||
|
||||
// everything else!
|
||||
return data;
|
||||
}
|
||||
|
||||
async findVarsUsed(fn, data = {}) {
|
||||
let keyRef = new Set();
|
||||
|
||||
// careful, logging proxyData will mess with test results!
|
||||
let proxyData = this.getProxyData(data, keyRef);
|
||||
|
||||
// squelch console logs for this fake proxy data pass 😅
|
||||
// let savedLog = console.log;
|
||||
// console.log = () => {};
|
||||
await fn(proxyData);
|
||||
// console.log = savedLog;
|
||||
|
||||
return Array.from(keyRef);
|
||||
}
|
||||
}
|
||||
|
||||
export default ComputedDataProxy;
|
||||
64
node_modules/@11ty/eleventy/src/Data/ComputedDataQueue.js
generated
vendored
Normal file
64
node_modules/@11ty/eleventy/src/Data/ComputedDataQueue.js
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import { DepGraph as DependencyGraph } from "dependency-graph";
|
||||
|
||||
/* Keeps track of the dependency graph between computed data variables
|
||||
* Removes keys from the graph when they are computed.
|
||||
*/
|
||||
class ComputedDataQueue {
|
||||
constructor() {
|
||||
this.graph = new DependencyGraph();
|
||||
}
|
||||
|
||||
getOrder() {
|
||||
return this.graph.overallOrder();
|
||||
}
|
||||
|
||||
getOrderFor(name) {
|
||||
return this.graph.dependenciesOf(name);
|
||||
}
|
||||
|
||||
getDependsOn(name) {
|
||||
return this.graph.dependantsOf(name);
|
||||
}
|
||||
|
||||
isUsesStartsWith(name, prefix) {
|
||||
if (name.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
this.graph.dependenciesOf(name).filter((entry) => {
|
||||
return entry.startsWith(prefix);
|
||||
}).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
addNode(name) {
|
||||
if (!this.graph.hasNode(name)) {
|
||||
this.graph.addNode(name);
|
||||
}
|
||||
}
|
||||
|
||||
_uses(graph, name, varsUsed = []) {
|
||||
if (!graph.hasNode(name)) {
|
||||
graph.addNode(name);
|
||||
}
|
||||
|
||||
for (let varUsed of varsUsed) {
|
||||
if (!graph.hasNode(varUsed)) {
|
||||
graph.addNode(varUsed);
|
||||
}
|
||||
graph.addDependency(name, varUsed);
|
||||
}
|
||||
}
|
||||
|
||||
uses(name, varsUsed = []) {
|
||||
this._uses(this.graph, name, varsUsed);
|
||||
}
|
||||
|
||||
markComputed(varsComputed = []) {
|
||||
for (let varComputed of varsComputed) {
|
||||
this.graph.removeNode(varComputed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ComputedDataQueue;
|
||||
70
node_modules/@11ty/eleventy/src/Data/ComputedDataTemplateString.js
generated
vendored
Normal file
70
node_modules/@11ty/eleventy/src/Data/ComputedDataTemplateString.js
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
import debugUtil from "debug";
|
||||
|
||||
const { set: lodashSet } = lodash;
|
||||
const debug = debugUtil("Eleventy:ComputedDataTemplateString");
|
||||
|
||||
/* Calculates computed data in Template Strings.
|
||||
* Ideally we would use the Proxy approach but it doesn’t work
|
||||
* in some template languages that visit all available data even if
|
||||
* it isn’t used in the template (Nunjucks)
|
||||
*/
|
||||
class ComputedDataTemplateString {
|
||||
constructor(computedKeys) {
|
||||
if (Array.isArray(computedKeys)) {
|
||||
this.computedKeys = new Set(computedKeys);
|
||||
} else {
|
||||
this.computedKeys = computedKeys;
|
||||
}
|
||||
|
||||
// is this ¯\_(lisp)_/¯
|
||||
// must be strings that won’t be escaped by template languages
|
||||
this.prefix = "(((11ty(((";
|
||||
this.suffix = ")))11ty)))";
|
||||
}
|
||||
|
||||
getProxyData() {
|
||||
let proxyData = {};
|
||||
|
||||
// use these special strings as a workaround to check the rendered output
|
||||
// can’t use proxies here as some template languages trigger proxy for all
|
||||
// keys in data
|
||||
for (let key of this.computedKeys) {
|
||||
// TODO don’t allow to set eleventyComputed.page? other disallowed computed things?
|
||||
lodashSet(proxyData, key, this.prefix + key + this.suffix);
|
||||
}
|
||||
|
||||
return proxyData;
|
||||
}
|
||||
|
||||
findVarsInOutput(output = "") {
|
||||
let vars = new Set();
|
||||
let splits = output.split(this.prefix);
|
||||
for (let split of splits) {
|
||||
let varName = split.slice(0, split.indexOf(this.suffix) < 0 ? 0 : split.indexOf(this.suffix));
|
||||
if (varName) {
|
||||
vars.add(varName);
|
||||
}
|
||||
}
|
||||
return Array.from(vars);
|
||||
}
|
||||
|
||||
async findVarsUsed(fn) {
|
||||
let proxyData = this.getProxyData();
|
||||
let output;
|
||||
// Mitigation for #1061, errors with filters in the first pass shouldn’t fail the whole thing.
|
||||
try {
|
||||
output = await fn(proxyData);
|
||||
} catch (e) {
|
||||
debug("Computed Data first pass data resolution error: %o", e);
|
||||
}
|
||||
|
||||
// page.outputPath on serverless urls returns false.
|
||||
if (typeof output === "string") {
|
||||
return this.findVarsInOutput(output);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default ComputedDataTemplateString;
|
||||
737
node_modules/@11ty/eleventy/src/Data/TemplateData.js
generated
vendored
Normal file
737
node_modules/@11ty/eleventy/src/Data/TemplateData.js
generated
vendored
Normal file
@@ -0,0 +1,737 @@
|
||||
import path from "node:path";
|
||||
import semver from "semver";
|
||||
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
import { Merge, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import unique from "../Util/Objects/Unique.js";
|
||||
import TemplateGlob from "../TemplateGlob.js";
|
||||
import EleventyExtensionMap from "../EleventyExtensionMap.js";
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
import TemplateDataInitialGlobalData from "./TemplateDataInitialGlobalData.js";
|
||||
import { getEleventyPackageJson, getWorkingProjectPackageJson } from "../Util/ImportJsonSync.js";
|
||||
import { EleventyImport, EleventyLoadContent } from "../Util/Require.js";
|
||||
import { DeepFreeze } from "../Util/Objects/DeepFreeze.js";
|
||||
|
||||
const { set: lodashSet, get: lodashGet } = lodash;
|
||||
|
||||
const debugWarn = debugUtil("Eleventy:Warnings");
|
||||
const debug = debugUtil("Eleventy:TemplateData");
|
||||
const debugDev = debugUtil("Dev:Eleventy:TemplateData");
|
||||
|
||||
class TemplateDataConfigError extends EleventyBaseError {}
|
||||
class TemplateDataParseError extends EleventyBaseError {}
|
||||
|
||||
class TemplateData {
|
||||
constructor(eleventyConfig) {
|
||||
if (!eleventyConfig) {
|
||||
throw new TemplateDataConfigError("Missing `config`.");
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
this.config = this.eleventyConfig.getConfig();
|
||||
|
||||
this.benchmarks = {
|
||||
data: this.config.benchmarkManager.get("Data"),
|
||||
aggregate: this.config.benchmarkManager.get("Aggregate"),
|
||||
};
|
||||
|
||||
this.rawImports = {};
|
||||
this.globalData = null;
|
||||
this.templateDirectoryData = {};
|
||||
this.isEsm = false;
|
||||
|
||||
this.initialGlobalData = new TemplateDataInitialGlobalData(this.eleventyConfig);
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
// if this was set but `falsy` we would fallback to inputDir
|
||||
get dataDir() {
|
||||
return this.dirs.data;
|
||||
}
|
||||
|
||||
// This was async in 2.0 and prior but doesn’t need to be any more.
|
||||
getInputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
getDataDir() {
|
||||
return this.dataDir;
|
||||
}
|
||||
|
||||
get _fsExistsCache() {
|
||||
// It's common for data files not to exist, so we avoid going to the FS to
|
||||
// re-check if they do via a quick-and-dirty cache.
|
||||
return this.eleventyConfig.existsCache;
|
||||
}
|
||||
|
||||
setFileSystemSearch(fileSystemSearch) {
|
||||
this.fileSystemSearch = fileSystemSearch;
|
||||
}
|
||||
|
||||
setProjectUsingEsm(isEsmProject) {
|
||||
this.isEsm = !!isEsmProject;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats([]);
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
set extensionMap(map) {
|
||||
this._extensionMap = map;
|
||||
}
|
||||
|
||||
get environmentVariables() {
|
||||
return this._env;
|
||||
}
|
||||
|
||||
set environmentVariables(env) {
|
||||
this._env = env;
|
||||
}
|
||||
|
||||
/* Used by tests */
|
||||
_setConfig(config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
getRawImports() {
|
||||
if (!this.config.keys.package) {
|
||||
debug(
|
||||
"Opted-out of package.json assignment for global data with falsy value for `keys.package` configuration.",
|
||||
);
|
||||
return this.rawImports;
|
||||
} else if (Object.keys(this.rawImports).length > 0) {
|
||||
return this.rawImports;
|
||||
}
|
||||
|
||||
try {
|
||||
let pkgJson = getWorkingProjectPackageJson();
|
||||
this.rawImports[this.config.keys.package] = pkgJson;
|
||||
|
||||
if (this.config.freezeReservedData) {
|
||||
DeepFreeze(this.rawImports);
|
||||
}
|
||||
} catch (e) {
|
||||
debug("Could not find or require package.json import for global data.");
|
||||
}
|
||||
|
||||
return this.rawImports;
|
||||
}
|
||||
|
||||
clearData() {
|
||||
this.globalData = null;
|
||||
this.configApiGlobalData = null;
|
||||
this.templateDirectoryData = {};
|
||||
}
|
||||
|
||||
_getGlobalDataGlobByExtension(extension) {
|
||||
return TemplateGlob.normalizePath(this.dataDir, `/**/*.${extension}`);
|
||||
}
|
||||
|
||||
// This is a backwards compatibility helper with the old `jsDataFileSuffix` configuration API
|
||||
getDataFileSuffixes() {
|
||||
// New API
|
||||
if (Array.isArray(this.config.dataFileSuffixes)) {
|
||||
return this.config.dataFileSuffixes;
|
||||
}
|
||||
|
||||
// Backwards compatibility
|
||||
if (this.config.jsDataFileSuffix) {
|
||||
let suffixes = [];
|
||||
suffixes.push(this.config.jsDataFileSuffix); // e.g. filename.11tydata.json
|
||||
suffixes.push(""); // suffix-less for free with old API, e.g. filename.json
|
||||
return suffixes;
|
||||
}
|
||||
return []; // if both of these entries are set to false, use no files
|
||||
}
|
||||
|
||||
// This is used exclusively for --watch and --serve chokidar targets
|
||||
async getTemplateDataFileGlob() {
|
||||
let suffixes = this.getDataFileSuffixes();
|
||||
let globSuffixesWithLeadingDot = new Set();
|
||||
globSuffixesWithLeadingDot.add("json"); // covers .11tydata.json too
|
||||
let globSuffixesWithoutLeadingDot = new Set();
|
||||
|
||||
// Typically using [ '.11tydata', '' ] suffixes to find data files
|
||||
for (let suffix of suffixes) {
|
||||
// TODO the `suffix` truthiness check is purely for backwards compat?
|
||||
if (suffix && typeof suffix === "string") {
|
||||
if (suffix.startsWith(".")) {
|
||||
// .suffix.js
|
||||
globSuffixesWithLeadingDot.add(`${suffix.slice(1)}.mjs`);
|
||||
globSuffixesWithLeadingDot.add(`${suffix.slice(1)}.cjs`);
|
||||
globSuffixesWithLeadingDot.add(`${suffix.slice(1)}.js`);
|
||||
} else {
|
||||
// "suffix.js" without leading dot
|
||||
globSuffixesWithoutLeadingDot.add(`${suffix || ""}.mjs`);
|
||||
globSuffixesWithoutLeadingDot.add(`${suffix || ""}.cjs`);
|
||||
globSuffixesWithoutLeadingDot.add(`${suffix || ""}.js`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configuration Data Extensions e.g. yaml
|
||||
if (this.hasUserDataExtensions()) {
|
||||
for (let extension of this.getUserDataExtensions()) {
|
||||
globSuffixesWithLeadingDot.add(extension); // covers .11tydata.{extension} too
|
||||
}
|
||||
}
|
||||
|
||||
let paths = [];
|
||||
if (globSuffixesWithLeadingDot.size > 0) {
|
||||
paths.push(`${this.inputDir}**/*.{${Array.from(globSuffixesWithLeadingDot).join(",")}}`);
|
||||
}
|
||||
if (globSuffixesWithoutLeadingDot.size > 0) {
|
||||
paths.push(`${this.inputDir}**/*{${Array.from(globSuffixesWithoutLeadingDot).join(",")}}`);
|
||||
}
|
||||
|
||||
return TemplatePath.addLeadingDotSlashArray(paths);
|
||||
}
|
||||
|
||||
// For spidering dependencies
|
||||
// TODO Can we reuse getTemplateDataFileGlob instead? Maybe just filter off the .json files before scanning for dependencies
|
||||
getTemplateJavaScriptDataFileGlob() {
|
||||
let paths = [];
|
||||
let suffixes = this.getDataFileSuffixes();
|
||||
for (let suffix of suffixes) {
|
||||
if (suffix) {
|
||||
// TODO this check is purely for backwards compat and I kinda feel like it shouldn’t be here
|
||||
// paths.push(`${this.inputDir}/**/*${suffix || ""}.cjs`); // Same as above
|
||||
paths.push(`${this.inputDir}**/*${suffix || ""}.js`);
|
||||
}
|
||||
}
|
||||
|
||||
return TemplatePath.addLeadingDotSlashArray(paths);
|
||||
}
|
||||
|
||||
getGlobalDataGlob() {
|
||||
let extGlob = this.getGlobalDataExtensionPriorities().join(",");
|
||||
return [this._getGlobalDataGlobByExtension("{" + extGlob + "}")];
|
||||
}
|
||||
|
||||
getWatchPathCache() {
|
||||
return this.pathCache;
|
||||
}
|
||||
|
||||
getGlobalDataExtensionPriorities() {
|
||||
return this.getUserDataExtensions().concat(["json", "mjs", "cjs", "js"]);
|
||||
}
|
||||
|
||||
static calculateExtensionPriority(path, priorities) {
|
||||
for (let i = 0; i < priorities.length; i++) {
|
||||
let ext = priorities[i];
|
||||
if (path.endsWith(ext)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return priorities.length;
|
||||
}
|
||||
|
||||
async getGlobalDataFiles() {
|
||||
let priorities = this.getGlobalDataExtensionPriorities();
|
||||
|
||||
let fsBench = this.benchmarks.aggregate.get("Searching the file system (data)");
|
||||
fsBench.before();
|
||||
let globs = this.getGlobalDataGlob();
|
||||
let paths = await this.fileSystemSearch.search("global-data", globs);
|
||||
fsBench.after();
|
||||
|
||||
// sort paths according to extension priorities
|
||||
// here we use reverse ordering, because paths with bigger index in array will override the first ones
|
||||
// example [path/file.json, path/file.js] here js will override json
|
||||
paths = paths.sort((first, second) => {
|
||||
let p1 = TemplateData.calculateExtensionPriority(first, priorities);
|
||||
let p2 = TemplateData.calculateExtensionPriority(second, priorities);
|
||||
if (p1 < p2) {
|
||||
return -1;
|
||||
}
|
||||
if (p1 > p2) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.pathCache = paths;
|
||||
return paths;
|
||||
}
|
||||
|
||||
getObjectPathForDataFile(dataFilePath) {
|
||||
let reducedPath = TemplatePath.stripLeadingSubPath(dataFilePath, this.dataDir);
|
||||
let parsed = path.parse(reducedPath);
|
||||
let folders = parsed.dir ? parsed.dir.split("/") : [];
|
||||
folders.push(parsed.name);
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
||||
async getAllGlobalData() {
|
||||
let globalData = {};
|
||||
let files = TemplatePath.addLeadingDotSlashArray(await this.getGlobalDataFiles());
|
||||
|
||||
this.config.events.emit("eleventy.globalDataFiles", files);
|
||||
|
||||
let dataFileConflicts = {};
|
||||
|
||||
for (let j = 0, k = files.length; j < k; j++) {
|
||||
let data = await this.getDataValue(files[j]);
|
||||
let objectPathTarget = this.getObjectPathForDataFile(files[j]);
|
||||
|
||||
// Since we're joining directory paths and an array is not usable as an objectkey since two identical arrays are not double equal,
|
||||
// we can just join the array by a forbidden character ("/"" is chosen here, since it works on Linux, Mac and Windows).
|
||||
// If at some point this isn't enough anymore, it would be possible to just use JSON.stringify(objectPathTarget) since that
|
||||
// is guaranteed to work but is signifivcantly slower.
|
||||
let objectPathTargetString = objectPathTarget.join(path.sep);
|
||||
|
||||
// if two global files have the same path (but different extensions)
|
||||
// and conflict, let’s merge them.
|
||||
if (dataFileConflicts[objectPathTargetString]) {
|
||||
debugWarn(
|
||||
`merging global data from ${files[j]} with an already existing global data file (${dataFileConflicts[objectPathTargetString]}). Overriding existing keys.`,
|
||||
);
|
||||
|
||||
let oldData = lodashGet(globalData, objectPathTarget);
|
||||
data = TemplateData.mergeDeep(this.config.dataDeepMerge, oldData, data);
|
||||
}
|
||||
|
||||
dataFileConflicts[objectPathTargetString] = files[j];
|
||||
debug(`Found global data file ${files[j]} and adding as: ${objectPathTarget}`);
|
||||
lodashSet(globalData, objectPathTarget, data);
|
||||
}
|
||||
|
||||
return globalData;
|
||||
}
|
||||
|
||||
async #getInitialGlobalData() {
|
||||
let globalData = await this.initialGlobalData.getData();
|
||||
|
||||
if (!("eleventy" in globalData)) {
|
||||
globalData.eleventy = {};
|
||||
}
|
||||
|
||||
// #2293 for meta[name=generator]
|
||||
const pkg = getEleventyPackageJson();
|
||||
globalData.eleventy.version = semver.coerce(pkg.version).toString();
|
||||
globalData.eleventy.generator = `Eleventy v${globalData.eleventy.version}`;
|
||||
|
||||
if (this.environmentVariables) {
|
||||
if (!("env" in globalData.eleventy)) {
|
||||
globalData.eleventy.env = {};
|
||||
}
|
||||
|
||||
Object.assign(globalData.eleventy.env, this.environmentVariables);
|
||||
}
|
||||
|
||||
if (this.dirs) {
|
||||
if (!("directories" in globalData.eleventy)) {
|
||||
globalData.eleventy.directories = {};
|
||||
}
|
||||
|
||||
Object.assign(globalData.eleventy.directories, this.dirs.getUserspaceInstance());
|
||||
}
|
||||
|
||||
// Reserved
|
||||
if (this.config.freezeReservedData) {
|
||||
DeepFreeze(globalData.eleventy);
|
||||
}
|
||||
|
||||
return globalData;
|
||||
}
|
||||
|
||||
async getInitialGlobalData() {
|
||||
if (!this.configApiGlobalData) {
|
||||
this.configApiGlobalData = this.#getInitialGlobalData();
|
||||
}
|
||||
|
||||
return this.configApiGlobalData;
|
||||
}
|
||||
|
||||
async #getGlobalData() {
|
||||
let rawImports = this.getRawImports();
|
||||
let configApiGlobalData = await this.getInitialGlobalData();
|
||||
|
||||
let globalJson = await this.getAllGlobalData();
|
||||
let mergedGlobalData = Merge(globalJson, configApiGlobalData);
|
||||
|
||||
// OK: Shallow merge when combining rawImports (pkg) with global data files
|
||||
return Object.assign({}, mergedGlobalData, rawImports);
|
||||
}
|
||||
|
||||
async getGlobalData() {
|
||||
if (!this.globalData) {
|
||||
this.globalData = this.#getGlobalData();
|
||||
}
|
||||
|
||||
return this.globalData;
|
||||
}
|
||||
|
||||
/* Template and Directory data files */
|
||||
async combineLocalData(localDataPaths) {
|
||||
let localData = {};
|
||||
if (!Array.isArray(localDataPaths)) {
|
||||
localDataPaths = [localDataPaths];
|
||||
}
|
||||
|
||||
// Filter out files we know don't exist to avoid overhead for checking
|
||||
const dataPaths = await Promise.all(
|
||||
localDataPaths.map((path) => {
|
||||
if (this._fsExistsCache.exists(path)) {
|
||||
return path;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
);
|
||||
|
||||
localDataPaths = dataPaths.filter((pathOrFalse) => {
|
||||
return pathOrFalse === false ? false : true;
|
||||
});
|
||||
|
||||
this.config.events.emit("eleventy.dataFiles", localDataPaths);
|
||||
|
||||
if (!localDataPaths.length) {
|
||||
return localData;
|
||||
}
|
||||
|
||||
let dataSource = {};
|
||||
for (let path of localDataPaths) {
|
||||
let dataForPath = await this.getDataValue(path);
|
||||
if (!isPlainObject(dataForPath)) {
|
||||
debug(
|
||||
"Warning: Template and Directory data files expect an object to be returned, instead `%o` returned `%o`",
|
||||
path,
|
||||
dataForPath,
|
||||
);
|
||||
} else {
|
||||
// clean up data for template/directory data files only.
|
||||
let cleanedDataForPath = TemplateData.cleanupData(dataForPath);
|
||||
for (let key in cleanedDataForPath) {
|
||||
if (Object.prototype.hasOwnProperty.call(dataSource, key)) {
|
||||
debugWarn(
|
||||
"Local data files have conflicting data. Overwriting '%s' with data from '%s'. Previous data location was from '%s'",
|
||||
key,
|
||||
path,
|
||||
dataSource[key],
|
||||
);
|
||||
}
|
||||
dataSource[key] = path;
|
||||
}
|
||||
TemplateData.mergeDeep(this.config.dataDeepMerge, localData, cleanedDataForPath);
|
||||
}
|
||||
}
|
||||
return localData;
|
||||
}
|
||||
|
||||
async getTemplateDirectoryData(templatePath) {
|
||||
if (!this.templateDirectoryData[templatePath]) {
|
||||
let localDataPaths = await this.getLocalDataPaths(templatePath);
|
||||
let importedData = await this.combineLocalData(localDataPaths);
|
||||
|
||||
this.templateDirectoryData[templatePath] = Object.assign({}, importedData);
|
||||
}
|
||||
return this.templateDirectoryData[templatePath];
|
||||
}
|
||||
|
||||
getUserDataExtensions() {
|
||||
if (!this.config.dataExtensions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// returning extensions in reverse order to create proper extension order
|
||||
// later added formats will override first ones
|
||||
return Array.from(this.config.dataExtensions.keys()).reverse();
|
||||
}
|
||||
|
||||
getUserDataParser(extension) {
|
||||
return this.config.dataExtensions.get(extension);
|
||||
}
|
||||
|
||||
isUserDataExtension(extension) {
|
||||
return this.config.dataExtensions && this.config.dataExtensions.has(extension);
|
||||
}
|
||||
|
||||
hasUserDataExtensions() {
|
||||
return this.config.dataExtensions && this.config.dataExtensions.size > 0;
|
||||
}
|
||||
|
||||
async _parseDataFile(path, parser, options = {}) {
|
||||
let readFile = !("read" in options) || options.read === true;
|
||||
let rawInput;
|
||||
|
||||
if (readFile) {
|
||||
rawInput = EleventyLoadContent(path, options);
|
||||
}
|
||||
|
||||
if (readFile && !rawInput) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
if (readFile) {
|
||||
return parser(rawInput, path);
|
||||
} else {
|
||||
// path as a first argument is when `read: false`
|
||||
// path as a second argument is for consistency with `read: true` API
|
||||
return parser(path, path);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new TemplateDataParseError(`Having trouble parsing data file ${path}`, e);
|
||||
}
|
||||
}
|
||||
|
||||
// ignoreProcessing = false for global data files
|
||||
// ignoreProcessing = true for local data files
|
||||
async getDataValue(path) {
|
||||
let extension = TemplatePath.getExtension(path);
|
||||
|
||||
if (extension === "js" || extension === "cjs" || extension === "mjs") {
|
||||
// JS data file or require’d JSON (no preprocessing needed)
|
||||
let localPath = TemplatePath.absolutePath(path);
|
||||
let exists = this._fsExistsCache.exists(localPath);
|
||||
// Make sure that relative lookups benefit from cache
|
||||
this._fsExistsCache.markExists(path, exists);
|
||||
|
||||
if (!exists) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let aggregateDataBench = this.benchmarks.aggregate.get("Data File");
|
||||
aggregateDataBench.before();
|
||||
let dataBench = this.benchmarks.data.get(`\`${path}\``);
|
||||
dataBench.before();
|
||||
|
||||
let type = "cjs";
|
||||
if (extension === "mjs" || (extension === "js" && this.isEsm)) {
|
||||
type = "esm";
|
||||
}
|
||||
|
||||
// We always need to use `import()`, as `require` isn’t available in ESM.
|
||||
let returnValue = await EleventyImport(localPath, type);
|
||||
|
||||
// TODO special exception for Global data `permalink.js`
|
||||
// module.exports = (data) => `${data.page.filePathStem}/`; // Does not work
|
||||
// module.exports = () => ((data) => `${data.page.filePathStem}/`); // Works
|
||||
if (typeof returnValue === "function") {
|
||||
let configApiGlobalData = await this.getInitialGlobalData();
|
||||
returnValue = await returnValue(configApiGlobalData || {});
|
||||
}
|
||||
|
||||
dataBench.after();
|
||||
aggregateDataBench.after();
|
||||
|
||||
return returnValue;
|
||||
} else if (this.isUserDataExtension(extension)) {
|
||||
// Other extensions
|
||||
let { parser, options } = this.getUserDataParser(extension);
|
||||
|
||||
return this._parseDataFile(path, parser, options);
|
||||
} else if (extension === "json") {
|
||||
// File to string, parse with JSON (preprocess)
|
||||
const parser = (content) => JSON.parse(content);
|
||||
return this._parseDataFile(path, parser);
|
||||
} else {
|
||||
throw new TemplateDataParseError(
|
||||
`Could not find an appropriate data parser for ${path}. Do you need to add a plugin to your config file?`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_pushExtensionsToPaths(paths, curpath, extensions) {
|
||||
for (let extension of extensions) {
|
||||
paths.push(curpath + "." + extension);
|
||||
}
|
||||
}
|
||||
|
||||
_addBaseToPaths(paths, base, extensions, nonEmptySuffixesOnly = false) {
|
||||
let suffixes = this.getDataFileSuffixes();
|
||||
|
||||
for (let suffix of suffixes) {
|
||||
suffix = suffix || "";
|
||||
|
||||
if (nonEmptySuffixesOnly && suffix === "") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// data suffix
|
||||
if (suffix) {
|
||||
paths.push(base + suffix + ".js");
|
||||
paths.push(base + suffix + ".cjs");
|
||||
paths.push(base + suffix + ".mjs");
|
||||
}
|
||||
paths.push(base + suffix + ".json"); // default: .11tydata.json
|
||||
|
||||
// inject user extensions
|
||||
this._pushExtensionsToPaths(paths, base + suffix, extensions);
|
||||
}
|
||||
}
|
||||
|
||||
async getLocalDataPaths(templatePath) {
|
||||
let paths = [];
|
||||
let parsed = path.parse(templatePath);
|
||||
let inputDir = this.inputDir;
|
||||
|
||||
debugDev("getLocalDataPaths(%o)", templatePath);
|
||||
debugDev("parsed.dir: %o", parsed.dir);
|
||||
|
||||
let userExtensions = this.getUserDataExtensions();
|
||||
|
||||
if (parsed.dir) {
|
||||
let fileNameNoExt = this.extensionMap.removeTemplateExtension(parsed.base);
|
||||
|
||||
// default dataSuffix: .11tydata, is appended in _addBaseToPaths
|
||||
debug("Using %o suffixes to find data files.", this.getDataFileSuffixes());
|
||||
|
||||
// Template data file paths
|
||||
let filePathNoExt = parsed.dir + "/" + fileNameNoExt;
|
||||
this._addBaseToPaths(paths, filePathNoExt, userExtensions);
|
||||
|
||||
// Directory data file paths
|
||||
let allDirs = TemplatePath.getAllDirs(parsed.dir);
|
||||
|
||||
debugDev("allDirs: %o", allDirs);
|
||||
for (let dir of allDirs) {
|
||||
let lastDir = TemplatePath.getLastPathSegment(dir);
|
||||
let dirPathNoExt = dir + "/" + lastDir;
|
||||
|
||||
if (inputDir) {
|
||||
debugDev("dirStr: %o; inputDir: %o", dir, inputDir);
|
||||
}
|
||||
if (!inputDir || (dir.startsWith(inputDir) && dir !== inputDir)) {
|
||||
if (this.config.dataFileDirBaseNameOverride) {
|
||||
let indexDataFile = dir + "/" + this.config.dataFileDirBaseNameOverride;
|
||||
this._addBaseToPaths(paths, indexDataFile, userExtensions, true);
|
||||
} else {
|
||||
this._addBaseToPaths(paths, dirPathNoExt, userExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0.11.0+ include root input dir files
|
||||
// if using `docs/` as input dir, looks for docs/docs.json et al
|
||||
if (inputDir) {
|
||||
let lastInputDir = TemplatePath.addLeadingDotSlash(
|
||||
TemplatePath.join(inputDir, TemplatePath.getLastPathSegment(inputDir)),
|
||||
);
|
||||
|
||||
// in root input dir, search for index.11tydata.json et al
|
||||
if (this.config.dataFileDirBaseNameOverride) {
|
||||
let indexDataFile =
|
||||
TemplatePath.getDirFromFilePath(lastInputDir) +
|
||||
"/" +
|
||||
this.config.dataFileDirBaseNameOverride;
|
||||
this._addBaseToPaths(paths, indexDataFile, userExtensions, true);
|
||||
} else if (lastInputDir !== "./") {
|
||||
this._addBaseToPaths(paths, lastInputDir, userExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug("getLocalDataPaths(%o): %o", templatePath, paths);
|
||||
return unique(paths).reverse();
|
||||
}
|
||||
|
||||
static mergeDeep(deepMerge, target, ...source) {
|
||||
if (!deepMerge && deepMerge !== undefined) {
|
||||
return Object.assign(target, ...source);
|
||||
} else {
|
||||
return TemplateData.merge(target, ...source);
|
||||
}
|
||||
}
|
||||
|
||||
static merge(target, ...source) {
|
||||
return Merge(target, ...source);
|
||||
}
|
||||
|
||||
/* Like cleanupData() but does not mutate */
|
||||
static getCleanedTagsImmutable(data) {
|
||||
let tags = [];
|
||||
|
||||
if (isPlainObject(data) && data.tags) {
|
||||
if (typeof data.tags === "string") {
|
||||
tags = (data.tags || "").split(",");
|
||||
} else if (Array.isArray(data.tags)) {
|
||||
tags = data.tags;
|
||||
}
|
||||
|
||||
// Deduplicate tags
|
||||
return [...new Set(tags)];
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
static cleanupData(data) {
|
||||
if (isPlainObject(data) && "tags" in data) {
|
||||
if (typeof data.tags === "string") {
|
||||
data.tags = data.tags ? [data.tags] : [];
|
||||
} else if (data.tags === null) {
|
||||
data.tags = [];
|
||||
}
|
||||
|
||||
// Deduplicate tags
|
||||
data.tags = [...new Set(data.tags)];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static getNormalizedExcludedCollections(data) {
|
||||
let excludes = [];
|
||||
let key = "eleventyExcludeFromCollections";
|
||||
|
||||
if (data?.[key] !== true) {
|
||||
if (Array.isArray(data[key])) {
|
||||
excludes = data[key];
|
||||
} else if (typeof data[key] === "string") {
|
||||
excludes = (data[key] || "").split(",");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
excludes,
|
||||
excludeAll: data?.eleventyExcludeFromCollections === true,
|
||||
};
|
||||
}
|
||||
|
||||
/* Same as getIncludedTagNames() but may also include "all" */
|
||||
static getIncludedCollectionNames(data) {
|
||||
let tags = TemplateData.getCleanedTagsImmutable(data);
|
||||
|
||||
if (tags.length > 0) {
|
||||
let { excludes, excludeAll } = TemplateData.getNormalizedExcludedCollections(data);
|
||||
if (excludeAll) {
|
||||
return [];
|
||||
} else {
|
||||
return ["all", ...tags].filter((tag) => !excludes.includes(tag));
|
||||
}
|
||||
} else {
|
||||
return ["all"];
|
||||
}
|
||||
}
|
||||
|
||||
static getIncludedTagNames(data) {
|
||||
let tags = TemplateData.getCleanedTagsImmutable(data);
|
||||
|
||||
if (tags.length > 0) {
|
||||
let { excludes, excludeAll } = TemplateData.getNormalizedExcludedCollections(data);
|
||||
if (excludeAll) {
|
||||
return [];
|
||||
} else {
|
||||
return tags.filter((tag) => !excludes.includes(tag));
|
||||
}
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateData;
|
||||
40
node_modules/@11ty/eleventy/src/Data/TemplateDataInitialGlobalData.js
generated
vendored
Normal file
40
node_modules/@11ty/eleventy/src/Data/TemplateDataInitialGlobalData.js
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
|
||||
const { set: lodashSet } = lodash;
|
||||
|
||||
class TemplateDataConfigError extends EleventyBaseError {}
|
||||
|
||||
class TemplateDataInitialGlobalData {
|
||||
constructor(templateConfig) {
|
||||
if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new TemplateDataConfigError("Missing or invalid `templateConfig` (via Render plugin).");
|
||||
}
|
||||
this.templateConfig = templateConfig;
|
||||
this.config = this.templateConfig.getConfig();
|
||||
}
|
||||
|
||||
async getData() {
|
||||
let globalData = {};
|
||||
|
||||
// via eleventyConfig.addGlobalData
|
||||
if (this.config.globalData) {
|
||||
let keys = Object.keys(this.config.globalData);
|
||||
for (let key of keys) {
|
||||
let returnValue = this.config.globalData[key];
|
||||
|
||||
// This section is problematic when used with eleventyComputed #3389
|
||||
if (typeof returnValue === "function") {
|
||||
returnValue = await returnValue();
|
||||
}
|
||||
|
||||
lodashSet(globalData, key, returnValue);
|
||||
}
|
||||
}
|
||||
|
||||
return globalData;
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateDataInitialGlobalData;
|
||||
1472
node_modules/@11ty/eleventy/src/Eleventy.js
generated
vendored
Normal file
1472
node_modules/@11ty/eleventy/src/Eleventy.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
43
node_modules/@11ty/eleventy/src/EleventyCommonJs.cjs
generated
vendored
Normal file
43
node_modules/@11ty/eleventy/src/EleventyCommonJs.cjs
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
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) {
|
||||
if(e.code === "ERR_REQUIRE_ESM") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rethrow if not an ESM require error.
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if(!canRequireModules()) {
|
||||
let error = new Error(`\`require("@11ty/eleventy")\` is incompatible with Eleventy v3 and this version of Node. 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 use \`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;
|
||||
}
|
||||
|
||||
// If we made it here require(ESM) works fine (via --experimental-require-module or newer Node.js defaults)
|
||||
let mod = require("./Eleventy.js");
|
||||
|
||||
module.exports = mod;
|
||||
284
node_modules/@11ty/eleventy/src/EleventyExtensionMap.js
generated
vendored
Normal file
284
node_modules/@11ty/eleventy/src/EleventyExtensionMap.js
generated
vendored
Normal file
@@ -0,0 +1,284 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import TemplateEngineManager from "./Engines/TemplateEngineManager.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
|
||||
class EleventyExtensionMapConfigError extends EleventyBaseError {}
|
||||
|
||||
class EleventyExtensionMap {
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
this._spiderJsDepsCache = {};
|
||||
|
||||
/** @type {Array} */
|
||||
this.validTemplateLanguageKeys;
|
||||
}
|
||||
|
||||
setFormats(formatKeys = []) {
|
||||
// raw
|
||||
this.formatKeys = formatKeys;
|
||||
|
||||
this.unfilteredFormatKeys = formatKeys.map(function (key) {
|
||||
return key.trim().toLowerCase();
|
||||
});
|
||||
|
||||
this.validTemplateLanguageKeys = this.unfilteredFormatKeys.filter((key) =>
|
||||
this.hasExtension(key),
|
||||
);
|
||||
|
||||
this.passthroughCopyKeys = this.unfilteredFormatKeys.filter((key) => !this.hasExtension(key));
|
||||
}
|
||||
|
||||
set config(cfg) {
|
||||
if (!cfg || cfg.constructor.name !== "TemplateConfig") {
|
||||
throw new EleventyExtensionMapConfigError(
|
||||
"Missing or invalid `config` argument (via setter).",
|
||||
);
|
||||
}
|
||||
this.eleventyConfig = cfg;
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this.eleventyConfig.getConfig();
|
||||
}
|
||||
|
||||
get engineManager() {
|
||||
if (!this._engineManager) {
|
||||
this._engineManager = new TemplateEngineManager(this.eleventyConfig);
|
||||
}
|
||||
|
||||
return this._engineManager;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.engineManager.reset();
|
||||
}
|
||||
|
||||
/* Used for layout path resolution */
|
||||
getFileList(path, dir) {
|
||||
if (!path) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let files = [];
|
||||
this.validTemplateLanguageKeys.forEach((key) => {
|
||||
this.getExtensionsFromKey(key).forEach(function (extension) {
|
||||
files.push((dir ? dir + "/" : "") + path + "." + extension);
|
||||
});
|
||||
});
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
// Warning: this would false positive on an include, but is only used
|
||||
// on paths found from the file system glob search.
|
||||
// TODO: Method name might just need to be renamed to something more accurate.
|
||||
isFullTemplateFilePath(path) {
|
||||
for (let extension of this.validTemplateLanguageKeys) {
|
||||
if (path.endsWith(`.${extension}`)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getCustomExtensionEntry(extension) {
|
||||
if (!this.config.extensionMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let entry of this.config.extensionMap) {
|
||||
if (entry.extension === extension) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getValidExtensionsForPath(path) {
|
||||
let extensions = new Set();
|
||||
for (let extension in this.extensionToKeyMap) {
|
||||
if (path.endsWith(`.${extension}`)) {
|
||||
extensions.add(extension);
|
||||
}
|
||||
}
|
||||
|
||||
// if multiple extensions are valid, sort from longest to shortest
|
||||
// e.g. .11ty.js and .js
|
||||
let sorted = Array.from(extensions)
|
||||
.filter((extension) => this.validTemplateLanguageKeys.includes(extension))
|
||||
.sort((a, b) => b.length - a.length);
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
async shouldSpiderJavaScriptDependencies(path) {
|
||||
let extensions = this.getValidExtensionsForPath(path);
|
||||
for (let extension of extensions) {
|
||||
if (extension in this._spiderJsDepsCache) {
|
||||
return this._spiderJsDepsCache[extension];
|
||||
}
|
||||
|
||||
let cls = await this.engineManager.getEngineClassByExtension(extension);
|
||||
if (cls) {
|
||||
let entry = this.getCustomExtensionEntry(extension);
|
||||
let shouldSpider = cls.shouldSpiderJavaScriptDependencies(entry);
|
||||
this._spiderJsDepsCache[extension] = shouldSpider;
|
||||
return shouldSpider;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getPassthroughCopyGlobs(inputDir) {
|
||||
return this._getGlobs(this.passthroughCopyKeys, inputDir);
|
||||
}
|
||||
|
||||
getValidGlobs(inputDir) {
|
||||
return this._getGlobs(this.validTemplateLanguageKeys, inputDir);
|
||||
}
|
||||
|
||||
getGlobs(inputDir) {
|
||||
return this._getGlobs(this.unfilteredFormatKeys, inputDir);
|
||||
}
|
||||
|
||||
_getGlobs(formatKeys, inputDir) {
|
||||
let dir = TemplatePath.convertToRecursiveGlobSync(inputDir);
|
||||
let extensions = new Set();
|
||||
|
||||
for (let key of formatKeys) {
|
||||
if (this.hasExtension(key)) {
|
||||
for (let extension of this.getExtensionsFromKey(key)) {
|
||||
extensions.add(extension);
|
||||
}
|
||||
} else {
|
||||
extensions.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (extensions.size === 1) {
|
||||
return [`${dir}/*.${Array.from(extensions)[0]}`];
|
||||
} else if (extensions.size > 1) {
|
||||
return [
|
||||
// extra curly brackets /*.{cjs,txt}
|
||||
`${dir}/*.{${Array.from(extensions).join(",")}}`,
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
hasExtension(key) {
|
||||
for (let extension in this.extensionToKeyMap) {
|
||||
if (
|
||||
this.extensionToKeyMap[extension].key === key ||
|
||||
this.extensionToKeyMap[extension].aliasKey === key
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getExtensionsFromKey(key) {
|
||||
let extensions = new Set();
|
||||
for (let extension in this.extensionToKeyMap) {
|
||||
if (this.extensionToKeyMap[extension].aliasKey) {
|
||||
// only add aliased extension if explicitly referenced in formats
|
||||
// overrides will not have an aliasKey (md => md)
|
||||
if (this.extensionToKeyMap[extension].aliasKey === key) {
|
||||
extensions.add(extension);
|
||||
}
|
||||
} else if (this.extensionToKeyMap[extension].key === key) {
|
||||
extensions.add(extension);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(extensions);
|
||||
}
|
||||
|
||||
// Only `addExtension` configuration API extensions
|
||||
getExtensionEntriesFromKey(key) {
|
||||
let entries = new Set();
|
||||
if ("extensionMap" in this.config) {
|
||||
for (let entry of this.config.extensionMap) {
|
||||
if (entry.key === key) {
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(entries);
|
||||
}
|
||||
|
||||
// Determines whether a path is a passthrough copy file or a template (via TemplateWriter)
|
||||
hasEngine(pathOrKey) {
|
||||
return !!this.getKey(pathOrKey);
|
||||
}
|
||||
|
||||
getKey(pathOrKey) {
|
||||
pathOrKey = (pathOrKey || "").toLowerCase();
|
||||
for (let extension in this.extensionToKeyMap) {
|
||||
if (pathOrKey === extension || pathOrKey.endsWith("." + extension)) {
|
||||
let key =
|
||||
this.extensionToKeyMap[extension].aliasKey || this.extensionToKeyMap[extension].key;
|
||||
// must be a valid format key passed (e.g. via --formats)
|
||||
if (this.validTemplateLanguageKeys.includes(key)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getExtensionEntry(pathOrKey) {
|
||||
pathOrKey = (pathOrKey || "").toLowerCase();
|
||||
for (let extension in this.extensionToKeyMap) {
|
||||
if (pathOrKey === extension || pathOrKey.endsWith("." + extension)) {
|
||||
return this.extensionToKeyMap[extension];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeTemplateExtension(path) {
|
||||
for (let extension in this.extensionToKeyMap) {
|
||||
if (path === extension || path.endsWith("." + extension)) {
|
||||
return path.slice(
|
||||
0,
|
||||
path.length - 1 - extension.length < 0 ? 0 : path.length - 1 - extension.length,
|
||||
);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// keys are file extensions
|
||||
// values are template language keys
|
||||
get extensionToKeyMap() {
|
||||
if (!this._extensionToKeyMap) {
|
||||
this._extensionToKeyMap = {
|
||||
md: { key: "md", extension: "md" },
|
||||
html: { key: "html", extension: "html" },
|
||||
njk: { key: "njk", extension: "njk" },
|
||||
liquid: { key: "liquid", extension: "liquid" },
|
||||
"11ty.js": { key: "11ty.js", extension: "11ty.js" },
|
||||
"11ty.cjs": { key: "11ty.js", extension: "11ty.cjs" },
|
||||
"11ty.mjs": { key: "11ty.js", extension: "11ty.mjs" },
|
||||
};
|
||||
|
||||
if ("extensionMap" in this.config) {
|
||||
for (let entry of this.config.extensionMap) {
|
||||
// extension and key are only different when aliasing.
|
||||
this._extensionToKeyMap[entry.extension] = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._extensionToKeyMap;
|
||||
}
|
||||
|
||||
getReadableFileExtensions() {
|
||||
return Object.keys(this.extensionToKeyMap).join(" ");
|
||||
}
|
||||
}
|
||||
|
||||
export default EleventyExtensionMap;
|
||||
517
node_modules/@11ty/eleventy/src/EleventyFiles.js
generated
vendored
Normal file
517
node_modules/@11ty/eleventy/src/EleventyFiles.js
generated
vendored
Normal file
@@ -0,0 +1,517 @@
|
||||
import fs from "node:fs";
|
||||
|
||||
import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import EleventyExtensionMap from "./EleventyExtensionMap.js";
|
||||
import TemplateData from "./Data/TemplateData.js";
|
||||
import TemplateGlob from "./TemplateGlob.js";
|
||||
import TemplatePassthroughManager from "./TemplatePassthroughManager.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:EleventyFiles");
|
||||
|
||||
class EleventyFilesError extends EleventyBaseError {}
|
||||
|
||||
class EleventyFiles {
|
||||
constructor(formats, eleventyConfig) {
|
||||
if (!eleventyConfig) {
|
||||
throw new EleventyFilesError("Missing `eleventyConfig`` argument.");
|
||||
}
|
||||
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
this.config = eleventyConfig.getConfig();
|
||||
this.aggregateBench = this.config.benchmarkManager.get("Aggregate");
|
||||
|
||||
this.formats = formats;
|
||||
this.eleventyIgnoreContent = false;
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get outputDir() {
|
||||
return this.dirs.output;
|
||||
}
|
||||
|
||||
get includesDir() {
|
||||
return this.dirs.includes;
|
||||
}
|
||||
|
||||
get layoutsDir() {
|
||||
return this.dirs.layouts;
|
||||
}
|
||||
|
||||
get dataDir() {
|
||||
return this.dirs.data;
|
||||
}
|
||||
|
||||
// Backwards compat
|
||||
getDataDir() {
|
||||
return this.dataDir;
|
||||
}
|
||||
|
||||
setFileSystemSearch(fileSystemSearch) {
|
||||
this.fileSystemSearch = fileSystemSearch;
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.dirs.inputFile || this.dirs.inputGlob) {
|
||||
this.templateGlobs = TemplateGlob.map([this.dirs.inputFile || this.dirs.inputGlob]);
|
||||
} else {
|
||||
// Input is a directory
|
||||
this.templateGlobs = this.extensionMap.getGlobs(this.inputDir);
|
||||
}
|
||||
|
||||
this.initPassthroughManager();
|
||||
this.setupGlobs();
|
||||
}
|
||||
|
||||
get validTemplateGlobs() {
|
||||
if (!this._validTemplateGlobs) {
|
||||
let globs;
|
||||
// Input is a file
|
||||
if (this.inputFile) {
|
||||
globs = this.templateGlobs;
|
||||
} else {
|
||||
// input is a directory
|
||||
globs = this.extensionMap.getValidGlobs(this.inputDir);
|
||||
}
|
||||
this._validTemplateGlobs = globs;
|
||||
}
|
||||
return this._validTemplateGlobs;
|
||||
}
|
||||
|
||||
get passthroughGlobs() {
|
||||
let paths = new Set();
|
||||
// stuff added in addPassthroughCopy()
|
||||
for (let path of this.passthroughManager.getConfigPathGlobs()) {
|
||||
paths.add(path);
|
||||
}
|
||||
// non-template language extensions
|
||||
for (let path of this.extensionMap.getPassthroughCopyGlobs(this.inputDir)) {
|
||||
paths.add(path);
|
||||
}
|
||||
return Array.from(paths);
|
||||
}
|
||||
|
||||
restart() {
|
||||
this.passthroughManager.reset();
|
||||
this.setupGlobs();
|
||||
this._glob = null;
|
||||
}
|
||||
|
||||
/* For testing */
|
||||
_setConfig(config) {
|
||||
if (!config.ignores) {
|
||||
config.ignores = new Set();
|
||||
config.ignores.add("**/node_modules/**");
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/* Set command root for local project paths */
|
||||
// This is only used by tests
|
||||
_setLocalPathRoot(dir) {
|
||||
this.localPathRoot = dir;
|
||||
}
|
||||
|
||||
set extensionMap(extensionMap) {
|
||||
this._extensionMap = extensionMap;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
// for tests
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats(this.formats);
|
||||
this._extensionMap.config = this.eleventyConfig;
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
setRunMode(runMode) {
|
||||
this.runMode = runMode;
|
||||
}
|
||||
|
||||
initPassthroughManager() {
|
||||
let mgr = new TemplatePassthroughManager(this.eleventyConfig);
|
||||
mgr.setRunMode(this.runMode);
|
||||
mgr.extensionMap = this.extensionMap;
|
||||
mgr.setFileSystemSearch(this.fileSystemSearch);
|
||||
this.passthroughManager = mgr;
|
||||
}
|
||||
|
||||
getPassthroughManager() {
|
||||
return this.passthroughManager;
|
||||
}
|
||||
|
||||
setPassthroughManager(mgr) {
|
||||
mgr.extensionMap = this.extensionMap;
|
||||
this.passthroughManager = mgr;
|
||||
}
|
||||
|
||||
set templateData(templateData) {
|
||||
this._templateData = templateData;
|
||||
}
|
||||
|
||||
get templateData() {
|
||||
if (!this._templateData) {
|
||||
this._templateData = new TemplateData(this.eleventyConfig);
|
||||
}
|
||||
|
||||
return this._templateData;
|
||||
}
|
||||
|
||||
setupGlobs() {
|
||||
this.fileIgnores = this.getIgnores();
|
||||
this.extraIgnores = this._getIncludesAndDataDirs();
|
||||
this.uniqueIgnores = this.getIgnoreGlobs();
|
||||
|
||||
// Conditional added for tests that don’t have a config
|
||||
if (this.config?.events) {
|
||||
this.config.events.emit("eleventy.ignores", this.uniqueIgnores);
|
||||
}
|
||||
|
||||
this.normalizedTemplateGlobs = this.templateGlobs;
|
||||
}
|
||||
|
||||
getIgnoreGlobs() {
|
||||
let uniqueIgnores = new Set();
|
||||
for (let ignore of this.fileIgnores) {
|
||||
uniqueIgnores.add(ignore);
|
||||
}
|
||||
for (let ignore of this.extraIgnores) {
|
||||
uniqueIgnores.add(ignore);
|
||||
}
|
||||
// Placing the config ignores last here is important to the tests
|
||||
for (let ignore of this.config.ignores) {
|
||||
uniqueIgnores.add(TemplateGlob.normalizePath(this.localPathRoot || ".", ignore));
|
||||
}
|
||||
return Array.from(uniqueIgnores);
|
||||
}
|
||||
|
||||
static getFileIgnores(ignoreFiles) {
|
||||
if (!Array.isArray(ignoreFiles)) {
|
||||
ignoreFiles = [ignoreFiles];
|
||||
}
|
||||
|
||||
let ignores = [];
|
||||
for (let ignorePath of ignoreFiles) {
|
||||
ignorePath = TemplatePath.normalize(ignorePath);
|
||||
|
||||
let dir = TemplatePath.getDirFromFilePath(ignorePath);
|
||||
|
||||
if (fs.existsSync(ignorePath) && fs.statSync(ignorePath).size > 0) {
|
||||
let ignoreContent = fs.readFileSync(ignorePath, "utf8");
|
||||
|
||||
ignores = ignores.concat(EleventyFiles.normalizeIgnoreContent(dir, ignoreContent));
|
||||
}
|
||||
}
|
||||
|
||||
ignores.forEach((path) => debug(`${ignoreFiles} ignoring: ${path}`));
|
||||
|
||||
return ignores;
|
||||
}
|
||||
|
||||
static normalizeIgnoreContent(dir, ignoreContent) {
|
||||
let ignores = [];
|
||||
|
||||
if (ignoreContent) {
|
||||
ignores = ignoreContent
|
||||
.split("\n")
|
||||
.map((line) => {
|
||||
return line.trim();
|
||||
})
|
||||
.filter((line) => {
|
||||
if (line.charAt(0) === "!") {
|
||||
debug(
|
||||
">>> When processing .gitignore/.eleventyignore, Eleventy does not currently support negative patterns but encountered one:",
|
||||
);
|
||||
debug(">>>", line);
|
||||
debug("Follow along at https://github.com/11ty/eleventy/issues/693 to track support.");
|
||||
}
|
||||
|
||||
// empty lines or comments get filtered out
|
||||
return line.length > 0 && line.charAt(0) !== "#" && line.charAt(0) !== "!";
|
||||
})
|
||||
.map((line) => {
|
||||
let path = TemplateGlob.normalizePath(dir, "/", line);
|
||||
path = TemplatePath.addLeadingDotSlash(TemplatePath.relativePath(path));
|
||||
|
||||
try {
|
||||
// Note these folders must exist to get /** suffix
|
||||
let stat = fs.statSync(path);
|
||||
if (stat.isDirectory()) {
|
||||
return path + "/**";
|
||||
}
|
||||
return path;
|
||||
} catch (e) {
|
||||
return path;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return ignores;
|
||||
}
|
||||
|
||||
setEleventyIgnoreContent(content) {
|
||||
this.eleventyIgnoreContent = content;
|
||||
}
|
||||
|
||||
getIgnores() {
|
||||
let files = new Set();
|
||||
|
||||
for (let ignore of EleventyFiles.getFileIgnores(this.getIgnoreFiles())) {
|
||||
files.add(ignore);
|
||||
}
|
||||
|
||||
// testing API
|
||||
if (this.eleventyIgnoreContent !== false) {
|
||||
files.add(this.eleventyIgnoreContent);
|
||||
}
|
||||
|
||||
// ignore output dir (unless this excludes all input)
|
||||
// input: . and output: . (skip)
|
||||
// input: ./content and output . (skip)
|
||||
// input: . and output: ./_site (add)
|
||||
if (!this.inputDir.startsWith(this.outputDir)) {
|
||||
// both are already normalized in 3.0
|
||||
files.add(TemplateGlob.map(this.outputDir + "/**"));
|
||||
}
|
||||
|
||||
return Array.from(files);
|
||||
}
|
||||
|
||||
getIgnoreFiles() {
|
||||
let ignoreFiles = new Set();
|
||||
let rootDirectory = this.localPathRoot || ".";
|
||||
|
||||
if (this.config.useGitIgnore) {
|
||||
ignoreFiles.add(TemplatePath.join(rootDirectory, ".gitignore"));
|
||||
}
|
||||
|
||||
if (this.eleventyIgnoreContent === false) {
|
||||
let absoluteInputDir = TemplatePath.absolutePath(this.inputDir);
|
||||
ignoreFiles.add(TemplatePath.join(rootDirectory, ".eleventyignore"));
|
||||
if (rootDirectory !== absoluteInputDir) {
|
||||
ignoreFiles.add(TemplatePath.join(this.inputDir, ".eleventyignore"));
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(ignoreFiles);
|
||||
}
|
||||
|
||||
/* Backwards compat */
|
||||
getIncludesDir() {
|
||||
return this.includesDir;
|
||||
}
|
||||
|
||||
/* Backwards compat */
|
||||
getLayoutsDir() {
|
||||
return this.layoutsDir;
|
||||
}
|
||||
|
||||
getFileGlobs() {
|
||||
return this.normalizedTemplateGlobs;
|
||||
}
|
||||
|
||||
getRawFiles() {
|
||||
return this.templateGlobs;
|
||||
}
|
||||
|
||||
async getWatchPathCache() {
|
||||
// Issue #1325: make sure passthrough copy files are not included here
|
||||
if (!this.pathCache) {
|
||||
throw new Error("Watching requires `.getFiles()` to be called first in EleventyFiles");
|
||||
}
|
||||
|
||||
let ret = [];
|
||||
// Filter out the passthrough copy paths.
|
||||
for (let path of this.pathCache) {
|
||||
if (
|
||||
this.extensionMap.isFullTemplateFilePath(path) &&
|
||||
(await this.extensionMap.shouldSpiderJavaScriptDependencies(path))
|
||||
) {
|
||||
ret.push(path);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
_globSearch() {
|
||||
let globs = this.getFileGlobs();
|
||||
|
||||
// returns a promise
|
||||
debug("Searching for: %o", globs);
|
||||
return this.fileSystemSearch.search("templates", globs, {
|
||||
ignore: this.uniqueIgnores,
|
||||
});
|
||||
}
|
||||
|
||||
getPathsWithVirtualTemplates(paths) {
|
||||
// Support for virtual templates added in 3.0
|
||||
if (this.config.virtualTemplates && isPlainObject(this.config.virtualTemplates)) {
|
||||
let virtualTemplates = Object.keys(this.config.virtualTemplates)
|
||||
.filter((path) => {
|
||||
// Filter out includes/layouts
|
||||
return this.dirs.isTemplateFile(path);
|
||||
})
|
||||
.map((path) => {
|
||||
let fullVirtualPath = this.dirs.getInputPath(path);
|
||||
if (!this.extensionMap.getKey(fullVirtualPath)) {
|
||||
this.eleventyConfig.logger.warn(
|
||||
`The virtual template at ${fullVirtualPath} is using a template format that’s not valid for your project. Your project is using: "${this.formats}". Read more about formats: https://v3.11ty.dev/docs/config/#template-formats`,
|
||||
);
|
||||
}
|
||||
return fullVirtualPath;
|
||||
});
|
||||
|
||||
paths = paths.concat(virtualTemplates);
|
||||
|
||||
// Virtual templates can not live at the same place as files on the file system!
|
||||
if (paths.length !== new Set(paths).size) {
|
||||
let conflicts = {};
|
||||
for (let path of paths) {
|
||||
if (conflicts[path]) {
|
||||
throw new Error(
|
||||
`A virtual template had the same path as a file on the file system: "${path}"`,
|
||||
);
|
||||
}
|
||||
|
||||
conflicts[path] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
async getFiles() {
|
||||
let bench = this.aggregateBench.get("Searching the file system (templates)");
|
||||
bench.before();
|
||||
let globResults = await this._globSearch();
|
||||
let paths = TemplatePath.addLeadingDotSlashArray(globResults);
|
||||
bench.after();
|
||||
|
||||
// Note 2.0.0-canary.19 removed a `filter` option for custom template syntax here that was unpublished and unused.
|
||||
|
||||
paths = this.getPathsWithVirtualTemplates(paths);
|
||||
|
||||
this.pathCache = paths;
|
||||
return paths;
|
||||
}
|
||||
|
||||
getFileShape(paths, filePath) {
|
||||
if (!filePath) {
|
||||
return;
|
||||
}
|
||||
if (this.isPassthroughCopyFile(paths, filePath)) {
|
||||
return "copy";
|
||||
}
|
||||
if (this.isFullTemplateFile(paths, filePath)) {
|
||||
return "template";
|
||||
}
|
||||
// include/layout/unknown
|
||||
}
|
||||
|
||||
isPassthroughCopyFile(paths, filePath) {
|
||||
return this.passthroughManager.isPassthroughCopyFile(paths, filePath);
|
||||
}
|
||||
|
||||
// Assumption here that filePath is not a passthrough copy file
|
||||
isFullTemplateFile(paths, filePath) {
|
||||
if (!filePath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let path of paths) {
|
||||
if (path === filePath) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* For `eleventy --watch` */
|
||||
getGlobWatcherFiles() {
|
||||
// TODO improvement: tie the includes and data to specific file extensions (currently using `**`)
|
||||
let directoryGlobs = this._getIncludesAndDataDirs();
|
||||
|
||||
if (checkPassthroughCopyBehavior(this.config, this.runMode)) {
|
||||
return this.validTemplateGlobs.concat(directoryGlobs);
|
||||
}
|
||||
|
||||
// Revert to old passthroughcopy copy files behavior
|
||||
return this.validTemplateGlobs.concat(this.passthroughGlobs).concat(directoryGlobs);
|
||||
}
|
||||
|
||||
/* For `eleventy --watch` */
|
||||
getGlobWatcherFilesForPassthroughCopy() {
|
||||
return this.passthroughGlobs;
|
||||
}
|
||||
|
||||
/* For `eleventy --watch` */
|
||||
async getGlobWatcherTemplateDataFiles() {
|
||||
let templateData = this.templateData;
|
||||
return await templateData.getTemplateDataFileGlob();
|
||||
}
|
||||
|
||||
/* For `eleventy --watch` */
|
||||
// TODO this isn’t great but reduces complexity avoiding using TemplateData:getLocalDataPaths for each template in the cache
|
||||
async getWatcherTemplateJavaScriptDataFiles() {
|
||||
let globs = this.templateData.getTemplateJavaScriptDataFileGlob();
|
||||
let bench = this.aggregateBench.get("Searching the file system (watching)");
|
||||
bench.before();
|
||||
let results = TemplatePath.addLeadingDotSlashArray(
|
||||
await this.fileSystemSearch.search("js-dependencies", globs, {
|
||||
ignore: ["**/node_modules/**"],
|
||||
}),
|
||||
);
|
||||
bench.after();
|
||||
return results;
|
||||
}
|
||||
|
||||
/* Ignored by `eleventy --watch` */
|
||||
getGlobWatcherIgnores() {
|
||||
// convert to format without ! since they are passed in as a separate argument to glob watcher
|
||||
let entries = new Set(
|
||||
this.fileIgnores.map((ignore) => TemplatePath.stripLeadingDotSlash(ignore)),
|
||||
);
|
||||
|
||||
for (let ignore of this.config.watchIgnores) {
|
||||
entries.add(TemplateGlob.normalizePath(this.localPathRoot || ".", ignore));
|
||||
}
|
||||
|
||||
// de-duplicated
|
||||
return Array.from(entries);
|
||||
}
|
||||
|
||||
_getIncludesAndDataDirs() {
|
||||
let rawPaths = new Set();
|
||||
rawPaths.add(this.includesDir);
|
||||
if (this.layoutsDir) {
|
||||
rawPaths.add(this.layoutsDir);
|
||||
}
|
||||
rawPaths.add(this.dataDir);
|
||||
|
||||
return Array.from(rawPaths)
|
||||
.filter((entry) => {
|
||||
// never ignore the input directory (even if config file returns "" for these)
|
||||
return entry && entry !== this.inputDir;
|
||||
})
|
||||
.map((entry) => {
|
||||
return TemplateGlob.map(entry + "**");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default EleventyFiles;
|
||||
305
node_modules/@11ty/eleventy/src/EleventyServe.js
generated
vendored
Normal file
305
node_modules/@11ty/eleventy/src/EleventyServe.js
generated
vendored
Normal file
@@ -0,0 +1,305 @@
|
||||
import assert from "node:assert";
|
||||
|
||||
import debugUtil from "debug";
|
||||
import { Merge, DeepCopy, TemplatePath } from "@11ty/eleventy-utils";
|
||||
import EleventyDevServer from "@11ty/eleventy-dev-server";
|
||||
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import ConsoleLogger from "./Util/ConsoleLogger.js";
|
||||
import PathPrefixer from "./Util/PathPrefixer.js";
|
||||
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
|
||||
import { getModulePackageJson } from "./Util/ImportJsonSync.js";
|
||||
import { EleventyImport } from "./Util/Require.js";
|
||||
import { isGlobMatch } from "./Util/GlobMatcher.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:EleventyServe");
|
||||
|
||||
class EleventyServeConfigError extends EleventyBaseError {}
|
||||
|
||||
const DEFAULT_SERVER_OPTIONS = {
|
||||
module: "@11ty/eleventy-dev-server",
|
||||
port: 8080,
|
||||
// pathPrefix: "/",
|
||||
// setup: function() {},
|
||||
// logger: { info: function() {}, error: function() {} }
|
||||
};
|
||||
|
||||
class EleventyServe {
|
||||
constructor() {
|
||||
this.logger = new ConsoleLogger();
|
||||
this._initOptionsFetched = false;
|
||||
this._aliases = undefined;
|
||||
this._watchedFiles = new Set();
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (!this.eleventyConfig) {
|
||||
throw new EleventyServeConfigError(
|
||||
"You need to set the eleventyConfig property on EleventyServe.",
|
||||
);
|
||||
}
|
||||
|
||||
return this.eleventyConfig.getConfig();
|
||||
}
|
||||
|
||||
set config(config) {
|
||||
throw new Error("It’s not allowed to set config on EleventyServe. Set eleventyConfig instead.");
|
||||
}
|
||||
|
||||
setAliases(aliases) {
|
||||
this._aliases = aliases;
|
||||
|
||||
if (this._server && "setAliases" in this._server) {
|
||||
this._server.setAliases(aliases);
|
||||
}
|
||||
}
|
||||
|
||||
get eleventyConfig() {
|
||||
if (!this._eleventyConfig) {
|
||||
throw new EleventyServeConfigError(
|
||||
"You need to set the eleventyConfig property on EleventyServe.",
|
||||
);
|
||||
}
|
||||
|
||||
return this._eleventyConfig;
|
||||
}
|
||||
|
||||
set eleventyConfig(config) {
|
||||
this._eleventyConfig = config;
|
||||
if (checkPassthroughCopyBehavior(this._eleventyConfig.userConfig, "serve")) {
|
||||
this._eleventyConfig.userConfig.events.on("eleventy.passthrough", ({ map }) => {
|
||||
// for-free passthrough copy
|
||||
this.setAliases(map);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO directorynorm
|
||||
setOutputDir(outputDir) {
|
||||
// TODO check if this is different and if so, restart server (if already running)
|
||||
// This applies if you change the output directory in your config file during watch/serve
|
||||
this.outputDir = outputDir;
|
||||
}
|
||||
|
||||
async getServerModule(name) {
|
||||
try {
|
||||
if (!name || name === DEFAULT_SERVER_OPTIONS.module) {
|
||||
return EleventyDevServer;
|
||||
}
|
||||
|
||||
// Look for peer dep in local project
|
||||
let projectNodeModulesPath = TemplatePath.absolutePath("./node_modules/");
|
||||
let serverPath = TemplatePath.absolutePath(projectNodeModulesPath, name);
|
||||
// No references outside of the project node_modules are allowed
|
||||
if (!serverPath.startsWith(projectNodeModulesPath)) {
|
||||
throw new Error("Invalid node_modules name for Eleventy server instance, received:" + name);
|
||||
}
|
||||
|
||||
let serverPackageJson = getModulePackageJson(serverPath);
|
||||
// Normalize with `main` entry from
|
||||
if (TemplatePath.isDirectorySync(serverPath)) {
|
||||
if (serverPackageJson.main) {
|
||||
serverPath = TemplatePath.absolutePath(
|
||||
projectNodeModulesPath,
|
||||
name,
|
||||
serverPackageJson.main,
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Eleventy server ${name} is missing a \`main\` entry in its package.json file. Traversed up from ${serverPath}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let module = await EleventyImport(serverPath);
|
||||
|
||||
if (!("getServer" in module)) {
|
||||
throw new Error(
|
||||
`Eleventy server module requires a \`getServer\` static method. Could not find one on module: \`${name}\``,
|
||||
);
|
||||
}
|
||||
|
||||
if (serverPackageJson["11ty"]?.compatibility) {
|
||||
try {
|
||||
this.eleventyConfig.userConfig.versionCheck(serverPackageJson["11ty"].compatibility);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Warning: \`${name}\` Plugin Compatibility: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return module;
|
||||
} catch (e) {
|
||||
this.logger.error(
|
||||
"There was an error with your custom Eleventy server. We’re using the default server instead.\n" +
|
||||
e.message,
|
||||
);
|
||||
debug("Eleventy server error %o", e);
|
||||
return EleventyDevServer;
|
||||
}
|
||||
}
|
||||
|
||||
get options() {
|
||||
if (this._options) {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
this._options = Object.assign(
|
||||
{
|
||||
pathPrefix: PathPrefixer.normalizePathPrefix(this.config.pathPrefix),
|
||||
logger: this.logger,
|
||||
},
|
||||
DEFAULT_SERVER_OPTIONS,
|
||||
this.config.serverOptions,
|
||||
);
|
||||
|
||||
this._savedConfigOptions = DeepCopy({}, this.config.serverOptions);
|
||||
|
||||
if (!this._initOptionsFetched && this.getSetupCallback()) {
|
||||
throw new Error(
|
||||
"Init options have not yet been fetched in the setup callback. This probably means that `init()` has not yet been called.",
|
||||
);
|
||||
}
|
||||
|
||||
return this._options;
|
||||
}
|
||||
|
||||
get server() {
|
||||
if (!this._server) {
|
||||
throw new Error("Missing server instance. Did you call .initServerInstance?");
|
||||
}
|
||||
|
||||
return this._server;
|
||||
}
|
||||
|
||||
async initServerInstance() {
|
||||
if (this._server) {
|
||||
return;
|
||||
}
|
||||
|
||||
let serverModule = await this.getServerModule(this.options.module);
|
||||
|
||||
// Static method `getServer` was already checked in `getServerModule`
|
||||
this._server = serverModule.getServer("eleventy-server", this.outputDir, this.options);
|
||||
|
||||
this.setAliases(this._aliases);
|
||||
|
||||
if (this._globsNeedWatching) {
|
||||
this._server.watchFiles(this._watchedFiles);
|
||||
this._globsNeedWatching = false;
|
||||
}
|
||||
}
|
||||
|
||||
getSetupCallback() {
|
||||
let setupCallback = this.config.serverOptions.setup;
|
||||
if (setupCallback && typeof setupCallback === "function") {
|
||||
return setupCallback;
|
||||
}
|
||||
}
|
||||
|
||||
async #init() {
|
||||
let setupCallback = this.getSetupCallback();
|
||||
if (setupCallback) {
|
||||
let opts = await setupCallback();
|
||||
this._initOptionsFetched = true;
|
||||
|
||||
if (opts) {
|
||||
Merge(this.options, opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this._initPromise) {
|
||||
this._initPromise = this.#init();
|
||||
}
|
||||
|
||||
return this._initPromise;
|
||||
}
|
||||
|
||||
// Port comes in here from --port on the command line
|
||||
async serve(port) {
|
||||
this._commandLinePort = port;
|
||||
|
||||
await this.init();
|
||||
await this.initServerInstance();
|
||||
|
||||
this.server.serve(port || this.options.port);
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this._server) {
|
||||
await this._server.close();
|
||||
|
||||
this._server = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async sendError({ error }) {
|
||||
if (this._server) {
|
||||
await this.server.sendError({
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Restart the server entirely
|
||||
// We don’t want to use a native `restart` method (e.g. restart() in Vite) so that
|
||||
// we can correctly handle a `module` property change (changing the server type)
|
||||
async restart() {
|
||||
// Blow away cached options
|
||||
delete this._options;
|
||||
|
||||
await this.close();
|
||||
|
||||
// saved --port in `serve()`
|
||||
await this.serve(this._commandLinePort);
|
||||
|
||||
// rewatch the saved watched files (passthrough copy)
|
||||
if ("watchFiles" in this.server) {
|
||||
this.server.watchFiles(this._watchedFiles);
|
||||
}
|
||||
}
|
||||
|
||||
// checkPassthroughCopyBehavior check is called upstream in Eleventy.js
|
||||
// TODO globs are not removed from watcher
|
||||
watchPassthroughCopy(globs) {
|
||||
this._watchedFiles = globs;
|
||||
|
||||
if (this._server && "watchFiles" in this.server) {
|
||||
this.server.watchFiles(globs);
|
||||
this._globsNeedWatching = false;
|
||||
} else {
|
||||
this._globsNeedWatching = true;
|
||||
}
|
||||
}
|
||||
|
||||
isEmulatedPassthroughCopyMatch(filepath) {
|
||||
return isGlobMatch(filepath, this._watchedFiles);
|
||||
}
|
||||
|
||||
hasOptionsChanged() {
|
||||
try {
|
||||
assert.deepStrictEqual(this.config.serverOptions, this._savedConfigOptions);
|
||||
return false;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Live reload the server
|
||||
async reload(reloadEvent = {}) {
|
||||
if (!this._server) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restart the server if the options have changed
|
||||
if (this.hasOptionsChanged()) {
|
||||
debug("Server options changed, we’re restarting the server");
|
||||
await this.restart();
|
||||
} else {
|
||||
await this.server.reload(reloadEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EleventyServe;
|
||||
131
node_modules/@11ty/eleventy/src/EleventyWatch.js
generated
vendored
Executable file
131
node_modules/@11ty/eleventy/src/EleventyWatch.js
generated
vendored
Executable file
@@ -0,0 +1,131 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import PathNormalizer from "./Util/PathNormalizer.js";
|
||||
|
||||
/* Decides when to watch and in what mode to watch
|
||||
* Incremental builds don’t batch changes, they queue.
|
||||
* Nonincremental builds batch.
|
||||
*/
|
||||
|
||||
class EleventyWatch {
|
||||
constructor() {
|
||||
this.incremental = false;
|
||||
this.isActive = false;
|
||||
this.activeQueue = [];
|
||||
}
|
||||
|
||||
isBuildRunning() {
|
||||
return this.isActive;
|
||||
}
|
||||
|
||||
setBuildRunning() {
|
||||
this.isActive = true;
|
||||
|
||||
// pop waiting queue into the active queue
|
||||
this.activeQueue = this.popNextActiveQueue();
|
||||
}
|
||||
|
||||
setBuildFinished() {
|
||||
this.isActive = false;
|
||||
this.activeQueue = [];
|
||||
}
|
||||
|
||||
getIncrementalFile() {
|
||||
if (this.incremental) {
|
||||
return this.activeQueue.length ? this.activeQueue[0] : false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Returns the changed files currently being operated on in the current `watch` build
|
||||
* Works with or without incremental (though in incremental only one file per time will be processed)
|
||||
*/
|
||||
getActiveQueue() {
|
||||
if (!this.isActive) {
|
||||
return [];
|
||||
} else if (this.incremental && this.activeQueue.length === 0) {
|
||||
return [];
|
||||
} else if (this.incremental) {
|
||||
return [this.activeQueue[0]];
|
||||
}
|
||||
|
||||
return this.activeQueue;
|
||||
}
|
||||
|
||||
_queueMatches(file) {
|
||||
let filterCallback;
|
||||
if (typeof file === "function") {
|
||||
filterCallback = file;
|
||||
} else {
|
||||
filterCallback = (path) => path === file;
|
||||
}
|
||||
|
||||
return this.activeQueue.filter(filterCallback);
|
||||
}
|
||||
|
||||
hasAllQueueFiles(file) {
|
||||
return (
|
||||
this.activeQueue.length > 0 && this.activeQueue.length === this._queueMatches(file).length
|
||||
);
|
||||
}
|
||||
|
||||
hasQueuedFile(file) {
|
||||
if (file) {
|
||||
return this._queueMatches(file).length > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
hasQueuedFiles(files) {
|
||||
for (const file of files) {
|
||||
if (this.hasQueuedFile(file)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get pendingQueue() {
|
||||
if (!this._queue) {
|
||||
this._queue = [];
|
||||
}
|
||||
return this._queue;
|
||||
}
|
||||
|
||||
set pendingQueue(value) {
|
||||
this._queue = value;
|
||||
}
|
||||
|
||||
addToPendingQueue(path) {
|
||||
if (path) {
|
||||
path = PathNormalizer.normalizeSeperator(TemplatePath.addLeadingDotSlash(path));
|
||||
this.pendingQueue.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
getPendingQueueSize() {
|
||||
return this.pendingQueue.length;
|
||||
}
|
||||
|
||||
getPendingQueue() {
|
||||
return this.pendingQueue;
|
||||
}
|
||||
|
||||
getActiveQueueSize() {
|
||||
return this.activeQueue.length;
|
||||
}
|
||||
|
||||
// returns array
|
||||
popNextActiveQueue() {
|
||||
if (this.incremental) {
|
||||
return this.pendingQueue.length ? [this.pendingQueue.shift()] : [];
|
||||
}
|
||||
|
||||
let ret = this.pendingQueue.slice();
|
||||
this.pendingQueue = [];
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
export default EleventyWatch;
|
||||
164
node_modules/@11ty/eleventy/src/EleventyWatchTargets.js
generated
vendored
Normal file
164
node_modules/@11ty/eleventy/src/EleventyWatchTargets.js
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import { DepGraph } from "dependency-graph";
|
||||
|
||||
import JavaScriptDependencies from "./Util/JavaScriptDependencies.js";
|
||||
import eventBus from "./EventBus.js";
|
||||
|
||||
class EleventyWatchTargets {
|
||||
#templateConfig;
|
||||
|
||||
constructor(templateConfig) {
|
||||
this.targets = new Set();
|
||||
this.dependencies = new Set();
|
||||
this.newTargets = new Set();
|
||||
this.isEsm = false;
|
||||
|
||||
this.graph = new DepGraph();
|
||||
this.#templateConfig = templateConfig;
|
||||
}
|
||||
|
||||
setProjectUsingEsm(isEsmProject) {
|
||||
this.isEsm = !!isEsmProject;
|
||||
}
|
||||
|
||||
isJavaScriptDependency(path) {
|
||||
return this.dependencies.has(path);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.newTargets = new Set();
|
||||
}
|
||||
|
||||
isWatched(target) {
|
||||
return this.targets.has(target);
|
||||
}
|
||||
|
||||
addToDependencyGraph(parent, deps) {
|
||||
if (!this.graph.hasNode(parent)) {
|
||||
this.graph.addNode(parent);
|
||||
}
|
||||
for (let dep of deps) {
|
||||
if (!this.graph.hasNode(dep)) {
|
||||
this.graph.addNode(dep);
|
||||
}
|
||||
this.graph.addDependency(parent, dep);
|
||||
}
|
||||
}
|
||||
|
||||
uses(parent, dep) {
|
||||
return this.getDependenciesOf(parent).includes(dep);
|
||||
}
|
||||
|
||||
getDependenciesOf(parent) {
|
||||
if (!this.graph.hasNode(parent)) {
|
||||
return [];
|
||||
}
|
||||
return this.graph.dependenciesOf(parent);
|
||||
}
|
||||
|
||||
getDependantsOf(child) {
|
||||
if (!this.graph.hasNode(child)) {
|
||||
return [];
|
||||
}
|
||||
return this.graph.dependantsOf(child);
|
||||
}
|
||||
|
||||
addRaw(targets, isDependency) {
|
||||
for (let target of targets) {
|
||||
let path = TemplatePath.addLeadingDotSlash(target);
|
||||
if (!this.isWatched(path)) {
|
||||
this.newTargets.add(path);
|
||||
}
|
||||
|
||||
this.targets.add(path);
|
||||
|
||||
if (isDependency) {
|
||||
this.dependencies.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static normalize(targets) {
|
||||
if (!targets) {
|
||||
return [];
|
||||
} else if (Array.isArray(targets)) {
|
||||
return targets;
|
||||
}
|
||||
|
||||
return [targets];
|
||||
}
|
||||
|
||||
// add only a target
|
||||
add(targets) {
|
||||
this.addRaw(EleventyWatchTargets.normalize(targets));
|
||||
}
|
||||
|
||||
static normalizeToGlobs(targets) {
|
||||
return EleventyWatchTargets.normalize(targets).map((entry) =>
|
||||
TemplatePath.convertToRecursiveGlobSync(entry),
|
||||
);
|
||||
}
|
||||
|
||||
addAndMakeGlob(targets) {
|
||||
this.addRaw(EleventyWatchTargets.normalizeToGlobs(targets));
|
||||
}
|
||||
|
||||
// add only a target’s dependencies
|
||||
async addDependencies(targets, filterCallback) {
|
||||
if (this.#templateConfig && !this.#templateConfig.shouldSpiderJavaScriptDependencies()) {
|
||||
return;
|
||||
}
|
||||
|
||||
targets = EleventyWatchTargets.normalize(targets);
|
||||
let deps = await JavaScriptDependencies.getDependencies(targets, this.isEsm);
|
||||
if (filterCallback) {
|
||||
deps = deps.filter(filterCallback);
|
||||
}
|
||||
|
||||
for (let target of targets) {
|
||||
this.addToDependencyGraph(target, deps);
|
||||
}
|
||||
this.addRaw(deps, true);
|
||||
}
|
||||
|
||||
setWriter(templateWriter) {
|
||||
this.writer = templateWriter;
|
||||
}
|
||||
|
||||
clearImportCacheFor(filePathArray) {
|
||||
let paths = new Set();
|
||||
for (const filePath of filePathArray) {
|
||||
paths.add(filePath);
|
||||
|
||||
// Delete from require cache so that updates to the module are re-required
|
||||
let importsTheChangedFile = this.getDependantsOf(filePath);
|
||||
for (let dep of importsTheChangedFile) {
|
||||
paths.add(dep);
|
||||
}
|
||||
|
||||
let isImportedInTheChangedFile = this.getDependenciesOf(filePath);
|
||||
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);
|
||||
}
|
||||
|
||||
getNewTargetsSinceLastReset() {
|
||||
return Array.from(this.newTargets);
|
||||
}
|
||||
|
||||
getTargets() {
|
||||
return Array.from(this.targets);
|
||||
}
|
||||
}
|
||||
|
||||
export default EleventyWatchTargets;
|
||||
338
node_modules/@11ty/eleventy/src/Engines/Custom.js
generated
vendored
Normal file
338
node_modules/@11ty/eleventy/src/Engines/Custom.js
generated
vendored
Normal file
@@ -0,0 +1,338 @@
|
||||
import TemplateEngine from "./TemplateEngine.js";
|
||||
import getJavaScriptData from "../Util/GetJavaScriptData.js";
|
||||
import eventBus from "../EventBus.js";
|
||||
|
||||
let lastModifiedFile = undefined;
|
||||
eventBus.on("eleventy.resourceModified", (path) => {
|
||||
lastModifiedFile = path;
|
||||
});
|
||||
|
||||
class CustomEngine extends TemplateEngine {
|
||||
constructor(name, eleventyConfig) {
|
||||
super(name, eleventyConfig);
|
||||
|
||||
this.entry = this.getExtensionMapEntry();
|
||||
this.needsInit = "init" in this.entry && typeof this.entry.init === "function";
|
||||
|
||||
this._defaultEngine = undefined;
|
||||
|
||||
// Enable cacheability for this template
|
||||
if (this.entry?.compileOptions?.cache) {
|
||||
this.cacheable = this.entry.compileOptions.cache;
|
||||
} else if (this.needsToReadFileContents()) {
|
||||
this.cacheable = true;
|
||||
}
|
||||
}
|
||||
|
||||
getExtensionMapEntry() {
|
||||
if ("extensionMap" in this.config) {
|
||||
let name = this.name.toLowerCase();
|
||||
// Iterates over only the user config `addExtension` entries
|
||||
for (let entry of this.config.extensionMap) {
|
||||
let entryKey = (entry.aliasKey || entry.key || "").toLowerCase();
|
||||
if (entryKey === name) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw Error(
|
||||
`Could not find a custom extension for ${this.name}. Did you add it to your config file?`,
|
||||
);
|
||||
}
|
||||
|
||||
setDefaultEngine(defaultEngine) {
|
||||
this._defaultEngine = defaultEngine;
|
||||
}
|
||||
|
||||
async getInstanceFromInputPath(inputPath) {
|
||||
if (
|
||||
"getInstanceFromInputPath" in this.entry &&
|
||||
typeof this.entry.getInstanceFromInputPath === "function"
|
||||
) {
|
||||
// returns Promise
|
||||
return this.entry.getInstanceFromInputPath(inputPath);
|
||||
}
|
||||
|
||||
// aliased upstream type
|
||||
if (
|
||||
this._defaultEngine &&
|
||||
"getInstanceFromInputPath" in this._defaultEngine &&
|
||||
typeof this._defaultEngine.getInstanceFromInputPath === "function"
|
||||
) {
|
||||
// returns Promise
|
||||
return this._defaultEngine.getInstanceFromInputPath(inputPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use the module loader directly
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
useJavaScriptImport() {
|
||||
if ("useJavaScriptImport" in this.entry) {
|
||||
return this.entry.useJavaScriptImport;
|
||||
}
|
||||
|
||||
if (
|
||||
this._defaultEngine &&
|
||||
"useJavaScriptImport" in this._defaultEngine &&
|
||||
typeof this._defaultEngine.useJavaScriptImport === "function"
|
||||
) {
|
||||
return this._defaultEngine.useJavaScriptImport();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
needsToReadFileContents() {
|
||||
if ("read" in this.entry) {
|
||||
return this.entry.read;
|
||||
}
|
||||
|
||||
// Handle aliases to `11ty.js` templates, avoid reading files in the alias, see #2279
|
||||
// Here, we are short circuiting fallback to defaultRenderer, does not account for compile
|
||||
// functions that call defaultRenderer explicitly
|
||||
if (this._defaultEngine && "needsToReadFileContents" in this._defaultEngine) {
|
||||
return this._defaultEngine.needsToReadFileContents();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we init from multiple places, wait for the first init to finish before continuing on.
|
||||
async _runningInit() {
|
||||
if (this.needsInit) {
|
||||
if (!this._initing) {
|
||||
this._initBench = this.benchmarks.aggregate.get(`Engine (${this.name}) Init`);
|
||||
this._initBench.before();
|
||||
this._initing = this.entry.init.bind({
|
||||
config: this.config,
|
||||
bench: this.benchmarks.aggregate,
|
||||
})();
|
||||
}
|
||||
await this._initing;
|
||||
this.needsInit = false;
|
||||
|
||||
if (this._initBench) {
|
||||
this._initBench.after();
|
||||
this._initBench = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getExtraDataFromFile(inputPath) {
|
||||
if (this.entry.getData === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!("getData" in this.entry)) {
|
||||
// Handle aliases to `11ty.js` templates, use upstream default engine data fetch, see #2279
|
||||
if (this._defaultEngine && "getExtraDataFromFile" in this._defaultEngine) {
|
||||
return this._defaultEngine.getExtraDataFromFile(inputPath);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this._runningInit();
|
||||
|
||||
if (typeof this.entry.getData === "function") {
|
||||
let dataBench = this.benchmarks.aggregate.get(
|
||||
`Engine (${this.name}) Get Data From File (Function)`,
|
||||
);
|
||||
dataBench.before();
|
||||
let data = this.entry.getData(inputPath);
|
||||
dataBench.after();
|
||||
return data;
|
||||
}
|
||||
|
||||
let keys = new Set();
|
||||
if (this.entry.getData === true) {
|
||||
keys.add("data");
|
||||
} else if (Array.isArray(this.entry.getData)) {
|
||||
for (let key of this.entry.getData) {
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
let dataBench = this.benchmarks.aggregate.get(`Engine (${this.name}) Get Data From File`);
|
||||
dataBench.before();
|
||||
|
||||
let inst = await this.getInstanceFromInputPath(inputPath);
|
||||
|
||||
if (inst === false) {
|
||||
dataBench.after();
|
||||
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
`\`getInstanceFromInputPath\` callback missing from '${this.name}' template engine plugin. It is required when \`getData\` is in use. You can set \`getData: false\` to opt-out of this.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// override keys set at the plugin level in the individual template
|
||||
if (inst.eleventyDataKey) {
|
||||
keys = new Set(inst.eleventyDataKey);
|
||||
}
|
||||
|
||||
let mixins;
|
||||
if (this.config) {
|
||||
// Object.assign usage: see TemplateRenderCustomTest.js: `JavaScript functions should not be mutable but not *that* mutable`
|
||||
mixins = Object.assign({}, this.config.javascriptFunctions);
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let key of keys) {
|
||||
promises.push(
|
||||
getJavaScriptData(inst, inputPath, key, {
|
||||
mixins,
|
||||
isObjectRequired: key === "data",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let results = await Promise.all(promises);
|
||||
let data = {};
|
||||
for (let result of results) {
|
||||
Object.assign(data, result);
|
||||
}
|
||||
dataBench.after();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async compile(str, inputPath, ...args) {
|
||||
await this._runningInit();
|
||||
let defaultCompilationFn;
|
||||
if (this._defaultEngine) {
|
||||
defaultCompilationFn = async (data) => {
|
||||
const renderFn = await this._defaultEngine.compile(str, inputPath, ...args);
|
||||
return renderFn(data);
|
||||
};
|
||||
}
|
||||
|
||||
// Fall back to default compiler if the user does not provide their own
|
||||
if (!this.entry.compile) {
|
||||
if (defaultCompilationFn) {
|
||||
return defaultCompilationFn;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Missing \`compile\` property for custom template syntax definition eleventyConfig.addExtension("${this.name}"). This is not necessary when aliasing to an existing template syntax.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO generalize this (look at JavaScript.js)
|
||||
let fn = this.entry.compile.bind({
|
||||
config: this.config,
|
||||
addDependencies: (from, toArray = []) => {
|
||||
this.config.uses.addDependency(from, toArray);
|
||||
},
|
||||
defaultRenderer: defaultCompilationFn, // bind defaultRenderer to compile function
|
||||
})(str, inputPath);
|
||||
|
||||
// Support `undefined` to skip compile/render
|
||||
if (fn) {
|
||||
// Bind defaultRenderer to render function
|
||||
if ("then" in fn && typeof fn.then === "function") {
|
||||
// Promise, wait to bind
|
||||
return fn.then((fn) => {
|
||||
if (typeof fn === "function") {
|
||||
return fn.bind({
|
||||
defaultRenderer: defaultCompilationFn,
|
||||
});
|
||||
}
|
||||
return fn;
|
||||
});
|
||||
} else if ("bind" in fn && typeof fn.bind === "function") {
|
||||
return fn.bind({
|
||||
defaultRenderer: defaultCompilationFn,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
get defaultTemplateFileExtension() {
|
||||
return this.entry.outputFileExtension ?? "html";
|
||||
}
|
||||
|
||||
// Whether or not to wrap in Eleventy layouts
|
||||
useLayouts() {
|
||||
// TODO future change fallback to `this.defaultTemplateFileExtension === "html"`
|
||||
return this.entry.useLayouts ?? true;
|
||||
}
|
||||
|
||||
hasDependencies(inputPath) {
|
||||
if (this.config.uses.getDependencies(inputPath) === false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
isFileRelevantTo(inputPath, comparisonFile, includeLayouts) {
|
||||
return this.config.uses.isFileRelevantTo(inputPath, comparisonFile, includeLayouts);
|
||||
}
|
||||
|
||||
getCompileCacheKey(str, inputPath) {
|
||||
// Return this separately so we know whether or not to use the cached version
|
||||
// but still return a key to cache this new render for next time
|
||||
let useCache = !this.isFileRelevantTo(inputPath, lastModifiedFile, false);
|
||||
|
||||
if (this.entry.compileOptions && "getCacheKey" in this.entry.compileOptions) {
|
||||
if (typeof this.entry.compileOptions.getCacheKey !== "function") {
|
||||
throw new Error(
|
||||
`\`compileOptions.getCacheKey\` must be a function in addExtension for the ${this.name} type`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
useCache,
|
||||
key: this.entry.compileOptions.getCacheKey(str, inputPath),
|
||||
};
|
||||
}
|
||||
|
||||
let { key } = super.getCompileCacheKey(str, inputPath);
|
||||
return {
|
||||
useCache,
|
||||
key,
|
||||
};
|
||||
}
|
||||
|
||||
permalinkNeedsCompilation(/*str*/) {
|
||||
if (this.entry.compileOptions && "permalink" in this.entry.compileOptions) {
|
||||
let p = this.entry.compileOptions.permalink;
|
||||
if (p === "raw") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// permalink: false is aliased to permalink: () => false
|
||||
if (p === false) {
|
||||
return () => false;
|
||||
}
|
||||
|
||||
return this.entry.compileOptions.permalink;
|
||||
}
|
||||
|
||||
// Breaking: default changed from `true` to `false` in 3.0.0-alpha.13
|
||||
return false;
|
||||
}
|
||||
|
||||
static shouldSpiderJavaScriptDependencies(entry) {
|
||||
if (entry.compileOptions && "spiderJavaScriptDependencies" in entry.compileOptions) {
|
||||
return entry.compileOptions.spiderJavaScriptDependencies;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomEngine;
|
||||
34
node_modules/@11ty/eleventy/src/Engines/FrontMatter/JavaScript.js
generated
vendored
Normal file
34
node_modules/@11ty/eleventy/src/Engines/FrontMatter/JavaScript.js
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import { RetrieveGlobals } from "node-retrieve-globals";
|
||||
|
||||
// `javascript` Front Matter Type
|
||||
export default function (frontMatterCode, context = {}) {
|
||||
let { filePath } = context;
|
||||
|
||||
// context.language would be nice as a guard, but was unreliable
|
||||
if (frontMatterCode.trimStart().startsWith("{")) {
|
||||
return context.engines.jsLegacy.parse(frontMatterCode, context);
|
||||
}
|
||||
|
||||
let vm = new RetrieveGlobals(frontMatterCode, {
|
||||
filePath,
|
||||
// ignored if vm.Module is stable (or --experimental-vm-modules)
|
||||
transformEsmImports: true,
|
||||
});
|
||||
|
||||
// Future warning until vm.Module is stable:
|
||||
// If the frontMatterCode uses `import` this uses the `experimentalModuleApi`
|
||||
// option in node-retrieve-globals to workaround https://github.com/zachleat/node-retrieve-globals/issues/2
|
||||
let data = {
|
||||
page: {
|
||||
// Theoretically fileSlug and filePathStem could be added here but require extensionMap
|
||||
inputPath: filePath,
|
||||
},
|
||||
};
|
||||
|
||||
// this is async, but it’s handled in Eleventy upstream.
|
||||
return vm.getGlobalContext(data, {
|
||||
reuseGlobal: true,
|
||||
dynamicImport: true,
|
||||
// addRequire: true,
|
||||
});
|
||||
}
|
||||
28
node_modules/@11ty/eleventy/src/Engines/Html.js
generated
vendored
Normal file
28
node_modules/@11ty/eleventy/src/Engines/Html.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import TemplateEngine from "./TemplateEngine.js";
|
||||
|
||||
class Html extends TemplateEngine {
|
||||
constructor(name, eleventyConfig) {
|
||||
super(name, eleventyConfig);
|
||||
this.cacheable = true;
|
||||
}
|
||||
|
||||
async compile(str, inputPath, preTemplateEngine) {
|
||||
if (preTemplateEngine) {
|
||||
let engine = await this.engineManager.getEngine(preTemplateEngine, this.extensionMap);
|
||||
let fnReady = engine.compile(str, inputPath);
|
||||
|
||||
return async function (data) {
|
||||
let fn = await fnReady;
|
||||
|
||||
return fn(data);
|
||||
};
|
||||
}
|
||||
|
||||
return function () {
|
||||
// do nothing with data if parseHtmlWith is falsy
|
||||
return str;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Html;
|
||||
237
node_modules/@11ty/eleventy/src/Engines/JavaScript.js
generated
vendored
Normal file
237
node_modules/@11ty/eleventy/src/Engines/JavaScript.js
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
|
||||
|
||||
import TemplateEngine from "./TemplateEngine.js";
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
import getJavaScriptData from "../Util/GetJavaScriptData.js";
|
||||
import EventBusUtil from "../Util/EventBusUtil.js";
|
||||
import { EleventyImport } from "../Util/Require.js";
|
||||
import { augmentFunction, augmentObject } from "./Util/ContextAugmenter.js";
|
||||
|
||||
class JavaScriptTemplateNotDefined extends EleventyBaseError {}
|
||||
|
||||
class JavaScript extends TemplateEngine {
|
||||
constructor(name, templateConfig) {
|
||||
super(name, templateConfig);
|
||||
this.instances = {};
|
||||
|
||||
this.cacheable = false;
|
||||
EventBusUtil.soloOn("eleventy.templateModified", (inputPath, metadata = {}) => {
|
||||
let { usedByDependants, relevantLayouts } = metadata;
|
||||
// Remove from cached instances when modified
|
||||
let instancesToDelete = [
|
||||
inputPath,
|
||||
...(usedByDependants || []),
|
||||
...(relevantLayouts || []),
|
||||
].map((entry) => TemplatePath.addLeadingDotSlash(entry));
|
||||
for (let inputPath of instancesToDelete) {
|
||||
if (inputPath in this.instances) {
|
||||
delete this.instances[inputPath];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
normalize(result) {
|
||||
if (Buffer.isBuffer(result)) {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// String, Buffer, Promise
|
||||
// Function, Class
|
||||
// Object
|
||||
// Module
|
||||
_getInstance(mod) {
|
||||
let noop = function () {
|
||||
return "";
|
||||
};
|
||||
|
||||
let originalModData = mod?.data;
|
||||
|
||||
if (typeof mod === "object" && mod.default && this.eleventyConfig.getIsProjectUsingEsm()) {
|
||||
mod = mod.default;
|
||||
}
|
||||
|
||||
if (typeof mod === "string" || mod instanceof Buffer || mod.then) {
|
||||
return { render: () => mod };
|
||||
} else if (typeof mod === "function") {
|
||||
if (mod.prototype?.data || mod.prototype?.render) {
|
||||
if (!("render" in mod.prototype)) {
|
||||
mod.prototype.render = noop;
|
||||
}
|
||||
|
||||
if (!("data" in mod.prototype) && !mod.data && originalModData) {
|
||||
mod.prototype.data = originalModData;
|
||||
}
|
||||
|
||||
return new mod();
|
||||
} else {
|
||||
return {
|
||||
...(originalModData ? { data: originalModData } : undefined),
|
||||
render: mod,
|
||||
};
|
||||
}
|
||||
} else if ("data" in mod || "render" in mod) {
|
||||
if (!mod.render) {
|
||||
mod.render = noop;
|
||||
}
|
||||
if (!mod.data && originalModData) {
|
||||
mod.data = originalModData;
|
||||
}
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
|
||||
async #getInstanceFromInputPath(inputPath) {
|
||||
let mod;
|
||||
let relativeInputPath =
|
||||
this.eleventyConfig.directories.getInputPathRelativeToInputDirectory(inputPath);
|
||||
if (this.eleventyConfig.userConfig.isVirtualTemplate(relativeInputPath)) {
|
||||
mod = this.eleventyConfig.userConfig.virtualTemplates[relativeInputPath].content;
|
||||
} else {
|
||||
let isEsm = this.eleventyConfig.getIsProjectUsingEsm();
|
||||
mod = await EleventyImport(inputPath, isEsm ? "esm" : "cjs");
|
||||
}
|
||||
|
||||
let inst = this._getInstance(mod);
|
||||
if (inst) {
|
||||
this.instances[inputPath] = inst;
|
||||
} else {
|
||||
throw new JavaScriptTemplateNotDefined(
|
||||
`No JavaScript template returned from ${inputPath}. Did you assign module.exports (CommonJS) or export (ESM)?`,
|
||||
);
|
||||
}
|
||||
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
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
needsToReadFileContents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the module loader directly
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
useJavaScriptImport() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getExtraDataFromFile(inputPath) {
|
||||
let inst = await this.getInstanceFromInputPath(inputPath);
|
||||
return getJavaScriptData(inst, inputPath);
|
||||
}
|
||||
|
||||
getJavaScriptFunctions(inst) {
|
||||
let fns = {};
|
||||
let configFns = this.config.javascriptFunctions;
|
||||
|
||||
for (let key in configFns) {
|
||||
// prefer pre-existing `page` javascriptFunction, if one exists
|
||||
fns[key] = augmentFunction(configFns[key], {
|
||||
source: inst,
|
||||
overwrite: false,
|
||||
});
|
||||
}
|
||||
return fns;
|
||||
}
|
||||
|
||||
// Backwards compat
|
||||
static wrapJavaScriptFunction(inst, fn) {
|
||||
return augmentFunction(fn, {
|
||||
source: inst,
|
||||
});
|
||||
}
|
||||
|
||||
addExportsToBundles(inst, url) {
|
||||
let cfg = this.eleventyConfig.userConfig;
|
||||
if (!("getBundleManagers" in cfg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let managers = cfg.getBundleManagers();
|
||||
for (let name in managers) {
|
||||
let mgr = managers[name];
|
||||
let key = mgr.getBundleExportKey();
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof inst[key] === "string") {
|
||||
// export const css = ``;
|
||||
mgr.addToPage(url, inst[key]);
|
||||
} else if (isPlainObject(inst[key])) {
|
||||
if (typeof inst[key][name] === "string") {
|
||||
// Object with bundle names:
|
||||
// export const bundle = {
|
||||
// css: ``
|
||||
// };
|
||||
mgr.addToPage(url, inst[key][name]);
|
||||
} else if (isPlainObject(inst[key][name])) {
|
||||
// Object with bucket names:
|
||||
// export const bundle = {
|
||||
// css: {
|
||||
// default: ``
|
||||
// }
|
||||
// };
|
||||
for (let bucketName in inst[key][name]) {
|
||||
mgr.addToPage(url, inst[key][name][bucketName], bucketName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
let inst;
|
||||
if (str) {
|
||||
// When str has a value, it's being used for permalinks in data
|
||||
inst = this._getInstance(str);
|
||||
} else {
|
||||
// For normal templates, str will be falsy.
|
||||
inst = await this.getInstanceFromInputPath(inputPath);
|
||||
}
|
||||
|
||||
if (inst?.render) {
|
||||
return function (data = {}) {
|
||||
// TODO does this do anything meaningful for non-classes?
|
||||
// `inst` should have a normalized `render` function from _getInstance
|
||||
|
||||
// Map exports to bundles
|
||||
if (data.page?.url) {
|
||||
this.addExportsToBundles(inst, data.page.url);
|
||||
}
|
||||
|
||||
augmentObject(inst, {
|
||||
source: data,
|
||||
overwrite: false,
|
||||
});
|
||||
|
||||
Object.assign(inst, this.getJavaScriptFunctions(inst));
|
||||
|
||||
return this.normalize(inst.render.call(inst, data));
|
||||
}.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
static shouldSpiderJavaScriptDependencies() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default JavaScript;
|
||||
326
node_modules/@11ty/eleventy/src/Engines/Liquid.js
generated
vendored
Normal file
326
node_modules/@11ty/eleventy/src/Engines/Liquid.js
generated
vendored
Normal file
@@ -0,0 +1,326 @@
|
||||
import moo from "moo";
|
||||
import { Tokenizer, TokenKind, evalToken, Liquid as LiquidJs } from "liquidjs";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
// import debugUtil from "debug";
|
||||
|
||||
import TemplateEngine from "./TemplateEngine.js";
|
||||
import { augmentObject } from "./Util/ContextAugmenter.js";
|
||||
|
||||
// const debug = debugUtil("Eleventy:Liquid");
|
||||
|
||||
class Liquid extends TemplateEngine {
|
||||
static argumentLexerOptions = {
|
||||
number: /[0-9]+\.*[0-9]*/,
|
||||
doubleQuoteString: /"(?:\\["\\]|[^\n"\\])*"/,
|
||||
singleQuoteString: /'(?:\\['\\]|[^\n'\\])*'/,
|
||||
keyword: /[a-zA-Z0-9.\-_]+/,
|
||||
"ignore:whitespace": /[, \t]+/, // includes comma separator
|
||||
};
|
||||
|
||||
constructor(name, eleventyConfig) {
|
||||
super(name, eleventyConfig);
|
||||
|
||||
this.liquidOptions = this.config.liquidOptions || {};
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.liquid);
|
||||
|
||||
this.argLexer = moo.compile(Liquid.argumentLexerOptions);
|
||||
this.cacheable = true;
|
||||
}
|
||||
|
||||
setLibrary(override) {
|
||||
// warning, the include syntax supported here does not exactly match what Jekyll uses.
|
||||
this.liquidLib = override || new LiquidJs(this.getLiquidOptions());
|
||||
this.setEngineLib(this.liquidLib);
|
||||
|
||||
this.addFilters(this.config.liquidFilters);
|
||||
|
||||
// TODO these all go to the same place (addTag), add warnings for overwrites
|
||||
this.addCustomTags(this.config.liquidTags);
|
||||
this.addAllShortcodes(this.config.liquidShortcodes);
|
||||
this.addAllPairedShortcodes(this.config.liquidPairedShortcodes);
|
||||
}
|
||||
|
||||
getLiquidOptions() {
|
||||
let defaults = {
|
||||
root: [this.dirs.includes, this.dirs.input], // supplemented in compile with inputPath below
|
||||
extname: ".liquid",
|
||||
strictFilters: true,
|
||||
// TODO?
|
||||
// cache: true,
|
||||
};
|
||||
|
||||
let options = Object.assign(defaults, this.liquidOptions || {});
|
||||
// debug("Liquid constructor options: %o", options);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
static wrapFilter(name, fn) {
|
||||
/**
|
||||
* @this {object}
|
||||
*/
|
||||
return function (...args) {
|
||||
// Set this.eleventy and this.page
|
||||
if (typeof this.context?.get === "function") {
|
||||
augmentObject(this, {
|
||||
source: this.context,
|
||||
getter: (key, context) => context.get([key]),
|
||||
|
||||
lazy: this.context.strictVariables,
|
||||
});
|
||||
}
|
||||
|
||||
// We *don’t* wrap this in an EleventyFilterError because Liquid has a better error message with line/column information in the template
|
||||
return fn.call(this, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
// Shortcodes
|
||||
static normalizeScope(context) {
|
||||
let obj = {};
|
||||
if (context) {
|
||||
obj.ctx = context; // Full context available on `ctx`
|
||||
|
||||
// Set this.eleventy and this.page
|
||||
augmentObject(obj, {
|
||||
source: context,
|
||||
getter: (key, context) => context.get([key]),
|
||||
lazy: context.strictVariables,
|
||||
});
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
addCustomTags(tags) {
|
||||
for (let name in tags) {
|
||||
this.addTag(name, tags[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addFilters(filters) {
|
||||
for (let name in filters) {
|
||||
this.addFilter(name, filters[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addFilter(name, filter) {
|
||||
this.liquidLib.registerFilter(name, Liquid.wrapFilter(name, filter));
|
||||
}
|
||||
|
||||
addTag(name, tagFn) {
|
||||
let tagObj;
|
||||
if (typeof tagFn === "function") {
|
||||
tagObj = tagFn(this.liquidLib);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Liquid.addTag expects a callback function to be passed in: addTag(name, function(liquidEngine) { return { parse: …, render: … } })",
|
||||
);
|
||||
}
|
||||
this.liquidLib.registerTag(name, tagObj);
|
||||
}
|
||||
|
||||
addAllShortcodes(shortcodes) {
|
||||
for (let name in shortcodes) {
|
||||
this.addShortcode(name, shortcodes[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addAllPairedShortcodes(shortcodes) {
|
||||
for (let name in shortcodes) {
|
||||
this.addPairedShortcode(name, shortcodes[name]);
|
||||
}
|
||||
}
|
||||
|
||||
static parseArguments(lexer, str) {
|
||||
let argArray = [];
|
||||
|
||||
if (!lexer) {
|
||||
lexer = moo.compile(Liquid.argumentLexerOptions);
|
||||
}
|
||||
|
||||
if (typeof str === "string") {
|
||||
lexer.reset(str);
|
||||
|
||||
let arg = lexer.next();
|
||||
while (arg) {
|
||||
/*{
|
||||
type: 'doubleQuoteString',
|
||||
value: '"test 2"',
|
||||
text: '"test 2"',
|
||||
toString: [Function: tokenToString],
|
||||
offset: 0,
|
||||
lineBreaks: 0,
|
||||
line: 1,
|
||||
col: 1 }*/
|
||||
if (arg.type.indexOf("ignore:") === -1) {
|
||||
// Push the promise into an array instead of awaiting it here.
|
||||
// This forces the promises to run in order with the correct scope value for each arg.
|
||||
// Otherwise they run out of order and can lead to undefined values for arguments in layout template shortcodes.
|
||||
// console.log( arg.value, scope, engine );
|
||||
argArray.push(arg.value);
|
||||
}
|
||||
arg = lexer.next();
|
||||
}
|
||||
}
|
||||
|
||||
return argArray;
|
||||
}
|
||||
|
||||
static parseArgumentsBuiltin(args) {
|
||||
let tokenizer = new Tokenizer(args);
|
||||
let parsedArgs = [];
|
||||
|
||||
let value = tokenizer.readValue();
|
||||
while (value) {
|
||||
parsedArgs.push(value);
|
||||
tokenizer.skipBlank();
|
||||
if (tokenizer.peek() === ",") {
|
||||
tokenizer.advance();
|
||||
}
|
||||
value = tokenizer.readValue();
|
||||
}
|
||||
tokenizer.end();
|
||||
|
||||
return parsedArgs;
|
||||
}
|
||||
|
||||
addShortcode(shortcodeName, shortcodeFn) {
|
||||
let _t = this;
|
||||
this.addTag(shortcodeName, function (liquidEngine) {
|
||||
return {
|
||||
parse(tagToken) {
|
||||
this.name = tagToken.name;
|
||||
if (_t.config.liquidParameterParsing === "builtin") {
|
||||
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
|
||||
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
|
||||
} else {
|
||||
this.legacyArgs = tagToken.args;
|
||||
}
|
||||
},
|
||||
render: function* (ctx) {
|
||||
let argArray = [];
|
||||
|
||||
if (this.legacyArgs) {
|
||||
let rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);
|
||||
for (let arg of rawArgs) {
|
||||
let b = yield liquidEngine.evalValue(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
} else if (this.orderedArgs) {
|
||||
for (let arg of this.orderedArgs) {
|
||||
let b = yield evalToken(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
let ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), ...argArray);
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
addPairedShortcode(shortcodeName, shortcodeFn) {
|
||||
let _t = this;
|
||||
this.addTag(shortcodeName, function (liquidEngine) {
|
||||
return {
|
||||
parse(tagToken, remainTokens) {
|
||||
this.name = tagToken.name;
|
||||
|
||||
if (_t.config.liquidParameterParsing === "builtin") {
|
||||
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
|
||||
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
|
||||
} else {
|
||||
this.legacyArgs = tagToken.args;
|
||||
}
|
||||
|
||||
this.templates = [];
|
||||
|
||||
var stream = liquidEngine.parser
|
||||
.parseStream(remainTokens)
|
||||
.on("template", (tpl) => this.templates.push(tpl))
|
||||
.on("tag:end" + shortcodeName, () => stream.stop())
|
||||
.on("end", () => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`);
|
||||
});
|
||||
|
||||
stream.start();
|
||||
},
|
||||
render: function* (ctx /*, emitter*/) {
|
||||
let argArray = [];
|
||||
if (this.legacyArgs) {
|
||||
let rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);
|
||||
for (let arg of rawArgs) {
|
||||
let b = yield liquidEngine.evalValue(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
} else if (this.orderedArgs) {
|
||||
for (let arg of this.orderedArgs) {
|
||||
let b = yield evalToken(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
const html = yield liquidEngine.renderer.renderTemplates(this.templates, ctx);
|
||||
|
||||
let ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), html, ...argArray);
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
parseForSymbols(str) {
|
||||
let tokenizer = new Tokenizer(str);
|
||||
/** @type {Array} */
|
||||
let tokens = tokenizer.readTopLevelTokens();
|
||||
let symbols = tokens
|
||||
.filter((token) => token.kind === TokenKind.Output)
|
||||
.map((token) => {
|
||||
// manually remove filters 😅
|
||||
return token.content.split("|").map((entry) => entry.trim())[0];
|
||||
});
|
||||
return symbols;
|
||||
}
|
||||
|
||||
// Don’t return a boolean if permalink is a function (see TemplateContent->renderPermalink)
|
||||
/** @returns {boolean|undefined} */
|
||||
permalinkNeedsCompilation(str) {
|
||||
if (typeof str === "string") {
|
||||
return this.needsCompilation(str);
|
||||
}
|
||||
}
|
||||
|
||||
needsCompilation(str) {
|
||||
let options = this.liquidLib.options;
|
||||
|
||||
return (
|
||||
str.indexOf(options.tagDelimiterLeft) !== -1 ||
|
||||
str.indexOf(options.outputDelimiterLeft) !== -1
|
||||
);
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
let engine = this.liquidLib;
|
||||
let tmplReady = engine.parse(str, inputPath);
|
||||
|
||||
// Required for relative includes
|
||||
let options = {};
|
||||
if (!inputPath || inputPath === "liquid" || inputPath === "md") {
|
||||
// do nothing
|
||||
} else {
|
||||
options.root = [TemplatePath.getDirFromFilePath(inputPath)];
|
||||
}
|
||||
|
||||
return async function (data) {
|
||||
let tmpl = await tmplReady;
|
||||
|
||||
return engine.render(tmpl, data, options);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Liquid;
|
||||
93
node_modules/@11ty/eleventy/src/Engines/Markdown.js
generated
vendored
Normal file
93
node_modules/@11ty/eleventy/src/Engines/Markdown.js
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
import markdownIt from "markdown-it";
|
||||
|
||||
import TemplateEngine from "./TemplateEngine.js";
|
||||
|
||||
class Markdown extends TemplateEngine {
|
||||
constructor(name, eleventyConfig) {
|
||||
super(name, eleventyConfig);
|
||||
|
||||
this.markdownOptions = {};
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.md);
|
||||
|
||||
this.cacheable = true;
|
||||
}
|
||||
|
||||
setLibrary(mdLib) {
|
||||
this.mdLib = mdLib || markdownIt(this.getMarkdownOptions());
|
||||
|
||||
// Overrides a highlighter set in `markdownOptions`
|
||||
// This is separate so devs can pass in a new mdLib and still use the official eleventy plugin for markdown highlighting
|
||||
if (this.config.markdownHighlighter && typeof this.mdLib.set === "function") {
|
||||
this.mdLib.set({
|
||||
highlight: this.config.markdownHighlighter,
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof this.mdLib.disable === "function") {
|
||||
// Disable indented code blocks by default (Issue #2438)
|
||||
this.mdLib.disable("code");
|
||||
}
|
||||
|
||||
this.setEngineLib(this.mdLib);
|
||||
}
|
||||
|
||||
setMarkdownOptions(options) {
|
||||
this.markdownOptions = options;
|
||||
}
|
||||
|
||||
getMarkdownOptions() {
|
||||
// work with "mode" presets https://github.com/markdown-it/markdown-it#init-with-presets-and-options
|
||||
if (typeof this.markdownOptions === "string") {
|
||||
return this.markdownOptions;
|
||||
}
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
html: true,
|
||||
},
|
||||
this.markdownOptions || {},
|
||||
);
|
||||
}
|
||||
|
||||
async compile(str, inputPath, preTemplateEngine, bypassMarkdown) {
|
||||
let mdlib = this.mdLib;
|
||||
|
||||
if (preTemplateEngine) {
|
||||
let engine;
|
||||
if (typeof preTemplateEngine === "string") {
|
||||
engine = await this.engineManager.getEngine(preTemplateEngine, this.extensionMap);
|
||||
} else {
|
||||
engine = preTemplateEngine;
|
||||
}
|
||||
|
||||
let fnReady = engine.compile(str, inputPath);
|
||||
|
||||
if (bypassMarkdown) {
|
||||
return async function (data) {
|
||||
let fn = await fnReady;
|
||||
return fn(data);
|
||||
};
|
||||
} else {
|
||||
return async function (data) {
|
||||
let fn = await fnReady;
|
||||
let preTemplateEngineRender = await fn(data);
|
||||
let finishedRender = mdlib.render(preTemplateEngineRender, data);
|
||||
return finishedRender;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (bypassMarkdown) {
|
||||
return function () {
|
||||
return str;
|
||||
};
|
||||
} else {
|
||||
return function (data) {
|
||||
return mdlib.render(str, data);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Markdown;
|
||||
447
node_modules/@11ty/eleventy/src/Engines/Nunjucks.js
generated
vendored
Executable file
447
node_modules/@11ty/eleventy/src/Engines/Nunjucks.js
generated
vendored
Executable file
@@ -0,0 +1,447 @@
|
||||
import NunjucksLib from "nunjucks";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import TemplateEngine from "./TemplateEngine.js";
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
import EventBusUtil from "../Util/EventBusUtil.js";
|
||||
import { augmentObject } from "./Util/ContextAugmenter.js";
|
||||
|
||||
class EleventyNunjucksError extends EleventyBaseError {}
|
||||
|
||||
class Nunjucks extends TemplateEngine {
|
||||
constructor(name, eleventyConfig) {
|
||||
super(name, eleventyConfig);
|
||||
|
||||
this.nunjucksEnvironmentOptions = this.config.nunjucksEnvironmentOptions || { dev: true };
|
||||
|
||||
this.nunjucksPrecompiledTemplates = this.config.nunjucksPrecompiledTemplates || {};
|
||||
this._usingPrecompiled = Object.keys(this.nunjucksPrecompiledTemplates).length > 0;
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.njk);
|
||||
|
||||
this.cacheable = true;
|
||||
}
|
||||
|
||||
_setEnv(override) {
|
||||
if (override) {
|
||||
this.njkEnv = override;
|
||||
} else if (this._usingPrecompiled) {
|
||||
// Precompiled templates to avoid eval!
|
||||
const NodePrecompiledLoader = function () {};
|
||||
|
||||
NodePrecompiledLoader.prototype.getSource = (name) => {
|
||||
// https://github.com/mozilla/nunjucks/blob/fd500902d7c88672470c87170796de52fc0f791a/nunjucks/src/precompiled-loader.js#L5
|
||||
return {
|
||||
src: {
|
||||
type: "code",
|
||||
obj: this.nunjucksPrecompiledTemplates[name],
|
||||
},
|
||||
// Maybe add this?
|
||||
// path,
|
||||
// noCache: true
|
||||
};
|
||||
};
|
||||
|
||||
this.njkEnv = new NunjucksLib.Environment(
|
||||
new NodePrecompiledLoader(),
|
||||
this.nunjucksEnvironmentOptions,
|
||||
);
|
||||
} else {
|
||||
let paths = new Set();
|
||||
paths.add(super.getIncludesDir());
|
||||
paths.add(TemplatePath.getWorkingDir());
|
||||
|
||||
// Filter out undefined paths
|
||||
let fsLoader = new NunjucksLib.FileSystemLoader(Array.from(paths).filter(Boolean));
|
||||
|
||||
this.njkEnv = new NunjucksLib.Environment(fsLoader, this.nunjucksEnvironmentOptions);
|
||||
}
|
||||
|
||||
this.config.events.emit("eleventy.engine.njk", {
|
||||
nunjucks: NunjucksLib,
|
||||
environment: this.njkEnv,
|
||||
});
|
||||
}
|
||||
|
||||
setLibrary(override) {
|
||||
this._setEnv(override);
|
||||
|
||||
// Correct, but overbroad. Better would be to evict more granularly, but
|
||||
// resolution from paths isn't straightforward.
|
||||
EventBusUtil.soloOn("eleventy.templateModified", (/*path*/) => {
|
||||
this.njkEnv.invalidateCache();
|
||||
});
|
||||
|
||||
this.setEngineLib(this.njkEnv);
|
||||
|
||||
this.addFilters(this.config.nunjucksFilters);
|
||||
this.addFilters(this.config.nunjucksAsyncFilters, true);
|
||||
|
||||
// TODO these all go to the same place (addTag), add warnings for overwrites
|
||||
// TODO(zachleat): variableName should work with quotes or without quotes (same as {% set %})
|
||||
this.addPairedShortcode("setAsync", function (content, variableName) {
|
||||
this.ctx[variableName] = content;
|
||||
return "";
|
||||
});
|
||||
|
||||
this.addCustomTags(this.config.nunjucksTags);
|
||||
this.addAllShortcodes(this.config.nunjucksShortcodes);
|
||||
this.addAllShortcodes(this.config.nunjucksAsyncShortcodes, true);
|
||||
this.addAllPairedShortcodes(this.config.nunjucksPairedShortcodes);
|
||||
this.addAllPairedShortcodes(this.config.nunjucksAsyncPairedShortcodes, true);
|
||||
this.addGlobals(this.config.nunjucksGlobals);
|
||||
}
|
||||
|
||||
addFilters(filters, isAsync) {
|
||||
for (let name in filters) {
|
||||
this.njkEnv.addFilter(name, Nunjucks.wrapFilter(name, filters[name]), isAsync);
|
||||
}
|
||||
}
|
||||
|
||||
static wrapFilter(name, fn) {
|
||||
return function (...args) {
|
||||
try {
|
||||
augmentObject(this, {
|
||||
source: this.ctx,
|
||||
lazy: false, // context.env?.opts.throwOnUndefined,
|
||||
});
|
||||
|
||||
return fn.call(this, ...args);
|
||||
} catch (e) {
|
||||
throw new EleventyNunjucksError(
|
||||
`Error in Nunjucks Filter \`${name}\`${this.page ? ` (${this.page.inputPath})` : ""}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Shortcodes
|
||||
static normalizeContext(context) {
|
||||
let obj = {};
|
||||
if (context.ctx) {
|
||||
obj.ctx = context.ctx;
|
||||
obj.env = context.env;
|
||||
|
||||
augmentObject(obj, {
|
||||
source: context.ctx,
|
||||
lazy: false, // context.env?.opts.throwOnUndefined,
|
||||
});
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
addCustomTags(tags) {
|
||||
for (let name in tags) {
|
||||
this.addTag(name, tags[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addTag(name, tagFn) {
|
||||
let tagObj;
|
||||
if (typeof tagFn === "function") {
|
||||
tagObj = tagFn(NunjucksLib, this.njkEnv);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Nunjucks.addTag expects a callback function to be passed in: addTag(name, function(nunjucksEngine) {})",
|
||||
);
|
||||
}
|
||||
|
||||
this.njkEnv.addExtension(name, tagObj);
|
||||
}
|
||||
|
||||
addGlobals(globals) {
|
||||
for (let name in globals) {
|
||||
this.addGlobal(name, globals[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addGlobal(name, globalFn) {
|
||||
this.njkEnv.addGlobal(name, globalFn);
|
||||
}
|
||||
|
||||
addAllShortcodes(shortcodes, isAsync = false) {
|
||||
for (let name in shortcodes) {
|
||||
this.addShortcode(name, shortcodes[name], isAsync);
|
||||
}
|
||||
}
|
||||
|
||||
addAllPairedShortcodes(shortcodes, isAsync = false) {
|
||||
for (let name in shortcodes) {
|
||||
this.addPairedShortcode(name, shortcodes[name], isAsync);
|
||||
}
|
||||
}
|
||||
|
||||
_getShortcodeFn(shortcodeName, shortcodeFn, isAsync = false) {
|
||||
return function ShortcodeFunction() {
|
||||
this.tags = [shortcodeName];
|
||||
|
||||
this.parse = function (parser, nodes) {
|
||||
let args;
|
||||
let tok = parser.nextToken();
|
||||
|
||||
args = parser.parseSignature(true, true);
|
||||
|
||||
// Nunjucks bug with non-paired custom tags bug still exists even
|
||||
// though this issue is closed. Works fine for paired.
|
||||
// https://github.com/mozilla/nunjucks/issues/158
|
||||
if (args.children.length === 0) {
|
||||
args.addChild(new nodes.Literal(0, 0, ""));
|
||||
}
|
||||
|
||||
parser.advanceAfterBlockEnd(tok.value);
|
||||
if (isAsync) {
|
||||
return new nodes.CallExtensionAsync(this, "run", args);
|
||||
}
|
||||
return new nodes.CallExtension(this, "run", args);
|
||||
};
|
||||
|
||||
this.run = function (...args) {
|
||||
let resolve;
|
||||
if (isAsync) {
|
||||
resolve = args.pop();
|
||||
}
|
||||
|
||||
let [context, ...argArray] = args;
|
||||
|
||||
if (isAsync) {
|
||||
let ret = shortcodeFn.call(Nunjucks.normalizeContext(context), ...argArray);
|
||||
|
||||
// #3286 error messaging when the shortcode is not a promise
|
||||
if (!ret?.then) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(
|
||||
`Error with Nunjucks shortcode \`${shortcodeName}\`: it was defined as asynchronous but was actually synchronous. This is important for Nunjucks.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ret.then(
|
||||
function (returnValue) {
|
||||
resolve(null, new NunjucksLib.runtime.SafeString("" + returnValue));
|
||||
},
|
||||
function (e) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(`Error with Nunjucks shortcode \`${shortcodeName}\``, e),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
let ret = shortcodeFn.call(Nunjucks.normalizeContext(context), ...argArray);
|
||||
return new NunjucksLib.runtime.SafeString("" + ret);
|
||||
} catch (e) {
|
||||
throw new EleventyNunjucksError(
|
||||
`Error with Nunjucks shortcode \`${shortcodeName}\``,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
_getPairedShortcodeFn(shortcodeName, shortcodeFn, isAsync = false) {
|
||||
return function PairedShortcodeFunction() {
|
||||
this.tags = [shortcodeName];
|
||||
|
||||
this.parse = function (parser, nodes) {
|
||||
var tok = parser.nextToken();
|
||||
|
||||
var args = parser.parseSignature(true, true);
|
||||
parser.advanceAfterBlockEnd(tok.value);
|
||||
|
||||
var body = parser.parseUntilBlocks("end" + shortcodeName);
|
||||
parser.advanceAfterBlockEnd();
|
||||
|
||||
return new nodes.CallExtensionAsync(this, "run", args, [body]);
|
||||
};
|
||||
|
||||
this.run = function (...args) {
|
||||
let resolve = args.pop();
|
||||
let body = args.pop();
|
||||
let [context, ...argArray] = args;
|
||||
|
||||
body(function (e, bodyContent) {
|
||||
if (e) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(
|
||||
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
|
||||
e,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (isAsync) {
|
||||
let ret = shortcodeFn.call(
|
||||
Nunjucks.normalizeContext(context),
|
||||
bodyContent,
|
||||
...argArray,
|
||||
);
|
||||
|
||||
// #3286 error messaging when the shortcode is not a promise
|
||||
if (!ret?.then) {
|
||||
throw new EleventyNunjucksError(
|
||||
`Error with Nunjucks shortcode \`${shortcodeName}\`: it was defined as asynchronous but was actually synchronous. This is important for Nunjucks.`,
|
||||
);
|
||||
}
|
||||
|
||||
ret.then(
|
||||
function (returnValue) {
|
||||
resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
|
||||
},
|
||||
function (e) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(
|
||||
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
|
||||
e,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
resolve(
|
||||
null,
|
||||
new NunjucksLib.runtime.SafeString(
|
||||
shortcodeFn.call(Nunjucks.normalizeContext(context), bodyContent, ...argArray),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(
|
||||
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
|
||||
e,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
addShortcode(shortcodeName, shortcodeFn, isAsync = false) {
|
||||
let fn = this._getShortcodeFn(shortcodeName, shortcodeFn, isAsync);
|
||||
this.njkEnv.addExtension(shortcodeName, new fn());
|
||||
}
|
||||
|
||||
addPairedShortcode(shortcodeName, shortcodeFn, isAsync = false) {
|
||||
let fn = this._getPairedShortcodeFn(shortcodeName, shortcodeFn, isAsync);
|
||||
this.njkEnv.addExtension(shortcodeName, new fn());
|
||||
}
|
||||
|
||||
// Don’t return a boolean if permalink is a function (see TemplateContent->renderPermalink)
|
||||
permalinkNeedsCompilation(str) {
|
||||
if (typeof str === "string") {
|
||||
return this.needsCompilation(str);
|
||||
}
|
||||
}
|
||||
|
||||
needsCompilation(str) {
|
||||
// Defend against syntax customisations:
|
||||
// https://mozilla.github.io/nunjucks/api.html#customizing-syntax
|
||||
let optsTags = this.njkEnv.opts.tags || {};
|
||||
let blockStart = optsTags.blockStart || "{%";
|
||||
let variableStart = optsTags.variableStart || "{{";
|
||||
let commentStart = optsTags.variableStart || "{#";
|
||||
|
||||
return (
|
||||
str.indexOf(blockStart) !== -1 ||
|
||||
str.indexOf(variableStart) !== -1 ||
|
||||
str.indexOf(commentStart) !== -1
|
||||
);
|
||||
}
|
||||
|
||||
_getParseExtensions() {
|
||||
if (this._parseExtensions) {
|
||||
return this._parseExtensions;
|
||||
}
|
||||
|
||||
// add extensions so the parser knows about our custom tags/blocks
|
||||
let ext = [];
|
||||
for (let name in this.config.nunjucksTags) {
|
||||
let fn = this._getShortcodeFn(name, () => {});
|
||||
ext.push(new fn());
|
||||
}
|
||||
for (let name in this.config.nunjucksShortcodes) {
|
||||
let fn = this._getShortcodeFn(name, () => {});
|
||||
ext.push(new fn());
|
||||
}
|
||||
for (let name in this.config.nunjucksAsyncShortcodes) {
|
||||
let fn = this._getShortcodeFn(name, () => {}, true);
|
||||
ext.push(new fn());
|
||||
}
|
||||
for (let name in this.config.nunjucksPairedShortcodes) {
|
||||
let fn = this._getPairedShortcodeFn(name, () => {});
|
||||
ext.push(new fn());
|
||||
}
|
||||
for (let name in this.config.nunjucksAsyncPairedShortcodes) {
|
||||
let fn = this._getPairedShortcodeFn(name, () => {}, true);
|
||||
ext.push(new fn());
|
||||
}
|
||||
|
||||
this._parseExtensions = ext;
|
||||
return ext;
|
||||
}
|
||||
|
||||
/* Outputs an Array of lodash get selectors */
|
||||
parseForSymbols(str) {
|
||||
const { parser, nodes } = NunjucksLib;
|
||||
let obj = parser.parse(str, this._getParseExtensions());
|
||||
let linesplit = str.split("\n");
|
||||
let values = obj.findAll(nodes.Value);
|
||||
let symbols = obj.findAll(nodes.Symbol).map((entry) => {
|
||||
let name = [entry.value];
|
||||
let nestedIndex = -1;
|
||||
for (let val of values) {
|
||||
if (nestedIndex > -1) {
|
||||
/* deep.object.syntax */
|
||||
if (linesplit[val.lineno].charAt(nestedIndex) === ".") {
|
||||
name.push(val.value);
|
||||
nestedIndex += val.value.length + 1;
|
||||
} else {
|
||||
nestedIndex = -1;
|
||||
}
|
||||
} else if (
|
||||
val.lineno === entry.lineno &&
|
||||
val.colno === entry.colno &&
|
||||
val.value === entry.value
|
||||
) {
|
||||
nestedIndex = entry.colno + entry.value.length;
|
||||
}
|
||||
}
|
||||
return name.join(".");
|
||||
});
|
||||
|
||||
let uniqueSymbols = Array.from(new Set(symbols));
|
||||
return uniqueSymbols;
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
let tmpl;
|
||||
|
||||
// *All* templates are precompiled to avoid runtime eval
|
||||
if (this._usingPrecompiled) {
|
||||
tmpl = this.njkEnv.getTemplate(str, true);
|
||||
} else if (!inputPath || inputPath === "njk" || inputPath === "md") {
|
||||
tmpl = new NunjucksLib.Template(str, this.njkEnv, null, false);
|
||||
} else {
|
||||
tmpl = new NunjucksLib.Template(str, this.njkEnv, inputPath, false);
|
||||
}
|
||||
|
||||
return function (data) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
tmpl.render(data, function (err, res) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Nunjucks;
|
||||
184
node_modules/@11ty/eleventy/src/Engines/TemplateEngine.js
generated
vendored
Normal file
184
node_modules/@11ty/eleventy/src/Engines/TemplateEngine.js
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
import EleventyExtensionMap from "../EleventyExtensionMap.js";
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
|
||||
class TemplateEngineConfigError extends EleventyBaseError {}
|
||||
|
||||
class TemplateEngine {
|
||||
constructor(name, eleventyConfig) {
|
||||
this.name = name;
|
||||
|
||||
this.engineLib = null;
|
||||
this.cacheable = false;
|
||||
|
||||
if (!eleventyConfig) {
|
||||
throw new TemplateEngineConfigError("Missing `eleventyConfig` argument.");
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get includesDir() {
|
||||
return this.dirs.includes;
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (this.eleventyConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new Error("Expecting a TemplateConfig instance.");
|
||||
}
|
||||
|
||||
return this.eleventyConfig.getConfig();
|
||||
}
|
||||
|
||||
get benchmarks() {
|
||||
if (!this._benchmarks) {
|
||||
this._benchmarks = {
|
||||
aggregate: this.config.benchmarkManager.get("Aggregate"),
|
||||
};
|
||||
}
|
||||
return this._benchmarks;
|
||||
}
|
||||
|
||||
get engineManager() {
|
||||
return this._engineManager;
|
||||
}
|
||||
|
||||
set engineManager(manager) {
|
||||
this._engineManager = manager;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats([]);
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
set extensionMap(map) {
|
||||
this._extensionMap = map;
|
||||
}
|
||||
|
||||
get extensions() {
|
||||
if (!this._extensions) {
|
||||
this._extensions = this.extensionMap.getExtensionsFromKey(this.name);
|
||||
}
|
||||
return this._extensions;
|
||||
}
|
||||
|
||||
get extensionEntries() {
|
||||
if (!this._extensionEntries) {
|
||||
this._extensionEntries = this.extensionMap.getExtensionEntriesFromKey(this.name);
|
||||
}
|
||||
return this._extensionEntries;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
// Backwards compat
|
||||
getIncludesDir() {
|
||||
return this.includesDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
setEngineLib(engineLib) {
|
||||
this.engineLib = engineLib;
|
||||
|
||||
// Run engine amendments (via issue #2438)
|
||||
for (let amendment of this.config.libraryAmendments[this.name] || []) {
|
||||
// TODO it’d be nice if this were async friendly
|
||||
amendment(engineLib);
|
||||
}
|
||||
}
|
||||
|
||||
getEngineLib() {
|
||||
return this.engineLib;
|
||||
}
|
||||
|
||||
async _testRender(str, data) {
|
||||
// @ts-ignore
|
||||
let fn = await this.compile(str);
|
||||
return fn(data);
|
||||
}
|
||||
|
||||
useJavaScriptImport() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// JavaScript files defer to the module loader rather than read the files to strings
|
||||
needsToReadFileContents() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getExtraDataFromFile() {
|
||||
return {};
|
||||
}
|
||||
|
||||
getCompileCacheKey(str, inputPath) {
|
||||
// Changing to use inputPath and contents, using only file contents (`str`) caused issues when two
|
||||
// different files had identical content (2.0.0-canary.16)
|
||||
|
||||
// Caches are now segmented based on inputPath so using inputPath here is superfluous (2.0.0-canary.19)
|
||||
// But we do want a non-falsy value here even if `str` is an empty string.
|
||||
return {
|
||||
useCache: true,
|
||||
key: inputPath + str,
|
||||
};
|
||||
}
|
||||
|
||||
get defaultTemplateFileExtension() {
|
||||
return "html";
|
||||
}
|
||||
|
||||
// Whether or not to wrap in Eleventy layouts
|
||||
useLayouts() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @returns {boolean|undefined} */
|
||||
permalinkNeedsCompilation(str) {
|
||||
return this.needsCompilation();
|
||||
}
|
||||
|
||||
// whether or not compile is needed or can we return the plaintext?
|
||||
needsCompilation(str) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure compile is implemented downstream.
|
||||
* @abstract
|
||||
* @return {Promise}
|
||||
*/
|
||||
async compile() {
|
||||
throw new Error("compile() must be implemented by engine");
|
||||
}
|
||||
|
||||
// See https://v3.11ty.dev/docs/watch-serve/#watch-javascript-dependencies
|
||||
static shouldSpiderJavaScriptDependencies() {
|
||||
return false;
|
||||
}
|
||||
|
||||
hasDependencies(inputPath) {
|
||||
if (this.config.uses.getDependencies(inputPath) === false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
isFileRelevantTo(inputPath, comparisonFile) {
|
||||
return this.config.uses.isFileRelevantTo(inputPath, comparisonFile);
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateEngine;
|
||||
197
node_modules/@11ty/eleventy/src/Engines/TemplateEngineManager.js
generated
vendored
Normal file
197
node_modules/@11ty/eleventy/src/Engines/TemplateEngineManager.js
generated
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
import debugUtil from "debug";
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
import { EleventyImportFromEleventy } from "../Util/Require.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:TemplateEngineManager");
|
||||
|
||||
class TemplateEngineManagerConfigError extends EleventyBaseError {}
|
||||
|
||||
class TemplateEngineManager {
|
||||
constructor(eleventyConfig) {
|
||||
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new TemplateEngineManagerConfigError("Missing or invalid `config` argument.");
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
|
||||
this.engineCache = {};
|
||||
this.importCache = {};
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this.eleventyConfig.getConfig();
|
||||
}
|
||||
|
||||
static isAlias(entry) {
|
||||
if (entry.aliasKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return entry.key !== entry.extension;
|
||||
}
|
||||
|
||||
static isSimpleAlias(entry) {
|
||||
if (!this.isAlias(entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// has keys other than key, extension, and aliasKey
|
||||
return (
|
||||
Object.keys(entry).some((key) => {
|
||||
return key !== "key" && key !== "extension" && key !== "aliasKey";
|
||||
}) === false
|
||||
);
|
||||
}
|
||||
|
||||
get keyToClassNameMap() {
|
||||
if (!this._keyToClassNameMap) {
|
||||
this._keyToClassNameMap = {
|
||||
md: "Markdown",
|
||||
html: "Html",
|
||||
njk: "Nunjucks",
|
||||
liquid: "Liquid",
|
||||
"11ty.js": "JavaScript",
|
||||
};
|
||||
|
||||
// Custom entries *can* overwrite default entries above
|
||||
if ("extensionMap" in this.config) {
|
||||
for (let entry of this.config.extensionMap) {
|
||||
// either the key does not already exist or it is not a simple alias and is an override: https://v3.11ty.dev/docs/languages/custom/#overriding-an-existing-template-language
|
||||
let existingTarget = this._keyToClassNameMap[entry.key];
|
||||
let isAlias = TemplateEngineManager.isAlias(entry);
|
||||
|
||||
if (!existingTarget && isAlias) {
|
||||
throw new Error(
|
||||
`An attempt to alias ${entry.aliasKey} to ${entry.key} was made, but ${entry.key} is not a recognized template syntax.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (isAlias) {
|
||||
// only `key` and `extension`, not `compile` or other options
|
||||
if (!TemplateEngineManager.isSimpleAlias(entry)) {
|
||||
this._keyToClassNameMap[entry.aliasKey] = "Custom";
|
||||
} else {
|
||||
this._keyToClassNameMap[entry.aliasKey] = this._keyToClassNameMap[entry.key];
|
||||
}
|
||||
} else {
|
||||
// not an alias, so `key` and `extension` are the same here.
|
||||
// *can* override a built-in extension!
|
||||
this._keyToClassNameMap[entry.key] = "Custom";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._keyToClassNameMap;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.engineCache = {};
|
||||
}
|
||||
|
||||
getClassNameFromTemplateKey(key) {
|
||||
return this.keyToClassNameMap[key];
|
||||
}
|
||||
|
||||
hasEngine(name) {
|
||||
return !!this.getClassNameFromTemplateKey(name);
|
||||
}
|
||||
|
||||
isEngineRemovedFromCore(name) {
|
||||
return ["ejs", "hbs", "mustache", "haml", "pug"].includes(name) && !this.hasEngine(name);
|
||||
}
|
||||
|
||||
async getEngineClassByExtension(extension) {
|
||||
if (this.importCache[extension]) {
|
||||
return this.importCache[extension];
|
||||
}
|
||||
|
||||
let promise;
|
||||
|
||||
// We include these as raw strings (and not more readable variables) so they’re parsed by a bundler.
|
||||
if (extension === "md") {
|
||||
promise = EleventyImportFromEleventy("./src/Engines/Markdown.js");
|
||||
} else if (extension === "html") {
|
||||
promise = EleventyImportFromEleventy("./src/Engines/Html.js");
|
||||
} else if (extension === "njk") {
|
||||
promise = EleventyImportFromEleventy("./src/Engines/Nunjucks.js");
|
||||
} else if (extension === "liquid") {
|
||||
promise = EleventyImportFromEleventy("./src/Engines/Liquid.js");
|
||||
} else if (extension === "11ty.js") {
|
||||
promise = EleventyImportFromEleventy("./src/Engines/JavaScript.js");
|
||||
} else {
|
||||
promise = this.getCustomEngineClass();
|
||||
}
|
||||
|
||||
this.importCache[extension] = promise;
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
async getCustomEngineClass() {
|
||||
if (!this._CustomEngine) {
|
||||
this._CustomEngine = EleventyImportFromEleventy("./src/Engines/Custom.js");
|
||||
}
|
||||
return this._CustomEngine;
|
||||
}
|
||||
|
||||
async #getEngine(name, extensionMap) {
|
||||
let cls = await this.getEngineClassByExtension(name);
|
||||
let instance = new cls(name, this.eleventyConfig);
|
||||
instance.extensionMap = extensionMap;
|
||||
instance.engineManager = this;
|
||||
|
||||
let extensionEntry = extensionMap.getExtensionEntry(name);
|
||||
|
||||
// Override a built-in extension (md => md)
|
||||
// If provided a "Custom" engine using addExtension, but that engine's instance is *not* custom,
|
||||
// The user must be overriding a built-in engine i.e. addExtension('md', { ...overrideBehavior })
|
||||
let className = this.getClassNameFromTemplateKey(name);
|
||||
|
||||
if (className === "Custom" && instance.constructor.name !== "CustomEngine") {
|
||||
let CustomEngine = await this.getCustomEngineClass();
|
||||
let overrideCustomEngine = new CustomEngine(name, this.eleventyConfig);
|
||||
|
||||
// Keep track of the "default" engine 11ty would normally use
|
||||
// This allows the user to access the default engine in their override
|
||||
overrideCustomEngine.setDefaultEngine(instance);
|
||||
|
||||
instance = overrideCustomEngine;
|
||||
// Alias to a built-in extension (11ty.tsx => 11ty.js)
|
||||
} else if (
|
||||
instance.constructor.name === "CustomEngine" &&
|
||||
TemplateEngineManager.isAlias(extensionEntry)
|
||||
) {
|
||||
// add defaultRenderer for complex aliases with their own compile functions.
|
||||
let originalEngineInstance = await this.getEngine(extensionEntry.key, extensionMap);
|
||||
instance.setDefaultEngine(originalEngineInstance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
async getEngine(name, extensionMap) {
|
||||
// Warning about engine deprecation
|
||||
if (this.isEngineRemovedFromCore(name)) {
|
||||
throw new Error(
|
||||
`Per the 11ty Community Survey (2023), the "${name}" template language was moved from core to an officially supported plugin in v3.0. These plugins live here: https://github.com/11ty/eleventy-plugin-template-languages and are documented on their respective template language docs at https://v3.11ty.dev/docs/languages/ You are also empowered to implement *any* template language yourself using https://v3.11ty.dev/docs/languages/custom/`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.hasEngine(name)) {
|
||||
throw new Error(`Template Engine ${name} does not exist in getEngine()`);
|
||||
}
|
||||
|
||||
// TODO these cached engines should be based on extensions not name, then we can remove the error in
|
||||
// "Double override (not aliases) throws an error" test in TemplateRenderCustomTest.js
|
||||
if (!this.engineCache[name]) {
|
||||
debug("Engine cache miss %o (should only happen once per type)", name);
|
||||
// Make sure cache key is based on name and not path
|
||||
// Custom class is used for all plugins, cache once per plugin
|
||||
this.engineCache[name] = this.#getEngine(name, extensionMap);
|
||||
}
|
||||
|
||||
return this.engineCache[name];
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateEngineManager;
|
||||
67
node_modules/@11ty/eleventy/src/Engines/Util/ContextAugmenter.js
generated
vendored
Normal file
67
node_modules/@11ty/eleventy/src/Engines/Util/ContextAugmenter.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
const DATA_KEYS = ["page", "eleventy"];
|
||||
|
||||
function augmentFunction(fn, options = {}) {
|
||||
let t = typeof fn;
|
||||
if (t !== "function") {
|
||||
throw new Error(
|
||||
"Invalid type passed to `augmentFunction`. A function was expected and received: " + t,
|
||||
);
|
||||
}
|
||||
|
||||
/** @this {object} */
|
||||
return function (...args) {
|
||||
let context = augmentObject(this || {}, options);
|
||||
return fn.call(context, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
function augmentObject(targetObject, options = {}) {
|
||||
options = Object.assign(
|
||||
{
|
||||
source: undefined, // where to copy from
|
||||
overwrite: true,
|
||||
lazy: false, // lazily fetch the property
|
||||
// getter: function() {},
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
for (let key of DATA_KEYS) {
|
||||
// Skip if overwrite: false and prop already exists on target
|
||||
if (!options.overwrite && targetObject[key]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (options.lazy) {
|
||||
let value;
|
||||
if (typeof options.getter == "function") {
|
||||
value = () => options.getter(key, options.source);
|
||||
} else {
|
||||
value = () => options.source?.[key];
|
||||
}
|
||||
|
||||
// lazy getter important for Liquid strictVariables support
|
||||
Object.defineProperty(targetObject, key, {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
let value;
|
||||
if (typeof options.getter == "function") {
|
||||
value = options.getter(key, options.source);
|
||||
} else {
|
||||
value = options.source?.[key];
|
||||
}
|
||||
|
||||
if (value) {
|
||||
targetObject[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targetObject;
|
||||
}
|
||||
|
||||
export { DATA_KEYS as augmentKeys, augmentFunction, augmentObject };
|
||||
9
node_modules/@11ty/eleventy/src/Errors/DuplicatePermalinkOutputError.js
generated
vendored
Normal file
9
node_modules/@11ty/eleventy/src/Errors/DuplicatePermalinkOutputError.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import EleventyBaseError from "./EleventyBaseError.js";
|
||||
|
||||
class DuplicatePermalinkOutputError extends EleventyBaseError {
|
||||
get removeDuplicateErrorStringFromOutput() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default DuplicatePermalinkOutputError;
|
||||
24
node_modules/@11ty/eleventy/src/Errors/EleventyBaseError.js
generated
vendored
Normal file
24
node_modules/@11ty/eleventy/src/Errors/EleventyBaseError.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* This class serves as basis for all Eleventy-specific errors.
|
||||
* @ignore
|
||||
*/
|
||||
class EleventyBaseError extends Error {
|
||||
/**
|
||||
* @param {string} message - The error message to display.
|
||||
* @param {unknown} [originalError] - The original error caught.
|
||||
*/
|
||||
constructor(message, originalError) {
|
||||
super(message);
|
||||
|
||||
this.name = this.constructor.name;
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
|
||||
if (originalError) {
|
||||
this.originalError = originalError;
|
||||
}
|
||||
}
|
||||
}
|
||||
export default EleventyBaseError;
|
||||
151
node_modules/@11ty/eleventy/src/Errors/EleventyErrorHandler.js
generated
vendored
Normal file
151
node_modules/@11ty/eleventy/src/Errors/EleventyErrorHandler.js
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
import util from "node:util";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import ConsoleLogger from "../Util/ConsoleLogger.js";
|
||||
import EleventyErrorUtil from "./EleventyErrorUtil.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:EleventyErrorHandler");
|
||||
|
||||
class EleventyErrorHandler {
|
||||
constructor() {
|
||||
this._isVerbose = true;
|
||||
}
|
||||
|
||||
get isVerbose() {
|
||||
return this._isVerbose;
|
||||
}
|
||||
|
||||
set isVerbose(verbose) {
|
||||
this._isVerbose = !!verbose;
|
||||
this.logger.isVerbose = !!verbose;
|
||||
}
|
||||
|
||||
get logger() {
|
||||
if (!this._logger) {
|
||||
this._logger = new ConsoleLogger();
|
||||
this._logger.isVerbose = this.isVerbose;
|
||||
}
|
||||
|
||||
return this._logger;
|
||||
}
|
||||
|
||||
set logger(logger) {
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
warn(e, msg) {
|
||||
if (msg) {
|
||||
this.initialMessage(msg, "warn", "yellow");
|
||||
}
|
||||
this.log(e, "warn");
|
||||
}
|
||||
|
||||
fatal(e, msg) {
|
||||
this.error(e, msg);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
once(type, e, msg) {
|
||||
if (e.__errorAlreadyLogged) {
|
||||
return;
|
||||
}
|
||||
|
||||
this[type || "error"](e, msg);
|
||||
|
||||
Object.defineProperty(e, "__errorAlreadyLogged", {
|
||||
value: true,
|
||||
});
|
||||
}
|
||||
|
||||
error(e, msg) {
|
||||
if (msg) {
|
||||
this.initialMessage(msg, "error", "red", true);
|
||||
}
|
||||
this.log(e, "error", undefined, true);
|
||||
}
|
||||
|
||||
static getTotalErrorCount(e) {
|
||||
let totalErrorCount = 0;
|
||||
let errorCountRef = e;
|
||||
while (errorCountRef) {
|
||||
totalErrorCount++;
|
||||
errorCountRef = errorCountRef.originalError;
|
||||
}
|
||||
return totalErrorCount;
|
||||
}
|
||||
|
||||
//https://nodejs.org/api/process.html
|
||||
log(e, type = "log", chalkColor = "", forceToConsole = false) {
|
||||
if (process.env.DEBUG) {
|
||||
debug("Full error object: %o", util.inspect(e, { showHidden: false, depth: null }));
|
||||
}
|
||||
|
||||
let showStack = true;
|
||||
if (e.skipOriginalStack) {
|
||||
showStack = false;
|
||||
}
|
||||
|
||||
let totalErrorCount = EleventyErrorHandler.getTotalErrorCount(e);
|
||||
let ref = e;
|
||||
let index = 1;
|
||||
while (ref) {
|
||||
let nextRef = ref.originalError;
|
||||
|
||||
// Nunjucks wraps errors and puts the original in error.cause
|
||||
if (nextRef?.cause?.originalError) {
|
||||
nextRef = nextRef.cause.originalError;
|
||||
}
|
||||
|
||||
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()}${ref.name !== "Error" ? ` (via ${ref.name})` : ""}`,
|
||||
type,
|
||||
chalkColor,
|
||||
forceToConsole,
|
||||
);
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
debug(`(${type} stack): ${ref.stack}`);
|
||||
} else if (!nextRef) {
|
||||
// last error in the loop
|
||||
|
||||
// remove duplicate error messages if the stack contains the original message output above
|
||||
let stackStr = ref.stack || "";
|
||||
if (e.removeDuplicateErrorStringFromOutput) {
|
||||
stackStr = stackStr.replace(
|
||||
`${ref.name}: ${ref.message}`,
|
||||
"(Repeated output has been truncated…)",
|
||||
);
|
||||
}
|
||||
|
||||
if (showStack) {
|
||||
this.logger.message(
|
||||
"\nOriginal error stack trace: " + stackStr,
|
||||
type,
|
||||
chalkColor,
|
||||
forceToConsole,
|
||||
);
|
||||
}
|
||||
}
|
||||
ref = nextRef;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
initialMessage(message, type = "log", chalkColor = "blue", forceToConsole = false) {
|
||||
if (message) {
|
||||
this.logger.message(message + ":", type, chalkColor, forceToConsole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { EleventyErrorHandler };
|
||||
72
node_modules/@11ty/eleventy/src/Errors/EleventyErrorUtil.js
generated
vendored
Normal file
72
node_modules/@11ty/eleventy/src/Errors/EleventyErrorUtil.js
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
import TemplateContentPrematureUseError from "./TemplateContentPrematureUseError.js";
|
||||
|
||||
/* Hack to workaround the variety of error handling schemes in template languages */
|
||||
class EleventyErrorUtil {
|
||||
static get prefix() {
|
||||
return ">>>>>11ty>>>>>";
|
||||
}
|
||||
static get suffix() {
|
||||
return "<<<<<11ty<<<<<";
|
||||
}
|
||||
|
||||
static hasEmbeddedError(msg) {
|
||||
if (!msg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return msg.includes(EleventyErrorUtil.prefix) && msg.includes(EleventyErrorUtil.suffix);
|
||||
}
|
||||
|
||||
static cleanMessage(msg) {
|
||||
if (!msg) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!EleventyErrorUtil.hasEmbeddedError(msg)) {
|
||||
return "" + msg;
|
||||
}
|
||||
|
||||
return msg.slice(0, Math.max(0, msg.indexOf(EleventyErrorUtil.prefix)));
|
||||
}
|
||||
|
||||
static deconvertErrorToObject(error) {
|
||||
if (!error || !error.message) {
|
||||
throw new Error(`Could not convert error object from: ${error}`);
|
||||
}
|
||||
if (!EleventyErrorUtil.hasEmbeddedError(error.message)) {
|
||||
return error;
|
||||
}
|
||||
|
||||
let msg = error.message;
|
||||
let objectString = msg.substring(
|
||||
msg.indexOf(EleventyErrorUtil.prefix) + EleventyErrorUtil.prefix.length,
|
||||
msg.lastIndexOf(EleventyErrorUtil.suffix),
|
||||
);
|
||||
let obj = JSON.parse(objectString);
|
||||
obj.name = error.name;
|
||||
return obj;
|
||||
}
|
||||
|
||||
// pass an error through a random template engine’s error handling unscathed
|
||||
static convertErrorToString(error) {
|
||||
return (
|
||||
EleventyErrorUtil.prefix +
|
||||
JSON.stringify({ message: error.message, stack: error.stack }) +
|
||||
EleventyErrorUtil.suffix
|
||||
);
|
||||
}
|
||||
|
||||
static isPrematureTemplateContentError(e) {
|
||||
// TODO the rest of the template engines
|
||||
return (
|
||||
e instanceof TemplateContentPrematureUseError ||
|
||||
(e.originalError &&
|
||||
(e.originalError.name === "RenderError" ||
|
||||
e.originalError.name === "UndefinedVariableError") &&
|
||||
e.originalError.originalError instanceof TemplateContentPrematureUseError) || // Liquid
|
||||
(e.message || "").indexOf("TemplateContentPrematureUseError") > -1
|
||||
); // Nunjucks
|
||||
}
|
||||
}
|
||||
|
||||
export default EleventyErrorUtil;
|
||||
5
node_modules/@11ty/eleventy/src/Errors/TemplateContentPrematureUseError.js
generated
vendored
Normal file
5
node_modules/@11ty/eleventy/src/Errors/TemplateContentPrematureUseError.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import EleventyBaseError from "./EleventyBaseError.js";
|
||||
|
||||
class TemplateContentPrematureUseError extends EleventyBaseError {}
|
||||
|
||||
export default TemplateContentPrematureUseError;
|
||||
5
node_modules/@11ty/eleventy/src/Errors/TemplateContentUnrenderedTemplateError.js
generated
vendored
Normal file
5
node_modules/@11ty/eleventy/src/Errors/TemplateContentUnrenderedTemplateError.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import EleventyBaseError from "./EleventyBaseError.js";
|
||||
|
||||
class TemplateContentUnrenderedTemplateError extends EleventyBaseError {}
|
||||
|
||||
export default TemplateContentUnrenderedTemplateError;
|
||||
5
node_modules/@11ty/eleventy/src/Errors/UsingCircularTemplateContentReferenceError.js
generated
vendored
Normal file
5
node_modules/@11ty/eleventy/src/Errors/UsingCircularTemplateContentReferenceError.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import EleventyBaseError from "./EleventyBaseError.js";
|
||||
|
||||
class UsingCircularTemplateContentReferenceError extends EleventyBaseError {}
|
||||
|
||||
export default UsingCircularTemplateContentReferenceError;
|
||||
23
node_modules/@11ty/eleventy/src/EventBus.js
generated
vendored
Normal file
23
node_modules/@11ty/eleventy/src/EventBus.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import debugUtil from "debug";
|
||||
|
||||
import EventEmitter from "./Util/AsyncEventEmitter.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:EventBus");
|
||||
|
||||
/**
|
||||
* @module 11ty/eleventy/EventBus
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
debug("Setting up global EventBus.");
|
||||
/**
|
||||
* Provides a global event bus that modules deep down in the stack can
|
||||
* subscribe to from a global singleton for decoupled pub/sub.
|
||||
* @type {module:11ty/eleventy/Util/AsyncEventEmitter~AsyncEventEmitter}
|
||||
*/
|
||||
let bus = new EventEmitter();
|
||||
bus.setMaxListeners(100);
|
||||
|
||||
debug("EventBus max listener count: %o", bus.getMaxListeners());
|
||||
|
||||
export default bus;
|
||||
102
node_modules/@11ty/eleventy/src/FileSystemSearch.js
generated
vendored
Normal file
102
node_modules/@11ty/eleventy/src/FileSystemSearch.js
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
import fastglob from "fast-glob";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import { isGlobMatch } from "./Util/GlobMatcher.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:FastGlobManager");
|
||||
|
||||
class FileSystemSearch {
|
||||
constructor() {
|
||||
this.inputs = {};
|
||||
this.outputs = {};
|
||||
this.promises = {};
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
getCacheKey(key, globs, options) {
|
||||
if (Array.isArray(globs)) {
|
||||
globs = globs.sort();
|
||||
}
|
||||
return key + JSON.stringify(globs) + JSON.stringify(options);
|
||||
}
|
||||
|
||||
// returns a promise
|
||||
search(key, globs, options = {}) {
|
||||
debug("Glob search (%o) searching for: %o", key, globs);
|
||||
|
||||
if (!Array.isArray(globs)) {
|
||||
globs = [globs];
|
||||
}
|
||||
|
||||
// Strip leading slashes from everything!
|
||||
globs = globs.map((entry) => TemplatePath.stripLeadingDotSlash(entry));
|
||||
|
||||
if (options.ignore && Array.isArray(options.ignore)) {
|
||||
options.ignore = options.ignore.map((entry) => TemplatePath.stripLeadingDotSlash(entry));
|
||||
debug("Glob search (%o) ignoring: %o", key, options.ignore);
|
||||
}
|
||||
|
||||
let cacheKey = this.getCacheKey(key, globs, options);
|
||||
|
||||
// Only after the promise has resolved
|
||||
if (this.outputs[cacheKey]) {
|
||||
return Array.from(this.outputs[cacheKey]);
|
||||
}
|
||||
|
||||
if (!this.promises[cacheKey]) {
|
||||
this.inputs[cacheKey] = {
|
||||
input: globs,
|
||||
options,
|
||||
};
|
||||
|
||||
this.count++;
|
||||
|
||||
this.promises[cacheKey] = fastglob(
|
||||
globs,
|
||||
Object.assign(
|
||||
{
|
||||
caseSensitiveMatch: false, // insensitive
|
||||
dot: true,
|
||||
},
|
||||
options,
|
||||
),
|
||||
).then((results) => {
|
||||
this.outputs[cacheKey] = new Set(
|
||||
results.map((entry) => TemplatePath.addLeadingDotSlash(entry)),
|
||||
);
|
||||
return Array.from(this.outputs[cacheKey]);
|
||||
});
|
||||
}
|
||||
|
||||
// may be an unresolved promise
|
||||
return this.promises[cacheKey];
|
||||
}
|
||||
|
||||
_modify(path, setOperation) {
|
||||
path = TemplatePath.stripLeadingDotSlash(path);
|
||||
|
||||
let normalized = TemplatePath.addLeadingDotSlash(path);
|
||||
|
||||
for (let key in this.inputs) {
|
||||
let { input, options } = this.inputs[key];
|
||||
if (
|
||||
isGlobMatch(path, input, {
|
||||
ignore: options.ignore,
|
||||
})
|
||||
) {
|
||||
this.outputs[key][setOperation](normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(path) {
|
||||
this._modify(path, "add");
|
||||
}
|
||||
|
||||
delete(path) {
|
||||
this._modify(path, "delete");
|
||||
}
|
||||
}
|
||||
|
||||
export default FileSystemSearch;
|
||||
20
node_modules/@11ty/eleventy/src/Filters/GetCollectionItem.js
generated
vendored
Normal file
20
node_modules/@11ty/eleventy/src/Filters/GetCollectionItem.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
export default function getCollectionItem(collection, page, modifier = 0) {
|
||||
let j = 0;
|
||||
let index;
|
||||
for (let item of collection) {
|
||||
if (
|
||||
item.inputPath === page.inputPath &&
|
||||
(item.outputPath === page.outputPath || item.url === page.url)
|
||||
) {
|
||||
index = j;
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
if (index !== undefined && collection?.length) {
|
||||
if (index + modifier >= 0 && index + modifier < collection.length) {
|
||||
return collection[index + modifier];
|
||||
}
|
||||
}
|
||||
}
|
||||
17
node_modules/@11ty/eleventy/src/Filters/GetCollectionItemIndex.js
generated
vendored
Normal file
17
node_modules/@11ty/eleventy/src/Filters/GetCollectionItemIndex.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// TODO locale-friendly, see GetLocaleCollectionItem.js)
|
||||
export default function getCollectionItemIndex(collection, page) {
|
||||
if (!page) {
|
||||
page = this.page;
|
||||
}
|
||||
|
||||
let j = 0;
|
||||
for (let item of collection) {
|
||||
if (
|
||||
item.inputPath === page.inputPath &&
|
||||
(item.outputPath === page.outputPath || item.url === page.url)
|
||||
) {
|
||||
return j;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
47
node_modules/@11ty/eleventy/src/Filters/GetLocaleCollectionItem.js
generated
vendored
Normal file
47
node_modules/@11ty/eleventy/src/Filters/GetLocaleCollectionItem.js
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import getCollectionItem from "./GetCollectionItem.js";
|
||||
|
||||
// Work with I18n Plugin src/Plugins/I18nPlugin.js to retrieve root pages (not i18n pages)
|
||||
function resolveRootPage(config, pageOverride, languageCode) {
|
||||
let localeFilter = config.getFilter("locale_page");
|
||||
if (!localeFilter || typeof localeFilter !== "function") {
|
||||
return pageOverride;
|
||||
}
|
||||
|
||||
// returns root/default-language `page` object
|
||||
return localeFilter.call(this, pageOverride, languageCode);
|
||||
}
|
||||
|
||||
function getLocaleCollectionItem(config, collection, pageOverride, langCode, indexModifier = 0) {
|
||||
if (!langCode) {
|
||||
// if page.lang exists (2.0.0-canary.14 and i18n plugin added, use page language)
|
||||
if (this.page.lang) {
|
||||
langCode = this.page.lang;
|
||||
} else {
|
||||
return getCollectionItem(collection, pageOverride || this.page, indexModifier);
|
||||
}
|
||||
}
|
||||
|
||||
let rootPage = resolveRootPage.call(this, config, pageOverride); // implied current page, default language
|
||||
let modifiedRootItem = getCollectionItem(collection, rootPage, indexModifier);
|
||||
if (!modifiedRootItem) {
|
||||
return; // no root item exists for the previous/next page
|
||||
}
|
||||
|
||||
// Resolve modified root `page` back to locale `page`
|
||||
// This will return a non localized version of the page as a fallback
|
||||
let modifiedLocalePage = resolveRootPage.call(this, config, modifiedRootItem.data.page, langCode);
|
||||
// already localized (or default language)
|
||||
if (!("__locale_page_resolved" in modifiedLocalePage)) {
|
||||
return modifiedRootItem;
|
||||
}
|
||||
|
||||
// find the modified locale `page` again in `collections.all`
|
||||
let all =
|
||||
this.collections?.all ||
|
||||
this.ctx?.collections?.all ||
|
||||
this.context?.environments?.collections?.all ||
|
||||
[];
|
||||
return getCollectionItem(all, modifiedLocalePage, 0);
|
||||
}
|
||||
|
||||
export default getLocaleCollectionItem;
|
||||
14
node_modules/@11ty/eleventy/src/Filters/Slug.js
generated
vendored
Normal file
14
node_modules/@11ty/eleventy/src/Filters/Slug.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import slugify from "slugify";
|
||||
|
||||
export default function (str, options = {}) {
|
||||
return slugify(
|
||||
"" + str,
|
||||
Object.assign(
|
||||
{
|
||||
replacement: "-",
|
||||
lower: true,
|
||||
},
|
||||
options,
|
||||
),
|
||||
);
|
||||
}
|
||||
14
node_modules/@11ty/eleventy/src/Filters/Slugify.js
generated
vendored
Normal file
14
node_modules/@11ty/eleventy/src/Filters/Slugify.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
|
||||
export default function (str, options = {}) {
|
||||
return slugify(
|
||||
"" + str,
|
||||
Object.assign(
|
||||
{
|
||||
// lowercase: true, // default
|
||||
decamelize: false,
|
||||
},
|
||||
options,
|
||||
),
|
||||
);
|
||||
}
|
||||
35
node_modules/@11ty/eleventy/src/Filters/Url.js
generated
vendored
Normal file
35
node_modules/@11ty/eleventy/src/Filters/Url.js
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import isValidUrl from "../Util/ValidUrl.js";
|
||||
|
||||
// Note: This filter is used in the Eleventy Navigation plugin in versions prior to 0.3.4
|
||||
export default function (url, pathPrefix) {
|
||||
// work with undefined
|
||||
url = url || "";
|
||||
|
||||
if (isValidUrl(url) || (url.startsWith("//") && url !== "//")) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (pathPrefix === undefined || typeof pathPrefix !== "string") {
|
||||
// When you retrieve this with config.getFilter("url") it
|
||||
// grabs the pathPrefix argument from your config for you (see defaultConfig.js)
|
||||
throw new Error("pathPrefix (String) is required in the `url` filter.");
|
||||
}
|
||||
|
||||
let normUrl = TemplatePath.normalizeUrlPath(url);
|
||||
let normRootDir = TemplatePath.normalizeUrlPath("/", pathPrefix);
|
||||
let normFull = TemplatePath.normalizeUrlPath("/", pathPrefix, url);
|
||||
let isRootDirTrailingSlash =
|
||||
normRootDir.length && normRootDir.charAt(normRootDir.length - 1) === "/";
|
||||
|
||||
// minor difference with straight `normalize`, "" resolves to root dir and not "."
|
||||
// minor difference with straight `normalize`, "/" resolves to root dir
|
||||
if (normUrl === "/" || normUrl === normRootDir) {
|
||||
return normRootDir + (!isRootDirTrailingSlash ? "/" : "");
|
||||
} else if (normUrl.indexOf("/") === 0) {
|
||||
return normFull;
|
||||
}
|
||||
|
||||
return normUrl;
|
||||
}
|
||||
424
node_modules/@11ty/eleventy/src/GlobalDependencyMap.js
generated
vendored
Normal file
424
node_modules/@11ty/eleventy/src/GlobalDependencyMap.js
generated
vendored
Normal file
@@ -0,0 +1,424 @@
|
||||
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");
|
||||
|
||||
class GlobalDependencyMap {
|
||||
// dependency-graph requires these keys to be alphabetic strings
|
||||
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;
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
|
||||
// These have leading dot slashes, but so do the paths from Eleventy
|
||||
this.config.events.once("eleventy.layouts", async (layouts) => {
|
||||
await this.addLayoutsToMap(layouts);
|
||||
});
|
||||
}
|
||||
|
||||
filterOutLayouts(nodes = []) {
|
||||
return nodes.filter((node) => {
|
||||
let data = this.map.getNodeData(node);
|
||||
if (data?.type === GlobalDependencyMap.LAYOUT_KEY) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
filterOutCollections(nodes = []) {
|
||||
return nodes.filter((node) => !node.startsWith(GlobalDependencyMap.COLLECTION_PREFIX));
|
||||
}
|
||||
|
||||
removeLayoutNodes(normalizedLayouts) {
|
||||
let nodes = this.map.overallOrder();
|
||||
for (let node of nodes) {
|
||||
let data = this.map.getNodeData(node);
|
||||
if (!data || !data.type || data.type !== GlobalDependencyMap.LAYOUT_KEY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// previous layout is not in the new layout map (no templates are using it)
|
||||
if (!normalizedLayouts[node]) {
|
||||
this.map.removeNode(node);
|
||||
}
|
||||
// important: if the layout map changed to have different templates (but was not removed)
|
||||
// this is already handled by `resetNode` called via TemplateMap
|
||||
}
|
||||
}
|
||||
|
||||
// Eleventy Layouts don’t show up in the dependency graph, so we handle those separately
|
||||
async addLayoutsToMap(layouts) {
|
||||
let normalizedLayouts = this.normalizeLayoutsObject(layouts);
|
||||
|
||||
// Clear out any previous layout relationships to make way for the new ones
|
||||
this.removeLayoutNodes(normalizedLayouts);
|
||||
|
||||
for (let layout in normalizedLayouts) {
|
||||
// We add this pre-emptively to add the `layout` data
|
||||
if (!this.map.hasNode(layout)) {
|
||||
this.map.addNode(layout, {
|
||||
type: GlobalDependencyMap.LAYOUT_KEY,
|
||||
});
|
||||
} else {
|
||||
this.map.setNodeData(layout, {
|
||||
type: GlobalDependencyMap.LAYOUT_KEY,
|
||||
});
|
||||
}
|
||||
|
||||
// Potential improvement: only add the first template in the chain for a template and manage any upstream layouts by their own relationships
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get map() {
|
||||
if (!this._map) {
|
||||
this._map = new DepGraph({ circular: true });
|
||||
}
|
||||
|
||||
return this._map;
|
||||
}
|
||||
|
||||
set map(graph) {
|
||||
this._map = graph;
|
||||
}
|
||||
|
||||
normalizeNode(node) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO tests for this
|
||||
// Fix URL objects passed in (sass does this)
|
||||
if (typeof node !== "string" && "toString" in node) {
|
||||
node = node.toString();
|
||||
}
|
||||
|
||||
if (typeof node !== "string") {
|
||||
throw new Error("`addDependencies` files must be strings. Received:" + node);
|
||||
}
|
||||
|
||||
return PathNormalizer.fullNormalization(node);
|
||||
}
|
||||
|
||||
normalizeLayoutsObject(layouts) {
|
||||
let o = {};
|
||||
for (let rawLayout in layouts) {
|
||||
let layout = this.normalizeNode(rawLayout);
|
||||
o[layout] = layouts[rawLayout].map((entry) => this.normalizeNode(entry));
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
getDependantsFor(node) {
|
||||
if (!node) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
node = this.normalizeNode(node);
|
||||
|
||||
if (!this.map.hasNode(node)) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
// Direct dependants and dependencies, both publish and consume from collections
|
||||
return this.map.directDependantsOf(node);
|
||||
}
|
||||
|
||||
hasNode(node) {
|
||||
return this.map.hasNode(this.normalizeNode(node));
|
||||
}
|
||||
|
||||
findCollectionsRemovedFrom(node, collectionNames) {
|
||||
if (!this.hasNode(node)) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
let prevDeps = this.getDependantsFor(node)
|
||||
.filter((entry) => {
|
||||
return entry.startsWith(GlobalDependencyMap.COLLECTION_PREFIX);
|
||||
})
|
||||
.map((entry) => {
|
||||
return GlobalDependencyMap.getEntryFromCollectionKey(entry);
|
||||
});
|
||||
|
||||
let prevDepsSet = new Set(prevDeps);
|
||||
let deleted = new Set();
|
||||
for (let dep of prevDepsSet) {
|
||||
if (!collectionNames.has(dep)) {
|
||||
deleted.add(dep);
|
||||
}
|
||||
}
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
resetNode(node) {
|
||||
node = this.normalizeNode(node);
|
||||
|
||||
if (!this.map.hasNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We don’t want to remove relationships that consume this, controlled by the upstream content
|
||||
// for (let dep of this.map.directDependantsOf(node)) {
|
||||
// this.map.removeDependency(dep, node);
|
||||
// }
|
||||
|
||||
for (let dep of this.map.directDependenciesOf(node)) {
|
||||
this.map.removeDependency(node, dep);
|
||||
}
|
||||
}
|
||||
|
||||
getTemplatesThatConsumeCollections(collectionNames) {
|
||||
let templates = new Set();
|
||||
for (let name of collectionNames) {
|
||||
let collectionName = GlobalDependencyMap.getCollectionKeyForEntry(name);
|
||||
if (!this.map.hasNode(collectionName)) {
|
||||
continue;
|
||||
}
|
||||
for (let node of this.map.dependantsOf(collectionName)) {
|
||||
if (!node.startsWith(GlobalDependencyMap.COLLECTION_PREFIX)) {
|
||||
let data = this.map.getNodeData(node);
|
||||
if (!data || !data.type || data.type != GlobalDependencyMap.LAYOUT_KEY) {
|
||||
templates.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
getLayoutsUsedBy(node) {
|
||||
node = this.normalizeNode(node);
|
||||
|
||||
if (!this.map.hasNode(node)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let layouts = [];
|
||||
|
||||
// include self, if layout
|
||||
if (this.map.getNodeData(node)?.type === GlobalDependencyMap.LAYOUT_KEY) {
|
||||
layouts.push(node);
|
||||
}
|
||||
|
||||
this.map.dependantsOf(node).forEach((node) => {
|
||||
let data = this.map.getNodeData(node);
|
||||
// we only want layouts
|
||||
if (data?.type === GlobalDependencyMap.LAYOUT_KEY) {
|
||||
return layouts.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
return layouts;
|
||||
}
|
||||
|
||||
// In order
|
||||
// Does not include original templatePaths (unless *they* are second-order relevant)
|
||||
getTemplatesRelevantToTemplateList(templatePaths) {
|
||||
let overallOrder = this.map.overallOrder();
|
||||
overallOrder = this.filterOutLayouts(overallOrder);
|
||||
overallOrder = this.filterOutCollections(overallOrder);
|
||||
|
||||
let relevantLookup = {};
|
||||
for (let inputPath of templatePaths) {
|
||||
inputPath = TemplatePath.stripLeadingDotSlash(inputPath);
|
||||
|
||||
let deps = this.getDependencies(inputPath, false);
|
||||
|
||||
if (Array.isArray(deps)) {
|
||||
let paths = this.filterOutCollections(deps);
|
||||
for (let node of paths) {
|
||||
relevantLookup[node] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return overallOrder.filter((node) => {
|
||||
if (relevantLookup[node]) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Layouts are not relevant to compile cache and can be ignored
|
||||
getDependencies(node, includeLayouts = true) {
|
||||
node = this.normalizeNode(node);
|
||||
|
||||
// `false` means the Node was unknown
|
||||
if (!this.map.hasNode(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.map.dependenciesOf(node).filter((node) => {
|
||||
if (includeLayouts) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// When includeLayouts is `false` we want to filter out layouts
|
||||
let data = this.map.getNodeData(node);
|
||||
if (data?.type === GlobalDependencyMap.LAYOUT_KEY) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// node arguments are already normalized
|
||||
_addDependency(from, toArray = []) {
|
||||
this.map.addNode(from);
|
||||
|
||||
if (!Array.isArray(toArray)) {
|
||||
throw new Error("Second argument to `addDependency` must be an Array.");
|
||||
}
|
||||
|
||||
// debug("%o depends on %o", from, toArray);
|
||||
|
||||
for (let to of toArray) {
|
||||
if (!this.map.hasNode(to)) {
|
||||
this.map.addNode(to);
|
||||
}
|
||||
if (from !== to) {
|
||||
this.map.addDependency(from, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addDependency(from, toArray = []) {
|
||||
this._addDependency(
|
||||
this.normalizeNode(from),
|
||||
toArray.map((to) => this.normalizeNode(to)),
|
||||
);
|
||||
}
|
||||
|
||||
static getEntryFromCollectionKey(entry) {
|
||||
return entry.slice(GlobalDependencyMap.COLLECTION_PREFIX.length);
|
||||
}
|
||||
|
||||
static getCollectionKeyForEntry(entry) {
|
||||
return `${GlobalDependencyMap.COLLECTION_PREFIX}${entry}`;
|
||||
}
|
||||
|
||||
addDependencyConsumesCollection(from, collectionName) {
|
||||
let nodeName = this.normalizeNode(from);
|
||||
debug("%o depends on collection: %o", nodeName, collectionName);
|
||||
this._addDependency(nodeName, [GlobalDependencyMap.getCollectionKeyForEntry(collectionName)]);
|
||||
}
|
||||
|
||||
addDependencyPublishesToCollection(from, collectionName) {
|
||||
let normalizedFrom = this.normalizeNode(from);
|
||||
this._addDependency(GlobalDependencyMap.getCollectionKeyForEntry(collectionName), [
|
||||
normalizedFrom,
|
||||
]);
|
||||
}
|
||||
|
||||
// Layouts are not relevant to compile cache and can be ignored
|
||||
hasDependency(from, to, includeLayouts) {
|
||||
to = this.normalizeNode(to);
|
||||
|
||||
let deps = this.getDependencies(from, includeLayouts); // normalizes `from`
|
||||
|
||||
if (!deps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return deps.includes(to);
|
||||
}
|
||||
|
||||
// Layouts are not relevant to compile cache and can be ignored
|
||||
isFileRelevantTo(fullTemplateInputPath, comparisonFile, includeLayouts) {
|
||||
fullTemplateInputPath = this.normalizeNode(fullTemplateInputPath);
|
||||
comparisonFile = this.normalizeNode(comparisonFile);
|
||||
|
||||
// No watch/serve changed file
|
||||
if (!comparisonFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The file that changed is the relevant file
|
||||
if (fullTemplateInputPath === comparisonFile) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The file that changed is a dependency of the template
|
||||
// comparisonFile is used by fullTemplateInputPath
|
||||
if (this.hasDependency(fullTemplateInputPath, comparisonFile, includeLayouts)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
isFileUsedBy(parent, child, includeLayouts) {
|
||||
if (this.hasDependency(parent, child, includeLayouts)) {
|
||||
// child is used by parent
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
stringify() {
|
||||
return JSON.stringify(this.map, function replacer(key, value) {
|
||||
// Serialize internal Map objects.
|
||||
if (value instanceof Map) {
|
||||
let obj = {};
|
||||
for (let [k, v] of value) {
|
||||
obj[k] = v;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
restore(persisted) {
|
||||
let obj = JSON.parse(persisted);
|
||||
let graph = new DepGraph({ circular: true });
|
||||
|
||||
// https://github.com/jriecken/dependency-graph/issues/44
|
||||
// Restore top level serialized Map objects (in stringify above)
|
||||
for (let key in obj) {
|
||||
let map = graph[key];
|
||||
for (let k in obj[key]) {
|
||||
let v = obj[key][k];
|
||||
map.set(k, v);
|
||||
}
|
||||
}
|
||||
this.map = graph;
|
||||
}
|
||||
}
|
||||
|
||||
export default GlobalDependencyMap;
|
||||
151
node_modules/@11ty/eleventy/src/Plugins/HtmlBasePlugin.js
generated
vendored
Normal file
151
node_modules/@11ty/eleventy/src/Plugins/HtmlBasePlugin.js
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
import { DeepCopy } from "@11ty/eleventy-utils";
|
||||
import urlFilter from "../Filters/Url.js";
|
||||
import PathPrefixer from "../Util/PathPrefixer.js";
|
||||
import { HtmlTransformer } from "../Util/HtmlTransformer.js";
|
||||
import isValidUrl from "../Util/ValidUrl.js";
|
||||
|
||||
function addPathPrefixToUrl(url, pathPrefix, base) {
|
||||
let u;
|
||||
if (base) {
|
||||
u = new URL(url, base);
|
||||
} else {
|
||||
u = new URL(url);
|
||||
}
|
||||
|
||||
// Add pathPrefix **after** url is transformed using base
|
||||
if (pathPrefix) {
|
||||
u.pathname = PathPrefixer.joinUrlParts(pathPrefix, u.pathname);
|
||||
}
|
||||
return u.toString();
|
||||
}
|
||||
|
||||
// pathprefix is only used when overrideBase is a full URL
|
||||
function transformUrl(url, base, opts = {}) {
|
||||
let { pathPrefix, pageUrl } = opts;
|
||||
|
||||
// full URL, return as-is
|
||||
if (isValidUrl(url)) {
|
||||
return url;
|
||||
}
|
||||
|
||||
// Not a full URL, but with a full base URL
|
||||
// e.g. relative urls like "subdir/", "../subdir", "./subdir"
|
||||
if (isValidUrl(base)) {
|
||||
// convert relative paths to absolute path first using pageUrl
|
||||
if (pageUrl && !url.startsWith("/")) {
|
||||
let urlObj = new URL(url, `http://example.com${pageUrl}`);
|
||||
url = urlObj.pathname + (urlObj.hash || "");
|
||||
}
|
||||
|
||||
return addPathPrefixToUrl(url, pathPrefix, base);
|
||||
}
|
||||
|
||||
// Not a full URL, nor a full base URL (call the built-in `url` filter)
|
||||
return urlFilter(url, base);
|
||||
}
|
||||
|
||||
function eleventyHtmlBasePlugin(eleventyConfig, defaultOptions = {}) {
|
||||
let opts = DeepCopy(
|
||||
{
|
||||
// eleventyConfig.pathPrefix is new in Eleventy 2.0.0-canary.15
|
||||
// `base` can be a directory (for path prefix transformations)
|
||||
// OR a full URL with origin and pathname
|
||||
baseHref: eleventyConfig.pathPrefix,
|
||||
|
||||
extensions: "html",
|
||||
},
|
||||
defaultOptions,
|
||||
);
|
||||
|
||||
// `filters` option to rename filters was removed in 3.0.0-alpha.13
|
||||
// Renaming these would cause issues in other plugins (e.g. RSS)
|
||||
if (opts.filters !== undefined) {
|
||||
throw new Error(
|
||||
"The `filters` option in the HTML Base plugin was removed to prevent future cross-plugin compatibility issues.",
|
||||
);
|
||||
}
|
||||
|
||||
if (opts.baseHref === undefined) {
|
||||
throw new Error("The `base` option is required in the HTML Base plugin.");
|
||||
}
|
||||
|
||||
eleventyConfig.addFilter("addPathPrefixToFullUrl", function (url) {
|
||||
return addPathPrefixToUrl(url, eleventyConfig.pathPrefix);
|
||||
});
|
||||
|
||||
// Apply to one URL
|
||||
eleventyConfig.addFilter(
|
||||
"htmlBaseUrl",
|
||||
|
||||
/** @this {object} */
|
||||
function (url, baseOverride, pageUrlOverride) {
|
||||
let base = baseOverride || opts.baseHref;
|
||||
|
||||
// Do nothing with a default base
|
||||
if (base === "/") {
|
||||
return url;
|
||||
}
|
||||
|
||||
return transformUrl(url, base, {
|
||||
pathPrefix: eleventyConfig.pathPrefix,
|
||||
pageUrl: pageUrlOverride || this.page?.url,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Apply to a block of HTML
|
||||
eleventyConfig.addAsyncFilter(
|
||||
"transformWithHtmlBase",
|
||||
|
||||
/** @this {object} */
|
||||
function (content, baseOverride, pageUrlOverride) {
|
||||
let base = baseOverride || opts.baseHref;
|
||||
|
||||
// Do nothing with a default base
|
||||
if (base === "/") {
|
||||
return content;
|
||||
}
|
||||
|
||||
return HtmlTransformer.transformStandalone(content, (url) => {
|
||||
return transformUrl(url.trim(), base, {
|
||||
pathPrefix: eleventyConfig.pathPrefix,
|
||||
pageUrl: pageUrlOverride || this.page?.url,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Apply to all HTML output in your project
|
||||
eleventyConfig.htmlTransformer.addUrlTransform(
|
||||
opts.extensions,
|
||||
|
||||
/** @this {object} */
|
||||
function (urlInMarkup) {
|
||||
// baseHref override is via renderTransforms filter for adding the absolute URL (e.g. https://example.com/pathPrefix/) for RSS/Atom/JSON feeds
|
||||
return transformUrl(urlInMarkup.trim(), this.baseHref || opts.baseHref, {
|
||||
pathPrefix: eleventyConfig.pathPrefix,
|
||||
pageUrl: this.url,
|
||||
});
|
||||
},
|
||||
{
|
||||
priority: -1, // run last (especially after PathToUrl transform)
|
||||
enabled: function (context) {
|
||||
// Enabled when pathPrefix is non-default or via renderTransforms
|
||||
return context.baseHref || opts.baseHref !== "/";
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Object.defineProperty(eleventyHtmlBasePlugin, "eleventyPackage", {
|
||||
value: "@11ty/eleventy/html-base-plugin",
|
||||
});
|
||||
|
||||
Object.defineProperty(eleventyHtmlBasePlugin, "eleventyPluginOptions", {
|
||||
value: {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default eleventyHtmlBasePlugin;
|
||||
export { transformUrl as applyBaseToUrl };
|
||||
317
node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js
generated
vendored
Normal file
317
node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
import { bcp47Normalize } from "bcp-47-normalize";
|
||||
import iso639 from "iso-639-1";
|
||||
import { DeepCopy } from "@11ty/eleventy-utils";
|
||||
|
||||
// pathPrefix note:
|
||||
// When using `locale_url` filter with the `url` filter, `locale_url` must run first like
|
||||
// `| locale_url | url`. If you run `| url | locale_url` it won’t match correctly.
|
||||
|
||||
// TODO improvement would be to throw an error if `locale_url` finds a url with the
|
||||
// path prefix at the beginning? Would need a better way to know `url` has transformed a string
|
||||
// rather than just raw comparison.
|
||||
// e.g. --pathprefix=/en/ should return `/en/en/` for `/en/index.liquid`
|
||||
|
||||
class LangUtils {
|
||||
static getLanguageCodeFromInputPath(filepath) {
|
||||
return (filepath || "").split("/").find((entry) => Comparator.isLangCode(entry));
|
||||
}
|
||||
|
||||
static getLanguageCodeFromUrl(url) {
|
||||
let s = (url || "").split("/");
|
||||
return s.length > 0 && Comparator.isLangCode(s[1]) ? s[1] : "";
|
||||
}
|
||||
|
||||
static swapLanguageCodeNoCheck(str, langCode) {
|
||||
let found = false;
|
||||
return str
|
||||
.split("/")
|
||||
.map((entry) => {
|
||||
// only match the first one
|
||||
if (!found && Comparator.isLangCode(entry)) {
|
||||
found = true;
|
||||
return langCode;
|
||||
}
|
||||
return entry;
|
||||
})
|
||||
.join("/");
|
||||
}
|
||||
|
||||
static swapLanguageCode(str, langCode) {
|
||||
if (!Comparator.isLangCode(langCode)) {
|
||||
return str;
|
||||
}
|
||||
|
||||
return LangUtils.swapLanguageCodeNoCheck(str, langCode);
|
||||
}
|
||||
}
|
||||
|
||||
class Comparator {
|
||||
// https://en.wikipedia.org/wiki/IETF_language_tag#Relation_to_other_standards
|
||||
// Requires a ISO-639-1 language code at the start (2 characters before the first -)
|
||||
static isLangCode(code) {
|
||||
let [s] = (code || "").split("-");
|
||||
if (!iso639.validate(s)) {
|
||||
return false;
|
||||
}
|
||||
if (!bcp47Normalize(code)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static urlHasLangCode(url, code) {
|
||||
if (!Comparator.isLangCode(code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return url.split("/").some((entry) => entry === code);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeInputPath(inputPath, extensionMap) {
|
||||
if (extensionMap) {
|
||||
return extensionMap.removeTemplateExtension(inputPath);
|
||||
}
|
||||
return inputPath;
|
||||
}
|
||||
|
||||
/*
|
||||
* Input: {
|
||||
* '/en-us/test/': './test/stubs-i18n/en-us/test.11ty.js',
|
||||
* '/en/test/': './test/stubs-i18n/en/test.liquid',
|
||||
* '/es/test/': './test/stubs-i18n/es/test.njk',
|
||||
* '/non-lang-file/': './test/stubs-i18n/non-lang-file.njk'
|
||||
* }
|
||||
*
|
||||
* Output: {
|
||||
* '/en-us/test/': [ { url: '/en/test/' }, { url: '/es/test/' } ],
|
||||
* '/en/test/': [ { url: '/en-us/test/' }, { url: '/es/test/' } ],
|
||||
* '/es/test/': [ { url: '/en-us/test/' }, { url: '/en/test/' } ]
|
||||
* }
|
||||
*/
|
||||
function getLocaleUrlsMap(urlToInputPath, extensionMap, options = {}) {
|
||||
let filemap = {};
|
||||
|
||||
for (let url in urlToInputPath) {
|
||||
// Group number comes from Pagination.js
|
||||
let { inputPath: originalFilepath, groupNumber } = urlToInputPath[url];
|
||||
let filepath = normalizeInputPath(originalFilepath, extensionMap);
|
||||
let replaced =
|
||||
LangUtils.swapLanguageCodeNoCheck(filepath, "__11ty_i18n") + `_group:${groupNumber}`;
|
||||
|
||||
if (!filemap[replaced]) {
|
||||
filemap[replaced] = [];
|
||||
}
|
||||
|
||||
let langCode = LangUtils.getLanguageCodeFromInputPath(originalFilepath);
|
||||
if (!langCode) {
|
||||
langCode = LangUtils.getLanguageCodeFromUrl(url);
|
||||
}
|
||||
if (!langCode) {
|
||||
langCode = options.defaultLanguage;
|
||||
}
|
||||
|
||||
if (langCode) {
|
||||
filemap[replaced].push({
|
||||
url,
|
||||
lang: langCode,
|
||||
label: iso639.getNativeName(langCode.split("-")[0]),
|
||||
});
|
||||
} else {
|
||||
filemap[replaced].push({ url });
|
||||
}
|
||||
}
|
||||
|
||||
// Default sorted by lang code
|
||||
for (let key in filemap) {
|
||||
filemap[key].sort(function (a, b) {
|
||||
if (a.lang < b.lang) {
|
||||
return -1;
|
||||
}
|
||||
if (a.lang > b.lang) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
// map of input paths => array of localized urls
|
||||
let urlMap = {};
|
||||
for (let filepath in filemap) {
|
||||
for (let entry of filemap[filepath]) {
|
||||
let url = entry.url;
|
||||
if (!urlMap[url]) {
|
||||
urlMap[url] = filemap[filepath].filter((entry) => {
|
||||
if (entry.lang) {
|
||||
return true;
|
||||
}
|
||||
return entry.url !== url;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return urlMap;
|
||||
}
|
||||
|
||||
function eleventyI18nPlugin(eleventyConfig, opts = {}) {
|
||||
let options = DeepCopy(
|
||||
{
|
||||
defaultLanguage: "",
|
||||
filters: {
|
||||
url: "locale_url",
|
||||
links: "locale_links",
|
||||
},
|
||||
errorMode: "strict", // allow-fallback, never
|
||||
},
|
||||
opts,
|
||||
);
|
||||
|
||||
if (!options.defaultLanguage) {
|
||||
throw new Error(
|
||||
"You must specify a `defaultLanguage` in Eleventy’s Internationalization (I18N) plugin.",
|
||||
);
|
||||
}
|
||||
|
||||
let extensionMap;
|
||||
eleventyConfig.on("eleventy.extensionmap", (map) => {
|
||||
extensionMap = map;
|
||||
});
|
||||
|
||||
let bench = eleventyConfig.benchmarkManager.get("Aggregate");
|
||||
let contentMaps = {};
|
||||
eleventyConfig.on("eleventy.contentMap", function ({ urlToInputPath, inputPathToUrl }) {
|
||||
let b = bench.get("(i18n Plugin) Setting up content map.");
|
||||
b.before();
|
||||
contentMaps.inputPathToUrl = inputPathToUrl;
|
||||
contentMaps.urlToInputPath = urlToInputPath;
|
||||
|
||||
contentMaps.localeUrlsMap = getLocaleUrlsMap(urlToInputPath, extensionMap, options);
|
||||
b.after();
|
||||
});
|
||||
|
||||
eleventyConfig.addGlobalData("eleventyComputed.page.lang", () => {
|
||||
// if addGlobalData receives a function it will execute it immediately,
|
||||
// so we return a nested function for computed data
|
||||
return (data) => {
|
||||
return LangUtils.getLanguageCodeFromUrl(data.page.url) || options.defaultLanguage;
|
||||
};
|
||||
});
|
||||
|
||||
// Normalize a theoretical URL based on the current page’s language
|
||||
// If a non-localized file exists, returns the URL without a language assigned
|
||||
// Fails if no file exists (localized and not localized)
|
||||
eleventyConfig.addFilter(options.filters.url, function (url, langCodeOverride) {
|
||||
let langCode =
|
||||
langCodeOverride ||
|
||||
LangUtils.getLanguageCodeFromUrl(this.page?.url) ||
|
||||
options.defaultLanguage;
|
||||
|
||||
// Already has a language code on it and has a relevant url with the target language code
|
||||
if (
|
||||
contentMaps.localeUrlsMap[url] ||
|
||||
(!url.endsWith("/") && contentMaps.localeUrlsMap[`${url}/`])
|
||||
) {
|
||||
for (let existingUrlObj of contentMaps.localeUrlsMap[url] ||
|
||||
contentMaps.localeUrlsMap[`${url}/`]) {
|
||||
if (Comparator.urlHasLangCode(existingUrlObj.url, langCode)) {
|
||||
return existingUrlObj.url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Needs the language code prepended to the URL
|
||||
let prependedLangCodeUrl = `/${langCode}${url}`;
|
||||
if (
|
||||
contentMaps.localeUrlsMap[prependedLangCodeUrl] ||
|
||||
(!prependedLangCodeUrl.endsWith("/") && contentMaps.localeUrlsMap[`${prependedLangCodeUrl}/`])
|
||||
) {
|
||||
return prependedLangCodeUrl;
|
||||
}
|
||||
|
||||
if (
|
||||
contentMaps.urlToInputPath[url] ||
|
||||
(!url.endsWith("/") && contentMaps.urlToInputPath[`${url}/`])
|
||||
) {
|
||||
// this is not a localized file (independent of a language code)
|
||||
if (options.errorMode === "strict") {
|
||||
throw new Error(
|
||||
`Localized file for URL ${prependedLangCodeUrl} was not found in your project. A non-localized version does exist—are you sure you meant to use the \`${options.filters.url}\` filter for this? You can bypass this error using the \`errorMode\` option in the I18N plugin (current value: "${options.errorMode}").`,
|
||||
);
|
||||
}
|
||||
} else if (options.errorMode === "allow-fallback") {
|
||||
// You’re linking to a localized file that doesn’t exist!
|
||||
throw new Error(
|
||||
`Localized file for URL ${prependedLangCodeUrl} was not found in your project! You will need to add it if you want to link to it using the \`${options.filters.url}\` filter. You can bypass this error using the \`errorMode\` option in the I18N plugin (current value: "${options.errorMode}").`,
|
||||
);
|
||||
}
|
||||
|
||||
return url;
|
||||
});
|
||||
|
||||
// Refactor to use url
|
||||
// Find the links that are localized alternates to the inputPath argument
|
||||
eleventyConfig.addFilter(options.filters.links, function (urlOverride) {
|
||||
let url = urlOverride || this.page?.url;
|
||||
return (contentMaps.localeUrlsMap[url] || []).filter((entry) => {
|
||||
return entry.url !== url;
|
||||
});
|
||||
});
|
||||
|
||||
// Returns a `page`-esque variable for the root default language page
|
||||
// If paginated, returns first result only
|
||||
eleventyConfig.addFilter(
|
||||
"locale_page", // This is not exposed in `options` because it is an Eleventy internals filter (used in get*CollectionItem filters)
|
||||
function (pageOverride, languageCode) {
|
||||
// both args here are optional
|
||||
if (!languageCode) {
|
||||
languageCode = options.defaultLanguage;
|
||||
}
|
||||
|
||||
let page = pageOverride || this.page;
|
||||
let url; // new url
|
||||
if (contentMaps.localeUrlsMap[page.url]) {
|
||||
for (let entry of contentMaps.localeUrlsMap[page.url]) {
|
||||
if (entry.lang === languageCode) {
|
||||
url = entry.url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inputPath = LangUtils.swapLanguageCode(page.inputPath, languageCode);
|
||||
|
||||
if (
|
||||
!url ||
|
||||
!Array.isArray(contentMaps.inputPathToUrl[inputPath]) ||
|
||||
contentMaps.inputPathToUrl[inputPath].length === 0
|
||||
) {
|
||||
// no internationalized pages found
|
||||
return page;
|
||||
}
|
||||
|
||||
let result = {
|
||||
// // note that the permalink/slug may be different for the localized file!
|
||||
url,
|
||||
inputPath,
|
||||
filePathStem: LangUtils.swapLanguageCode(page.filePathStem, languageCode),
|
||||
// outputPath is omitted here, not necessary for GetCollectionItem.js if url is provided
|
||||
__locale_page_resolved: true,
|
||||
};
|
||||
return result;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export { Comparator, LangUtils };
|
||||
|
||||
Object.defineProperty(eleventyI18nPlugin, "eleventyPackage", {
|
||||
value: "@11ty/eleventy/i18n-plugin",
|
||||
});
|
||||
|
||||
Object.defineProperty(eleventyI18nPlugin, "eleventyPluginOptions", {
|
||||
value: {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default eleventyI18nPlugin;
|
||||
103
node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js
generated
vendored
Normal file
103
node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
import matchHelper from "posthtml-match-helper";
|
||||
import { decodeHTML } from "entities";
|
||||
|
||||
import slugifyFilter from "../Filters/Slugify.js";
|
||||
import MemoizeUtil from "../Util/MemoizeFunction.js";
|
||||
|
||||
function getTextNodeContent(node) {
|
||||
if (node.attrs?.["eleventy:id-ignore"] === "") {
|
||||
delete node.attrs["eleventy:id-ignore"];
|
||||
return "";
|
||||
}
|
||||
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 IdAttributePlugin(eleventyConfig, options = {}) {
|
||||
if (!options.slugify) {
|
||||
options.slugify = MemoizeUtil(slugifyFilter);
|
||||
}
|
||||
if (!options.selector) {
|
||||
options.selector = "[id],h1,h2,h3,h4,h5,h6";
|
||||
}
|
||||
options.decodeEntities = options.decodeEntities ?? true;
|
||||
options.checkDuplicates = options.checkDuplicates ?? "error";
|
||||
|
||||
eleventyConfig.htmlTransformer.addPosthtmlPlugin(
|
||||
"html",
|
||||
function (pluginOptions = {}) {
|
||||
if (typeof options.filter === "function") {
|
||||
if (options.filter(pluginOptions) === false) {
|
||||
return function () {};
|
||||
}
|
||||
}
|
||||
|
||||
return function (tree) {
|
||||
// One per page
|
||||
let conflictCheck = {};
|
||||
// Cache heading nodes for conflict resolution
|
||||
let headingNodes = {};
|
||||
|
||||
tree.match(matchHelper(options.selector), function (node) {
|
||||
if (node.attrs?.id) {
|
||||
let id = node.attrs?.id;
|
||||
if (conflictCheck[id]) {
|
||||
conflictCheck[id]++;
|
||||
if (headingNodes[id]) {
|
||||
// Rename conflicting assigned heading id
|
||||
let newId = `${id}-${conflictCheck[id]}`;
|
||||
headingNodes[newId] = headingNodes[id];
|
||||
headingNodes[newId].attrs.id = newId;
|
||||
delete headingNodes[id];
|
||||
} else if (options.checkDuplicates === "error") {
|
||||
// Existing `id` conflicts with assigned heading id, throw error
|
||||
throw new Error(
|
||||
"Duplicate `id` attribute (" +
|
||||
id +
|
||||
") in markup on " +
|
||||
pluginOptions.page.inputPath,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
conflictCheck[id] = 1;
|
||||
}
|
||||
} else if (!node.attrs?.id && node.content) {
|
||||
node.attrs = node.attrs || {};
|
||||
let textContent = getTextNodeContent(node);
|
||||
if (options.decodeEntities) {
|
||||
textContent = decodeHTML(textContent);
|
||||
}
|
||||
let id = options.slugify(textContent);
|
||||
|
||||
if (conflictCheck[id]) {
|
||||
conflictCheck[id]++;
|
||||
id = `${id}-${conflictCheck[id]}`;
|
||||
} else {
|
||||
conflictCheck[id] = 1;
|
||||
}
|
||||
|
||||
headingNodes[id] = node;
|
||||
node.attrs.id = id;
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
};
|
||||
} /* , {} // pluginOptions */,
|
||||
);
|
||||
}
|
||||
|
||||
export { IdAttributePlugin };
|
||||
177
node_modules/@11ty/eleventy/src/Plugins/InputPathToUrl.js
generated
vendored
Normal file
177
node_modules/@11ty/eleventy/src/Plugins/InputPathToUrl.js
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
import path from "node:path";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import isValidUrl from "../Util/ValidUrl.js";
|
||||
|
||||
function getValidPath(contentMap, testPath) {
|
||||
let normalized = TemplatePath.addLeadingDotSlash(testPath);
|
||||
|
||||
// it must exist in the content map to be valid
|
||||
if (contentMap[normalized]) {
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeInputPath(targetInputPath, inputDir, sourceInputPath, contentMap) {
|
||||
// inputDir is optional at the beginning of the developer supplied-path
|
||||
|
||||
// Input directory already on the input path
|
||||
if (TemplatePath.join(targetInputPath).startsWith(TemplatePath.join(inputDir))) {
|
||||
let absolutePath = getValidPath(contentMap, targetInputPath);
|
||||
if (absolutePath) {
|
||||
return absolutePath;
|
||||
}
|
||||
}
|
||||
|
||||
// Relative to project input directory
|
||||
let relativeToInputDir = getValidPath(contentMap, TemplatePath.join(inputDir, targetInputPath));
|
||||
if (relativeToInputDir) {
|
||||
return relativeToInputDir;
|
||||
}
|
||||
|
||||
if (targetInputPath && !path.isAbsolute(targetInputPath)) {
|
||||
// Relative to source file’s input path
|
||||
let sourceInputDir = TemplatePath.getDirFromFilePath(sourceInputPath);
|
||||
let relativeToSourceFile = getValidPath(
|
||||
contentMap,
|
||||
TemplatePath.join(sourceInputDir, targetInputPath),
|
||||
);
|
||||
if (relativeToSourceFile) {
|
||||
return relativeToSourceFile;
|
||||
}
|
||||
}
|
||||
|
||||
// the transform may have sent in a URL so we just return it as-is
|
||||
return targetInputPath;
|
||||
}
|
||||
|
||||
function parseFilePath(filepath) {
|
||||
try {
|
||||
/* u: URL {
|
||||
href: 'file:///tmpl.njk#anchor',
|
||||
origin: 'null',
|
||||
protocol: 'file:',
|
||||
username: '',
|
||||
password: '',
|
||||
host: '',
|
||||
hostname: '',
|
||||
port: '',
|
||||
pathname: '/tmpl.njk',
|
||||
search: '',
|
||||
searchParams: URLSearchParams {},
|
||||
hash: '#anchor'
|
||||
} */
|
||||
|
||||
// Note that `node:url` -> pathToFileURL creates an absolute path, which we don’t want
|
||||
// URL(`file:#anchor`) gives back a pathname of `/`
|
||||
let u = new URL(`file:${filepath}`);
|
||||
filepath = filepath.replace(u.search, "");
|
||||
filepath = filepath.replace(u.hash, "");
|
||||
|
||||
return [
|
||||
// search includes ?, hash includes #
|
||||
u.search + u.hash,
|
||||
filepath,
|
||||
];
|
||||
} catch (e) {
|
||||
return ["", filepath];
|
||||
}
|
||||
}
|
||||
|
||||
function FilterPlugin(eleventyConfig) {
|
||||
let contentMap;
|
||||
eleventyConfig.on("eleventy.contentMap", function ({ inputPathToUrl }) {
|
||||
contentMap = inputPathToUrl;
|
||||
});
|
||||
|
||||
eleventyConfig.addFilter("inputPathToUrl", function (targetFilePath) {
|
||||
if (!contentMap) {
|
||||
throw new Error("Internal error: contentMap not available for `inputPathToUrl` filter.");
|
||||
}
|
||||
|
||||
if (isValidUrl(targetFilePath)) {
|
||||
return targetFilePath;
|
||||
}
|
||||
|
||||
let inputDir = eleventyConfig.directories.input;
|
||||
let suffix = "";
|
||||
[suffix, targetFilePath] = parseFilePath(targetFilePath);
|
||||
// @ts-ignore
|
||||
targetFilePath = normalizeInputPath(targetFilePath, inputDir, this.page.inputPath, contentMap);
|
||||
|
||||
let urls = contentMap[targetFilePath];
|
||||
if (!urls || urls.length === 0) {
|
||||
throw new Error(
|
||||
"`inputPathToUrl` filter could not find a matching target for " + targetFilePath,
|
||||
);
|
||||
}
|
||||
|
||||
return `${urls[0]}${suffix}`;
|
||||
});
|
||||
}
|
||||
|
||||
function TransformPlugin(eleventyConfig, defaultOptions = {}) {
|
||||
let opts = Object.assign(
|
||||
{
|
||||
extensions: "html",
|
||||
},
|
||||
defaultOptions,
|
||||
);
|
||||
|
||||
let contentMap = null;
|
||||
eleventyConfig.on("eleventy.contentMap", function ({ inputPathToUrl }) {
|
||||
contentMap = inputPathToUrl;
|
||||
});
|
||||
|
||||
eleventyConfig.htmlTransformer.addUrlTransform(opts.extensions, function (targetFilepathOrUrl) {
|
||||
if (!contentMap) {
|
||||
throw new Error("Internal error: contentMap not available for the `pathToUrl` Transform.");
|
||||
}
|
||||
if (isValidUrl(targetFilepathOrUrl)) {
|
||||
return targetFilepathOrUrl;
|
||||
}
|
||||
|
||||
let inputDir = eleventyConfig.directories.input;
|
||||
|
||||
let suffix = "";
|
||||
[suffix, targetFilepathOrUrl] = parseFilePath(targetFilepathOrUrl);
|
||||
targetFilepathOrUrl = normalizeInputPath(
|
||||
targetFilepathOrUrl,
|
||||
inputDir,
|
||||
// @ts-ignore
|
||||
this.page.inputPath,
|
||||
contentMap,
|
||||
);
|
||||
|
||||
let urls = contentMap[targetFilepathOrUrl];
|
||||
if (!targetFilepathOrUrl || !urls || urls.length === 0) {
|
||||
// fallback, transforms don’t error on missing paths (though the pathToUrl filter does)
|
||||
return `${targetFilepathOrUrl}${suffix}`;
|
||||
}
|
||||
|
||||
return `${urls[0]}${suffix}`;
|
||||
});
|
||||
}
|
||||
|
||||
Object.defineProperty(FilterPlugin, "eleventyPackage", {
|
||||
value: "@11ty/eleventy/inputpath-to-url-filter-plugin",
|
||||
});
|
||||
|
||||
Object.defineProperty(FilterPlugin, "eleventyPluginOptions", {
|
||||
value: {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(TransformPlugin, "eleventyPackage", {
|
||||
value: "@11ty/eleventy/inputpath-to-url-transform-plugin",
|
||||
});
|
||||
|
||||
Object.defineProperty(TransformPlugin, "eleventyPluginOptions", {
|
||||
value: {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default TransformPlugin;
|
||||
|
||||
export { FilterPlugin, TransformPlugin };
|
||||
380
node_modules/@11ty/eleventy/src/Plugins/Pagination.js
generated
vendored
Executable file
380
node_modules/@11ty/eleventy/src/Plugins/Pagination.js
generated
vendored
Executable file
@@ -0,0 +1,380 @@
|
||||
import { isPlainObject } from "@11ty/eleventy-utils";
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
import { DeepCopy } from "@11ty/eleventy-utils";
|
||||
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
import { ProxyWrap } from "../Util/Objects/ProxyWrap.js";
|
||||
// import { DeepFreeze } from "../Util/Objects/DeepFreeze.js";
|
||||
import TemplateData from "../Data/TemplateData.js";
|
||||
|
||||
const { set: lodashSet, get: lodashGet, chunk: lodashChunk } = lodash;
|
||||
|
||||
class PaginationConfigError extends EleventyBaseError {}
|
||||
class PaginationError extends EleventyBaseError {}
|
||||
|
||||
class Pagination {
|
||||
constructor(tmpl, data, config) {
|
||||
if (!config) {
|
||||
throw new PaginationConfigError("Expected `config` argument to Pagination class.");
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
|
||||
this.setTemplate(tmpl);
|
||||
this.setData(data);
|
||||
}
|
||||
|
||||
get inputPathForErrorMessages() {
|
||||
if (this.template) {
|
||||
return ` (${this.template.inputPath})`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static hasPagination(data) {
|
||||
return "pagination" in data;
|
||||
}
|
||||
|
||||
hasPagination() {
|
||||
if (!this.data) {
|
||||
throw new Error(
|
||||
`Missing \`setData\` call for Pagination object${this.inputPathForErrorMessages}`,
|
||||
);
|
||||
}
|
||||
return Pagination.hasPagination(this.data);
|
||||
}
|
||||
|
||||
circularReferenceCheck(data) {
|
||||
let key = data.pagination.data;
|
||||
let includedTags = TemplateData.getIncludedTagNames(data);
|
||||
|
||||
for (let tag of includedTags) {
|
||||
if (`collections.${tag}` === key) {
|
||||
throw new PaginationError(
|
||||
`Pagination circular reference${this.inputPathForErrorMessages}, data:\`${key}\` iterates over both the \`${tag}\` collection and also supplies pages to that collection.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
this.data = data || {};
|
||||
this.target = [];
|
||||
|
||||
if (!this.hasPagination()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.pagination) {
|
||||
throw new Error(
|
||||
`Misconfigured pagination data in template front matter${this.inputPathForErrorMessages} (YAML front matter precaution: did you use tabs and not spaces for indentation?).`,
|
||||
);
|
||||
} else if (!("size" in data.pagination)) {
|
||||
throw new Error(
|
||||
`Missing pagination size in front matter data${this.inputPathForErrorMessages}`,
|
||||
);
|
||||
}
|
||||
this.circularReferenceCheck(data);
|
||||
|
||||
this.size = data.pagination.size;
|
||||
this.alias = data.pagination.alias;
|
||||
this.fullDataSet = this._get(this.data, this._getDataKey());
|
||||
// this returns an array
|
||||
this.target = this._resolveItems();
|
||||
this.chunkedItems = this.pagedItems;
|
||||
}
|
||||
|
||||
setTemplate(tmpl) {
|
||||
this.template = tmpl;
|
||||
}
|
||||
|
||||
_getDataKey() {
|
||||
return this.data.pagination.data;
|
||||
}
|
||||
|
||||
shouldResolveDataToObjectValues() {
|
||||
if ("resolve" in this.data.pagination) {
|
||||
return this.data.pagination.resolve === "values";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isFiltered(value) {
|
||||
if ("filter" in this.data.pagination) {
|
||||
let filtered = this.data.pagination.filter;
|
||||
if (Array.isArray(filtered)) {
|
||||
return filtered.indexOf(value) > -1;
|
||||
}
|
||||
|
||||
return filtered === value;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_has(target, key) {
|
||||
let notFoundValue = "__NOT_FOUND_ERROR__";
|
||||
let data = lodashGet(target, key, notFoundValue);
|
||||
return data !== notFoundValue;
|
||||
}
|
||||
|
||||
_get(target, key) {
|
||||
let notFoundValue = "__NOT_FOUND_ERROR__";
|
||||
let data = lodashGet(target, key, notFoundValue);
|
||||
if (data === notFoundValue) {
|
||||
throw new Error(
|
||||
`Could not find pagination data${this.inputPathForErrorMessages}, went looking for: ${key}`,
|
||||
);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
_resolveItems() {
|
||||
let keys;
|
||||
if (Array.isArray(this.fullDataSet)) {
|
||||
keys = this.fullDataSet;
|
||||
this.paginationTargetType = "array";
|
||||
} else if (isPlainObject(this.fullDataSet)) {
|
||||
this.paginationTargetType = "object";
|
||||
if (this.shouldResolveDataToObjectValues()) {
|
||||
keys = Object.values(this.fullDataSet);
|
||||
} else {
|
||||
keys = Object.keys(this.fullDataSet);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected data found in pagination target${this.inputPathForErrorMessages}: expected an Array or an Object.`,
|
||||
);
|
||||
}
|
||||
|
||||
// keys must be an array
|
||||
let result = keys.slice();
|
||||
|
||||
if (this.data.pagination.before && typeof this.data.pagination.before === "function") {
|
||||
// we don’t need to make a copy of this because we .slice() above to create a new copy
|
||||
let fns = {};
|
||||
if (this.config) {
|
||||
fns = this.config.javascriptFunctions;
|
||||
}
|
||||
result = this.data.pagination.before.call(fns, result, this.data);
|
||||
}
|
||||
|
||||
if (this.data.pagination.reverse === true) {
|
||||
result = result.reverse();
|
||||
}
|
||||
|
||||
if (this.data.pagination.filter) {
|
||||
result = result.filter((value) => !this.isFiltered(value));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
get pagedItems() {
|
||||
if (!this.data) {
|
||||
throw new Error(
|
||||
`Missing \`setData\` call for Pagination object${this.inputPathForErrorMessages}`,
|
||||
);
|
||||
}
|
||||
|
||||
const chunks = lodashChunk(this.target, this.size);
|
||||
if (this.data.pagination?.generatePageOnEmptyData) {
|
||||
return chunks.length ? chunks : [[]];
|
||||
} else {
|
||||
return chunks;
|
||||
}
|
||||
}
|
||||
|
||||
getPageCount() {
|
||||
if (!this.hasPagination()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.chunkedItems.length;
|
||||
}
|
||||
|
||||
getNormalizedItems(pageItems) {
|
||||
return this.size === 1 ? pageItems[0] : pageItems;
|
||||
}
|
||||
|
||||
getOverrideDataPages(items, pageNumber) {
|
||||
return {
|
||||
// See Issue #345 for more examples
|
||||
page: {
|
||||
previous: pageNumber > 0 ? this.getNormalizedItems(items[pageNumber - 1]) : null,
|
||||
next: pageNumber < items.length - 1 ? this.getNormalizedItems(items[pageNumber + 1]) : null,
|
||||
first: items.length ? this.getNormalizedItems(items[0]) : null,
|
||||
last: items.length ? this.getNormalizedItems(items[items.length - 1]) : null,
|
||||
},
|
||||
|
||||
pageNumber,
|
||||
};
|
||||
}
|
||||
|
||||
getOverrideDataLinks(pageNumber, templateCount, links) {
|
||||
let obj = {};
|
||||
|
||||
// links are okay but hrefs are better
|
||||
obj.previousPageLink = pageNumber > 0 ? links[pageNumber - 1] : null;
|
||||
obj.previous = obj.previousPageLink;
|
||||
|
||||
obj.nextPageLink = pageNumber < templateCount - 1 ? links[pageNumber + 1] : null;
|
||||
obj.next = obj.nextPageLink;
|
||||
|
||||
obj.firstPageLink = links.length > 0 ? links[0] : null;
|
||||
obj.lastPageLink = links.length > 0 ? links[links.length - 1] : null;
|
||||
|
||||
obj.links = links;
|
||||
// todo deprecated, consistency with collections and use links instead
|
||||
obj.pageLinks = links;
|
||||
return obj;
|
||||
}
|
||||
|
||||
getOverrideDataHrefs(pageNumber, templateCount, hrefs) {
|
||||
let obj = {};
|
||||
|
||||
// hrefs are better than links
|
||||
obj.previousPageHref = pageNumber > 0 ? hrefs[pageNumber - 1] : null;
|
||||
obj.nextPageHref = pageNumber < templateCount - 1 ? hrefs[pageNumber + 1] : null;
|
||||
|
||||
obj.firstPageHref = hrefs.length > 0 ? hrefs[0] : null;
|
||||
obj.lastPageHref = hrefs.length > 0 ? hrefs[hrefs.length - 1] : null;
|
||||
|
||||
obj.hrefs = hrefs;
|
||||
|
||||
// better names
|
||||
obj.href = {
|
||||
previous: obj.previousPageHref,
|
||||
next: obj.nextPageHref,
|
||||
first: obj.firstPageHref,
|
||||
last: obj.lastPageHref,
|
||||
};
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
async getPageTemplates() {
|
||||
if (!this.data) {
|
||||
throw new Error(
|
||||
`Missing \`setData\` call for Pagination object${this.inputPathForErrorMessages}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.hasPagination()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let entries = [];
|
||||
let items = this.chunkedItems;
|
||||
let pages = this.size === 1 ? items.map((entry) => entry[0]) : items;
|
||||
|
||||
let links = [];
|
||||
let hrefs = [];
|
||||
|
||||
let hasPermalinkField = Boolean(this.data[this.config.keys.permalink]);
|
||||
let hasComputedPermalinkField = Boolean(
|
||||
this.data.eleventyComputed && this.data.eleventyComputed[this.config.keys.permalink],
|
||||
);
|
||||
|
||||
// Do *not* pass collections through DeepCopy, we’ll re-add them back in later.
|
||||
let collections = this.data.collections;
|
||||
if (collections) {
|
||||
delete this.data.collections;
|
||||
}
|
||||
|
||||
let parentData = DeepCopy(
|
||||
{
|
||||
pagination: {
|
||||
data: this.data.pagination.data,
|
||||
size: this.data.pagination.size,
|
||||
alias: this.alias,
|
||||
pages,
|
||||
},
|
||||
},
|
||||
this.data,
|
||||
);
|
||||
|
||||
// Restore skipped collections
|
||||
if (collections) {
|
||||
this.data.collections = collections;
|
||||
// Keep the original reference to the collections, no deep copy!!
|
||||
parentData.collections = collections;
|
||||
}
|
||||
|
||||
// TODO this does work fine but let’s wait on enabling it.
|
||||
// DeepFreeze(parentData, ["collections"]);
|
||||
|
||||
// TODO future improvement dea: use a light Template wrapper for paged template clones (PagedTemplate?)
|
||||
// so that we don’t have the memory cost of the full template (and can reuse the parent
|
||||
// template for some things)
|
||||
|
||||
let indices = new Set();
|
||||
for (let j = 0; j <= items.length - 1; j++) {
|
||||
indices.add(j);
|
||||
}
|
||||
|
||||
for (let pageNumber of indices) {
|
||||
let cloned = await this.template.clone();
|
||||
|
||||
if (pageNumber > 0 && !hasPermalinkField && !hasComputedPermalinkField) {
|
||||
cloned.setExtraOutputSubdirectory(pageNumber);
|
||||
}
|
||||
|
||||
let paginationData = {
|
||||
pagination: {
|
||||
items: items[pageNumber],
|
||||
},
|
||||
page: {},
|
||||
};
|
||||
Object.assign(paginationData.pagination, this.getOverrideDataPages(items, pageNumber));
|
||||
|
||||
if (this.alias) {
|
||||
lodashSet(paginationData, this.alias, this.getNormalizedItems(items[pageNumber]));
|
||||
}
|
||||
|
||||
// Do *not* deep merge pagination data! See https://github.com/11ty/eleventy/issues/147#issuecomment-440802454
|
||||
let clonedData = ProxyWrap(paginationData, parentData);
|
||||
|
||||
// Previous method:
|
||||
// let clonedData = DeepCopy(paginationData, parentData);
|
||||
|
||||
let { /*linkInstance,*/ rawPath, path, href } = await cloned.getOutputLocations(clonedData);
|
||||
// TODO subdirectory to links if the site doesn’t live at /
|
||||
if (rawPath) {
|
||||
links.push("/" + rawPath);
|
||||
}
|
||||
|
||||
hrefs.push(href);
|
||||
|
||||
// page.url and page.outputPath are used to avoid another getOutputLocations call later, see Template->addComputedData
|
||||
clonedData.page.url = href;
|
||||
clonedData.page.outputPath = path;
|
||||
|
||||
entries.push({
|
||||
pageNumber,
|
||||
|
||||
// This is used by i18n Plugin to allow subgroups of nested pagination to be separate
|
||||
groupNumber: items[pageNumber]?.[0]?.eleventyPaginationGroupNumber,
|
||||
|
||||
template: cloned,
|
||||
data: clonedData,
|
||||
});
|
||||
}
|
||||
|
||||
// we loop twice to pass in the appropriate prev/next links (already full generated now)
|
||||
let index = 0;
|
||||
for (let pageEntry of entries) {
|
||||
let linksObj = this.getOverrideDataLinks(index, items.length, links);
|
||||
|
||||
Object.assign(pageEntry.data.pagination, linksObj);
|
||||
|
||||
let hrefsObj = this.getOverrideDataHrefs(index, items.length, hrefs);
|
||||
Object.assign(pageEntry.data.pagination, hrefsObj);
|
||||
index++;
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
||||
export default Pagination;
|
||||
481
node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js
generated
vendored
Normal file
481
node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js
generated
vendored
Normal file
@@ -0,0 +1,481 @@
|
||||
import fs from "graceful-fs";
|
||||
import { Merge, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
|
||||
import { evalToken } from "liquidjs";
|
||||
|
||||
// TODO add a first-class Markdown component to expose this using Markdown-only syntax (will need to be synchronous for markdown-it)
|
||||
|
||||
import { ProxyWrap } from "../Util/Objects/ProxyWrap.js";
|
||||
import TemplateDataInitialGlobalData from "../Data/TemplateDataInitialGlobalData.js";
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
import TemplateRender from "../TemplateRender.js";
|
||||
import ProjectDirectories from "../Util/ProjectDirectories.js";
|
||||
import TemplateConfig from "../TemplateConfig.js";
|
||||
import Liquid from "../Engines/Liquid.js";
|
||||
|
||||
class EleventyNunjucksError extends EleventyBaseError {}
|
||||
|
||||
/** @this {object} */
|
||||
async function compile(content, templateLang, options = {}) {
|
||||
let { templateConfig, extensionMap } = options;
|
||||
|
||||
if (!templateConfig) {
|
||||
templateConfig = new TemplateConfig(null, false);
|
||||
templateConfig.setDirectories(new ProjectDirectories());
|
||||
await templateConfig.init();
|
||||
}
|
||||
|
||||
// Breaking change in 2.0+, previous default was `html` and now we default to the page template syntax
|
||||
if (!templateLang) {
|
||||
templateLang = this.page.templateSyntax;
|
||||
}
|
||||
|
||||
let tr = new TemplateRender(templateLang, templateConfig);
|
||||
tr.extensionMap = extensionMap;
|
||||
if (templateLang) {
|
||||
await tr.setEngineOverride(templateLang);
|
||||
} else {
|
||||
await tr.init();
|
||||
}
|
||||
|
||||
// TODO tie this to the class, not the extension
|
||||
if (
|
||||
tr.engine.name === "11ty.js" ||
|
||||
tr.engine.name === "11ty.cjs" ||
|
||||
tr.engine.name === "11ty.mjs"
|
||||
) {
|
||||
throw new Error(
|
||||
"11ty.js is not yet supported as a template engine for `renderTemplate`. Use `renderFile` instead!",
|
||||
);
|
||||
}
|
||||
|
||||
return tr.getCompiledTemplate(content);
|
||||
}
|
||||
|
||||
// No templateLang default, it should infer from the inputPath.
|
||||
async function compileFile(inputPath, options = {}, templateLang) {
|
||||
let { templateConfig, extensionMap, config } = options;
|
||||
if (!inputPath) {
|
||||
throw new Error("Missing file path argument passed to the `renderFile` shortcode.");
|
||||
}
|
||||
|
||||
if (!fs.existsSync(TemplatePath.normalizeOperatingSystemFilePath(inputPath))) {
|
||||
throw new Error(
|
||||
"Could not find render plugin file for the `renderFile` shortcode, looking for: " + inputPath,
|
||||
);
|
||||
}
|
||||
|
||||
let wasTemplateConfigMissing = false;
|
||||
if (!templateConfig) {
|
||||
templateConfig = new TemplateConfig(null, false);
|
||||
templateConfig.setDirectories(new ProjectDirectories());
|
||||
wasTemplateConfigMissing = true;
|
||||
}
|
||||
if (config && typeof config === "function") {
|
||||
await config(templateConfig.userConfig);
|
||||
}
|
||||
if (wasTemplateConfigMissing) {
|
||||
await templateConfig.init();
|
||||
}
|
||||
|
||||
let tr = new TemplateRender(inputPath, templateConfig);
|
||||
tr.extensionMap = extensionMap;
|
||||
|
||||
if (templateLang) {
|
||||
await tr.setEngineOverride(templateLang);
|
||||
} else {
|
||||
await tr.init();
|
||||
}
|
||||
|
||||
if (!tr.engine.needsToReadFileContents()) {
|
||||
return tr.getCompiledTemplate(null);
|
||||
}
|
||||
|
||||
// TODO we could make this work with full templates (with front matter?)
|
||||
let content = fs.readFileSync(inputPath, "utf8");
|
||||
return tr.getCompiledTemplate(content);
|
||||
}
|
||||
|
||||
/** @this {object} */
|
||||
async function renderShortcodeFn(fn, data) {
|
||||
if (fn === undefined) {
|
||||
return;
|
||||
} else if (typeof fn !== "function") {
|
||||
throw new Error(`The \`compile\` function did not return a function. Received ${fn}`);
|
||||
}
|
||||
|
||||
// if the user passes a string or other literal, remap to an object.
|
||||
if (!isPlainObject(data)) {
|
||||
data = {
|
||||
_: data,
|
||||
};
|
||||
}
|
||||
|
||||
if ("data" in this && isPlainObject(this.data)) {
|
||||
// when options.accessGlobalData is true, this allows the global data
|
||||
// to be accessed inside of the shortcode as a fallback
|
||||
|
||||
data = ProxyWrap(data, this.data);
|
||||
} else {
|
||||
// save `page` and `eleventy` for reuse
|
||||
data.page = this.page;
|
||||
data.eleventy = this.eleventy;
|
||||
}
|
||||
|
||||
return fn(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @module 11ty/eleventy/Plugins/RenderPlugin
|
||||
*/
|
||||
|
||||
/**
|
||||
* A plugin to add shortcodes to render an Eleventy template
|
||||
* string (or file) inside of another template. {@link https://v3.11ty.dev/docs/plugins/render/}
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param {module:11ty/eleventy/UserConfig} eleventyConfig - User-land configuration instance.
|
||||
* @param {object} options - Plugin options
|
||||
*/
|
||||
function eleventyRenderPlugin(eleventyConfig, options = {}) {
|
||||
/**
|
||||
* @typedef {object} options
|
||||
* @property {string} [tagName] - The shortcode name to render a template string.
|
||||
* @property {string} [tagNameFile] - The shortcode name to render a template file.
|
||||
* @property {module:11ty/eleventy/TemplateConfig} [templateConfig] - Configuration object
|
||||
* @property {boolean} [accessGlobalData] - Whether or not the template has access to the page’s data.
|
||||
*/
|
||||
let defaultOptions = {
|
||||
tagName: "renderTemplate",
|
||||
tagNameFile: "renderFile",
|
||||
filterName: "renderContent",
|
||||
templateConfig: null,
|
||||
accessGlobalData: false,
|
||||
};
|
||||
let opts = Object.assign(defaultOptions, options);
|
||||
|
||||
function liquidTemplateTag(liquidEngine, tagName) {
|
||||
// via https://github.com/harttle/liquidjs/blob/b5a22fa0910c708fe7881ef170ed44d3594e18f3/src/builtin/tags/raw.ts
|
||||
return {
|
||||
parse: function (tagToken, remainTokens) {
|
||||
this.name = tagToken.name;
|
||||
|
||||
if (eleventyConfig.liquid.parameterParsing === "builtin") {
|
||||
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
|
||||
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
|
||||
} else {
|
||||
this.legacyArgs = tagToken.args;
|
||||
}
|
||||
|
||||
this.tokens = [];
|
||||
|
||||
var stream = liquidEngine.parser
|
||||
.parseStream(remainTokens)
|
||||
.on("token", (token) => {
|
||||
if (token.name === "end" + tagName) stream.stop();
|
||||
else this.tokens.push(token);
|
||||
})
|
||||
.on("end", () => {
|
||||
throw new Error(`tag ${tagToken.getText()} not closed`);
|
||||
});
|
||||
|
||||
stream.start();
|
||||
},
|
||||
render: function* (ctx) {
|
||||
let normalizedContext = {};
|
||||
if (ctx) {
|
||||
if (opts.accessGlobalData) {
|
||||
// parent template data cascade
|
||||
normalizedContext.data = ctx.getAll();
|
||||
}
|
||||
|
||||
normalizedContext.page = ctx.get(["page"]);
|
||||
normalizedContext.eleventy = ctx.get(["eleventy"]);
|
||||
}
|
||||
|
||||
let argArray = [];
|
||||
if (this.legacyArgs) {
|
||||
let rawArgs = Liquid.parseArguments(null, this.legacyArgs);
|
||||
for (let arg of rawArgs) {
|
||||
let b = yield liquidEngine.evalValue(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
} else if (this.orderedArgs) {
|
||||
for (let arg of this.orderedArgs) {
|
||||
let b = yield evalToken(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
// plaintext paired shortcode content
|
||||
let body = this.tokens.map((token) => token.getText()).join("");
|
||||
|
||||
let ret = _renderStringShortcodeFn.call(
|
||||
normalizedContext,
|
||||
body,
|
||||
// templateLang, data
|
||||
...argArray,
|
||||
);
|
||||
yield ret;
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// TODO I don’t think this works with whitespace control, e.g. {%- endrenderTemplate %}
|
||||
function nunjucksTemplateTag(NunjucksLib, tagName) {
|
||||
return new (function () {
|
||||
this.tags = [tagName];
|
||||
|
||||
this.parse = function (parser, nodes) {
|
||||
var tok = parser.nextToken();
|
||||
|
||||
var args = parser.parseSignature(true, true);
|
||||
const begun = parser.advanceAfterBlockEnd(tok.value);
|
||||
|
||||
// This code was ripped from the Nunjucks parser for `raw`
|
||||
// https://github.com/mozilla/nunjucks/blob/fd500902d7c88672470c87170796de52fc0f791a/nunjucks/src/parser.js#L655
|
||||
const endTagName = "end" + tagName;
|
||||
// Look for upcoming raw blocks (ignore all other kinds of blocks)
|
||||
const rawBlockRegex = new RegExp(
|
||||
"([\\s\\S]*?){%\\s*(" + tagName + "|" + endTagName + ")\\s*(?=%})%}",
|
||||
);
|
||||
let rawLevel = 1;
|
||||
let str = "";
|
||||
let matches = null;
|
||||
|
||||
// Exit when there's nothing to match
|
||||
// or when we've found the matching "endraw" block
|
||||
while ((matches = parser.tokens._extractRegex(rawBlockRegex)) && rawLevel > 0) {
|
||||
const all = matches[0];
|
||||
const pre = matches[1];
|
||||
const blockName = matches[2];
|
||||
|
||||
// Adjust rawlevel
|
||||
if (blockName === tagName) {
|
||||
rawLevel += 1;
|
||||
} else if (blockName === endTagName) {
|
||||
rawLevel -= 1;
|
||||
}
|
||||
|
||||
// Add to str
|
||||
if (rawLevel === 0) {
|
||||
// We want to exclude the last "endraw"
|
||||
str += pre;
|
||||
// Move tokenizer to beginning of endraw block
|
||||
parser.tokens.backN(all.length - pre.length);
|
||||
} else {
|
||||
str += all;
|
||||
}
|
||||
}
|
||||
|
||||
let body = new nodes.Output(begun.lineno, begun.colno, [
|
||||
new nodes.TemplateData(begun.lineno, begun.colno, str),
|
||||
]);
|
||||
return new nodes.CallExtensionAsync(this, "run", args, [body]);
|
||||
};
|
||||
|
||||
this.run = function (...args) {
|
||||
let resolve = args.pop();
|
||||
let body = args.pop();
|
||||
let [context, ...argArray] = args;
|
||||
|
||||
let normalizedContext = {};
|
||||
if (context.ctx?.page) {
|
||||
normalizedContext.ctx = context.ctx;
|
||||
|
||||
// TODO .data
|
||||
// if(opts.accessGlobalData) {
|
||||
// normalizedContext.data = context.ctx;
|
||||
// }
|
||||
|
||||
normalizedContext.page = context.ctx.page;
|
||||
normalizedContext.eleventy = context.ctx.eleventy;
|
||||
}
|
||||
|
||||
body(function (e, bodyContent) {
|
||||
if (e) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(`Error with Nunjucks paired shortcode \`${tagName}\``, e),
|
||||
);
|
||||
}
|
||||
|
||||
Promise.resolve(
|
||||
_renderStringShortcodeFn.call(
|
||||
normalizedContext,
|
||||
bodyContent,
|
||||
// templateLang, data
|
||||
...argArray,
|
||||
),
|
||||
).then(
|
||||
function (returnValue) {
|
||||
resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
|
||||
},
|
||||
function (e) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(`Error with Nunjucks paired shortcode \`${tagName}\``, e),
|
||||
null,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
})();
|
||||
}
|
||||
|
||||
// This will only work on 1.0.0-beta.5+ but is only necessary if you want to reuse your config inside of template shortcodes.
|
||||
// Just rendering raw templates (without filters, shortcodes, etc. from your config) will work fine on old versions.
|
||||
let templateConfig;
|
||||
eleventyConfig.on("eleventy.config", (cfg) => {
|
||||
templateConfig = cfg;
|
||||
});
|
||||
|
||||
let extensionMap;
|
||||
eleventyConfig.on("eleventy.extensionmap", (map) => {
|
||||
extensionMap = map;
|
||||
});
|
||||
|
||||
/** @this {object} */
|
||||
async function _renderStringShortcodeFn(content, templateLang, data = {}) {
|
||||
// Default is fn(content, templateLang, data) but we want to support fn(content, data) too
|
||||
if (typeof templateLang !== "string") {
|
||||
data = templateLang;
|
||||
templateLang = false;
|
||||
}
|
||||
|
||||
let fn = await compile.call(this, content, templateLang, {
|
||||
templateConfig: opts.templateConfig || templateConfig,
|
||||
extensionMap,
|
||||
});
|
||||
|
||||
return renderShortcodeFn.call(this, fn, data);
|
||||
}
|
||||
|
||||
/** @this {object} */
|
||||
async function _renderFileShortcodeFn(inputPath, data = {}, templateLang) {
|
||||
let options = {
|
||||
templateConfig: opts.templateConfig || templateConfig,
|
||||
extensionMap,
|
||||
};
|
||||
|
||||
let fn = await compileFile.call(this, inputPath, options, templateLang);
|
||||
|
||||
return renderShortcodeFn.call(this, fn, data);
|
||||
}
|
||||
|
||||
// Render strings
|
||||
if (opts.tagName) {
|
||||
// use falsy to opt-out
|
||||
eleventyConfig.addJavaScriptFunction(opts.tagName, _renderStringShortcodeFn);
|
||||
|
||||
eleventyConfig.addLiquidTag(opts.tagName, function (liquidEngine) {
|
||||
return liquidTemplateTag(liquidEngine, opts.tagName);
|
||||
});
|
||||
|
||||
eleventyConfig.addNunjucksTag(opts.tagName, function (nunjucksLib) {
|
||||
return nunjucksTemplateTag(nunjucksLib, opts.tagName);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter for rendering strings
|
||||
if (opts.filterName) {
|
||||
eleventyConfig.addAsyncFilter(opts.filterName, _renderStringShortcodeFn);
|
||||
}
|
||||
|
||||
// Render File
|
||||
// use `false` to opt-out
|
||||
if (opts.tagNameFile) {
|
||||
eleventyConfig.addAsyncShortcode(opts.tagNameFile, _renderFileShortcodeFn);
|
||||
}
|
||||
}
|
||||
|
||||
// Will re-use the same configuration instance both at a top level and across any nested renders
|
||||
class RenderManager {
|
||||
/** @type {Promise|undefined} */
|
||||
#hasConfigInitialized;
|
||||
|
||||
constructor() {
|
||||
this.templateConfig = new TemplateConfig(null, false);
|
||||
this.templateConfig.setDirectories(new ProjectDirectories());
|
||||
|
||||
// This is the only plugin running on the Edge
|
||||
this.templateConfig.userConfig.addPlugin(eleventyRenderPlugin, {
|
||||
templateConfig: this.templateConfig,
|
||||
accessGlobalData: true,
|
||||
});
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.#hasConfigInitialized) {
|
||||
return this.#hasConfigInitialized;
|
||||
}
|
||||
if (this.templateConfig.hasInitialized()) {
|
||||
return true;
|
||||
}
|
||||
this.#hasConfigInitialized = this.templateConfig.init();
|
||||
await this.#hasConfigInitialized;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// `callback` is async-friendly but requires await upstream
|
||||
config(callback) {
|
||||
// run an extra `function(eleventyConfig)` configuration callbacks
|
||||
if (callback && typeof callback === "function") {
|
||||
return callback(this.templateConfig.userConfig);
|
||||
}
|
||||
}
|
||||
|
||||
get initialGlobalData() {
|
||||
if (!this._data) {
|
||||
this._data = new TemplateDataInitialGlobalData(this.templateConfig);
|
||||
}
|
||||
return this._data;
|
||||
}
|
||||
|
||||
// because we don’t have access to the full data cascade—but
|
||||
// we still want configuration data added via `addGlobalData`
|
||||
async getData(...data) {
|
||||
await this.init();
|
||||
|
||||
let globalData = await this.initialGlobalData.getData();
|
||||
let merged = Merge({}, globalData, ...data);
|
||||
return merged;
|
||||
}
|
||||
|
||||
async compile(content, templateLang, options = {}) {
|
||||
await this.init();
|
||||
|
||||
// Missing here: extensionMap
|
||||
options.templateConfig = this.templateConfig;
|
||||
|
||||
// We don’t need `compile.call(this)` here because the Edge always uses "liquid" as the template lang (instead of relying on this.page.templateSyntax)
|
||||
// returns promise
|
||||
return compile(content, templateLang, options);
|
||||
}
|
||||
|
||||
async render(fn, edgeData, buildTimeData) {
|
||||
await this.init();
|
||||
|
||||
let mergedData = await this.getData(edgeData);
|
||||
// Set .data for options.accessGlobalData feature
|
||||
let context = {
|
||||
data: mergedData,
|
||||
};
|
||||
|
||||
return renderShortcodeFn.call(context, fn, buildTimeData);
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(eleventyRenderPlugin, "eleventyPackage", {
|
||||
value: "@11ty/eleventy/render-plugin",
|
||||
});
|
||||
|
||||
Object.defineProperty(eleventyRenderPlugin, "eleventyPluginOptions", {
|
||||
value: {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default eleventyRenderPlugin;
|
||||
|
||||
export { compileFile as File, compile as String, RenderManager };
|
||||
1124
node_modules/@11ty/eleventy/src/Template.js
generated
vendored
Executable file
1124
node_modules/@11ty/eleventy/src/Template.js
generated
vendored
Executable file
File diff suppressed because it is too large
Load Diff
85
node_modules/@11ty/eleventy/src/TemplateBehavior.js
generated
vendored
Normal file
85
node_modules/@11ty/eleventy/src/TemplateBehavior.js
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
import { isPlainObject } from "@11ty/eleventy-utils";
|
||||
|
||||
class TemplateBehavior {
|
||||
#isRenderOptional;
|
||||
|
||||
constructor(config) {
|
||||
this.render = true;
|
||||
this.write = true;
|
||||
this.outputFormat = null;
|
||||
|
||||
if (!config) {
|
||||
throw new Error("Missing config argument in TemplateBehavior");
|
||||
}
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
// Render override set to false
|
||||
isRenderableDisabled() {
|
||||
return this.renderableOverride === false;
|
||||
}
|
||||
|
||||
isRenderableOptional() {
|
||||
return this.#isRenderOptional;
|
||||
}
|
||||
|
||||
// undefined (fallback), true, false
|
||||
setRenderableOverride(renderableOverride) {
|
||||
if (renderableOverride === "optional") {
|
||||
this.#isRenderOptional = true;
|
||||
this.renderableOverride = undefined;
|
||||
} else {
|
||||
this.#isRenderOptional = false;
|
||||
this.renderableOverride = renderableOverride;
|
||||
}
|
||||
}
|
||||
|
||||
// permalink *has* a build key or output is json/ndjson
|
||||
isRenderable() {
|
||||
return this.renderableOverride ?? (this.render || this.isRenderForced());
|
||||
}
|
||||
|
||||
setOutputFormat(format) {
|
||||
this.outputFormat = format;
|
||||
}
|
||||
|
||||
isRenderForced() {
|
||||
return this.outputFormat === "json" || this.outputFormat === "ndjson";
|
||||
}
|
||||
|
||||
isWriteable() {
|
||||
return this.write;
|
||||
}
|
||||
|
||||
// Duplicate logic with TemplatePermalink constructor
|
||||
setRenderViaDataCascade(data) {
|
||||
// render is false *only* if `build` key does not exist in permalink objects (both in data and eleventyComputed)
|
||||
// (note that permalink: false means it won’t write but will still render)
|
||||
|
||||
let keys = new Set();
|
||||
if (isPlainObject(data.permalink)) {
|
||||
for (let key of Object.keys(data.permalink)) {
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
let computedKey = this.config.keys.computed;
|
||||
if (computedKey in data && isPlainObject(data[computedKey]?.permalink)) {
|
||||
for (let key of Object.keys(data[computedKey].permalink)) {
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (keys.size) {
|
||||
this.render = keys.has("build");
|
||||
}
|
||||
}
|
||||
|
||||
setFromPermalink(templatePermalink) {
|
||||
// this.render is duplicated between TemplatePermalink and `setRenderViaDataCascade` above
|
||||
this.render = templatePermalink._isRendered;
|
||||
|
||||
this.write = templatePermalink._writeToFileSystem;
|
||||
}
|
||||
}
|
||||
export default TemplateBehavior;
|
||||
104
node_modules/@11ty/eleventy/src/TemplateCache.js
generated
vendored
Normal file
104
node_modules/@11ty/eleventy/src/TemplateCache.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import eventBus from "./EventBus.js";
|
||||
|
||||
// Note: this is only used for TemplateLayout right now but could be used for more
|
||||
// Just be careful because right now the TemplateLayout cache keys are not directly mapped to paths
|
||||
// So you may get collisions if you use this for other things.
|
||||
class TemplateCache {
|
||||
constructor() {
|
||||
this.cache = {};
|
||||
this.cacheByInputPath = {};
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.cache = {};
|
||||
this.cacheByInputPath = {};
|
||||
}
|
||||
|
||||
// alias
|
||||
removeAll() {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
size() {
|
||||
return Object.keys(this.cacheByInputPath).length;
|
||||
}
|
||||
|
||||
add(layoutTemplate) {
|
||||
let keys = new Set();
|
||||
|
||||
if (typeof layoutTemplate === "string") {
|
||||
throw new Error(
|
||||
"Invalid argument type passed to TemplateCache->add(). Should be a TemplateLayout.",
|
||||
);
|
||||
}
|
||||
|
||||
if ("getFullKey" in layoutTemplate) {
|
||||
keys.add(layoutTemplate.getFullKey());
|
||||
}
|
||||
|
||||
if ("getKey" in layoutTemplate) {
|
||||
// if `key` was an alias, also set to the pathed layout value too
|
||||
// e.g. `layout: "default"` and `layout: "default.liquid"` will both map to the same template.
|
||||
keys.add(layoutTemplate.getKey());
|
||||
}
|
||||
|
||||
for (let key of keys) {
|
||||
this.cache[key] = layoutTemplate;
|
||||
}
|
||||
|
||||
// also the full template input path for use with eleventy --serve/--watch e.g. `_includes/default.liquid` (see `remove` below)
|
||||
let fullPath = TemplatePath.stripLeadingDotSlash(layoutTemplate.inputPath);
|
||||
this.cacheByInputPath[fullPath] = layoutTemplate;
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return key in this.cache;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
if (!this.has(key)) {
|
||||
throw new Error(`Could not find ${key} in TemplateCache.`);
|
||||
}
|
||||
|
||||
return this.cache[key];
|
||||
}
|
||||
|
||||
remove(layoutFilePath) {
|
||||
layoutFilePath = TemplatePath.stripLeadingDotSlash(layoutFilePath);
|
||||
if (!this.cacheByInputPath[layoutFilePath]) {
|
||||
// not a layout file
|
||||
return;
|
||||
}
|
||||
|
||||
let layoutTemplate = this.cacheByInputPath[layoutFilePath];
|
||||
layoutTemplate.resetCaches();
|
||||
|
||||
let keys = layoutTemplate.getCacheKeys();
|
||||
for (let key of keys) {
|
||||
delete this.cache[key];
|
||||
}
|
||||
|
||||
delete this.cacheByInputPath[layoutFilePath];
|
||||
}
|
||||
}
|
||||
|
||||
let layoutCache = new TemplateCache();
|
||||
|
||||
eventBus.on("eleventy.resourceModified", (path, usedBy, metadata = {}) => {
|
||||
// https://github.com/11ty/eleventy-plugin-bundle/issues/10
|
||||
if (metadata.viaConfigReset) {
|
||||
layoutCache.removeAll();
|
||||
} else if (metadata.relevantLayouts?.length) {
|
||||
// reset the appropriate layouts relevant to the file.
|
||||
for (let layoutPath of metadata.relevantLayouts || []) {
|
||||
layoutCache.remove(layoutPath);
|
||||
}
|
||||
} else {
|
||||
layoutCache.remove(path);
|
||||
}
|
||||
});
|
||||
|
||||
// singleton
|
||||
export default layoutCache;
|
||||
81
node_modules/@11ty/eleventy/src/TemplateCollection.js
generated
vendored
Executable file
81
node_modules/@11ty/eleventy/src/TemplateCollection.js
generated
vendored
Executable file
@@ -0,0 +1,81 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import TemplateData from "./Data/TemplateData.js";
|
||||
import Sortable from "./Util/Objects/Sortable.js";
|
||||
import { isGlobMatch } from "./Util/GlobMatcher.js";
|
||||
|
||||
class TemplateCollection extends Sortable {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._filteredByGlobsCache = new Map();
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return this.items.slice();
|
||||
}
|
||||
|
||||
getAllSorted() {
|
||||
return this.sort(Sortable.sortFunctionDateInputPath);
|
||||
}
|
||||
|
||||
getSortedByDate() {
|
||||
return this.sort(Sortable.sortFunctionDate);
|
||||
}
|
||||
|
||||
getGlobs(globs) {
|
||||
if (typeof globs === "string") {
|
||||
globs = [globs];
|
||||
}
|
||||
|
||||
globs = globs.map((glob) => TemplatePath.addLeadingDotSlash(glob));
|
||||
|
||||
return globs;
|
||||
}
|
||||
|
||||
getFilteredByGlob(globs) {
|
||||
globs = this.getGlobs(globs);
|
||||
|
||||
let key = globs.join("::");
|
||||
if (!this._dirty) {
|
||||
// Try to find a pre-sorted list and clone it.
|
||||
if (this._filteredByGlobsCache.has(key)) {
|
||||
return [...this._filteredByGlobsCache.get(key)];
|
||||
}
|
||||
} else if (this._filteredByGlobsCache.size) {
|
||||
// Blow away cache
|
||||
this._filteredByGlobsCache = new Map();
|
||||
}
|
||||
|
||||
let filtered = this.getAllSorted().filter((item) => {
|
||||
return isGlobMatch(item.inputPath, globs);
|
||||
});
|
||||
this._dirty = false;
|
||||
this._filteredByGlobsCache.set(key, [...filtered]);
|
||||
return filtered;
|
||||
}
|
||||
|
||||
getFilteredByTag(tagName) {
|
||||
return this.getAllSorted().filter((item) => {
|
||||
if (!tagName || TemplateData.getIncludedTagNames(item.data).includes(tagName)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
getFilteredByTags(...tags) {
|
||||
return this.getAllSorted().filter((item) => {
|
||||
let itemTags = TemplateData.getIncludedTagNames(item.data);
|
||||
return tags.every((requiredTag) => {
|
||||
if (Array.isArray(itemTags)) {
|
||||
return itemTags.includes(requiredTag);
|
||||
} else {
|
||||
return itemTags === requiredTag;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateCollection;
|
||||
551
node_modules/@11ty/eleventy/src/TemplateConfig.js
generated
vendored
Normal file
551
node_modules/@11ty/eleventy/src/TemplateConfig.js
generated
vendored
Normal file
@@ -0,0 +1,551 @@
|
||||
import fs from "node:fs";
|
||||
import chalk from "kleur";
|
||||
import { Merge, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import { EleventyImportRaw, EleventyImportRawFromEleventy } from "./Util/Require.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import UserConfig from "./UserConfig.js";
|
||||
import GlobalDependencyMap from "./GlobalDependencyMap.js";
|
||||
import ExistsCache from "./Util/ExistsCache.js";
|
||||
import eventBus from "./EventBus.js";
|
||||
import ProjectTemplateFormats from "./Util/ProjectTemplateFormats.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:TemplateConfig");
|
||||
const debugDev = debugUtil("Dev:Eleventy:TemplateConfig");
|
||||
|
||||
/**
|
||||
* @module 11ty/eleventy/TemplateConfig
|
||||
*/
|
||||
|
||||
/**
|
||||
* Config as used by the template.
|
||||
* @typedef {object} module:11ty/eleventy/TemplateConfig~TemplateConfig~config
|
||||
* @property {String} [pathPrefix] - The path prefix.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Errors in eleventy config.
|
||||
* @ignore
|
||||
*/
|
||||
class EleventyConfigError extends EleventyBaseError {}
|
||||
|
||||
/**
|
||||
* Errors in eleventy plugins.
|
||||
* @ignore
|
||||
*/
|
||||
class EleventyPluginError extends EleventyBaseError {}
|
||||
|
||||
/**
|
||||
* Config for a template.
|
||||
* @ignore
|
||||
* @param {{}} customRootConfig - tbd.
|
||||
* @param {String} projectConfigPath - Path to local project config.
|
||||
*/
|
||||
class TemplateConfig {
|
||||
#templateFormats;
|
||||
#runMode;
|
||||
#configManuallyDefined = false;
|
||||
/** @type {UserConfig} */
|
||||
#userConfig = new UserConfig();
|
||||
|
||||
constructor(customRootConfig, projectConfigPath) {
|
||||
/** @type {object} */
|
||||
this.overrides = {};
|
||||
|
||||
/**
|
||||
* @type {String}
|
||||
* @description Path to local project config.
|
||||
* @default .eleventy.js
|
||||
*/
|
||||
if (projectConfigPath !== undefined) {
|
||||
this.#configManuallyDefined = true;
|
||||
|
||||
if (!projectConfigPath) {
|
||||
// falsy skips config files
|
||||
this.projectConfigPaths = [];
|
||||
} else {
|
||||
this.projectConfigPaths = [projectConfigPath];
|
||||
}
|
||||
} else {
|
||||
this.projectConfigPaths = [
|
||||
".eleventy.js",
|
||||
"eleventy.config.js",
|
||||
"eleventy.config.mjs",
|
||||
"eleventy.config.cjs",
|
||||
];
|
||||
}
|
||||
|
||||
if (customRootConfig) {
|
||||
/**
|
||||
* @type {object}
|
||||
* @description Custom root config.
|
||||
*/
|
||||
this.customRootConfig = customRootConfig;
|
||||
debug("Warning: Using custom root config!");
|
||||
} else {
|
||||
this.customRootConfig = null;
|
||||
}
|
||||
|
||||
this.hasConfigMerged = false;
|
||||
this.isEsm = false;
|
||||
}
|
||||
|
||||
get userConfig() {
|
||||
return this.#userConfig;
|
||||
}
|
||||
|
||||
get aggregateBenchmark() {
|
||||
return this.userConfig.benchmarks.aggregate;
|
||||
}
|
||||
|
||||
/* Setter for Logger */
|
||||
setLogger(logger) {
|
||||
this.logger = logger;
|
||||
this.userConfig.logger = this.logger;
|
||||
}
|
||||
|
||||
/* Setter for Directories instance */
|
||||
setDirectories(directories) {
|
||||
this.directories = directories;
|
||||
this.userConfig.directories = directories.getUserspaceInstance();
|
||||
}
|
||||
|
||||
/* Setter for TemplateFormats instance */
|
||||
setTemplateFormats(templateFormats) {
|
||||
this.#templateFormats = templateFormats;
|
||||
}
|
||||
|
||||
get templateFormats() {
|
||||
if (!this.#templateFormats) {
|
||||
this.#templateFormats = new ProjectTemplateFormats();
|
||||
}
|
||||
return this.#templateFormats;
|
||||
}
|
||||
|
||||
/* Backwards compat */
|
||||
get inputDir() {
|
||||
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.
|
||||
*
|
||||
* @method
|
||||
* @returns {String|undefined} - The normalised local project config file path.
|
||||
*/
|
||||
getLocalProjectConfigFile() {
|
||||
let configFiles = this.getLocalProjectConfigFiles();
|
||||
// Add the configFiles[0] in case of a test, where no file exists on the file system
|
||||
let configFile = configFiles.find((path) => path && fs.existsSync(path)) || configFiles[0];
|
||||
if (configFile) {
|
||||
return configFile;
|
||||
}
|
||||
}
|
||||
|
||||
getLocalProjectConfigFiles() {
|
||||
if (this.projectConfigPaths?.length > 0) {
|
||||
return TemplatePath.addLeadingDotSlashArray(this.projectConfigPaths.filter((path) => path));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
setProjectUsingEsm(isEsmProject) {
|
||||
this.isEsm = !!isEsmProject;
|
||||
this.usesGraph.setIsEsm(isEsmProject);
|
||||
}
|
||||
|
||||
getIsProjectUsingEsm() {
|
||||
return this.isEsm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the configuration.
|
||||
*/
|
||||
async reset() {
|
||||
debugDev("Resetting configuration: TemplateConfig and UserConfig.");
|
||||
this.userConfig.reset();
|
||||
// await this.initializeRootConfig();
|
||||
await this.forceReloadConfig();
|
||||
this.usesGraph.reset();
|
||||
|
||||
// Clear the compile cache
|
||||
eventBus.emit("eleventy.compileCacheReset");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the configuration while in watch mode.
|
||||
*
|
||||
* @todo Add implementation.
|
||||
*/
|
||||
resetOnWatch() {
|
||||
// nothing yet
|
||||
}
|
||||
|
||||
hasInitialized() {
|
||||
return this.hasConfigMerged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async-friendly init method
|
||||
*/
|
||||
async init(overrides) {
|
||||
await this.initializeRootConfig();
|
||||
|
||||
if (overrides) {
|
||||
this.appendToRootConfig(overrides);
|
||||
}
|
||||
|
||||
this.config = await this.mergeConfig();
|
||||
this.hasConfigMerged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a reload of the configuration object.
|
||||
*/
|
||||
async forceReloadConfig() {
|
||||
this.hasConfigMerged = false;
|
||||
await this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the config object.
|
||||
*
|
||||
* @returns {{}} - The config object.
|
||||
*/
|
||||
getConfig() {
|
||||
if (!this.hasConfigMerged) {
|
||||
throw new Error("Invalid call to .getConfig(). Needs an .init() first.");
|
||||
}
|
||||
|
||||
return this.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the config path.
|
||||
*
|
||||
* @param {String} path - The new config path.
|
||||
*/
|
||||
async setProjectConfigPath(path) {
|
||||
this.#configManuallyDefined = true;
|
||||
|
||||
if (path !== undefined) {
|
||||
this.projectConfigPaths = [path];
|
||||
} else {
|
||||
this.projectConfigPaths = [];
|
||||
}
|
||||
|
||||
if (this.hasConfigMerged) {
|
||||
// merge it again
|
||||
debugDev("Merging in getConfig again after setting the local project config path.");
|
||||
await this.forceReloadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the path prefix.
|
||||
*
|
||||
* @param {String} pathPrefix - The new path prefix.
|
||||
*/
|
||||
setPathPrefix(pathPrefix) {
|
||||
if (pathPrefix && pathPrefix !== "/") {
|
||||
debug("Setting pathPrefix to %o", pathPrefix);
|
||||
this.overrides.pathPrefix = pathPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current path prefix denoting the root folder the output will be deployed to
|
||||
*
|
||||
* @returns {String} - The path prefix string
|
||||
*/
|
||||
getPathPrefix() {
|
||||
if (this.overrides.pathPrefix) {
|
||||
return this.overrides.pathPrefix;
|
||||
}
|
||||
|
||||
if (!this.hasConfigMerged) {
|
||||
throw new Error("Config has not yet merged. Needs `init()`.");
|
||||
}
|
||||
|
||||
return this.config?.pathPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstraps the config object.
|
||||
*/
|
||||
async initializeRootConfig() {
|
||||
this.rootConfig = this.customRootConfig;
|
||||
if (!this.rootConfig) {
|
||||
let { default: cfg } = await EleventyImportRawFromEleventy("./src/defaultConfig.js");
|
||||
this.rootConfig = cfg;
|
||||
}
|
||||
|
||||
if (typeof this.rootConfig === "function") {
|
||||
// Not yet using async in defaultConfig.js
|
||||
this.rootConfig = this.rootConfig.call(this, this.userConfig);
|
||||
}
|
||||
|
||||
debug("Default Eleventy config %o", this.rootConfig);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add additional overrides to the root config object, used for testing
|
||||
*
|
||||
* @param {object} - a subset of the return Object from the user’s config file.
|
||||
*/
|
||||
appendToRootConfig(obj) {
|
||||
Object.assign(this.rootConfig, obj);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process the userland plugins from the Config
|
||||
*
|
||||
* @param {object} - the return Object from the user’s config file.
|
||||
*/
|
||||
async processPlugins({ dir, pathPrefix }) {
|
||||
this.userConfig.dir = dir;
|
||||
this.userConfig.pathPrefix = pathPrefix;
|
||||
|
||||
// for Nested addPlugin calls, Issue #1925
|
||||
this.userConfig._enablePluginExecution();
|
||||
|
||||
let storedActiveNamespace = this.userConfig.activeNamespace;
|
||||
for (let { plugin, options, pluginNamespace } of this.userConfig.plugins) {
|
||||
try {
|
||||
this.userConfig.activeNamespace = pluginNamespace;
|
||||
await this.userConfig._executePlugin(plugin, options);
|
||||
} catch (e) {
|
||||
let name = this.userConfig._getPluginName(plugin);
|
||||
let namespaces = [storedActiveNamespace, pluginNamespace].filter((entry) => !!entry);
|
||||
|
||||
let namespaceStr = "";
|
||||
if (namespaces.length) {
|
||||
namespaceStr = ` (namespace: ${namespaces.join(".")})`;
|
||||
}
|
||||
|
||||
throw new EleventyPluginError(
|
||||
`Error processing ${name ? `the \`${name}\`` : "a"} plugin${namespaceStr}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.userConfig.activeNamespace = storedActiveNamespace;
|
||||
|
||||
this.userConfig._disablePluginExecution();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and executes the local configuration file
|
||||
*
|
||||
* @returns {Promise<object>} merged - The merged config file object.
|
||||
*/
|
||||
async requireLocalConfigFile() {
|
||||
let localConfig = {};
|
||||
let exportedConfig = {};
|
||||
|
||||
let path = this.projectConfigPaths.filter((path) => path).find((path) => fs.existsSync(path));
|
||||
|
||||
if (this.projectConfigPaths.length > 0 && this.#configManuallyDefined && !path) {
|
||||
throw new EleventyConfigError(
|
||||
"A configuration file was specified but not found: " + this.projectConfigPaths.join(", "),
|
||||
);
|
||||
}
|
||||
|
||||
debug(`Merging default config with ${path}`);
|
||||
if (path) {
|
||||
try {
|
||||
let { default: configDefaultReturn, config: exportedConfigObject } =
|
||||
await EleventyImportRaw(path, this.isEsm ? "esm" : "cjs");
|
||||
|
||||
exportedConfig = exportedConfigObject || {};
|
||||
|
||||
if (this.directories && Object.keys(exportedConfigObject?.dir || {}).length > 0) {
|
||||
debug(
|
||||
"Setting directories via `config.dir` export from config file: %o",
|
||||
exportedConfigObject.dir,
|
||||
);
|
||||
this.directories.setViaConfigObject(exportedConfigObject.dir);
|
||||
}
|
||||
|
||||
if (typeof configDefaultReturn === "function") {
|
||||
localConfig = await configDefaultReturn(this.userConfig);
|
||||
} else {
|
||||
localConfig = configDefaultReturn;
|
||||
}
|
||||
|
||||
// Removed a check for `filters` in 3.0.0-alpha.6 (now using addTransform instead) https://v3.11ty.dev/docs/config/#transforms
|
||||
} catch (err) {
|
||||
let isModuleError =
|
||||
err instanceof Error && (err?.message || "").includes("Cannot find module");
|
||||
|
||||
// TODO the error message here is bad and I feel bad (needs more accurate info)
|
||||
return Promise.reject(
|
||||
new EleventyConfigError(
|
||||
`Error in your Eleventy config file '${path}'.` +
|
||||
(isModuleError ? chalk.cyan(" You may need to run `npm install`.") : ""),
|
||||
err,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debug(
|
||||
"Project config file not found (not an error—skipping). Looked in: %o",
|
||||
this.projectConfigPaths,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
localConfig,
|
||||
exportedConfig,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges different config files together.
|
||||
*
|
||||
* @returns {Promise<object>} merged - The merged config file.
|
||||
*/
|
||||
async mergeConfig() {
|
||||
let { localConfig, exportedConfig } = await this.requireLocalConfigFile();
|
||||
|
||||
// Merge `export const config = {}` with `return {}` in config callback
|
||||
if (isPlainObject(exportedConfig)) {
|
||||
localConfig = Merge(localConfig || {}, exportedConfig);
|
||||
}
|
||||
|
||||
if (this.directories) {
|
||||
if (Object.keys(this.userConfig.directoryAssignments || {}).length > 0) {
|
||||
debug(
|
||||
"Setting directories via set*Directory configuration APIs %o",
|
||||
this.userConfig.directoryAssignments,
|
||||
);
|
||||
this.directories.setViaConfigObject(this.userConfig.directoryAssignments);
|
||||
}
|
||||
|
||||
if (localConfig && Object.keys(localConfig?.dir || {}).length > 0) {
|
||||
debug(
|
||||
"Setting directories via `dir` object return from configuration file: %o",
|
||||
localConfig.dir,
|
||||
);
|
||||
this.directories.setViaConfigObject(localConfig.dir);
|
||||
}
|
||||
}
|
||||
|
||||
// `templateFormats` is an override via `setTemplateFormats`
|
||||
if (this.userConfig?.templateFormats) {
|
||||
this.templateFormats.setViaConfig(this.userConfig.templateFormats);
|
||||
} else if (localConfig?.templateFormats || this.rootConfig?.templateFormats) {
|
||||
// Local project config or defaultConfig.js
|
||||
this.templateFormats.setViaConfig(
|
||||
localConfig.templateFormats || this.rootConfig?.templateFormats,
|
||||
);
|
||||
}
|
||||
|
||||
// `templateFormatsAdded` is additive via `addTemplateFormats`
|
||||
if (this.userConfig?.templateFormatsAdded) {
|
||||
this.templateFormats.addViaConfig(this.userConfig.templateFormatsAdded);
|
||||
}
|
||||
|
||||
let mergedConfig = Merge({}, this.rootConfig, localConfig);
|
||||
|
||||
// Setup a few properties for plugins:
|
||||
|
||||
// Set frozen templateFormats
|
||||
mergedConfig.templateFormats = Object.freeze(this.templateFormats.getTemplateFormats());
|
||||
|
||||
// Setup pathPrefix set via command line for plugin consumption
|
||||
if (this.overrides.pathPrefix) {
|
||||
mergedConfig.pathPrefix = this.overrides.pathPrefix;
|
||||
}
|
||||
|
||||
// Returning a falsy value (e.g. "") from user config should reset to the default value.
|
||||
if (!mergedConfig.pathPrefix) {
|
||||
mergedConfig.pathPrefix = this.rootConfig.pathPrefix;
|
||||
}
|
||||
|
||||
// This is not set in UserConfig.js so that getters aren’t converted to strings
|
||||
// We want to error if someone attempts to use a setter there.
|
||||
if (this.directories) {
|
||||
mergedConfig.directories = this.directories.getUserspaceInstance();
|
||||
}
|
||||
|
||||
// Delay processing plugins until after the result of localConfig is returned
|
||||
// But BEFORE the rest of the config options are merged
|
||||
// this way we can pass directories and other template information to plugins
|
||||
|
||||
await this.userConfig.events.emit("eleventy.beforeConfig", this.userConfig);
|
||||
|
||||
let pluginsBench = this.aggregateBenchmark.get("Processing plugins in config");
|
||||
pluginsBench.before();
|
||||
await this.processPlugins(mergedConfig);
|
||||
pluginsBench.after();
|
||||
|
||||
// Template formats added via plugins
|
||||
if (this.userConfig?.templateFormatsAdded) {
|
||||
this.templateFormats.addViaConfig(this.userConfig.templateFormatsAdded);
|
||||
mergedConfig.templateFormats = Object.freeze(this.templateFormats.getTemplateFormats());
|
||||
}
|
||||
|
||||
let eleventyConfigApiMergingObject = this.userConfig.getMergingConfigObject();
|
||||
|
||||
if ("templateFormats" in eleventyConfigApiMergingObject) {
|
||||
throw new Error(
|
||||
"Internal error: templateFormats should not return from `getMergingConfigObject`",
|
||||
);
|
||||
}
|
||||
|
||||
// Overrides are only used by pathPrefix
|
||||
debug("Configuration overrides: %o", this.overrides);
|
||||
Merge(mergedConfig, eleventyConfigApiMergingObject, this.overrides);
|
||||
|
||||
debug("Current configuration: %o", mergedConfig);
|
||||
|
||||
// Add to the merged config too
|
||||
mergedConfig.uses = this.usesGraph;
|
||||
|
||||
// this is used for the layouts event
|
||||
this.usesGraph.setConfig(mergedConfig);
|
||||
|
||||
return mergedConfig;
|
||||
}
|
||||
|
||||
get usesGraph() {
|
||||
if (!this._usesGraph) {
|
||||
this._usesGraph = new GlobalDependencyMap();
|
||||
this._usesGraph.setIsEsm(this.isEsm);
|
||||
this._usesGraph.setTemplateConfig(this);
|
||||
}
|
||||
return this._usesGraph;
|
||||
}
|
||||
|
||||
get uses() {
|
||||
if (!this.usesGraph) {
|
||||
throw new Error("The Eleventy Global Dependency Graph has not yet been initialized.");
|
||||
}
|
||||
return this.usesGraph;
|
||||
}
|
||||
|
||||
get existsCache() {
|
||||
if (!this._existsCache) {
|
||||
this._existsCache = new ExistsCache();
|
||||
this._existsCache.setDirectoryCheck(true);
|
||||
}
|
||||
return this._existsCache;
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateConfig;
|
||||
712
node_modules/@11ty/eleventy/src/TemplateContent.js
generated
vendored
Normal file
712
node_modules/@11ty/eleventy/src/TemplateContent.js
generated
vendored
Normal file
@@ -0,0 +1,712 @@
|
||||
import os from "node:os";
|
||||
|
||||
import fs from "graceful-fs";
|
||||
import matter from "gray-matter";
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
import chardet from "chardet";
|
||||
|
||||
import EleventyExtensionMap from "./EleventyExtensionMap.js";
|
||||
import TemplateData from "./Data/TemplateData.js";
|
||||
import TemplateRender from "./TemplateRender.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import EleventyErrorUtil from "./Errors/EleventyErrorUtil.js";
|
||||
import eventBus from "./EventBus.js";
|
||||
|
||||
const { set: lodashSet } = lodash;
|
||||
const debug = debugUtil("Eleventy:TemplateContent");
|
||||
const debugDiagnostic = debugUtil("Eleventy:Diagnostics");
|
||||
const debugDev = debugUtil("Dev:Eleventy:TemplateContent");
|
||||
|
||||
class TemplateContentConfigError extends EleventyBaseError {}
|
||||
class TemplateContentFrontMatterError extends EleventyBaseError {}
|
||||
class TemplateContentCompileError extends EleventyBaseError {}
|
||||
class TemplateContentRenderError extends EleventyBaseError {}
|
||||
|
||||
class TemplateContent {
|
||||
constructor(inputPath, templateConfig) {
|
||||
if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new TemplateContentConfigError(
|
||||
"Missing or invalid `templateConfig` argument to TemplateContent",
|
||||
);
|
||||
}
|
||||
this.eleventyConfig = templateConfig;
|
||||
this.inputPath = inputPath;
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get outputDir() {
|
||||
return this.dirs.output;
|
||||
}
|
||||
|
||||
getResetTypes(types) {
|
||||
if (types) {
|
||||
return Object.assign(
|
||||
{
|
||||
data: false,
|
||||
read: false,
|
||||
render: false,
|
||||
},
|
||||
types,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
data: true,
|
||||
read: true,
|
||||
render: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Called during an incremental build when the template instance is cached but needs to be reset because it has changed
|
||||
resetCaches(types) {
|
||||
types = this.getResetTypes(types);
|
||||
|
||||
if (types.read) {
|
||||
delete this.readingPromise;
|
||||
delete this.inputContent;
|
||||
delete this._frontMatterDataCache;
|
||||
}
|
||||
}
|
||||
|
||||
/* Used by tests */
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats([]);
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
set extensionMap(map) {
|
||||
this._extensionMap = map;
|
||||
}
|
||||
|
||||
set eleventyConfig(config) {
|
||||
this._config = config;
|
||||
|
||||
if (this._config.constructor.name === "TemplateConfig") {
|
||||
this._configOptions = this._config.getConfig();
|
||||
} else {
|
||||
throw new TemplateContentConfigError("Tried to get an TemplateConfig but none was found.");
|
||||
}
|
||||
}
|
||||
|
||||
get eleventyConfig() {
|
||||
if (this._config.constructor.name === "TemplateConfig") {
|
||||
return this._config;
|
||||
}
|
||||
throw new TemplateContentConfigError("Tried to get an TemplateConfig but none was found.");
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (this._config.constructor.name === "TemplateConfig" && !this._configOptions) {
|
||||
this._configOptions = this._config.getConfig();
|
||||
}
|
||||
|
||||
return this._configOptions;
|
||||
}
|
||||
|
||||
get bench() {
|
||||
return this.config.benchmarkManager.get("Aggregate");
|
||||
}
|
||||
|
||||
get engine() {
|
||||
return this.templateRender.engine;
|
||||
}
|
||||
|
||||
get templateRender() {
|
||||
if (!this.hasTemplateRender()) {
|
||||
throw new Error(`\`templateRender\` has not yet initialized on ${this.inputPath}`);
|
||||
}
|
||||
|
||||
return this._templateRender;
|
||||
}
|
||||
|
||||
hasTemplateRender() {
|
||||
return !!this._templateRender;
|
||||
}
|
||||
|
||||
async getTemplateRender() {
|
||||
if (!this._templateRender) {
|
||||
this._templateRender = new TemplateRender(this.inputPath, this.eleventyConfig);
|
||||
this._templateRender.extensionMap = this.extensionMap;
|
||||
await this._templateRender.init();
|
||||
}
|
||||
|
||||
return this._templateRender;
|
||||
}
|
||||
|
||||
// For monkey patchers
|
||||
get frontMatter() {
|
||||
if (this.frontMatterOverride) {
|
||||
return this.frontMatterOverride;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Unfortunately you’re using code that monkey patched some Eleventy internals and it isn’t async-friendly. Change your code to use the async `read()` method on the template instead!",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// For monkey patchers
|
||||
set frontMatter(contentOverride) {
|
||||
this.frontMatterOverride = contentOverride;
|
||||
}
|
||||
|
||||
getInputPath() {
|
||||
return this.inputPath;
|
||||
}
|
||||
|
||||
getInputDir() {
|
||||
return this.inputDir;
|
||||
}
|
||||
|
||||
isVirtualTemplate() {
|
||||
let def = this.getVirtualTemplateDefinition();
|
||||
return !!def;
|
||||
}
|
||||
|
||||
getVirtualTemplateDefinition() {
|
||||
let inputDirRelativeInputPath =
|
||||
this.eleventyConfig.directories.getInputPathRelativeToInputDirectory(this.inputPath);
|
||||
return this.config.virtualTemplates[inputDirRelativeInputPath];
|
||||
}
|
||||
|
||||
async #read() {
|
||||
let content = await this.inputContent;
|
||||
|
||||
if (content || content === "") {
|
||||
if (this.engine.useJavaScriptImport()) {
|
||||
return {
|
||||
data: {},
|
||||
content,
|
||||
};
|
||||
}
|
||||
|
||||
let options = this.config.frontMatterParsingOptions || {};
|
||||
let fm;
|
||||
try {
|
||||
// Added in 3.0, passed along to front matter engines
|
||||
options.filePath = this.inputPath;
|
||||
fm = matter(content, options);
|
||||
} catch (e) {
|
||||
throw new TemplateContentFrontMatterError(
|
||||
`Having trouble reading front matter from template ${this.inputPath}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
|
||||
if (options.excerpt && fm.excerpt) {
|
||||
let excerptString = fm.excerpt + (options.excerpt_separator || "---");
|
||||
if (fm.content.startsWith(excerptString + os.EOL)) {
|
||||
// with an os-specific newline after excerpt separator
|
||||
fm.content = fm.excerpt.trim() + "\n" + fm.content.slice((excerptString + os.EOL).length);
|
||||
} else if (fm.content.startsWith(excerptString + "\n")) {
|
||||
// with a newline (\n) after excerpt separator
|
||||
// This is necessary for some git configurations on windows
|
||||
fm.content = fm.excerpt.trim() + "\n" + fm.content.slice((excerptString + 1).length);
|
||||
} else if (fm.content.startsWith(excerptString)) {
|
||||
// no newline after excerpt separator
|
||||
fm.content = fm.excerpt + fm.content.slice(excerptString.length);
|
||||
}
|
||||
|
||||
// alias, defaults to page.excerpt
|
||||
let alias = options.excerpt_alias || "page.excerpt";
|
||||
lodashSet(fm.data, alias, fm.excerpt);
|
||||
}
|
||||
|
||||
// For monkey patchers that used `frontMatter` 🤧
|
||||
// https://github.com/11ty/eleventy/issues/613#issuecomment-999637109
|
||||
// https://github.com/11ty/eleventy/issues/2710#issuecomment-1373854834
|
||||
// Removed this._frontMatter monkey patcher help in 3.0.0-alpha.7
|
||||
|
||||
return fm;
|
||||
} else {
|
||||
return {
|
||||
data: {},
|
||||
content: "",
|
||||
excerpt: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async read() {
|
||||
if (!this.readingPromise) {
|
||||
if (!this.inputContent) {
|
||||
// @cachedproperty
|
||||
this.inputContent = this.getInputContent();
|
||||
}
|
||||
|
||||
// @cachedproperty
|
||||
this.readingPromise = this.#read();
|
||||
}
|
||||
|
||||
return this.readingPromise;
|
||||
}
|
||||
|
||||
/* Incremental builds cache the Template instances (in TemplateWriter) but
|
||||
* these template specific caches are important for Pagination */
|
||||
static cache(path, content) {
|
||||
this._inputCache.set(TemplatePath.absolutePath(path), content);
|
||||
}
|
||||
|
||||
static getCached(path) {
|
||||
return this._inputCache.get(TemplatePath.absolutePath(path));
|
||||
}
|
||||
|
||||
static deleteFromInputCache(path) {
|
||||
this._inputCache.delete(TemplatePath.absolutePath(path));
|
||||
}
|
||||
|
||||
// Used via clone
|
||||
setInputContent(content) {
|
||||
this.inputContent = content;
|
||||
}
|
||||
|
||||
async getInputContent() {
|
||||
let tr = await this.getTemplateRender();
|
||||
|
||||
let virtualTemplateDefinition = this.getVirtualTemplateDefinition();
|
||||
if (virtualTemplateDefinition) {
|
||||
let { content } = virtualTemplateDefinition;
|
||||
return content;
|
||||
}
|
||||
|
||||
if (
|
||||
tr.engine.useJavaScriptImport() &&
|
||||
typeof tr.engine.getInstanceFromInputPath === "function"
|
||||
) {
|
||||
return tr.engine.getInstanceFromInputPath(this.inputPath);
|
||||
}
|
||||
|
||||
if (!tr.engine.needsToReadFileContents()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let templateBenchmark = this.bench.get("Template Read");
|
||||
templateBenchmark.before();
|
||||
|
||||
let content;
|
||||
|
||||
if (this.config.useTemplateCache) {
|
||||
content = TemplateContent.getCached(this.inputPath);
|
||||
}
|
||||
|
||||
if (!content && content !== "") {
|
||||
let contentBuffer = fs.readFileSync(this.inputPath);
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
// Warning: this is slow!!
|
||||
let encoding = chardet.detect(contentBuffer);
|
||||
|
||||
if (encoding.startsWith("UTF-16")) {
|
||||
debug("Warning: %o encoding detected on %o.", encoding, this.inputPath);
|
||||
debugDiagnostic(
|
||||
"%o encoding detected on %o. Please re-save as UTF-8.",
|
||||
encoding,
|
||||
this.inputPath,
|
||||
);
|
||||
} else {
|
||||
debug("%o encoding detected on %o.", encoding, this.inputPath);
|
||||
}
|
||||
}
|
||||
|
||||
content = contentBuffer.toString("utf8");
|
||||
|
||||
if (this.config.useTemplateCache) {
|
||||
TemplateContent.cache(this.inputPath, content);
|
||||
}
|
||||
}
|
||||
|
||||
templateBenchmark.after();
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
async _testGetFrontMatter() {
|
||||
let fm = this.frontMatterOverride ? this.frontMatterOverride : await this.read();
|
||||
|
||||
return fm;
|
||||
}
|
||||
|
||||
async getPreRender() {
|
||||
let fm = this.frontMatterOverride ? this.frontMatterOverride : await this.read();
|
||||
|
||||
return fm.content;
|
||||
}
|
||||
|
||||
async #getFrontMatterData() {
|
||||
let fm = await this.read();
|
||||
|
||||
// gray-matter isn’t async-friendly but can return a promise from custom front matter
|
||||
if (fm.data instanceof Promise) {
|
||||
fm.data = await fm.data;
|
||||
}
|
||||
|
||||
let extraData = await this.engine.getExtraDataFromFile(this.inputPath);
|
||||
|
||||
let virtualTemplateDefinition = this.getVirtualTemplateDefinition();
|
||||
let virtualTemplateData;
|
||||
if (virtualTemplateDefinition) {
|
||||
virtualTemplateData = virtualTemplateDefinition.data;
|
||||
}
|
||||
|
||||
let data = TemplateData.mergeDeep(false, fm.data, extraData, virtualTemplateData);
|
||||
let cleanedData = TemplateData.cleanupData(data);
|
||||
|
||||
return {
|
||||
data: cleanedData,
|
||||
excerpt: fm.excerpt,
|
||||
};
|
||||
}
|
||||
|
||||
async getFrontMatterData() {
|
||||
if (!this._frontMatterDataCache) {
|
||||
// @cachedproperty
|
||||
this._frontMatterDataCache = this.#getFrontMatterData();
|
||||
}
|
||||
|
||||
return this._frontMatterDataCache;
|
||||
}
|
||||
|
||||
async getEngineOverride() {
|
||||
let { data: frontMatterData } = await this.getFrontMatterData();
|
||||
return frontMatterData[this.config.keys.engineOverride];
|
||||
}
|
||||
|
||||
_getCompileCache(str) {
|
||||
// Caches used to be bifurcated based on engine name, now they’re based on inputPath
|
||||
let inputPathMap = TemplateContent._compileCache.get(this.inputPath);
|
||||
if (!inputPathMap) {
|
||||
inputPathMap = new Map();
|
||||
TemplateContent._compileCache.set(this.inputPath, inputPathMap);
|
||||
}
|
||||
|
||||
let cacheable = this.engine.cacheable;
|
||||
let { useCache, key } = this.engine.getCompileCacheKey(str, this.inputPath);
|
||||
|
||||
// We also tie the compile cache key to the UserConfig instance, to alleviate issues with global template cache
|
||||
// Better to move the cache to the Eleventy instance instead, no?
|
||||
// (This specifically failed I18nPluginTest cases with filters being cached across tests and not having access to each plugin’s options)
|
||||
key = this.eleventyConfig.userConfig._getUniqueId() + key;
|
||||
|
||||
return [cacheable, key, inputPathMap, useCache];
|
||||
}
|
||||
|
||||
async compile(str, options = {}) {
|
||||
let { type, bypassMarkdown, engineOverride } = options;
|
||||
|
||||
let tr = await this.getTemplateRender();
|
||||
|
||||
if (engineOverride !== undefined) {
|
||||
debugDev("%o overriding template engine to use %o", this.inputPath, engineOverride);
|
||||
await tr.setEngineOverride(engineOverride, bypassMarkdown);
|
||||
} else {
|
||||
tr.setUseMarkdown(!bypassMarkdown);
|
||||
}
|
||||
|
||||
if (bypassMarkdown && !this.engine.needsCompilation(str)) {
|
||||
return function () {
|
||||
return str;
|
||||
};
|
||||
}
|
||||
|
||||
debugDev("%o compile() using engine: %o", this.inputPath, tr.engineName);
|
||||
|
||||
try {
|
||||
let res;
|
||||
if (this.config.useTemplateCache) {
|
||||
let [cacheable, key, cache, useCache] = this._getCompileCache(str);
|
||||
if (cacheable && key) {
|
||||
if (useCache && cache.has(key)) {
|
||||
this.bench.get("(count) Template Compile Cache Hit").incrementCount();
|
||||
return cache.get(key);
|
||||
}
|
||||
|
||||
this.bench.get("(count) Template Compile Cache Miss").incrementCount();
|
||||
|
||||
// Compile cache is cleared when the resource is modified (below)
|
||||
|
||||
// Compilation is async, so we eagerly cache a Promise that eventually
|
||||
// resolves to the compiled function
|
||||
cache.set(
|
||||
key,
|
||||
new Promise((resolve) => {
|
||||
res = resolve;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let typeStr = type ? ` ${type}` : "";
|
||||
let templateBenchmark = this.bench.get(`Template Compile${typeStr}`);
|
||||
let inputPathBenchmark = this.bench.get(`> Compile${typeStr} > ${this.inputPath}`);
|
||||
templateBenchmark.before();
|
||||
inputPathBenchmark.before();
|
||||
let fn = await tr.getCompiledTemplate(str);
|
||||
inputPathBenchmark.after();
|
||||
templateBenchmark.after();
|
||||
debugDev("%o getCompiledTemplate function created", this.inputPath);
|
||||
if (this.config.useTemplateCache && res) {
|
||||
res(fn);
|
||||
}
|
||||
return fn;
|
||||
} catch (e) {
|
||||
let [cacheable, key, cache] = this._getCompileCache(str);
|
||||
if (cacheable && key) {
|
||||
cache.delete(key);
|
||||
}
|
||||
debug(`Having trouble compiling template ${this.inputPath}: %O`, str);
|
||||
throw new TemplateContentCompileError(
|
||||
`Having trouble compiling template ${this.inputPath}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getParseForSymbolsFunction(str) {
|
||||
let engine = this.engine;
|
||||
|
||||
// Don’t use markdown as the engine to parse for symbols
|
||||
// TODO pass in engineOverride here
|
||||
let preprocessorEngine = this.templateRender.getPreprocessorEngine();
|
||||
if (preprocessorEngine && engine.getName() !== preprocessorEngine) {
|
||||
let replacementEngine = this.templateRender.getEngineByName(preprocessorEngine);
|
||||
if (replacementEngine) {
|
||||
engine = replacementEngine;
|
||||
}
|
||||
}
|
||||
|
||||
if ("parseForSymbols" in engine) {
|
||||
return () => {
|
||||
return engine.parseForSymbols(str);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// used by computed data or for permalink functions
|
||||
async _renderFunction(fn, ...args) {
|
||||
let mixins = Object.assign({}, this.config.javascriptFunctions);
|
||||
let result = await fn.call(mixins, ...args);
|
||||
|
||||
// normalize Buffer away if returned from permalink
|
||||
if (Buffer.isBuffer(result)) {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async renderComputedData(str, data) {
|
||||
if (typeof str === "function") {
|
||||
return this._renderFunction(str, data);
|
||||
}
|
||||
|
||||
return this._render(str, data, {
|
||||
type: "Computed Data",
|
||||
bypassMarkdown: true,
|
||||
});
|
||||
}
|
||||
|
||||
async renderPermalink(permalink, data) {
|
||||
let permalinkCompilation = this.engine.permalinkNeedsCompilation(permalink);
|
||||
|
||||
// No string compilation:
|
||||
// ({ compileOptions: { permalink: "raw" }})
|
||||
// These mean `permalink: false`, which is no file system writing:
|
||||
// ({ compileOptions: { permalink: false }})
|
||||
// ({ compileOptions: { permalink: () => false }})
|
||||
// ({ compileOptions: { permalink: () => (() = > false) }})
|
||||
if (permalinkCompilation === false) {
|
||||
return permalink;
|
||||
}
|
||||
|
||||
/* Custom `compile` function for permalinks, usage:
|
||||
permalink: function(permalinkString, inputPath) {
|
||||
return async function(data) {
|
||||
return "THIS IS MY RENDERED PERMALINK";
|
||||
}
|
||||
}
|
||||
*/
|
||||
if (permalinkCompilation && typeof permalinkCompilation === "function") {
|
||||
permalink = await this._renderFunction(permalinkCompilation, permalink, this.inputPath);
|
||||
}
|
||||
|
||||
// Raw permalink function (in the app code data cascade)
|
||||
if (typeof permalink === "function") {
|
||||
return this._renderFunction(permalink, data);
|
||||
}
|
||||
|
||||
return this._render(permalink, data, {
|
||||
type: "Permalink",
|
||||
bypassMarkdown: true,
|
||||
});
|
||||
}
|
||||
|
||||
async render(str, data, bypassMarkdown) {
|
||||
return this._render(str, data, {
|
||||
bypassMarkdown,
|
||||
type: "",
|
||||
});
|
||||
}
|
||||
|
||||
_getPaginationLogSuffix(data) {
|
||||
let suffix = [];
|
||||
if ("pagination" in data) {
|
||||
suffix.push(" (");
|
||||
if (data.pagination.pages) {
|
||||
suffix.push(
|
||||
`${data.pagination.pages.length} page${data.pagination.pages.length !== 1 ? "s" : ""}`,
|
||||
);
|
||||
} else {
|
||||
suffix.push("Pagination");
|
||||
}
|
||||
suffix.push(")");
|
||||
}
|
||||
return suffix.join("");
|
||||
}
|
||||
|
||||
async _render(str, data, options = {}) {
|
||||
let { bypassMarkdown, type } = options;
|
||||
|
||||
try {
|
||||
if (bypassMarkdown && !this.engine.needsCompilation(str)) {
|
||||
return str;
|
||||
}
|
||||
|
||||
let fn = await this.compile(str, {
|
||||
bypassMarkdown,
|
||||
engineOverride: data[this.config.keys.engineOverride],
|
||||
type,
|
||||
});
|
||||
|
||||
if (fn === undefined) {
|
||||
return;
|
||||
} else if (typeof fn !== "function") {
|
||||
throw new Error(`The \`compile\` function did not return a function. Received ${fn}`);
|
||||
}
|
||||
|
||||
// Benchmark
|
||||
let templateBenchmark = this.bench.get("Render");
|
||||
let inputPathBenchmark = this.bench.get(
|
||||
`> Render${type ? ` ${type}` : ""} > ${this.inputPath}${this._getPaginationLogSuffix(data)}`,
|
||||
);
|
||||
|
||||
templateBenchmark.before();
|
||||
if (inputPathBenchmark) {
|
||||
inputPathBenchmark.before();
|
||||
}
|
||||
|
||||
let rendered = await fn(data);
|
||||
|
||||
if (inputPathBenchmark) {
|
||||
inputPathBenchmark.after();
|
||||
}
|
||||
templateBenchmark.after();
|
||||
debugDev("%o getCompiledTemplate called, rendered content created", this.inputPath);
|
||||
return rendered;
|
||||
} catch (e) {
|
||||
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
|
||||
return Promise.reject(e);
|
||||
} else {
|
||||
let tr = await this.getTemplateRender();
|
||||
let engine = tr.getReadableEnginesList();
|
||||
debug(`Having trouble rendering ${engine} template ${this.inputPath}: %O`, str);
|
||||
return Promise.reject(
|
||||
new TemplateContentRenderError(
|
||||
`Having trouble rendering ${engine} template ${this.inputPath}`,
|
||||
e,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getExtensionEntries() {
|
||||
return this.engine.extensionEntries;
|
||||
}
|
||||
|
||||
isFileRelevantToThisTemplate(incrementalFile, metadata = {}) {
|
||||
// always relevant if incremental file not set (build everything)
|
||||
if (!incrementalFile) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let hasDependencies = this.engine.hasDependencies(incrementalFile);
|
||||
|
||||
let isRelevant = this.engine.isFileRelevantTo(this.inputPath, incrementalFile);
|
||||
|
||||
debug(
|
||||
"Test dependencies to see if %o is relevant to %o: %o",
|
||||
this.inputPath,
|
||||
incrementalFile,
|
||||
isRelevant,
|
||||
);
|
||||
|
||||
let extensionEntries = this.getExtensionEntries().filter((entry) => !!entry.isIncrementalMatch);
|
||||
if (extensionEntries.length) {
|
||||
for (let entry of extensionEntries) {
|
||||
if (
|
||||
entry.isIncrementalMatch.call(
|
||||
{
|
||||
inputPath: this.inputPath,
|
||||
isFullTemplate: metadata.isFullTemplate,
|
||||
isFileRelevantToInputPath: isRelevant,
|
||||
doesFileHaveDependencies: hasDependencies,
|
||||
},
|
||||
incrementalFile,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// Not great way of building all templates if this is a layout, include, JS dependency.
|
||||
// TODO improve this for default template syntaxes
|
||||
|
||||
// This is the fallback way of determining if something is incremental (no isIncrementalMatch available)
|
||||
// This will be true if the inputPath and incrementalFile are the same
|
||||
if (isRelevant) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// only return true here if dependencies are not known
|
||||
if (!hasDependencies && !metadata.isFullTemplate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
TemplateContent._inputCache = new Map();
|
||||
TemplateContent._compileCache = new Map();
|
||||
eventBus.on("eleventy.resourceModified", (path) => {
|
||||
// delete from input cache
|
||||
TemplateContent.deleteFromInputCache(path);
|
||||
|
||||
// delete from compile cache
|
||||
let normalized = TemplatePath.addLeadingDotSlash(path);
|
||||
let compileCache = TemplateContent._compileCache.get(normalized);
|
||||
if (compileCache) {
|
||||
compileCache.clear();
|
||||
}
|
||||
});
|
||||
|
||||
// Used when the configuration file reset https://github.com/11ty/eleventy/issues/2147
|
||||
eventBus.on("eleventy.compileCacheReset", (/*path*/) => {
|
||||
TemplateContent._compileCache = new Map();
|
||||
});
|
||||
|
||||
export default TemplateContent;
|
||||
57
node_modules/@11ty/eleventy/src/TemplateFileSlug.js
generated
vendored
Normal file
57
node_modules/@11ty/eleventy/src/TemplateFileSlug.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
import path from "node:path";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
class TemplateFileSlug {
|
||||
constructor(inputPath, extensionMap, eleventyConfig) {
|
||||
let inputDir = eleventyConfig.directories.input;
|
||||
if (inputDir) {
|
||||
inputPath = TemplatePath.stripLeadingSubPath(inputPath, inputDir);
|
||||
}
|
||||
|
||||
this.inputPath = inputPath;
|
||||
this.cleanInputPath = inputPath.replace(/^.\//, "");
|
||||
|
||||
let dirs = this.cleanInputPath.split("/");
|
||||
this.dirs = dirs;
|
||||
this.dirs.pop();
|
||||
|
||||
this.parsed = path.parse(inputPath);
|
||||
this.filenameNoExt = extensionMap.removeTemplateExtension(this.parsed.base);
|
||||
}
|
||||
|
||||
// `page.filePathStem` see https://v3.11ty.dev/docs/data-eleventy-supplied/#page-variable
|
||||
getFullPathWithoutExtension() {
|
||||
return "/" + TemplatePath.join(...this.dirs, this._getRawSlug());
|
||||
}
|
||||
|
||||
_getRawSlug() {
|
||||
let slug = this.filenameNoExt;
|
||||
return this._stripDateFromSlug(slug);
|
||||
}
|
||||
|
||||
/** Removes dates in the format of YYYY-MM-DD from a given slug string candidate. */
|
||||
_stripDateFromSlug(slug) {
|
||||
let reg = slug.match(/\d{4}-\d{2}-\d{2}-(.*)/);
|
||||
if (reg) {
|
||||
return reg[1];
|
||||
}
|
||||
return slug;
|
||||
}
|
||||
|
||||
// `page.fileSlug` see https://v3.11ty.dev/docs/data-eleventy-supplied/#page-variable
|
||||
getSlug() {
|
||||
let rawSlug = this._getRawSlug();
|
||||
|
||||
if (rawSlug === "index") {
|
||||
if (!this.dirs.length) {
|
||||
return "";
|
||||
}
|
||||
let lastDir = this.dirs[this.dirs.length - 1];
|
||||
return this._stripDateFromSlug(lastDir);
|
||||
}
|
||||
|
||||
return rawSlug;
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateFileSlug;
|
||||
35
node_modules/@11ty/eleventy/src/TemplateGlob.js
generated
vendored
Normal file
35
node_modules/@11ty/eleventy/src/TemplateGlob.js
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
class TemplateGlob {
|
||||
static normalizePath(...paths) {
|
||||
if (paths[0].charAt(0) === "!") {
|
||||
throw new Error(
|
||||
`TemplateGlob.normalizePath does not accept ! glob paths like: ${paths.join("")}`,
|
||||
);
|
||||
}
|
||||
return TemplatePath.addLeadingDotSlash(TemplatePath.join(...paths));
|
||||
}
|
||||
|
||||
static normalize(path) {
|
||||
path = path.trim();
|
||||
if (path.charAt(0) === "!") {
|
||||
return "!" + TemplateGlob.normalizePath(path.slice(1));
|
||||
} else {
|
||||
return TemplateGlob.normalizePath(path);
|
||||
}
|
||||
}
|
||||
|
||||
static map(files) {
|
||||
if (typeof files === "string") {
|
||||
return TemplateGlob.normalize(files);
|
||||
} else if (Array.isArray(files)) {
|
||||
return files.map(function (path) {
|
||||
return TemplateGlob.normalize(path);
|
||||
});
|
||||
} else {
|
||||
return files;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateGlob;
|
||||
242
node_modules/@11ty/eleventy/src/TemplateLayout.js
generated
vendored
Normal file
242
node_modules/@11ty/eleventy/src/TemplateLayout.js
generated
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import TemplateLayoutPathResolver from "./TemplateLayoutPathResolver.js";
|
||||
import TemplateContent from "./TemplateContent.js";
|
||||
import TemplateData from "./Data/TemplateData.js";
|
||||
import templateCache from "./TemplateCache.js";
|
||||
|
||||
// const debug = debugUtil("Eleventy:TemplateLayout");
|
||||
const debugDev = debugUtil("Dev:Eleventy:TemplateLayout");
|
||||
|
||||
class TemplateLayout extends TemplateContent {
|
||||
constructor(key, extensionMap, eleventyConfig) {
|
||||
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new Error("Expected `eleventyConfig` in TemplateLayout constructor.");
|
||||
}
|
||||
|
||||
let resolver = new TemplateLayoutPathResolver(key, extensionMap, eleventyConfig);
|
||||
let resolvedPath = resolver.getFullPath();
|
||||
|
||||
super(resolvedPath, eleventyConfig);
|
||||
|
||||
if (!extensionMap) {
|
||||
throw new Error("Expected `extensionMap` in TemplateLayout constructor.");
|
||||
}
|
||||
|
||||
this.extensionMap = extensionMap;
|
||||
this.key = resolver.getNormalizedLayoutKey();
|
||||
this.dataKeyLayoutPath = key;
|
||||
this.inputPath = resolvedPath;
|
||||
}
|
||||
|
||||
getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
getFullKey() {
|
||||
return TemplateLayout.resolveFullKey(this.dataKeyLayoutPath, this.inputDir);
|
||||
}
|
||||
|
||||
getCacheKeys() {
|
||||
return new Set([this.dataKeyLayoutPath, this.getFullKey(), this.key]);
|
||||
}
|
||||
|
||||
static resolveFullKey(key, inputDir) {
|
||||
return TemplatePath.join(inputDir, key);
|
||||
}
|
||||
|
||||
static getTemplate(key, eleventyConfig, extensionMap) {
|
||||
let config = eleventyConfig.getConfig();
|
||||
if (!config.useTemplateCache) {
|
||||
return new TemplateLayout(key, extensionMap, eleventyConfig);
|
||||
}
|
||||
|
||||
let inputDir = eleventyConfig.directories.input;
|
||||
let fullKey = TemplateLayout.resolveFullKey(key, inputDir);
|
||||
if (!templateCache.has(fullKey)) {
|
||||
let layout = new TemplateLayout(key, extensionMap, eleventyConfig);
|
||||
|
||||
templateCache.add(layout);
|
||||
debugDev("Added %o to TemplateCache", key);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
return templateCache.get(fullKey);
|
||||
}
|
||||
|
||||
async getTemplateLayoutMapEntry() {
|
||||
let { data: frontMatterData } = await this.getFrontMatterData();
|
||||
return {
|
||||
// Used by `TemplateLayout.getTemplate()`
|
||||
key: this.dataKeyLayoutPath,
|
||||
|
||||
// used by `this.getData()`
|
||||
frontMatterData,
|
||||
};
|
||||
}
|
||||
|
||||
async #getTemplateLayoutMap() {
|
||||
// For both the eleventy.layouts event and cyclical layout chain checking (e.g., a => b => c => a)
|
||||
let layoutChain = new Set();
|
||||
layoutChain.add(this.inputPath);
|
||||
|
||||
let cfgKey = this.config.keys.layout;
|
||||
let map = [];
|
||||
let mapEntry = await this.getTemplateLayoutMapEntry();
|
||||
|
||||
map.push(mapEntry);
|
||||
|
||||
while (mapEntry.frontMatterData && cfgKey in mapEntry.frontMatterData) {
|
||||
// Layout of the current layout
|
||||
let parentLayoutKey = mapEntry.frontMatterData[cfgKey];
|
||||
|
||||
let layout = TemplateLayout.getTemplate(
|
||||
parentLayoutKey,
|
||||
this.eleventyConfig,
|
||||
this.extensionMap,
|
||||
);
|
||||
|
||||
// Abort if a circular layout chain is detected. Otherwise, we'll time out and run out of memory.
|
||||
if (layoutChain.has(layout.inputPath)) {
|
||||
throw new Error(
|
||||
`Your layouts have a circular reference, starting at ${map[0].key}! The layout at ${layout.inputPath} was specified twice in this layout chain.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Keep track of this layout so we can detect duplicates in subsequent iterations
|
||||
layoutChain.add(layout.inputPath);
|
||||
|
||||
// reassign for next loop
|
||||
mapEntry = await layout.getTemplateLayoutMapEntry();
|
||||
|
||||
map.push(mapEntry);
|
||||
}
|
||||
|
||||
this.layoutChain = Array.from(layoutChain);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
async getTemplateLayoutMap() {
|
||||
if (!this.cachedLayoutMap) {
|
||||
this.cachedLayoutMap = this.#getTemplateLayoutMap();
|
||||
}
|
||||
|
||||
return this.cachedLayoutMap;
|
||||
}
|
||||
|
||||
async getLayoutChain() {
|
||||
if (!Array.isArray(this.layoutChain)) {
|
||||
await this.getTemplateLayoutMap();
|
||||
}
|
||||
|
||||
return this.layoutChain;
|
||||
}
|
||||
|
||||
async #getData() {
|
||||
let map = await this.getTemplateLayoutMap();
|
||||
let dataToMerge = [];
|
||||
for (let j = map.length - 1; j >= 0; j--) {
|
||||
dataToMerge.push(map[j].frontMatterData);
|
||||
}
|
||||
|
||||
// Deep merge of layout front matter
|
||||
let data = TemplateData.mergeDeep(this.config.dataDeepMerge, {}, ...dataToMerge);
|
||||
delete data[this.config.keys.layout];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getData() {
|
||||
if (!this.dataCache) {
|
||||
this.dataCache = this.#getData();
|
||||
}
|
||||
|
||||
return this.dataCache;
|
||||
}
|
||||
|
||||
async #getCachedCompiledLayoutFunction() {
|
||||
let rawInput = await this.getPreRender();
|
||||
let renderFunction = await this.compile(rawInput);
|
||||
return renderFunction;
|
||||
}
|
||||
|
||||
// Do only cache this layout’s render function and delegate the rest to the other templates.
|
||||
async getCachedCompiledLayoutFunction() {
|
||||
if (!this.cachedCompiledLayoutFunction) {
|
||||
this.cachedCompiledLayoutFunction = this.#getCachedCompiledLayoutFunction();
|
||||
}
|
||||
|
||||
return this.cachedCompiledLayoutFunction;
|
||||
}
|
||||
|
||||
async getCompiledLayoutFunctions() {
|
||||
let layoutMap = await this.getTemplateLayoutMap();
|
||||
let fns = [];
|
||||
|
||||
try {
|
||||
fns.push({
|
||||
render: await this.getCachedCompiledLayoutFunction(),
|
||||
});
|
||||
|
||||
if (layoutMap.length > 1) {
|
||||
let [, /*currentLayout*/ parentLayout] = layoutMap;
|
||||
let { key } = parentLayout;
|
||||
|
||||
let layoutTemplate = TemplateLayout.getTemplate(
|
||||
key,
|
||||
this.eleventyConfig,
|
||||
this.extensionMap,
|
||||
);
|
||||
|
||||
// The parent already includes the rest of the layout chain
|
||||
let upstreamFns = await layoutTemplate.getCompiledLayoutFunctions();
|
||||
for (let j = 0, k = upstreamFns.length; j < k; j++) {
|
||||
fns.push(upstreamFns[j]);
|
||||
}
|
||||
}
|
||||
|
||||
return fns;
|
||||
} catch (e) {
|
||||
debugDev("Clearing TemplateCache after error.");
|
||||
templateCache.clear();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async render() {
|
||||
throw new Error("Internal error: `render` was removed from TemplateLayout.js in Eleventy 3.0.");
|
||||
}
|
||||
|
||||
// Inefficient? We want to compile all the templatelayouts into a single reusable callback?
|
||||
// Trouble: layouts may need data variables present downstream/upstream
|
||||
// This is called from Template->renderPageEntry
|
||||
async renderPageEntry(pageEntry) {
|
||||
let templateContent = pageEntry.templateContent;
|
||||
let compiledFunctions = await this.getCompiledLayoutFunctions();
|
||||
for (let { render } of compiledFunctions) {
|
||||
let data = {
|
||||
content: templateContent,
|
||||
...pageEntry.data,
|
||||
};
|
||||
|
||||
templateContent = await render(data);
|
||||
}
|
||||
|
||||
// Don’t set `templateContent` on pageEntry because collection items should not have layout markup
|
||||
return templateContent;
|
||||
}
|
||||
|
||||
resetCaches(types) {
|
||||
super.resetCaches(types);
|
||||
|
||||
delete this.dataCache;
|
||||
delete this.layoutChain;
|
||||
delete this.cachedLayoutMap;
|
||||
delete this.cachedCompiledLayoutFunction;
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateLayout;
|
||||
136
node_modules/@11ty/eleventy/src/TemplateLayoutPathResolver.js
generated
vendored
Normal file
136
node_modules/@11ty/eleventy/src/TemplateLayoutPathResolver.js
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
import fs from "node:fs";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
// import debugUtil from "debug";
|
||||
// const debug = debugUtil("Eleventy:TemplateLayoutPathResolver");
|
||||
|
||||
class TemplateLayoutPathResolver {
|
||||
constructor(path, extensionMap, eleventyConfig) {
|
||||
if (!eleventyConfig) {
|
||||
throw new Error("Expected `eleventyConfig` in TemplateLayoutPathResolver constructor");
|
||||
}
|
||||
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
this.originalPath = path;
|
||||
this.originalDisplayPath =
|
||||
TemplatePath.join(this.layoutsDir, this.originalPath) +
|
||||
` (via \`layout: ${this.originalPath}\`)`; // for error messaging
|
||||
|
||||
this.path = path;
|
||||
this.aliases = {};
|
||||
this.extensionMap = extensionMap;
|
||||
if (!extensionMap) {
|
||||
throw new Error("Expected `extensionMap` in TemplateLayoutPathResolver constructor.");
|
||||
}
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
getVirtualTemplate(layoutPath) {
|
||||
let inputDirRelativePath =
|
||||
this.eleventyConfig.directories.getLayoutPathRelativeToInputDirectory(layoutPath);
|
||||
return this.config.virtualTemplates[inputDirRelativePath];
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get layoutsDir() {
|
||||
return this.dirs.layouts || this.dirs.includes;
|
||||
}
|
||||
|
||||
/* Backwards compat */
|
||||
getLayoutsDir() {
|
||||
return this.layoutsDir;
|
||||
}
|
||||
|
||||
setAliases() {
|
||||
this.aliases = Object.assign({}, this.config.layoutAliases, this.aliases);
|
||||
}
|
||||
|
||||
// for testing
|
||||
set config(cfg) {
|
||||
this._config = cfg;
|
||||
this.init();
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (this.eleventyConfig) {
|
||||
return this.eleventyConfig.getConfig();
|
||||
} else {
|
||||
throw new Error("Missing this.eleventyConfig");
|
||||
}
|
||||
}
|
||||
|
||||
exists(layoutPath) {
|
||||
if (this.getVirtualTemplate(layoutPath)) {
|
||||
return true;
|
||||
}
|
||||
let fullPath = this.eleventyConfig.directories.getLayoutPath(layoutPath);
|
||||
if (fs.existsSync(fullPath)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
init() {
|
||||
// we might be able to move this into the constructor?
|
||||
this.aliases = Object.assign({}, this.config.layoutAliases, this.aliases);
|
||||
|
||||
if (this.aliases[this.path]) {
|
||||
this.path = this.aliases[this.path];
|
||||
}
|
||||
|
||||
let useLayoutResolution = this.config.layoutResolution;
|
||||
|
||||
if (this.path.split(".").length > 0 && this.exists(this.path)) {
|
||||
this.filename = this.path;
|
||||
this.fullPath = this.eleventyConfig.directories.getLayoutPath(this.path);
|
||||
} else if (useLayoutResolution) {
|
||||
this.filename = this.findFileName();
|
||||
this.fullPath = this.eleventyConfig.directories.getLayoutPath(this.filename || "");
|
||||
}
|
||||
}
|
||||
|
||||
addLayoutAlias(from, to) {
|
||||
this.aliases[from] = to;
|
||||
}
|
||||
|
||||
getFileName() {
|
||||
if (!this.filename) {
|
||||
throw new Error(
|
||||
`You’re trying to use a layout that does not exist: ${this.originalDisplayPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
return this.filename;
|
||||
}
|
||||
|
||||
getFullPath() {
|
||||
if (!this.filename) {
|
||||
throw new Error(
|
||||
`You’re trying to use a layout that does not exist: ${this.originalDisplayPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
return this.fullPath;
|
||||
}
|
||||
|
||||
findFileName() {
|
||||
for (let filename of this.extensionMap.getFileList(this.path)) {
|
||||
if (this.exists(filename)) {
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getNormalizedLayoutKey() {
|
||||
return TemplatePath.stripLeadingSubPath(this.fullPath, this.layoutsDir);
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateLayoutPathResolver;
|
||||
828
node_modules/@11ty/eleventy/src/TemplateMap.js
generated
vendored
Normal file
828
node_modules/@11ty/eleventy/src/TemplateMap.js
generated
vendored
Normal file
@@ -0,0 +1,828 @@
|
||||
import { DepGraph as DependencyGraph } from "dependency-graph";
|
||||
import { isPlainObject, TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import TemplateCollection from "./TemplateCollection.js";
|
||||
import EleventyErrorUtil from "./Errors/EleventyErrorUtil.js";
|
||||
import UsingCircularTemplateContentReferenceError from "./Errors/UsingCircularTemplateContentReferenceError.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import DuplicatePermalinkOutputError from "./Errors/DuplicatePermalinkOutputError.js";
|
||||
import TemplateData from "./Data/TemplateData.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:TemplateMap");
|
||||
const debugDev = debugUtil("Dev:Eleventy:TemplateMap");
|
||||
|
||||
class TemplateMapConfigError extends EleventyBaseError {}
|
||||
class EleventyDataSchemaError extends EleventyBaseError {}
|
||||
|
||||
// These template URL filenames are allowed to exclude file extensions
|
||||
const EXTENSIONLESS_URL_ALLOWLIST = [
|
||||
"/_redirects", // Netlify specific
|
||||
"/.htaccess", // Apache
|
||||
"/_headers", // Cloudflare
|
||||
];
|
||||
|
||||
class TemplateMap {
|
||||
constructor(eleventyConfig) {
|
||||
if (!eleventyConfig) {
|
||||
throw new TemplateMapConfigError("Missing config argument.");
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
this.map = [];
|
||||
this.collectionsData = null;
|
||||
this.cached = false;
|
||||
this.verboseOutput = true;
|
||||
this.collection = new TemplateCollection();
|
||||
}
|
||||
|
||||
set userConfig(config) {
|
||||
this._userConfig = config;
|
||||
}
|
||||
|
||||
get userConfig() {
|
||||
if (!this._userConfig) {
|
||||
// TODO use this.config for this, need to add collections to mergeable props in userconfig
|
||||
this._userConfig = this.eleventyConfig.userConfig;
|
||||
}
|
||||
|
||||
return this._userConfig;
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (!this._config) {
|
||||
this._config = this.eleventyConfig.getConfig();
|
||||
}
|
||||
return this._config;
|
||||
}
|
||||
|
||||
static get tagPrefix() {
|
||||
return "___TAG___";
|
||||
}
|
||||
|
||||
async add(template) {
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = await template.getData();
|
||||
let entries = await template.getTemplateMapEntries(data);
|
||||
|
||||
for (let map of entries) {
|
||||
this.map.push(map);
|
||||
}
|
||||
}
|
||||
|
||||
getMap() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
getTagTarget(str) {
|
||||
if (str.startsWith("collections.")) {
|
||||
return str.slice("collections.".length);
|
||||
}
|
||||
// Fixes #2851
|
||||
if (str.startsWith("collections['") || str.startsWith('collections["')) {
|
||||
return str.slice("collections['".length, -2);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---
|
||||
* pagination:
|
||||
* data: collections
|
||||
* ---
|
||||
*/
|
||||
isPaginationOverAllCollections(entry) {
|
||||
if (entry.data.pagination?.data) {
|
||||
return (
|
||||
entry.data.pagination.data === "collections" ||
|
||||
entry.data.pagination.data === "collections.all"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getPaginationTagTarget(entry) {
|
||||
if (entry.data.pagination?.data) {
|
||||
return this.getTagTarget(entry.data.pagination.data);
|
||||
}
|
||||
}
|
||||
|
||||
addTagsToGraph(graph, inputPath, tags) {
|
||||
if (!Array.isArray(tags)) {
|
||||
return;
|
||||
}
|
||||
for (let tag of tags) {
|
||||
let tagWithPrefix = TemplateMap.tagPrefix + tag;
|
||||
if (!graph.hasNode(tagWithPrefix)) {
|
||||
graph.addNode(tagWithPrefix);
|
||||
}
|
||||
|
||||
// Populates to collections.tagName
|
||||
// Dependency from tag to inputPath
|
||||
graph.addDependency(tagWithPrefix, inputPath);
|
||||
}
|
||||
}
|
||||
|
||||
addDeclaredDependenciesToGraph(graph, inputPath, deps) {
|
||||
if (!Array.isArray(deps)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let tag of deps) {
|
||||
let tagWithPrefix = TemplateMap.tagPrefix + tag;
|
||||
if (!graph.hasNode(tagWithPrefix)) {
|
||||
graph.addNode(tagWithPrefix);
|
||||
}
|
||||
|
||||
// Dependency from inputPath to collection/tag
|
||||
graph.addDependency(inputPath, tagWithPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude: Pagination templates consuming `collections` or `collections.all`
|
||||
// Exclude: Pagination templates that consume config API collections
|
||||
|
||||
// Include: Pagination templates that don’t consume config API collections
|
||||
// Include: Templates that don’t use Pagination
|
||||
getMappedDependencies() {
|
||||
let graph = new DependencyGraph();
|
||||
let tagPrefix = TemplateMap.tagPrefix;
|
||||
|
||||
graph.addNode(tagPrefix + "all");
|
||||
|
||||
for (let entry of this.map) {
|
||||
if (this.isPaginationOverAllCollections(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// using Pagination (but not targeting a user config collection)
|
||||
let paginationTagTarget = this.getPaginationTagTarget(entry);
|
||||
if (paginationTagTarget) {
|
||||
if (this.isUserConfigCollectionName(paginationTagTarget)) {
|
||||
// delay this one to the second stage
|
||||
continue;
|
||||
} else {
|
||||
// using pagination but over a tagged collection
|
||||
graph.addNode(entry.inputPath);
|
||||
if (!graph.hasNode(tagPrefix + paginationTagTarget)) {
|
||||
graph.addNode(tagPrefix + paginationTagTarget);
|
||||
}
|
||||
graph.addDependency(entry.inputPath, tagPrefix + paginationTagTarget);
|
||||
}
|
||||
} else {
|
||||
// not using pagination
|
||||
graph.addNode(entry.inputPath);
|
||||
}
|
||||
|
||||
let collections = TemplateData.getIncludedCollectionNames(entry.data);
|
||||
this.addTagsToGraph(graph, entry.inputPath, collections);
|
||||
|
||||
this.addDeclaredDependenciesToGraph(
|
||||
graph,
|
||||
entry.inputPath,
|
||||
entry.data.eleventyImport?.collections,
|
||||
);
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
// Exclude: Pagination templates consuming `collections` or `collections.all`
|
||||
// Include: Pagination templates that consume config API collections
|
||||
getDelayedMappedDependencies() {
|
||||
let graph = new DependencyGraph();
|
||||
let tagPrefix = TemplateMap.tagPrefix;
|
||||
|
||||
graph.addNode(tagPrefix + "all");
|
||||
|
||||
let userConfigCollections = this.getUserConfigCollectionNames();
|
||||
|
||||
// Add tags from named user config collections
|
||||
for (let tag of userConfigCollections) {
|
||||
graph.addNode(tagPrefix + tag);
|
||||
}
|
||||
|
||||
for (let entry of this.map) {
|
||||
if (this.isPaginationOverAllCollections(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let paginationTagTarget = this.getPaginationTagTarget(entry);
|
||||
if (paginationTagTarget && this.isUserConfigCollectionName(paginationTagTarget)) {
|
||||
if (!graph.hasNode(entry.inputPath)) {
|
||||
graph.addNode(entry.inputPath);
|
||||
}
|
||||
graph.addDependency(entry.inputPath, tagPrefix + paginationTagTarget);
|
||||
|
||||
let collections = TemplateData.getIncludedCollectionNames(entry.data);
|
||||
this.addTagsToGraph(graph, entry.inputPath, collections);
|
||||
|
||||
this.addDeclaredDependenciesToGraph(
|
||||
graph,
|
||||
entry.inputPath,
|
||||
entry.data.eleventyImport?.collections,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
// Exclude: Pagination templates consuming `collections.all`
|
||||
// Include: Pagination templates consuming `collections`
|
||||
getPaginatedOverCollectionsMappedDependencies() {
|
||||
let graph = new DependencyGraph();
|
||||
let tagPrefix = TemplateMap.tagPrefix;
|
||||
let allNodeAdded = false;
|
||||
|
||||
for (let entry of this.map) {
|
||||
if (this.isPaginationOverAllCollections(entry) && !this.getPaginationTagTarget(entry)) {
|
||||
if (!allNodeAdded) {
|
||||
graph.addNode(tagPrefix + "all");
|
||||
allNodeAdded = true;
|
||||
}
|
||||
|
||||
if (!graph.hasNode(entry.inputPath)) {
|
||||
graph.addNode(entry.inputPath);
|
||||
}
|
||||
|
||||
let collectionNames = TemplateData.getIncludedCollectionNames(entry.data);
|
||||
if (collectionNames.includes("all")) {
|
||||
// collections.all
|
||||
graph.addDependency(tagPrefix + "all", entry.inputPath);
|
||||
|
||||
// Note that `tags` are otherwise ignored here
|
||||
}
|
||||
|
||||
this.addDeclaredDependenciesToGraph(
|
||||
graph,
|
||||
entry.inputPath,
|
||||
entry.data.eleventyImport?.collections,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
// Include: Pagination templates consuming `collections.all`
|
||||
getPaginatedOverAllCollectionMappedDependencies() {
|
||||
let graph = new DependencyGraph();
|
||||
let tagPrefix = TemplateMap.tagPrefix;
|
||||
let allNodeAdded = false;
|
||||
|
||||
for (let entry of this.map) {
|
||||
if (
|
||||
this.isPaginationOverAllCollections(entry) &&
|
||||
this.getPaginationTagTarget(entry) === "all"
|
||||
) {
|
||||
if (!allNodeAdded) {
|
||||
graph.addNode(tagPrefix + "all");
|
||||
allNodeAdded = true;
|
||||
}
|
||||
|
||||
if (!graph.hasNode(entry.inputPath)) {
|
||||
graph.addNode(entry.inputPath);
|
||||
}
|
||||
|
||||
let collectionNames = TemplateData.getIncludedCollectionNames(entry.data);
|
||||
if (collectionNames.includes("all")) {
|
||||
// Populates into collections.all
|
||||
// This is circular!
|
||||
graph.addDependency(tagPrefix + "all", entry.inputPath);
|
||||
|
||||
// Note that `tags` are otherwise ignored here
|
||||
}
|
||||
|
||||
this.addDeclaredDependenciesToGraph(
|
||||
graph,
|
||||
entry.inputPath,
|
||||
entry.data.eleventyImport?.collections,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
getTemplateMapDependencyGraph() {
|
||||
return [
|
||||
this.getMappedDependencies(),
|
||||
this.getDelayedMappedDependencies(),
|
||||
this.getPaginatedOverCollectionsMappedDependencies(),
|
||||
this.getPaginatedOverAllCollectionMappedDependencies(),
|
||||
];
|
||||
}
|
||||
|
||||
getFullTemplateMapOrder() {
|
||||
// convert dependency graphs to ordered arrays
|
||||
return this.getTemplateMapDependencyGraph().map((entry) => entry.overallOrder());
|
||||
}
|
||||
|
||||
#addEntryToGlobalDependencyGraph(entry) {
|
||||
let paginationTagTarget = this.getPaginationTagTarget(entry);
|
||||
if (paginationTagTarget) {
|
||||
this.config.uses.addDependencyConsumesCollection(entry.inputPath, paginationTagTarget);
|
||||
}
|
||||
|
||||
let collectionNames = TemplateData.getIncludedCollectionNames(entry.data);
|
||||
for (let name of collectionNames) {
|
||||
this.config.uses.addDependencyPublishesToCollection(entry.inputPath, name);
|
||||
}
|
||||
|
||||
if (Array.isArray(entry.data.eleventyImport?.collections)) {
|
||||
for (let tag of entry.data.eleventyImport.collections) {
|
||||
this.config.uses.addDependencyConsumesCollection(entry.inputPath, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addAllToGlobalDependencyGraph() {
|
||||
for (let entry of this.map) {
|
||||
this.#addEntryToGlobalDependencyGraph(entry);
|
||||
}
|
||||
}
|
||||
|
||||
async setCollectionByTagName(tagName) {
|
||||
if (this.isUserConfigCollectionName(tagName)) {
|
||||
// async
|
||||
this.collectionsData[tagName] = await this.getUserConfigCollection(tagName);
|
||||
} else {
|
||||
this.collectionsData[tagName] = this.getTaggedCollection(tagName);
|
||||
}
|
||||
|
||||
let precompiled = this.config.precompiledCollections;
|
||||
if (precompiled?.[tagName]) {
|
||||
if (
|
||||
tagName === "all" ||
|
||||
!Array.isArray(this.collectionsData[tagName]) ||
|
||||
this.collectionsData[tagName].length === 0
|
||||
) {
|
||||
this.collectionsData[tagName] = precompiled[tagName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(slightlyoff): major bottleneck
|
||||
async initDependencyMap(dependencyMap) {
|
||||
let tagPrefix = TemplateMap.tagPrefix;
|
||||
for (let depEntry of dependencyMap) {
|
||||
if (depEntry.startsWith(tagPrefix)) {
|
||||
// is a tag (collection) entry
|
||||
let tagName = depEntry.slice(tagPrefix.length);
|
||||
await this.setCollectionByTagName(tagName);
|
||||
} else {
|
||||
// is a template entry
|
||||
let map = this.getMapEntryForInputPath(depEntry);
|
||||
map._pages = await map.template.getTemplates(map.data);
|
||||
|
||||
if (map._pages.length === 0) {
|
||||
// Reminder: a serverless code path was removed here.
|
||||
} else {
|
||||
let counter = 0;
|
||||
for (let page of map._pages) {
|
||||
// Copy outputPath to map entry
|
||||
// This is no longer used internally, just for backwards compatibility
|
||||
// Error added in v3 for https://github.com/11ty/eleventy/issues/3183
|
||||
if (map.data.pagination) {
|
||||
if (!Object.prototype.hasOwnProperty.call(map, "outputPath")) {
|
||||
Object.defineProperty(map, "outputPath", {
|
||||
get() {
|
||||
throw new Error(
|
||||
"Internal error: `.outputPath` on a paginated map entry is not consistent. Use `_pages[…].outputPath` instead.",
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (!map.outputPath) {
|
||||
map.outputPath = page.outputPath;
|
||||
}
|
||||
|
||||
if (counter === 0 || map.data.pagination?.addAllPagesToCollections) {
|
||||
if (map.data.eleventyExcludeFromCollections !== true) {
|
||||
// is in *some* collections
|
||||
this.collection.add(page);
|
||||
}
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async cache() {
|
||||
debug("Caching collections objects.");
|
||||
this.collectionsData = {};
|
||||
|
||||
for (let entry of this.map) {
|
||||
entry.data.collections = this.collectionsData;
|
||||
}
|
||||
|
||||
let [dependencyMap, delayedDependencyMap, firstPaginatedDepMap, secondPaginatedDepMap] =
|
||||
this.getFullTemplateMapOrder();
|
||||
|
||||
await this.initDependencyMap(dependencyMap);
|
||||
await this.initDependencyMap(delayedDependencyMap);
|
||||
await this.initDependencyMap(firstPaginatedDepMap);
|
||||
await this.initDependencyMap(secondPaginatedDepMap);
|
||||
|
||||
await this.resolveRemainingComputedData();
|
||||
|
||||
let orderedPaths = this.getOrderedInputPaths(
|
||||
dependencyMap,
|
||||
delayedDependencyMap,
|
||||
firstPaginatedDepMap,
|
||||
secondPaginatedDepMap,
|
||||
);
|
||||
|
||||
let orderedMap = orderedPaths.map((inputPath) => {
|
||||
return this.getMapEntryForInputPath(inputPath);
|
||||
});
|
||||
|
||||
await this.config.events.emitLazy("eleventy.contentMap", () => {
|
||||
return {
|
||||
inputPathToUrl: this.generateInputUrlContentMap(orderedMap),
|
||||
urlToInputPath: this.generateUrlMap(orderedMap),
|
||||
};
|
||||
});
|
||||
|
||||
await this.runDataSchemas(orderedMap);
|
||||
await this.populateContentDataInMap(orderedMap);
|
||||
|
||||
this.populateCollectionsWithContent();
|
||||
this.cached = true;
|
||||
|
||||
this.checkForDuplicatePermalinks();
|
||||
this.checkForMissingFileExtensions();
|
||||
|
||||
await this.config.events.emitLazy("eleventy.layouts", () => this.generateLayoutsMap());
|
||||
}
|
||||
|
||||
generateInputUrlContentMap(orderedMap) {
|
||||
let entries = {};
|
||||
for (let entry of orderedMap) {
|
||||
entries[entry.inputPath] = entry._pages.map((entry) => entry.url);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
generateUrlMap(orderedMap) {
|
||||
let entries = {};
|
||||
for (let entry of orderedMap) {
|
||||
for (let page of entry._pages) {
|
||||
// duplicate urls throw an error, so we can return non array here
|
||||
entries[page.url] = {
|
||||
inputPath: entry.inputPath,
|
||||
groupNumber: page.groupNumber,
|
||||
};
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
// TODO(slightlyoff): hot inner loop?
|
||||
getMapEntryForInputPath(inputPath) {
|
||||
for (let map of this.map) {
|
||||
if (map.inputPath === inputPath) {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out any tag nodes
|
||||
getOrderedInputPaths(...maps) {
|
||||
let orderedMap = [];
|
||||
let tagPrefix = TemplateMap.tagPrefix;
|
||||
|
||||
for (let map of maps) {
|
||||
for (let dep of map) {
|
||||
if (!dep.startsWith(tagPrefix)) {
|
||||
orderedMap.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
return orderedMap;
|
||||
}
|
||||
|
||||
async runDataSchemas(orderedMap) {
|
||||
for (let map of orderedMap) {
|
||||
if (!map._pages) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let pageEntry of map._pages) {
|
||||
// Data Schema callback #879
|
||||
if (typeof pageEntry.data[this.config.keys.dataSchema] === "function") {
|
||||
try {
|
||||
await pageEntry.data[this.config.keys.dataSchema](pageEntry.data);
|
||||
} catch (e) {
|
||||
throw new EleventyDataSchemaError(
|
||||
`Error in the data schema for: ${map.inputPath} (via \`eleventyDataSchema\`)`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async populateContentDataInMap(orderedMap) {
|
||||
let usedTemplateContentTooEarlyMap = [];
|
||||
|
||||
// Note that empty pagination templates will be skipped here as not renderable
|
||||
let filteredMap = orderedMap.filter((entry) => entry.template.isRenderable());
|
||||
|
||||
for (let map of filteredMap) {
|
||||
if (!map._pages) {
|
||||
throw new Error(`Internal error: _pages not found for ${map.inputPath}`);
|
||||
}
|
||||
|
||||
// IMPORTANT: this is where template content is rendered
|
||||
try {
|
||||
for (let pageEntry of map._pages) {
|
||||
pageEntry.templateContent =
|
||||
await pageEntry.template.renderPageEntryWithoutLayout(pageEntry);
|
||||
}
|
||||
} catch (e) {
|
||||
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
|
||||
usedTemplateContentTooEarlyMap.push(map);
|
||||
|
||||
// Reset cached render promise
|
||||
for (let pageEntry of map._pages) {
|
||||
pageEntry.template.resetCaches({ render: true });
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
debugDev("Added this.map[...].templateContent, outputPath, et al for one map entry");
|
||||
}
|
||||
|
||||
for (let map of usedTemplateContentTooEarlyMap) {
|
||||
try {
|
||||
for (let pageEntry of map._pages) {
|
||||
pageEntry.templateContent =
|
||||
await pageEntry.template.renderPageEntryWithoutLayout(pageEntry);
|
||||
}
|
||||
} catch (e) {
|
||||
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
|
||||
throw new UsingCircularTemplateContentReferenceError(
|
||||
`${map.inputPath} contains a circular reference (using collections) to its own templateContent.`,
|
||||
);
|
||||
} else {
|
||||
// rethrow?
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTaggedCollection(tag) {
|
||||
let result;
|
||||
if (!tag || tag === "all") {
|
||||
result = this.collection.getAllSorted();
|
||||
} else {
|
||||
result = this.collection.getFilteredByTag(tag);
|
||||
}
|
||||
debug(`Collection: collections.${tag || "all"} size: ${result.length}`);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* 3.0.0-alpha.1: setUserConfigCollections method removed (was only used for testing) */
|
||||
isUserConfigCollectionName(name) {
|
||||
let collections = this.userConfig.getCollections();
|
||||
return name && !!collections[name];
|
||||
}
|
||||
|
||||
getUserConfigCollectionNames() {
|
||||
return Object.keys(this.userConfig.getCollections());
|
||||
}
|
||||
|
||||
async getUserConfigCollection(name) {
|
||||
let configCollections = this.userConfig.getCollections();
|
||||
|
||||
// This works with async now
|
||||
let result = await configCollections[name](this.collection);
|
||||
|
||||
debug(`Collection: collections.${name} size: ${result.length}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
populateCollectionsWithContent() {
|
||||
for (let collectionName in this.collectionsData) {
|
||||
// skip custom collections set in configuration files that have arbitrary types
|
||||
if (!Array.isArray(this.collectionsData[collectionName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let item of this.collectionsData[collectionName]) {
|
||||
// skip custom collections set in configuration files that have arbitrary types
|
||||
if (!isPlainObject(item) || !("inputPath" in item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let entry = this.getMapEntryForInputPath(item.inputPath);
|
||||
// This check skips precompiled collections
|
||||
if (entry) {
|
||||
let index = item.pageNumber || 0;
|
||||
let content = entry._pages[index]._templateContent;
|
||||
if (content !== undefined) {
|
||||
item.templateContent = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async resolveRemainingComputedData() {
|
||||
let promises = [];
|
||||
for (let entry of this.map) {
|
||||
for (let pageEntry of entry._pages) {
|
||||
if (this.config.keys.computed in pageEntry.data) {
|
||||
promises.push(await pageEntry.template.resolveRemainingComputedData(pageEntry.data));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
async generateLayoutsMap() {
|
||||
let layouts = {};
|
||||
|
||||
for (let entry of this.map) {
|
||||
for (let page of entry._pages) {
|
||||
let tmpl = page.template;
|
||||
let layoutKey = page.data[this.config.keys.layout];
|
||||
if (layoutKey) {
|
||||
let layout = tmpl.getLayout(layoutKey);
|
||||
let layoutChain = await layout.getLayoutChain();
|
||||
let priors = [];
|
||||
for (let filepath of layoutChain) {
|
||||
if (!layouts[filepath]) {
|
||||
layouts[filepath] = new Set();
|
||||
}
|
||||
layouts[filepath].add(page.inputPath);
|
||||
for (let prior of priors) {
|
||||
layouts[filepath].add(prior);
|
||||
}
|
||||
priors.push(filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let key in layouts) {
|
||||
layouts[key] = Array.from(layouts[key]);
|
||||
}
|
||||
|
||||
return layouts;
|
||||
}
|
||||
|
||||
#onEachPage(callback) {
|
||||
for (let template of this.map) {
|
||||
for (let page of template._pages) {
|
||||
callback(page, template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkForDuplicatePermalinks() {
|
||||
let inputs = {};
|
||||
let permalinks = {};
|
||||
let warnings = {};
|
||||
this.#onEachPage((page, template) => {
|
||||
if (page.outputPath === false || page.url === false) {
|
||||
// do nothing (also serverless)
|
||||
} else {
|
||||
// Make sure output doesn’t overwrite input (e.g. --input=. --output=.)
|
||||
// Related to https://github.com/11ty/eleventy/issues/3327
|
||||
if (page.outputPath === page.inputPath) {
|
||||
throw new DuplicatePermalinkOutputError(
|
||||
`The template at "${page.inputPath}" attempted to overwrite itself.`,
|
||||
);
|
||||
} else if (inputs[page.outputPath]) {
|
||||
throw new DuplicatePermalinkOutputError(
|
||||
`The template at "${page.inputPath}" attempted to overwrite an existing template at "${page.outputPath}".`,
|
||||
);
|
||||
}
|
||||
inputs[page.inputPath] = true;
|
||||
|
||||
if (!permalinks[page.outputPath]) {
|
||||
permalinks[page.outputPath] = [template.inputPath];
|
||||
} else {
|
||||
warnings[page.outputPath] = `Output conflict: multiple input files are writing to \`${
|
||||
page.outputPath
|
||||
}\`. Use distinct \`permalink\` values to resolve this conflict.
|
||||
1. ${template.inputPath}
|
||||
${permalinks[page.outputPath]
|
||||
.map(function (inputPath, index) {
|
||||
return ` ${index + 2}. ${inputPath}\n`;
|
||||
})
|
||||
.join("")}
|
||||
`;
|
||||
permalinks[page.outputPath].push(template.inputPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let warningList = Object.values(warnings);
|
||||
if (warningList.length) {
|
||||
// throw one at a time
|
||||
throw new DuplicatePermalinkOutputError(warningList[0]);
|
||||
}
|
||||
}
|
||||
|
||||
checkForMissingFileExtensions() {
|
||||
// disabled in config
|
||||
if (this.userConfig?.errorReporting?.allowMissingExtensions === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#onEachPage((page) => {
|
||||
if (
|
||||
page.outputPath === false ||
|
||||
page.url === false ||
|
||||
page.data.eleventyAllowMissingExtension ||
|
||||
EXTENSIONLESS_URL_ALLOWLIST.some((url) => page.url.endsWith(url))
|
||||
) {
|
||||
// do nothing (also serverless)
|
||||
} else {
|
||||
if (TemplatePath.getExtension(page.outputPath) === "") {
|
||||
let e =
|
||||
new Error(`The template at '${page.inputPath}' attempted to write to '${page.outputPath}'${page.data.permalink ? ` (via \`permalink\` value: '${page.data.permalink}')` : ""}, which is a target on the file system that does not include a file extension.
|
||||
|
||||
You *probably* want to add a file extension to your permalink so that hosts will know how to correctly serve this file to web browsers. Without a file extension, this file may not be reliably deployed without additional hosting configuration (it won’t have a mime type) and may also cause local development issues if you later attempt to write to a subdirectory of the same name.
|
||||
|
||||
Learn more: https://v3.11ty.dev/docs/permalinks/#trailing-slashes
|
||||
|
||||
This is usually but not *always* an error so if you’d like to disable this error message, add \`eleventyAllowMissingExtension: true\` somewhere in the data cascade for this template or use \`eleventyConfig.configureErrorReporting({ allowMissingExtensions: true });\` to disable this feature globally.`);
|
||||
e.skipOriginalStack = true;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO move these into TemplateMapTest.js
|
||||
_testGetAllTags() {
|
||||
let allTags = {};
|
||||
for (let map of this.map) {
|
||||
let tags = map.data.tags;
|
||||
if (Array.isArray(tags)) {
|
||||
for (let tag of tags) {
|
||||
allTags[tag] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(allTags);
|
||||
}
|
||||
|
||||
async _testGetUserConfigCollectionsData() {
|
||||
let collections = {};
|
||||
let configCollections = this.userConfig.getCollections();
|
||||
|
||||
for (let name in configCollections) {
|
||||
collections[name] = configCollections[name](this.collection);
|
||||
|
||||
debug(`Collection: collections.${name} size: ${collections[name].length}`);
|
||||
}
|
||||
|
||||
return collections;
|
||||
}
|
||||
|
||||
async _testGetTaggedCollectionsData() {
|
||||
let collections = {};
|
||||
collections.all = this.collection.getAllSorted();
|
||||
debug(`Collection: collections.all size: ${collections.all.length}`);
|
||||
|
||||
let tags = this._testGetAllTags();
|
||||
for (let tag of tags) {
|
||||
collections[tag] = this.collection.getFilteredByTag(tag);
|
||||
debug(`Collection: collections.${tag} size: ${collections[tag].length}`);
|
||||
}
|
||||
return collections;
|
||||
}
|
||||
|
||||
async _testGetAllCollectionsData() {
|
||||
let collections = {};
|
||||
let taggedCollections = await this._testGetTaggedCollectionsData();
|
||||
Object.assign(collections, taggedCollections);
|
||||
|
||||
let userConfigCollections = await this._testGetUserConfigCollectionsData();
|
||||
Object.assign(collections, userConfigCollections);
|
||||
|
||||
return collections;
|
||||
}
|
||||
|
||||
async _testGetCollectionsData() {
|
||||
if (!this.cached) {
|
||||
await this.cache();
|
||||
}
|
||||
|
||||
return this.collectionsData;
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateMap;
|
||||
339
node_modules/@11ty/eleventy/src/TemplatePassthrough.js
generated
vendored
Normal file
339
node_modules/@11ty/eleventy/src/TemplatePassthrough.js
generated
vendored
Normal file
@@ -0,0 +1,339 @@
|
||||
import util from "node:util";
|
||||
import path from "node:path";
|
||||
import fs from "graceful-fs";
|
||||
|
||||
import isGlob from "is-glob";
|
||||
import copy from "@11ty/recursive-copy";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
|
||||
import ProjectDirectories from "./Util/ProjectDirectories.js";
|
||||
|
||||
const fsExists = util.promisify(fs.exists);
|
||||
|
||||
const debug = debugUtil("Eleventy:TemplatePassthrough");
|
||||
|
||||
class TemplatePassthroughError extends EleventyBaseError {}
|
||||
|
||||
class TemplatePassthrough {
|
||||
#isExistsCache = {};
|
||||
#isDirectoryCache = {};
|
||||
|
||||
constructor(path, eleventyConfig) {
|
||||
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new TemplatePassthroughError(
|
||||
"Missing `eleventyConfig` or was not an instance of `TemplateConfig`.",
|
||||
);
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
|
||||
this.benchmarks = {
|
||||
aggregate: this.config.benchmarkManager.get("Aggregate"),
|
||||
};
|
||||
|
||||
this.rawPath = path;
|
||||
|
||||
// inputPath is relative to the root of your project and not your Eleventy input directory.
|
||||
// TODO normalize these with forward slashes
|
||||
this.inputPath = this.normalizeDirectory(path.inputPath);
|
||||
this.isInputPathGlob = isGlob(this.inputPath);
|
||||
|
||||
this.outputPath = path.outputPath;
|
||||
|
||||
this.copyOptions = path.copyOptions; // custom options for recursive-copy
|
||||
|
||||
this.isDryRun = false;
|
||||
this.isIncremental = false;
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this.eleventyConfig.getConfig();
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
// inputDir is used when stripping from output path in `getOutputPath`
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get outputDir() {
|
||||
return this.dirs.output;
|
||||
}
|
||||
|
||||
/* { inputPath, outputPath } though outputPath is *not* the full path: just the output directory */
|
||||
getPath() {
|
||||
return this.rawPath;
|
||||
}
|
||||
|
||||
async getOutputPath(inputFileFromGlob) {
|
||||
let { inputDir, outputDir, outputPath, inputPath } = this;
|
||||
|
||||
if (outputPath === true) {
|
||||
// no explicit target, implied target
|
||||
if (this.isDirectory(inputPath)) {
|
||||
let inputRelativePath = TemplatePath.stripLeadingSubPath(
|
||||
inputFileFromGlob || inputPath,
|
||||
inputDir,
|
||||
);
|
||||
return ProjectDirectories.normalizeDirectory(
|
||||
TemplatePath.join(outputDir, inputRelativePath),
|
||||
);
|
||||
}
|
||||
|
||||
return TemplatePath.normalize(
|
||||
TemplatePath.join(
|
||||
outputDir,
|
||||
TemplatePath.stripLeadingSubPath(inputFileFromGlob || inputPath, inputDir),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (inputFileFromGlob) {
|
||||
return this.getOutputPathForGlobFile(inputFileFromGlob);
|
||||
}
|
||||
|
||||
// Has explicit target
|
||||
|
||||
// Bug when copying incremental file overwriting output directory (and making it a file)
|
||||
// e.g. public/test.css -> _site
|
||||
// https://github.com/11ty/eleventy/issues/2278
|
||||
let fullOutputPath = TemplatePath.normalize(TemplatePath.join(outputDir, outputPath));
|
||||
if (outputPath === "" || this.isDirectory(inputPath)) {
|
||||
fullOutputPath = ProjectDirectories.normalizeDirectory(fullOutputPath);
|
||||
}
|
||||
|
||||
// TODO room for improvement here:
|
||||
if (
|
||||
!this.isInputPathGlob &&
|
||||
(await fsExists(inputPath)) &&
|
||||
!this.isDirectory(inputPath) &&
|
||||
this.isDirectory(fullOutputPath)
|
||||
) {
|
||||
let filename = path.parse(inputPath).base;
|
||||
return TemplatePath.normalize(TemplatePath.join(fullOutputPath, filename));
|
||||
}
|
||||
|
||||
return fullOutputPath;
|
||||
}
|
||||
|
||||
async getOutputPathForGlobFile(inputFileFromGlob) {
|
||||
return TemplatePath.join(
|
||||
await this.getOutputPath(),
|
||||
TemplatePath.getLastPathSegment(inputFileFromGlob),
|
||||
);
|
||||
}
|
||||
|
||||
setDryRun(isDryRun) {
|
||||
this.isDryRun = !!isDryRun;
|
||||
}
|
||||
|
||||
setRunMode(runMode) {
|
||||
this.runMode = runMode;
|
||||
}
|
||||
|
||||
setIsIncremental(isIncremental) {
|
||||
this.isIncremental = isIncremental;
|
||||
}
|
||||
|
||||
setFileSystemSearch(fileSystemSearch) {
|
||||
this.fileSystemSearch = fileSystemSearch;
|
||||
}
|
||||
|
||||
async getFiles(glob) {
|
||||
debug("Searching for: %o", glob);
|
||||
let b = this.benchmarks.aggregate.get("Searching the file system (passthrough)");
|
||||
b.before();
|
||||
let files = TemplatePath.addLeadingDotSlashArray(
|
||||
await this.fileSystemSearch.search("passthrough", glob),
|
||||
);
|
||||
b.after();
|
||||
return files;
|
||||
}
|
||||
|
||||
isExists(dir) {
|
||||
if (this.#isExistsCache[dir] === undefined) {
|
||||
this.#isExistsCache[dir] = fs.existsSync(dir);
|
||||
}
|
||||
return this.#isExistsCache[dir];
|
||||
}
|
||||
|
||||
isDirectory(dir) {
|
||||
if (this.#isDirectoryCache[dir] === undefined) {
|
||||
if (isGlob(this.inputPath)) {
|
||||
this.#isDirectoryCache[dir] = false;
|
||||
} else if (!this.isExists(dir)) {
|
||||
this.#isDirectoryCache[dir] = false;
|
||||
} else if (fs.statSync(dir).isDirectory()) {
|
||||
this.#isDirectoryCache[dir] = true;
|
||||
} else {
|
||||
this.#isDirectoryCache[dir] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return this.#isDirectoryCache[dir];
|
||||
}
|
||||
|
||||
// dir is guaranteed to exist by context
|
||||
// dir may not be a directory
|
||||
normalizeDirectory(dir) {
|
||||
if (dir && typeof dir === "string") {
|
||||
if (dir.endsWith(path.sep) || dir.endsWith("/")) {
|
||||
return dir;
|
||||
}
|
||||
|
||||
// When inputPath is a directory, make sure it has a slash for passthrough copy aliasing
|
||||
// https://github.com/11ty/eleventy/issues/2709
|
||||
if (this.isDirectory(dir)) {
|
||||
return `${dir}/`;
|
||||
}
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
// maps input paths to output paths
|
||||
async getFileMap() {
|
||||
// TODO VirtualFileSystem candidate
|
||||
if (!isGlob(this.inputPath) && this.isExists(this.inputPath)) {
|
||||
return [
|
||||
{
|
||||
inputPath: this.inputPath,
|
||||
outputPath: await this.getOutputPath(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
let paths = [];
|
||||
// If not directory or file, attempt to get globs
|
||||
let files = await this.getFiles(this.inputPath);
|
||||
for (let filePathFromGlob of files) {
|
||||
paths.push({
|
||||
inputPath: filePathFromGlob,
|
||||
outputPath: await this.getOutputPath(filePathFromGlob),
|
||||
});
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/* Types:
|
||||
* 1. via glob, individual files found
|
||||
* 2. directory, triggers an event for each file
|
||||
* 3. individual file
|
||||
*/
|
||||
async copy(src, dest, copyOptions) {
|
||||
if (
|
||||
!TemplatePath.stripLeadingDotSlash(dest).startsWith(
|
||||
TemplatePath.stripLeadingDotSlash(this.outputDir),
|
||||
)
|
||||
) {
|
||||
return Promise.reject(
|
||||
new TemplatePassthroughError(
|
||||
"Destination is not in the site output directory. Check your passthrough paths.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let fileCopyCount = 0;
|
||||
let fileSizeCount = 0;
|
||||
let map = {};
|
||||
let b = this.benchmarks.aggregate.get("Passthrough Copy File");
|
||||
// returns a promise
|
||||
return copy(src, dest, copyOptions)
|
||||
.on(copy.events.COPY_FILE_START, (copyOp) => {
|
||||
// Access to individual files at `copyOp.src`
|
||||
debug("Copying individual file %o", copyOp.src);
|
||||
map[copyOp.src] = copyOp.dest;
|
||||
b.before();
|
||||
})
|
||||
.on(copy.events.COPY_FILE_COMPLETE, (copyOp) => {
|
||||
fileCopyCount++;
|
||||
fileSizeCount += copyOp.stats.size;
|
||||
b.after();
|
||||
})
|
||||
.then(() => {
|
||||
return {
|
||||
count: fileCopyCount,
|
||||
size: fileSizeCount,
|
||||
map,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async write() {
|
||||
if (this.isDryRun) {
|
||||
return Promise.resolve({
|
||||
count: 0,
|
||||
map: {},
|
||||
});
|
||||
}
|
||||
|
||||
debug("Copying %o", this.inputPath);
|
||||
let fileMap = await this.getFileMap();
|
||||
|
||||
// default options for recursive-copy
|
||||
// see https://www.npmjs.com/package/recursive-copy#arguments
|
||||
let copyOptionsDefault = {
|
||||
overwrite: true, // overwrite output. fails when input is directory (mkdir) and output is file
|
||||
dot: true, // copy dotfiles
|
||||
junk: false, // copy cache files like Thumbs.db
|
||||
results: false,
|
||||
expand: false, // follow symlinks (matches recursive-copy default)
|
||||
debug: false, // (matches recursive-copy default)
|
||||
|
||||
// Note: `filter` callback function only passes in a relative path, which is unreliable
|
||||
// See https://github.com/timkendrick/recursive-copy/blob/4c9a8b8a4bf573285e9c4a649a30a2b59ccf441c/lib/copy.js#L59
|
||||
// e.g. `{ filePaths: [ './img/coolkid.jpg' ], relativePaths: [ '' ] }`
|
||||
};
|
||||
|
||||
let copyOptions = Object.assign(copyOptionsDefault, this.copyOptions);
|
||||
|
||||
let promises = fileMap.map((entry) => {
|
||||
// For-free passthrough copy
|
||||
if (checkPassthroughCopyBehavior(this.config, this.runMode)) {
|
||||
let aliasMap = {};
|
||||
aliasMap[entry.inputPath] = entry.outputPath;
|
||||
|
||||
return Promise.resolve({
|
||||
count: 0,
|
||||
map: aliasMap,
|
||||
});
|
||||
}
|
||||
|
||||
// Copy the files (only in build mode)
|
||||
return this.copy(entry.inputPath, entry.outputPath, copyOptions);
|
||||
});
|
||||
|
||||
// IMPORTANT: this returns an array of promises, does not await for promise to finish
|
||||
return Promise.all(promises).then(
|
||||
(results) => {
|
||||
// collate the count and input/output map results from the array.
|
||||
let count = 0;
|
||||
let size = 0;
|
||||
let map = {};
|
||||
|
||||
for (let result of results) {
|
||||
count += result.count;
|
||||
size += result.size;
|
||||
Object.assign(map, result.map);
|
||||
}
|
||||
|
||||
return {
|
||||
count,
|
||||
size,
|
||||
map,
|
||||
};
|
||||
},
|
||||
(err) => {
|
||||
throw new TemplatePassthroughError(`Error copying passthrough files: ${err.message}`, err);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplatePassthrough;
|
||||
311
node_modules/@11ty/eleventy/src/TemplatePassthroughManager.js
generated
vendored
Normal file
311
node_modules/@11ty/eleventy/src/TemplatePassthroughManager.js
generated
vendored
Normal file
@@ -0,0 +1,311 @@
|
||||
import isGlob from "is-glob";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import EleventyExtensionMap from "./EleventyExtensionMap.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import TemplatePassthrough from "./TemplatePassthrough.js";
|
||||
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
|
||||
import { isGlobMatch } from "./Util/GlobMatcher.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:TemplatePassthroughManager");
|
||||
const debugDev = debugUtil("Dev:Eleventy:TemplatePassthroughManager");
|
||||
|
||||
class TemplatePassthroughManagerConfigError extends EleventyBaseError {}
|
||||
class TemplatePassthroughManagerCopyError extends EleventyBaseError {}
|
||||
|
||||
class TemplatePassthroughManager {
|
||||
constructor(eleventyConfig) {
|
||||
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new TemplatePassthroughManagerConfigError("Missing or invalid `config` argument.");
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
this.config = eleventyConfig.getConfig();
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.count = 0;
|
||||
this.size = 0;
|
||||
this.conflictMap = {};
|
||||
this.incrementalFile = null;
|
||||
debug("Resetting counts to 0");
|
||||
}
|
||||
|
||||
set extensionMap(extensionMap) {
|
||||
this._extensionMap = extensionMap;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats([]);
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get outputDir() {
|
||||
return this.dirs.output;
|
||||
}
|
||||
|
||||
setDryRun(isDryRun) {
|
||||
this.isDryRun = !!isDryRun;
|
||||
}
|
||||
|
||||
setRunMode(runMode) {
|
||||
this.runMode = runMode;
|
||||
}
|
||||
|
||||
setIncrementalFile(path) {
|
||||
if (path) {
|
||||
this.incrementalFile = path;
|
||||
}
|
||||
}
|
||||
|
||||
_normalizePaths(path, outputPath, copyOptions = {}) {
|
||||
return {
|
||||
inputPath: TemplatePath.addLeadingDotSlash(path),
|
||||
outputPath: outputPath ? TemplatePath.stripLeadingDotSlash(outputPath) : true,
|
||||
copyOptions,
|
||||
};
|
||||
}
|
||||
|
||||
getConfigPaths() {
|
||||
let paths = [];
|
||||
let pathsRaw = this.config.passthroughCopies || {};
|
||||
debug("`addPassthroughCopy` config API paths: %o", pathsRaw);
|
||||
for (let [inputPath, { outputPath, copyOptions }] of Object.entries(pathsRaw)) {
|
||||
paths.push(this._normalizePaths(inputPath, outputPath, copyOptions));
|
||||
}
|
||||
debug("`addPassthroughCopy` config API normalized paths: %o", paths);
|
||||
return paths;
|
||||
}
|
||||
|
||||
getConfigPathGlobs() {
|
||||
return this.getConfigPaths().map((path) => {
|
||||
return TemplatePath.convertToRecursiveGlobSync(path.inputPath);
|
||||
});
|
||||
}
|
||||
|
||||
getNonTemplatePaths(paths) {
|
||||
let matches = [];
|
||||
for (let path of paths) {
|
||||
if (!this.extensionMap.hasEngine(path)) {
|
||||
matches.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
getCopyCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
getCopySize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
setFileSystemSearch(fileSystemSearch) {
|
||||
this.fileSystemSearch = fileSystemSearch;
|
||||
}
|
||||
|
||||
getTemplatePassthroughForPath(path, isIncremental = false) {
|
||||
let inst = new TemplatePassthrough(path, this.eleventyConfig);
|
||||
|
||||
inst.setFileSystemSearch(this.fileSystemSearch);
|
||||
inst.setIsIncremental(isIncremental);
|
||||
inst.setDryRun(this.isDryRun);
|
||||
inst.setRunMode(this.runMode);
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
async copyPassthrough(pass) {
|
||||
if (!(pass instanceof TemplatePassthrough)) {
|
||||
throw new TemplatePassthroughManagerCopyError(
|
||||
"copyPassthrough expects an instance of TemplatePassthrough",
|
||||
);
|
||||
}
|
||||
|
||||
let { inputPath } = pass.getPath();
|
||||
|
||||
// TODO https://github.com/11ty/eleventy/issues/2452
|
||||
// De-dupe both the input and output paired together to avoid the case
|
||||
// where an input/output pair has been added via multiple passthrough methods (glob, file suffix, etc)
|
||||
// Probably start with the `filter` callback in recursive-copy but it only passes relative paths
|
||||
// See the note in TemplatePassthrough.js->write()
|
||||
|
||||
// Also note that `recursive-copy` handles repeated overwrite copy to the same destination just fine.
|
||||
// e.g. `for(let j=0, k=1000; j<k; j++) { copy("coolkid.jpg", "_site/coolkid.jpg"); }`
|
||||
|
||||
// Eventually we’ll want to move all of this to use Node’s fs.cp, which is experimental and only on Node 16+
|
||||
|
||||
return pass.write().then(
|
||||
({ size, count, map }) => {
|
||||
for (let src in map) {
|
||||
let dest = map[src];
|
||||
if (this.conflictMap[dest]) {
|
||||
if (src !== this.conflictMap[dest]) {
|
||||
throw new TemplatePassthroughManagerCopyError(
|
||||
`Multiple passthrough copy files are trying to write to the same output file (${dest}). ${src} and ${this.conflictMap[dest]}`,
|
||||
);
|
||||
} else {
|
||||
// Multiple entries from the same source
|
||||
debug(
|
||||
"A passthrough copy entry (%o) caused the same file (%o) to be copied more than once to the output (%o). This is atomically safe but a waste of build resources.",
|
||||
inputPath,
|
||||
src,
|
||||
dest,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
debugDev("Adding %o to passthrough copy conflict map, from %o", dest, src);
|
||||
|
||||
this.conflictMap[dest] = src;
|
||||
}
|
||||
|
||||
if (pass.isDryRun) {
|
||||
// We don’t count the skipped files as we need to iterate over them
|
||||
debug(
|
||||
"Skipped %o (either from --dryrun or --incremental or for-free passthrough copy)",
|
||||
inputPath,
|
||||
);
|
||||
} else {
|
||||
if (count) {
|
||||
this.count += count;
|
||||
this.size += size;
|
||||
debug("Copied %o (%d files, %d size)", inputPath, count || 0, size || 0);
|
||||
} else {
|
||||
debug("Skipped copying %o (emulated passthrough copy)", inputPath);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
count,
|
||||
map,
|
||||
};
|
||||
},
|
||||
function (e) {
|
||||
return Promise.reject(
|
||||
new TemplatePassthroughManagerCopyError(`Having trouble copying '${inputPath}'`, e),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
isPassthroughCopyFile(paths, changedFile) {
|
||||
if (!changedFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// passthrough copy by non-matching engine extension (via templateFormats)
|
||||
for (let path of paths) {
|
||||
if (path === changedFile && !this.extensionMap.hasEngine(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (let path of this.getConfigPaths()) {
|
||||
if (TemplatePath.startsWithSubPath(changedFile, path.inputPath)) {
|
||||
return path;
|
||||
}
|
||||
if (changedFile && isGlob(path.inputPath) && isGlobMatch(changedFile, [path.inputPath])) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getAllNormalizedPaths(paths) {
|
||||
if (this.incrementalFile) {
|
||||
let isPassthrough = this.isPassthroughCopyFile(paths, this.incrementalFile);
|
||||
|
||||
if (isPassthrough) {
|
||||
if (isPassthrough.outputPath) {
|
||||
return [this._normalizePaths(this.incrementalFile, isPassthrough.outputPath)];
|
||||
}
|
||||
|
||||
return [this._normalizePaths(this.incrementalFile)];
|
||||
}
|
||||
|
||||
// Fixes https://github.com/11ty/eleventy/issues/2491
|
||||
if (!checkPassthroughCopyBehavior(this.config, this.runMode)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
let normalizedPaths = this.getConfigPaths();
|
||||
if (debug.enabled) {
|
||||
for (let path of normalizedPaths) {
|
||||
debug("TemplatePassthrough copying from config: %o", path);
|
||||
}
|
||||
}
|
||||
|
||||
if (paths?.length) {
|
||||
let passthroughPaths = this.getNonTemplatePaths(paths);
|
||||
for (let path of passthroughPaths) {
|
||||
let normalizedPath = this._normalizePaths(path);
|
||||
|
||||
debug(
|
||||
`TemplatePassthrough copying from non-matching file extension: ${normalizedPath.inputPath}`,
|
||||
);
|
||||
|
||||
normalizedPaths.push(normalizedPath);
|
||||
}
|
||||
}
|
||||
|
||||
return normalizedPaths;
|
||||
}
|
||||
|
||||
// keys: output
|
||||
// values: input
|
||||
getAliasesFromPassthroughResults(result) {
|
||||
let entries = {};
|
||||
for (let entry of result) {
|
||||
for (let src in entry.map) {
|
||||
let dest = TemplatePath.stripLeadingSubPath(entry.map[src], this.outputDir);
|
||||
entries["/" + encodeURI(dest)] = src;
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
// Performance note: these can actually take a fair bit of time, but aren’t a
|
||||
// bottleneck to eleventy. The copies are performed asynchronously and don’t affect eleventy
|
||||
// write times in a significant way.
|
||||
async copyAll(templateExtensionPaths) {
|
||||
debug("TemplatePassthrough copy started.");
|
||||
let normalizedPaths = this.getAllNormalizedPaths(templateExtensionPaths);
|
||||
|
||||
let passthroughs = normalizedPaths.map((path) => {
|
||||
// if incrementalFile is set but it isn’t a passthrough copy, normalizedPaths will be an empty array
|
||||
let isIncremental = !!this.incrementalFile;
|
||||
|
||||
return this.getTemplatePassthroughForPath(path, isIncremental);
|
||||
});
|
||||
|
||||
let promises = passthroughs.map((pass) => this.copyPassthrough(pass));
|
||||
return Promise.all(promises).then(async (results) => {
|
||||
let aliases = this.getAliasesFromPassthroughResults(results);
|
||||
await this.config.events.emit("eleventy.passthrough", {
|
||||
map: aliases,
|
||||
});
|
||||
|
||||
debug(`TemplatePassthrough copy finished. Current count: ${this.count} (size: ${this.size})`);
|
||||
return results;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplatePassthroughManager;
|
||||
189
node_modules/@11ty/eleventy/src/TemplatePermalink.js
generated
vendored
Normal file
189
node_modules/@11ty/eleventy/src/TemplatePermalink.js
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
import path from "node:path";
|
||||
import normalize from "normalize-path";
|
||||
import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
|
||||
|
||||
class TemplatePermalink {
|
||||
// `link` with template syntax should have already been rendered in Template.js
|
||||
constructor(link, extraSubdir) {
|
||||
let isLinkAnObject = isPlainObject(link);
|
||||
|
||||
this._isRendered = true;
|
||||
this._writeToFileSystem = true;
|
||||
|
||||
let buildLink;
|
||||
|
||||
if (isLinkAnObject) {
|
||||
if ("build" in link) {
|
||||
buildLink = link.build;
|
||||
}
|
||||
|
||||
// find the first string key
|
||||
for (let key in link) {
|
||||
if (typeof key !== "string") {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
buildLink = link;
|
||||
}
|
||||
|
||||
// permalink: false and permalink: build: false
|
||||
if (typeof buildLink === "boolean") {
|
||||
if (buildLink === false) {
|
||||
this._writeToFileSystem = false;
|
||||
} else {
|
||||
throw new Error(
|
||||
`\`permalink: ${
|
||||
isLinkAnObject ? "build: " : ""
|
||||
}true\` is not a supported feature in Eleventy. Did you mean \`permalink: ${
|
||||
isLinkAnObject ? "build: " : ""
|
||||
}false\`?`,
|
||||
);
|
||||
}
|
||||
} else if (buildLink) {
|
||||
this.buildLink = buildLink;
|
||||
}
|
||||
|
||||
if (isLinkAnObject) {
|
||||
// default if permalink is an Object but does not have a `build` prop
|
||||
if (!("build" in link)) {
|
||||
this._writeToFileSystem = false;
|
||||
this._isRendered = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.extraPaginationSubdir = extraSubdir || "";
|
||||
}
|
||||
|
||||
setUrlTransforms(transforms) {
|
||||
this._urlTransforms = transforms;
|
||||
}
|
||||
|
||||
get urlTransforms() {
|
||||
return this._urlTransforms || [];
|
||||
}
|
||||
|
||||
_addDefaultLinkFilename(link) {
|
||||
return link + (link.slice(-1) === "/" ? "index.html" : "");
|
||||
}
|
||||
|
||||
toOutputPath() {
|
||||
if (!this.buildLink) {
|
||||
// empty or false
|
||||
return false;
|
||||
}
|
||||
|
||||
let cleanLink = this._addDefaultLinkFilename(this.buildLink);
|
||||
let parsed = path.parse(cleanLink);
|
||||
|
||||
return TemplatePath.join(parsed.dir, this.extraPaginationSubdir, parsed.base);
|
||||
}
|
||||
|
||||
// Used in url transforms feature
|
||||
static getUrlStem(original) {
|
||||
let subject = original;
|
||||
if (original.endsWith(".html")) {
|
||||
subject = original.slice(0, -1 * ".html".length);
|
||||
}
|
||||
return TemplatePermalink.normalizePathToUrl(subject);
|
||||
}
|
||||
|
||||
static normalizePathToUrl(original) {
|
||||
let compare = original || "";
|
||||
|
||||
let needleHtml = "/index.html";
|
||||
let needleBareTrailingSlash = "/index/";
|
||||
let needleBare = "/index";
|
||||
if (compare.endsWith(needleHtml)) {
|
||||
return compare.slice(0, compare.length - needleHtml.length) + "/";
|
||||
} else if (compare.endsWith(needleBareTrailingSlash)) {
|
||||
return compare.slice(0, compare.length - needleBareTrailingSlash.length) + "/";
|
||||
} else if (compare.endsWith(needleBare)) {
|
||||
return compare.slice(0, compare.length - needleBare.length) + "/";
|
||||
}
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
// This method is used to generate the `page.url` variable.
|
||||
|
||||
// remove all index.html’s from links
|
||||
// index.html becomes /
|
||||
// test/index.html becomes test/
|
||||
toHref() {
|
||||
if (!this.buildLink) {
|
||||
// empty or false
|
||||
return false;
|
||||
}
|
||||
|
||||
let transformedLink = this.toOutputPath();
|
||||
let original = (transformedLink.charAt(0) !== "/" ? "/" : "") + transformedLink;
|
||||
|
||||
let normalized = TemplatePermalink.normalizePathToUrl(original) || "";
|
||||
for (let transform of this.urlTransforms) {
|
||||
original =
|
||||
transform({
|
||||
url: normalized,
|
||||
urlStem: TemplatePermalink.getUrlStem(original),
|
||||
}) ?? original;
|
||||
}
|
||||
|
||||
return TemplatePermalink.normalizePathToUrl(original);
|
||||
}
|
||||
|
||||
toPath(outputDir) {
|
||||
if (!this.buildLink) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let uri = this.toOutputPath();
|
||||
|
||||
if (uri === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return normalize(outputDir + "/" + uri);
|
||||
}
|
||||
|
||||
toPathFromRoot() {
|
||||
if (!this.buildLink) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let uri = this.toOutputPath();
|
||||
|
||||
if (uri === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return normalize(uri);
|
||||
}
|
||||
|
||||
static _hasDuplicateFolder(dir, base) {
|
||||
let folders = dir.split("/");
|
||||
if (!folders[folders.length - 1]) {
|
||||
folders.pop();
|
||||
}
|
||||
return folders[folders.length - 1] === base;
|
||||
}
|
||||
|
||||
static generate(dir, filenameNoExt, extraSubdir, fileExtension = "html") {
|
||||
let path;
|
||||
if (fileExtension === "html") {
|
||||
let hasDupeFolder = TemplatePermalink._hasDuplicateFolder(dir, filenameNoExt);
|
||||
|
||||
path =
|
||||
(dir ? dir + "/" : "") +
|
||||
(filenameNoExt !== "index" && !hasDupeFolder ? filenameNoExt + "/" : "") +
|
||||
"index" +
|
||||
".html";
|
||||
} else {
|
||||
path = (dir ? dir + "/" : "") + filenameNoExt + "." + fileExtension;
|
||||
}
|
||||
|
||||
return new TemplatePermalink(path, extraSubdir);
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplatePermalink;
|
||||
294
node_modules/@11ty/eleventy/src/TemplateRender.js
generated
vendored
Normal file
294
node_modules/@11ty/eleventy/src/TemplateRender.js
generated
vendored
Normal file
@@ -0,0 +1,294 @@
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import EleventyExtensionMap from "./EleventyExtensionMap.js";
|
||||
import TemplateEngineManager from "./Engines/TemplateEngineManager.js";
|
||||
import CustomEngine from "./Engines/Custom.js";
|
||||
|
||||
// import debugUtil from "debug";
|
||||
// const debug = debugUtil("Eleventy:TemplateRender");
|
||||
|
||||
class TemplateRenderConfigError extends EleventyBaseError {}
|
||||
class TemplateRenderUnknownEngineError extends EleventyBaseError {}
|
||||
|
||||
// works with full path names or short engine name
|
||||
class TemplateRender {
|
||||
constructor(tmplPath, config) {
|
||||
if (!tmplPath) {
|
||||
throw new Error(`TemplateRender requires a tmplPath argument, instead of ${tmplPath}`);
|
||||
}
|
||||
if (!config) {
|
||||
throw new TemplateRenderConfigError("Missing `config` argument.");
|
||||
}
|
||||
if (config.constructor.name === "TemplateConfig") {
|
||||
this.eleventyConfig = config;
|
||||
this.config = config.getConfig();
|
||||
} else {
|
||||
throw new Error("Third argument to TemplateRender must be a TemplateConfig instance.");
|
||||
}
|
||||
|
||||
this.engineNameOrPath = tmplPath;
|
||||
|
||||
this.parseMarkdownWith = this.config.markdownTemplateEngine;
|
||||
this.parseHtmlWith = this.config.htmlTemplateEngine;
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get includesDir() {
|
||||
return this.dirs.includes;
|
||||
}
|
||||
|
||||
/* Backwards compat */
|
||||
getIncludesDir() {
|
||||
return this.includesDir;
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
set config(config) {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
set extensionMap(extensionMap) {
|
||||
this._extensionMap = extensionMap;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats([]);
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
async getEngineByName(name) {
|
||||
let engine = await this.extensionMap.engineManager.getEngine(name, this.extensionMap);
|
||||
engine.eleventyConfig = this.eleventyConfig;
|
||||
|
||||
return engine;
|
||||
}
|
||||
|
||||
// Runs once per template
|
||||
async init(engineNameOrPath) {
|
||||
let name = engineNameOrPath || this.engineNameOrPath;
|
||||
this.extensionMap.config = this.eleventyConfig;
|
||||
|
||||
let extensionEntry = this.extensionMap.getExtensionEntry(name);
|
||||
let engineName = extensionEntry?.aliasKey || extensionEntry?.key;
|
||||
if (TemplateEngineManager.isSimpleAlias(extensionEntry)) {
|
||||
engineName = extensionEntry?.key;
|
||||
}
|
||||
this._engineName = engineName;
|
||||
|
||||
if (!extensionEntry || !this._engineName) {
|
||||
throw new TemplateRenderUnknownEngineError(
|
||||
`Unknown engine for ${name} (supported extensions: ${this.extensionMap.getReadableFileExtensions()})`,
|
||||
);
|
||||
}
|
||||
|
||||
this._engine = await this.getEngineByName(this._engineName);
|
||||
|
||||
if (this.useMarkdown === undefined) {
|
||||
this.setUseMarkdown(this._engineName === "md");
|
||||
}
|
||||
}
|
||||
|
||||
get engineName() {
|
||||
if (!this._engineName) {
|
||||
throw new Error("TemplateRender needs a call to the init() method.");
|
||||
}
|
||||
return this._engineName;
|
||||
}
|
||||
|
||||
get engine() {
|
||||
if (!this._engine) {
|
||||
throw new Error("TemplateRender needs a call to the init() method.");
|
||||
}
|
||||
return this._engine;
|
||||
}
|
||||
|
||||
static parseEngineOverrides(engineName) {
|
||||
if (typeof (engineName || "") !== "string") {
|
||||
throw new Error("Expected String passed to parseEngineOverrides. Received: " + engineName);
|
||||
}
|
||||
|
||||
let overlappingEngineWarningCount = 0;
|
||||
let engines = [];
|
||||
let uniqueLookup = {};
|
||||
let usingMarkdown = false;
|
||||
(engineName || "")
|
||||
.split(",")
|
||||
.map((name) => {
|
||||
return name.toLowerCase().trim();
|
||||
})
|
||||
.forEach((name) => {
|
||||
// html is assumed (treated as plaintext by the system)
|
||||
if (!name || name === "html") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === "md") {
|
||||
usingMarkdown = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!uniqueLookup[name]) {
|
||||
engines.push(name);
|
||||
uniqueLookup[name] = true;
|
||||
|
||||
// we already short circuit md and html types above
|
||||
overlappingEngineWarningCount++;
|
||||
}
|
||||
});
|
||||
|
||||
if (overlappingEngineWarningCount > 1) {
|
||||
throw new Error(
|
||||
`Don’t mix multiple templating engines in your front matter overrides (exceptions for HTML and Markdown). You used: ${engineName}`,
|
||||
);
|
||||
}
|
||||
|
||||
// markdown should always be first
|
||||
if (usingMarkdown) {
|
||||
engines.unshift("md");
|
||||
}
|
||||
|
||||
return engines;
|
||||
}
|
||||
|
||||
// used for error logging and console output.
|
||||
getReadableEnginesList() {
|
||||
return this.getReadableEnginesListDifferingFromFileExtension() || this.engineName;
|
||||
}
|
||||
|
||||
getReadableEnginesListDifferingFromFileExtension() {
|
||||
let keyFromFilename = this.extensionMap.getKey(this.engineNameOrPath);
|
||||
if (this.engine instanceof CustomEngine) {
|
||||
if (
|
||||
this.engine.entry &&
|
||||
this.engine.entry.name &&
|
||||
keyFromFilename !== this.engine.entry.name
|
||||
) {
|
||||
return this.engine.entry.name;
|
||||
} else {
|
||||
// We don’t have a name for it so we return nothing so we don’t misreport (per #2386)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.engineName === "md" && this.useMarkdown && this.parseMarkdownWith) {
|
||||
return this.parseMarkdownWith;
|
||||
}
|
||||
if (this.engineName === "html" && this.parseHtmlWith) {
|
||||
return this.parseHtmlWith;
|
||||
}
|
||||
|
||||
// templateEngineOverride in play and template language differs from file extension
|
||||
if (keyFromFilename !== this.engineName) {
|
||||
return this.engineName;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO templateEngineOverride
|
||||
getPreprocessorEngine() {
|
||||
if (this.engineName === "md" && this.parseMarkdownWith) {
|
||||
return this.parseMarkdownWith;
|
||||
}
|
||||
if (this.engineName === "html" && this.parseHtmlWith) {
|
||||
return this.parseHtmlWith;
|
||||
}
|
||||
return this.extensionMap.getKey(this.engineNameOrPath);
|
||||
}
|
||||
|
||||
// We pass in templateEngineOverride here because it isn’t yet applied to templateRender
|
||||
getEnginesList(engineOverride) {
|
||||
if (engineOverride) {
|
||||
let engines = TemplateRender.parseEngineOverrides(engineOverride).reverse();
|
||||
return engines.join(",");
|
||||
}
|
||||
|
||||
if (this.engineName === "md" && this.useMarkdown && this.parseMarkdownWith) {
|
||||
return `${this.parseMarkdownWith},md`;
|
||||
}
|
||||
if (this.engineName === "html" && this.parseHtmlWith) {
|
||||
return this.parseHtmlWith;
|
||||
}
|
||||
|
||||
// templateEngineOverride in play
|
||||
return this.extensionMap.getKey(this.engineNameOrPath);
|
||||
}
|
||||
|
||||
async setEngineOverride(engineName, bypassMarkdown) {
|
||||
let engines = TemplateRender.parseEngineOverrides(engineName);
|
||||
|
||||
// when overriding, Template Engines with HTML will instead use the Template Engine as primary and output HTML
|
||||
// So any HTML engine usage here will never use a preprocessor templating engine.
|
||||
this.setHtmlEngine(false);
|
||||
|
||||
if (!engines.length) {
|
||||
await this.init("html");
|
||||
return;
|
||||
}
|
||||
|
||||
await this.init(engines[0]);
|
||||
|
||||
let usingMarkdown = engines[0] === "md" && !bypassMarkdown;
|
||||
|
||||
this.setUseMarkdown(usingMarkdown);
|
||||
|
||||
if (usingMarkdown) {
|
||||
// false means only parse markdown and not with a preprocessor template engine
|
||||
this.setMarkdownEngine(engines.length > 1 ? engines[1] : false);
|
||||
}
|
||||
}
|
||||
|
||||
getEngineName() {
|
||||
return this.engineName;
|
||||
}
|
||||
|
||||
isEngine(engine) {
|
||||
return this.engineName === engine;
|
||||
}
|
||||
|
||||
setUseMarkdown(useMarkdown) {
|
||||
this.useMarkdown = !!useMarkdown;
|
||||
}
|
||||
|
||||
// this is only called for templateEngineOverride
|
||||
setMarkdownEngine(markdownEngine) {
|
||||
this.parseMarkdownWith = markdownEngine;
|
||||
}
|
||||
|
||||
// this is only called for templateEngineOverride
|
||||
setHtmlEngine(htmlEngineName) {
|
||||
this.parseHtmlWith = htmlEngineName;
|
||||
}
|
||||
|
||||
async _testRender(str, data) {
|
||||
return this.engine._testRender(str, data);
|
||||
}
|
||||
|
||||
async getCompiledTemplate(str) {
|
||||
// TODO refactor better, move into TemplateEngine logic
|
||||
if (this.engineName === "md") {
|
||||
return this.engine.compile(
|
||||
str,
|
||||
this.engineNameOrPath,
|
||||
this.parseMarkdownWith,
|
||||
!this.useMarkdown,
|
||||
);
|
||||
} else if (this.engineName === "html") {
|
||||
return this.engine.compile(str, this.engineNameOrPath, this.parseHtmlWith);
|
||||
} else {
|
||||
return this.engine.compile(str, this.engineNameOrPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateRender;
|
||||
518
node_modules/@11ty/eleventy/src/TemplateWriter.js
generated
vendored
Executable file
518
node_modules/@11ty/eleventy/src/TemplateWriter.js
generated
vendored
Executable file
@@ -0,0 +1,518 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import Template from "./Template.js";
|
||||
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 EleventyErrorUtil from "./Errors/EleventyErrorUtil.js";
|
||||
import FileSystemSearch from "./FileSystemSearch.js";
|
||||
import ConsoleLogger from "./Util/ConsoleLogger.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:TemplateWriter");
|
||||
|
||||
class TemplateWriterMissingConfigArgError extends EleventyBaseError {}
|
||||
class EleventyPassthroughCopyError extends EleventyBaseError {}
|
||||
class EleventyTemplateError extends EleventyBaseError {}
|
||||
|
||||
class TemplateWriter {
|
||||
#eleventyFiles;
|
||||
|
||||
constructor(
|
||||
templateFormats, // TODO remove this, see `get eleventyFiles` first
|
||||
templateData,
|
||||
eleventyConfig,
|
||||
) {
|
||||
if (!eleventyConfig) {
|
||||
throw new TemplateWriterMissingConfigArgError("Missing config argument.");
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
this.config = eleventyConfig.getConfig();
|
||||
this.userConfig = eleventyConfig.userConfig;
|
||||
|
||||
this.templateFormats = templateFormats;
|
||||
|
||||
this.templateData = templateData;
|
||||
this.isVerbose = true;
|
||||
this.isDryRun = false;
|
||||
this.writeCount = 0;
|
||||
this.renderCount = 0;
|
||||
this.skippedCount = 0;
|
||||
this.isRunInitialBuild = true;
|
||||
|
||||
this._templatePathCache = new Map();
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get outputDir() {
|
||||
return this.dirs.output;
|
||||
}
|
||||
|
||||
get templateFormats() {
|
||||
return this._templateFormats;
|
||||
}
|
||||
|
||||
set templateFormats(value) {
|
||||
this._templateFormats = value;
|
||||
}
|
||||
|
||||
/* Getter for error handler */
|
||||
get errorHandler() {
|
||||
if (!this._errorHandler) {
|
||||
this._errorHandler = new EleventyErrorHandler();
|
||||
this._errorHandler.isVerbose = this.verboseMode;
|
||||
this._errorHandler.logger = this.logger;
|
||||
}
|
||||
|
||||
return this._errorHandler;
|
||||
}
|
||||
|
||||
/* Getter for Logger */
|
||||
get logger() {
|
||||
if (!this._logger) {
|
||||
this._logger = new ConsoleLogger();
|
||||
this._logger.isVerbose = this.verboseMode;
|
||||
}
|
||||
|
||||
return this._logger;
|
||||
}
|
||||
|
||||
/* Setter for Logger */
|
||||
set logger(logger) {
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
/* For testing */
|
||||
overrideConfig(config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
restart() {
|
||||
this.writeCount = 0;
|
||||
this.renderCount = 0;
|
||||
this.skippedCount = 0;
|
||||
}
|
||||
|
||||
set extensionMap(extensionMap) {
|
||||
this._extensionMap = extensionMap;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats(this.templateFormats);
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
setEleventyFiles(eleventyFiles) {
|
||||
this.#eleventyFiles = eleventyFiles;
|
||||
}
|
||||
|
||||
set eleventyFiles(eleventyFiles) {
|
||||
this.#eleventyFiles = eleventyFiles;
|
||||
}
|
||||
|
||||
get eleventyFiles() {
|
||||
// usually Eleventy.js will setEleventyFiles with the EleventyFiles manager
|
||||
if (!this.#eleventyFiles) {
|
||||
// if not, we can create one (used only by tests)
|
||||
this.#eleventyFiles = new EleventyFiles(this.templateFormats, this.eleventyConfig);
|
||||
|
||||
this.#eleventyFiles.setFileSystemSearch(new FileSystemSearch());
|
||||
this.#eleventyFiles.init();
|
||||
}
|
||||
|
||||
return this.#eleventyFiles;
|
||||
}
|
||||
|
||||
async _getAllPaths() {
|
||||
// this is now cached upstream by FileSystemSearch
|
||||
return this.eleventyFiles.getFiles();
|
||||
}
|
||||
|
||||
_createTemplate(path, to = "fs") {
|
||||
let tmpl = this._templatePathCache.get(path);
|
||||
let wasCached = false;
|
||||
if (tmpl) {
|
||||
wasCached = true;
|
||||
// Update config for https://github.com/11ty/eleventy/issues/3468
|
||||
tmpl.eleventyConfig = this.eleventyConfig;
|
||||
|
||||
// TODO reset other constructor things here like inputDir/outputDir/extensionMap/
|
||||
tmpl.setTemplateData(this.templateData);
|
||||
} else {
|
||||
tmpl = new Template(path, this.templateData, this.extensionMap, this.eleventyConfig);
|
||||
|
||||
tmpl.setOutputFormat(to);
|
||||
|
||||
tmpl.logger = this.logger;
|
||||
this._templatePathCache.set(path, tmpl);
|
||||
|
||||
/*
|
||||
* Sample filter: arg str, return pretty HTML string
|
||||
* function(str) {
|
||||
* return pretty(str, { ocd: true });
|
||||
* }
|
||||
*/
|
||||
tmpl.setTransforms(this.config.transforms);
|
||||
|
||||
for (let linterName in this.config.linters) {
|
||||
let linter = this.config.linters[linterName];
|
||||
if (typeof linter === "function") {
|
||||
tmpl.addLinter(linter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tmpl.setDryRun(this.isDryRun);
|
||||
tmpl.setIsVerbose(this.isVerbose);
|
||||
tmpl.reset();
|
||||
|
||||
return {
|
||||
template: tmpl,
|
||||
wasCached,
|
||||
};
|
||||
}
|
||||
|
||||
// incrementalFileShape is `template` or `copy` (for passthrough file copy)
|
||||
async _addToTemplateMapIncrementalBuild(incrementalFileShape, paths, to = "fs") {
|
||||
// Render overrides are only used when `--ignore-initial` is in play and an initial build is not run
|
||||
let ignoreInitialBuild = !this.isRunInitialBuild;
|
||||
let secondOrderRelevantLookup = {};
|
||||
let templates = [];
|
||||
|
||||
let promises = [];
|
||||
for (let path of paths) {
|
||||
let { template: tmpl } = this._createTemplate(path, to);
|
||||
|
||||
// Note: removed a fix here to fetch missing templateRender instances
|
||||
// that was tested as no longer needed (Issue #3170).
|
||||
|
||||
templates.push(tmpl);
|
||||
|
||||
// This must happen before data is generated for the incremental file only
|
||||
if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) {
|
||||
tmpl.resetCaches();
|
||||
}
|
||||
|
||||
// IMPORTANT: This is where the data is first generated for the template
|
||||
promises.push(this.templateMap.add(tmpl));
|
||||
}
|
||||
|
||||
// Important to set up template dependency relationships first
|
||||
await Promise.all(promises);
|
||||
|
||||
// Delete incremental file from the dependency graph so we get fresh entries!
|
||||
// This _must_ happen before any additions, the other ones are in Custom.js and GlobalDependencyMap.js (from the eleventy.layouts Event)
|
||||
this.config.uses.resetNode(this.incrementalFile);
|
||||
|
||||
// write new template relationships to the global dependency graph for next time
|
||||
this.templateMap.addAllToGlobalDependencyGraph();
|
||||
|
||||
// Always disable render for --ignore-initial
|
||||
if (ignoreInitialBuild) {
|
||||
for (let tmpl of templates) {
|
||||
tmpl.setRenderableOverride(false); // disable render
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (let tmpl of templates) {
|
||||
if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) {
|
||||
tmpl.setRenderableOverride(undefined); // unset, probably render
|
||||
} else if (
|
||||
tmpl.isFileRelevantToThisTemplate(this.incrementalFile, {
|
||||
isFullTemplate: incrementalFileShape === "template",
|
||||
})
|
||||
) {
|
||||
// changed file is used by template
|
||||
// template uses the changed file
|
||||
tmpl.setRenderableOverride(undefined); // unset, probably render
|
||||
secondOrderRelevantLookup[tmpl.inputPath] = true;
|
||||
} else if (this.config.uses.isFileUsedBy(this.incrementalFile, tmpl.inputPath)) {
|
||||
// changed file uses this template
|
||||
tmpl.setRenderableOverride("optional");
|
||||
} else {
|
||||
// For incremental, always disable render on irrelevant templates
|
||||
tmpl.setRenderableOverride(false); // disable render
|
||||
}
|
||||
}
|
||||
|
||||
let secondOrderRelevantArray = this.config.uses
|
||||
.getTemplatesRelevantToTemplateList(Object.keys(secondOrderRelevantLookup))
|
||||
.map((entry) => TemplatePath.addLeadingDotSlash(entry));
|
||||
let secondOrderTemplates = Object.fromEntries(
|
||||
Object.entries(secondOrderRelevantArray).map(([index, value]) => [value, true]),
|
||||
);
|
||||
|
||||
for (let tmpl of templates) {
|
||||
// second order templates must also be rendered if not yet already rendered at least once and available in cache.
|
||||
if (secondOrderTemplates[tmpl.inputPath]) {
|
||||
if (tmpl.isRenderableDisabled()) {
|
||||
tmpl.setRenderableOverride("optional");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Order of templates does not matter here, they’re reordered later based on dependencies in TemplateMap.js
|
||||
for (let tmpl of templates) {
|
||||
if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) {
|
||||
// Cache is reset above (to invalidate data cache at the right time)
|
||||
tmpl.setDryRunViaIncremental(false);
|
||||
} else if (!tmpl.isRenderableDisabled() && !tmpl.isRenderableOptional()) {
|
||||
// Related to the template but not the template (reset the render cache, not the read cache)
|
||||
tmpl.resetCaches({
|
||||
data: true,
|
||||
render: true,
|
||||
});
|
||||
|
||||
tmpl.setDryRunViaIncremental(false);
|
||||
} else {
|
||||
// During incremental we only reset the data cache for non-matching templates, see https://github.com/11ty/eleventy/issues/2710
|
||||
// Keep caches for read/render
|
||||
tmpl.resetCaches({
|
||||
data: true,
|
||||
});
|
||||
|
||||
tmpl.setDryRunViaIncremental(true);
|
||||
|
||||
this.skippedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_addToTemplateMapFullBuild(paths, to = "fs") {
|
||||
if (this.incrementalFile) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let ignoreInitialBuild = !this.isRunInitialBuild;
|
||||
let promises = [];
|
||||
for (let path of paths) {
|
||||
let { template: tmpl, wasCached } = this._createTemplate(path, to);
|
||||
|
||||
// Render overrides are only used when `--ignore-initial` is in play and an initial build is not run
|
||||
if (ignoreInitialBuild) {
|
||||
tmpl.setRenderableOverride(false); // disable render
|
||||
} else {
|
||||
tmpl.setRenderableOverride(undefined); // unset, render
|
||||
}
|
||||
|
||||
if (wasCached) {
|
||||
tmpl.resetCaches();
|
||||
}
|
||||
|
||||
// IMPORTANT: This is where the data is first generated for the template
|
||||
promises.push(this.templateMap.add(tmpl));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
async _addToTemplateMap(paths, to = "fs") {
|
||||
let incrementalFileShape = this.eleventyFiles.getFileShape(paths, this.incrementalFile);
|
||||
|
||||
// Filter out passthrough copy files
|
||||
paths = paths.filter((path) => {
|
||||
if (!this.extensionMap.hasEngine(path)) {
|
||||
return false;
|
||||
}
|
||||
if (incrementalFileShape === "copy") {
|
||||
this.skippedCount++;
|
||||
// Filters out templates if the incremental file is a passthrough copy file
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Full Build
|
||||
if (!this.incrementalFile) {
|
||||
let ret = await this._addToTemplateMapFullBuild(paths, to);
|
||||
|
||||
// write new template relationships to the global dependency graph for next time
|
||||
this.templateMap.addAllToGlobalDependencyGraph();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Top level async to get at the promises returned.
|
||||
return await this._addToTemplateMapIncrementalBuild(incrementalFileShape, paths, to);
|
||||
}
|
||||
|
||||
async _createTemplateMap(paths, to) {
|
||||
this.templateMap = new TemplateMap(this.eleventyConfig);
|
||||
|
||||
await this._addToTemplateMap(paths, to);
|
||||
await this.templateMap.cache();
|
||||
|
||||
return this.templateMap;
|
||||
}
|
||||
|
||||
async _generateTemplate(mapEntry, to) {
|
||||
let tmpl = mapEntry.template;
|
||||
|
||||
return tmpl.generateMapEntry(mapEntry, to).then((pages) => {
|
||||
this.renderCount += tmpl.getRenderCount();
|
||||
this.writeCount += tmpl.getWriteCount();
|
||||
return pages;
|
||||
});
|
||||
}
|
||||
|
||||
async writePassthroughCopy(templateExtensionPaths) {
|
||||
let passthroughManager = this.eleventyFiles.getPassthroughManager();
|
||||
passthroughManager.setIncrementalFile(this.incrementalFile);
|
||||
|
||||
return passthroughManager.copyAll(templateExtensionPaths).catch((e) => {
|
||||
this.errorHandler.warn(e, "Error with passthrough copy");
|
||||
return Promise.reject(new EleventyPassthroughCopyError("Having trouble copying", e));
|
||||
});
|
||||
}
|
||||
|
||||
async generateTemplates(paths, to = "fs") {
|
||||
let promises = [];
|
||||
|
||||
// console.time("generateTemplates:_createTemplateMap");
|
||||
// TODO optimize await here
|
||||
await this._createTemplateMap(paths, to);
|
||||
// console.timeEnd("generateTemplates:_createTemplateMap");
|
||||
debug("Template map created.");
|
||||
|
||||
let usedTemplateContentTooEarlyMap = [];
|
||||
for (let mapEntry of this.templateMap.getMap()) {
|
||||
promises.push(
|
||||
this._generateTemplate(mapEntry, to).catch(function (e) {
|
||||
// Premature templateContent in layout render, this also happens in
|
||||
// TemplateMap.populateContentDataInMap for non-layout content
|
||||
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
|
||||
usedTemplateContentTooEarlyMap.push(mapEntry);
|
||||
} else {
|
||||
let outputPaths = `"${mapEntry._pages.map((page) => page.outputPath).join(`", "`)}"`;
|
||||
return Promise.reject(
|
||||
new EleventyTemplateError(
|
||||
`Having trouble writing to ${outputPaths} from "${mapEntry.inputPath}"`,
|
||||
e,
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
for (let mapEntry of usedTemplateContentTooEarlyMap) {
|
||||
promises.push(
|
||||
this._generateTemplate(mapEntry, to).catch(function (e) {
|
||||
return Promise.reject(
|
||||
new EleventyTemplateError(
|
||||
`Having trouble writing to (second pass) "${mapEntry.outputPath}" from "${mapEntry.inputPath}"`,
|
||||
e,
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return promises;
|
||||
}
|
||||
|
||||
async write() {
|
||||
let paths = await this._getAllPaths();
|
||||
let promises = [];
|
||||
|
||||
// The ordering here is important to destructuring in Eleventy->_watch
|
||||
promises.push(this.writePassthroughCopy(paths));
|
||||
|
||||
promises.push(...(await this.generateTemplates(paths)));
|
||||
|
||||
return Promise.all(promises).then(
|
||||
([passthroughCopyResults, ...templateResults]) => {
|
||||
return {
|
||||
passthroughCopy: passthroughCopyResults,
|
||||
// New in 3.0: flatten and filter out falsy templates
|
||||
templates: templateResults.flat().filter(Boolean),
|
||||
};
|
||||
},
|
||||
(e) => {
|
||||
return Promise.reject(e);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Passthrough copy not supported in JSON output.
|
||||
// --incremental not supported in JSON output.
|
||||
async getJSON(to = "json") {
|
||||
let paths = await this._getAllPaths();
|
||||
let promises = await this.generateTemplates(paths, to);
|
||||
|
||||
return Promise.all(promises).then(
|
||||
(templateResults) => {
|
||||
return {
|
||||
// New in 3.0: flatten and filter out falsy templates
|
||||
templates: templateResults.flat().filter(Boolean),
|
||||
};
|
||||
},
|
||||
(e) => {
|
||||
return Promise.reject(e);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
setVerboseOutput(isVerbose) {
|
||||
this.isVerbose = isVerbose;
|
||||
this.errorHandler.isVerbose = isVerbose;
|
||||
}
|
||||
|
||||
setDryRun(isDryRun) {
|
||||
this.isDryRun = !!isDryRun;
|
||||
|
||||
this.eleventyFiles.getPassthroughManager().setDryRun(this.isDryRun);
|
||||
}
|
||||
|
||||
setRunInitialBuild(runInitialBuild) {
|
||||
this.isRunInitialBuild = runInitialBuild;
|
||||
}
|
||||
setIncrementalBuild(isIncremental) {
|
||||
this.isIncremental = isIncremental;
|
||||
}
|
||||
setIncrementalFile(incrementalFile) {
|
||||
this.incrementalFile = incrementalFile;
|
||||
}
|
||||
resetIncrementalFile() {
|
||||
this.incrementalFile = null;
|
||||
}
|
||||
|
||||
getCopyCount() {
|
||||
return this.eleventyFiles.getPassthroughManager().getCopyCount();
|
||||
}
|
||||
|
||||
getCopySize() {
|
||||
return this.eleventyFiles.getPassthroughManager().getCopySize();
|
||||
}
|
||||
|
||||
getRenderCount() {
|
||||
return this.renderCount;
|
||||
}
|
||||
|
||||
getWriteCount() {
|
||||
return this.writeCount;
|
||||
}
|
||||
|
||||
getSkippedCount() {
|
||||
return this.skippedCount;
|
||||
}
|
||||
|
||||
get caches() {
|
||||
return ["_templatePathCache"];
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateWriter;
|
||||
1292
node_modules/@11ty/eleventy/src/UserConfig.js
generated
vendored
Normal file
1292
node_modules/@11ty/eleventy/src/UserConfig.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
75
node_modules/@11ty/eleventy/src/Util/AsyncEventEmitter.js
generated
vendored
Normal file
75
node_modules/@11ty/eleventy/src/Util/AsyncEventEmitter.js
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
|
||||
/**
|
||||
* This class emits events asynchronously.
|
||||
* It can be used for time measurements during a build.
|
||||
*/
|
||||
class AsyncEventEmitter extends EventEmitter {
|
||||
#handlerMode = "parallel";
|
||||
|
||||
// TypeScript slop
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type - The event name to emit.
|
||||
* @param {...*} args - Additional arguments that get passed to listeners.
|
||||
* @returns {Promise} - Promise resolves once all listeners were invoked
|
||||
*/
|
||||
/** @ts-expect-error */
|
||||
async emit(type, ...args) {
|
||||
let listeners = this.listeners(type);
|
||||
if (listeners.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.#handlerMode == "sequential") {
|
||||
const result = [];
|
||||
for (const listener of listeners) {
|
||||
const returnValue = await listener.apply(this, args);
|
||||
result.push(returnValue);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return Promise.all(
|
||||
listeners.map((listener) => {
|
||||
return listener.apply(this, args);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type - The event name to emit.
|
||||
* @param {...*} args - Additional lazy-executed function arguments that get passed to listeners.
|
||||
* @returns {Promise} - Promise resolves once all listeners were invoked
|
||||
*/
|
||||
async emitLazy(type, ...args) {
|
||||
let listeners = this.listeners(type);
|
||||
if (listeners.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let argsMap = [];
|
||||
for (let arg of args) {
|
||||
if (typeof arg === "function") {
|
||||
let r = arg();
|
||||
if (r instanceof Promise) {
|
||||
r = await r;
|
||||
}
|
||||
argsMap.push(r);
|
||||
} else {
|
||||
argsMap.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return this.emit.call(this, type, ...argsMap);
|
||||
}
|
||||
|
||||
setHandlerMode(mode) {
|
||||
this.#handlerMode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
export default AsyncEventEmitter;
|
||||
55
node_modules/@11ty/eleventy/src/Util/Compatibility.js
generated
vendored
Normal file
55
node_modules/@11ty/eleventy/src/Util/Compatibility.js
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import semver from "semver";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import { getEleventyPackageJson, getWorkingProjectPackageJson } from "./ImportJsonSync.js";
|
||||
|
||||
const pkg = getEleventyPackageJson();
|
||||
const debug = debugUtil("Eleventy:Compatibility");
|
||||
|
||||
// Used in user config versionCheck method.
|
||||
class Compatibility {
|
||||
static NORMALIZE_PRERELEASE_REGEX = /-canary\b/g;
|
||||
|
||||
constructor(compatibleRange) {
|
||||
this.compatibleRange = Compatibility.getCompatibilityValue(compatibleRange);
|
||||
}
|
||||
|
||||
static normalizeIdentifier(identifier) {
|
||||
return identifier.replace(Compatibility.NORMALIZE_PRERELEASE_REGEX, "-alpha");
|
||||
}
|
||||
|
||||
static getCompatibilityValue(compatibleRange) {
|
||||
if (compatibleRange) {
|
||||
return compatibleRange;
|
||||
}
|
||||
|
||||
try {
|
||||
// fetch from project’s package.json
|
||||
let projectPackageJson = getWorkingProjectPackageJson();
|
||||
return projectPackageJson["11ty"]?.compatibility;
|
||||
} catch (e) {
|
||||
debug("Could not find a project package.json for compatibility version check: %O", e);
|
||||
return; // do nothing, no compatibility information to check
|
||||
}
|
||||
}
|
||||
|
||||
isCompatible() {
|
||||
return Compatibility.satisfies(pkg.version, this.compatibleRange);
|
||||
}
|
||||
|
||||
static satisfies(version, compatibleRange) {
|
||||
return semver.satisfies(
|
||||
Compatibility.normalizeIdentifier(version),
|
||||
Compatibility.normalizeIdentifier(compatibleRange),
|
||||
{
|
||||
includePrerelease: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
getErrorMessage() {
|
||||
return `We found Eleventy version '${pkg.version}' which does not meet the required version range: '${this.compatibleRange}'. Use \`npm install @11ty/eleventy\` to upgrade your local project to the latest Eleventy version (or \`npm install @11ty/eleventy -g\` to upgrade the globally installed version).`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Compatibility;
|
||||
126
node_modules/@11ty/eleventy/src/Util/ConsoleLogger.js
generated
vendored
Normal file
126
node_modules/@11ty/eleventy/src/Util/ConsoleLogger.js
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
import { Readable } from "node:stream";
|
||||
import chalk from "kleur";
|
||||
import debugUtil from "debug";
|
||||
|
||||
const debug = debugUtil("Eleventy:Logger");
|
||||
|
||||
/**
|
||||
* Logger implementation that logs to STDOUT.
|
||||
* @typedef {'error'|'log'|'warn'|'info'} LogType
|
||||
*/
|
||||
class ConsoleLogger {
|
||||
/** @type {boolean} */
|
||||
#isVerbose = true;
|
||||
/** @type {boolean} */
|
||||
#isChalkEnabled = true;
|
||||
/** @type {object|boolean|undefined} */
|
||||
#logger;
|
||||
|
||||
constructor() {
|
||||
this.outputStream = new Readable({
|
||||
read() {},
|
||||
});
|
||||
}
|
||||
|
||||
get isVerbose() {
|
||||
return this.#isVerbose;
|
||||
}
|
||||
|
||||
set isVerbose(verbose) {
|
||||
this.#isVerbose = !!verbose;
|
||||
}
|
||||
|
||||
get isChalkEnabled() {
|
||||
return this.#isChalkEnabled;
|
||||
}
|
||||
|
||||
set isChalkEnabled(enabled) {
|
||||
this.#isChalkEnabled = !!enabled;
|
||||
}
|
||||
|
||||
overrideLogger(logger) {
|
||||
this.#logger = logger;
|
||||
}
|
||||
|
||||
get logger() {
|
||||
return this.#logger || console;
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
log(msg) {
|
||||
this.message(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef LogOptions
|
||||
* @property {string} message
|
||||
* @property {string=} prefix
|
||||
* @property {LogType=} type
|
||||
* @property {string=} color
|
||||
* @property {boolean=} force
|
||||
* @param {LogOptions} options
|
||||
*/
|
||||
logWithOptions({ message, type, prefix, color, force }) {
|
||||
this.message(message, type, color, force, prefix);
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
forceLog(msg) {
|
||||
this.message(msg, undefined, undefined, true);
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
info(msg) {
|
||||
this.message(msg, "warn", "blue");
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
warn(msg) {
|
||||
this.message(msg, "warn", "yellow");
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
error(msg) {
|
||||
this.message(msg, "error", "red");
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
toStream(msg) {
|
||||
this.outputStream.push(msg);
|
||||
}
|
||||
|
||||
closeStream() {
|
||||
this.outputStream.push(null);
|
||||
return this.outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the message to log.
|
||||
*
|
||||
* @param {string} message - The raw message to log.
|
||||
* @param {LogType} [type='log'] - The error level to log.
|
||||
* @param {string|undefined} [chalkColor=undefined] - Color name or falsy to disable
|
||||
* @param {boolean} [forceToConsole=false] - Enforce a log on console instead of specified target.
|
||||
*/
|
||||
message(
|
||||
message,
|
||||
type = "log",
|
||||
chalkColor = undefined,
|
||||
forceToConsole = false,
|
||||
prefix = "[11ty]",
|
||||
) {
|
||||
if (!forceToConsole && (!this.isVerbose || process.env.DEBUG)) {
|
||||
debug(message);
|
||||
} else if (this.#logger !== false) {
|
||||
message = `${chalk.gray(prefix)} ${message.split("\n").join(`\n${chalk.gray(prefix)} `)}`;
|
||||
|
||||
if (chalkColor && this.isChalkEnabled) {
|
||||
this.logger[type](chalk[chalkColor](message));
|
||||
} else {
|
||||
this.logger[type](message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ConsoleLogger;
|
||||
24
node_modules/@11ty/eleventy/src/Util/DateGitFirstAdded.js
generated
vendored
Normal file
24
node_modules/@11ty/eleventy/src/Util/DateGitFirstAdded.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import spawn from "cross-spawn";
|
||||
|
||||
function getGitFirstAddedTimeStamp(filePath) {
|
||||
return (
|
||||
parseInt(
|
||||
spawn
|
||||
.sync(
|
||||
"git",
|
||||
// Formats https://www.git-scm.com/docs/git-log#_pretty_formats
|
||||
// %at author date, UNIX timestamp
|
||||
["log", "--diff-filter=A", "--follow", "-1", "--format=%at", filePath],
|
||||
)
|
||||
.stdout.toString("utf-8"),
|
||||
) * 1000
|
||||
);
|
||||
}
|
||||
|
||||
// return a Date
|
||||
export default function (inputPath) {
|
||||
let timestamp = getGitFirstAddedTimeStamp(inputPath);
|
||||
if (timestamp) {
|
||||
return new Date(timestamp);
|
||||
}
|
||||
}
|
||||
28
node_modules/@11ty/eleventy/src/Util/DateGitLastUpdated.js
generated
vendored
Normal file
28
node_modules/@11ty/eleventy/src/Util/DateGitLastUpdated.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import spawn from "cross-spawn";
|
||||
|
||||
/* Thank you to Vuepress!
|
||||
* https://github.com/vuejs/vuepress/blob/89440ce552675859189ed4ab254ce19c4bba5447/packages/%40vuepress/plugin-last-updated/index.js
|
||||
* MIT licensed: https://github.com/vuejs/vuepress/blob/89440ce552675859189ed4ab254ce19c4bba5447/LICENSE
|
||||
*/
|
||||
function getGitLastUpdatedTimeStamp(filePath) {
|
||||
return (
|
||||
parseInt(
|
||||
spawn
|
||||
.sync(
|
||||
"git",
|
||||
// Formats https://www.git-scm.com/docs/git-log#_pretty_formats
|
||||
// %at author date, UNIX timestamp
|
||||
["log", "-1", "--format=%at", filePath],
|
||||
)
|
||||
.stdout.toString("utf-8"),
|
||||
) * 1000
|
||||
);
|
||||
}
|
||||
|
||||
// return a Date
|
||||
export default function (inputPath) {
|
||||
let timestamp = getGitLastUpdatedTimeStamp(inputPath);
|
||||
if (timestamp) {
|
||||
return new Date(timestamp);
|
||||
}
|
||||
}
|
||||
9
node_modules/@11ty/eleventy/src/Util/DirContains.js
generated
vendored
Normal file
9
node_modules/@11ty/eleventy/src/Util/DirContains.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import path from "node:path";
|
||||
|
||||
// Returns true if subfolder is in parent (accepts absolute or relative paths for both)
|
||||
export default function (parent, subfolder) {
|
||||
if (path.resolve(subfolder).startsWith(path.resolve(parent))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
51
node_modules/@11ty/eleventy/src/Util/EsmResolver.js
generated
vendored
Normal file
51
node_modules/@11ty/eleventy/src/Util/EsmResolver.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
import debugUtil from "debug";
|
||||
|
||||
const debug = debugUtil("Eleventy:EsmResolver");
|
||||
|
||||
let lastModifiedPaths = new Map();
|
||||
export async function initialize({ port }) {
|
||||
// From `eleventy.importCacheReset` event in Require.js
|
||||
port.on("message", ({ path, newDate }) => {
|
||||
lastModifiedPaths.set(path, newDate);
|
||||
});
|
||||
}
|
||||
|
||||
// Fixes issue https://github.com/11ty/eleventy/issues/3270
|
||||
// Docs: https://nodejs.org/docs/latest/api/module.html#resolvespecifier-context-nextresolve
|
||||
export async function resolve(specifier, context, nextResolve) {
|
||||
try {
|
||||
// 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("./") &&
|
||||
!specifier.startsWith("file:")) ||
|
||||
context.parentURL.includes("/node_modules/")
|
||||
) {
|
||||
return nextResolve(specifier);
|
||||
}
|
||||
|
||||
let fileUrl = new URL(specifier, context.parentURL);
|
||||
if (fileUrl.searchParams.has("_cache_bust")) {
|
||||
// already is cache busted outside resolver (wider compat, url was changed prior to import, probably in Require.js)
|
||||
return nextResolve(specifier);
|
||||
}
|
||||
|
||||
let absolutePath = fileUrl.pathname;
|
||||
// Bust the import cache if this is a recently modified file
|
||||
if (lastModifiedPaths.has(absolutePath)) {
|
||||
fileUrl.search = ""; // delete existing searchparams
|
||||
fileUrl.searchParams.set("_cache_bust", lastModifiedPaths.get(absolutePath));
|
||||
debug("Cache busting %o to %o", specifier, fileUrl.toString());
|
||||
|
||||
return nextResolve(fileUrl.toString());
|
||||
}
|
||||
} catch (e) {
|
||||
debug("EsmResolver Error parsing specifier (%o): %o", specifier, e);
|
||||
}
|
||||
|
||||
return nextResolve(specifier);
|
||||
}
|
||||
|
||||
// export async function load(url, context, nextLoad) {
|
||||
// }
|
||||
26
node_modules/@11ty/eleventy/src/Util/EventBusUtil.js
generated
vendored
Normal file
26
node_modules/@11ty/eleventy/src/Util/EventBusUtil.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import eventBus from "../EventBus.js";
|
||||
import debugUtil from "debug";
|
||||
|
||||
const debug = debugUtil("Eleventy:EventBus");
|
||||
|
||||
class EventBusUtil {
|
||||
// Used for non-global subscriptions that will blow away the previous listener
|
||||
static soloOn(name, callback) {
|
||||
eventBus.off(name, callback);
|
||||
eventBus.on(name, callback);
|
||||
}
|
||||
|
||||
static resetForConfig() {
|
||||
this.debug();
|
||||
debug("Config reset (removing eleventy.templateModified listeners).");
|
||||
eventBus.removeAllListeners("eleventy.templateModified");
|
||||
}
|
||||
|
||||
static debug() {
|
||||
for (let name of eventBus.eventNames()) {
|
||||
debug("Listeners for %o: %o", name, eventBus.listenerCount(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EventBusUtil;
|
||||
82
node_modules/@11ty/eleventy/src/Util/ExistsCache.js
generated
vendored
Normal file
82
node_modules/@11ty/eleventy/src/Util/ExistsCache.js
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
import fs from "graceful-fs";
|
||||
import PathNormalizer from "./PathNormalizer.js";
|
||||
|
||||
// Checks both files and directories
|
||||
class ExistsCache {
|
||||
constructor() {
|
||||
this._cache = new Map();
|
||||
this.lookupCount = 0;
|
||||
}
|
||||
|
||||
setDirectoryCheck(check) {
|
||||
this.cacheDirectories = !!check;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._cache.size;
|
||||
}
|
||||
|
||||
parentsDoNotExist(path) {
|
||||
if (!this.cacheDirectories) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let allPaths = PathNormalizer.getAllPaths(path).filter((entry) => entry !== path);
|
||||
for (let parentPath of allPaths) {
|
||||
if (this._cache.has(parentPath)) {
|
||||
if (this._cache.get(parentPath) === false) {
|
||||
return true; // we know this parent doesn’t exist
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if you’ve made it here: we don’t know if the parents exist or not
|
||||
return false;
|
||||
}
|
||||
|
||||
has(path) {
|
||||
return this._cache.has(path);
|
||||
}
|
||||
|
||||
exists(path) {
|
||||
path = PathNormalizer.fullNormalization(path);
|
||||
|
||||
let exists = this._cache.get(path);
|
||||
if (this.parentsDoNotExist(path)) {
|
||||
// we don’t need to check if a parent directory does not exist
|
||||
exists = false;
|
||||
} else if (!this.has(path)) {
|
||||
exists = fs.existsSync(path);
|
||||
this.markExistsWithParentDirectories(path, exists);
|
||||
this.lookupCount++;
|
||||
}
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
// if a file exists, we can mark the parent directories as existing also
|
||||
// if a file does not exist, we don’t know if the parent directories exist or not (yet)
|
||||
markExistsWithParentDirectories(path, exists = true) {
|
||||
path = PathNormalizer.fullNormalization(path);
|
||||
|
||||
if (!this.cacheDirectories || !exists) {
|
||||
this.markExists(path, false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let paths = PathNormalizer.getAllPaths(path);
|
||||
for (let fullpath of paths) {
|
||||
this.markExists(fullpath, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
markExists(path, exists = true, alreadyNormalized = false) {
|
||||
if (!alreadyNormalized) {
|
||||
path = PathNormalizer.fullNormalization(path);
|
||||
}
|
||||
|
||||
this._cache.set(path, !!exists);
|
||||
}
|
||||
}
|
||||
|
||||
export default ExistsCache;
|
||||
19
node_modules/@11ty/eleventy/src/Util/FilePathUtil.js
generated
vendored
Normal file
19
node_modules/@11ty/eleventy/src/Util/FilePathUtil.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
class FilePathUtil {
|
||||
static isMatchingExtension(filepath, fileExtension) {
|
||||
if (!fileExtension) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(fileExtension || "").startsWith(".")) {
|
||||
fileExtension = "." + fileExtension;
|
||||
}
|
||||
|
||||
return filepath.endsWith(fileExtension);
|
||||
}
|
||||
|
||||
static getFileExtension(filepath) {
|
||||
return (filepath || "").split(".").pop();
|
||||
}
|
||||
}
|
||||
|
||||
export { FilePathUtil };
|
||||
30
node_modules/@11ty/eleventy/src/Util/GetJavaScriptData.js
generated
vendored
Normal file
30
node_modules/@11ty/eleventy/src/Util/GetJavaScriptData.js
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
|
||||
class JavaScriptInvalidDataFormatError extends EleventyBaseError {}
|
||||
|
||||
export default async function (inst, inputPath, key = "data", options = {}) {
|
||||
let { mixins, isObjectRequired } = Object.assign(
|
||||
{
|
||||
mixins: {},
|
||||
isObjectRequired: true,
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
if (inst && key in inst) {
|
||||
// get extra data from `data` method,
|
||||
// either as a function or getter or object literal
|
||||
let result = await (typeof inst[key] === "function"
|
||||
? Object.keys(mixins).length > 0
|
||||
? inst[key].call(mixins)
|
||||
: inst[key]()
|
||||
: inst[key]);
|
||||
|
||||
if (isObjectRequired && typeof result !== "object") {
|
||||
throw new JavaScriptInvalidDataFormatError(
|
||||
`Invalid data format returned from ${inputPath}: typeof ${typeof result}`,
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
21
node_modules/@11ty/eleventy/src/Util/GlobMatcher.js
generated
vendored
Normal file
21
node_modules/@11ty/eleventy/src/Util/GlobMatcher.js
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import micromatch from "micromatch";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
function isGlobMatch(filepath, globs = [], options = undefined) {
|
||||
if (!filepath || !Array.isArray(globs) || globs.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let inputPath = TemplatePath.stripLeadingDotSlash(filepath);
|
||||
let opts = Object.assign(
|
||||
{
|
||||
dot: true,
|
||||
nocase: true, // insensitive
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
return micromatch.isMatch(inputPath, globs, opts);
|
||||
}
|
||||
|
||||
export { isGlobMatch };
|
||||
158
node_modules/@11ty/eleventy/src/Util/HtmlTransformer.js
generated
vendored
Normal file
158
node_modules/@11ty/eleventy/src/Util/HtmlTransformer.js
generated
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
import posthtml from "posthtml";
|
||||
import urls from "@11ty/posthtml-urls";
|
||||
import { FilePathUtil } from "./FilePathUtil.js";
|
||||
|
||||
class HtmlTransformer {
|
||||
// feature test for Eleventy Bundle Plugin
|
||||
static SUPPORTS_PLUGINS_ENABLED_CALLBACK = true;
|
||||
|
||||
constructor() {
|
||||
// execution order is important (not order of addition/object key order)
|
||||
this.callbacks = {};
|
||||
this.posthtmlProcessOptions = {};
|
||||
this.plugins = {};
|
||||
}
|
||||
|
||||
get aggregateBench() {
|
||||
if (!this.userConfig) {
|
||||
throw new Error("Internal error: Missing `userConfig` in HtmlTransformer.");
|
||||
}
|
||||
return this.userConfig.benchmarkManager.get("Aggregate");
|
||||
}
|
||||
|
||||
setUserConfig(config) {
|
||||
this.userConfig = config;
|
||||
}
|
||||
|
||||
static prioritySort(a, b) {
|
||||
if (b.priority > a.priority) {
|
||||
return 1;
|
||||
}
|
||||
if (a.priority > b.priority) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// context is important as it is used in html base plugin for page specific URL
|
||||
static _getPosthtmlInstance(callbacks = [], plugins = [], context = {}) {
|
||||
let inst = posthtml();
|
||||
|
||||
// already sorted by priority when added
|
||||
for (let { fn: plugin, options } of plugins) {
|
||||
inst.use(plugin(Object.assign({}, context, options)));
|
||||
}
|
||||
|
||||
// Run the built-ins last
|
||||
if (callbacks.length > 0) {
|
||||
inst.use(
|
||||
urls({
|
||||
eachURL: (url) => {
|
||||
// and: attrName, tagName
|
||||
for (let { fn: callback } of callbacks) {
|
||||
// already sorted by priority when added
|
||||
url = callback.call(context, url);
|
||||
}
|
||||
|
||||
return url;
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
_add(extensions, addType, value, options = {}) {
|
||||
options = Object.assign(
|
||||
{
|
||||
priority: 0,
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
let extensionsArray = (extensions || "").split(",");
|
||||
for (let ext of extensionsArray) {
|
||||
let target = this[addType];
|
||||
if (!target[ext]) {
|
||||
target[ext] = [];
|
||||
}
|
||||
|
||||
target[ext].push({
|
||||
fn: value, // callback or plugin
|
||||
priority: options.priority,
|
||||
enabled: options.enabled || (() => true),
|
||||
options: options.pluginOptions,
|
||||
});
|
||||
|
||||
target[ext].sort(HtmlTransformer.prioritySort);
|
||||
}
|
||||
}
|
||||
|
||||
addPosthtmlPlugin(extensions, plugin, options = {}) {
|
||||
this._add(extensions, "plugins", plugin, options);
|
||||
}
|
||||
|
||||
addUrlTransform(extensions, callback, options = {}) {
|
||||
this._add(extensions, "callbacks", callback, options);
|
||||
}
|
||||
|
||||
setPosthtmlProcessOptions(options) {
|
||||
Object.assign(this.posthtmlProcessOptions, options);
|
||||
}
|
||||
|
||||
isTransformable(extension, context) {
|
||||
return (
|
||||
this.getCallbacks(extension, context).length > 0 || this.getPlugins(extension).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
getCallbacks(extension, context) {
|
||||
let callbacks = this.callbacks[extension] || [];
|
||||
return callbacks.filter(({ enabled }) => {
|
||||
if (!enabled || typeof enabled !== "function") {
|
||||
return true;
|
||||
}
|
||||
return enabled(context);
|
||||
});
|
||||
}
|
||||
|
||||
getPlugins(extension) {
|
||||
let plugins = this.plugins[extension] || [];
|
||||
return plugins.filter(({ enabled }) => {
|
||||
if (!enabled || typeof enabled !== "function") {
|
||||
return true;
|
||||
}
|
||||
return enabled();
|
||||
});
|
||||
}
|
||||
|
||||
static async transformStandalone(content, callback, posthtmlProcessOptions = {}) {
|
||||
let posthtmlInstance = this._getPosthtmlInstance([
|
||||
{
|
||||
fn: callback,
|
||||
enabled: () => true,
|
||||
},
|
||||
]);
|
||||
let result = await posthtmlInstance.process(content, posthtmlProcessOptions);
|
||||
return result.html;
|
||||
}
|
||||
|
||||
async transformContent(outputPath, content, context) {
|
||||
let extension = FilePathUtil.getFileExtension(outputPath);
|
||||
if (!this.isTransformable(extension, context)) {
|
||||
return content;
|
||||
}
|
||||
|
||||
let bench = this.aggregateBench.get(`Transforming \`${extension}\` with posthtml`);
|
||||
bench.before();
|
||||
let callbacks = this.getCallbacks(extension, context);
|
||||
let plugins = this.getPlugins(extension);
|
||||
let posthtmlInstance = HtmlTransformer._getPosthtmlInstance(callbacks, plugins, context);
|
||||
let result = await posthtmlInstance.process(content, this.posthtmlProcessOptions);
|
||||
bench.after();
|
||||
return result.html;
|
||||
}
|
||||
}
|
||||
|
||||
export { HtmlTransformer };
|
||||
64
node_modules/@11ty/eleventy/src/Util/ImportJsonSync.js
generated
vendored
Normal file
64
node_modules/@11ty/eleventy/src/Util/ImportJsonSync.js
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import { normalizeFilePathInEleventyPackage } from "./Require.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:ImportJsonSync");
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
function findFilePathInParentDirs(dir, filename) {
|
||||
// `package.json` searches look in parent dirs:
|
||||
// https://docs.npmjs.com/cli/v7/configuring-npm/folders#more-information
|
||||
// Fixes issue #3178, limited to working dir paths only
|
||||
let workingDir = TemplatePath.getWorkingDir();
|
||||
let allDirs = TemplatePath.getAllDirs(dir).filter((entry) => entry.startsWith(workingDir));
|
||||
|
||||
for (let dir of allDirs) {
|
||||
let newPath = TemplatePath.join(dir, filename);
|
||||
if (fs.existsSync(newPath)) {
|
||||
debug("Found %o searching parent directories at: %o", filename, dir);
|
||||
return newPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function importJsonSync(filePath) {
|
||||
if (!filePath.endsWith(".json")) {
|
||||
throw new Error(`importJsonSync expects a .json file extension (received: ${filePath})`);
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO clear require.cache when these files change
|
||||
return require(filePath);
|
||||
} catch (e) {
|
||||
debug("Attempted to import %o, received this error: %o", filePath, e);
|
||||
// if file does not exist, return nothing
|
||||
}
|
||||
}
|
||||
|
||||
function getEleventyPackageJson() {
|
||||
let filePath = normalizeFilePathInEleventyPackage("package.json");
|
||||
return importJsonSync(filePath);
|
||||
}
|
||||
|
||||
function getModulePackageJson(dir) {
|
||||
let filePath = findFilePathInParentDirs(TemplatePath.absolutePath(dir), "package.json");
|
||||
return importJsonSync(filePath);
|
||||
}
|
||||
|
||||
function getWorkingProjectPackageJson() {
|
||||
let dir = TemplatePath.absolutePath(TemplatePath.getWorkingDir());
|
||||
let filePath = findFilePathInParentDirs(dir, "package.json");
|
||||
return importJsonSync(filePath);
|
||||
}
|
||||
|
||||
export {
|
||||
importJsonSync,
|
||||
findFilePathInParentDirs,
|
||||
getEleventyPackageJson,
|
||||
getModulePackageJson,
|
||||
getWorkingProjectPackageJson,
|
||||
};
|
||||
5
node_modules/@11ty/eleventy/src/Util/IsAsyncFunction.js
generated
vendored
Normal file
5
node_modules/@11ty/eleventy/src/Util/IsAsyncFunction.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
const ComparisonAsyncFunction = (async () => {}).constructor;
|
||||
|
||||
export default function isAsyncFunction(fn) {
|
||||
return fn instanceof ComparisonAsyncFunction;
|
||||
}
|
||||
55
node_modules/@11ty/eleventy/src/Util/JavaScriptDependencies.js
generated
vendored
Normal file
55
node_modules/@11ty/eleventy/src/Util/JavaScriptDependencies.js
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import dependencyTree from "@11ty/dependency-tree";
|
||||
import { find } from "@11ty/dependency-tree-esm";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
|
||||
class JavaScriptDependencies {
|
||||
static getErrorMessage(file, type) {
|
||||
return `A problem was encountered looking for JavaScript dependencies in ${type} file: ${file}. This only affects --watch and --serve behavior and does not affect your build.`;
|
||||
}
|
||||
|
||||
static async getDependencies(inputFiles, isProjectUsingEsm) {
|
||||
let depSet = new Set();
|
||||
|
||||
// TODO does this need to work with aliasing? what other JS extensions will have deps?
|
||||
let commonJsFiles = inputFiles.filter(
|
||||
(file) => (!isProjectUsingEsm && file.endsWith(".js")) || file.endsWith(".cjs"),
|
||||
);
|
||||
|
||||
for (let file of commonJsFiles) {
|
||||
try {
|
||||
let modules = dependencyTree(file, {
|
||||
nodeModuleNames: "exclude",
|
||||
allowNotFound: true,
|
||||
}).map((dependency) => {
|
||||
return TemplatePath.addLeadingDotSlash(TemplatePath.relativePath(dependency));
|
||||
});
|
||||
|
||||
for (let dep of modules) {
|
||||
depSet.add(dep);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new EleventyBaseError(this.getErrorMessage(file, "CommonJS"), e);
|
||||
}
|
||||
}
|
||||
|
||||
let esmFiles = inputFiles.filter(
|
||||
(file) => (isProjectUsingEsm && file.endsWith(".js")) || file.endsWith(".mjs"),
|
||||
);
|
||||
for (let file of esmFiles) {
|
||||
try {
|
||||
let modules = await find(file);
|
||||
for (let dep of modules) {
|
||||
depSet.add(dep);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new EleventyBaseError(this.getErrorMessage(file, "ESM"), e);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(depSet).sort();
|
||||
}
|
||||
}
|
||||
|
||||
export default JavaScriptDependencies;
|
||||
26
node_modules/@11ty/eleventy/src/Util/MemoizeFunction.js
generated
vendored
Normal file
26
node_modules/@11ty/eleventy/src/Util/MemoizeFunction.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
export default function (callback, options = {}) {
|
||||
let { bench, name } = options;
|
||||
let cache = new Map();
|
||||
|
||||
return (...args) => {
|
||||
// Only supports single-arg functions for now.
|
||||
if (args.filter(Boolean).length > 1) {
|
||||
bench?.get(`(count) ${name} Not valid for memoize`).incrementCount();
|
||||
return callback(...args);
|
||||
}
|
||||
|
||||
let [cacheKey] = args;
|
||||
|
||||
if (!cache.has(cacheKey)) {
|
||||
cache.set(cacheKey, callback(...args));
|
||||
|
||||
bench?.get(`(count) ${name} memoize miss`).incrementCount();
|
||||
|
||||
return cache.get(cacheKey);
|
||||
}
|
||||
|
||||
bench?.get(`(count) ${name} memoize hit`).incrementCount();
|
||||
|
||||
return cache.get(cacheKey);
|
||||
};
|
||||
}
|
||||
20
node_modules/@11ty/eleventy/src/Util/Objects/DeepFreeze.js
generated
vendored
Normal file
20
node_modules/@11ty/eleventy/src/Util/Objects/DeepFreeze.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import { isPlainObject } from "@11ty/eleventy-utils";
|
||||
|
||||
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
|
||||
|
||||
function DeepFreeze(obj, topLevelExceptions) {
|
||||
for (let name of Reflect.ownKeys(obj)) {
|
||||
if ((topLevelExceptions || []).find((key) => key === name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = obj[name];
|
||||
if (isPlainObject(value)) {
|
||||
DeepFreeze(value);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze(obj);
|
||||
}
|
||||
|
||||
export { DeepFreeze };
|
||||
9
node_modules/@11ty/eleventy/src/Util/Objects/ObjectFilter.js
generated
vendored
Normal file
9
node_modules/@11ty/eleventy/src/Util/Objects/ObjectFilter.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
export default function objectFilter(obj, callback) {
|
||||
let newObject = {};
|
||||
for (let [key, value] of Object.entries(obj || {})) {
|
||||
if (callback(value, key)) {
|
||||
newObject[key] = value;
|
||||
}
|
||||
}
|
||||
return newObject;
|
||||
}
|
||||
111
node_modules/@11ty/eleventy/src/Util/Objects/ProxyWrap.js
generated
vendored
Normal file
111
node_modules/@11ty/eleventy/src/Util/Objects/ProxyWrap.js
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
import types from "node:util/types";
|
||||
import debugUtil from "debug";
|
||||
import { isPlainObject } from "@11ty/eleventy-utils";
|
||||
|
||||
const debug = debugUtil("Dev:Eleventy:Proxy");
|
||||
|
||||
function wrapObject(target, fallback) {
|
||||
if (Object.isFrozen(target)) {
|
||||
return target;
|
||||
}
|
||||
|
||||
return new Proxy(target, {
|
||||
getOwnPropertyDescriptor(target, prop) {
|
||||
let ret;
|
||||
|
||||
if (Reflect.has(target, prop)) {
|
||||
ret = Reflect.getOwnPropertyDescriptor(target, prop);
|
||||
} else if (Reflect.has(fallback, prop)) {
|
||||
ret = Reflect.getOwnPropertyDescriptor(fallback, prop);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
has(target, prop) {
|
||||
if (Reflect.has(target, prop)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Reflect.has(fallback, prop);
|
||||
},
|
||||
ownKeys(target) {
|
||||
let s = new Set();
|
||||
for (let k of Reflect.ownKeys(target)) {
|
||||
s.add(k);
|
||||
}
|
||||
if (isPlainObject(fallback)) {
|
||||
for (let k of Reflect.ownKeys(fallback)) {
|
||||
s.add(k);
|
||||
}
|
||||
}
|
||||
return Array.from(s);
|
||||
},
|
||||
get(target, prop) {
|
||||
debug("handler:get", prop);
|
||||
|
||||
let value = Reflect.get(target, prop);
|
||||
|
||||
if (Reflect.has(target, prop)) {
|
||||
// Already proxied
|
||||
if (types.isProxy(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (isPlainObject(value) && Reflect.has(fallback, prop)) {
|
||||
if (Object.isFrozen(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
let ret = wrapObject(value, Reflect.get(fallback, prop));
|
||||
debug("handler:get (primary, object)", prop);
|
||||
return ret;
|
||||
}
|
||||
|
||||
debug("handler:get (primary)", prop);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Does not exist in primary
|
||||
if (Reflect.has(fallback, prop)) {
|
||||
// fallback has prop
|
||||
let fallbackValue = Reflect.get(fallback, prop);
|
||||
|
||||
if (isPlainObject(fallbackValue)) {
|
||||
if (Object.isFrozen(fallbackValue)) {
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
debug("handler:get (fallback, object)", prop);
|
||||
// set empty object on primary
|
||||
let emptyObject = {};
|
||||
Reflect.set(target, prop, emptyObject);
|
||||
|
||||
return wrapObject(emptyObject, fallbackValue);
|
||||
}
|
||||
|
||||
debug("handler:get (fallback)", prop);
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
// primary *and* fallback do _not_ have prop
|
||||
debug("handler:get (not on primary or fallback)", prop);
|
||||
|
||||
return value;
|
||||
},
|
||||
set(target, prop, value) {
|
||||
debug("handler:set", prop);
|
||||
|
||||
return Reflect.set(target, prop, value);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function ProxyWrap(target, fallback) {
|
||||
if (!isPlainObject(target) || !isPlainObject(fallback)) {
|
||||
throw new Error("ProxyWrap expects objects for both the target and fallback");
|
||||
}
|
||||
|
||||
return wrapObject(target, fallback);
|
||||
}
|
||||
|
||||
export { ProxyWrap };
|
||||
1
node_modules/@11ty/eleventy/src/Util/Objects/SampleModule.mjs
generated
vendored
Normal file
1
node_modules/@11ty/eleventy/src/Util/Objects/SampleModule.mjs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default {};
|
||||
136
node_modules/@11ty/eleventy/src/Util/Objects/Sortable.js
generated
vendored
Normal file
136
node_modules/@11ty/eleventy/src/Util/Objects/Sortable.js
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
class Sortable {
|
||||
constructor() {
|
||||
this.isSortAscending = true;
|
||||
this.isSortNumeric = false;
|
||||
this.items = [];
|
||||
this._dirty = true;
|
||||
|
||||
this.sortFunctionStringMap = {
|
||||
"A-Z": "sortFunctionAscending",
|
||||
"Z-A": "sortFunctionDescending",
|
||||
"0-9": "sortFunctionNumericAscending",
|
||||
"9-0": "sortFunctionNumericDescending",
|
||||
};
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.items.length;
|
||||
}
|
||||
|
||||
add(item) {
|
||||
this._dirty = true;
|
||||
this.items.push(item);
|
||||
}
|
||||
|
||||
sort(sortFunction) {
|
||||
if (!sortFunction) {
|
||||
sortFunction = this.getSortFunction();
|
||||
} else if (typeof sortFunction === "string") {
|
||||
let key = sortFunction;
|
||||
let name;
|
||||
if (key in this.sortFunctionStringMap) {
|
||||
name = this.sortFunctionStringMap[key];
|
||||
}
|
||||
if (Sortable[name]) {
|
||||
sortFunction = Sortable[name];
|
||||
} else {
|
||||
throw new Error(
|
||||
`Invalid String argument for sort(). Received \`${key}\`. Valid values: ${Object.keys(
|
||||
this.sortFunctionStringMap,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return this.items.slice().sort(sortFunction);
|
||||
}
|
||||
|
||||
sortAscending() {
|
||||
return this.sort(this.getSortFunctionAscending());
|
||||
}
|
||||
|
||||
sortDescending() {
|
||||
return this.sort(this.getSortFunctionDescending());
|
||||
}
|
||||
|
||||
setSortDescending(isDescending = true) {
|
||||
this.isSortAscending = !isDescending;
|
||||
}
|
||||
|
||||
setSortAscending(isAscending = true) {
|
||||
this.isSortAscending = isAscending;
|
||||
}
|
||||
|
||||
setSortNumeric(isNumeric) {
|
||||
this.isSortNumeric = isNumeric;
|
||||
}
|
||||
|
||||
/* Sort functions */
|
||||
static sortFunctionNumericAscending(a, b) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
static sortFunctionNumericDescending(a, b) {
|
||||
return b - a;
|
||||
}
|
||||
|
||||
static sortFunctionAscending(a, b) {
|
||||
if (a > b) {
|
||||
return 1;
|
||||
} else if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static sortFunctionDescending(a, b) {
|
||||
return Sortable.sortFunctionAscending(b, a);
|
||||
}
|
||||
|
||||
static sortFunctionAlphabeticAscending(a, b) {
|
||||
return Sortable.sortFunctionAscending(a, b);
|
||||
}
|
||||
|
||||
static sortFunctionAlphabeticDescending(a, b) {
|
||||
return Sortable.sortFunctionAscending(b, a);
|
||||
}
|
||||
|
||||
static sortFunctionDate(mapA, mapB) {
|
||||
return Sortable.sortFunctionNumericAscending(mapA.date.getTime(), mapB.date.getTime());
|
||||
}
|
||||
|
||||
static sortFunctionDateInputPath(mapA, mapB) {
|
||||
let sortDate = Sortable.sortFunctionNumericAscending(mapA.date.getTime(), mapB.date.getTime());
|
||||
if (sortDate === 0) {
|
||||
return Sortable.sortFunctionAlphabeticAscending(mapA.inputPath, mapB.inputPath);
|
||||
}
|
||||
return sortDate;
|
||||
}
|
||||
/* End sort functions */
|
||||
|
||||
getSortFunction() {
|
||||
if (this.isSortAscending) {
|
||||
return this.getSortFunctionAscending();
|
||||
} else {
|
||||
return this.getSortFunctionDescending();
|
||||
}
|
||||
}
|
||||
|
||||
getSortFunctionAscending() {
|
||||
if (this.isSortNumeric) {
|
||||
return Sortable.sortFunctionNumericAscending;
|
||||
} else {
|
||||
return Sortable.sortFunctionAlphabeticAscending;
|
||||
}
|
||||
}
|
||||
|
||||
getSortFunctionDescending() {
|
||||
if (this.isSortNumeric) {
|
||||
return Sortable.sortFunctionNumericDescending;
|
||||
} else {
|
||||
return Sortable.sortFunctionAlphabeticDescending;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Sortable;
|
||||
3
node_modules/@11ty/eleventy/src/Util/Objects/Unique.js
generated
vendored
Normal file
3
node_modules/@11ty/eleventy/src/Util/Objects/Unique.js
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Unique(arr) {
|
||||
return Array.from(new Set(arr));
|
||||
}
|
||||
16
node_modules/@11ty/eleventy/src/Util/PassthroughCopyBehaviorCheck.js
generated
vendored
Normal file
16
node_modules/@11ty/eleventy/src/Util/PassthroughCopyBehaviorCheck.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
function isUsingEleventyDevServer(config) {
|
||||
return (
|
||||
!config.serverOptions.module || config.serverOptions.module === "@11ty/eleventy-dev-server"
|
||||
);
|
||||
}
|
||||
|
||||
// Config opt-out via serverPassthroughCopyBehavior
|
||||
// False when other server is used
|
||||
// False when runMode is "build" or "watch"
|
||||
export default function (config, runMode) {
|
||||
return (
|
||||
config.serverPassthroughCopyBehavior === "passthrough" &&
|
||||
isUsingEleventyDevServer(config) &&
|
||||
runMode === "serve"
|
||||
);
|
||||
}
|
||||
60
node_modules/@11ty/eleventy/src/Util/PathNormalizer.js
generated
vendored
Normal file
60
node_modules/@11ty/eleventy/src/Util/PathNormalizer.js
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
class PathNormalizer {
|
||||
static getParts(inputPath) {
|
||||
if (!inputPath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let separator = "/";
|
||||
if (inputPath.includes(path.sep)) {
|
||||
separator = path.sep;
|
||||
}
|
||||
|
||||
return inputPath.split(separator).filter((entry) => entry !== ".");
|
||||
}
|
||||
|
||||
// order is important here: the top-most directory returns first
|
||||
// array of file and all parent directories
|
||||
static getAllPaths(inputPath) {
|
||||
let parts = this.getParts(inputPath);
|
||||
let allPaths = [];
|
||||
|
||||
let fullpath = "";
|
||||
for (let part of parts) {
|
||||
fullpath += (fullpath.length > 0 ? "/" : "") + part;
|
||||
allPaths.push(fullpath);
|
||||
}
|
||||
|
||||
return allPaths;
|
||||
}
|
||||
|
||||
static normalizeSeperator(inputPath) {
|
||||
if (!inputPath) {
|
||||
return inputPath;
|
||||
}
|
||||
return inputPath.split(path.sep).join("/");
|
||||
}
|
||||
|
||||
static fullNormalization(inputPath) {
|
||||
if (typeof inputPath !== "string") {
|
||||
return inputPath;
|
||||
}
|
||||
|
||||
// Fix file:///Users/ or file:///C:/ paths passed in
|
||||
if (inputPath.startsWith("file://")) {
|
||||
inputPath = fileURLToPath(inputPath);
|
||||
}
|
||||
|
||||
// Paths should not be absolute (we convert absolute paths to relative)
|
||||
// Paths should not have a leading dot slash
|
||||
// Paths should always be `/` independent of OS path separator
|
||||
return TemplatePath.stripLeadingDotSlash(
|
||||
this.normalizeSeperator(TemplatePath.relativePath(inputPath)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PathNormalizer;
|
||||
21
node_modules/@11ty/eleventy/src/Util/PathPrefixer.js
generated
vendored
Normal file
21
node_modules/@11ty/eleventy/src/Util/PathPrefixer.js
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import path from "node:path";
|
||||
|
||||
import PathNormalizer from "./PathNormalizer.js";
|
||||
|
||||
class PathPrefixer {
|
||||
static normalizePathPrefix(pathPrefix) {
|
||||
if (pathPrefix) {
|
||||
// add leading / (for browsersync), see #1454
|
||||
// path.join uses \\ for Windows so we split and rejoin
|
||||
return PathPrefixer.joinUrlParts("/", pathPrefix);
|
||||
}
|
||||
|
||||
return "/";
|
||||
}
|
||||
|
||||
static joinUrlParts(...parts) {
|
||||
return PathNormalizer.normalizeSeperator(path.join(...parts));
|
||||
}
|
||||
}
|
||||
|
||||
export default PathPrefixer;
|
||||
3
node_modules/@11ty/eleventy/src/Util/Pluralize.js
generated
vendored
Normal file
3
node_modules/@11ty/eleventy/src/Util/Pluralize.js
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function (count, singleWord, pluralWord) {
|
||||
return count === 1 ? singleWord : pluralWord;
|
||||
}
|
||||
344
node_modules/@11ty/eleventy/src/Util/ProjectDirectories.js
generated
vendored
Normal file
344
node_modules/@11ty/eleventy/src/Util/ProjectDirectories.js
generated
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import isGlob from "is-glob";
|
||||
|
||||
/* Directories internally should always use *nix forward slashes */
|
||||
class ProjectDirectories {
|
||||
static defaults = {
|
||||
input: "./",
|
||||
data: "./_data/", // Relative to input directory
|
||||
includes: "./_includes/", // Relative to input directory
|
||||
layouts: "./_layouts/", // Relative to input directory
|
||||
output: "./_site/",
|
||||
};
|
||||
|
||||
// no updates allowed, input/output set via CLI
|
||||
#frozen = false;
|
||||
|
||||
#raw = {};
|
||||
|
||||
#dirs = {};
|
||||
|
||||
inputFile = undefined;
|
||||
inputGlob = undefined;
|
||||
|
||||
// Add leading dot slash
|
||||
// Use forward slashes
|
||||
static normalizePath(fileOrDir) {
|
||||
return TemplatePath.standardizeFilePath(fileOrDir);
|
||||
}
|
||||
|
||||
// Must be a directory
|
||||
// Always include a trailing slash
|
||||
static normalizeDirectory(dir) {
|
||||
return this.addTrailingSlash(this.normalizePath(dir));
|
||||
}
|
||||
|
||||
normalizeDirectoryPathRelativeToInputDirectory(filePath) {
|
||||
return ProjectDirectories.normalizeDirectory(path.join(this.input, filePath));
|
||||
}
|
||||
|
||||
static addTrailingSlash(path) {
|
||||
if (path.slice(-1) === "/") {
|
||||
return path;
|
||||
}
|
||||
return path + "/";
|
||||
}
|
||||
|
||||
// If input/output are set via CLI, they take precedence over all other configuration values.
|
||||
freeze() {
|
||||
this.#frozen = true;
|
||||
}
|
||||
|
||||
setViaConfigObject(configDirs = {}) {
|
||||
// input must come last
|
||||
let inputChanged = false;
|
||||
if (
|
||||
configDirs.input &&
|
||||
ProjectDirectories.normalizeDirectory(configDirs.input) !== this.input
|
||||
) {
|
||||
this.#setInputRaw(configDirs.input);
|
||||
inputChanged = true;
|
||||
}
|
||||
|
||||
// If falsy or an empty string, the current directory is used.
|
||||
if (configDirs.output !== undefined) {
|
||||
if (ProjectDirectories.normalizeDirectory(configDirs.output) !== this.output) {
|
||||
this.setOutput(configDirs.output);
|
||||
}
|
||||
}
|
||||
|
||||
// Input relative directory, if falsy or an empty string, inputDir is used!
|
||||
// Always set if input changed, e.g. input is `src` and data is `../_data` (resulting in `./_data`) we still want to set data to this new value
|
||||
if (configDirs.data !== undefined) {
|
||||
if (
|
||||
inputChanged ||
|
||||
this.normalizeDirectoryPathRelativeToInputDirectory(configDirs.data || "") !== this.data
|
||||
) {
|
||||
this.setData(configDirs.data);
|
||||
}
|
||||
}
|
||||
|
||||
// Input relative directory, if falsy or an empty string, inputDir is used!
|
||||
if (configDirs.includes !== undefined) {
|
||||
if (
|
||||
inputChanged ||
|
||||
this.normalizeDirectoryPathRelativeToInputDirectory(configDirs.includes || "") !==
|
||||
this.includes
|
||||
) {
|
||||
this.setIncludes(configDirs.includes);
|
||||
}
|
||||
}
|
||||
|
||||
// Input relative directory, if falsy or an empty string, inputDir is used!
|
||||
if (configDirs.layouts !== undefined) {
|
||||
if (
|
||||
inputChanged ||
|
||||
this.normalizeDirectoryPathRelativeToInputDirectory(configDirs.layouts || "") !==
|
||||
this.layouts
|
||||
) {
|
||||
this.setLayouts(configDirs.layouts);
|
||||
}
|
||||
}
|
||||
|
||||
if (inputChanged) {
|
||||
this.updateInputDependencies();
|
||||
}
|
||||
}
|
||||
|
||||
updateInputDependencies() {
|
||||
// raw first, fall back to Eleventy defaults if not yet set
|
||||
this.setData(this.#raw.data ?? ProjectDirectories.defaults.data);
|
||||
this.setIncludes(this.#raw.includes ?? ProjectDirectories.defaults.includes);
|
||||
|
||||
// Should not include this if not explicitly opted-in
|
||||
if (this.#raw.layouts !== undefined) {
|
||||
this.setLayouts(this.#raw.layouts ?? ProjectDirectories.defaults.layouts);
|
||||
}
|
||||
}
|
||||
|
||||
/* Relative to project root, must exist */
|
||||
#setInputRaw(dirOrFile, inputDir = undefined) {
|
||||
// is frozen and was defined previously
|
||||
if (this.#frozen && this.#raw.input !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#raw.input = dirOrFile;
|
||||
|
||||
if (!dirOrFile) {
|
||||
// input must exist if inputDir is not set.
|
||||
return;
|
||||
}
|
||||
|
||||
// Input has to exist (assumed glob if it does not exist)
|
||||
let inputExists = fs.existsSync(dirOrFile);
|
||||
let inputExistsAndIsDirectory = inputExists && fs.statSync(dirOrFile).isDirectory();
|
||||
|
||||
if (inputExistsAndIsDirectory) {
|
||||
// is not a file or glob
|
||||
this.#dirs.input = ProjectDirectories.normalizeDirectory(dirOrFile);
|
||||
} else {
|
||||
if (inputExists) {
|
||||
this.inputFile = ProjectDirectories.normalizePath(dirOrFile);
|
||||
} else {
|
||||
if (!isGlob(dirOrFile)) {
|
||||
throw new Error(
|
||||
`The "${dirOrFile}" \`input\` parameter (directory or file path) must exist on the file system (unless detected as a glob by the \`is-glob\` package)`,
|
||||
);
|
||||
}
|
||||
|
||||
this.inputGlob = dirOrFile;
|
||||
}
|
||||
|
||||
// Explicit Eleventy option for inputDir
|
||||
if (inputDir) {
|
||||
// Changed in 3.0: must exist
|
||||
if (!fs.existsSync(inputDir)) {
|
||||
throw new Error("Directory must exist (via inputDir option to Eleventy constructor).");
|
||||
}
|
||||
|
||||
this.#dirs.input = ProjectDirectories.normalizeDirectory(inputDir);
|
||||
} else {
|
||||
// the input directory is implied to be the parent directory of the
|
||||
// file, unless inputDir is explicitly specified (via Eleventy constructor `options`)
|
||||
this.#dirs.input = ProjectDirectories.normalizeDirectory(
|
||||
TemplatePath.getDirFromFilePath(dirOrFile), // works with globs
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setInput(dirOrFile, inputDir = undefined) {
|
||||
this.#setInputRaw(dirOrFile, inputDir); // does not update
|
||||
this.updateInputDependencies();
|
||||
}
|
||||
|
||||
/* Relative to input dir */
|
||||
setIncludes(dir) {
|
||||
if (dir !== undefined) {
|
||||
// falsy or an empty string is valid (falls back to input dir)
|
||||
this.#raw.includes = dir;
|
||||
this.#dirs.includes = ProjectDirectories.normalizeDirectory(
|
||||
TemplatePath.join(this.input, dir || ""),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Relative to input dir */
|
||||
/* Optional */
|
||||
setLayouts(dir) {
|
||||
if (dir !== undefined) {
|
||||
// falsy or an empty string is valid (falls back to input dir)
|
||||
this.#raw.layouts = dir;
|
||||
this.#dirs.layouts = ProjectDirectories.normalizeDirectory(
|
||||
TemplatePath.join(this.input, dir || ""),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Relative to input dir */
|
||||
setData(dir) {
|
||||
if (dir !== undefined) {
|
||||
// falsy or an empty string is valid (falls back to input dir)
|
||||
// TODO must exist if specified
|
||||
this.#raw.data = dir;
|
||||
this.#dirs.data = ProjectDirectories.normalizeDirectory(
|
||||
TemplatePath.join(this.input, dir || ""),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Relative to project root */
|
||||
setOutput(dir) {
|
||||
// is frozen and was defined previously
|
||||
if (this.#frozen && this.#raw.output !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dir !== undefined) {
|
||||
this.#raw.output = dir;
|
||||
this.#dirs.output = ProjectDirectories.normalizeDirectory(dir || "");
|
||||
}
|
||||
}
|
||||
|
||||
get input() {
|
||||
return this.#dirs.input || ProjectDirectories.defaults.input;
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this.#dirs.data || ProjectDirectories.defaults.data;
|
||||
}
|
||||
|
||||
get includes() {
|
||||
return this.#dirs.includes || ProjectDirectories.defaults.includes;
|
||||
}
|
||||
|
||||
get layouts() {
|
||||
// explicit opt-in, no fallback.
|
||||
return this.#dirs.layouts;
|
||||
}
|
||||
|
||||
get output() {
|
||||
return this.#dirs.output || ProjectDirectories.defaults.output;
|
||||
}
|
||||
|
||||
isTemplateFile(filePath) {
|
||||
let inputPath = this.getInputPath(filePath);
|
||||
if (this.layouts && inputPath.startsWith(this.layouts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputPath.startsWith(this.includes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return inputPath.startsWith(this.input);
|
||||
}
|
||||
|
||||
// for a hypothetical template file
|
||||
getInputPath(filePathRelativeToInputDir) {
|
||||
// TODO change ~/ to project root dir
|
||||
return TemplatePath.addLeadingDotSlash(
|
||||
TemplatePath.join(this.input, TemplatePath.standardizeFilePath(filePathRelativeToInputDir)),
|
||||
);
|
||||
}
|
||||
|
||||
// Inverse of getInputPath
|
||||
// Removes input dir from path
|
||||
getInputPathRelativeToInputDirectory(filePathRelativeToInputDir) {
|
||||
let inputDir = TemplatePath.addLeadingDotSlash(TemplatePath.join(this.input));
|
||||
|
||||
// No leading dot slash
|
||||
return TemplatePath.stripLeadingSubPath(filePathRelativeToInputDir, inputDir);
|
||||
}
|
||||
|
||||
// for a hypothetical Eleventy layout file
|
||||
getLayoutPath(filePathRelativeToLayoutDir) {
|
||||
return TemplatePath.addLeadingDotSlash(
|
||||
TemplatePath.join(
|
||||
this.layouts || this.includes,
|
||||
TemplatePath.standardizeFilePath(filePathRelativeToLayoutDir),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Removes layout dir from path
|
||||
getLayoutPathRelativeToInputDirectory(filePathRelativeToLayoutDir) {
|
||||
let layoutPath = this.getLayoutPath(filePathRelativeToLayoutDir);
|
||||
let inputDir = TemplatePath.addLeadingDotSlash(TemplatePath.join(this.input));
|
||||
|
||||
// No leading dot slash
|
||||
return TemplatePath.stripLeadingSubPath(layoutPath, inputDir);
|
||||
}
|
||||
|
||||
getProjectPath(filePath) {
|
||||
return TemplatePath.addLeadingDotSlash(
|
||||
TemplatePath.join(".", TemplatePath.standardizeFilePath(filePath)),
|
||||
);
|
||||
}
|
||||
|
||||
// Access the data without being able to set the data.
|
||||
getUserspaceInstance() {
|
||||
let d = this;
|
||||
|
||||
return {
|
||||
get input() {
|
||||
return d.input;
|
||||
},
|
||||
get inputFile() {
|
||||
return d.inputFile;
|
||||
},
|
||||
get inputGlob() {
|
||||
return d.inputGlob;
|
||||
},
|
||||
get data() {
|
||||
return d.data;
|
||||
},
|
||||
get includes() {
|
||||
return d.includes;
|
||||
},
|
||||
get layouts() {
|
||||
return d.layouts;
|
||||
},
|
||||
get output() {
|
||||
return d.output;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
toString() {
|
||||
return {
|
||||
input: this.input,
|
||||
inputFile: this.inputFile,
|
||||
inputGlob: this.inputGlob,
|
||||
data: this.data,
|
||||
includes: this.includes,
|
||||
layouts: this.layouts,
|
||||
output: this.output,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default ProjectDirectories;
|
||||
134
node_modules/@11ty/eleventy/src/Util/ProjectTemplateFormats.js
generated
vendored
Normal file
134
node_modules/@11ty/eleventy/src/Util/ProjectTemplateFormats.js
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
import debugUtil from "debug";
|
||||
const debug = debugUtil("Eleventy:Util:ProjectTemplateFormats");
|
||||
|
||||
class ProjectTemplateFormats {
|
||||
#useAll = {};
|
||||
#raw = {};
|
||||
|
||||
#values = {}; // Set objects
|
||||
|
||||
static union(...sets) {
|
||||
let s = new Set();
|
||||
|
||||
for (let set of sets) {
|
||||
if (!set || typeof set[Symbol.iterator] !== "function") {
|
||||
continue;
|
||||
}
|
||||
for (let v of set) {
|
||||
s.add(v);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
#normalize(formats) {
|
||||
if (Array.isArray(formats)) {
|
||||
formats = "" + formats.join(",");
|
||||
}
|
||||
|
||||
if (typeof formats !== "string") {
|
||||
throw new Error(
|
||||
`Invalid formats (expect String, Array) passed to ProjectTemplateFormats->normalize: ${formats}`,
|
||||
);
|
||||
}
|
||||
|
||||
let final = new Set();
|
||||
for (let format of formats.split(",")) {
|
||||
format = format.trim();
|
||||
if (format && format !== "*") {
|
||||
final.add(format);
|
||||
}
|
||||
}
|
||||
|
||||
return final;
|
||||
}
|
||||
|
||||
isWildcard() {
|
||||
return this.#useAll.cli || this.#useAll.config || false;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
#isUseAll(rawFormats) {
|
||||
if (rawFormats === "") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof rawFormats === "string") {
|
||||
rawFormats = rawFormats.split(",");
|
||||
}
|
||||
|
||||
if (Array.isArray(rawFormats)) {
|
||||
return rawFormats.find((entry) => entry === "*") !== undefined;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3.x Breaking: "" now means no formats. In 2.x and prior it meant "*"
|
||||
setViaCommandLine(formats) {
|
||||
if (formats === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#useAll.cli = this.#isUseAll(formats);
|
||||
this.#raw.cli = formats;
|
||||
this.#values.cli = this.#normalize(formats);
|
||||
}
|
||||
|
||||
// 3.x Breaking: "" now means no formats—in 2.x and prior it meant "*"
|
||||
// 3.x Adds support for comma separated string—in 2.x this required an Array
|
||||
setViaConfig(formats) {
|
||||
if (formats === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// "*" is supported
|
||||
this.#useAll.config = this.#isUseAll(formats);
|
||||
this.#raw.config = formats;
|
||||
this.#values.config = this.#normalize(formats);
|
||||
}
|
||||
|
||||
addViaConfig(formats) {
|
||||
if (!formats) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#isUseAll(formats)) {
|
||||
throw new Error(
|
||||
`\`addTemplateFormats("*")\` is not supported for project template syntaxes.`,
|
||||
);
|
||||
}
|
||||
|
||||
// "*" not supported here
|
||||
this.#raw.configAdd = formats;
|
||||
this.#values.configAdd = this.#normalize(formats);
|
||||
}
|
||||
|
||||
getAllTemplateFormats() {
|
||||
return Array.from(ProjectTemplateFormats.union(this.#values.config, this.#values.configAdd));
|
||||
}
|
||||
|
||||
getTemplateFormats() {
|
||||
if (this.#useAll.cli) {
|
||||
let v = this.getAllTemplateFormats();
|
||||
debug("Using CLI --formats='*': %o", v);
|
||||
return v;
|
||||
}
|
||||
|
||||
if (this.#raw.cli !== undefined) {
|
||||
let v = Array.from(this.#values.cli);
|
||||
debug("Using CLI --formats: %o", v);
|
||||
return v;
|
||||
}
|
||||
|
||||
let v = this.getAllTemplateFormats();
|
||||
debug(
|
||||
"Using configuration `templateFormats`, `setTemplateFormats()`, `addTemplateFormats()`: %o",
|
||||
v,
|
||||
);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
export default ProjectTemplateFormats;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user