devsite/node_modules/@11ty/eleventy/src/Serverless.js
2024-07-07 18:49:38 -07:00

253 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const path = require("path");
const fs = require("fs");
const { match } = require("path-to-regexp");
const { TemplatePath } = require("@11ty/eleventy-utils");
const Eleventy = require("./Eleventy");
const normalizeServerlessUrl = require("./Util/NormalizeServerlessUrl");
const deleteRequireCache = require("./Util/DeleteRequireCache");
const debug = require("debug")("Eleventy:Serverless");
class Serverless {
constructor(name, path, options = {}) {
this.name = name;
// second argument is path
if (typeof path === "string") {
this.path = path;
} else {
// options is the second argument and path is inside options
options = path;
this.path = options.path;
}
if (!this.path) {
throw new Error("`path` must exist in the options argument in Eleventy Serverless.");
}
// ServerlessBundlerPlugin hard-codes to this (even if you used a different file name)
this.configFilename = "eleventy.config.js";
// Configuration Information
this.configInfoFilename = "eleventy-serverless.json";
// Maps input files to eligible serverless URLs
this.mapFilename = "eleventy-serverless-map.json";
this.options = Object.assign(
{
inputDir: null, // override only, we now inject this.
functionsDir: "functions/",
matchUrlToPattern(path, urlToCompare) {
urlToCompare = normalizeServerlessUrl(urlToCompare);
let fn = match(urlToCompare, { decode: decodeURIComponent });
return fn(path);
},
// Query String Parameters
query: {},
// Configuration callback
config: function (eleventyConfig) {},
// Is serverless build scoped to a single template?
// Use `false` to make serverless more collections-friendly (but slower!)
// With `false` you dont need precompiledCollections.
// Works great with on-demand builders
singleTemplateScope: true,
// Inject shared collections
precompiledCollections: {},
},
options
);
this.dir = this.getProjectDir();
}
initializeEnvironmentVariables() {
this.serverlessEnvironmentVariableAlreadySet = !!process.env.ELEVENTY_SERVERLESS;
}
deleteEnvironmentVariables() {
if (!this.serverlessEnvironmentVariableAlreadySet) {
delete process.env.ELEVENTY_SERVERLESS;
}
}
getProjectDir() {
// TODO? improve with process.env.LAMBDA_TASK_ROOT—was `/var/task/` on lambda (not local)
let dir = path.join(this.options.functionsDir, this.name);
let paths = [
path.join(TemplatePath.getWorkingDir(), dir), // netlify dev
path.join("/var/task/src/", dir), // AWS Lambda absolute path
path.join(TemplatePath.getWorkingDir()), // after the chdir below
];
for (let path of paths) {
if (fs.existsSync(path)) {
return path;
}
}
throw new Error(`Couldnt find the "${dir}" directory. Looked in: ${paths}`);
}
getContentMap() {
let fullPath = TemplatePath.absolutePath(this.dir, this.mapFilename);
debug(`Including content map (maps output URLs to input files) from ${fullPath}`);
// TODO dedicated reset method, dont delete this every time
deleteRequireCache(fullPath);
return require(fullPath);
}
getConfigInfo() {
let fullPath = TemplatePath.absolutePath(this.dir, this.configInfoFilename);
debug(`Including config info file from ${fullPath}`);
// TODO dedicated reset method, dont delete this every time
deleteRequireCache(fullPath);
return require(fullPath);
}
isServerlessUrl(urlPath) {
let contentMap = this.getContentMap();
for (let url in contentMap) {
if (this.options.matchUrlToPattern(urlPath, url)) {
return true;
}
}
return false;
}
matchUrlPattern(urlPath) {
let contentMap = this.getContentMap();
let matches = [];
for (let url in contentMap) {
let result = this.options.matchUrlToPattern(urlPath, url);
if (result) {
matches.push({
compareTo: url,
pathParams: result.params,
inputPath: contentMap[url],
});
}
}
if (matches.length) {
if (matches.length > 1) {
console.log(
`Eleventy Serverless conflict: there are multiple serverless paths that match the current URL (${urlPath}): ${JSON.stringify(
matches,
null,
2
)}`
);
}
return matches[0];
}
return {};
}
async getOutput() {
if (this.dir.startsWith("/var/task/")) {
process.chdir(this.dir);
}
let inputDir = this.options.input || this.options.inputDir || this.getConfigInfo().dir.input;
let configPath = path.join(this.dir, this.configFilename);
let { pathParams, inputPath } = this.matchUrlPattern(this.path);
if (!pathParams || !inputPath) {
let err = new Error(
`No matching URL found for ${this.path} in ${JSON.stringify(this.getContentMap())}`
);
err.httpStatusCode = 404;
throw err;
}
debug(`Current dir: ${process.cwd()}`);
debug(`Project dir: ${this.dir}`);
debug(`Config path: ${configPath}`);
debug(`Input dir: ${inputDir}`);
debug(`Requested URL: ${this.path}`);
debug("Path params: %o", pathParams);
debug(`Input path: ${inputPath}`);
this.initializeEnvironmentVariables();
let isScoped = !!this.options.singleTemplateScope;
let projectInput = isScoped ? this.options.input || inputPath : inputDir;
let elev = new Eleventy(projectInput, null, {
// https://github.com/11ty/eleventy/issues/1957
isServerless: true,
configPath,
inputDir,
config: (eleventyConfig) => {
if (Object.keys(this.options.precompiledCollections).length > 0) {
eleventyConfig.setPrecompiledCollections(this.options.precompiledCollections);
}
// Add the params to Global Data
let globalData = {
query: this.options.query,
path: pathParams,
};
eleventyConfig.addGlobalData("eleventy.serverless", globalData);
if (this.options.config && typeof this.options.config === "function") {
this.options.config(eleventyConfig);
}
},
});
if (!isScoped) {
elev.setIncrementalFile(this.options.input || inputPath);
}
let json = await elev.toJSON();
// https://github.com/11ty/eleventy/issues/1957
this.deleteEnvironmentVariables();
let filtered = [];
if (Array.isArray(json)) {
filtered = json.filter((entry) => {
return entry.inputPath === inputPath;
});
}
if (!filtered.length) {
let err = new Error(
`Couldnt find any generated output from Eleventy (Input path: ${inputPath}, URL path parameters: ${JSON.stringify(
pathParams
)}).`
);
err.httpStatusCode = 404;
throw err;
}
return filtered;
}
/* Deprecated, use `getOutput` directly instead. */
async render() {
let json = await this.getOutput();
return json[0].content;
}
}
module.exports = Serverless;