@netlify/plugin-nextjs
Version:
Run Next.js seamlessly on Netlify
658 lines (648 loc) • 24.9 kB
JavaScript
var require = await (async () => {
var { createRequire } = await import("node:module");
return createRequire(import.meta.url);
})();
import {
require_out
} from "../../esm-chunks/chunk-YUXQHOYO.js";
import {
__commonJS,
__toESM
} from "../../esm-chunks/chunk-6BT4RYQJ.js";
// node_modules/path-to-regexp/dist/index.js
var require_dist = __commonJS({
"node_modules/path-to-regexp/dist/index.js"(exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.pathToRegexp = exports.tokensToRegexp = exports.regexpToFunction = exports.match = exports.tokensToFunction = exports.compile = exports.parse = void 0;
function lexer(str) {
var tokens = [];
var i = 0;
while (i < str.length) {
var char = str[i];
if (char === "*" || char === "+" || char === "?") {
tokens.push({ type: "MODIFIER", index: i, value: str[i++] });
continue;
}
if (char === "\\") {
tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] });
continue;
}
if (char === "{") {
tokens.push({ type: "OPEN", index: i, value: str[i++] });
continue;
}
if (char === "}") {
tokens.push({ type: "CLOSE", index: i, value: str[i++] });
continue;
}
if (char === ":") {
var name = "";
var j = i + 1;
while (j < str.length) {
var code = str.charCodeAt(j);
if (
// `0-9`
code >= 48 && code <= 57 || // `A-Z`
code >= 65 && code <= 90 || // `a-z`
code >= 97 && code <= 122 || // `_`
code === 95
) {
name += str[j++];
continue;
}
break;
}
if (!name)
throw new TypeError("Missing parameter name at ".concat(i));
tokens.push({ type: "NAME", index: i, value: name });
i = j;
continue;
}
if (char === "(") {
var count = 1;
var pattern = "";
var j = i + 1;
if (str[j] === "?") {
throw new TypeError('Pattern cannot start with "?" at '.concat(j));
}
while (j < str.length) {
if (str[j] === "\\") {
pattern += str[j++] + str[j++];
continue;
}
if (str[j] === ")") {
count--;
if (count === 0) {
j++;
break;
}
} else if (str[j] === "(") {
count++;
if (str[j + 1] !== "?") {
throw new TypeError("Capturing groups are not allowed at ".concat(j));
}
}
pattern += str[j++];
}
if (count)
throw new TypeError("Unbalanced pattern at ".concat(i));
if (!pattern)
throw new TypeError("Missing pattern at ".concat(i));
tokens.push({ type: "PATTERN", index: i, value: pattern });
i = j;
continue;
}
tokens.push({ type: "CHAR", index: i, value: str[i++] });
}
tokens.push({ type: "END", index: i, value: "" });
return tokens;
}
function parse(str, options) {
if (options === void 0) {
options = {};
}
var tokens = lexer(str);
var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a, _b = options.delimiter, delimiter = _b === void 0 ? "/#?" : _b;
var result = [];
var key = 0;
var i = 0;
var path = "";
var tryConsume = function(type) {
if (i < tokens.length && tokens[i].type === type)
return tokens[i++].value;
};
var mustConsume = function(type) {
var value2 = tryConsume(type);
if (value2 !== void 0)
return value2;
var _a2 = tokens[i], nextType = _a2.type, index = _a2.index;
throw new TypeError("Unexpected ".concat(nextType, " at ").concat(index, ", expected ").concat(type));
};
var consumeText = function() {
var result2 = "";
var value2;
while (value2 = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR")) {
result2 += value2;
}
return result2;
};
var isSafe = function(value2) {
for (var _i = 0, delimiter_1 = delimiter; _i < delimiter_1.length; _i++) {
var char2 = delimiter_1[_i];
if (value2.indexOf(char2) > -1)
return true;
}
return false;
};
var safePattern = function(prefix2) {
var prev = result[result.length - 1];
var prevText = prefix2 || (prev && typeof prev === "string" ? prev : "");
if (prev && !prevText) {
throw new TypeError('Must have text between two parameters, missing text after "'.concat(prev.name, '"'));
}
if (!prevText || isSafe(prevText))
return "[^".concat(escapeString(delimiter), "]+?");
return "(?:(?!".concat(escapeString(prevText), ")[^").concat(escapeString(delimiter), "])+?");
};
while (i < tokens.length) {
var char = tryConsume("CHAR");
var name = tryConsume("NAME");
var pattern = tryConsume("PATTERN");
if (name || pattern) {
var prefix = char || "";
if (prefixes.indexOf(prefix) === -1) {
path += prefix;
prefix = "";
}
if (path) {
result.push(path);
path = "";
}
result.push({
name: name || key++,
prefix,
suffix: "",
pattern: pattern || safePattern(prefix),
modifier: tryConsume("MODIFIER") || ""
});
continue;
}
var value = char || tryConsume("ESCAPED_CHAR");
if (value) {
path += value;
continue;
}
if (path) {
result.push(path);
path = "";
}
var open = tryConsume("OPEN");
if (open) {
var prefix = consumeText();
var name_1 = tryConsume("NAME") || "";
var pattern_1 = tryConsume("PATTERN") || "";
var suffix = consumeText();
mustConsume("CLOSE");
result.push({
name: name_1 || (pattern_1 ? key++ : ""),
pattern: name_1 && !pattern_1 ? safePattern(prefix) : pattern_1,
prefix,
suffix,
modifier: tryConsume("MODIFIER") || ""
});
continue;
}
mustConsume("END");
}
return result;
}
exports.parse = parse;
function compile(str, options) {
return tokensToFunction(parse(str, options), options);
}
exports.compile = compile;
function tokensToFunction(tokens, options) {
if (options === void 0) {
options = {};
}
var reFlags = flags(options);
var _a = options.encode, encode = _a === void 0 ? function(x) {
return x;
} : _a, _b = options.validate, validate = _b === void 0 ? true : _b;
var matches = tokens.map(function(token) {
if (typeof token === "object") {
return new RegExp("^(?:".concat(token.pattern, ")$"), reFlags);
}
});
return function(data) {
var path = "";
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (typeof token === "string") {
path += token;
continue;
}
var value = data ? data[token.name] : void 0;
var optional = token.modifier === "?" || token.modifier === "*";
var repeat = token.modifier === "*" || token.modifier === "+";
if (Array.isArray(value)) {
if (!repeat) {
throw new TypeError('Expected "'.concat(token.name, '" to not repeat, but got an array'));
}
if (value.length === 0) {
if (optional)
continue;
throw new TypeError('Expected "'.concat(token.name, '" to not be empty'));
}
for (var j = 0; j < value.length; j++) {
var segment = encode(value[j], token);
if (validate && !matches[i].test(segment)) {
throw new TypeError('Expected all "'.concat(token.name, '" to match "').concat(token.pattern, '", but got "').concat(segment, '"'));
}
path += token.prefix + segment + token.suffix;
}
continue;
}
if (typeof value === "string" || typeof value === "number") {
var segment = encode(String(value), token);
if (validate && !matches[i].test(segment)) {
throw new TypeError('Expected "'.concat(token.name, '" to match "').concat(token.pattern, '", but got "').concat(segment, '"'));
}
path += token.prefix + segment + token.suffix;
continue;
}
if (optional)
continue;
var typeOfMessage = repeat ? "an array" : "a string";
throw new TypeError('Expected "'.concat(token.name, '" to be ').concat(typeOfMessage));
}
return path;
};
}
exports.tokensToFunction = tokensToFunction;
function match(str, options) {
var keys = [];
var re = pathToRegexp2(str, keys, options);
return regexpToFunction(re, keys, options);
}
exports.match = match;
function regexpToFunction(re, keys, options) {
if (options === void 0) {
options = {};
}
var _a = options.decode, decode = _a === void 0 ? function(x) {
return x;
} : _a;
return function(pathname) {
var m = re.exec(pathname);
if (!m)
return false;
var path = m[0], index = m.index;
var params = /* @__PURE__ */ Object.create(null);
var _loop_1 = function(i2) {
if (m[i2] === void 0)
return "continue";
var key = keys[i2 - 1];
if (key.modifier === "*" || key.modifier === "+") {
params[key.name] = m[i2].split(key.prefix + key.suffix).map(function(value) {
return decode(value, key);
});
} else {
params[key.name] = decode(m[i2], key);
}
};
for (var i = 1; i < m.length; i++) {
_loop_1(i);
}
return { path, index, params };
};
}
exports.regexpToFunction = regexpToFunction;
function escapeString(str) {
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
}
function flags(options) {
return options && options.sensitive ? "" : "i";
}
function regexpToRegexp(path, keys) {
if (!keys)
return path;
var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g;
var index = 0;
var execResult = groupsRegex.exec(path.source);
while (execResult) {
keys.push({
// Use parenthesized substring match if available, index otherwise
name: execResult[1] || index++,
prefix: "",
suffix: "",
modifier: "",
pattern: ""
});
execResult = groupsRegex.exec(path.source);
}
return path;
}
function arrayToRegexp(paths, keys, options) {
var parts = paths.map(function(path) {
return pathToRegexp2(path, keys, options).source;
});
return new RegExp("(?:".concat(parts.join("|"), ")"), flags(options));
}
function stringToRegexp(path, keys, options) {
return tokensToRegexp(parse(path, options), keys, options);
}
function tokensToRegexp(tokens, keys, options) {
if (options === void 0) {
options = {};
}
var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function(x) {
return x;
} : _d, _e = options.delimiter, delimiter = _e === void 0 ? "/#?" : _e, _f = options.endsWith, endsWith = _f === void 0 ? "" : _f;
var endsWithRe = "[".concat(escapeString(endsWith), "]|$");
var delimiterRe = "[".concat(escapeString(delimiter), "]");
var route = start ? "^" : "";
for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) {
var token = tokens_1[_i];
if (typeof token === "string") {
route += escapeString(encode(token));
} else {
var prefix = escapeString(encode(token.prefix));
var suffix = escapeString(encode(token.suffix));
if (token.pattern) {
if (keys)
keys.push(token);
if (prefix || suffix) {
if (token.modifier === "+" || token.modifier === "*") {
var mod = token.modifier === "*" ? "?" : "";
route += "(?:".concat(prefix, "((?:").concat(token.pattern, ")(?:").concat(suffix).concat(prefix, "(?:").concat(token.pattern, "))*)").concat(suffix, ")").concat(mod);
} else {
route += "(?:".concat(prefix, "(").concat(token.pattern, ")").concat(suffix, ")").concat(token.modifier);
}
} else {
if (token.modifier === "+" || token.modifier === "*") {
throw new TypeError('Can not repeat "'.concat(token.name, '" without a prefix and suffix'));
}
route += "(".concat(token.pattern, ")").concat(token.modifier);
}
} else {
route += "(?:".concat(prefix).concat(suffix, ")").concat(token.modifier);
}
}
}
if (end) {
if (!strict)
route += "".concat(delimiterRe, "?");
route += !options.endsWith ? "$" : "(?=".concat(endsWithRe, ")");
} else {
var endToken = tokens[tokens.length - 1];
var isEndDelimited = typeof endToken === "string" ? delimiterRe.indexOf(endToken[endToken.length - 1]) > -1 : endToken === void 0;
if (!strict) {
route += "(?:".concat(delimiterRe, "(?=").concat(endsWithRe, "))?");
}
if (!isEndDelimited) {
route += "(?=".concat(delimiterRe, "|").concat(endsWithRe, ")");
}
}
return new RegExp(route, flags(options));
}
exports.tokensToRegexp = tokensToRegexp;
function pathToRegexp2(path, keys, options) {
if (path instanceof RegExp)
return regexpToRegexp(path, keys);
if (Array.isArray(path))
return arrayToRegexp(path, keys, options);
return stringToRegexp(path, keys, options);
}
exports.pathToRegexp = pathToRegexp2;
}
});
// src/build/functions/edge.ts
var import_fast_glob = __toESM(require_out(), 1);
var import_path_to_regexp = __toESM(require_dist(), 1);
import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
import { dirname, join, relative } from "node:path/posix";
import { EDGE_HANDLER_NAME } from "../plugin-context.js";
function nodeMiddlewareDefinitionHasMatcher(definition) {
return Array.isArray(definition.matchers);
}
var writeEdgeManifest = async (ctx, manifest) => {
await mkdir(ctx.edgeFunctionsDir, { recursive: true });
await writeFile(join(ctx.edgeFunctionsDir, "manifest.json"), JSON.stringify(manifest, null, 2));
};
var copyRuntime = async (ctx, handlerDirectory) => {
const files = await (0, import_fast_glob.glob)("edge-runtime/**/*", {
cwd: ctx.pluginDir,
ignore: ["**/*.test.ts"],
dot: true
});
await Promise.all(
files.map(
(path) => cp(join(ctx.pluginDir, path), join(handlerDirectory, path), { recursive: true })
)
);
};
var augmentMatchers = (matchers, ctx) => {
const i18NConfig = ctx.buildConfig.i18n;
if (!i18NConfig) {
return matchers;
}
return matchers.flatMap((matcher) => {
if (matcher.originalSource && matcher.locale !== false) {
return [
matcher.regexp ? {
...matcher,
// https://github.com/vercel/next.js/blob/5e236c9909a768dc93856fdfad53d4f4adc2db99/packages/next/src/build/analysis/get-page-static-info.ts#L332-L336
// Next is producing pretty broad matcher for i18n locale. Presumably rest of their infrastructure protects this broad matcher
// from matching on non-locale paths. For us this becomes request entry point, so we need to narrow it down to just defined locales
// otherwise users might get unexpected matches on paths like `/api*`
regexp: matcher.regexp.replace(/\[\^\/\.]+/g, `(${i18NConfig.locales.join("|")})`)
} : matcher,
{
...matcher,
regexp: (0, import_path_to_regexp.pathToRegexp)(matcher.originalSource).source
}
];
}
return matcher;
});
};
var writeHandlerFile = async (ctx, { matchers, name }) => {
const nextConfig = ctx.buildConfig;
const handlerName = getHandlerName({ name });
const handlerDirectory = join(ctx.edgeFunctionsDir, handlerName);
const handlerRuntimeDirectory = join(handlerDirectory, "edge-runtime");
await copyRuntime(ctx, handlerDirectory);
await writeFile(join(handlerRuntimeDirectory, "matchers.json"), JSON.stringify(matchers));
const minimalNextConfig = {
basePath: nextConfig.basePath,
i18n: nextConfig.i18n,
trailingSlash: nextConfig.trailingSlash,
skipMiddlewareUrlNormalize: nextConfig.skipProxyUrlNormalize ?? nextConfig.skipMiddlewareUrlNormalize
};
await writeFile(
join(handlerRuntimeDirectory, "next.config.json"),
JSON.stringify(minimalNextConfig)
);
const htmlRewriterWasm = await readFile(
join(
ctx.pluginDir,
"edge-runtime/vendor/deno.land/x/htmlrewriter@v1.0.0/pkg/htmlrewriter_bg.wasm"
)
);
await writeFile(
join(handlerDirectory, `${handlerName}.js`),
`
import { init as htmlRewriterInit } from './edge-runtime/vendor/deno.land/x/htmlrewriter@v1.0.0/src/index.ts'
import { handleMiddleware } from './edge-runtime/middleware.ts';
import handler from './server/${name}.js';
await htmlRewriterInit({ module_or_path: Uint8Array.from(${JSON.stringify([
...htmlRewriterWasm
])}) });
export default (req, context) => handleMiddleware(req, context, handler);
`
);
};
var copyHandlerDependenciesForEdgeMiddleware = async (ctx, { name, env, files, wasm }) => {
const srcDir = join(ctx.standaloneDir, ctx.nextDistDir);
const destDir = join(ctx.edgeFunctionsDir, getHandlerName({ name }));
const edgeRuntimeDir = join(ctx.pluginDir, "edge-runtime");
const shimPath = join(edgeRuntimeDir, "shim/edge.js");
const shim = await readFile(shimPath, "utf8");
const parts = [shim];
const outputFile = join(destDir, `server/${name}.js`);
if (env) {
for (const [key, value] of Object.entries(env)) {
parts.push(`process.env.${key} = '${value}';`);
}
}
if (wasm?.length) {
for (const wasmChunk of wasm ?? []) {
const data = await readFile(join(srcDir, wasmChunk.filePath));
parts.push(`const ${wasmChunk.name} = Uint8Array.from(${JSON.stringify([...data])})`);
}
}
for (const file of files) {
const entrypoint = await readFile(join(srcDir, file), "utf8");
parts.push(`;// Concatenated file: ${file}
`, entrypoint);
}
parts.push(
`const middlewareEntryKey = Object.keys(_ENTRIES).find(entryKey => entryKey.startsWith("middleware_${name}"));`,
// turbopack entries are promises so we await here to get actual entry
// non-turbopack entries are already resolved, so await does not change anything
`export default await _ENTRIES[middlewareEntryKey].default;`
);
await mkdir(dirname(outputFile), { recursive: true });
await writeFile(outputFile, parts.join("\n"));
};
var NODE_MIDDLEWARE_NAME = "node-middleware";
var copyHandlerDependenciesForNodeMiddleware = async (ctx) => {
const name = NODE_MIDDLEWARE_NAME;
const srcDir = join(ctx.standaloneDir, ctx.nextDistDir);
const destDir = join(ctx.edgeFunctionsDir, getHandlerName({ name }));
const edgeRuntimeDir = join(ctx.pluginDir, "edge-runtime");
const shimPath = join(edgeRuntimeDir, "shim/node.js");
const shim = await readFile(shimPath, "utf8");
const parts = [shim];
const entry = "server/middleware.js";
const nft = `${entry}.nft.json`;
const nftFilesPath = join(ctx.publishDir, nft);
const nftManifest = JSON.parse(await readFile(nftFilesPath, "utf8"));
const files = nftManifest.files.map((file) => join("server", file));
files.push(entry);
const { maxParentDirectoriesPath, unsupportedDotNodeModules } = files.reduce(
(acc, file) => {
let dirsUp = 0;
let parentDirectoriesPath = "";
for (const part of file.split("/")) {
if (part === "..") {
dirsUp += 1;
parentDirectoriesPath += "../";
} else {
break;
}
}
if (file.endsWith(".node")) {
acc.unsupportedDotNodeModules.push(join(srcDir, file));
}
if (dirsUp > acc.maxDirsUp) {
return {
...acc,
maxDirsUp: dirsUp,
maxParentDirectoriesPath: parentDirectoriesPath
};
}
return acc;
},
{ maxDirsUp: 0, maxParentDirectoriesPath: "", unsupportedDotNodeModules: [] }
);
if (unsupportedDotNodeModules.length !== 0) {
throw new Error(
`Usage of unsupported C++ Addon(s) found in Node.js Middleware:
${unsupportedDotNodeModules.map((file) => `- ${file}`).join("\n")}
Check https://docs.netlify.com/build/frameworks/framework-setup-guides/nextjs/overview/#limitations for more information.`
);
}
const commonPrefix = relative(join(srcDir, maxParentDirectoriesPath), srcDir);
parts.push(`const virtualModules = new Map();`);
const handleFileOrDirectory = async (fileOrDir) => {
const srcPath = join(srcDir, fileOrDir);
const stats = await stat(srcPath);
if (stats.isDirectory()) {
const filesInDir = await readdir(srcPath);
for (const fileInDir of filesInDir) {
await handleFileOrDirectory(join(fileOrDir, fileInDir));
}
} else {
const content = await readFile(srcPath, "utf8");
parts.push(
`virtualModules.set(${JSON.stringify(join(commonPrefix, fileOrDir))}, ${JSON.stringify(content)});`
);
}
};
for (const file of files) {
await handleFileOrDirectory(file);
}
parts.push(`registerCJSModules(import.meta.url, virtualModules);
const require = createRequire(import.meta.url);
const handlerMod = require("./${join(commonPrefix, entry)}");
const handler = handlerMod.default || handlerMod;
export default handler
`);
const outputFile = join(destDir, `server/${name}.js`);
await mkdir(dirname(outputFile), { recursive: true });
await writeFile(outputFile, parts.join("\n"));
};
var createEdgeHandler = async (ctx, definition) => {
await (definition.runtime === "edge" ? copyHandlerDependenciesForEdgeMiddleware(ctx, definition.functionDefinition) : copyHandlerDependenciesForNodeMiddleware(ctx));
await writeHandlerFile(ctx, definition);
};
var getHandlerName = ({ name }) => `${EDGE_HANDLER_NAME}-${name.replace(/\W/g, "-")}`;
var buildHandlerDefinition = (ctx, def) => {
return augmentMatchers(def.matchers, ctx).map((matcher) => ({
function: getHandlerName({ name: def.name }),
name: "Next.js Middleware Handler",
pattern: matcher.regexp,
generator: `${ctx.pluginName}@${ctx.pluginVersion}`
}));
};
var clearStaleEdgeHandlers = async (ctx) => {
await rm(ctx.edgeFunctionsDir, { recursive: true, force: true });
};
var createEdgeHandlers = async (ctx) => {
const nextManifest = await ctx.getMiddlewareManifest();
const middlewareDefinitions = [
...Object.values(nextManifest.middleware)
].map((edgeDefinition) => {
return {
runtime: "edge",
functionDefinition: edgeDefinition,
name: edgeDefinition.name,
matchers: edgeDefinition.matchers
};
});
const functionsConfigManifest = await ctx.getFunctionsConfigManifest();
if (functionsConfigManifest?.functions?.["/_middleware"] && nodeMiddlewareDefinitionHasMatcher(functionsConfigManifest?.functions?.["/_middleware"])) {
middlewareDefinitions.push({
runtime: "nodejs",
functionDefinition: functionsConfigManifest?.functions?.["/_middleware"],
name: NODE_MIDDLEWARE_NAME,
matchers: functionsConfigManifest?.functions?.["/_middleware"]?.matchers
});
}
await Promise.all(middlewareDefinitions.map((def) => createEdgeHandler(ctx, def)));
const netlifyDefinitions = middlewareDefinitions.flatMap(
(def) => buildHandlerDefinition(ctx, def)
);
const netlifyManifest = {
version: 1,
functions: netlifyDefinitions
};
await writeEdgeManifest(ctx, netlifyManifest);
};
export {
clearStaleEdgeHandlers,
createEdgeHandlers
};