Compare commits
No commits in common. "main" and "4f63b4f9a02caabf1a6f5b6dd138011690bd21b1" have entirely different histories.
main
...
4f63b4f9a0
|
@ -1,7 +0,0 @@
|
|||
# Old Dev Website
|
||||
My old dev and contact website written in HTML, CSS, and some JavaScript.
|
||||
|
||||
Link to it is https://lunaisa.dev/
|
||||
|
||||
Requires eleventy static site generator.
|
||||
|
|
@ -0,0 +1 @@
|
|||
../acorn/bin/acorn
|
|
@ -0,0 +1 @@
|
|||
../browser-sync/dist/bin.js
|
|
@ -0,0 +1 @@
|
|||
../js-beautify/js/bin/css-beautify.js
|
|
@ -0,0 +1 @@
|
|||
../dev-ip/lib/dev-ip.js
|
|
@ -0,0 +1 @@
|
|||
../editorconfig/bin/editorconfig
|
|
@ -0,0 +1 @@
|
|||
../@11ty/eleventy/cmd.js
|
|
@ -0,0 +1 @@
|
|||
../errno/cli.js
|
|
@ -0,0 +1 @@
|
|||
../esprima/bin/esparse.js
|
|
@ -0,0 +1 @@
|
|||
../esprima/bin/esvalidate.js
|
|
@ -0,0 +1 @@
|
|||
../handlebars/bin/handlebars
|
|
@ -0,0 +1 @@
|
|||
../js-beautify/js/bin/html-beautify.js
|
|
@ -0,0 +1 @@
|
|||
../js-beautify/js/bin/js-beautify.js
|
|
@ -0,0 +1 @@
|
|||
../js-yaml/bin/js-yaml.js
|
|
@ -0,0 +1 @@
|
|||
../localtunnel/bin/lt.js
|
|
@ -0,0 +1 @@
|
|||
../markdown-it/bin/markdown-it.js
|
|
@ -0,0 +1 @@
|
|||
../mime/cli.js
|
|
@ -0,0 +1 @@
|
|||
../mkdirp/bin/cmd.js
|
|
@ -0,0 +1 @@
|
|||
../mustache/bin/mustache
|
|
@ -0,0 +1 @@
|
|||
../nopt/bin/nopt.js
|
|
@ -0,0 +1 @@
|
|||
../nunjucks/bin/precompile
|
|
@ -0,0 +1 @@
|
|||
../@babel/parser/bin/babel-parser.js
|
|
@ -0,0 +1 @@
|
|||
../pretty-ms/cli.js
|
|
@ -0,0 +1 @@
|
|||
../rimraf/bin.js
|
|
@ -0,0 +1 @@
|
|||
../semver/bin/semver.js
|
|
@ -0,0 +1 @@
|
|||
../stream-throttle/bin/throttleproxy.js
|
|
@ -0,0 +1 @@
|
|||
../uglify-js/bin/uglifyjs
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Eleventy
|
||||
|
||||
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.
|
|
@ -0,0 +1,47 @@
|
|||
# dependency-tree
|
||||
|
||||
Returns an unordered array of local paths to dependencies of a node JavaScript file (everything it or any of its dependencies `require`s).
|
||||
|
||||
Reduced feature (faster) alternative to the [`dependency-tree` package](https://www.npmjs.com/package/dependency-tree) that only works with stock node JS. This is used by Eleventy to find dependencies of a JavaScript file to watch for changes to re-run Eleventy’s build.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install --save-dev @11ty/dependency-tree
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
* Ignores `node_modules`
|
||||
* Ignores Node’s built-ins (e.g. `path`)
|
||||
* Handles circular dependencies (Node does this too)
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
// my-file.js
|
||||
|
||||
// if my-local-dependency.js has dependencies, it will include those too
|
||||
const test = require("./my-local-dependency.js");
|
||||
|
||||
// ignored, is a built-in
|
||||
const path = require("path");
|
||||
```
|
||||
|
||||
```js
|
||||
const DependencyTree = require("@11ty/dependency-tree");
|
||||
|
||||
DependencyTree("./my-file.js");
|
||||
// returns ["./my-local-dependency.js"]
|
||||
```
|
||||
|
||||
### `allowNotFound`
|
||||
|
||||
```js
|
||||
const DependencyTree = require("@11ty/dependency-tree");
|
||||
|
||||
DependencyTree("./this-does-not-exist.js"); // throws an error
|
||||
|
||||
DependencyTree("./this-does-not-exist.js", { allowNotFound: true });
|
||||
// returns []
|
||||
```
|
|
@ -0,0 +1,76 @@
|
|||
const path = require("path");
|
||||
|
||||
function getAbsolutePath(filename) {
|
||||
let normalizedFilename = path.normalize(filename); // removes dot slash
|
||||
let hasDotSlash = filename.startsWith("./");
|
||||
return hasDotSlash ? path.join(path.resolve("."), normalizedFilename) : normalizedFilename;
|
||||
}
|
||||
|
||||
function getRelativePath(filename) {
|
||||
let normalizedFilename = path.normalize(filename); // removes dot slash
|
||||
let workingDirectory = path.resolve(".");
|
||||
let result = "./" + (normalizedFilename.startsWith(workingDirectory) ? normalizedFilename.substr(workingDirectory.length + 1) : normalizedFilename);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* unordered */
|
||||
function getDependenciesFor(filename, avoidCircular, allowNotFound = false) {
|
||||
let absoluteFilename = getAbsolutePath(filename)
|
||||
|
||||
try {
|
||||
require(absoluteFilename);
|
||||
} catch(e) {
|
||||
if(e.code === "MODULE_NOT_FOUND" && allowNotFound) {
|
||||
// do nothing
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let mod;
|
||||
for(let entry in require.cache) {
|
||||
if(entry === absoluteFilename) {
|
||||
mod = require.cache[entry];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let dependencies = new Set();
|
||||
|
||||
if(!mod) {
|
||||
if(!allowNotFound) {
|
||||
throw new Error(`Could not find ${filename} in @11ty/dependency-tree`);
|
||||
}
|
||||
} else {
|
||||
let relativeFilename = getRelativePath(mod.filename);
|
||||
if(!avoidCircular) {
|
||||
avoidCircular = {};
|
||||
} else {
|
||||
dependencies.add(relativeFilename);
|
||||
}
|
||||
|
||||
avoidCircular[relativeFilename] = true;
|
||||
|
||||
if(mod.children) {
|
||||
for(let child of mod.children) {
|
||||
let relativeChildFilename = getRelativePath(child.filename);
|
||||
if(relativeChildFilename.indexOf("node_modules") === -1 && // filter out node_modules
|
||||
!dependencies.has(relativeChildFilename) && // avoid infinite looping with circular deps
|
||||
!avoidCircular[relativeChildFilename] ) {
|
||||
for(let dependency of getDependenciesFor(relativeChildFilename, avoidCircular, allowNotFound)) {
|
||||
dependencies.add(dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
function getCleanDependencyListFor(filename, options = {}) {
|
||||
return Array.from( getDependenciesFor(filename, null, options.allowNotFound) );
|
||||
}
|
||||
|
||||
module.exports = getCleanDependencyListFor;
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"_from": "@11ty/dependency-tree@^1.0.0",
|
||||
"_id": "@11ty/dependency-tree@1.0.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-2FWYlkphQ/83MG7b9qqBJfJJ0K9zupNz/6n4EdDuNLw6hQHGp4Sp4UMDRyBvA/xCTYDBaPSuSjHuu45tSujegg==",
|
||||
"_location": "/@11ty/dependency-tree",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "@11ty/dependency-tree@^1.0.0",
|
||||
"name": "@11ty/dependency-tree",
|
||||
"escapedName": "@11ty%2fdependency-tree",
|
||||
"scope": "@11ty",
|
||||
"rawSpec": "^1.0.0",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^1.0.0"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/@11ty/eleventy"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-1.0.0.tgz",
|
||||
"_shasum": "b1fa53da49aafe0ab3fe38bc6b6058b704aa59a1",
|
||||
"_spec": "@11ty/dependency-tree@^1.0.0",
|
||||
"_where": "/var/www/html/node_modules/@11ty/eleventy",
|
||||
"author": {
|
||||
"name": "Zach Leatherman",
|
||||
"email": "zach@zachleat.com",
|
||||
"url": "https://zachleat.com/"
|
||||
},
|
||||
"ava": {
|
||||
"files": [
|
||||
"./test/*.js"
|
||||
],
|
||||
"sources": [
|
||||
"./main.js",
|
||||
"./test/stubs/**"
|
||||
]
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"deprecated": false,
|
||||
"description": "Finds all JavaScript require dependencies from a filename.",
|
||||
"devDependencies": {
|
||||
"ava": "^2.4.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "main.js",
|
||||
"name": "@11ty/dependency-tree",
|
||||
"scripts": {
|
||||
"test": "npx ava"
|
||||
},
|
||||
"version": "1.0.0"
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import test from "ava";
|
||||
import DependencyTree from "../main.js";
|
||||
|
||||
test("Nonexistent", t => {
|
||||
t.throws(() => {
|
||||
DependencyTree("./test/stubs/thisdoesnotexist.js");
|
||||
});
|
||||
});
|
||||
|
||||
test("Allow not Found", t => {
|
||||
DependencyTree("./test/stubs/thisdoesnotexist.js", { allowNotFound: true });
|
||||
t.true(true);
|
||||
});
|
||||
|
||||
test("Not require()’d before calling", t => {
|
||||
DependencyTree("./test/stubs/parent/parent.js");
|
||||
t.true(true);
|
||||
});
|
||||
|
||||
test("simple.js", t => {
|
||||
t.deepEqual(DependencyTree("./test/stubs/simple.js"), ["./test/stubs/simple2.js"]);
|
||||
});
|
||||
|
||||
test("parent.js", t => {
|
||||
t.deepEqual(DependencyTree("./test/stubs/parent/parent.js").sort(), [
|
||||
"./test/stubs/parent/child1.js",
|
||||
"./test/stubs/parent/child2.js",
|
||||
"./test/stubs/parent/grandchild.js",
|
||||
"./test/stubs/parent/greatgrandchild.js"
|
||||
]);
|
||||
|
||||
t.deepEqual(DependencyTree("./test/stubs/parent/child1.js").sort(), [
|
||||
"./test/stubs/parent/grandchild.js",
|
||||
"./test/stubs/parent/greatgrandchild.js"
|
||||
]);
|
||||
|
||||
t.deepEqual(DependencyTree("./test/stubs/parent/child2.js").sort(), [
|
||||
"./test/stubs/parent/grandchild.js",
|
||||
"./test/stubs/parent/greatgrandchild.js"
|
||||
]);
|
||||
|
||||
t.deepEqual(DependencyTree("./test/stubs/parent/grandchild.js"), [
|
||||
"./test/stubs/parent/greatgrandchild.js"
|
||||
]);
|
||||
|
||||
t.deepEqual(DependencyTree("./test/stubs/parent/greatgrandchild.js"), []);
|
||||
});
|
||||
|
||||
test("circular", t => {
|
||||
t.deepEqual(DependencyTree("./test/stubs/circular/circle-a.js").sort(), [
|
||||
"./test/stubs/circular/circle-b.js",
|
||||
"./test/stubs/circular/circle-c.js"]);
|
||||
});
|
||||
|
||||
test("another circular", t => {
|
||||
t.deepEqual(DependencyTree("./test/stubs/circular2/circle-a.js").sort(), [
|
||||
"./test/stubs/circular2/circle-b.js",
|
||||
"./test/stubs/circular2/circle-c.js"
|
||||
]);
|
||||
});
|
||||
|
||||
test("dot dot (dependency is up a directory)", t => {
|
||||
t.deepEqual(DependencyTree("./test/stubs/dotdot/dotdot.js").sort(), [
|
||||
"./test/stubs/simple2.js"
|
||||
]);
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
const circleB = require("./circle-b");
|
|
@ -0,0 +1 @@
|
|||
const circleC = require("./circle-c");
|
|
@ -0,0 +1 @@
|
|||
const circleA = require("./circle-a");
|
1
node_modules/@11ty/dependency-tree/test/stubs/circular2/circle-a.js
generated
vendored
Normal file
1
node_modules/@11ty/dependency-tree/test/stubs/circular2/circle-a.js
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
const circleB = require("./circle-b");
|
1
node_modules/@11ty/dependency-tree/test/stubs/circular2/circle-b.js
generated
vendored
Normal file
1
node_modules/@11ty/dependency-tree/test/stubs/circular2/circle-b.js
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
const circleC = require("./circle-c");
|
1
node_modules/@11ty/dependency-tree/test/stubs/circular2/circle-c.js
generated
vendored
Normal file
1
node_modules/@11ty/dependency-tree/test/stubs/circular2/circle-c.js
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
const circleA = require("./circle-b");
|
|
@ -0,0 +1 @@
|
|||
const test = require("../simple2")
|
|
@ -0,0 +1,4 @@
|
|||
const test2 = require("./grandchild");
|
||||
const lodash = require("lodash");
|
||||
|
||||
module.exports = {};
|
|
@ -0,0 +1,3 @@
|
|||
const test = require("./grandchild");
|
||||
|
||||
module.exports = {};
|
|
@ -0,0 +1,2 @@
|
|||
const test = require("./greatgrandchild.js");
|
||||
module.exports = {};
|
0
node_modules/@11ty/dependency-tree/test/stubs/parent/greatgrandchild.js
generated
vendored
Normal file
0
node_modules/@11ty/dependency-tree/test/stubs/parent/greatgrandchild.js
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
const path = require("path");
|
||||
const test = require("./child1");
|
||||
const test2 = require("./child2");
|
||||
|
||||
module.exports = {};
|
|
@ -0,0 +1 @@
|
|||
const test = require("./simple2");
|
|
@ -0,0 +1,46 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## 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, 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/
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 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.
|
|
@ -0,0 +1,48 @@
|
|||
<p align="center"><img src="https://www.11ty.io/img/logo-github.png" 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, Liquid, Nunjucks, Handlebars, Mustache, EJS, Haml, Pug, and JavaScript Template Literals.
|
||||
|
||||
## ➡ [Documentation](https://www.11ty.io/docs/)
|
||||
|
||||
- Please star [this repo on GitHub](https://github.com/11ty/eleventy/)!
|
||||
- Follow us on Twitter [@eleven_ty](https://twitter.com/eleven_ty)
|
||||
- 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)
|
||||
- [11ty/eleventy on Travis CI](https://travis-ci.org/11ty/eleventy)
|
||||
|
||||
[![npm Version](https://img.shields.io/npm/v/@11ty/eleventy.svg?style=for-the-badge)](https://www.npmjs.com/package/@11ty/eleventy) [![GitHub issues](https://img.shields.io/github/issues/11ty/eleventy.svg?style=for-the-badge)](https://github.com/11ty/eleventy/issues) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=for-the-badge)](https://github.com/prettier/prettier) [![npm Downloads](https://img.shields.io/npm/dt/@11ty/eleventy.svg?style=for-the-badge)](https://www.npmjs.com/package/@11ty/eleventy)
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install @11ty/eleventy --save-dev
|
||||
```
|
||||
|
||||
Read our [Getting Started guide](https://www.11ty.io/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.
|
||||
- [Code Coverage Statistics](https://github.com/11ty/eleventy/blob/master/docs/coverage.md)
|
||||
- [Benchmark for Performance Regressions](https://github.com/11ty/eleventy-benchmark)
|
||||
|
||||
## Major Roadmapped Features
|
||||
|
||||
- [Top Feature Requests](https://github.com/11ty/eleventy/issues?q=label%3Aneeds-votes+sort%3Areactions-%2B1-desc) (Add your own votes using the 👍 reaction)
|
||||
- [Documentation Requests](https://github.com/11ty/eleventy/issues?utf8=%E2%9C%93&q=is%3Aissue+sort%3Areactions-%2B1-desc+is%3Aclosed+label%3Adocumentation+label%3Aneeds-votes) (Add your own votes using the 👍 reaction)
|
||||
- [Top Bugs 😱](https://github.com/11ty/eleventy/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc) (Add your own votes using the 👍 reaction)
|
||||
- [Newest Bugs 🙀](https://github.com/11ty/eleventy/issues?q=is%3Aopen+is%3Aissue+label%3Abug)
|
||||
|
||||
## Plugins
|
||||
|
||||
See the [official docs on plugins](https://www.11ty.io/docs/plugins/).
|
|
@ -0,0 +1,84 @@
|
|||
#!/usr/bin/env node
|
||||
const pkg = require("./package.json");
|
||||
require("please-upgrade-node")(pkg, {
|
||||
message: function (requiredVersion) {
|
||||
return (
|
||||
"Eleventy requires Node " +
|
||||
requiredVersion +
|
||||
". You will need to upgrade Node to use Eleventy!"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
require("time-require");
|
||||
}
|
||||
|
||||
const EleventyErrorHandler = require("./src/EleventyErrorHandler");
|
||||
|
||||
try {
|
||||
const argv = require("minimist")(process.argv.slice(2), {
|
||||
boolean: ["quiet"],
|
||||
default: {
|
||||
quiet: null,
|
||||
},
|
||||
});
|
||||
const Eleventy = require("./src/Eleventy");
|
||||
const EleventyCommandCheck = require("./src/EleventyCommandCheck");
|
||||
|
||||
process.on("unhandledRejection", (error, promise) => {
|
||||
EleventyErrorHandler.error(
|
||||
error,
|
||||
`Unhandled rejection in promise (${promise})`
|
||||
);
|
||||
});
|
||||
process.on("uncaughtException", (error) => {
|
||||
EleventyErrorHandler.fatal(error, "Uncaught exception");
|
||||
});
|
||||
process.on("rejectionHandled", (promise) => {
|
||||
EleventyErrorHandler.warn(
|
||||
promise,
|
||||
"A promise rejection was handled asynchronously"
|
||||
);
|
||||
});
|
||||
|
||||
// TODO refactor to use minimist options.unknown callback?
|
||||
let cmdCheck = new EleventyCommandCheck(argv);
|
||||
cmdCheck.hasUnknownArguments();
|
||||
|
||||
let elev = new Eleventy(argv.input, argv.output);
|
||||
elev.setConfigPathOverride(argv.config);
|
||||
elev.setPathPrefix(argv.pathprefix);
|
||||
elev.setDryRun(argv.dryrun);
|
||||
elev.setIncrementalBuild(argv.incremental);
|
||||
elev.setPassthroughAll(argv.passthroughall);
|
||||
elev.setFormats(argv.formats);
|
||||
|
||||
// --quiet and --quiet=true resolves to true
|
||||
if (argv.quiet === true || argv.quiet === false) {
|
||||
elev.setIsVerbose(!argv.quiet);
|
||||
}
|
||||
|
||||
// careful, we can’t use async/await here to error properly
|
||||
// with old node versions in `please-upgrade-node` above.
|
||||
elev
|
||||
.init()
|
||||
.then(function () {
|
||||
if (argv.version) {
|
||||
console.log(elev.getVersion());
|
||||
} else if (argv.help) {
|
||||
console.log(elev.getHelp());
|
||||
} else if (argv.serve) {
|
||||
elev.watch().then(function () {
|
||||
elev.serve(argv.port);
|
||||
});
|
||||
} else if (argv.watch) {
|
||||
elev.watch();
|
||||
} else {
|
||||
elev.write();
|
||||
}
|
||||
})
|
||||
.catch(EleventyErrorHandler.fatal);
|
||||
} catch (e) {
|
||||
EleventyErrorHandler.fatal(e, "Eleventy fatal error");
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
{"total": {"lines":{"total":3472,"covered":3071,"skipped":0,"pct":88.45},"statements":{"total":3493,"covered":3091,"skipped":0,"pct":88.49},"functions":{"total":820,"covered":716,"skipped":0,"pct":87.32},"branches":{"total":1503,"covered":1215,"skipped":0,"pct":80.84}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Benchmark.js": {"lines":{"total":18,"covered":17,"skipped":0,"pct":94.44},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":18,"covered":17,"skipped":0,"pct":94.44},"branches":{"total":6,"covered":5,"skipped":0,"pct":83.33}}
|
||||
,"/Users/zachleat/Code/eleventy/src/BenchmarkGroup.js": {"lines":{"total":46,"covered":34,"skipped":0,"pct":73.91},"functions":{"total":9,"covered":6,"skipped":0,"pct":66.67},"statements":{"total":46,"covered":34,"skipped":0,"pct":73.91},"branches":{"total":21,"covered":11,"skipped":0,"pct":52.38}}
|
||||
,"/Users/zachleat/Code/eleventy/src/BenchmarkManager.js": {"lines":{"total":27,"covered":21,"skipped":0,"pct":77.78},"functions":{"total":8,"covered":6,"skipped":0,"pct":75},"statements":{"total":27,"covered":21,"skipped":0,"pct":77.78},"branches":{"total":8,"covered":6,"skipped":0,"pct":75}}
|
||||
,"/Users/zachleat/Code/eleventy/src/ComputedData.js": {"lines":{"total":46,"covered":46,"skipped":0,"pct":100},"functions":{"total":8,"covered":8,"skipped":0,"pct":100},"statements":{"total":46,"covered":46,"skipped":0,"pct":100},"branches":{"total":17,"covered":17,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/ComputedDataProxy.js": {"lines":{"total":51,"covered":50,"skipped":0,"pct":98.04},"functions":{"total":10,"covered":10,"skipped":0,"pct":100},"statements":{"total":51,"covered":50,"skipped":0,"pct":98.04},"branches":{"total":32,"covered":29,"skipped":0,"pct":90.63}}
|
||||
,"/Users/zachleat/Code/eleventy/src/ComputedDataQueue.js": {"lines":{"total":21,"covered":21,"skipped":0,"pct":100},"functions":{"total":10,"covered":10,"skipped":0,"pct":100},"statements":{"total":21,"covered":21,"skipped":0,"pct":100},"branches":{"total":11,"covered":8,"skipped":0,"pct":72.73}}
|
||||
,"/Users/zachleat/Code/eleventy/src/ComputedDataTemplateString.js": {"lines":{"total":24,"covered":24,"skipped":0,"pct":100},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":24,"covered":24,"skipped":0,"pct":100},"branches":{"total":6,"covered":5,"skipped":0,"pct":83.33}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Config.js": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Eleventy.js": {"lines":{"total":223,"covered":123,"skipped":0,"pct":55.16},"functions":{"total":40,"covered":20,"skipped":0,"pct":50},"statements":{"total":224,"covered":123,"skipped":0,"pct":54.91},"branches":{"total":70,"covered":30,"skipped":0,"pct":42.86}}
|
||||
,"/Users/zachleat/Code/eleventy/src/EleventyBaseError.js": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":6,"covered":6,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/EleventyCommandCheck.js": {"lines":{"total":28,"covered":28,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":28,"covered":28,"skipped":0,"pct":100},"branches":{"total":8,"covered":7,"skipped":0,"pct":87.5}}
|
||||
,"/Users/zachleat/Code/eleventy/src/EleventyConfig.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/EleventyErrorHandler.js": {"lines":{"total":39,"covered":35,"skipped":0,"pct":89.74},"functions":{"total":8,"covered":8,"skipped":0,"pct":100},"statements":{"total":39,"covered":35,"skipped":0,"pct":89.74},"branches":{"total":43,"covered":29,"skipped":0,"pct":67.44}}
|
||||
,"/Users/zachleat/Code/eleventy/src/EleventyErrorUtil.js": {"lines":{"total":23,"covered":23,"skipped":0,"pct":100},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":23,"covered":23,"skipped":0,"pct":100},"branches":{"total":19,"covered":19,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/EleventyExtensionMap.js": {"lines":{"total":64,"covered":64,"skipped":0,"pct":100},"functions":{"total":23,"covered":23,"skipped":0,"pct":100},"statements":{"total":64,"covered":64,"skipped":0,"pct":100},"branches":{"total":31,"covered":31,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/EleventyFiles.js": {"lines":{"total":165,"covered":155,"skipped":0,"pct":93.94},"functions":{"total":40,"covered":36,"skipped":0,"pct":90},"statements":{"total":165,"covered":155,"skipped":0,"pct":93.94},"branches":{"total":61,"covered":51,"skipped":0,"pct":83.61}}
|
||||
,"/Users/zachleat/Code/eleventy/src/EleventyServe.js": {"lines":{"total":59,"covered":22,"skipped":0,"pct":37.29},"functions":{"total":16,"covered":9,"skipped":0,"pct":56.25},"statements":{"total":59,"covered":22,"skipped":0,"pct":37.29},"branches":{"total":36,"covered":10,"skipped":0,"pct":27.78}}
|
||||
,"/Users/zachleat/Code/eleventy/src/EleventyWatch.js": {"lines":{"total":39,"covered":39,"skipped":0,"pct":100},"functions":{"total":18,"covered":18,"skipped":0,"pct":100},"statements":{"total":41,"covered":41,"skipped":0,"pct":100},"branches":{"total":21,"covered":19,"skipped":0,"pct":90.48}}
|
||||
,"/Users/zachleat/Code/eleventy/src/EleventyWatchTargets.js": {"lines":{"total":49,"covered":45,"skipped":0,"pct":91.84},"functions":{"total":21,"covered":18,"skipped":0,"pct":85.71},"statements":{"total":49,"covered":45,"skipped":0,"pct":91.84},"branches":{"total":15,"covered":14,"skipped":0,"pct":93.33}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Template.js": {"lines":{"total":309,"covered":294,"skipped":0,"pct":95.15},"functions":{"total":55,"covered":54,"skipped":0,"pct":98.18},"statements":{"total":311,"covered":296,"skipped":0,"pct":95.18},"branches":{"total":115,"covered":102,"skipped":0,"pct":88.7}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateCache.js": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateCollection.js": {"lines":{"total":31,"covered":28,"skipped":0,"pct":90.32},"functions":{"total":15,"covered":14,"skipped":0,"pct":93.33},"statements":{"total":34,"covered":31,"skipped":0,"pct":91.18},"branches":{"total":10,"covered":7,"skipped":0,"pct":70}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateConfig.js": {"lines":{"total":62,"covered":55,"skipped":0,"pct":88.71},"functions":{"total":10,"covered":6,"skipped":0,"pct":60},"statements":{"total":62,"covered":55,"skipped":0,"pct":88.71},"branches":{"total":26,"covered":24,"skipped":0,"pct":92.31}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateContent.js": {"lines":{"total":98,"covered":94,"skipped":0,"pct":95.92},"functions":{"total":18,"covered":18,"skipped":0,"pct":100},"statements":{"total":98,"covered":94,"skipped":0,"pct":95.92},"branches":{"total":36,"covered":33,"skipped":0,"pct":91.67}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateData.js": {"lines":{"total":221,"covered":212,"skipped":0,"pct":95.93},"functions":{"total":40,"covered":39,"skipped":0,"pct":97.5},"statements":{"total":224,"covered":215,"skipped":0,"pct":95.98},"branches":{"total":78,"covered":68,"skipped":0,"pct":87.18}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateEngineManager.js": {"lines":{"total":28,"covered":28,"skipped":0,"pct":100},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":28,"covered":28,"skipped":0,"pct":100},"branches":{"total":10,"covered":10,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateFileSlug.js": {"lines":{"total":22,"covered":22,"skipped":0,"pct":100},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":22,"covered":22,"skipped":0,"pct":100},"branches":{"total":8,"covered":8,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateGlob.js": {"lines":{"total":15,"covered":14,"skipped":0,"pct":93.33},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":15,"covered":14,"skipped":0,"pct":93.33},"branches":{"total":8,"covered":7,"skipped":0,"pct":87.5}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateLayout.js": {"lines":{"total":76,"covered":75,"skipped":0,"pct":98.68},"functions":{"total":10,"covered":10,"skipped":0,"pct":100},"statements":{"total":77,"covered":76,"skipped":0,"pct":98.7},"branches":{"total":18,"covered":17,"skipped":0,"pct":94.44}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateLayoutPathResolver.js": {"lines":{"total":49,"covered":46,"skipped":0,"pct":93.88},"functions":{"total":11,"covered":11,"skipped":0,"pct":100},"statements":{"total":49,"covered":46,"skipped":0,"pct":93.88},"branches":{"total":20,"covered":17,"skipped":0,"pct":85}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateMap.js": {"lines":{"total":243,"covered":238,"skipped":0,"pct":97.94},"functions":{"total":33,"covered":32,"skipped":0,"pct":96.97},"statements":{"total":243,"covered":238,"skipped":0,"pct":97.94},"branches":{"total":125,"covered":113,"skipped":0,"pct":90.4}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplatePassthrough.js": {"lines":{"total":45,"covered":43,"skipped":0,"pct":95.56},"functions":{"total":12,"covered":11,"skipped":0,"pct":91.67},"statements":{"total":45,"covered":43,"skipped":0,"pct":95.56},"branches":{"total":12,"covered":10,"skipped":0,"pct":83.33}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplatePassthroughManager.js": {"lines":{"total":82,"covered":72,"skipped":0,"pct":87.8},"functions":{"total":22,"covered":20,"skipped":0,"pct":90.91},"statements":{"total":82,"covered":72,"skipped":0,"pct":87.8},"branches":{"total":34,"covered":25,"skipped":0,"pct":73.53}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplatePath.js": {"lines":{"total":93,"covered":93,"skipped":0,"pct":100},"functions":{"total":30,"covered":29,"skipped":0,"pct":96.67},"statements":{"total":94,"covered":94,"skipped":0,"pct":100},"branches":{"total":48,"covered":48,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplatePermalink.js": {"lines":{"total":32,"covered":32,"skipped":0,"pct":100},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":32,"covered":32,"skipped":0,"pct":100},"branches":{"total":27,"covered":26,"skipped":0,"pct":96.3}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplatePermalinkNoWrite.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateRender.js": {"lines":{"total":80,"covered":79,"skipped":0,"pct":98.75},"functions":{"total":22,"covered":22,"skipped":0,"pct":100},"statements":{"total":80,"covered":79,"skipped":0,"pct":98.75},"branches":{"total":46,"covered":45,"skipped":0,"pct":97.83}}
|
||||
,"/Users/zachleat/Code/eleventy/src/TemplateWriter.js": {"lines":{"total":112,"covered":88,"skipped":0,"pct":78.57},"functions":{"total":32,"covered":23,"skipped":0,"pct":71.88},"statements":{"total":112,"covered":88,"skipped":0,"pct":78.57},"branches":{"total":28,"covered":14,"skipped":0,"pct":50}}
|
||||
,"/Users/zachleat/Code/eleventy/src/UserConfig.js": {"lines":{"total":216,"covered":129,"skipped":0,"pct":59.72},"functions":{"total":57,"covered":26,"skipped":0,"pct":45.61},"statements":{"total":216,"covered":129,"skipped":0,"pct":59.72},"branches":{"total":88,"covered":41,"skipped":0,"pct":46.59}}
|
||||
,"/Users/zachleat/Code/eleventy/src/defaultConfig.js": {"lines":{"total":14,"covered":11,"skipped":0,"pct":78.57},"functions":{"total":4,"covered":1,"skipped":0,"pct":25},"statements":{"total":14,"covered":11,"skipped":0,"pct":78.57},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/Custom.js": {"lines":{"total":34,"covered":15,"skipped":0,"pct":44.12},"functions":{"total":7,"covered":4,"skipped":0,"pct":57.14},"statements":{"total":34,"covered":15,"skipped":0,"pct":44.12},"branches":{"total":20,"covered":5,"skipped":0,"pct":25}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/Ejs.js": {"lines":{"total":19,"covered":18,"skipped":0,"pct":94.74},"functions":{"total":7,"covered":6,"skipped":0,"pct":85.71},"statements":{"total":19,"covered":18,"skipped":0,"pct":94.74},"branches":{"total":9,"covered":8,"skipped":0,"pct":88.89}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/Haml.js": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/Handlebars.js": {"lines":{"total":30,"covered":30,"skipped":0,"pct":100},"functions":{"total":9,"covered":9,"skipped":0,"pct":100},"statements":{"total":30,"covered":30,"skipped":0,"pct":100},"branches":{"total":6,"covered":5,"skipped":0,"pct":83.33}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/Html.js": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/JavaScript.js": {"lines":{"total":58,"covered":56,"skipped":0,"pct":96.55},"functions":{"total":13,"covered":13,"skipped":0,"pct":100},"statements":{"total":59,"covered":57,"skipped":0,"pct":96.61},"branches":{"total":42,"covered":35,"skipped":0,"pct":83.33}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/JavaScriptTemplateLiteral.js": {"lines":{"total":18,"covered":17,"skipped":0,"pct":94.44},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":18,"covered":17,"skipped":0,"pct":94.44},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/Liquid.js": {"lines":{"total":77,"covered":74,"skipped":0,"pct":96.1},"functions":{"total":27,"covered":26,"skipped":0,"pct":96.3},"statements":{"total":77,"covered":74,"skipped":0,"pct":96.1},"branches":{"total":20,"covered":17,"skipped":0,"pct":85}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/Markdown.js": {"lines":{"total":32,"covered":29,"skipped":0,"pct":90.63},"functions":{"total":9,"covered":8,"skipped":0,"pct":88.89},"statements":{"total":32,"covered":29,"skipped":0,"pct":90.63},"branches":{"total":16,"covered":13,"skipped":0,"pct":81.25}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/Mustache.js": {"lines":{"total":10,"covered":10,"skipped":0,"pct":100},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":10,"covered":10,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/Nunjucks.js": {"lines":{"total":87,"covered":78,"skipped":0,"pct":89.66},"functions":{"total":24,"covered":23,"skipped":0,"pct":95.83},"statements":{"total":87,"covered":78,"skipped":0,"pct":89.66},"branches":{"total":33,"covered":31,"skipped":0,"pct":93.94}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/Pug.js": {"lines":{"total":17,"covered":17,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":17,"covered":17,"skipped":0,"pct":100},"branches":{"total":9,"covered":8,"skipped":0,"pct":88.89}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Engines/TemplateEngine.js": {"lines":{"total":58,"covered":58,"skipped":0,"pct":100},"functions":{"total":21,"covered":21,"skipped":0,"pct":100},"statements":{"total":60,"covered":60,"skipped":0,"pct":100},"branches":{"total":10,"covered":10,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Errors/TemplateContentPrematureUseError.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Errors/UsingCircularTemplateContentReferenceError.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Filters/GetCollectionItem.js": {"lines":{"total":10,"covered":10,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":10,"covered":10,"skipped":0,"pct":100},"branches":{"total":14,"covered":13,"skipped":0,"pct":92.86}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Filters/Slug.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Filters/Url.js": {"lines":{"total":18,"covered":18,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":18,"covered":18,"skipped":0,"pct":100},"branches":{"total":21,"covered":21,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Plugins/Pagination.js": {"lines":{"total":115,"covered":106,"skipped":0,"pct":92.17},"functions":{"total":18,"covered":17,"skipped":0,"pct":94.44},"statements":{"total":119,"covered":110,"skipped":0,"pct":92.44},"branches":{"total":90,"covered":75,"skipped":0,"pct":83.33}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Util/Capitalize.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Util/DeleteRequireCache.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Util/GetJavaScriptData.js": {"lines":{"total":7,"covered":7,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":7,"covered":7,"skipped":0,"pct":100},"branches":{"total":9,"covered":9,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Util/Merge.js": {"lines":{"total":28,"covered":26,"skipped":0,"pct":92.86},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":28,"covered":26,"skipped":0,"pct":92.86},"branches":{"total":24,"covered":21,"skipped":0,"pct":87.5}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Util/Pluralize.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
|
||||
,"/Users/zachleat/Code/eleventy/src/Util/Sortable.js": {"lines":{"total":44,"covered":42,"skipped":0,"pct":95.45},"functions":{"total":21,"covered":19,"skipped":0,"pct":90.48},"statements":{"total":45,"covered":43,"skipped":0,"pct":95.56},"branches":{"total":18,"covered":18,"skipped":0,"pct":100}}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
{
|
||||
"name": "@11ty/eleventy",
|
||||
"version": "0.12.1",
|
||||
"description": "Transform a directory of templates into HTML.",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"main": "src/Eleventy.js",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"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",
|
||||
"pug",
|
||||
"handlebars",
|
||||
"mustache",
|
||||
"ejs",
|
||||
"haml"
|
||||
],
|
||||
"bin": {
|
||||
"eleventy": "./cmd.js"
|
||||
},
|
||||
"scripts": {
|
||||
"default": "npm run test",
|
||||
"doc": "jsdoc -c .jsdoc.conf.json -d ./api-docs -p ./package.json -R README.md -t ./node_modules/ink-docstrap/template -r src/*.js",
|
||||
"test": "npx ava --verbose",
|
||||
"lint-staged": "lint-staged",
|
||||
"coverage": "npx nyc ava && npx nyc report --reporter=json-summary && cp coverage/coverage-summary.json docs-src/_data/coverage.json && node cmd.js --config=docs-src/.eleventy.docs.js",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"author": {
|
||||
"name": "Zach Leatherman",
|
||||
"email": "zachleatherman@gmail.com",
|
||||
"url": "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": {
|
||||
"babel": true,
|
||||
"failFast": false,
|
||||
"files": [
|
||||
"./test/*.js"
|
||||
],
|
||||
"ignoredByWatcher": [
|
||||
"./test/stubs/**/*"
|
||||
]
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,css,md}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy-plugin-syntaxhighlight": "^3.1.0",
|
||||
"@ava/babel": "^1.0.1",
|
||||
"ava": "^3.15.0",
|
||||
"husky": "^5.1.3",
|
||||
"ink-docstrap": "1.3.2",
|
||||
"js-yaml": "^4.0.0",
|
||||
"jsdoc": "3.6.6",
|
||||
"lint-staged": "^10.5.4",
|
||||
"markdown-it-emoji": "^2.0.0",
|
||||
"node-sass": "^5.0.0",
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.2.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"toml": "^3.0.0",
|
||||
"viperhtml": "^2.17.1",
|
||||
"vue": "^2.6.12",
|
||||
"vue-server-renderer": "^2.6.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@11ty/dependency-tree": "^1.0.0",
|
||||
"browser-sync": "^2.26.14",
|
||||
"chalk": "^4.1.0",
|
||||
"chokidar": "^3.5.1",
|
||||
"debug": "^4.3.1",
|
||||
"dependency-graph": "^0.11.0",
|
||||
"ejs": "^2.7.4",
|
||||
"fast-glob": "^3.2.5",
|
||||
"fs-extra": "^8.1.0",
|
||||
"gray-matter": "^4.0.2",
|
||||
"hamljs": "^0.6.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"javascript-stringify": "^2.0.1",
|
||||
"liquidjs": "^6.4.3",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^1.26.0",
|
||||
"markdown-it": "^10.0.0",
|
||||
"minimist": "^1.2.5",
|
||||
"moo": "^0.5.1",
|
||||
"multimatch": "^4.0.0",
|
||||
"mustache": "^2.3.2",
|
||||
"normalize-path": "^3.0.0",
|
||||
"nunjucks": "^3.2.3",
|
||||
"parse-filepath": "^1.0.2",
|
||||
"please-upgrade-node": "^3.2.0",
|
||||
"pretty": "^2.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"recursive-copy": "^2.0.11",
|
||||
"semver": "^7.3.4",
|
||||
"slugify": "^1.4.7",
|
||||
"time-require": "^0.1.2",
|
||||
"valid-url": "^1.0.9"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
const { performance } = require("perf_hooks");
|
||||
|
||||
class Benchmark {
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
getNewTimestamp() {
|
||||
if (performance) {
|
||||
return performance.now();
|
||||
}
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.timeSpent = 0;
|
||||
this.timesCalled = 0;
|
||||
this.beforeTimers = [];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Benchmark;
|
|
@ -0,0 +1,116 @@
|
|||
const chalk = require("chalk");
|
||||
|
||||
const Benchmark = require("./Benchmark");
|
||||
const debugBenchmark = require("debug")("Eleventy:Benchmark");
|
||||
|
||||
class BenchmarkGroup {
|
||||
constructor() {
|
||||
this.benchmarks = {};
|
||||
this.isVerbose = true;
|
||||
this.minimumThresholdMs = 0;
|
||||
this.minimumThresholdPercent = 8;
|
||||
}
|
||||
|
||||
setIsVerbose(isVerbose) {
|
||||
this.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());
|
||||
|
||||
return function(...args) {
|
||||
benchmark.before();
|
||||
let ret = callback.call(this, ...args);
|
||||
benchmark.after();
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
get(type) {
|
||||
if (!this.benchmarks[type]) {
|
||||
this.benchmarks[type] = new Benchmark();
|
||||
}
|
||||
return this.benchmarks[type];
|
||||
}
|
||||
|
||||
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 = (totalForBenchmark * 100) / totalTimeSpent;
|
||||
|
||||
let extraOutput = [];
|
||||
if (!isAbsoluteMinimumComparison) {
|
||||
extraOutput.push(`${percent.toFixed(1)}%`);
|
||||
}
|
||||
let timesCalledCount = bench.getTimesCalled();
|
||||
if (timesCalledCount > 1) {
|
||||
extraOutput.push(`called ${timesCalledCount}×`);
|
||||
extraOutput.push(
|
||||
`${(totalForBenchmark / timesCalledCount).toFixed(1)}ms each`
|
||||
);
|
||||
}
|
||||
|
||||
let str = chalk.yellow(
|
||||
`Benchmark (${label}): ${type} took ${totalForBenchmark.toFixed(0)}ms ${
|
||||
extraOutput.length ? `(${extraOutput.join(", ")})` : ""
|
||||
}`
|
||||
);
|
||||
|
||||
if (
|
||||
(isAbsoluteMinimumComparison &&
|
||||
totalForBenchmark >= this.minimumThresholdMs) ||
|
||||
percent > this.minimumThresholdPercent
|
||||
) {
|
||||
if (this.isVerbose) {
|
||||
console.log(str);
|
||||
}
|
||||
}
|
||||
|
||||
if (totalForBenchmark.toFixed(0) > 0) {
|
||||
debugBenchmark(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BenchmarkGroup;
|
|
@ -0,0 +1,67 @@
|
|||
const BenchmarkGroup = require("./BenchmarkGroup");
|
||||
const { performance } = require("perf_hooks");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let manager = new BenchmarkManager();
|
||||
module.exports = manager;
|
|
@ -0,0 +1,94 @@
|
|||
const lodashGet = require("lodash/get");
|
||||
const lodashSet = require("lodash/set");
|
||||
|
||||
const ComputedDataQueue = require("./ComputedDataQueue");
|
||||
const ComputedDataTemplateString = require("./ComputedDataTemplateString");
|
||||
const ComputedDataProxy = require("./ComputedDataProxy");
|
||||
|
||||
const debug = require("debug")("Eleventy:ComputedData");
|
||||
|
||||
class ComputedData {
|
||||
constructor() {
|
||||
this.computed = {};
|
||||
this.templateStringKeyLookup = {};
|
||||
this.computedKeys = new Set();
|
||||
this.declaredDependencies = {};
|
||||
this.queue = new ComputedDataQueue();
|
||||
}
|
||||
|
||||
add(key, fn, declaredDependencies = []) {
|
||||
this.computedKeys.add(key);
|
||||
this.declaredDependencies[key] = declaredDependencies;
|
||||
|
||||
lodashSet(this.computed, key, fn);
|
||||
}
|
||||
|
||||
addTemplateString(key, fn, declaredDependencies = []) {
|
||||
this.add(key, fn, declaredDependencies);
|
||||
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.queue.addNode(key);
|
||||
} else {
|
||||
this.queue.uses(key, this.declaredDependencies[key]);
|
||||
|
||||
let isTemplateString = !!this.templateStringKeyLookup[key];
|
||||
let proxy = isTemplateString ? proxyByTemplateString : proxyByProxy;
|
||||
let 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);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ComputedData;
|
|
@ -0,0 +1,126 @@
|
|||
const lodashSet = require("lodash/set");
|
||||
const lodashGet = require("lodash/get");
|
||||
const lodashIsPlainObject = require("lodash/isPlainObject");
|
||||
|
||||
/* 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) || lodashIsPlainObject(data);
|
||||
}
|
||||
|
||||
getProxyData(data, keyRef) {
|
||||
// Set defaults for keys not already set on parent data
|
||||
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 (lodashIsPlainObject(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);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ComputedDataProxy;
|
|
@ -0,0 +1,64 @@
|
|||
const DependencyGraph = require("dependency-graph").DepGraph;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ComputedDataQueue;
|
|
@ -0,0 +1,66 @@
|
|||
const lodashSet = require("lodash/set");
|
||||
const debug = require("debug")("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.substr(0, split.indexOf(this.suffix));
|
||||
if (varName) {
|
||||
vars.add(varName);
|
||||
}
|
||||
}
|
||||
return Array.from(vars);
|
||||
}
|
||||
|
||||
async findVarsUsed(fn, data = {}) {
|
||||
let proxyData = this.getProxyData();
|
||||
let output;
|
||||
// let savedLog = console.log;
|
||||
// console.log = () => {};
|
||||
// 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);
|
||||
}
|
||||
// console.log = savedLog;
|
||||
|
||||
return this.findVarsInOutput(output);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ComputedDataTemplateString;
|
|
@ -0,0 +1,7 @@
|
|||
const TemplateConfig = require("./TemplateConfig");
|
||||
const debug = require("debug")("Eleventy:Config");
|
||||
|
||||
debug("Setting up global TemplateConfig.");
|
||||
let config = new TemplateConfig();
|
||||
|
||||
module.exports = config;
|
|
@ -0,0 +1,767 @@
|
|||
const pkg = require("../package.json");
|
||||
const TemplatePath = require("./TemplatePath");
|
||||
const TemplateData = require("./TemplateData");
|
||||
const TemplateWriter = require("./TemplateWriter");
|
||||
const EleventyExtensionMap = require("./EleventyExtensionMap");
|
||||
const EleventyErrorHandler = require("./EleventyErrorHandler");
|
||||
const EleventyServe = require("./EleventyServe");
|
||||
const EleventyWatch = require("./EleventyWatch");
|
||||
const EleventyWatchTargets = require("./EleventyWatchTargets");
|
||||
const EleventyFiles = require("./EleventyFiles");
|
||||
const { performance } = require("perf_hooks");
|
||||
|
||||
const templateCache = require("./TemplateCache");
|
||||
const simplePlural = require("./Util/Pluralize");
|
||||
const deleteRequireCache = require("./Util/DeleteRequireCache");
|
||||
const config = require("./Config");
|
||||
const bench = require("./BenchmarkManager");
|
||||
const debug = require("debug")("Eleventy");
|
||||
|
||||
/**
|
||||
* @module @11ty/eleventy/Eleventy
|
||||
*/
|
||||
|
||||
/**
|
||||
* Runtime of eleventy.
|
||||
*
|
||||
* @param {String} input - Where to read files from.
|
||||
* @param {String} output - Where to write rendered files to.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
class Eleventy {
|
||||
constructor(input, output) {
|
||||
/** @member {Object} - tbd. */
|
||||
this.config = config.getConfig();
|
||||
|
||||
/**
|
||||
* @member {String} - The path to Eleventy's config file.
|
||||
* @default null
|
||||
*/
|
||||
this.configPath = null;
|
||||
|
||||
/**
|
||||
* @member {Boolean} - Is Eleventy running in verbose mode?
|
||||
* @default true
|
||||
*/
|
||||
this.isVerbose = process.env.DEBUG ? false : !this.config.quietMode;
|
||||
|
||||
/**
|
||||
* @member {Boolean} - Was verbose mode overridden manually?
|
||||
* @default false
|
||||
*/
|
||||
this.isVerboseOverride = false;
|
||||
|
||||
/**
|
||||
* @member {Boolean} - Is Eleventy running in debug mode?
|
||||
* @default false
|
||||
*/
|
||||
this.isDebug = false;
|
||||
|
||||
/**
|
||||
* @member {Boolean} - Is Eleventy running in dry mode?
|
||||
* @default false
|
||||
*/
|
||||
this.isDryRun = false;
|
||||
|
||||
if (performance) {
|
||||
debug("Eleventy warm up time (in ms) %o", performance.now());
|
||||
}
|
||||
|
||||
/** @member {Number} - The timestamp of Eleventy start. */
|
||||
this.start = this.getNewTimestamp();
|
||||
|
||||
/**
|
||||
* @member {Array<String>} - Subset of template types.
|
||||
* @default null
|
||||
*/
|
||||
this.formatsOverride = null;
|
||||
|
||||
/** @member {Object} - tbd. */
|
||||
this.eleventyServe = new EleventyServe();
|
||||
|
||||
/** @member {String} - Holds the path to the input directory. */
|
||||
this.rawInput = input;
|
||||
|
||||
/** @member {String} - Holds the path to the output directory. */
|
||||
this.rawOutput = output;
|
||||
|
||||
/** @member {Object} - tbd. */
|
||||
this.watchManager = new EleventyWatch();
|
||||
|
||||
/** @member {Object} - tbd. */
|
||||
this.watchTargets = new EleventyWatchTargets();
|
||||
this.watchTargets.addAndMakeGlob(this.config.additionalWatchTargets);
|
||||
this.watchTargets.watchJavaScriptDependencies = this.config.watchJavaScriptDependencies;
|
||||
}
|
||||
|
||||
getNewTimestamp() {
|
||||
if (performance) {
|
||||
return performance.now();
|
||||
}
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
/** @type {String} */
|
||||
get input() {
|
||||
return this.rawInput || this.config.dir.input;
|
||||
}
|
||||
|
||||
/** @type {String} */
|
||||
get inputDir() {
|
||||
return TemplatePath.getDir(this.input);
|
||||
}
|
||||
|
||||
/** @type {String} */
|
||||
get outputDir() {
|
||||
let dir = this.rawOutput || this.config.dir.output;
|
||||
if (dir !== this._savedOutputDir) {
|
||||
this.eleventyServe.setOutputDir(dir);
|
||||
}
|
||||
this._savedOutputDir = dir;
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the dry-run mode of Eleventy.
|
||||
*
|
||||
* @method
|
||||
* @param {Boolean} isDryRun - Shall Eleventy run in dry mode?
|
||||
*/
|
||||
setDryRun(isDryRun) {
|
||||
this.isDryRun = !!isDryRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the incremental build mode.
|
||||
*
|
||||
* @method
|
||||
* @param {Boolean} isIncremental - Shall Eleventy run in incremental build mode and only write the files that trigger watch updates
|
||||
*/
|
||||
setIncrementalBuild(isIncremental) {
|
||||
this.isIncremental = !!isIncremental;
|
||||
this.watchManager.incremental = !!isIncremental;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the passthrough mode of Eleventy.
|
||||
*
|
||||
* @method
|
||||
* @param {Boolean} isPassthroughAll - Shall Eleventy passthrough everything?
|
||||
*/
|
||||
setPassthroughAll(isPassthroughAll) {
|
||||
this.isPassthroughAll = !!isPassthroughAll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the path prefix used in the config.
|
||||
*
|
||||
* @method
|
||||
* @param {String} pathPrefix - The new path prefix.
|
||||
*/
|
||||
setPathPrefix(pathPrefix) {
|
||||
if (pathPrefix || pathPrefix === "") {
|
||||
config.setPathPrefix(pathPrefix);
|
||||
this.config = config.getConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the watch targets.
|
||||
*
|
||||
* @method
|
||||
* @param {} watchTargets - The new watch targets.
|
||||
*/
|
||||
setWatchTargets(watchTargets) {
|
||||
this.watchTargets = watchTargets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the config path.
|
||||
*
|
||||
* @method
|
||||
* @param {String} configPath - The new config path.
|
||||
*/
|
||||
setConfigPathOverride(configPath) {
|
||||
if (configPath) {
|
||||
this.configPath = configPath;
|
||||
|
||||
config.setProjectConfigPath(configPath);
|
||||
this.config = config.getConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts Eleventy.
|
||||
*
|
||||
* @async
|
||||
* @method
|
||||
*/
|
||||
async restart() {
|
||||
debug("Restarting");
|
||||
this.start = this.getNewTimestamp();
|
||||
templateCache.clear();
|
||||
bench.reset();
|
||||
this.eleventyFiles.restart();
|
||||
|
||||
// reload package.json values (if applicable)
|
||||
// TODO only reset this if it changed
|
||||
deleteRequireCache(TemplatePath.absolutePath("package.json"));
|
||||
|
||||
await this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the finish of a run of Eleventy.
|
||||
*
|
||||
* @method
|
||||
*/
|
||||
finish() {
|
||||
bench.finish();
|
||||
|
||||
(this.logger || console).log(this.logFinished());
|
||||
debug("Finished writing templates.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs some statistics after a complete run of Eleventy.
|
||||
*
|
||||
* @method
|
||||
* @returns {String} ret - The log message.
|
||||
*/
|
||||
logFinished() {
|
||||
if (!this.writer) {
|
||||
throw new Error(
|
||||
"Did you call Eleventy.init to create the TemplateWriter instance? Hint: you probably didn’t."
|
||||
);
|
||||
}
|
||||
|
||||
let ret = [];
|
||||
|
||||
let writeCount = this.writer.getWriteCount();
|
||||
let skippedCount = this.writer.getSkippedCount();
|
||||
let copyCount = this.writer.getCopyCount();
|
||||
|
||||
let slashRet = [];
|
||||
|
||||
if (copyCount) {
|
||||
slashRet.push(
|
||||
`Copied ${copyCount} ${simplePlural(copyCount, "file", "files")}`
|
||||
);
|
||||
}
|
||||
|
||||
slashRet.push(
|
||||
`Wrote ${writeCount} ${simplePlural(writeCount, "file", "files")}${
|
||||
skippedCount ? ` (skipped ${skippedCount})` : ""
|
||||
}`
|
||||
);
|
||||
|
||||
if (slashRet.length) {
|
||||
ret.push(slashRet.join(" / "));
|
||||
}
|
||||
|
||||
let versionStr = `v${pkg.version}`;
|
||||
let time = ((this.getNewTimestamp() - this.start) / 1000).toFixed(2);
|
||||
ret.push(`in ${time} ${simplePlural(time, "second", "seconds")}`);
|
||||
|
||||
if (writeCount >= 10) {
|
||||
ret.push(
|
||||
`(${((time * 1000) / writeCount).toFixed(1)}ms each, ${versionStr})`
|
||||
);
|
||||
} else {
|
||||
ret.push(`(${versionStr})`);
|
||||
}
|
||||
|
||||
let pathPrefix = this.config.pathPrefix;
|
||||
if (pathPrefix && pathPrefix !== "/") {
|
||||
return `Using pathPrefix: ${pathPrefix}\n${ret.join(" ")}`;
|
||||
}
|
||||
|
||||
return ret.join(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts Eleventy.
|
||||
*
|
||||
* @async
|
||||
* @method
|
||||
* @returns {} - tbd.
|
||||
*/
|
||||
async init() {
|
||||
this.config.inputDir = this.inputDir;
|
||||
|
||||
let formats = this.formatsOverride || this.config.templateFormats;
|
||||
this.extensionMap = new EleventyExtensionMap(formats);
|
||||
|
||||
this.eleventyFiles = new EleventyFiles(
|
||||
this.input,
|
||||
this.outputDir,
|
||||
formats,
|
||||
this.isPassthroughAll
|
||||
);
|
||||
this.eleventyFiles.extensionMap = this.extensionMap;
|
||||
this.eleventyFiles.init();
|
||||
|
||||
this.templateData = new TemplateData(this.inputDir);
|
||||
this.templateData.extensionMap = this.extensionMap;
|
||||
this.eleventyFiles.setTemplateData(this.templateData);
|
||||
|
||||
this.writer = new TemplateWriter(
|
||||
this.input,
|
||||
this.outputDir,
|
||||
formats,
|
||||
this.templateData,
|
||||
this.isPassthroughAll
|
||||
);
|
||||
|
||||
this.writer.extensionMap = this.extensionMap;
|
||||
this.writer.setEleventyFiles(this.eleventyFiles);
|
||||
|
||||
debug(`Directories:
|
||||
Input: ${this.inputDir}
|
||||
Data: ${this.templateData.getDataDir()}
|
||||
Includes: ${this.eleventyFiles.getIncludesDir()}
|
||||
Layouts: ${this.eleventyFiles.getLayoutsDir()}
|
||||
Output: ${this.outputDir}
|
||||
Template Formats: ${formats.join(",")}
|
||||
Verbose Output: ${this.isVerbose}`);
|
||||
|
||||
this.writer.setVerboseOutput(this.isVerbose);
|
||||
this.writer.setDryRun(this.isDryRun);
|
||||
|
||||
return this.templateData.cacheData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the debug mode of Eleventy.
|
||||
*
|
||||
* @method
|
||||
* @param {Boolean} isDebug - Shall Eleventy run in debug mode?
|
||||
*/
|
||||
setIsDebug(isDebug) {
|
||||
this.isDebug = !!isDebug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the verbose mode of Eleventy.
|
||||
*
|
||||
* @method
|
||||
* @param {Boolean} isVerbose - Shall Eleventy run in verbose mode?
|
||||
*/
|
||||
setIsVerbose(isVerbose) {
|
||||
this.isVerbose = !!isVerbose;
|
||||
|
||||
// mark that this was changed from the default (probably from --quiet)
|
||||
// this is used when we reset the config (only applies if not overridden)
|
||||
this.isVerboseOverride = true;
|
||||
|
||||
if (this.writer) {
|
||||
this.writer.setVerboseOutput(this.isVerbose);
|
||||
}
|
||||
if (bench) {
|
||||
bench.setVerboseOutput(this.isVerbose);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the template formats of Eleventy.
|
||||
*
|
||||
* @method
|
||||
* @param {String} formats - The new template formats.
|
||||
*/
|
||||
setFormats(formats) {
|
||||
if (formats && formats !== "*") {
|
||||
this.formatsOverride = formats.split(",");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the version of Eleventy.
|
||||
*
|
||||
* @method
|
||||
* @returns {String} - The version of Eleventy.
|
||||
*/
|
||||
getVersion() {
|
||||
return require("../package.json").version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a help message including usage.
|
||||
*
|
||||
* @method
|
||||
* @returns {String} - The help mesage.
|
||||
*/
|
||||
getHelp() {
|
||||
return `usage: eleventy
|
||||
eleventy --input=. --output=./_site
|
||||
eleventy --serve
|
||||
|
||||
Arguments:
|
||||
--version
|
||||
--input=.
|
||||
Input template files (default: \`.\`)
|
||||
--output=_site
|
||||
Write HTML output to this folder (default: \`_site\`)
|
||||
--serve
|
||||
Run web server on --port (default 8080) and watch them too
|
||||
--watch
|
||||
Wait for files to change and automatically rewrite (no web server)
|
||||
--formats=liquid,md
|
||||
Whitelist only certain template types (default: \`*\`)
|
||||
--quiet
|
||||
Don’t print all written files (off by default)
|
||||
--config=filename.js
|
||||
Override the eleventy config file path (default: \`.eleventy.js\`)
|
||||
--pathprefix='/'
|
||||
Change all url template filters to use this subdirectory.
|
||||
--dryrun
|
||||
Don’t write any files. Useful with \`DEBUG=Eleventy* npx eleventy\`
|
||||
--help`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the config of Eleventy.
|
||||
*
|
||||
* @method
|
||||
*/
|
||||
resetConfig() {
|
||||
config.reset();
|
||||
|
||||
this.config = config.getConfig();
|
||||
this.eleventyServe.config = this.config;
|
||||
|
||||
if (!this.isVerboseOverride && !process.env.DEBUG) {
|
||||
this.isVerbose = !this.config.quietMode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tbd.
|
||||
*
|
||||
* @private
|
||||
* @method
|
||||
* @param {String} changedFilePath - File that triggered a re-run (added or modified)
|
||||
*/
|
||||
async _addFileToWatchQueue(changedFilePath) {
|
||||
this.watchManager.addToPendingQueue(changedFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* tbd.
|
||||
*
|
||||
* @private
|
||||
* @method
|
||||
*/
|
||||
async _watch() {
|
||||
if (this.watchManager.isBuildRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.watchManager.setBuildRunning();
|
||||
|
||||
this.config.events.emit("beforeWatch", this.watchManager.getActiveQueue());
|
||||
|
||||
// reset and reload global configuration :O
|
||||
if (this.watchManager.hasQueuedFile(config.getLocalProjectConfigFile())) {
|
||||
this.resetConfig();
|
||||
}
|
||||
|
||||
await this.restart();
|
||||
|
||||
this.watchTargets.clearDependencyRequireCache();
|
||||
|
||||
let incrementalFile = this.watchManager.getIncrementalFile();
|
||||
if (incrementalFile) {
|
||||
// TODO remove these and delegate to the template dependency graph
|
||||
let isInclude = TemplatePath.startsWithSubPath(
|
||||
incrementalFile,
|
||||
this.eleventyFiles.getIncludesDir()
|
||||
);
|
||||
let isJSDependency = this.watchTargets.isJavaScriptDependency(
|
||||
incrementalFile
|
||||
);
|
||||
if (!isInclude && !isJSDependency) {
|
||||
this.writer.setIncrementalFile(incrementalFile);
|
||||
}
|
||||
}
|
||||
|
||||
await this.write();
|
||||
|
||||
this.writer.resetIncrementalFile();
|
||||
|
||||
this.watchTargets.reset();
|
||||
|
||||
await this._initWatchDependencies();
|
||||
|
||||
// Add new deps to chokidar
|
||||
this.watcher.add(this.watchTargets.getNewTargetsSinceLastReset());
|
||||
|
||||
// Is a CSS input file and is not in the includes folder
|
||||
// TODO check output path file extension of this template (not input path)
|
||||
// TODO add additional API for this, maybe a config callback?
|
||||
let onlyCssChanges = this.watchManager.hasAllQueueFiles((path) => {
|
||||
return (
|
||||
path.endsWith(".css") &&
|
||||
// TODO how to make this work with relative includes?
|
||||
!TemplatePath.startsWithSubPath(
|
||||
path,
|
||||
this.eleventyFiles.getIncludesDir()
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
if (onlyCssChanges) {
|
||||
this.eleventyServe.reload("*.css");
|
||||
} else {
|
||||
this.eleventyServe.reload();
|
||||
}
|
||||
|
||||
this.watchManager.setBuildFinished();
|
||||
|
||||
if (this.watchManager.getPendingQueueSize() > 0) {
|
||||
console.log(
|
||||
`You saved while Eleventy was running, let’s run again. (${this.watchManager.getPendingQueueSize()} remain)`
|
||||
);
|
||||
await this._watch();
|
||||
} else {
|
||||
console.log("Watching…");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tbd.
|
||||
*
|
||||
* @returns {} - tbd.
|
||||
*/
|
||||
get watcherBench() {
|
||||
return bench.get("Watcher");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up watchers and benchmarks.
|
||||
*
|
||||
* @async
|
||||
* @method
|
||||
*/
|
||||
async initWatch() {
|
||||
this.watchManager = new EleventyWatch();
|
||||
this.watchManager.incremental = this.isIncremental;
|
||||
|
||||
this.watchTargets.add(this.eleventyFiles.getGlobWatcherFiles());
|
||||
|
||||
// Watch the local project config file
|
||||
this.watchTargets.add(config.getLocalProjectConfigFile());
|
||||
|
||||
// Template and Directory Data Files
|
||||
this.watchTargets.add(
|
||||
await this.eleventyFiles.getGlobWatcherTemplateDataFiles()
|
||||
);
|
||||
|
||||
let benchmark = this.watcherBench.get(
|
||||
"Watching JavaScript Dependencies (disable with `eleventyConfig.setWatchJavaScriptDependencies(false)`)"
|
||||
);
|
||||
benchmark.before();
|
||||
await this._initWatchDependencies();
|
||||
benchmark.after();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts watching dependencies.
|
||||
*
|
||||
* @private
|
||||
* @async
|
||||
* @method
|
||||
*/
|
||||
async _initWatchDependencies() {
|
||||
if (!this.watchTargets.watchJavaScriptDependencies) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dataDir = this.templateData.getDataDir();
|
||||
function filterOutGlobalDataFiles(path) {
|
||||
return !dataDir || path.indexOf(dataDir) === -1;
|
||||
}
|
||||
|
||||
// Template files .11ty.js
|
||||
this.watchTargets.addDependencies(this.eleventyFiles.getWatchPathCache());
|
||||
|
||||
// Config file dependencies
|
||||
this.watchTargets.addDependencies(
|
||||
config.getLocalProjectConfigFile(),
|
||||
filterOutGlobalDataFiles.bind(this)
|
||||
);
|
||||
|
||||
// Deps from Global Data (that aren’t in the global data directory, everything is watched there)
|
||||
this.watchTargets.addDependencies(
|
||||
this.templateData.getWatchPathCache(),
|
||||
filterOutGlobalDataFiles.bind(this)
|
||||
);
|
||||
|
||||
this.watchTargets.addDependencies(
|
||||
await this.eleventyFiles.getWatcherTemplateJavaScriptDataFiles()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all watched files.
|
||||
*
|
||||
* @async
|
||||
* @method
|
||||
* @returns {} targets - The watched files.
|
||||
*/
|
||||
async getWatchedFiles() {
|
||||
return this.watchTargets.getTargets();
|
||||
}
|
||||
|
||||
getChokidarConfig() {
|
||||
let ignores = this.eleventyFiles.getGlobWatcherIgnores();
|
||||
debug("Ignoring watcher changes to: %o", ignores);
|
||||
|
||||
let configOptions = this.config.chokidarConfig;
|
||||
|
||||
// can’t override these yet
|
||||
// TODO maybe if array, merge the array?
|
||||
delete configOptions.ignored;
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
ignored: ignores,
|
||||
ignoreInitial: true,
|
||||
// also interesting: awaitWriteFinish
|
||||
},
|
||||
configOptions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the watching of files.
|
||||
*
|
||||
* @async
|
||||
* @method
|
||||
*/
|
||||
async watch() {
|
||||
this.watcherBench.setMinimumThresholdMs(500);
|
||||
this.watcherBench.reset();
|
||||
|
||||
const chokidar = require("chokidar");
|
||||
|
||||
// Note that watching indirectly depends on this for fetching dependencies from JS files
|
||||
// See: TemplateWriter:pathCache and EleventyWatchTargets
|
||||
await this.write();
|
||||
|
||||
let initWatchBench = this.watcherBench.get("Start up --watch");
|
||||
initWatchBench.before();
|
||||
|
||||
await this.initWatch();
|
||||
|
||||
// TODO improve unwatching if JS dependencies are removed (or files are deleted)
|
||||
let rawFiles = await this.getWatchedFiles();
|
||||
debug("Watching for changes to: %o", rawFiles);
|
||||
|
||||
let watcher = chokidar.watch(rawFiles, this.getChokidarConfig());
|
||||
|
||||
initWatchBench.after();
|
||||
|
||||
this.watcherBench.setIsVerbose(true);
|
||||
this.watcherBench.finish("Watch");
|
||||
|
||||
console.log("Watching…");
|
||||
|
||||
this.watcher = watcher;
|
||||
|
||||
let watchDelay;
|
||||
async function watchRun(path) {
|
||||
try {
|
||||
this._addFileToWatchQueue(path);
|
||||
clearTimeout(watchDelay);
|
||||
watchDelay = setTimeout(async () => {
|
||||
await this._watch();
|
||||
}, this.config.watchThrottleWaitTime);
|
||||
} catch (e) {
|
||||
EleventyErrorHandler.fatal(e, "Eleventy fatal watch error");
|
||||
this.stopWatch();
|
||||
}
|
||||
}
|
||||
|
||||
watcher.on("change", async (path) => {
|
||||
console.log("File changed:", path);
|
||||
await watchRun.call(this, path);
|
||||
});
|
||||
|
||||
watcher.on("add", async (path) => {
|
||||
console.log("File added:", path);
|
||||
await watchRun.call(this, path);
|
||||
});
|
||||
|
||||
process.on("SIGINT", () => this.stopWatch());
|
||||
}
|
||||
|
||||
stopWatch() {
|
||||
debug("Cleaning up chokidar and browsersync (if exists) instances.");
|
||||
this.eleventyServe.close();
|
||||
this.watcher.close();
|
||||
process.exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve Eleventy on this port.
|
||||
*
|
||||
* @param {Number} port - The HTTP port to serve Eleventy from.
|
||||
*/
|
||||
serve(port) {
|
||||
this.eleventyServe.serve(port);
|
||||
}
|
||||
|
||||
/* For testing */
|
||||
/**
|
||||
* Updates the logger.
|
||||
*
|
||||
* @param {} logger - The new logger.
|
||||
*/
|
||||
setLogger(logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* tbd.
|
||||
*
|
||||
* @async
|
||||
* @method
|
||||
* @returns {Promise<{}>} ret - tbd.
|
||||
*/
|
||||
async write() {
|
||||
let ret;
|
||||
if (this.logger) {
|
||||
EleventyErrorHandler.logger = this.logger;
|
||||
}
|
||||
|
||||
this.config.events.emit("beforeBuild");
|
||||
|
||||
try {
|
||||
let promise = this.writer.write();
|
||||
|
||||
ret = await promise;
|
||||
this.config.events.emit("afterBuild");
|
||||
} catch (e) {
|
||||
EleventyErrorHandler.initialMessage(
|
||||
"Problem writing Eleventy templates",
|
||||
"error",
|
||||
"red"
|
||||
);
|
||||
EleventyErrorHandler.fatal(e);
|
||||
}
|
||||
|
||||
this.finish();
|
||||
|
||||
debug(`
|
||||
Getting frustrated? Have a suggestion/feature request/feedback?
|
||||
I want to hear it! Open an issue: https://github.com/11ty/eleventy/issues/new`);
|
||||
|
||||
// unset the logger
|
||||
EleventyErrorHandler.logger = undefined;
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Eleventy;
|
|
@ -0,0 +1,14 @@
|
|||
class EleventyBaseError extends Error {
|
||||
constructor(message, originalError) {
|
||||
super(message);
|
||||
|
||||
this.name = this.constructor.name;
|
||||
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
|
||||
if (originalError) {
|
||||
this.originalError = originalError;
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = EleventyBaseError;
|
|
@ -0,0 +1,83 @@
|
|||
const EleventyBaseError = require("./EleventyBaseError");
|
||||
const debug = require("debug")("Eleventy:CommandCheck");
|
||||
|
||||
class EleventyCommandCheckError extends EleventyBaseError {}
|
||||
|
||||
class EleventyCommandCheck {
|
||||
constructor(argv) {
|
||||
this.valueArgs = [
|
||||
"input",
|
||||
"output",
|
||||
"formats",
|
||||
"config",
|
||||
"pathprefix",
|
||||
"port"
|
||||
];
|
||||
|
||||
this.booleanArgs = [
|
||||
"quiet",
|
||||
"version",
|
||||
"watch",
|
||||
"dryrun",
|
||||
"help",
|
||||
"serve",
|
||||
"passthroughall",
|
||||
"incremental"
|
||||
];
|
||||
|
||||
this.args = argv;
|
||||
this.argsMap = this.getArgumentLookupMap();
|
||||
|
||||
debug("command: eleventy ", this.toString());
|
||||
}
|
||||
|
||||
toString() {
|
||||
let cmd = [];
|
||||
|
||||
for (let valueArgName of this.valueArgs) {
|
||||
if (this.args[valueArgName]) {
|
||||
cmd.push(`--${valueArgName}=${this.args[valueArgName]}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (let booleanArgName of this.booleanArgs) {
|
||||
if (this.args[booleanArgName]) {
|
||||
cmd.push(`--${booleanArgName}`);
|
||||
}
|
||||
}
|
||||
|
||||
return cmd.join(" ");
|
||||
}
|
||||
|
||||
getArgumentLookupMap() {
|
||||
let obj = {};
|
||||
for (let valueArgName of this.valueArgs) {
|
||||
obj[valueArgName] = true;
|
||||
}
|
||||
for (let booleanArgName of this.booleanArgs) {
|
||||
obj[booleanArgName] = true;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
isKnownArgument(name) {
|
||||
// _ is the default keyless parameter
|
||||
if (name === "_") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !!this.argsMap[name];
|
||||
}
|
||||
|
||||
hasUnknownArguments() {
|
||||
for (let argName in this.args) {
|
||||
if (!this.isKnownArgument(argName)) {
|
||||
throw new EleventyCommandCheckError(
|
||||
`We don’t know what '${argName}' is. Use --help to see the list of supported commands.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EleventyCommandCheck;
|
|
@ -0,0 +1,3 @@
|
|||
const UserConfig = require("./UserConfig");
|
||||
|
||||
module.exports = new UserConfig();
|
|
@ -0,0 +1,102 @@
|
|||
const chalk = require("chalk");
|
||||
const EleventyErrorUtil = require("./EleventyErrorUtil");
|
||||
const debug = require("debug")("Eleventy:EleventyErrorHandler");
|
||||
|
||||
class EleventyErrorHandler {
|
||||
static get isChalkEnabled() {
|
||||
if (this._isChalkEnabled !== undefined) {
|
||||
return this._isChalkEnabled;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static set isChalkEnabled(enabled) {
|
||||
this._isChalkEnabled = !!enabled;
|
||||
}
|
||||
|
||||
static warn(e, msg) {
|
||||
if (msg) {
|
||||
EleventyErrorHandler.initialMessage(msg, "warn", "yellow");
|
||||
}
|
||||
EleventyErrorHandler.log(e, "warn");
|
||||
}
|
||||
|
||||
static fatal(e, msg) {
|
||||
EleventyErrorHandler.error(e, msg);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
static error(e, msg) {
|
||||
if (msg) {
|
||||
EleventyErrorHandler.initialMessage(msg, "error", "red");
|
||||
}
|
||||
EleventyErrorHandler.log(e, "error");
|
||||
}
|
||||
|
||||
//https://nodejs.org/api/process.html
|
||||
static log(e, type = "log", prefix = ">") {
|
||||
let ref = e;
|
||||
while (ref) {
|
||||
let nextRef = ref.originalError;
|
||||
if (!nextRef && EleventyErrorUtil.hasEmbeddedError(ref.message)) {
|
||||
nextRef = EleventyErrorUtil.deconvertErrorToObject(ref);
|
||||
}
|
||||
|
||||
EleventyErrorHandler.message(
|
||||
(process.env.DEBUG ? "" : `${prefix} `) +
|
||||
`${(
|
||||
EleventyErrorUtil.cleanMessage(ref.message) ||
|
||||
"(No error message provided)"
|
||||
).trim()}
|
||||
|
||||
\`${ref.name}\` was thrown${!nextRef && ref.stack ? ":" : ""}`,
|
||||
type
|
||||
);
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
debug(`(${type} stack): ${ref.stack}`);
|
||||
} else if (!nextRef) {
|
||||
// last error in the loop
|
||||
let prefix = " ";
|
||||
|
||||
// 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…)"
|
||||
);
|
||||
}
|
||||
EleventyErrorHandler.message(
|
||||
prefix + stackStr.split("\n").join("\n" + prefix)
|
||||
);
|
||||
}
|
||||
ref = nextRef;
|
||||
}
|
||||
}
|
||||
|
||||
static initialMessage(message, type = "log", chalkColor = "blue") {
|
||||
if (message) {
|
||||
EleventyErrorHandler.message(
|
||||
message + ":" + (process.env.DEBUG ? "" : " (more in DEBUG output)"),
|
||||
type,
|
||||
chalkColor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static message(message, type = "log", chalkColor) {
|
||||
if (process.env.DEBUG) {
|
||||
debug(message);
|
||||
} else {
|
||||
let logger = EleventyErrorHandler.logger || console;
|
||||
if (chalkColor && EleventyErrorHandler.isChalkEnabled) {
|
||||
logger[type](chalk[chalkColor](message));
|
||||
} else {
|
||||
logger[type](message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EleventyErrorHandler;
|
|
@ -0,0 +1,74 @@
|
|||
const TemplateContentPrematureUseError = require("./Errors/TemplateContentPrematureUseError");
|
||||
|
||||
/* 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.indexOf(EleventyErrorUtil.prefix) > -1 &&
|
||||
msg.indexOf(EleventyErrorUtil.suffix) > -1
|
||||
);
|
||||
}
|
||||
|
||||
static cleanMessage(msg) {
|
||||
if (!msg) {
|
||||
return "";
|
||||
}
|
||||
if (!EleventyErrorUtil.hasEmbeddedError(msg)) {
|
||||
return "" + msg;
|
||||
}
|
||||
|
||||
return msg.substr(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.originalError instanceof
|
||||
TemplateContentPrematureUseError) || // Liquid
|
||||
e.message.indexOf("TemplateContentPrematureUseError") > -1
|
||||
); // Nunjucks
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EleventyErrorUtil;
|
|
@ -0,0 +1,167 @@
|
|||
const TemplateEngineManager = require("./TemplateEngineManager");
|
||||
const TemplatePath = require("./TemplatePath");
|
||||
|
||||
class EleventyExtensionMap {
|
||||
constructor(formatKeys) {
|
||||
this.formatKeys = formatKeys;
|
||||
|
||||
this.setFormats(formatKeys);
|
||||
}
|
||||
|
||||
setFormats(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)
|
||||
);
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this.configOverride || require("./Config").getConfig();
|
||||
}
|
||||
set config(cfg) {
|
||||
this.configOverride = cfg;
|
||||
}
|
||||
|
||||
get engineManager() {
|
||||
if (!this._engineManager) {
|
||||
this._engineManager = new TemplateEngineManager();
|
||||
this._engineManager.config = this.config;
|
||||
}
|
||||
|
||||
return this._engineManager;
|
||||
}
|
||||
|
||||
/* Used for layout path resolution */
|
||||
getFileList(path, dir) {
|
||||
if (!path) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let files = [];
|
||||
this.validTemplateLanguageKeys.forEach(
|
||||
function(key) {
|
||||
this.getExtensionsFromKey(key).forEach(function(extension) {
|
||||
files.push((dir ? dir + "/" : "") + path + "." + extension);
|
||||
});
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
getPassthroughCopyGlobs(inputDir) {
|
||||
return this._getGlobs(this.passthroughCopyKeys, inputDir);
|
||||
}
|
||||
|
||||
getValidGlobs(inputDir) {
|
||||
return this._getGlobs(this.validTemplateLanguageKeys, inputDir);
|
||||
}
|
||||
|
||||
getGlobs(inputDir) {
|
||||
if (this.config.passthroughFileCopy) {
|
||||
return this._getGlobs(this.unfilteredFormatKeys, inputDir);
|
||||
}
|
||||
|
||||
return this._getGlobs(this.validTemplateLanguageKeys, inputDir);
|
||||
}
|
||||
|
||||
_getGlobs(formatKeys, inputDir) {
|
||||
let dir = TemplatePath.convertToRecursiveGlobSync(inputDir);
|
||||
let globs = [];
|
||||
formatKeys.forEach(
|
||||
function(key) {
|
||||
if (this.hasExtension(key)) {
|
||||
this.getExtensionsFromKey(key).forEach(function(extension) {
|
||||
globs.push(dir + "/*." + extension);
|
||||
});
|
||||
} else {
|
||||
globs.push(dir + "/*." + key);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
return globs;
|
||||
}
|
||||
|
||||
hasExtension(key) {
|
||||
for (var extension in this.extensionToKeyMap) {
|
||||
if (this.extensionToKeyMap[extension] === key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getExtensionsFromKey(key) {
|
||||
let extensions = [];
|
||||
for (var extension in this.extensionToKeyMap) {
|
||||
if (this.extensionToKeyMap[extension] === key) {
|
||||
extensions.push(extension);
|
||||
}
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
hasEngine(pathOrKey) {
|
||||
return !!this.getKey(pathOrKey);
|
||||
}
|
||||
|
||||
getKey(pathOrKey) {
|
||||
pathOrKey = (pathOrKey || "").toLowerCase();
|
||||
|
||||
for (var extension in this.extensionToKeyMap) {
|
||||
let key = this.extensionToKeyMap[extension];
|
||||
if (pathOrKey === extension) {
|
||||
return key;
|
||||
} else if (pathOrKey.endsWith("." + extension)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeTemplateExtension(path) {
|
||||
for (var extension in this.extensionToKeyMap) {
|
||||
if (path === extension || path.endsWith("." + extension)) {
|
||||
return path.substr(0, path.length - 1 - extension.length);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// keys are file extensions
|
||||
// values are template language keys
|
||||
get extensionToKeyMap() {
|
||||
if (!this._extensionToKeyMap) {
|
||||
this._extensionToKeyMap = {
|
||||
ejs: "ejs",
|
||||
md: "md",
|
||||
jstl: "jstl",
|
||||
html: "html",
|
||||
hbs: "hbs",
|
||||
mustache: "mustache",
|
||||
haml: "haml",
|
||||
pug: "pug",
|
||||
njk: "njk",
|
||||
liquid: "liquid",
|
||||
"11ty.js": "11ty.js",
|
||||
"11ty.cjs": "11ty.js"
|
||||
};
|
||||
|
||||
if ("extensionMap" in this.config) {
|
||||
for (let entry of this.config.extensionMap) {
|
||||
this._extensionToKeyMap[entry.extension] = entry.key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._extensionToKeyMap;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EleventyExtensionMap;
|
|
@ -0,0 +1,412 @@
|
|||
const fs = require("fs-extra");
|
||||
const fastglob = require("fast-glob");
|
||||
|
||||
const EleventyExtensionMap = require("./EleventyExtensionMap");
|
||||
const TemplateData = require("./TemplateData");
|
||||
const TemplateGlob = require("./TemplateGlob");
|
||||
const TemplatePath = require("./TemplatePath");
|
||||
const TemplatePassthroughManager = require("./TemplatePassthroughManager");
|
||||
|
||||
const config = require("./Config");
|
||||
const debug = require("debug")("Eleventy:EleventyFiles");
|
||||
// const debugDev = require("debug")("Dev:Eleventy:EleventyFiles");
|
||||
const aggregateBench = require("./BenchmarkManager").get("Aggregate");
|
||||
|
||||
class EleventyFiles {
|
||||
constructor(input, outputDir, formats, passthroughAll) {
|
||||
this.config = config.getConfig();
|
||||
this.input = input;
|
||||
this.inputDir = TemplatePath.getDir(this.input);
|
||||
this.outputDir = outputDir;
|
||||
|
||||
this.initConfig();
|
||||
|
||||
this.passthroughAll = !!passthroughAll;
|
||||
|
||||
this.formats = formats;
|
||||
}
|
||||
|
||||
initConfig() {
|
||||
this.includesDir = TemplatePath.join(
|
||||
this.inputDir,
|
||||
this.config.dir.includes
|
||||
);
|
||||
|
||||
if ("layouts" in this.config.dir) {
|
||||
this.layoutsDir = TemplatePath.join(
|
||||
this.inputDir,
|
||||
this.config.dir.layouts
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
// Input was a directory
|
||||
if (this.input === this.inputDir) {
|
||||
this.templateGlobs = this.extensionMap.getGlobs(this.inputDir);
|
||||
} else {
|
||||
this.templateGlobs = TemplateGlob.map([this.input]);
|
||||
}
|
||||
|
||||
this.initPassthroughManager();
|
||||
this.setupGlobs();
|
||||
}
|
||||
|
||||
get validTemplateGlobs() {
|
||||
if (!this._validTemplateGlobs) {
|
||||
let globs;
|
||||
if (this.input === this.inputDir) {
|
||||
globs = this.extensionMap.getValidGlobs(this.inputDir);
|
||||
} else {
|
||||
globs = this.templateGlobs;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
/* For testing */
|
||||
_setConfig(config) {
|
||||
this.config = config;
|
||||
this.initConfig();
|
||||
}
|
||||
/* Set command root for local project paths */
|
||||
_setLocalPathRoot(dir) {
|
||||
this.localPathRoot = dir;
|
||||
}
|
||||
|
||||
set extensionMap(extensionMap) {
|
||||
this._extensionMap = extensionMap;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
// for tests
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.formats);
|
||||
this._extensionMap.config = this.config;
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
setPassthroughAll(passthroughAll) {
|
||||
this.passthroughAll = !!passthroughAll;
|
||||
}
|
||||
|
||||
initPassthroughManager() {
|
||||
let mgr = new TemplatePassthroughManager();
|
||||
mgr.setInputDir(this.inputDir);
|
||||
mgr.setOutputDir(this.outputDir);
|
||||
mgr.extensionMap = this.extensionMap;
|
||||
this.passthroughManager = mgr;
|
||||
}
|
||||
|
||||
getPassthroughManager() {
|
||||
return this.passthroughManager;
|
||||
}
|
||||
|
||||
setPassthroughManager(mgr) {
|
||||
mgr.extensionMap = this.extensionMap;
|
||||
this.passthroughManager = mgr;
|
||||
}
|
||||
|
||||
setTemplateData(templateData) {
|
||||
this.templateData = templateData;
|
||||
}
|
||||
|
||||
// TODO make this a getter
|
||||
getTemplateData() {
|
||||
if (!this.templateData) {
|
||||
this.templateData = new TemplateData(this.inputDir);
|
||||
}
|
||||
return this.templateData;
|
||||
}
|
||||
|
||||
getDataDir() {
|
||||
let data = this.getTemplateData();
|
||||
|
||||
return data.getDataDir();
|
||||
}
|
||||
|
||||
setupGlobs() {
|
||||
this.fileIgnores = this.getIgnores();
|
||||
|
||||
if (this.passthroughAll) {
|
||||
this.templateGlobsWithIgnoresFromFiles = TemplateGlob.map([
|
||||
TemplateGlob.normalizePath(this.input, "/**")
|
||||
]).concat(this.fileIgnores);
|
||||
} else {
|
||||
this.templateGlobsWithIgnoresFromFiles = this.templateGlobs.concat(
|
||||
this.fileIgnores
|
||||
);
|
||||
}
|
||||
|
||||
this.templateGlobsWithAllIgnores = this.templateGlobsWithIgnoresFromFiles.concat(
|
||||
this._getIncludesAndDataDirIgnores()
|
||||
);
|
||||
}
|
||||
|
||||
static getFileIgnores(ignoreFiles, defaultIfFileDoesNotExist) {
|
||||
if (!Array.isArray(ignoreFiles)) {
|
||||
ignoreFiles = [ignoreFiles];
|
||||
}
|
||||
|
||||
let ignores = [];
|
||||
let fileFound = false;
|
||||
let dirs = [];
|
||||
for (let ignorePath of ignoreFiles) {
|
||||
ignorePath = TemplatePath.normalize(ignorePath);
|
||||
|
||||
let dir = TemplatePath.getDirFromFilePath(ignorePath);
|
||||
dirs.push(dir);
|
||||
|
||||
if (fs.existsSync(ignorePath) && fs.statSync(ignorePath).size > 0) {
|
||||
fileFound = true;
|
||||
let ignoreContent = fs.readFileSync(ignorePath, "utf-8");
|
||||
|
||||
// make sure that empty .gitignore with spaces takes default ignore.
|
||||
if (ignoreContent.trim().length === 0) {
|
||||
fileFound = false;
|
||||
} else {
|
||||
ignores = ignores.concat(
|
||||
EleventyFiles.normalizeIgnoreContent(dir, ignoreContent)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileFound && defaultIfFileDoesNotExist) {
|
||||
ignores.push("!" + TemplateGlob.normalizePath(defaultIfFileDoesNotExist));
|
||||
for (let dir of dirs) {
|
||||
ignores.push(
|
||||
"!" + TemplateGlob.normalizePath(dir, defaultIfFileDoesNotExist)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ignores.forEach(function(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;
|
||||
}
|
||||
|
||||
getIgnores() {
|
||||
let files = [];
|
||||
let rootDirectory = this.localPathRoot || TemplatePath.getWorkingDir();
|
||||
let absoluteInputDir = TemplatePath.absolutePath(this.inputDir);
|
||||
if (this.config.useGitIgnore) {
|
||||
let gitIgnores = [TemplatePath.join(rootDirectory, ".gitignore")];
|
||||
if (rootDirectory !== absoluteInputDir) {
|
||||
gitIgnores.push(TemplatePath.join(this.inputDir, ".gitignore"));
|
||||
}
|
||||
|
||||
files = files.concat(
|
||||
EleventyFiles.getFileIgnores(gitIgnores, "node_modules/**")
|
||||
);
|
||||
}
|
||||
|
||||
if (this.config.eleventyignoreOverride !== false) {
|
||||
let eleventyIgnores = [
|
||||
TemplatePath.join(rootDirectory, ".eleventyignore")
|
||||
];
|
||||
if (rootDirectory !== absoluteInputDir) {
|
||||
eleventyIgnores.push(
|
||||
TemplatePath.join(this.inputDir, ".eleventyignore")
|
||||
);
|
||||
}
|
||||
|
||||
files = files.concat(
|
||||
this.config.eleventyignoreOverride ||
|
||||
EleventyFiles.getFileIgnores(eleventyIgnores)
|
||||
);
|
||||
}
|
||||
|
||||
files = files.concat(TemplateGlob.map("!" + this.outputDir + "/**"));
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
getIncludesDir() {
|
||||
return this.includesDir;
|
||||
}
|
||||
|
||||
getLayoutsDir() {
|
||||
return this.layoutsDir;
|
||||
}
|
||||
|
||||
getFileGlobs() {
|
||||
return this.templateGlobsWithAllIgnores;
|
||||
}
|
||||
|
||||
getRawFiles() {
|
||||
return this.templateGlobs;
|
||||
}
|
||||
|
||||
getWatchPathCache() {
|
||||
return this.pathCache;
|
||||
}
|
||||
|
||||
async getFiles() {
|
||||
let globs = this.getFileGlobs();
|
||||
|
||||
debug("Searching for: %o", globs);
|
||||
let bench = aggregateBench.get("Searching the file system");
|
||||
bench.before();
|
||||
let paths = TemplatePath.addLeadingDotSlashArray(
|
||||
await fastglob(globs, {
|
||||
caseSensitiveMatch: false,
|
||||
dot: true
|
||||
})
|
||||
);
|
||||
bench.after();
|
||||
|
||||
if ("extensionMap" in this.config) {
|
||||
let extensions = this.config.extensionMap;
|
||||
paths = paths.filter(function(path) {
|
||||
for (let entry of extensions) {
|
||||
// TODO `.${extension}` ?
|
||||
if (path.endsWith(entry.extension) && entry.filter) {
|
||||
return entry.filter(path);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
this.pathCache = paths;
|
||||
return paths;
|
||||
}
|
||||
|
||||
/* For `eleventy --watch` */
|
||||
getGlobWatcherFiles() {
|
||||
// TODO is it better to tie the includes and data to specific file extensions or keep the **?
|
||||
return this.validTemplateGlobs
|
||||
.concat(this.passthroughGlobs)
|
||||
.concat(this._getIncludesAndDataDirs());
|
||||
}
|
||||
|
||||
/* For `eleventy --watch` */
|
||||
async getGlobWatcherTemplateDataFiles() {
|
||||
let templateData = this.getTemplateData();
|
||||
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 = await this.getTemplateData().getTemplateJavaScriptDataFileGlob();
|
||||
let bench = aggregateBench.get("Searching the file system");
|
||||
bench.before();
|
||||
let results = TemplatePath.addLeadingDotSlashArray(
|
||||
await fastglob(globs, {
|
||||
ignore: ["**/node_modules/**"],
|
||||
caseSensitiveMatch: false,
|
||||
dot: true
|
||||
})
|
||||
);
|
||||
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
|
||||
return this.fileIgnores.map(ignore =>
|
||||
TemplatePath.stripLeadingDotSlash(ignore.substr(1))
|
||||
);
|
||||
}
|
||||
|
||||
_getIncludesAndDataDirs() {
|
||||
let files = [];
|
||||
// we want this to fail on "" because we don’t want to ignore the
|
||||
// entire input directory when using ""
|
||||
if (this.config.dir.includes) {
|
||||
files = files.concat(TemplateGlob.map(this.includesDir + "/**"));
|
||||
}
|
||||
|
||||
// we want this to fail on "" because we don’t want to ignore the
|
||||
// entire input directory when using ""
|
||||
if (this.config.dir.layouts) {
|
||||
files = files.concat(TemplateGlob.map(this.layoutsDir + "/**"));
|
||||
}
|
||||
|
||||
if (this.config.dir.data && this.config.dir.data !== ".") {
|
||||
let dataDir = this.getDataDir();
|
||||
files = files.concat(TemplateGlob.map(dataDir + "/**"));
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
_getIncludesAndDataDirIgnores() {
|
||||
return this._getIncludesAndDataDirs().map(function(dir) {
|
||||
return "!" + dir;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EleventyFiles;
|
|
@ -0,0 +1,169 @@
|
|||
const fs = require("fs-extra");
|
||||
|
||||
const TemplatePath = require("./TemplatePath");
|
||||
const config = require("./Config");
|
||||
const debug = require("debug")("EleventyServe");
|
||||
|
||||
class EleventyServe {
|
||||
constructor() {}
|
||||
|
||||
get config() {
|
||||
return this.configOverride || config.getConfig();
|
||||
}
|
||||
set config(config) {
|
||||
this.configOverride = config;
|
||||
}
|
||||
|
||||
setOutputDir(outputDir) {
|
||||
this.outputDir = outputDir;
|
||||
}
|
||||
|
||||
getPathPrefix() {
|
||||
return this.config.pathPrefix || "/";
|
||||
}
|
||||
|
||||
getRedirectDir(dirName) {
|
||||
return TemplatePath.join(this.outputDir, dirName);
|
||||
}
|
||||
getRedirectDirOverride() {
|
||||
// has a pathPrefix, add a /index.html template to redirect to /pathPrefix/
|
||||
if (this.getPathPrefix() !== "/") {
|
||||
return "_eleventy_redirect";
|
||||
}
|
||||
}
|
||||
|
||||
getRedirectFilename(dirName) {
|
||||
return TemplatePath.join(this.getRedirectDir(dirName), "index.html");
|
||||
}
|
||||
|
||||
getOptions(port) {
|
||||
let pathPrefix = this.getPathPrefix();
|
||||
|
||||
// TODO customize this in Configuration API?
|
||||
let serverConfig = {
|
||||
baseDir: this.outputDir
|
||||
};
|
||||
|
||||
let redirectDirName = this.getRedirectDirOverride();
|
||||
// has a pathPrefix, add a /index.html template to redirect to /pathPrefix/
|
||||
if (redirectDirName) {
|
||||
serverConfig.baseDir = this.getRedirectDir(redirectDirName);
|
||||
serverConfig.routes = {};
|
||||
serverConfig.routes[pathPrefix] = this.outputDir;
|
||||
|
||||
// if has a savedPathPrefix, use the /savedPathPrefix/index.html template to redirect to /pathPrefix/
|
||||
if (this.savedPathPrefix) {
|
||||
serverConfig.routes[this.savedPathPrefix] = TemplatePath.join(
|
||||
this.outputDir,
|
||||
this.savedPathPrefix
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
server: serverConfig,
|
||||
port: port || 8080,
|
||||
ignore: ["node_modules"],
|
||||
watch: false,
|
||||
open: false,
|
||||
notify: false,
|
||||
index: "index.html"
|
||||
},
|
||||
this.config.browserSyncConfig
|
||||
);
|
||||
}
|
||||
|
||||
cleanupRedirect(dirName) {
|
||||
if (dirName && dirName !== "/") {
|
||||
let savedPathFilename = this.getRedirectFilename(dirName);
|
||||
|
||||
setTimeout(function() {
|
||||
if (!fs.existsSync(savedPathFilename)) {
|
||||
debug(`Cleanup redirect: Could not find ${savedPathFilename}`);
|
||||
return;
|
||||
}
|
||||
|
||||
let savedPathContent = fs.readFileSync(savedPathFilename, "utf-8");
|
||||
if (
|
||||
savedPathContent.indexOf("Browsersync pathPrefix Redirect") === -1
|
||||
) {
|
||||
debug(
|
||||
`Cleanup redirect: Found ${savedPathFilename} but it wasn’t an eleventy redirect.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
fs.unlink(savedPathFilename, err => {
|
||||
if (!err) {
|
||||
debug(`Cleanup redirect: Deleted ${savedPathFilename}`);
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
serveRedirect(dirName) {
|
||||
fs.outputFile(
|
||||
this.getRedirectFilename(dirName),
|
||||
`<!doctype html>
|
||||
<meta http-equiv="refresh" content="0; url=${this.config.pathPrefix}">
|
||||
<title>Browsersync pathPrefix Redirect</title>
|
||||
<a href="${this.config.pathPrefix}">Go to ${this.config.pathPrefix}</a>`
|
||||
);
|
||||
}
|
||||
|
||||
serve(port) {
|
||||
// only load on serve—this is pretty expensive
|
||||
const browserSync = require("browser-sync");
|
||||
this.server = browserSync.create();
|
||||
|
||||
let pathPrefix = this.getPathPrefix();
|
||||
|
||||
if (this.savedPathPrefix && pathPrefix !== this.savedPathPrefix) {
|
||||
let redirectFilename = this.getRedirectFilename(this.savedPathPrefix);
|
||||
if (!fs.existsSync(redirectFilename)) {
|
||||
debug(
|
||||
`Redirecting BrowserSync from ${this.savedPathPrefix} to ${pathPrefix}`
|
||||
);
|
||||
this.serveRedirect(this.savedPathPrefix);
|
||||
} else {
|
||||
debug(
|
||||
`Config updated with a new pathPrefix. Tried to set up a transparent redirect but found a template already existing at ${redirectFilename}. You’ll have to navigate manually.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let redirectDirName = this.getRedirectDirOverride();
|
||||
// has a pathPrefix, add a /index.html template to redirect to /pathPrefix/
|
||||
if (redirectDirName) {
|
||||
this.serveRedirect(redirectDirName);
|
||||
}
|
||||
|
||||
this.cleanupRedirect(this.savedPathPrefix);
|
||||
this.server.init(this.getOptions(port));
|
||||
|
||||
// this needs to happen after `.getOptions`
|
||||
this.savedPathPrefix = pathPrefix;
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.server) {
|
||||
this.server.exit();
|
||||
}
|
||||
}
|
||||
|
||||
/* filesToReload is optional */
|
||||
reload(filesToReload) {
|
||||
if (this.server) {
|
||||
if (this.getPathPrefix() !== this.savedPathPrefix) {
|
||||
this.server.exit();
|
||||
this.serve();
|
||||
} else {
|
||||
this.server.reload(filesToReload);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EleventyServe;
|
|
@ -0,0 +1,116 @@
|
|||
const TemplatePath = require("./TemplatePath");
|
||||
|
||||
/* 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.isActive || !this.incremental || this.activeQueue.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.activeQueue[0];
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
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) {
|
||||
return this._queueMatches(file).length > 0;
|
||||
}
|
||||
|
||||
get pendingQueue() {
|
||||
if (!this._queue) {
|
||||
this._queue = [];
|
||||
}
|
||||
return this._queue;
|
||||
}
|
||||
|
||||
set pendingQueue(value) {
|
||||
this._queue = value;
|
||||
}
|
||||
|
||||
addToPendingQueue(path) {
|
||||
if (path) {
|
||||
path = 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.filter(() => true);
|
||||
this.pendingQueue = [];
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EleventyWatch;
|
|
@ -0,0 +1,124 @@
|
|||
const dependencyTree = require("@11ty/dependency-tree");
|
||||
const TemplatePath = require("./TemplatePath");
|
||||
const deleteRequireCache = require("./Util/DeleteRequireCache");
|
||||
|
||||
class EleventyWatchTargets {
|
||||
constructor() {
|
||||
this.targets = new Set();
|
||||
this.dependencies = new Set();
|
||||
this.newTargets = new Set();
|
||||
this._watchJavaScriptDependencies = true;
|
||||
}
|
||||
|
||||
set watchJavaScriptDependencies(watch) {
|
||||
this._watchJavaScriptDependencies = !!watch;
|
||||
}
|
||||
|
||||
get watchJavaScriptDependencies() {
|
||||
return this._watchJavaScriptDependencies;
|
||||
}
|
||||
|
||||
isJavaScriptDependency(path) {
|
||||
return this.dependencies.has(path);
|
||||
}
|
||||
|
||||
_normalizeTargets(targets) {
|
||||
if (!targets) {
|
||||
return [];
|
||||
} else if (Array.isArray(targets)) {
|
||||
return targets;
|
||||
}
|
||||
|
||||
return [targets];
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.newTargets = new Set();
|
||||
}
|
||||
|
||||
isWatched(target) {
|
||||
return this.targets.has(target);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add only a target
|
||||
add(targets) {
|
||||
targets = this._normalizeTargets(targets);
|
||||
this.addRaw(targets);
|
||||
}
|
||||
|
||||
addAndMakeGlob(targets) {
|
||||
targets = this._normalizeTargets(targets).map(entry =>
|
||||
TemplatePath.convertToRecursiveGlobSync(entry)
|
||||
);
|
||||
this.addRaw(targets);
|
||||
}
|
||||
|
||||
// add only a target’s dependencies
|
||||
addDependencies(targets, filterCallback) {
|
||||
if (!this.watchJavaScriptDependencies) {
|
||||
return;
|
||||
}
|
||||
|
||||
targets = this._normalizeTargets(targets);
|
||||
let deps = this.getJavaScriptDependenciesFromList(targets);
|
||||
if (filterCallback) {
|
||||
deps = deps.filter(filterCallback);
|
||||
}
|
||||
|
||||
this.addRaw(deps, true);
|
||||
}
|
||||
|
||||
setWriter(templateWriter) {
|
||||
this.writer = templateWriter;
|
||||
}
|
||||
|
||||
getJavaScriptDependenciesFromList(files = []) {
|
||||
let depSet = new Set();
|
||||
files
|
||||
.filter(file => file.endsWith(".js") || file.endsWith(".cjs")) // TODO does this need to work with aliasing? what other JS extensions will have deps?
|
||||
.forEach(file => {
|
||||
dependencyTree(file, { allowNotFound: true })
|
||||
.map(dependency => {
|
||||
return TemplatePath.addLeadingDotSlash(
|
||||
TemplatePath.relativePath(dependency)
|
||||
);
|
||||
})
|
||||
.forEach(dependency => {
|
||||
depSet.add(dependency);
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(depSet);
|
||||
}
|
||||
|
||||
clearDependencyRequireCache() {
|
||||
for (let path of this.dependencies) {
|
||||
deleteRequireCache(TemplatePath.absolutePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
getNewTargetsSinceLastReset() {
|
||||
return Array.from(this.newTargets);
|
||||
}
|
||||
|
||||
getTargets() {
|
||||
return Array.from(this.targets);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EleventyWatchTargets;
|
|
@ -0,0 +1,82 @@
|
|||
const TemplateEngine = require("./TemplateEngine");
|
||||
const getJavaScriptData = require("../Util/GetJavaScriptData");
|
||||
|
||||
class CustomEngine extends TemplateEngine {
|
||||
constructor(name, includesDir) {
|
||||
super(name, includesDir);
|
||||
|
||||
this.entry = this.getExtensionMapEntry();
|
||||
this.needsInit =
|
||||
"init" in this.entry && typeof this.entry.init === "function";
|
||||
this.initStarted = false;
|
||||
this.initFinished = false;
|
||||
}
|
||||
|
||||
getExtensionMapEntry() {
|
||||
if ("extensionMap" in this.config) {
|
||||
for (let entry of this.config.extensionMap) {
|
||||
if (entry.key.toLowerCase() === this.name.toLowerCase()) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw Error(
|
||||
`Could not find a custom extension for ${this.name}. Did you add it to your config file?`
|
||||
);
|
||||
}
|
||||
|
||||
needsToReadFileContents() {
|
||||
if ("read" in this.entry) {
|
||||
return this.entry.read;
|
||||
}
|
||||
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.initStarted) {
|
||||
await this.initStarted;
|
||||
} else if (!this.initFinished) {
|
||||
this.initStarted = this.entry.init.bind({ config: this.config })();
|
||||
await this.initStarted;
|
||||
this.initFinished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getExtraDataFromFile(inputPath) {
|
||||
await this._runningInit();
|
||||
|
||||
if ("getData" in this.entry) {
|
||||
if (typeof this.entry.getData === "function") {
|
||||
return await this.entry.getData(inputPath);
|
||||
} else {
|
||||
if (!("getInstanceFromInputPath" in this.entry)) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
`getInstanceFromInputPath callback missing from ${this.name} template engine plugin.`
|
||||
)
|
||||
);
|
||||
}
|
||||
let inst = await this.entry.getInstanceFromInputPath(inputPath);
|
||||
return await getJavaScriptData(inst, inputPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
await this._runningInit();
|
||||
|
||||
// TODO generalize this (look at JavaScript.js)
|
||||
return this.entry.compile.bind({ config: this.config })(str, inputPath);
|
||||
}
|
||||
|
||||
get defaultTemplateFileExtension() {
|
||||
return this.entry.outputFileExtension;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CustomEngine;
|
|
@ -0,0 +1,55 @@
|
|||
const ejsLib = require("ejs");
|
||||
const TemplateEngine = require("./TemplateEngine");
|
||||
|
||||
class Ejs extends TemplateEngine {
|
||||
constructor(name, includesDir) {
|
||||
super(name, includesDir);
|
||||
|
||||
this.ejsOptions = {};
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.ejs);
|
||||
this.setEjsOptions(this.config.ejsOptions);
|
||||
}
|
||||
|
||||
setLibrary(lib) {
|
||||
this.ejsLib = lib || ejsLib;
|
||||
this.setEngineLib(this.ejsLib);
|
||||
}
|
||||
|
||||
getEngine() {
|
||||
return this.ejsLib;
|
||||
}
|
||||
|
||||
setEjsOptions(options) {
|
||||
this.ejsOptions = options;
|
||||
}
|
||||
|
||||
getEjsOptions() {
|
||||
let includesDir = super.getIncludesDir();
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
root: "./" + includesDir,
|
||||
compileDebug: true,
|
||||
filename: "./" + includesDir
|
||||
},
|
||||
this.ejsOptions || {}
|
||||
);
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
let options = this.getEjsOptions();
|
||||
|
||||
if (inputPath && inputPath !== "ejs" && inputPath !== "md") {
|
||||
options.filename = inputPath;
|
||||
}
|
||||
|
||||
let fn = this.ejsLib.compile(str, options);
|
||||
|
||||
return function(data) {
|
||||
return fn(data);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Ejs;
|
|
@ -0,0 +1,22 @@
|
|||
const HamlLib = require("hamljs");
|
||||
const TemplateEngine = require("./TemplateEngine");
|
||||
const config = require("../Config");
|
||||
|
||||
class Haml extends TemplateEngine {
|
||||
constructor(name, includesDir) {
|
||||
super(name, includesDir);
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.haml);
|
||||
}
|
||||
|
||||
setLibrary(lib) {
|
||||
this.hamlLib = lib || HamlLib;
|
||||
this.setEngineLib(lib);
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
return this.hamlLib.compile(str);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Haml;
|
|
@ -0,0 +1,66 @@
|
|||
const HandlebarsLib = require("handlebars");
|
||||
const TemplateEngine = require("./TemplateEngine");
|
||||
const config = require("../Config");
|
||||
|
||||
class Handlebars extends TemplateEngine {
|
||||
constructor(name, includesDir) {
|
||||
super(name, includesDir);
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.hbs);
|
||||
}
|
||||
|
||||
setLibrary(lib) {
|
||||
this.handlebarsLib = lib || HandlebarsLib;
|
||||
this.setEngineLib(this.handlebarsLib);
|
||||
|
||||
let partials = super.getPartials();
|
||||
for (let name in partials) {
|
||||
this.handlebarsLib.registerPartial(name, partials[name]);
|
||||
}
|
||||
|
||||
// TODO these all go to the same place (addHelper), add warnings for overwrites
|
||||
this.addHelpers(this.config.handlebarsHelpers);
|
||||
this.addShortcodes(this.config.handlebarsShortcodes);
|
||||
this.addPairedShortcodes(this.config.handlebarsPairedShortcodes);
|
||||
}
|
||||
|
||||
addHelper(name, callback) {
|
||||
this.handlebarsLib.registerHelper(name, callback);
|
||||
}
|
||||
|
||||
addHelpers(helpers) {
|
||||
for (let name in helpers) {
|
||||
this.addHelper(name, helpers[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addShortcodes(shortcodes) {
|
||||
for (let name in shortcodes) {
|
||||
this.addHelper(name, shortcodes[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addPairedShortcodes(shortcodes) {
|
||||
for (let name in shortcodes) {
|
||||
let callback = shortcodes[name];
|
||||
this.addHelper(name, function(...args) {
|
||||
let options = args[args.length - 1];
|
||||
let content = "";
|
||||
if (options && options.fn) {
|
||||
content = options.fn(this);
|
||||
}
|
||||
|
||||
return callback.call(this, content, ...args);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
let fn = this.handlebarsLib.compile(str);
|
||||
return function(data) {
|
||||
return fn(data);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Handlebars;
|
|
@ -0,0 +1,25 @@
|
|||
const TemplateEngine = require("./TemplateEngine");
|
||||
|
||||
class Html extends TemplateEngine {
|
||||
async compile(str, inputPath, preTemplateEngine) {
|
||||
if (preTemplateEngine) {
|
||||
let engine = this.engineManager.getEngine(
|
||||
preTemplateEngine,
|
||||
super.getIncludesDir(),
|
||||
this.extensionMap
|
||||
);
|
||||
let fn = await engine.compile(str, inputPath);
|
||||
|
||||
return async function(data) {
|
||||
return fn(data);
|
||||
};
|
||||
} else {
|
||||
return function(data) {
|
||||
// do nothing with data if parseHtmlWith is falsy
|
||||
return str;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Html;
|
|
@ -0,0 +1,135 @@
|
|||
const TemplateEngine = require("./TemplateEngine");
|
||||
const TemplatePath = require("../TemplatePath");
|
||||
const EleventyBaseError = require("../EleventyBaseError");
|
||||
const deleteRequireCache = require("../Util/DeleteRequireCache");
|
||||
const getJavaScriptData = require("../Util/GetJavaScriptData");
|
||||
|
||||
class JavaScriptTemplateNotDefined extends EleventyBaseError {}
|
||||
|
||||
class JavaScript extends TemplateEngine {
|
||||
constructor(name, includesDir) {
|
||||
super(name, includesDir);
|
||||
this.instances = {};
|
||||
}
|
||||
|
||||
normalize(result) {
|
||||
if (Buffer.isBuffer(result)) {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// String, Buffer, Promise
|
||||
// Function, Class
|
||||
// Object
|
||||
_getInstance(mod) {
|
||||
let noop = function () {
|
||||
return "";
|
||||
};
|
||||
|
||||
if (typeof mod === "string" || mod instanceof Buffer || mod.then) {
|
||||
return { render: () => mod };
|
||||
} else if (typeof mod === "function") {
|
||||
if (
|
||||
mod.prototype &&
|
||||
("data" in mod.prototype || "render" in mod.prototype)
|
||||
) {
|
||||
if (!("render" in mod.prototype)) {
|
||||
mod.prototype.render = noop;
|
||||
}
|
||||
return new mod();
|
||||
} else {
|
||||
return { render: mod };
|
||||
}
|
||||
} else if ("data" in mod || "render" in mod) {
|
||||
if (!("render" in mod)) {
|
||||
mod.render = noop;
|
||||
}
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
|
||||
getInstanceFromInputPath(inputPath) {
|
||||
if (this.instances[inputPath]) {
|
||||
return this.instances[inputPath];
|
||||
}
|
||||
|
||||
const mod = this._getRequire(inputPath);
|
||||
let inst = this._getInstance(mod);
|
||||
|
||||
if (inst) {
|
||||
this.instances[inputPath] = inst;
|
||||
} else {
|
||||
throw new JavaScriptTemplateNotDefined(
|
||||
`No JavaScript template returned from ${inputPath} (did you assign to module.exports?)`
|
||||
);
|
||||
}
|
||||
return inst;
|
||||
}
|
||||
|
||||
_getRequire(inputPath) {
|
||||
let requirePath = TemplatePath.absolutePath(inputPath);
|
||||
return require(requirePath);
|
||||
}
|
||||
|
||||
needsToReadFileContents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only remove from cache once on startup (if it already exists)
|
||||
initRequireCache(inputPath) {
|
||||
let requirePath = TemplatePath.absolutePath(inputPath);
|
||||
if (requirePath) {
|
||||
deleteRequireCache(requirePath);
|
||||
}
|
||||
|
||||
if (inputPath in this.instances) {
|
||||
delete this.instances[inputPath];
|
||||
}
|
||||
}
|
||||
|
||||
async getExtraDataFromFile(inputPath) {
|
||||
let inst = this.getInstanceFromInputPath(inputPath);
|
||||
return await 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
|
||||
if (key === "page") {
|
||||
// do nothing
|
||||
} else {
|
||||
fns[key] = configFns[key].bind(inst);
|
||||
}
|
||||
}
|
||||
return fns;
|
||||
}
|
||||
|
||||
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 = this.getInstanceFromInputPath(inputPath);
|
||||
}
|
||||
if (inst && "render" in inst) {
|
||||
return function (data) {
|
||||
// only blow away existing inst.page if it has a page.url
|
||||
if (!inst.page || inst.page.url) {
|
||||
inst.page = data.page;
|
||||
}
|
||||
Object.assign(inst, this.getJavaScriptFunctions(inst));
|
||||
|
||||
return this.normalize(inst.render.call(inst, data));
|
||||
}.bind(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JavaScript;
|
63
node_modules/@11ty/eleventy/src/Engines/JavaScriptTemplateLiteral.js
generated
vendored
Normal file
63
node_modules/@11ty/eleventy/src/Engines/JavaScriptTemplateLiteral.js
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
const javascriptStringify = require("javascript-stringify");
|
||||
const TemplateEngine = require("./TemplateEngine");
|
||||
const EleventyBaseError = require("../EleventyBaseError");
|
||||
|
||||
class JavaScriptTemplateLiteralCompileError extends EleventyBaseError {}
|
||||
|
||||
class JavaScriptTemplateLiteral extends TemplateEngine {
|
||||
// add ` around template if it doesn’t exist.
|
||||
static normalizeTicks(str) {
|
||||
// TODO make this work with tagged templates html``
|
||||
let trimmedStr = str.trim();
|
||||
if (
|
||||
trimmedStr.charAt(0) === "`" &&
|
||||
trimmedStr.charAt(trimmedStr.length - 1) === "`"
|
||||
) {
|
||||
str =
|
||||
"`" +
|
||||
trimmedStr.substr(1, trimmedStr.length - 2).replace(/`/g, "\\`") +
|
||||
"`";
|
||||
} else {
|
||||
str =
|
||||
"`" +
|
||||
str.replace(/`/g, "\\`") +
|
||||
(trimmedStr.charAt(trimmedStr.length - 1) === "`" ? "\n" : "") +
|
||||
"`";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
return function(data) {
|
||||
// avoid `with`
|
||||
let dataStr = "";
|
||||
for (let j in data) {
|
||||
dataStr += `let ${j} = ${javascriptStringify.stringify(
|
||||
data[j],
|
||||
null,
|
||||
null,
|
||||
{
|
||||
references: true
|
||||
}
|
||||
)};\n`;
|
||||
}
|
||||
|
||||
let evalStr = `${dataStr}\n${JavaScriptTemplateLiteral.normalizeTicks(
|
||||
str
|
||||
)};`;
|
||||
try {
|
||||
// TODO switch to https://www.npmjs.com/package/es6-template-strings
|
||||
let val = eval(evalStr);
|
||||
return val;
|
||||
} catch (e) {
|
||||
throw new JavaScriptTemplateLiteralCompileError(
|
||||
`Broken ES6 template:\n${evalStr}`,
|
||||
e
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JavaScriptTemplateLiteral;
|
|
@ -0,0 +1,217 @@
|
|||
const moo = require("moo");
|
||||
const LiquidLib = require("liquidjs");
|
||||
const TemplateEngine = require("./TemplateEngine");
|
||||
const TemplatePath = require("../TemplatePath");
|
||||
// const debug = require("debug")("Eleventy:Liquid");
|
||||
|
||||
class Liquid extends TemplateEngine {
|
||||
constructor(name, includesDir) {
|
||||
super(name, includesDir);
|
||||
|
||||
this.liquidOptions = {};
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.liquid);
|
||||
this.setLiquidOptions(this.config.liquidOptions);
|
||||
|
||||
this.argLexer = moo.compile({
|
||||
number: /[0-9]+\.*[0-9]*/,
|
||||
doubleQuoteString: /"(?:\\["\\]|[^\n"\\])*"/,
|
||||
singleQuoteString: /'(?:\\['\\]|[^\n'\\])*'/,
|
||||
keyword: /[a-zA-Z0-9\.\-\_]+/,
|
||||
"ignore:whitespace": /[, \t]+/ // includes comma separator
|
||||
});
|
||||
}
|
||||
|
||||
setLibrary(lib) {
|
||||
this.liquidLibOverride = lib;
|
||||
|
||||
// warning, the include syntax supported here does not exactly match what Jekyll uses.
|
||||
this.liquidLib = lib || LiquidLib(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);
|
||||
}
|
||||
|
||||
setLiquidOptions(options) {
|
||||
this.liquidOptions = options;
|
||||
|
||||
this.setLibrary(this.liquidLibOverride);
|
||||
}
|
||||
|
||||
getLiquidOptions() {
|
||||
let defaults = {
|
||||
root: [super.getIncludesDir()], // overrides in compile with inputPath below
|
||||
extname: ".liquid",
|
||||
dynamicPartials: false,
|
||||
strict_filters: false
|
||||
};
|
||||
|
||||
let options = Object.assign(defaults, this.liquidOptions || {});
|
||||
// debug("Liquid constructor options: %o", options);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
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, 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, scope) {
|
||||
let argArray = [];
|
||||
|
||||
if (typeof str === "string") {
|
||||
// TODO key=value key2=value
|
||||
// TODO JSON?
|
||||
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) {
|
||||
argArray.push(LiquidLib.evalExp(arg.value, scope)); // or evalValue
|
||||
}
|
||||
arg = lexer.next();
|
||||
}
|
||||
}
|
||||
|
||||
return argArray;
|
||||
}
|
||||
|
||||
static _normalizeShortcodeScope(scope) {
|
||||
let obj = {};
|
||||
if (scope && scope.contexts && scope.contexts[0]) {
|
||||
obj.page = scope.contexts[0].page;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
addShortcode(shortcodeName, shortcodeFn) {
|
||||
let _t = this;
|
||||
this.addTag(shortcodeName, function(liquidEngine) {
|
||||
return {
|
||||
parse: function(tagToken, remainTokens) {
|
||||
this.name = tagToken.name;
|
||||
this.args = tagToken.args;
|
||||
},
|
||||
render: function(scope, hash) {
|
||||
let argArray = Liquid.parseArguments(_t.argLexer, this.args, scope);
|
||||
return Promise.resolve(
|
||||
shortcodeFn.call(
|
||||
Liquid._normalizeShortcodeScope(scope),
|
||||
...argArray
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
addPairedShortcode(shortcodeName, shortcodeFn) {
|
||||
let _t = this;
|
||||
this.addTag(shortcodeName, function(liquidEngine) {
|
||||
return {
|
||||
parse: function(tagToken, remainTokens) {
|
||||
this.name = tagToken.name;
|
||||
this.args = tagToken.args;
|
||||
this.templates = [];
|
||||
|
||||
var stream = liquidEngine.parser
|
||||
.parseStream(remainTokens)
|
||||
.on("template", tpl => this.templates.push(tpl))
|
||||
.on("tag:end" + shortcodeName, token => stream.stop())
|
||||
.on("end", x => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`);
|
||||
});
|
||||
|
||||
stream.start();
|
||||
},
|
||||
render: function(scope, hash) {
|
||||
let argArray = Liquid.parseArguments(_t.argLexer, this.args, scope);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
liquidEngine.renderer
|
||||
.renderTemplates(this.templates, scope)
|
||||
.then(function(html) {
|
||||
resolve(
|
||||
shortcodeFn.call(
|
||||
Liquid._normalizeShortcodeScope(scope),
|
||||
html,
|
||||
...argArray
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
let engine = this.liquidLib;
|
||||
let tmpl = await engine.parse(str, inputPath);
|
||||
|
||||
// Required for relative includes
|
||||
let options = {};
|
||||
if (!inputPath || inputPath === "njk" || inputPath === "md") {
|
||||
// do nothing
|
||||
} else {
|
||||
options.root = [
|
||||
super.getIncludesDir(),
|
||||
TemplatePath.getDirFromFilePath(inputPath)
|
||||
];
|
||||
}
|
||||
return async function(data) {
|
||||
return engine.render(tmpl, data, options);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Liquid;
|
|
@ -0,0 +1,90 @@
|
|||
const markdownIt = require("markdown-it");
|
||||
const TemplateEngine = require("./TemplateEngine");
|
||||
// const debug = require("debug")("Eleventy:Markdown");
|
||||
|
||||
class Markdown extends TemplateEngine {
|
||||
constructor(name, includesDir) {
|
||||
super(name, includesDir);
|
||||
|
||||
this.markdownOptions = {};
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.md);
|
||||
}
|
||||
|
||||
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) {
|
||||
this.mdLib.set({
|
||||
highlight: this.config.markdownHighlighter
|
||||
});
|
||||
}
|
||||
|
||||
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 fn;
|
||||
|
||||
let engine;
|
||||
if (typeof preTemplateEngine === "string") {
|
||||
engine = this.engineManager.getEngine(
|
||||
preTemplateEngine,
|
||||
super.getIncludesDir(),
|
||||
this.extensionMap
|
||||
);
|
||||
} else {
|
||||
engine = preTemplateEngine;
|
||||
}
|
||||
|
||||
fn = await engine.compile(str, inputPath);
|
||||
|
||||
if (bypassMarkdown) {
|
||||
return async function(data) {
|
||||
return fn(data);
|
||||
};
|
||||
} else {
|
||||
return async function(data) {
|
||||
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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Markdown;
|
|
@ -0,0 +1,25 @@
|
|||
const MustacheLib = require("mustache");
|
||||
const TemplateEngine = require("./TemplateEngine");
|
||||
|
||||
class Mustache extends TemplateEngine {
|
||||
constructor(name, includesDir) {
|
||||
super(name, includesDir);
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.mustache);
|
||||
}
|
||||
|
||||
setLibrary(lib) {
|
||||
this.mustacheLib = lib || MustacheLib;
|
||||
this.setEngineLib(this.mustacheLib);
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
let partials = super.getPartials();
|
||||
|
||||
return function(data) {
|
||||
return this.render(str, data, partials).trim();
|
||||
}.bind(this.mustacheLib);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Mustache;
|
|
@ -0,0 +1,248 @@
|
|||
const NunjucksLib = require("nunjucks");
|
||||
const TemplateEngine = require("./TemplateEngine");
|
||||
const TemplatePath = require("../TemplatePath");
|
||||
const EleventyErrorUtil = require("../EleventyErrorUtil");
|
||||
const EleventyBaseError = require("../EleventyBaseError");
|
||||
|
||||
class EleventyShortcodeError extends EleventyBaseError {}
|
||||
|
||||
class Nunjucks extends TemplateEngine {
|
||||
constructor(name, includesDir) {
|
||||
super(name, includesDir);
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.njk);
|
||||
}
|
||||
|
||||
setLibrary(env) {
|
||||
this.njkEnv =
|
||||
env ||
|
||||
new NunjucksLib.Environment(
|
||||
new NunjucksLib.FileSystemLoader([
|
||||
super.getIncludesDir(),
|
||||
TemplatePath.getWorkingDir()
|
||||
])
|
||||
);
|
||||
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
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
addFilters(helpers, isAsync) {
|
||||
for (let name in helpers) {
|
||||
this.njkEnv.addFilter(name, helpers[name], isAsync);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static _normalizeShortcodeContext(context) {
|
||||
let obj = {};
|
||||
if (context.ctx && context.ctx.page) {
|
||||
obj.page = context.ctx.page;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
addShortcode(shortcodeName, shortcodeFn, isAsync = false) {
|
||||
function ShortcodeFunction() {
|
||||
this.tags = [shortcodeName];
|
||||
|
||||
this.parse = function(parser, nodes, lexer) {
|
||||
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) {
|
||||
shortcodeFn
|
||||
.call(Nunjucks._normalizeShortcodeContext(context), ...argArray)
|
||||
.then(function(returnValue) {
|
||||
resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
|
||||
})
|
||||
.catch(function(e) {
|
||||
resolve(
|
||||
new EleventyShortcodeError(
|
||||
`Error with Nunjucks shortcode \`${shortcodeName}\`${EleventyErrorUtil.convertErrorToString(
|
||||
e
|
||||
)}`
|
||||
),
|
||||
null
|
||||
);
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
// console.log( shortcodeFn.toString() );
|
||||
return new NunjucksLib.runtime.SafeString(
|
||||
shortcodeFn.call(
|
||||
Nunjucks._normalizeShortcodeContext(context),
|
||||
...argArray
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
throw new EleventyShortcodeError(
|
||||
`Error with Nunjucks shortcode \`${shortcodeName}\`${EleventyErrorUtil.convertErrorToString(
|
||||
e
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.njkEnv.addExtension(shortcodeName, new ShortcodeFunction());
|
||||
}
|
||||
|
||||
addPairedShortcode(shortcodeName, shortcodeFn, isAsync = false) {
|
||||
function PairedShortcodeFunction() {
|
||||
this.tags = [shortcodeName];
|
||||
|
||||
this.parse = function(parser, nodes, lexer) {
|
||||
var tok = parser.nextToken();
|
||||
|
||||
var args = parser.parseSignature(true, true);
|
||||
parser.advanceAfterBlockEnd(tok.value);
|
||||
|
||||
var body = parser.parseUntilBlocks("end" + shortcodeName);
|
||||
parser.advanceAfterBlockEnd();
|
||||
|
||||
if (isAsync) {
|
||||
return new nodes.CallExtensionAsync(this, "run", args, [body]);
|
||||
}
|
||||
return new nodes.CallExtension(this, "run", args, [body]);
|
||||
};
|
||||
|
||||
this.run = function(...args) {
|
||||
let resolve;
|
||||
if (isAsync) {
|
||||
resolve = args.pop();
|
||||
}
|
||||
let body = args.pop();
|
||||
let [context, ...argArray] = args;
|
||||
|
||||
if (isAsync) {
|
||||
shortcodeFn
|
||||
.call(
|
||||
Nunjucks._normalizeShortcodeContext(context),
|
||||
body(),
|
||||
...argArray
|
||||
)
|
||||
.then(function(returnValue) {
|
||||
resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
|
||||
})
|
||||
.catch(function(e) {
|
||||
resolve(
|
||||
new EleventyShortcodeError(
|
||||
`Error with Nunjucks paired shortcode \`${shortcodeName}\`${EleventyErrorUtil.convertErrorToString(
|
||||
e
|
||||
)}`
|
||||
),
|
||||
null
|
||||
);
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
return new NunjucksLib.runtime.SafeString(
|
||||
shortcodeFn.call(
|
||||
Nunjucks._normalizeShortcodeContext(context),
|
||||
body(),
|
||||
...argArray
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
throw new EleventyShortcodeError(
|
||||
`Error with Nunjucks paired shortcode \`${shortcodeName}\`${EleventyErrorUtil.convertErrorToString(
|
||||
e
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.njkEnv.addExtension(shortcodeName, new PairedShortcodeFunction());
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
let tmpl;
|
||||
if (!inputPath || inputPath === "njk" || inputPath === "md") {
|
||||
tmpl = NunjucksLib.compile(str, this.njkEnv);
|
||||
} else {
|
||||
tmpl = NunjucksLib.compile(str, this.njkEnv, inputPath);
|
||||
}
|
||||
return async function(data) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
tmpl.render(data, function(err, res) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Nunjucks;
|
|
@ -0,0 +1,48 @@
|
|||
const PugLib = require("pug");
|
||||
const TemplateEngine = require("./TemplateEngine");
|
||||
const TemplatePath = require("../TemplatePath");
|
||||
|
||||
class Pug extends TemplateEngine {
|
||||
constructor(name, includesDir) {
|
||||
super(name, includesDir);
|
||||
|
||||
this.pugOptions = {};
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.pug);
|
||||
this.setPugOptions(this.config.pugOptions);
|
||||
}
|
||||
|
||||
setLibrary(lib) {
|
||||
this.pugLib = lib || PugLib;
|
||||
this.setEngineLib(this.pugLib);
|
||||
}
|
||||
|
||||
setPugOptions(options) {
|
||||
this.pugOptions = options;
|
||||
}
|
||||
|
||||
getPugOptions() {
|
||||
let includesDir = super.getIncludesDir();
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
basedir: includesDir,
|
||||
filename: includesDir
|
||||
},
|
||||
this.pugOptions || {}
|
||||
);
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
let options = this.getPugOptions();
|
||||
if (!inputPath || inputPath === "pug" || inputPath === "md") {
|
||||
// do nothing
|
||||
} else {
|
||||
options.filename = inputPath;
|
||||
}
|
||||
|
||||
return this.pugLib.compile(str, options);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Pug;
|
|
@ -0,0 +1,155 @@
|
|||
const fastglob = require("fast-glob");
|
||||
const fs = require("fs-extra");
|
||||
const TemplatePath = require("../TemplatePath");
|
||||
const EleventyExtensionMap = require("../EleventyExtensionMap");
|
||||
const debug = require("debug")("Eleventy:TemplateEngine");
|
||||
const aggregateBench = require("../BenchmarkManager").get("Aggregate");
|
||||
|
||||
class TemplateEngine {
|
||||
constructor(name, includesDir) {
|
||||
this.name = name;
|
||||
this.includesDir = includesDir;
|
||||
this.partialsHaveBeenCached = false;
|
||||
this.partials = [];
|
||||
this.engineLib = null;
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (!this._config) {
|
||||
this._config = require("../Config").getConfig();
|
||||
}
|
||||
return this._config;
|
||||
}
|
||||
|
||||
set config(config) {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
get engineManager() {
|
||||
return this._engineManager;
|
||||
}
|
||||
|
||||
set engineManager(manager) {
|
||||
this._engineManager = manager;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap();
|
||||
// this._extensionMap.config = this.config;
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
set extensionMap(map) {
|
||||
this._extensionMap = map;
|
||||
}
|
||||
|
||||
get extensions() {
|
||||
if (!this._extensions) {
|
||||
this._extensions = this.extensionMap.getExtensionsFromKey(this.name);
|
||||
}
|
||||
return this._extensions;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
getIncludesDir() {
|
||||
return this.includesDir;
|
||||
}
|
||||
|
||||
// TODO make async
|
||||
getPartials() {
|
||||
if (!this.partialsHaveBeenCached) {
|
||||
this.partials = this.cachePartialFiles();
|
||||
}
|
||||
|
||||
return this.partials;
|
||||
}
|
||||
|
||||
// TODO make async
|
||||
cachePartialFiles() {
|
||||
// This only runs if getPartials() is called, which is only for Mustache/Handlebars
|
||||
this.partialsHaveBeenCached = true;
|
||||
let partials = {};
|
||||
let prefix = this.includesDir + "/**/*.";
|
||||
// TODO: reuse mustache partials in handlebars?
|
||||
let partialFiles = [];
|
||||
if (this.includesDir) {
|
||||
let bench = aggregateBench.get("Searching the file system");
|
||||
bench.before();
|
||||
this.extensions.forEach(function(extension) {
|
||||
partialFiles = partialFiles.concat(
|
||||
fastglob.sync(prefix + extension, {
|
||||
caseSensitiveMatch: false,
|
||||
dot: true
|
||||
})
|
||||
);
|
||||
});
|
||||
bench.after();
|
||||
}
|
||||
|
||||
partialFiles = TemplatePath.addLeadingDotSlashArray(partialFiles);
|
||||
|
||||
for (let j = 0, k = partialFiles.length; j < k; j++) {
|
||||
let partialPath = TemplatePath.stripLeadingSubPath(
|
||||
partialFiles[j],
|
||||
this.includesDir
|
||||
);
|
||||
let partialPathNoExt = partialPath;
|
||||
this.extensions.forEach(function(extension) {
|
||||
partialPathNoExt = TemplatePath.removeExtension(
|
||||
partialPathNoExt,
|
||||
"." + extension
|
||||
);
|
||||
});
|
||||
partials[partialPathNoExt] = fs.readFileSync(partialFiles[j], "utf-8");
|
||||
}
|
||||
|
||||
debug(
|
||||
`${this.includesDir}/*.{${this.extensions}} found partials for: %o`,
|
||||
Object.keys(partials)
|
||||
);
|
||||
|
||||
return partials;
|
||||
}
|
||||
|
||||
setEngineLib(engineLib) {
|
||||
this.engineLib = engineLib;
|
||||
}
|
||||
|
||||
getEngineLib() {
|
||||
return this.engineLib;
|
||||
}
|
||||
|
||||
async _testRender(str, data) {
|
||||
/* TODO compile needs to pass in inputPath? */
|
||||
try {
|
||||
let fn = await this.compile(str);
|
||||
return fn(data);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
// JavaScript files defer to the module loader rather than read the files to strings
|
||||
needsToReadFileContents() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getExtraDataFromFile(inputPath) {
|
||||
return {};
|
||||
}
|
||||
|
||||
initRequireCache(inputPath) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
get defaultTemplateFileExtension() {
|
||||
return "html";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TemplateEngine;
|
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 @@
|
|||
const EleventyBaseError = require("../EleventyBaseError");
|
||||
|
||||
class TemplateContentPrematureUseError extends EleventyBaseError {}
|
||||
|
||||
module.exports = TemplateContentPrematureUseError;
|
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 @@
|
|||
const EleventyBaseError = require("../EleventyBaseError");
|
||||
|
||||
class UsingCircularTemplateContentReferenceError extends EleventyBaseError {}
|
||||
|
||||
module.exports = UsingCircularTemplateContentReferenceError;
|
|
@ -0,0 +1,20 @@
|
|||
module.exports = 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
|
||||
) {
|
||||
index = j;
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
if (index !== undefined && collection && collection.length) {
|
||||
if (index + modifier >= 0 && index + modifier < collection.length) {
|
||||
return collection[index + modifier];
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
const slugify = require("slugify");
|
||||
|
||||
module.exports = function(str) {
|
||||
return slugify(str, {
|
||||
replacement: "-",
|
||||
lower: true
|
||||
});
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
const validUrl = require("valid-url");
|
||||
const TemplatePath = require("../TemplatePath");
|
||||
|
||||
module.exports = function(url, pathPrefix) {
|
||||
// work with undefined
|
||||
url = url || "";
|
||||
|
||||
if (
|
||||
validUrl.isUri(url) ||
|
||||
url.indexOf("http://") === 0 ||
|
||||
url.indexOf("https://") === 0
|
||||
) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (pathPrefix === undefined || typeof pathPrefix !== "string") {
|
||||
let projectConfig = require("../Config").getConfig();
|
||||
pathPrefix = projectConfig.pathPrefix;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
|
@ -0,0 +1,302 @@
|
|||
const lodashChunk = require("lodash/chunk");
|
||||
const lodashGet = require("lodash/get");
|
||||
const lodashSet = require("lodash/set");
|
||||
const EleventyBaseError = require("../EleventyBaseError");
|
||||
const config = require("../Config");
|
||||
|
||||
class PaginationError extends EleventyBaseError {}
|
||||
|
||||
class Pagination {
|
||||
constructor(data) {
|
||||
this.config = config.getConfig();
|
||||
|
||||
this.setData(data);
|
||||
}
|
||||
|
||||
static hasPagination(data) {
|
||||
return "pagination" in data;
|
||||
}
|
||||
|
||||
hasPagination() {
|
||||
if (!this.data) {
|
||||
throw new Error("Missing `setData` call for Pagination object.");
|
||||
}
|
||||
return Pagination.hasPagination(this.data);
|
||||
}
|
||||
|
||||
circularReferenceCheck(data) {
|
||||
if (data.eleventyExcludeFromCollections) {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = data.pagination.data;
|
||||
let tags = data.tags || [];
|
||||
for (let tag of tags) {
|
||||
if (`collections.${tag}` === key) {
|
||||
throw new PaginationError(
|
||||
`Pagination circular reference${
|
||||
this.template ? ` on ${this.template.inputPath}` : ""
|
||||
}, data:\`${key}\` iterates over both the \`${tag}\` tag and also supplies pages to that tag.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
this.data = data || {};
|
||||
this.target = [];
|
||||
|
||||
if (!this.hasPagination()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.pagination) {
|
||||
throw new Error(
|
||||
"Misconfigured pagination data in template front matter (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.circularReferenceCheck(data);
|
||||
|
||||
this.size = data.pagination.size;
|
||||
this.alias = data.pagination.alias;
|
||||
|
||||
this.target = this._resolveItems();
|
||||
this.items = this.getPagedItems();
|
||||
}
|
||||
|
||||
setTemplate(tmpl) {
|
||||
this.template = tmpl;
|
||||
}
|
||||
|
||||
_getDataKey() {
|
||||
return this.data.pagination.data;
|
||||
}
|
||||
|
||||
doResolveToObjectValues() {
|
||||
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;
|
||||
}
|
||||
|
||||
_resolveItems() {
|
||||
let notFoundValue = "__NOT_FOUND_ERROR__";
|
||||
let key = this._getDataKey();
|
||||
let fullDataSet = lodashGet(this.data, key, notFoundValue);
|
||||
if (fullDataSet === notFoundValue) {
|
||||
throw new Error(
|
||||
`Could not resolve pagination key in template data: ${key}`
|
||||
);
|
||||
}
|
||||
|
||||
let keys;
|
||||
if (Array.isArray(fullDataSet)) {
|
||||
keys = fullDataSet;
|
||||
} else if (this.doResolveToObjectValues()) {
|
||||
keys = Object.values(fullDataSet);
|
||||
} else {
|
||||
keys = Object.keys(fullDataSet);
|
||||
}
|
||||
|
||||
let result = keys.filter(() => true);
|
||||
|
||||
if (
|
||||
this.data.pagination.before &&
|
||||
typeof this.data.pagination.before === "function"
|
||||
) {
|
||||
// we don’t need to make a copy of this because we already .filter() above
|
||||
result = this.data.pagination.before(result);
|
||||
}
|
||||
|
||||
if (this.data.pagination.reverse === true) {
|
||||
result = result.reverse();
|
||||
}
|
||||
|
||||
if (this.data.pagination.filter) {
|
||||
result = result.filter(value => !this.isFiltered(value));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
getPagedItems() {
|
||||
// TODO switch to a getter
|
||||
if (!this.data) {
|
||||
throw new Error("Missing `setData` call for Pagination object.");
|
||||
}
|
||||
|
||||
return lodashChunk(this.target, this.size);
|
||||
}
|
||||
|
||||
// TODO this name is not good
|
||||
// “To cancel” means to not write the original root template
|
||||
cancel() {
|
||||
return this.hasPagination();
|
||||
}
|
||||
|
||||
getPageCount() {
|
||||
if (!this.hasPagination()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.items.length;
|
||||
}
|
||||
|
||||
async getPageTemplates() {
|
||||
if (!this.data) {
|
||||
throw new Error("Missing `setData` call for Pagination object.");
|
||||
}
|
||||
|
||||
if (!this.hasPagination()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.pagesCache) {
|
||||
return this.pagesCache;
|
||||
}
|
||||
|
||||
let pages = [];
|
||||
let items = this.items;
|
||||
let tmpl = this.template;
|
||||
let templates = [];
|
||||
let links = [];
|
||||
let hrefs = [];
|
||||
let overrides = [];
|
||||
|
||||
for (let pageNumber = 0, k = items.length; pageNumber < k; pageNumber++) {
|
||||
let cloned = tmpl.clone();
|
||||
|
||||
// TODO maybe also move this permalink additions up into the pagination class
|
||||
if (pageNumber > 0 && !this.data[this.config.keys.permalink]) {
|
||||
cloned.setExtraOutputSubdirectory(pageNumber);
|
||||
}
|
||||
|
||||
templates.push(cloned);
|
||||
|
||||
let override = {
|
||||
pagination: {
|
||||
data: this.data.pagination.data,
|
||||
size: this.data.pagination.size,
|
||||
alias: this.alias,
|
||||
|
||||
pages: this.size === 1 ? items.map(entry => entry[0]) : items,
|
||||
|
||||
// See Issue #345 for more examples
|
||||
page: {
|
||||
previous:
|
||||
pageNumber > 0
|
||||
? this.size === 1
|
||||
? items[pageNumber - 1][0]
|
||||
: items[pageNumber - 1]
|
||||
: null,
|
||||
next:
|
||||
pageNumber < items.length - 1
|
||||
? this.size === 1
|
||||
? items[pageNumber + 1][0]
|
||||
: items[pageNumber + 1]
|
||||
: null,
|
||||
first: items.length
|
||||
? this.size === 1
|
||||
? items[0][0]
|
||||
: items[0]
|
||||
: null,
|
||||
last: items.length
|
||||
? this.size === 1
|
||||
? items[items.length - 1][0]
|
||||
: items[items.length - 1]
|
||||
: null
|
||||
},
|
||||
|
||||
items: items[pageNumber],
|
||||
pageNumber: pageNumber
|
||||
}
|
||||
};
|
||||
|
||||
if (this.alias) {
|
||||
lodashSet(
|
||||
override,
|
||||
this.alias,
|
||||
this.size === 1 ? items[pageNumber][0] : items[pageNumber]
|
||||
);
|
||||
}
|
||||
|
||||
overrides.push(override);
|
||||
cloned.setPaginationData(override);
|
||||
|
||||
// TO DO subdirectory to links if the site doesn’t live at /
|
||||
links.push("/" + (await cloned.getOutputLink()));
|
||||
hrefs.push(await cloned.getOutputHref());
|
||||
}
|
||||
|
||||
// we loop twice to pass in the appropriate prev/next links (already full generated now)
|
||||
templates.forEach(
|
||||
function(cloned, pageNumber) {
|
||||
let pageObj = {};
|
||||
|
||||
// links are okay but hrefs are better
|
||||
pageObj.previousPageLink =
|
||||
pageNumber > 0 ? links[pageNumber - 1] : null;
|
||||
pageObj.previous = pageObj.previousPageLink;
|
||||
|
||||
pageObj.nextPageLink =
|
||||
pageNumber < templates.length - 1 ? links[pageNumber + 1] : null;
|
||||
pageObj.next = pageObj.nextPageLink;
|
||||
|
||||
pageObj.firstPageLink = links.length > 0 ? links[0] : null;
|
||||
pageObj.lastPageLink =
|
||||
links.length > 0 ? links[links.length - 1] : null;
|
||||
|
||||
pageObj.links = links;
|
||||
// todo deprecated, consistency with collections and use links instead
|
||||
pageObj.pageLinks = links;
|
||||
|
||||
// hrefs are better than links
|
||||
pageObj.previousPageHref =
|
||||
pageNumber > 0 ? hrefs[pageNumber - 1] : null;
|
||||
pageObj.nextPageHref =
|
||||
pageNumber < templates.length - 1 ? hrefs[pageNumber + 1] : null;
|
||||
|
||||
pageObj.firstPageHref = hrefs.length > 0 ? hrefs[0] : null;
|
||||
pageObj.lastPageHref =
|
||||
hrefs.length > 0 ? hrefs[hrefs.length - 1] : null;
|
||||
|
||||
pageObj.hrefs = hrefs;
|
||||
|
||||
// better names
|
||||
pageObj.href = {
|
||||
previous: pageObj.previousPageHref,
|
||||
next: pageObj.nextPageHref,
|
||||
first: pageObj.firstPageHref,
|
||||
last: pageObj.lastPageHref
|
||||
};
|
||||
|
||||
Object.assign(overrides[pageNumber].pagination, pageObj);
|
||||
|
||||
cloned.setPaginationData(overrides[pageNumber]);
|
||||
|
||||
pages.push(cloned);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.pagesCache = pages;
|
||||
|
||||
return pages;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Pagination;
|
|
@ -0,0 +1,774 @@
|
|||
const fs = require("fs-extra");
|
||||
const parsePath = require("parse-filepath");
|
||||
const normalize = require("normalize-path");
|
||||
const lodashIsObject = require("lodash/isObject");
|
||||
const { DateTime } = require("luxon");
|
||||
|
||||
const TemplateData = require("./TemplateData");
|
||||
const TemplateContent = require("./TemplateContent");
|
||||
const TemplatePath = require("./TemplatePath");
|
||||
const TemplatePermalink = require("./TemplatePermalink");
|
||||
const TemplatePermalinkNoWrite = require("./TemplatePermalinkNoWrite");
|
||||
const TemplateLayout = require("./TemplateLayout");
|
||||
const TemplateFileSlug = require("./TemplateFileSlug");
|
||||
const ComputedData = require("./ComputedData");
|
||||
const Pagination = require("./Plugins/Pagination");
|
||||
const TemplateContentPrematureUseError = require("./Errors/TemplateContentPrematureUseError");
|
||||
|
||||
const debug = require("debug")("Eleventy:Template");
|
||||
const debugDev = require("debug")("Dev:Eleventy:Template");
|
||||
const bench = require("./BenchmarkManager").get("Aggregate");
|
||||
|
||||
class Template extends TemplateContent {
|
||||
constructor(path, inputDir, outputDir, templateData, extensionMap) {
|
||||
debugDev("new Template(%o)", path);
|
||||
super(path, inputDir);
|
||||
|
||||
this.parsed = parsePath(path);
|
||||
|
||||
// for pagination
|
||||
this.extraOutputSubdirectory = "";
|
||||
|
||||
if (outputDir) {
|
||||
this.outputDir = normalize(outputDir);
|
||||
} else {
|
||||
this.outputDir = false;
|
||||
}
|
||||
|
||||
this.extensionMap = extensionMap;
|
||||
|
||||
this.linters = [];
|
||||
this.transforms = [];
|
||||
this.plugins = {};
|
||||
this.templateData = templateData;
|
||||
if (this.templateData) {
|
||||
this.templateData.setInputDir(this.inputDir);
|
||||
}
|
||||
this.paginationData = {};
|
||||
|
||||
this.isVerbose = true;
|
||||
this.isDryRun = false;
|
||||
this.writeCount = 0;
|
||||
this.skippedCount = 0;
|
||||
this.wrapWithLayouts = true;
|
||||
this.fileSlug = new TemplateFileSlug(
|
||||
this.inputPath,
|
||||
this.inputDir,
|
||||
this.extensionMap
|
||||
);
|
||||
this.fileSlugStr = this.fileSlug.getSlug();
|
||||
this.filePathStem = this.fileSlug.getFullPathWithoutExtension();
|
||||
}
|
||||
|
||||
setIsVerbose(isVerbose) {
|
||||
this.isVerbose = isVerbose;
|
||||
}
|
||||
|
||||
setDryRun(isDryRun) {
|
||||
this.isDryRun = !!isDryRun;
|
||||
}
|
||||
|
||||
setWrapWithLayouts(wrap) {
|
||||
this.wrapWithLayouts = wrap;
|
||||
}
|
||||
|
||||
setExtraOutputSubdirectory(dir) {
|
||||
this.extraOutputSubdirectory = dir + "/";
|
||||
}
|
||||
|
||||
getTemplateSubfolder() {
|
||||
return TemplatePath.stripLeadingSubPath(this.parsed.dir, this.inputDir);
|
||||
}
|
||||
|
||||
getLayout(layoutKey) {
|
||||
if (!this._layout || layoutKey !== this._layoutKey) {
|
||||
this._layoutKey = layoutKey;
|
||||
this._layout = TemplateLayout.getTemplate(
|
||||
layoutKey,
|
||||
this.getInputDir(),
|
||||
this.config,
|
||||
this.extensionMap
|
||||
);
|
||||
}
|
||||
return this._layout;
|
||||
}
|
||||
|
||||
async getLayoutChain() {
|
||||
if (!this._layout) {
|
||||
await this.getData();
|
||||
}
|
||||
|
||||
return this._layout.getLayoutChain();
|
||||
}
|
||||
|
||||
get baseFile() {
|
||||
return this.extensionMap.removeTemplateExtension(this.parsed.base);
|
||||
}
|
||||
|
||||
get htmlIOException() {
|
||||
// HTML output can’t overwrite the HTML input file.
|
||||
return (
|
||||
this.inputDir === this.outputDir &&
|
||||
this.templateRender.isEngine("html") &&
|
||||
this.baseFile === "index"
|
||||
);
|
||||
}
|
||||
|
||||
async _getLink(data) {
|
||||
if (!data) {
|
||||
data = await this.getData();
|
||||
}
|
||||
|
||||
let permalink = data[this.config.keys.permalink];
|
||||
if (permalink) {
|
||||
// render variables inside permalink front matter, bypass markdown
|
||||
let permalinkValue;
|
||||
if (!this.config.dynamicPermalinks || data.dynamicPermalink === false) {
|
||||
debugDev("Not using dynamicPermalinks, using %o", permalink);
|
||||
permalinkValue = permalink;
|
||||
} else {
|
||||
permalinkValue = await super.render(permalink, data, true);
|
||||
debug(
|
||||
"Rendering permalink for %o: %s becomes %o",
|
||||
this.inputPath,
|
||||
permalink,
|
||||
permalinkValue
|
||||
);
|
||||
debugDev("Permalink rendered with data: %o", data);
|
||||
}
|
||||
|
||||
let perm = new TemplatePermalink(
|
||||
permalinkValue,
|
||||
this.extraOutputSubdirectory
|
||||
);
|
||||
|
||||
return perm;
|
||||
} else if (permalink === false) {
|
||||
return new TemplatePermalinkNoWrite();
|
||||
}
|
||||
|
||||
return TemplatePermalink.generate(
|
||||
this.getTemplateSubfolder(),
|
||||
this.baseFile,
|
||||
this.extraOutputSubdirectory,
|
||||
this.htmlIOException ? this.config.htmlOutputSuffix : "",
|
||||
this.engine.defaultTemplateFileExtension
|
||||
);
|
||||
}
|
||||
|
||||
// TODO instead of htmlIOException, do a global search to check if output path = input path and then add extra suffix
|
||||
async getOutputLink(data) {
|
||||
let link = await this._getLink(data);
|
||||
return link.toString();
|
||||
}
|
||||
|
||||
async getOutputHref(data) {
|
||||
let link = await this._getLink(data);
|
||||
return link.toHref();
|
||||
}
|
||||
|
||||
async getOutputPath(data) {
|
||||
let uri = await this.getOutputLink(data);
|
||||
|
||||
if (uri === false) {
|
||||
return false;
|
||||
} else if (
|
||||
(await this.getFrontMatterData())[this.config.keys.permalinkRoot]
|
||||
) {
|
||||
// TODO this only works with immediate front matter and not data files
|
||||
return normalize(uri);
|
||||
} else {
|
||||
return normalize(this.outputDir + "/" + uri);
|
||||
}
|
||||
}
|
||||
|
||||
setPaginationData(paginationData) {
|
||||
this.paginationData = paginationData;
|
||||
}
|
||||
|
||||
async mapDataAsRenderedTemplates(data, templateData) {
|
||||
// function supported in JavaScript type
|
||||
if (typeof data === "string" || typeof data === "function") {
|
||||
debug("rendering data.renderData for %o", this.inputPath);
|
||||
// bypassMarkdown
|
||||
let str = await super.render(data, templateData, true);
|
||||
return str;
|
||||
} else if (Array.isArray(data)) {
|
||||
let arr = [];
|
||||
for (let j = 0, k = data.length; j < k; j++) {
|
||||
arr.push(await this.mapDataAsRenderedTemplates(data[j], templateData));
|
||||
}
|
||||
return arr;
|
||||
} else if (lodashIsObject(data)) {
|
||||
let obj = {};
|
||||
for (let value in data) {
|
||||
obj[value] = await this.mapDataAsRenderedTemplates(
|
||||
data[value],
|
||||
templateData
|
||||
);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async _testGetAllLayoutFrontMatterData() {
|
||||
let frontMatterData = await this.getFrontMatterData();
|
||||
if (frontMatterData[this.config.keys.layout]) {
|
||||
let layout = this.getLayout(frontMatterData[this.config.keys.layout]);
|
||||
return await layout.getData();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async getData() {
|
||||
if (!this.dataCache) {
|
||||
debugDev("%o getData()", this.inputPath);
|
||||
let localData = {};
|
||||
|
||||
if (this.templateData) {
|
||||
localData = await this.templateData.getLocalData(this.inputPath);
|
||||
debugDev("%o getData() getLocalData", this.inputPath);
|
||||
}
|
||||
|
||||
let frontMatterData = await this.getFrontMatterData();
|
||||
let layoutKey =
|
||||
frontMatterData[this.config.keys.layout] ||
|
||||
localData[this.config.keys.layout];
|
||||
|
||||
let mergedLayoutData = {};
|
||||
if (layoutKey) {
|
||||
let layout = this.getLayout(layoutKey);
|
||||
|
||||
mergedLayoutData = await layout.getData();
|
||||
debugDev(
|
||||
"%o getData() get merged layout chain front matter",
|
||||
this.inputPath
|
||||
);
|
||||
}
|
||||
|
||||
let mergedData = TemplateData.mergeDeep(
|
||||
this.config,
|
||||
{},
|
||||
localData,
|
||||
mergedLayoutData,
|
||||
frontMatterData
|
||||
);
|
||||
mergedData = await this.addPageDate(mergedData);
|
||||
mergedData = this.addPageData(mergedData);
|
||||
debugDev("%o getData() mergedData", this.inputPath);
|
||||
|
||||
this.dataCache = mergedData;
|
||||
}
|
||||
|
||||
// Don’t deep merge pagination data! See https://github.com/11ty/eleventy/issues/147#issuecomment-440802454
|
||||
return Object.assign(
|
||||
TemplateData.mergeDeep(this.config, {}, this.dataCache),
|
||||
this.paginationData
|
||||
);
|
||||
}
|
||||
|
||||
async addPageDate(data) {
|
||||
if (!("page" in data)) {
|
||||
data.page = {};
|
||||
}
|
||||
|
||||
let newDate = await this.getMappedDate(data);
|
||||
|
||||
if ("page" in data && "date" in data.page) {
|
||||
debug(
|
||||
"Warning: data.page.date is in use (%o) will be overwritten with: %o",
|
||||
data.page.date,
|
||||
newDate
|
||||
);
|
||||
}
|
||||
|
||||
data.page.date = newDate;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
addPageData(data) {
|
||||
if (!("page" in data)) {
|
||||
data.page = {};
|
||||
}
|
||||
|
||||
data.page.inputPath = this.inputPath;
|
||||
data.page.fileSlug = this.fileSlugStr;
|
||||
data.page.filePathStem = this.filePathStem;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async renderLayout(tmpl, tmplData) {
|
||||
let layoutKey = tmplData[tmpl.config.keys.layout];
|
||||
let layout = this.getLayout(layoutKey);
|
||||
debug("%o is using layout %o", this.inputPath, layoutKey);
|
||||
|
||||
// TODO reuse templateContent from templateMap
|
||||
let templateContent = await super.render(
|
||||
await this.getPreRender(),
|
||||
tmplData
|
||||
);
|
||||
return layout.render(tmplData, templateContent);
|
||||
}
|
||||
|
||||
async _testRenderWithoutLayouts(data) {
|
||||
this.setWrapWithLayouts(false);
|
||||
let ret = await this.render(data);
|
||||
this.setWrapWithLayouts(true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
async renderContent(str, data, bypassMarkdown) {
|
||||
return super.render(str, data, bypassMarkdown);
|
||||
}
|
||||
|
||||
// TODO at least some of this isn’t being used in the normal build
|
||||
// Render is used for `renderData` and `permalink` but otherwise `renderPageEntry` is being used
|
||||
async render(data) {
|
||||
debugDev("%o render()", this.inputPath);
|
||||
if (!data) {
|
||||
throw new Error("`data` needs to be passed into render()");
|
||||
}
|
||||
|
||||
if (!this.wrapWithLayouts && data[this.config.keys.layout]) {
|
||||
debugDev("Template.render is bypassing layouts for %o.", this.inputPath);
|
||||
}
|
||||
|
||||
if (this.wrapWithLayouts && data[this.config.keys.layout]) {
|
||||
debugDev(
|
||||
"Template.render found layout: %o",
|
||||
data[this.config.keys.layout]
|
||||
);
|
||||
return this.renderLayout(this, data);
|
||||
} else {
|
||||
debugDev("Template.render renderContent for %o", this.inputPath);
|
||||
return super.render(await this.getPreRender(), data);
|
||||
}
|
||||
}
|
||||
|
||||
addLinter(callback) {
|
||||
this.linters.push(callback);
|
||||
}
|
||||
|
||||
async runLinters(str, inputPath, outputPath) {
|
||||
this.linters.forEach(function (linter) {
|
||||
// these can be asynchronous but no guarantee of order when they run
|
||||
linter.call(this, str, inputPath, outputPath);
|
||||
});
|
||||
}
|
||||
|
||||
addTransform(callback) {
|
||||
this.transforms.push(callback);
|
||||
}
|
||||
|
||||
async runTransforms(str, outputPath, inputPath) {
|
||||
for (let transform of this.transforms) {
|
||||
str = await transform.call(this, str, outputPath, inputPath);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
_addComputedEntry(computedData, obj, parentKey, declaredDependencies) {
|
||||
// this check must come before lodashIsObject
|
||||
if (typeof obj === "function") {
|
||||
computedData.add(parentKey, obj, declaredDependencies);
|
||||
} else if (lodashIsObject(obj)) {
|
||||
for (let key in obj) {
|
||||
let keys = [];
|
||||
if (parentKey) {
|
||||
keys.push(parentKey);
|
||||
}
|
||||
keys.push(key);
|
||||
this._addComputedEntry(
|
||||
computedData,
|
||||
obj[key],
|
||||
keys.join("."),
|
||||
declaredDependencies
|
||||
);
|
||||
}
|
||||
} else if (typeof obj === "string") {
|
||||
computedData.addTemplateString(
|
||||
parentKey,
|
||||
async (innerData) => {
|
||||
return await super.render(obj, innerData, true);
|
||||
},
|
||||
declaredDependencies
|
||||
);
|
||||
} else {
|
||||
// Numbers, booleans, etc
|
||||
computedData.add(parentKey, obj, declaredDependencies);
|
||||
}
|
||||
}
|
||||
|
||||
async addComputedData(data) {
|
||||
// will _not_ consume renderData
|
||||
this.computedData = new ComputedData();
|
||||
|
||||
// Add permalink (outside of eleventyComputed) to computed graph
|
||||
// if(data.permalink) {
|
||||
// this._addComputedEntry(this.computedData, data.permalink, "permalink");
|
||||
// }
|
||||
|
||||
// Note that `permalink` is only a thing that gets consumed—it does not go directly into generated data
|
||||
// this allows computed entries to use page.url or page.outputPath and they’ll be resolved properly
|
||||
this.computedData.addTemplateString(
|
||||
"page.url",
|
||||
async (data) => await this.getOutputHref(data),
|
||||
data.permalink ? ["permalink"] : undefined
|
||||
);
|
||||
|
||||
this.computedData.addTemplateString(
|
||||
"page.outputPath",
|
||||
async (data) => await this.getOutputPath(data),
|
||||
data.permalink ? ["permalink"] : undefined
|
||||
);
|
||||
|
||||
if (this.config.keys.computed in data) {
|
||||
this._addComputedEntry(
|
||||
this.computedData,
|
||||
data[this.config.keys.computed]
|
||||
);
|
||||
}
|
||||
|
||||
// limited run of computed data—save the stuff that relies on collections for later.
|
||||
debug("First round of computed data for %o", this.inputPath);
|
||||
await this.computedData.setupData(data, function (entry) {
|
||||
return !this.isUsesStartsWith(entry, "collections.");
|
||||
|
||||
// TODO possible improvement here is to only process page.url, page.outputPath, permalink
|
||||
// instead of only punting on things that rely on collections.
|
||||
// let firstPhaseComputedData = ["page.url", "page.outputPath", ...this.getOrderFor("page.url"), ...this.getOrderFor("page.outputPath")];
|
||||
// return firstPhaseComputedData.indexOf(entry) > -1;
|
||||
});
|
||||
|
||||
// Deprecated, use eleventyComputed instead.
|
||||
if ("renderData" in data) {
|
||||
data.renderData = await this.mapDataAsRenderedTemplates(
|
||||
data.renderData,
|
||||
data
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async resolveRemainingComputedData(data) {
|
||||
debug("Second round of computed data for %o", this.inputPath);
|
||||
await this.computedData.processRemainingData(data);
|
||||
}
|
||||
|
||||
async getTemplates(data) {
|
||||
// TODO cache this
|
||||
let results = [];
|
||||
|
||||
if (!Pagination.hasPagination(data)) {
|
||||
await this.addComputedData(data);
|
||||
|
||||
results.push({
|
||||
template: this,
|
||||
inputPath: this.inputPath,
|
||||
fileSlug: this.fileSlugStr,
|
||||
filePathStem: this.filePathStem,
|
||||
data: data,
|
||||
date: data.page.date,
|
||||
outputPath: data.page.outputPath,
|
||||
url: data.page.url,
|
||||
set templateContent(content) {
|
||||
this._templateContent = content;
|
||||
},
|
||||
get templateContent() {
|
||||
if (this._templateContent === undefined) {
|
||||
// should at least warn here
|
||||
throw new TemplateContentPrematureUseError(
|
||||
`Tried to use templateContent too early (${this.inputPath})`
|
||||
);
|
||||
}
|
||||
return this._templateContent;
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// needs collections for pagination items
|
||||
// but individual pagination entries won’t be part of a collection
|
||||
this.paging = new Pagination(data);
|
||||
this.paging.setTemplate(this);
|
||||
let pageTemplates = await this.paging.getPageTemplates();
|
||||
let pageNumber = 0;
|
||||
for (let page of pageTemplates) {
|
||||
let pageData = Object.assign({}, await page.getData());
|
||||
|
||||
await page.addComputedData(pageData);
|
||||
|
||||
// Issue #115
|
||||
if (data.collections) {
|
||||
pageData.collections = data.collections;
|
||||
}
|
||||
|
||||
results.push({
|
||||
template: page,
|
||||
inputPath: this.inputPath,
|
||||
fileSlug: this.fileSlugStr,
|
||||
filePathStem: this.filePathStem,
|
||||
data: pageData,
|
||||
date: pageData.page.date,
|
||||
pageNumber: pageNumber++,
|
||||
outputPath: pageData.page.outputPath,
|
||||
url: pageData.page.url,
|
||||
set templateContent(content) {
|
||||
this._templateContent = content;
|
||||
},
|
||||
get templateContent() {
|
||||
if (this._templateContent === undefined) {
|
||||
throw new TemplateContentPrematureUseError(
|
||||
`Tried to use templateContent too early (${this.inputPath} page ${this.pageNumber})`
|
||||
);
|
||||
}
|
||||
return this._templateContent;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async getRenderedTemplates(data) {
|
||||
let pages = await this.getTemplates(data);
|
||||
for (let page of pages) {
|
||||
let content = await page.template._getContent(page.outputPath, page.data);
|
||||
|
||||
page.templateContent = content;
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
|
||||
async _getContent(outputPath, data) {
|
||||
return await this.render(data);
|
||||
}
|
||||
|
||||
async _write(outputPath, finalContent) {
|
||||
let shouldWriteFile = true;
|
||||
|
||||
if (this.isDryRun) {
|
||||
shouldWriteFile = false;
|
||||
}
|
||||
|
||||
if (outputPath === false) {
|
||||
debug(
|
||||
"Ignored %o from %o (permalink: false).",
|
||||
outputPath,
|
||||
this.inputPath
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let lang = {
|
||||
start: "Writing",
|
||||
finished: "written.",
|
||||
};
|
||||
|
||||
if (!shouldWriteFile) {
|
||||
lang = {
|
||||
start: "Skipping",
|
||||
finished: "", // not used, promise doesn’t resolve
|
||||
};
|
||||
}
|
||||
|
||||
if (this.isVerbose) {
|
||||
console.log(`${lang.start} ${outputPath} from ${this.inputPath}.`);
|
||||
} else {
|
||||
debug(`${lang.start} %o from %o.`, outputPath, this.inputPath);
|
||||
}
|
||||
|
||||
if (!shouldWriteFile) {
|
||||
this.skippedCount++;
|
||||
} else {
|
||||
let templateBenchmark = bench.get("Template Write");
|
||||
templateBenchmark.before();
|
||||
return fs.outputFile(outputPath, finalContent).then(() => {
|
||||
templateBenchmark.after();
|
||||
this.writeCount++;
|
||||
debug(`${outputPath} ${lang.finished}.`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async renderPageEntry(mapEntry, page) {
|
||||
let content;
|
||||
let layoutKey = mapEntry.data[this.config.keys.layout];
|
||||
if (layoutKey) {
|
||||
let layout = this.getLayout(layoutKey);
|
||||
|
||||
content = await layout.render(page.data, page.templateContent);
|
||||
} else {
|
||||
content = page.templateContent;
|
||||
}
|
||||
|
||||
await this.runLinters(content, page.inputPath, page.outputPath);
|
||||
content = await this.runTransforms(content, page.outputPath); // pass in page.inputPath?
|
||||
return content;
|
||||
}
|
||||
|
||||
async writeMapEntry(mapEntry) {
|
||||
let promises = [];
|
||||
for (let page of mapEntry._pages) {
|
||||
let content = await this.renderPageEntry(mapEntry, page);
|
||||
let promise = this._write(page.outputPath, content);
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
// TODO is this still used by anything but tests?
|
||||
async write(outputPath, data) {
|
||||
let templates = await this.getRenderedTemplates(data);
|
||||
let promises = [];
|
||||
for (let tmpl of templates) {
|
||||
promises.push(this._write(tmpl.outputPath, tmpl.templateContent));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
// TODO this but better
|
||||
clone() {
|
||||
let tmpl = new Template(
|
||||
this.inputPath,
|
||||
this.inputDir,
|
||||
this.outputDir,
|
||||
this.templateData,
|
||||
this.extensionMap
|
||||
);
|
||||
tmpl.config = this.config;
|
||||
|
||||
for (let transform of this.transforms) {
|
||||
tmpl.addTransform(transform);
|
||||
}
|
||||
for (let linter of this.linters) {
|
||||
tmpl.addLinter(linter);
|
||||
}
|
||||
tmpl.setIsVerbose(this.isVerbose);
|
||||
tmpl.setDryRun(this.isDryRun);
|
||||
|
||||
return tmpl;
|
||||
}
|
||||
|
||||
getWriteCount() {
|
||||
return this.writeCount;
|
||||
}
|
||||
|
||||
getSkippedCount() {
|
||||
return this.skippedCount;
|
||||
}
|
||||
|
||||
async getMappedDate(data) {
|
||||
// should we use Luxon dates everywhere? Right now using built-in `Date`
|
||||
if ("date" in data && data.date) {
|
||||
debug(
|
||||
"getMappedDate: using a date in the data for %o of %o",
|
||||
this.inputPath,
|
||||
data.date
|
||||
);
|
||||
if (data.date instanceof Date) {
|
||||
// YAML does its own date parsing
|
||||
debug("getMappedDate: YAML parsed it: %o", data.date);
|
||||
return data.date;
|
||||
} else {
|
||||
let stat = await fs.stat(this.inputPath);
|
||||
// string
|
||||
if (data.date.toLowerCase() === "last modified") {
|
||||
return new Date(stat.ctimeMs);
|
||||
} else if (data.date.toLowerCase() === "created") {
|
||||
return new Date(stat.birthtimeMs);
|
||||
} else {
|
||||
// try to parse with Luxon
|
||||
let date = DateTime.fromISO(data.date, { zone: "utc" });
|
||||
if (!date.isValid) {
|
||||
throw new Error(
|
||||
`date front matter value (${data.date}) is invalid for ${this.inputPath}`
|
||||
);
|
||||
}
|
||||
debug(
|
||||
"getMappedDate: Luxon parsed %o: %o and %o",
|
||||
data.date,
|
||||
date,
|
||||
date.toJSDate()
|
||||
);
|
||||
|
||||
return date.toJSDate();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let filenameRegex = this.inputPath.match(/(\d{4}-\d{2}-\d{2})/);
|
||||
if (filenameRegex !== null) {
|
||||
let dateObj = DateTime.fromISO(filenameRegex[1]).toJSDate();
|
||||
debug(
|
||||
"getMappedDate: using filename regex time for %o of %o: %o",
|
||||
this.inputPath,
|
||||
filenameRegex[1],
|
||||
dateObj
|
||||
);
|
||||
return dateObj;
|
||||
}
|
||||
|
||||
let stat = await fs.stat(this.inputPath);
|
||||
let createdDate = new Date(stat.birthtimeMs);
|
||||
debug(
|
||||
"getMappedDate: using file created time for %o of %o (from %o)",
|
||||
this.inputPath,
|
||||
createdDate,
|
||||
stat.birthtimeMs
|
||||
);
|
||||
|
||||
// CREATED
|
||||
return createdDate;
|
||||
}
|
||||
}
|
||||
|
||||
async getTemplateMapContent(pageMapEntry) {
|
||||
pageMapEntry.template.setWrapWithLayouts(false);
|
||||
let content = await pageMapEntry.template._getContent(
|
||||
pageMapEntry.outputPath,
|
||||
pageMapEntry.data
|
||||
);
|
||||
pageMapEntry.template.setWrapWithLayouts(true);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
async getTemplateMapEntries() {
|
||||
debugDev("%o getMapped()", this.inputPath);
|
||||
|
||||
let data = await this.getData();
|
||||
let entries = [];
|
||||
// does not return outputPath or url, we don’t want to render permalinks yet
|
||||
entries.push({
|
||||
template: this,
|
||||
inputPath: this.inputPath,
|
||||
data: data,
|
||||
});
|
||||
return entries;
|
||||
}
|
||||
|
||||
async _testCompleteRender() {
|
||||
let entries = await this.getTemplateMapEntries();
|
||||
let contents = [];
|
||||
|
||||
for (let entry of entries) {
|
||||
entry._pages = await entry.template.getTemplates(entry.data);
|
||||
|
||||
let page;
|
||||
for (page of entry._pages) {
|
||||
page.templateContent = await entry.template.getTemplateMapContent(page);
|
||||
}
|
||||
for (page of entry._pages) {
|
||||
contents.push(await this.renderPageEntry(entry, page));
|
||||
}
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Template;
|
|
@ -0,0 +1,35 @@
|
|||
// 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 = {};
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.cache = {};
|
||||
}
|
||||
|
||||
size() {
|
||||
return Object.keys(this.cache).length;
|
||||
}
|
||||
|
||||
add(key, template) {
|
||||
this.cache[key] = template;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
// singleton
|
||||
module.exports = new TemplateCache();
|
|
@ -0,0 +1,73 @@
|
|||
const multimatch = require("multimatch");
|
||||
const Sortable = require("./Util/Sortable");
|
||||
const TemplatePath = require("./TemplatePath");
|
||||
|
||||
class TemplateCollection extends Sortable {
|
||||
// right now this is only used by the tests
|
||||
async _testAddTemplate(template) {
|
||||
let data = await template.getData();
|
||||
for (let map of await template.getTemplates(data)) {
|
||||
this.add(map);
|
||||
}
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return this.items.filter(() => true);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return this.getAllSorted().filter(item => {
|
||||
if (multimatch([item.inputPath], globs).length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
getFilteredByTag(tagName) {
|
||||
return this.getAllSorted().filter(item => {
|
||||
if (!tagName) {
|
||||
return true;
|
||||
} else if (Array.isArray(item.data.tags)) {
|
||||
return item.data.tags.some(tag => tag === tagName);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
getFilteredByTags(...tags) {
|
||||
return this.getAllSorted().filter(item =>
|
||||
tags.every(requiredTag => {
|
||||
const itemTags = item.data.tags;
|
||||
if (Array.isArray(itemTags)) {
|
||||
return itemTags.includes(requiredTag);
|
||||
} else {
|
||||
return itemTags === requiredTag;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TemplateCollection;
|
|
@ -0,0 +1,157 @@
|
|||
const fs = require("fs-extra");
|
||||
const chalk = require("chalk");
|
||||
const lodashUniq = require("lodash/uniq");
|
||||
const lodashMerge = require("lodash/merge");
|
||||
const TemplatePath = require("./TemplatePath");
|
||||
const EleventyBaseError = require("./EleventyBaseError");
|
||||
const eleventyConfig = require("./EleventyConfig");
|
||||
const debug = require("debug")("Eleventy:TemplateConfig");
|
||||
const deleteRequireCache = require("./Util/DeleteRequireCache");
|
||||
|
||||
class EleventyConfigError extends EleventyBaseError {}
|
||||
|
||||
class TemplateConfig {
|
||||
constructor(customRootConfig, localProjectConfigPath) {
|
||||
this.overrides = {};
|
||||
this.localProjectConfigPath = localProjectConfigPath || ".eleventy.js";
|
||||
|
||||
if (customRootConfig) {
|
||||
this.customRootConfig = customRootConfig;
|
||||
debug("Warning: Using custom root config!");
|
||||
} else {
|
||||
this.customRootConfig = null;
|
||||
}
|
||||
this.initializeRootConfig();
|
||||
this.config = this.mergeConfig(this.localProjectConfigPath);
|
||||
}
|
||||
|
||||
getLocalProjectConfigFile() {
|
||||
return TemplatePath.addLeadingDotSlash(this.localProjectConfigPath);
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this._inputDir;
|
||||
}
|
||||
|
||||
set inputDir(inputDir) {
|
||||
this._inputDir = inputDir;
|
||||
}
|
||||
|
||||
reset() {
|
||||
eleventyConfig.reset();
|
||||
this.initializeRootConfig();
|
||||
this.config = this.mergeConfig(this.localProjectConfigPath);
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
setProjectConfigPath(path) {
|
||||
this.localProjectConfigPath = path;
|
||||
|
||||
this.config = this.mergeConfig(path);
|
||||
}
|
||||
|
||||
setPathPrefix(pathPrefix) {
|
||||
debug("Setting pathPrefix to %o", pathPrefix);
|
||||
this.overrides.pathPrefix = pathPrefix;
|
||||
this.config.pathPrefix = pathPrefix;
|
||||
}
|
||||
|
||||
initializeRootConfig() {
|
||||
this.rootConfig = this.customRootConfig || require("./defaultConfig.js");
|
||||
|
||||
if (typeof this.rootConfig === "function") {
|
||||
this.rootConfig = this.rootConfig(eleventyConfig);
|
||||
// debug( "rootConfig is a function, after calling, eleventyConfig is %o", eleventyConfig );
|
||||
}
|
||||
debug("rootConfig %o", this.rootConfig);
|
||||
}
|
||||
|
||||
mergeConfig(localProjectConfigPath) {
|
||||
let localConfig = {};
|
||||
let path = TemplatePath.join(
|
||||
TemplatePath.getWorkingDir(),
|
||||
localProjectConfigPath
|
||||
);
|
||||
debug(`Merging config with ${path}`);
|
||||
|
||||
if (fs.existsSync(path)) {
|
||||
try {
|
||||
// remove from require cache so it will grab a fresh copy
|
||||
if (path in require.cache) {
|
||||
deleteRequireCache(path);
|
||||
}
|
||||
|
||||
localConfig = require(path);
|
||||
// debug( "localConfig require return value: %o", localConfig );
|
||||
|
||||
if (typeof localConfig === "function") {
|
||||
localConfig = localConfig(eleventyConfig);
|
||||
// debug( "localConfig is a function, after calling, eleventyConfig is %o", eleventyConfig );
|
||||
|
||||
if (
|
||||
typeof localConfig === "object" &&
|
||||
typeof localConfig.then === "function"
|
||||
) {
|
||||
throw new EleventyConfigError(
|
||||
`Error in your Eleventy config file '${path}': Returning a promise is not supported`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
throw new EleventyConfigError(
|
||||
`Error in your Eleventy config file '${path}'.` +
|
||||
(err.message.includes("Cannot find module")
|
||||
? chalk.blueBright(" You may need to run `npm install`.")
|
||||
: ""),
|
||||
err
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debug("Eleventy local project config file not found, skipping.");
|
||||
}
|
||||
|
||||
let eleventyConfigApiMergingObject = eleventyConfig.getMergingConfigObject();
|
||||
|
||||
// remove special merge keys from object
|
||||
let savedForSpecialMerge = {
|
||||
templateFormatsAdded: eleventyConfigApiMergingObject.templateFormatsAdded
|
||||
};
|
||||
delete eleventyConfigApiMergingObject.templateFormatsAdded;
|
||||
|
||||
localConfig = lodashMerge(localConfig, eleventyConfigApiMergingObject);
|
||||
|
||||
// blow away any templateFormats set in config return object and prefer those set in config API.
|
||||
localConfig.templateFormats =
|
||||
eleventyConfigApiMergingObject.templateFormats ||
|
||||
localConfig.templateFormats;
|
||||
|
||||
// debug("eleventyConfig.getMergingConfigObject: %o", eleventyConfig.getMergingConfigObject());
|
||||
debug("localConfig: %o", localConfig);
|
||||
debug("overrides: %o", this.overrides);
|
||||
|
||||
// Object assign overrides original values (good only for templateFormats) but not good for anything else
|
||||
let merged = lodashMerge({}, this.rootConfig, localConfig, this.overrides);
|
||||
// blow away any templateFormats upstream (don’t deep merge)
|
||||
merged.templateFormats =
|
||||
localConfig.templateFormats || this.rootConfig.templateFormats;
|
||||
|
||||
// Additive should preserve original templateFormats, wherever those come from (config API or config return object)
|
||||
if (savedForSpecialMerge.templateFormatsAdded) {
|
||||
merged.templateFormats = merged.templateFormats.concat(
|
||||
savedForSpecialMerge.templateFormatsAdded
|
||||
);
|
||||
}
|
||||
|
||||
// Unique
|
||||
merged.templateFormats = lodashUniq(merged.templateFormats);
|
||||
|
||||
debug("Current configuration: %o", merged);
|
||||
|
||||
return merged;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TemplateConfig;
|
|
@ -0,0 +1,224 @@
|
|||
const os = require("os");
|
||||
const fs = require("fs-extra");
|
||||
const normalize = require("normalize-path");
|
||||
const matter = require("gray-matter");
|
||||
const lodashSet = require("lodash/set");
|
||||
|
||||
const EleventyExtensionMap = require("./EleventyExtensionMap");
|
||||
const TemplateData = require("./TemplateData");
|
||||
const TemplateRender = require("./TemplateRender");
|
||||
const EleventyBaseError = require("./EleventyBaseError");
|
||||
const EleventyErrorUtil = require("./EleventyErrorUtil");
|
||||
const config = require("./Config");
|
||||
const debug = require("debug")("Eleventy:TemplateContent");
|
||||
const debugDev = require("debug")("Dev:Eleventy:TemplateContent");
|
||||
const bench = require("./BenchmarkManager").get("Aggregate");
|
||||
|
||||
class TemplateContentCompileError extends EleventyBaseError {}
|
||||
class TemplateContentRenderError extends EleventyBaseError {}
|
||||
|
||||
class TemplateContent {
|
||||
constructor(inputPath, inputDir) {
|
||||
this.inputPath = inputPath;
|
||||
|
||||
if (inputDir) {
|
||||
this.inputDir = normalize(inputDir);
|
||||
} else {
|
||||
this.inputDir = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Used by tests */
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap();
|
||||
this._extensionMap.config = this.config;
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
set extensionMap(map) {
|
||||
this._extensionMap = map;
|
||||
}
|
||||
|
||||
set config(config) {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (!this._config) {
|
||||
this._config = config.getConfig();
|
||||
}
|
||||
|
||||
return this._config;
|
||||
}
|
||||
|
||||
get engine() {
|
||||
return this.templateRender.engine;
|
||||
}
|
||||
|
||||
get templateRender() {
|
||||
if (!this._templateRender) {
|
||||
this._templateRender = new TemplateRender(this.inputPath, this.inputDir);
|
||||
this._templateRender.extensionMap = this.extensionMap;
|
||||
}
|
||||
|
||||
return this._templateRender;
|
||||
}
|
||||
|
||||
getInputPath() {
|
||||
return this.inputPath;
|
||||
}
|
||||
|
||||
getInputDir() {
|
||||
return this.inputDir;
|
||||
}
|
||||
|
||||
async read() {
|
||||
this.inputContent = await this.getInputContent();
|
||||
|
||||
if (this.inputContent) {
|
||||
let options = this.config.frontMatterParsingOptions || {};
|
||||
let fm = matter(this.inputContent, options);
|
||||
if (options.excerpt && fm.excerpt) {
|
||||
let excerptString = fm.excerpt + (options.excerpt_separator || "---");
|
||||
if (fm.content.startsWith(excerptString + os.EOL)) {
|
||||
// with a newline after excerpt separator
|
||||
fm.content =
|
||||
fm.excerpt.trim() +
|
||||
"\n" +
|
||||
fm.content.substr((excerptString + os.EOL).length);
|
||||
} else if (fm.content.startsWith(excerptString)) {
|
||||
// no newline after excerpt separator
|
||||
fm.content = fm.excerpt + fm.content.substr(excerptString.length);
|
||||
}
|
||||
|
||||
// alias, defaults to page.excerpt
|
||||
let alias = options.excerpt_alias || "page.excerpt";
|
||||
lodashSet(fm.data, alias, fm.excerpt);
|
||||
}
|
||||
this.frontMatter = fm;
|
||||
} else {
|
||||
this.frontMatter = {
|
||||
data: {},
|
||||
content: "",
|
||||
excerpt: ""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async getInputContent() {
|
||||
if (!this.engine.needsToReadFileContents()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let templateBenchmark = bench.get("Template Read");
|
||||
templateBenchmark.before();
|
||||
let content = await fs.readFile(this.inputPath, "utf-8");
|
||||
templateBenchmark.after();
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
async getFrontMatter() {
|
||||
if (!this.frontMatter) {
|
||||
await this.read();
|
||||
}
|
||||
|
||||
return this.frontMatter;
|
||||
}
|
||||
|
||||
async getPreRender() {
|
||||
if (!this.frontMatter) {
|
||||
await this.read();
|
||||
}
|
||||
|
||||
return this.frontMatter.content;
|
||||
}
|
||||
|
||||
async getFrontMatterData() {
|
||||
if (!this.frontMatter) {
|
||||
await this.read();
|
||||
}
|
||||
|
||||
let extraData = await this.engine.getExtraDataFromFile(this.inputPath);
|
||||
let data = TemplateData.mergeDeep({}, this.frontMatter.data, extraData);
|
||||
return TemplateData.cleanupData(data);
|
||||
}
|
||||
|
||||
async getEngineOverride() {
|
||||
let frontMatterData = await this.getFrontMatterData();
|
||||
return frontMatterData[this.config.keys.engineOverride];
|
||||
}
|
||||
|
||||
async setupTemplateRender(bypassMarkdown) {
|
||||
let engineOverride = await this.getEngineOverride();
|
||||
if (engineOverride !== undefined) {
|
||||
debugDev(
|
||||
"%o overriding template engine to use %o",
|
||||
this.inputPath,
|
||||
engineOverride
|
||||
);
|
||||
|
||||
this.templateRender.setEngineOverride(engineOverride, bypassMarkdown);
|
||||
} else {
|
||||
this.templateRender.setUseMarkdown(!bypassMarkdown);
|
||||
}
|
||||
}
|
||||
|
||||
async compile(str, bypassMarkdown) {
|
||||
await this.setupTemplateRender(bypassMarkdown);
|
||||
|
||||
debugDev(
|
||||
"%o compile() using engine: %o",
|
||||
this.inputPath,
|
||||
this.templateRender.engineName
|
||||
);
|
||||
|
||||
try {
|
||||
let templateBenchmark = bench.get("Template Compile");
|
||||
templateBenchmark.before();
|
||||
let fn = await this.templateRender.getCompiledTemplate(str);
|
||||
templateBenchmark.after();
|
||||
debugDev("%o getCompiledTemplate function created", this.inputPath);
|
||||
return fn;
|
||||
} catch (e) {
|
||||
debug(`Having trouble compiling template ${this.inputPath}: %O`, str);
|
||||
throw new TemplateContentCompileError(
|
||||
`Having trouble compiling template ${this.inputPath}`,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async render(str, data, bypassMarkdown) {
|
||||
try {
|
||||
let fn = await this.compile(str, bypassMarkdown);
|
||||
let templateBenchmark = bench.get("Template Render");
|
||||
templateBenchmark.before();
|
||||
let rendered = await fn(data);
|
||||
templateBenchmark.after();
|
||||
debugDev(
|
||||
"%o getCompiledTemplate called, rendered content created",
|
||||
this.inputPath
|
||||
);
|
||||
return rendered;
|
||||
} catch (e) {
|
||||
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
|
||||
throw e;
|
||||
} else {
|
||||
let engine = this.templateRender.getEnginesStr();
|
||||
debug(
|
||||
`Having trouble rendering ${engine} template ${this.inputPath}: %O`,
|
||||
str
|
||||
);
|
||||
throw new TemplateContentRenderError(
|
||||
`Having trouble rendering ${engine} template ${this.inputPath}`,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TemplateContent;
|
|
@ -0,0 +1,511 @@
|
|||
const fs = require("fs-extra");
|
||||
const fastglob = require("fast-glob");
|
||||
const parsePath = require("parse-filepath");
|
||||
const lodashset = require("lodash/set");
|
||||
const lodashget = require("lodash/get");
|
||||
const lodashUniq = require("lodash/uniq");
|
||||
const merge = require("./Util/Merge");
|
||||
const TemplateRender = require("./TemplateRender");
|
||||
const TemplatePath = require("./TemplatePath");
|
||||
const TemplateGlob = require("./TemplateGlob");
|
||||
const EleventyExtensionMap = require("./EleventyExtensionMap");
|
||||
const EleventyBaseError = require("./EleventyBaseError");
|
||||
|
||||
const config = require("./Config");
|
||||
const debugWarn = require("debug")("Eleventy:Warnings");
|
||||
const debug = require("debug")("Eleventy:TemplateData");
|
||||
const debugDev = require("debug")("Dev:Eleventy:TemplateData");
|
||||
const deleteRequireCache = require("./Util/DeleteRequireCache");
|
||||
|
||||
const bench = require("./BenchmarkManager").get("Data");
|
||||
const aggregateBench = require("./BenchmarkManager").get("Aggregate");
|
||||
|
||||
class TemplateDataParseError extends EleventyBaseError {}
|
||||
|
||||
class TemplateData {
|
||||
constructor(inputDir) {
|
||||
this.config = config.getConfig();
|
||||
this.dataTemplateEngine = this.config.dataTemplateEngine;
|
||||
|
||||
this.inputDirNeedsCheck = false;
|
||||
this.setInputDir(inputDir);
|
||||
|
||||
this.rawImports = {};
|
||||
this.globalData = null;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap();
|
||||
this._extensionMap.config = this.config;
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
set extensionMap(map) {
|
||||
this._extensionMap = map;
|
||||
}
|
||||
|
||||
/* Used by tests */
|
||||
_setConfig(config) {
|
||||
this.config = config;
|
||||
this.dataTemplateEngine = this.config.dataTemplateEngine;
|
||||
}
|
||||
|
||||
setInputDir(inputDir) {
|
||||
this.inputDirNeedsCheck = true;
|
||||
this.inputDir = inputDir;
|
||||
this.dataDir = this.config.dir.data
|
||||
? TemplatePath.join(inputDir, this.config.dir.data)
|
||||
: inputDir;
|
||||
}
|
||||
|
||||
setDataTemplateEngine(engineName) {
|
||||
this.dataTemplateEngine = engineName;
|
||||
}
|
||||
|
||||
getRawImports() {
|
||||
let pkgPath = TemplatePath.absolutePath("package.json");
|
||||
|
||||
try {
|
||||
this.rawImports[this.config.keys.package] = require(pkgPath);
|
||||
} catch (e) {
|
||||
debug(
|
||||
"Could not find and/or require package.json for data preprocessing at %o",
|
||||
pkgPath
|
||||
);
|
||||
}
|
||||
|
||||
return this.rawImports;
|
||||
}
|
||||
|
||||
getDataDir() {
|
||||
return this.dataDir;
|
||||
}
|
||||
|
||||
clearData() {
|
||||
this.globalData = null;
|
||||
}
|
||||
|
||||
async cacheData() {
|
||||
this.clearData();
|
||||
|
||||
return this.getData();
|
||||
}
|
||||
|
||||
_getGlobalDataGlobByExtension(dir, extension) {
|
||||
return TemplateGlob.normalizePath(
|
||||
dir,
|
||||
"/",
|
||||
this.config.dir.data !== "." ? this.config.dir.data : "",
|
||||
`/**/*.${extension}`
|
||||
);
|
||||
}
|
||||
|
||||
async _checkInputDir() {
|
||||
if (this.inputDirNeedsCheck) {
|
||||
let globalPathStat = await fs.stat(this.inputDir);
|
||||
|
||||
if (!globalPathStat.isDirectory()) {
|
||||
throw new Error("Could not find data path directory: " + this.inputDir);
|
||||
}
|
||||
|
||||
this.inputDirNeedsCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
async getInputDir() {
|
||||
let dir = ".";
|
||||
|
||||
if (this.inputDir) {
|
||||
await this._checkInputDir();
|
||||
dir = this.inputDir;
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
async getTemplateDataFileGlob() {
|
||||
let dir = await this.getInputDir();
|
||||
let paths = [
|
||||
`${dir}/**/*.json`, // covers .11tydata.json too
|
||||
`${dir}/**/*${this.config.jsDataFileSuffix}.cjs`,
|
||||
`${dir}/**/*${this.config.jsDataFileSuffix}.js`
|
||||
];
|
||||
|
||||
if (this.hasUserDataExtensions()) {
|
||||
let userPaths = this.getUserDataExtensions().map(
|
||||
extension => `${dir}/**/*.${extension}` // covers .11tydata.{extension} too
|
||||
);
|
||||
paths = userPaths.concat(paths);
|
||||
}
|
||||
|
||||
return TemplatePath.addLeadingDotSlashArray(paths);
|
||||
}
|
||||
|
||||
async getTemplateJavaScriptDataFileGlob() {
|
||||
let dir = await this.getInputDir();
|
||||
return TemplatePath.addLeadingDotSlashArray([
|
||||
`${dir}/**/*${this.config.jsDataFileSuffix}.js`
|
||||
]);
|
||||
}
|
||||
|
||||
async getGlobalDataGlob() {
|
||||
let dir = await this.getInputDir();
|
||||
|
||||
let extGlob = this.getGlobalDataExtensionPriorities().join("|");
|
||||
return [this._getGlobalDataGlobByExtension(dir, "(" + extGlob + ")")];
|
||||
}
|
||||
|
||||
getWatchPathCache() {
|
||||
return this.pathCache;
|
||||
}
|
||||
|
||||
getGlobalDataExtensionPriorities() {
|
||||
return this.getUserDataExtensions().concat(["json", "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 = aggregateBench.get("Searching the file system");
|
||||
fsBench.before();
|
||||
let paths = await fastglob(await this.getGlobalDataGlob(), {
|
||||
caseSensitiveMatch: false,
|
||||
dot: true
|
||||
});
|
||||
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(path) {
|
||||
let reducedPath = TemplatePath.stripLeadingSubPath(path, this.dataDir);
|
||||
let parsed = parsePath(reducedPath);
|
||||
let folders = parsed.dir ? parsed.dir.split("/") : [];
|
||||
folders.push(parsed.name);
|
||||
|
||||
return folders.join(".");
|
||||
}
|
||||
|
||||
async getAllGlobalData() {
|
||||
let rawImports = this.getRawImports();
|
||||
let globalData = {};
|
||||
let files = TemplatePath.addLeadingDotSlashArray(
|
||||
await this.getGlobalDataFiles()
|
||||
);
|
||||
|
||||
let dataFileConflicts = {};
|
||||
|
||||
for (let j = 0, k = files.length; j < k; j++) {
|
||||
let objectPathTarget = await this.getObjectPathForDataFile(files[j]);
|
||||
let data = await this.getDataValue(files[j], rawImports);
|
||||
|
||||
// if two global files have the same path (but different extensions)
|
||||
// and conflict, let’s merge them.
|
||||
if (dataFileConflicts[objectPathTarget]) {
|
||||
debugWarn(
|
||||
`merging global data from ${files[j]} with an already existing global data file (${dataFileConflicts[objectPathTarget]}). Overriding existing keys.`
|
||||
);
|
||||
|
||||
let oldData = lodashget(globalData, objectPathTarget);
|
||||
data = TemplateData.mergeDeep(this.config, oldData, data);
|
||||
}
|
||||
|
||||
dataFileConflicts[objectPathTarget] = files[j];
|
||||
debug(
|
||||
`Found global data file ${files[j]} and adding as: ${objectPathTarget}`
|
||||
);
|
||||
lodashset(globalData, objectPathTarget, data);
|
||||
}
|
||||
|
||||
return globalData;
|
||||
}
|
||||
|
||||
async getData() {
|
||||
let rawImports = this.getRawImports();
|
||||
|
||||
if (!this.globalData) {
|
||||
let globalJson = await this.getAllGlobalData();
|
||||
|
||||
// OK: Shallow merge when combining rawImports (pkg) with global data files
|
||||
this.globalData = Object.assign({}, globalJson, rawImports);
|
||||
}
|
||||
|
||||
return this.globalData;
|
||||
}
|
||||
|
||||
/* Template and Directory data files */
|
||||
async combineLocalData(localDataPaths) {
|
||||
let localData = {};
|
||||
if (!Array.isArray(localDataPaths)) {
|
||||
localDataPaths = [localDataPaths];
|
||||
}
|
||||
for (let path of localDataPaths) {
|
||||
// clean up data for template/directory data files only.
|
||||
let dataForPath = await this.getDataValue(path, null, true);
|
||||
let cleanedDataForPath = TemplateData.cleanupData(dataForPath);
|
||||
TemplateData.mergeDeep(this.config, localData, cleanedDataForPath);
|
||||
// debug("`combineLocalData` (iterating) for %o: %O", path, localData);
|
||||
}
|
||||
return localData;
|
||||
}
|
||||
|
||||
async getLocalData(templatePath) {
|
||||
let localDataPaths = await this.getLocalDataPaths(templatePath);
|
||||
let importedData = await this.combineLocalData(localDataPaths);
|
||||
let globalData = await this.getData();
|
||||
|
||||
// OK-ish: shallow merge when combining template/data dir files with global data files
|
||||
let localData = Object.assign({}, globalData, importedData);
|
||||
// debug("`getLocalData` for %o: %O", templatePath, localData);
|
||||
return localData;
|
||||
}
|
||||
|
||||
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 _loadFileContents(path) {
|
||||
let rawInput;
|
||||
try {
|
||||
rawInput = await fs.readFile(path, "utf-8");
|
||||
} catch (e) {
|
||||
// if file does not exist, return nothing
|
||||
}
|
||||
return rawInput;
|
||||
}
|
||||
|
||||
async _parseDataFile(path, rawImports, ignoreProcessing, parser) {
|
||||
let rawInput = await this._loadFileContents(path);
|
||||
let engineName = this.dataTemplateEngine;
|
||||
|
||||
if (!rawInput) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (ignoreProcessing || engineName === false) {
|
||||
try {
|
||||
return parser(rawInput);
|
||||
} catch (e) {
|
||||
throw new TemplateDataParseError(
|
||||
`Having trouble parsing data file ${path}`,
|
||||
e
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let tr = new TemplateRender(engineName, this.inputDir);
|
||||
tr.extensionMap = this.extensionMap;
|
||||
|
||||
let fn = await tr.getCompiledTemplate(rawInput);
|
||||
|
||||
try {
|
||||
// pass in rawImports, don’t pass in global data, that’s what we’re parsing
|
||||
let raw = await fn(rawImports);
|
||||
return parser(raw);
|
||||
} catch (e) {
|
||||
throw new TemplateDataParseError(
|
||||
`Having trouble parsing data file ${path}`,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getDataValue(path, rawImports, ignoreProcessing) {
|
||||
let extension = TemplatePath.getExtension(path);
|
||||
|
||||
// ignoreProcessing = false for global data files
|
||||
// ignoreProcessing = true for local data files
|
||||
if (
|
||||
extension === "js" ||
|
||||
extension === "cjs" ||
|
||||
(extension === "json" && (ignoreProcessing || !this.dataTemplateEngine))
|
||||
) {
|
||||
// JS data file or require’d JSON (no preprocessing needed)
|
||||
let localPath = TemplatePath.absolutePath(path);
|
||||
if (!(await fs.pathExists(localPath))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let aggregateDataBench = aggregateBench.get("Data File");
|
||||
aggregateDataBench.before();
|
||||
let dataBench = bench.get(`\`${path}\``);
|
||||
dataBench.before();
|
||||
deleteRequireCache(localPath);
|
||||
|
||||
let returnValue = require(localPath);
|
||||
if (typeof returnValue === "function") {
|
||||
returnValue = await returnValue();
|
||||
}
|
||||
|
||||
dataBench.after();
|
||||
aggregateDataBench.after();
|
||||
return returnValue;
|
||||
} else if (this.isUserDataExtension(extension)) {
|
||||
// Other extensions
|
||||
var parser = this.getUserDataParser(extension);
|
||||
return this._parseDataFile(path, rawImports, ignoreProcessing, parser);
|
||||
} else if (extension === "json") {
|
||||
// File to string, parse with JSON (preprocess)
|
||||
return this._parseDataFile(
|
||||
path,
|
||||
rawImports,
|
||||
ignoreProcessing,
|
||||
JSON.parse
|
||||
);
|
||||
} 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) {
|
||||
let dataSuffix = this.config.jsDataFileSuffix;
|
||||
|
||||
// data suffix
|
||||
paths.push(base + dataSuffix + ".js");
|
||||
paths.push(base + dataSuffix + ".cjs");
|
||||
paths.push(base + dataSuffix + ".json");
|
||||
|
||||
// inject user extensions
|
||||
this._pushExtensionsToPaths(paths, base + dataSuffix, extensions);
|
||||
|
||||
// top level
|
||||
paths.push(base + ".json");
|
||||
this._pushExtensionsToPaths(paths, base, extensions);
|
||||
}
|
||||
|
||||
async getLocalDataPaths(templatePath) {
|
||||
let paths = [];
|
||||
let parsed = parsePath(templatePath);
|
||||
let inputDir = TemplatePath.addLeadingDotSlash(
|
||||
TemplatePath.normalize(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
|
||||
);
|
||||
|
||||
let filePathNoExt = parsed.dir + "/" + fileNameNoExt;
|
||||
let dataSuffix = this.config.jsDataFileSuffix;
|
||||
debug("Using %o to find data files.", dataSuffix);
|
||||
|
||||
this._addBaseToPaths(paths, filePathNoExt, userExtensions);
|
||||
|
||||
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.indexOf(inputDir) === 0 && dir !== inputDir)) {
|
||||
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))
|
||||
);
|
||||
if (lastInputDir !== "./") {
|
||||
this._addBaseToPaths(paths, lastInputDir, userExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug("getLocalDataPaths(%o): %o", templatePath, paths);
|
||||
return lodashUniq(paths).reverse();
|
||||
}
|
||||
|
||||
static mergeDeep(config, target, ...source) {
|
||||
if (config.dataDeepMerge) {
|
||||
return TemplateData.merge(target, ...source);
|
||||
} else {
|
||||
return Object.assign(target, ...source);
|
||||
}
|
||||
}
|
||||
|
||||
static merge(target, ...source) {
|
||||
return merge(target, ...source);
|
||||
}
|
||||
|
||||
static cleanupData(data) {
|
||||
if ("tags" in data) {
|
||||
if (typeof data.tags === "string") {
|
||||
data.tags = data.tags ? [data.tags] : [];
|
||||
} else if (data.tags === null) {
|
||||
data.tags = [];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TemplateData;
|
|
@ -0,0 +1,79 @@
|
|||
const config = require("./Config");
|
||||
|
||||
class TemplateEngineManager {
|
||||
constructor() {
|
||||
this.engineCache = {};
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (!this._config) {
|
||||
this._config = config.getConfig();
|
||||
}
|
||||
return this._config;
|
||||
}
|
||||
|
||||
set config(cfg) {
|
||||
this._config = cfg;
|
||||
}
|
||||
|
||||
get keyToClassNameMap() {
|
||||
if (!this._keyToClassNameMap) {
|
||||
this._keyToClassNameMap = {
|
||||
ejs: "Ejs",
|
||||
md: "Markdown",
|
||||
jstl: "JavaScriptTemplateLiteral",
|
||||
html: "Html",
|
||||
hbs: "Handlebars",
|
||||
mustache: "Mustache",
|
||||
haml: "Haml",
|
||||
pug: "Pug",
|
||||
njk: "Nunjucks",
|
||||
liquid: "Liquid",
|
||||
"11ty.js": "JavaScript",
|
||||
};
|
||||
|
||||
if ("extensionMap" in this.config) {
|
||||
for (let entry of this.config.extensionMap) {
|
||||
this._keyToClassNameMap[entry.key] = "Custom";
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._keyToClassNameMap;
|
||||
}
|
||||
|
||||
getClassNameFromTemplateKey(key) {
|
||||
let keys = this.keyToClassNameMap;
|
||||
|
||||
return keys[key];
|
||||
}
|
||||
|
||||
hasEngine(name) {
|
||||
return !!this.getClassNameFromTemplateKey(name);
|
||||
}
|
||||
|
||||
getEngine(name, includesDir, extensionMap) {
|
||||
if (!this.hasEngine(name)) {
|
||||
throw new Error(
|
||||
`Template Engine ${name} does not exist in getEngine (includes dir: ${includesDir})`
|
||||
);
|
||||
}
|
||||
|
||||
if (this.engineCache[name]) {
|
||||
return this.engineCache[name];
|
||||
}
|
||||
|
||||
let path = "./Engines/" + this.getClassNameFromTemplateKey(name);
|
||||
const cls = require(path);
|
||||
let instance = new cls(name, includesDir);
|
||||
instance.extensionMap = extensionMap;
|
||||
instance.config = this.config;
|
||||
instance.engineManager = this;
|
||||
|
||||
// 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] = instance;
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TemplateEngineManager;
|
|
@ -0,0 +1,45 @@
|
|||
const parsePath = require("parse-filepath");
|
||||
const TemplatePath = require("./TemplatePath");
|
||||
|
||||
class TemplateFileSlug {
|
||||
constructor(inputPath, inputDir, extensionMap) {
|
||||
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 = parsePath(inputPath);
|
||||
this.filenameNoExt = extensionMap.removeTemplateExtension(this.parsed.base);
|
||||
}
|
||||
|
||||
getFullPathWithoutExtension() {
|
||||
return "/" + TemplatePath.join(...this.dirs, this._getRawSlug());
|
||||
}
|
||||
|
||||
_getRawSlug() {
|
||||
let slug = this.filenameNoExt;
|
||||
let reg = slug.match(/\d{4}-\d{2}-\d{2}-(.*)/);
|
||||
if (reg) {
|
||||
return reg[1];
|
||||
}
|
||||
return slug;
|
||||
}
|
||||
|
||||
getSlug() {
|
||||
let rawSlug = this._getRawSlug();
|
||||
|
||||
if (rawSlug === "index") {
|
||||
return this.dirs.length ? this.dirs[this.dirs.length - 1] : "";
|
||||
}
|
||||
|
||||
return rawSlug;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TemplateFileSlug;
|
|
@ -0,0 +1,37 @@
|
|||
const TemplatePath = require("./TemplatePath");
|
||||
|
||||
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.substr(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TemplateGlob;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue