devsite/node_modules/@11ty/eleventy/src/Serverless.js

253 lines
7.0 KiB
JavaScript
Raw Normal View History

2024-07-08 01:49:38 +00:00
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;