No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.getDevModeHandle = exports.ɵcodegenFunctionsDirectory = exports.ɵcodegenPublicDirectory = exports.init = exports.build = exports.discover = exports.type = exports.support = exports.name = void 0;
  4. const child_process_1 = require("child_process");
  5. const promises_1 = require("fs/promises");
  6. const path_1 = require("path");
  7. const fs_extra_1 = require("fs-extra");
  8. const url_1 = require("url");
  9. const fs_1 = require("fs");
  10. const semver_1 = require("semver");
  11. const clc = require("colorette");
  12. const __1 = require("..");
  13. const prompt_1 = require("../../prompt");
  14. const error_1 = require("../../error");
  15. const utils_1 = require("./utils");
  16. const utils_2 = require("../utils");
  17. const utils_3 = require("../utils");
  18. const utils_4 = require("./utils");
  19. const constants_1 = require("./constants");
  20. const DEFAULT_BUILD_SCRIPT = ["next build"];
  21. const PUBLIC_DIR = "public";
  22. exports.name = "Next.js";
  23. exports.support = "experimental";
  24. exports.type = 2;
  25. const DEFAULT_NUMBER_OF_REASONS_TO_LIST = 5;
  26. function getNextVersion(cwd) {
  27. var _a;
  28. return (_a = (0, __1.findDependency)("next", { cwd, depth: 0, omitDev: false })) === null || _a === void 0 ? void 0 : _a.version;
  29. }
  30. function getReactVersion(cwd) {
  31. var _a;
  32. return (_a = (0, __1.findDependency)("react-dom", { cwd, omitDev: false })) === null || _a === void 0 ? void 0 : _a.version;
  33. }
  34. async function discover(dir) {
  35. if (!(await (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "package.json"))))
  36. return;
  37. if (!(await (0, fs_extra_1.pathExists)("next.config.js")) && !getNextVersion(dir))
  38. return;
  39. return { mayWantBackend: true, publicDirectory: (0, path_1.join)(dir, PUBLIC_DIR) };
  40. }
  41. exports.discover = discover;
  42. async function build(dir) {
  43. var _a, _b;
  44. const { default: nextBuild } = (0, __1.relativeRequire)(dir, "next/dist/build");
  45. await (0, utils_3.warnIfCustomBuildScript)(dir, exports.name, DEFAULT_BUILD_SCRIPT);
  46. const reactVersion = getReactVersion(dir);
  47. if (reactVersion && (0, semver_1.gte)(reactVersion, "18.0.0")) {
  48. process.env.__NEXT_REACT_ROOT = "true";
  49. }
  50. await nextBuild(dir, null, false, false, true).catch((e) => {
  51. console.error(e.message);
  52. throw e;
  53. });
  54. const reasonsForBackend = [];
  55. const { distDir } = await getConfig(dir);
  56. if (await (0, utils_1.isUsingMiddleware)((0, path_1.join)(dir, distDir), false)) {
  57. reasonsForBackend.push("middleware");
  58. }
  59. if (await (0, utils_1.isUsingImageOptimization)((0, path_1.join)(dir, distDir))) {
  60. reasonsForBackend.push(`Image Optimization`);
  61. }
  62. if ((0, utils_1.isUsingAppDirectory)((0, path_1.join)(dir, distDir))) {
  63. reasonsForBackend.push("app directory (unstable)");
  64. }
  65. const prerenderManifest = await (0, utils_2.readJSON)((0, path_1.join)(dir, distDir, constants_1.PRERENDER_MANIFEST));
  66. const dynamicRoutesWithFallback = Object.entries(prerenderManifest.dynamicRoutes || {}).filter(([, it]) => it.fallback !== false);
  67. if (dynamicRoutesWithFallback.length > 0) {
  68. for (const [key] of dynamicRoutesWithFallback) {
  69. reasonsForBackend.push(`use of fallback ${key}`);
  70. }
  71. }
  72. const routesWithRevalidate = Object.entries(prerenderManifest.routes).filter(([, it]) => it.initialRevalidateSeconds);
  73. if (routesWithRevalidate.length > 0) {
  74. for (const [key] of routesWithRevalidate) {
  75. reasonsForBackend.push(`use of revalidate ${key}`);
  76. }
  77. }
  78. const pagesManifestJSON = await (0, utils_2.readJSON)((0, path_1.join)(dir, distDir, "server", constants_1.PAGES_MANIFEST));
  79. const prerenderedRoutes = Object.keys(prerenderManifest.routes);
  80. const dynamicRoutes = Object.keys(prerenderManifest.dynamicRoutes);
  81. const unrenderedPages = Object.keys(pagesManifestJSON).filter((it) => !(["/_app", "/", "/_error", "/_document", "/404"].includes(it) ||
  82. prerenderedRoutes.includes(it) ||
  83. dynamicRoutes.includes(it)));
  84. if (unrenderedPages.length > 0) {
  85. for (const key of unrenderedPages) {
  86. reasonsForBackend.push(`non-static route ${key}`);
  87. }
  88. }
  89. const manifest = await (0, utils_2.readJSON)((0, path_1.join)(dir, distDir, constants_1.ROUTES_MANIFEST));
  90. const { headers: nextJsHeaders = [], redirects: nextJsRedirects = [], rewrites: nextJsRewrites = [], } = manifest;
  91. const isEveryHeaderSupported = nextJsHeaders.every(utils_1.isHeaderSupportedByHosting);
  92. if (!isEveryHeaderSupported) {
  93. reasonsForBackend.push("advanced headers");
  94. }
  95. const headers = nextJsHeaders.filter(utils_1.isHeaderSupportedByHosting).map(({ source, headers }) => ({
  96. source: (0, utils_1.cleanEscapedChars)(source),
  97. headers,
  98. }));
  99. const isEveryRedirectSupported = nextJsRedirects.every(utils_1.isRedirectSupportedByHosting);
  100. if (!isEveryRedirectSupported) {
  101. reasonsForBackend.push("advanced redirects");
  102. }
  103. const redirects = nextJsRedirects
  104. .filter(utils_1.isRedirectSupportedByHosting)
  105. .map(({ source, destination, statusCode: type }) => ({
  106. source: (0, utils_1.cleanEscapedChars)(source),
  107. destination,
  108. type,
  109. }));
  110. const nextJsRewritesToUse = (0, utils_1.getNextjsRewritesToUse)(nextJsRewrites);
  111. if (!Array.isArray(nextJsRewrites) &&
  112. (((_a = nextJsRewrites.afterFiles) === null || _a === void 0 ? void 0 : _a.length) || ((_b = nextJsRewrites.fallback) === null || _b === void 0 ? void 0 : _b.length))) {
  113. reasonsForBackend.push("advanced rewrites");
  114. }
  115. const isEveryRewriteSupported = nextJsRewritesToUse.every(utils_1.isRewriteSupportedByHosting);
  116. if (!isEveryRewriteSupported) {
  117. reasonsForBackend.push("advanced rewrites");
  118. }
  119. const rewrites = nextJsRewritesToUse
  120. .filter(utils_1.isRewriteSupportedByHosting)
  121. .map(({ source, destination }) => ({
  122. source: (0, utils_1.cleanEscapedChars)(source),
  123. destination,
  124. }));
  125. const wantsBackend = reasonsForBackend.length > 0;
  126. if (wantsBackend) {
  127. const numberOfReasonsToList = process.env.DEBUG ? Infinity : DEFAULT_NUMBER_OF_REASONS_TO_LIST;
  128. console.log("Building a Cloud Function to run this application. This is needed due to:");
  129. for (const reason of reasonsForBackend.slice(0, numberOfReasonsToList)) {
  130. console.log(` • ${reason}`);
  131. }
  132. if (reasonsForBackend.length > numberOfReasonsToList) {
  133. console.log(` • and ${reasonsForBackend.length - numberOfReasonsToList} other reasons, use --debug to see more`);
  134. }
  135. console.log("");
  136. }
  137. return { wantsBackend, headers, redirects, rewrites };
  138. }
  139. exports.build = build;
  140. async function init(setup) {
  141. const language = await (0, prompt_1.promptOnce)({
  142. type: "list",
  143. default: "JavaScript",
  144. message: "What language would you like to use?",
  145. choices: ["JavaScript", "TypeScript"],
  146. });
  147. (0, child_process_1.execSync)(`npx --yes create-next-app@latest -e hello-world ${setup.hosting.source} --use-npm ${language === "TypeScript" ? "--ts" : ""}`, { stdio: "inherit" });
  148. }
  149. exports.init = init;
  150. async function ɵcodegenPublicDirectory(sourceDir, destDir) {
  151. const { distDir } = await getConfig(sourceDir);
  152. const publicPath = (0, path_1.join)(sourceDir, "public");
  153. await (0, promises_1.mkdir)((0, path_1.join)(destDir, "_next", "static"), { recursive: true });
  154. if (await (0, fs_extra_1.pathExists)(publicPath)) {
  155. await (0, fs_extra_1.copy)(publicPath, destDir);
  156. }
  157. await (0, fs_extra_1.copy)((0, path_1.join)(sourceDir, distDir, "static"), (0, path_1.join)(destDir, "_next", "static"));
  158. for (const file of ["index.html", "404.html", "500.html"]) {
  159. const pagesPath = (0, path_1.join)(sourceDir, distDir, "server", "pages", file);
  160. if (await (0, fs_extra_1.pathExists)(pagesPath)) {
  161. await (0, promises_1.copyFile)(pagesPath, (0, path_1.join)(destDir, file));
  162. continue;
  163. }
  164. const appPath = (0, path_1.join)(sourceDir, distDir, "server", "app", file);
  165. if (await (0, fs_extra_1.pathExists)(appPath)) {
  166. await (0, promises_1.copyFile)(appPath, (0, path_1.join)(destDir, file));
  167. }
  168. }
  169. const [middlewareManifest, prerenderManifest, routesManifest] = await Promise.all([
  170. (0, utils_2.readJSON)((0, path_1.join)(sourceDir, distDir, "server", constants_1.MIDDLEWARE_MANIFEST)),
  171. (0, utils_2.readJSON)((0, path_1.join)(sourceDir, distDir, constants_1.PRERENDER_MANIFEST)),
  172. (0, utils_2.readJSON)((0, path_1.join)(sourceDir, distDir, constants_1.ROUTES_MANIFEST)),
  173. ]);
  174. const middlewareMatcherRegexes = Object.values(middlewareManifest.middleware)
  175. .map((it) => it.matchers)
  176. .flat()
  177. .map((it) => new RegExp(it.regexp));
  178. const { redirects = [], rewrites = [], headers = [] } = routesManifest;
  179. const rewritesRegexesNotSupportedByHosting = (0, utils_1.getNextjsRewritesToUse)(rewrites)
  180. .filter((rewrite) => !(0, utils_1.isRewriteSupportedByHosting)(rewrite))
  181. .map((rewrite) => new RegExp(rewrite.regex));
  182. const redirectsRegexesNotSupportedByHosting = redirects
  183. .filter((redirect) => !(0, utils_1.isRedirectSupportedByHosting)(redirect))
  184. .map((redirect) => new RegExp(redirect.regex));
  185. const headersRegexesNotSupportedByHosting = headers
  186. .filter((header) => !(0, utils_1.isHeaderSupportedByHosting)(header))
  187. .map((header) => new RegExp(header.regex));
  188. const pathsUsingsFeaturesNotSupportedByHosting = [
  189. ...middlewareMatcherRegexes,
  190. ...rewritesRegexesNotSupportedByHosting,
  191. ...redirectsRegexesNotSupportedByHosting,
  192. ...headersRegexesNotSupportedByHosting,
  193. ];
  194. for (const [path, route] of Object.entries(prerenderManifest.routes)) {
  195. if (route.initialRevalidateSeconds ||
  196. pathsUsingsFeaturesNotSupportedByHosting.some((it) => path.match(it))) {
  197. continue;
  198. }
  199. const isReactServerComponent = route.dataRoute.endsWith(".rsc");
  200. const contentDist = (0, path_1.join)(sourceDir, distDir, "server", isReactServerComponent ? "app" : "pages");
  201. const parts = path.split("/").filter((it) => !!it);
  202. const partsOrIndex = parts.length > 0 ? parts : ["index"];
  203. const htmlPath = `${(0, path_1.join)(...partsOrIndex)}.html`;
  204. await (0, promises_1.mkdir)((0, path_1.join)(destDir, (0, path_1.dirname)(htmlPath)), { recursive: true });
  205. await (0, promises_1.copyFile)((0, path_1.join)(contentDist, htmlPath), (0, path_1.join)(destDir, htmlPath));
  206. if (!isReactServerComponent) {
  207. const dataPath = `${(0, path_1.join)(...partsOrIndex)}.json`;
  208. await (0, promises_1.mkdir)((0, path_1.join)(destDir, (0, path_1.dirname)(route.dataRoute)), { recursive: true });
  209. await (0, promises_1.copyFile)((0, path_1.join)(contentDist, dataPath), (0, path_1.join)(destDir, route.dataRoute));
  210. }
  211. }
  212. }
  213. exports.ɵcodegenPublicDirectory = ɵcodegenPublicDirectory;
  214. async function ɵcodegenFunctionsDirectory(sourceDir, destDir) {
  215. const { distDir } = await getConfig(sourceDir);
  216. const packageJson = await (0, utils_2.readJSON)((0, path_1.join)(sourceDir, "package.json"));
  217. if ((0, fs_1.existsSync)((0, path_1.join)(sourceDir, "next.config.js"))) {
  218. const dependencyTree = JSON.parse((0, child_process_1.spawnSync)("npm", ["ls", "--omit=dev", "--all", "--json"], {
  219. cwd: sourceDir,
  220. }).stdout.toString());
  221. const esbuildArgs = (0, utils_1.allDependencyNames)(dependencyTree)
  222. .map((it) => `--external:${it}`)
  223. .concat("--bundle", "--platform=node", `--target=node${__1.NODE_VERSION}`, `--outdir=${destDir}`, "--log-level=error");
  224. const bundle = (0, child_process_1.spawnSync)("npx", ["--yes", "esbuild", "next.config.js", ...esbuildArgs], {
  225. cwd: sourceDir,
  226. });
  227. if (bundle.status) {
  228. console.error(bundle.stderr.toString());
  229. throw new error_1.FirebaseError("Unable to bundle next.config.js for use in Cloud Functions");
  230. }
  231. }
  232. if (await (0, fs_extra_1.pathExists)((0, path_1.join)(sourceDir, "public"))) {
  233. await (0, promises_1.mkdir)((0, path_1.join)(destDir, "public"));
  234. await (0, fs_extra_1.copy)((0, path_1.join)(sourceDir, "public"), (0, path_1.join)(destDir, "public"));
  235. }
  236. if (!(await (0, utils_4.hasUnoptimizedImage)(sourceDir, distDir)) &&
  237. ((0, utils_4.usesAppDirRouter)(sourceDir) || (await (0, utils_4.usesNextImage)(sourceDir, distDir)))) {
  238. packageJson.dependencies["sharp"] = "latest";
  239. }
  240. await (0, fs_extra_1.mkdirp)((0, path_1.join)(destDir, distDir));
  241. await (0, fs_extra_1.copy)((0, path_1.join)(sourceDir, distDir), (0, path_1.join)(destDir, distDir));
  242. return { packageJson, frameworksEntry: "next.js" };
  243. }
  244. exports.ɵcodegenFunctionsDirectory = ɵcodegenFunctionsDirectory;
  245. async function getDevModeHandle(dir, hostingEmulatorInfo) {
  246. if (!hostingEmulatorInfo) {
  247. if (await (0, utils_1.isUsingMiddleware)(dir, true)) {
  248. throw new error_1.FirebaseError(`${clc.bold("firebase serve")} does not support Next.js Middleware. Please use ${clc.bold("firebase emulators:start")} instead.`);
  249. }
  250. }
  251. const { default: next } = (0, __1.relativeRequire)(dir, "next");
  252. const nextApp = next({
  253. dev: true,
  254. dir,
  255. hostname: hostingEmulatorInfo === null || hostingEmulatorInfo === void 0 ? void 0 : hostingEmulatorInfo.host,
  256. port: hostingEmulatorInfo === null || hostingEmulatorInfo === void 0 ? void 0 : hostingEmulatorInfo.port,
  257. });
  258. const handler = nextApp.getRequestHandler();
  259. await nextApp.prepare();
  260. return (req, res, next) => {
  261. const parsedUrl = (0, url_1.parse)(req.url, true);
  262. const proxy = (0, __1.createServerResponseProxy)(req, res, next);
  263. handler(req, proxy, parsedUrl);
  264. };
  265. }
  266. exports.getDevModeHandle = getDevModeHandle;
  267. async function getConfig(dir) {
  268. let config = {};
  269. if ((0, fs_1.existsSync)((0, path_1.join)(dir, "next.config.js"))) {
  270. const version = getNextVersion(dir);
  271. if (!version)
  272. throw new Error("Unable to find the next dep, try NPM installing?");
  273. if ((0, semver_1.gte)(version, "12.0.0")) {
  274. const { default: loadConfig } = (0, __1.relativeRequire)(dir, "next/dist/server/config");
  275. const { PHASE_PRODUCTION_BUILD } = (0, __1.relativeRequire)(dir, "next/constants");
  276. config = await loadConfig(PHASE_PRODUCTION_BUILD, dir, null);
  277. }
  278. else {
  279. try {
  280. config = await Promise.resolve().then(() => require((0, url_1.pathToFileURL)((0, path_1.join)(dir, "next.config.js")).toString()));
  281. }
  282. catch (e) {
  283. throw new Error("Unable to load next.config.js.");
  284. }
  285. }
  286. }
  287. return Object.assign({ distDir: ".next" }, config);
  288. }