@netlify/plugin-nextjs 4.0.0-beta.8 → 4.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateRedirects = void 0;
7
+ const fs_extra_1 = require("fs-extra");
8
+ const globby_1 = __importDefault(require("globby"));
9
+ const pathe_1 = require("pathe");
10
+ const constants_1 = require("../constants");
11
+ const utils_1 = require("./utils");
12
+ const generateLocaleRedirects = ({ i18n, basePath, trailingSlash, }) => {
13
+ const redirects = [];
14
+ // If the cookie is set, we need to redirect at the origin
15
+ redirects.push({
16
+ from: `${basePath}/`,
17
+ to: constants_1.HANDLER_FUNCTION_PATH,
18
+ status: 200,
19
+ force: true,
20
+ conditions: {
21
+ Cookie: ['NEXT_LOCALE'],
22
+ },
23
+ });
24
+ i18n.locales.forEach((locale) => {
25
+ if (locale === i18n.defaultLocale) {
26
+ return;
27
+ }
28
+ redirects.push({
29
+ from: `${basePath}/`,
30
+ to: `${basePath}/${locale}${trailingSlash ? '/' : ''}`,
31
+ status: 301,
32
+ conditions: {
33
+ Language: [locale],
34
+ },
35
+ force: true,
36
+ });
37
+ });
38
+ return redirects;
39
+ };
40
+ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath, trailingSlash, appDir }, }) => {
41
+ const { dynamicRoutes, routes: staticRoutes } = await fs_extra_1.readJSON(pathe_1.join(netlifyConfig.build.publish, 'prerender-manifest.json'));
42
+ netlifyConfig.redirects.push(...constants_1.HIDDEN_PATHS.map((path) => ({
43
+ from: `${basePath}${path}`,
44
+ to: '/404.html',
45
+ status: 404,
46
+ force: true,
47
+ })));
48
+ if (i18n && i18n.localeDetection !== false) {
49
+ netlifyConfig.redirects.push(...generateLocaleRedirects({ i18n, basePath, trailingSlash }));
50
+ }
51
+ const dataRedirects = [];
52
+ const pageRedirects = [];
53
+ const isrRedirects = [];
54
+ const dynamicRouteEntries = Object.entries(dynamicRoutes);
55
+ const staticRouteEntries = Object.entries(staticRoutes);
56
+ staticRouteEntries.forEach(([route, { dataRoute, initialRevalidateSeconds }]) => {
57
+ // Only look for revalidate as we need to rewrite these to SSR rather than ODB
58
+ if (initialRevalidateSeconds === false) {
59
+ // These can be ignored, as they're static files handled by the CDN
60
+ return;
61
+ }
62
+ if ((i18n === null || i18n === void 0 ? void 0 : i18n.defaultLocale) && route.startsWith(`/${i18n.defaultLocale}/`)) {
63
+ route = route.slice(i18n.defaultLocale.length + 1);
64
+ }
65
+ isrRedirects.push(...utils_1.netlifyRoutesForNextRoute(dataRoute), ...utils_1.netlifyRoutesForNextRoute(route));
66
+ });
67
+ dynamicRouteEntries.forEach(([route, { dataRoute, fallback }]) => {
68
+ // Add redirects if fallback is "null" (aka blocking) or true/a string
69
+ if (fallback === false) {
70
+ return;
71
+ }
72
+ pageRedirects.push(...utils_1.netlifyRoutesForNextRoute(route));
73
+ dataRedirects.push(...utils_1.netlifyRoutesForNextRoute(dataRoute));
74
+ });
75
+ if (i18n) {
76
+ netlifyConfig.redirects.push({ from: `${basePath}/:locale/_next/static/*`, to: `/static/:splat`, status: 200 });
77
+ }
78
+ const publicFiles = await globby_1.default('**/*', { cwd: pathe_1.join(appDir, 'public') });
79
+ // This is only used in prod, so dev uses `next dev` directly
80
+ netlifyConfig.redirects.push(
81
+ // Static files are in `static`
82
+ { from: `${basePath}/_next/static/*`, to: `/static/:splat`, status: 200 },
83
+ // API routes always need to be served from the regular function
84
+ {
85
+ from: `${basePath}/api`,
86
+ to: constants_1.HANDLER_FUNCTION_PATH,
87
+ status: 200,
88
+ }, {
89
+ from: `${basePath}/api/*`,
90
+ to: constants_1.HANDLER_FUNCTION_PATH,
91
+ status: 200,
92
+ },
93
+ // Preview mode gets forced to the function, to bypass pre-rendered pages, but static files need to be skipped
94
+ ...publicFiles.map((file) => ({
95
+ from: `${basePath}/${file}`,
96
+ // This is a no-op, but we do it to stop it matching the following rule
97
+ to: `${basePath}/${file}`,
98
+ conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
99
+ status: 200,
100
+ })), {
101
+ from: `${basePath}/*`,
102
+ to: constants_1.HANDLER_FUNCTION_PATH,
103
+ status: 200,
104
+ conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
105
+ force: true,
106
+ },
107
+ // ISR redirects are handled by the regular function. Forced to avoid pre-rendered pages
108
+ ...isrRedirects.map((redirect) => ({
109
+ from: `${basePath}${redirect}`,
110
+ to: constants_1.ODB_FUNCTION_PATH,
111
+ status: 200,
112
+ force: true,
113
+ })),
114
+ // These are pages with fallback set, which need an ODB
115
+ // Data redirects go first, to avoid conflict with splat redirects
116
+ ...dataRedirects.map((redirect) => ({
117
+ from: `${basePath}${redirect}`,
118
+ to: constants_1.ODB_FUNCTION_PATH,
119
+ status: 200,
120
+ })),
121
+ // ...then all the other fallback pages
122
+ ...pageRedirects.map((redirect) => ({
123
+ from: `${basePath}${redirect}`,
124
+ to: constants_1.ODB_FUNCTION_PATH,
125
+ status: 200,
126
+ })),
127
+ // Everything else is handled by the regular function
128
+ { from: `${basePath}/*`, to: constants_1.HANDLER_FUNCTION_PATH, status: 200 });
129
+ };
130
+ exports.generateRedirects = generateRedirects;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.netlifyRoutesForNextRoute = void 0;
4
+ const constants_1 = require("../constants");
5
+ const netlifyRoutesForNextRoute = (nextRoute) => {
6
+ const netlifyRoutes = [nextRoute];
7
+ // If the route is an optional catch-all route, we need to add a second
8
+ // Netlify route for the base path (when no parameters are present).
9
+ // The file ending must be present!
10
+ if (constants_1.OPTIONAL_CATCH_ALL_REGEX.test(nextRoute)) {
11
+ let netlifyRoute = nextRoute.replace(constants_1.OPTIONAL_CATCH_ALL_REGEX, '$2');
12
+ // create an empty string, but actually needs to be a forward slash
13
+ if (netlifyRoute === '') {
14
+ netlifyRoute = '/';
15
+ }
16
+ // When optional catch-all route is at top-level, the regex on line 19 will
17
+ // create an incorrect route for the data route. For example, it creates
18
+ // /_next/data/%BUILDID%.json, but NextJS looks for
19
+ // /_next/data/%BUILDID%/index.json
20
+ netlifyRoute = netlifyRoute.replace(/(\/_next\/data\/[^/]+).json/, '$1/index.json');
21
+ // Add second route to the front of the array
22
+ netlifyRoutes.unshift(netlifyRoute);
23
+ }
24
+ return netlifyRoutes.map((route) => route
25
+ // Replace catch-all, e.g., [...slug]
26
+ .replace(constants_1.CATCH_ALL_REGEX, '/:$1/*')
27
+ // Replace optional catch-all, e.g., [[...slug]]
28
+ .replace(constants_1.OPTIONAL_CATCH_ALL_REGEX, '/*')
29
+ // Replace dynamic parameters, e.g., [id]
30
+ .replace(constants_1.DYNAMIC_PARAMETER_REGEX, '/:$1'));
31
+ };
32
+ exports.netlifyRoutesForNextRoute = netlifyRoutesForNextRoute;
@@ -1,93 +1,123 @@
1
- const { existsSync, promises } = require('fs');
2
- const path = require('path');
3
- const { relative } = require('path');
4
- const { yellowBright, greenBright, blueBright, redBright, reset } = require('chalk');
5
- const { async: StreamZip } = require('node-stream-zip');
6
- const outdent = require('outdent');
7
- const prettyBytes = require('pretty-bytes');
8
- const { satisfies } = require('semver');
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.logBetaMessage = exports.checkZipSize = exports.checkForRootPublish = exports.checkNextSiteHasBuilt = exports.checkForOldFunctions = exports.verifyNetlifyBuildVersion = void 0;
26
+ const fs_1 = require("fs");
27
+ const path_1 = __importStar(require("path"));
28
+ const chalk_1 = require("chalk");
29
+ const node_stream_zip_1 = require("node-stream-zip");
30
+ const outdent_1 = require("outdent");
31
+ const pretty_bytes_1 = __importDefault(require("pretty-bytes"));
32
+ const semver_1 = require("semver");
33
+ const constants_1 = require("../constants");
9
34
  // This is when nft support was added
10
35
  const REQUIRED_BUILD_VERSION = '>=18.16.0';
11
- exports.verifyNetlifyBuildVersion = ({ IS_LOCAL, NETLIFY_BUILD_VERSION, failBuild }) => {
36
+ const verifyNetlifyBuildVersion = ({ IS_LOCAL, NETLIFY_BUILD_VERSION, failBuild, }) => {
12
37
  // We check for build version because that's what's available to us, but prompt about the cli because that's what they can upgrade
13
- if (IS_LOCAL && !satisfies(NETLIFY_BUILD_VERSION, REQUIRED_BUILD_VERSION, { includePrerelease: true })) {
14
- return failBuild(outdent `
38
+ if (IS_LOCAL && !semver_1.satisfies(NETLIFY_BUILD_VERSION, REQUIRED_BUILD_VERSION, { includePrerelease: true })) {
39
+ return failBuild(outdent_1.outdent `
15
40
  This version of the Essential Next.js plugin requires netlify-cli@6.12.4 or higher. Please upgrade and try again.
16
41
  You can do this by running: "npm install -g netlify-cli@latest" or "yarn global add netlify-cli@latest"
17
42
  `);
18
43
  }
19
44
  };
20
- exports.checkForOldFunctions = async ({ functions }) => {
21
- const oldFunctions = (await functions.list()).filter(({ name }) => name.startsWith('next_'));
45
+ exports.verifyNetlifyBuildVersion = verifyNetlifyBuildVersion;
46
+ const checkForOldFunctions = async ({ functions }) => {
47
+ const allOldFunctions = await functions.list();
48
+ const oldFunctions = allOldFunctions.filter(({ name }) => name.startsWith('next_'));
22
49
  if (oldFunctions.length !== 0) {
23
- console.log(yellowBright(outdent `
50
+ console.log(chalk_1.yellowBright(outdent_1.outdent `
24
51
  We have found the following functions in your site that seem to be left over from the old Next.js plugin (v3). We have guessed this because the name starts with "next_".
25
52
 
26
- ${reset(oldFunctions.map(({ name }) => `- ${name}`).join('\n'))}
53
+ ${chalk_1.reset(oldFunctions.map(({ name }) => `- ${name}`).join('\n'))}
27
54
 
28
55
  If they were created by the old plugin, these functions are likely to cause errors so should be removed. You can do this by deleting the following directories:
29
56
 
30
- ${reset(oldFunctions.map(({ mainFile }) => `- ${path.relative(process.cwd(), path.dirname(mainFile))}`).join('\n'))}
57
+ ${chalk_1.reset(oldFunctions.map(({ mainFile }) => `- ${path_1.default.relative(process.cwd(), path_1.default.dirname(mainFile))}`).join('\n'))}
31
58
  `));
32
59
  }
33
60
  };
34
- exports.checkNextSiteHasBuilt = ({ publish, failBuild }) => {
35
- if (!existsSync(path.join(publish, 'BUILD_ID'))) {
36
- return failBuild(outdent `
37
- The directory "${path.relative(process.cwd(), publish)}" does not contain a Next.js production build. Perhaps the build command was not run, or you specified the wrong publish directory.
61
+ exports.checkForOldFunctions = checkForOldFunctions;
62
+ const checkNextSiteHasBuilt = ({ publish, failBuild, }) => {
63
+ if (!fs_1.existsSync(path_1.default.join(publish, 'BUILD_ID'))) {
64
+ return failBuild(outdent_1.outdent `
65
+ The directory "${path_1.default.relative(process.cwd(), publish)}" does not contain a Next.js production build. Perhaps the build command was not run, or you specified the wrong publish directory.
38
66
  In most cases it should be set to the site's ".next" directory path, unless you have chosen a custom "distDir" in your Next config.
39
67
  If you are using "next export" then the Essential Next.js plugin should be removed. See https://ntl.fyi/remove-plugin for details.
40
68
  `);
41
69
  }
42
- if (existsSync(path.join(publish, 'export-detail.json'))) {
43
- failBuild(outdent `
70
+ if (fs_1.existsSync(path_1.default.join(publish, 'export-detail.json'))) {
71
+ failBuild(outdent_1.outdent `
44
72
  Detected that "next export" was run, but site is incorrectly publishing the ".next" directory.
45
73
  This plugin is not needed for "next export" so should be removed, and publish directory set to "out".
46
74
  See https://ntl.fyi/remove-plugin for more details on how to remove this plugin.
47
75
  `);
48
76
  }
49
77
  };
50
- exports.checkForRootPublish = ({ publish, failBuild }) => {
51
- if (path.resolve(publish) === path.resolve('.')) {
52
- failBuild(outdent `
78
+ exports.checkNextSiteHasBuilt = checkNextSiteHasBuilt;
79
+ const checkForRootPublish = ({ publish, failBuild, }) => {
80
+ if (path_1.default.resolve(publish) === path_1.default.resolve('.')) {
81
+ failBuild(outdent_1.outdent `
53
82
  Your publish directory is pointing to the base directory of your site. This is not supported for Next.js sites, and is probably a mistake.
54
83
  In most cases it should be set to ".next", unless you have chosen a custom "distDir" in your Next config, or the Next site is in a subdirectory.
55
84
  `);
56
85
  }
57
86
  };
58
- // 50MB, which is the documented max, though the hard max seems to be higher
59
- const LAMBDA_MAX_SIZE = 1024 * 1024 * 50;
60
- exports.checkZipSize = async (file, maxSize = LAMBDA_MAX_SIZE) => {
61
- if (!existsSync(file)) {
87
+ exports.checkForRootPublish = checkForRootPublish;
88
+ const checkZipSize = async (file, maxSize = constants_1.LAMBDA_MAX_SIZE) => {
89
+ if (!fs_1.existsSync(file)) {
62
90
  console.warn(`Could not check zip size because ${file} does not exist`);
63
91
  return;
64
92
  }
65
- const size = await promises.stat(file).then(({ size }) => size);
66
- if (size < maxSize) {
93
+ const fileSize = await fs_1.promises.stat(file).then(({ size }) => size);
94
+ if (fileSize < maxSize) {
67
95
  return;
68
96
  }
69
97
  // We don't fail the build, because the actual hard max size is larger so it might still succeed
70
- console.log(redBright(outdent `
71
- The function zip ${yellowBright(relative(process.cwd(), file))} size is ${prettyBytes(size)}, which is larger than the maximum supported size of ${prettyBytes(maxSize)}.
98
+ console.log(chalk_1.redBright(outdent_1.outdent `
99
+ The function zip ${chalk_1.yellowBright(path_1.relative(process.cwd(), file))} size is ${pretty_bytes_1.default(fileSize)}, which is larger than the maximum supported size of ${pretty_bytes_1.default(maxSize)}.
72
100
  There are a few reasons this could happen. You may have accidentally bundled a large dependency, or you might have a
73
101
  large number of pre-rendered pages included.
74
102
  `));
75
- const zip = new StreamZip({ file });
103
+ const zip = new node_stream_zip_1.async({ file });
76
104
  console.log(`Contains ${await zip.entriesCount} files`);
77
105
  const sortedFiles = Object.values(await zip.entries()).sort((a, b) => b.size - a.size);
78
106
  const largest = {};
79
107
  for (let i = 0; i < 10 && i < sortedFiles.length; i++) {
80
108
  largest[`${i + 1}`] = {
81
109
  File: sortedFiles[i].name,
82
- 'Compressed Size': prettyBytes(sortedFiles[i].compressedSize),
83
- 'Uncompressed Size': prettyBytes(sortedFiles[i].size),
110
+ 'Compressed Size': pretty_bytes_1.default(sortedFiles[i].compressedSize),
111
+ 'Uncompressed Size': pretty_bytes_1.default(sortedFiles[i].size),
84
112
  };
85
113
  }
86
- console.log(yellowBright `\n\nThese are the largest files in the zip:`);
114
+ console.log(chalk_1.yellowBright `\n\nThese are the largest files in the zip:`);
87
115
  console.table(largest);
88
- console.log(greenBright `\n\nFor more information on fixing this, see ${blueBright `https://ntl.fyi/large-next-functions`}`);
116
+ console.log(chalk_1.greenBright `\n\nFor more information on fixing this, see ${chalk_1.blueBright `https://ntl.fyi/large-next-functions`}`);
89
117
  };
90
- exports.logBetaMessage = () => console.log(greenBright(outdent `
118
+ exports.checkZipSize = checkZipSize;
119
+ const logBetaMessage = () => console.log(chalk_1.greenBright(outdent_1.outdent `
91
120
  Thank you for trying the Essential Next.js beta plugin.
92
- Please share feedback (both good and bad) at ${blueBright `https://ntl.fyi/next-beta-feedback`}
121
+ Please share feedback (both good and bad) at ${chalk_1.blueBright `https://ntl.fyi/next-beta-feedback`}
93
122
  `));
123
+ exports.logBetaMessage = logBetaMessage;
package/lib/index.js CHANGED
@@ -1,50 +1,60 @@
1
- const { join, relative } = require('path');
2
- const { ODB_FUNCTION_NAME } = require('./constants');
3
- const { restoreCache, saveCache } = require('./helpers/cache');
4
- const { getNextConfig, configureHandlerFunctions, generateRedirects } = require('./helpers/config');
5
- const { moveStaticPages, movePublicFiles } = require('./helpers/files');
6
- const { generateFunctions, setupImageFunction, generatePagesResolver } = require('./helpers/functions');
7
- const { verifyNetlifyBuildVersion, checkNextSiteHasBuilt, checkForRootPublish, logBetaMessage, checkZipSize, checkForOldFunctions, } = require('./helpers/verification');
8
- /** @type import("@netlify/build").NetlifyPlugin */
9
- module.exports = {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const path_1 = require("path");
4
+ const constants_1 = require("./constants");
5
+ const cache_1 = require("./helpers/cache");
6
+ const config_1 = require("./helpers/config");
7
+ const files_1 = require("./helpers/files");
8
+ const functions_1 = require("./helpers/functions");
9
+ const redirects_1 = require("./helpers/redirects");
10
+ const verification_1 = require("./helpers/verification");
11
+ const plugin = {
10
12
  async onPreBuild({ constants, netlifyConfig, utils: { build: { failBuild }, cache, }, }) {
11
13
  var _a;
12
- logBetaMessage();
14
+ verification_1.logBetaMessage();
13
15
  const { publish } = netlifyConfig.build;
14
- checkForRootPublish({ publish, failBuild });
15
- verifyNetlifyBuildVersion({ failBuild, ...constants });
16
- await restoreCache({ cache, publish });
16
+ verification_1.checkForRootPublish({ publish, failBuild });
17
+ verification_1.verifyNetlifyBuildVersion({ failBuild, ...constants });
18
+ await cache_1.restoreCache({ cache, publish });
17
19
  (_a = netlifyConfig.build).environment || (_a.environment = {});
18
20
  // eslint-disable-next-line unicorn/consistent-destructuring
19
21
  netlifyConfig.build.environment.NEXT_PRIVATE_TARGET = 'server';
20
22
  },
21
23
  async onBuild({ constants, netlifyConfig, utils: { build: { failBuild }, }, }) {
22
24
  const { publish } = netlifyConfig.build;
23
- checkNextSiteHasBuilt({ publish, failBuild });
24
- const { appDir, basePath, i18n, images, target, ignore } = await getNextConfig({ publish, failBuild });
25
- configureHandlerFunctions({ netlifyConfig, ignore, publish: relative(process.cwd(), publish) });
26
- await generateFunctions(constants, appDir);
27
- await generatePagesResolver({ netlifyConfig, target, constants });
28
- await movePublicFiles({ appDir, publish });
25
+ verification_1.checkNextSiteHasBuilt({ publish, failBuild });
26
+ const { appDir, basePath, i18n, images, target, ignore, trailingSlash, outdir } = await config_1.getNextConfig({
27
+ publish,
28
+ failBuild,
29
+ });
30
+ config_1.configureHandlerFunctions({ netlifyConfig, ignore, publish: path_1.relative(process.cwd(), publish) });
31
+ await functions_1.generateFunctions(constants, appDir);
32
+ await functions_1.generatePagesResolver({ target, constants });
33
+ await files_1.movePublicFiles({ appDir, outdir, publish });
34
+ if (process.env.EXPERIMENTAL_ODB_TTL) {
35
+ await files_1.patchNextFiles(basePath);
36
+ }
29
37
  if (process.env.EXPERIMENTAL_MOVE_STATIC_PAGES) {
30
38
  console.log("The flag 'EXPERIMENTAL_MOVE_STATIC_PAGES' is no longer required, as it is now the default. To disable this behavior, set the env var 'SERVE_STATIC_FILES_FROM_ORIGIN' to 'true'");
31
39
  }
32
40
  if (!process.env.SERVE_STATIC_FILES_FROM_ORIGIN) {
33
- await moveStaticPages({ target, failBuild, netlifyConfig, i18n });
41
+ await files_1.moveStaticPages({ target, netlifyConfig, i18n });
34
42
  }
35
- await setupImageFunction({ constants, imageconfig: images, netlifyConfig, basePath });
36
- await generateRedirects({
43
+ await functions_1.setupImageFunction({ constants, imageconfig: images, netlifyConfig, basePath });
44
+ await redirects_1.generateRedirects({
37
45
  netlifyConfig,
38
- basePath,
39
- i18n,
46
+ nextConfig: { basePath, i18n, trailingSlash, appDir },
40
47
  });
41
48
  },
42
- async onPostBuild({ netlifyConfig, utils: { cache, functions }, constants: { FUNCTIONS_DIST } }) {
43
- await saveCache({ cache, publish: netlifyConfig.build.publish });
44
- await checkForOldFunctions({ functions });
45
- await checkZipSize(join(FUNCTIONS_DIST, `${ODB_FUNCTION_NAME}.zip`));
49
+ async onPostBuild({ netlifyConfig, utils: { cache, functions, build: { failBuild }, }, constants: { FUNCTIONS_DIST }, }) {
50
+ await cache_1.saveCache({ cache, publish: netlifyConfig.build.publish });
51
+ await verification_1.checkForOldFunctions({ functions });
52
+ await verification_1.checkZipSize(path_1.join(FUNCTIONS_DIST, `${constants_1.ODB_FUNCTION_NAME}.zip`));
53
+ const { basePath } = await config_1.getNextConfig({ publish: netlifyConfig.build.publish, failBuild });
54
+ await files_1.unpatchNextFiles(basePath);
46
55
  },
47
56
  onEnd() {
48
- logBetaMessage();
57
+ verification_1.logBetaMessage();
49
58
  },
50
59
  };
60
+ module.exports = plugin;
@@ -1,89 +1,32 @@
1
- const { promises, createWriteStream, existsSync } = require('fs');
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getHandler = void 0;
4
+ const { promises } = require('fs');
2
5
  const { Server } = require('http');
3
- const { tmpdir } = require('os');
4
6
  const path = require('path');
5
- const { promisify } = require('util');
6
- const streamPipeline = promisify(require('stream').pipeline);
7
+ // eslint-disable-next-line node/prefer-global/url, node/prefer-global/url-search-params
8
+ const { URLSearchParams, URL } = require('url');
7
9
  const { Bridge } = require('@vercel/node/dist/bridge');
8
- const fetch = require('node-fetch');
10
+ const { augmentFsModule, getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils');
9
11
  const makeHandler = () =>
10
12
  // We return a function and then call `toString()` on it to serialise it as the launcher function
11
- (conf, app, pageRoot, staticManifest = []) => {
13
+ // eslint-disable-next-line max-params
14
+ (conf, app, pageRoot, staticManifest = [], mode = 'ssr') => {
12
15
  // This is just so nft knows about the page entrypoints. It's not actually used
13
16
  try {
14
17
  // eslint-disable-next-line node/no-missing-require
15
18
  require.resolve('./pages.js');
16
19
  }
17
20
  catch { }
21
+ // eslint-disable-next-line no-underscore-dangle
22
+ process.env._BYPASS_SSG = 'true';
23
+ const ONE_YEAR_IN_SECONDS = 31536000;
24
+ // We don't want to write ISR files to disk in the lambda environment
25
+ conf.experimental.isrFlushToDisk = false;
18
26
  // Set during the request as it needs the host header. Hoisted so we can define the function once
19
27
  let base;
20
- // Only do this if we have some static files moved to the CDN
21
- if (staticManifest.length !== 0) {
22
- // These are static page files that have been removed from the function bundle
23
- // In most cases these are served from the CDN, but for rewrites Next may try to read them
24
- // from disk. We need to intercept these and load them from the CDN instead
25
- // Sadly the only way to do this is to monkey-patch fs.promises. Yeah, I know.
26
- const staticFiles = new Set(staticManifest);
27
- // Yes, you can cache stuff locally in a Lambda
28
- const cacheDir = path.join(tmpdir(), 'next-static-cache');
29
- // Grab the real fs.promises.readFile...
30
- const readfileOrig = promises.readFile;
31
- // ...then money-patch it to see if it's requesting a CDN file
32
- promises.readFile = async (file, options) => {
33
- // We only care about page files
34
- if (file.startsWith(pageRoot)) {
35
- // We only want the part after `pages/`
36
- const filePath = file.slice(pageRoot.length + 1);
37
- // Is it in the CDN and not local?
38
- if (staticFiles.has(filePath) && !existsSync(file)) {
39
- // This name is safe to use, because it's one that was already created by Next
40
- const cacheFile = path.join(cacheDir, filePath);
41
- // Have we already cached it? We ignore the cache if running locally to avoid staleness
42
- if ((!existsSync(cacheFile) || process.env.NETLIFY_DEV) && base) {
43
- await promises.mkdir(path.dirname(cacheFile), { recursive: true });
44
- // Append the path to our host and we can load it like a regular page
45
- const url = `${base}/${filePath}`;
46
- console.log(`Downloading ${url} to ${cacheFile}`);
47
- const response = await fetch(url);
48
- if (!response.ok) {
49
- // Next catches this and returns it as a not found file
50
- throw new Error(`Failed to fetch ${url}`);
51
- }
52
- // Stream it to disk
53
- await streamPipeline(response.body, createWriteStream(cacheFile));
54
- }
55
- // Return the cache file
56
- return readfileOrig(cacheFile, options);
57
- }
58
- }
59
- return readfileOrig(file, options);
60
- };
61
- }
62
- let NextServer;
63
- try {
64
- // next >= 11.0.1. Yay breaking changes in patch releases!
65
- NextServer = require('next/dist/server/next-server').default;
66
- }
67
- catch (error) {
68
- if (!error.message.includes("Cannot find module 'next/dist/server/next-server'")) {
69
- // A different error, so rethrow it
70
- throw error;
71
- }
72
- // Probably an old version of next
73
- }
74
- if (!NextServer) {
75
- try {
76
- // next < 11.0.1
77
- // eslint-disable-next-line node/no-missing-require, import/no-unresolved
78
- NextServer = require('next/dist/next-server/server/next-server').default;
79
- }
80
- catch (error) {
81
- if (!error.message.includes("Cannot find module 'next/dist/next-server/server/next-server'")) {
82
- throw error;
83
- }
84
- throw new Error('Could not find Next.js server');
85
- }
86
- }
28
+ augmentFsModule({ promises, staticManifest, pageRoot, getBase: () => base });
29
+ const NextServer = getNextServer();
87
30
  const nextServer = new NextServer({
88
31
  conf,
89
32
  dir: path.resolve(__dirname, app),
@@ -102,9 +45,10 @@ const makeHandler = () =>
102
45
  const bridge = new Bridge(server);
103
46
  bridge.listen();
104
47
  return async (event, context) => {
105
- var _a, _b, _c, _d;
48
+ var _a, _b, _c;
49
+ let requestMode = mode;
106
50
  // Ensure that paths are encoded - but don't double-encode them
107
- event.path = new URL(event.path, event.rawUrl).pathname;
51
+ event.path = new URL(event.rawUrl).pathname;
108
52
  // Next expects to be able to parse the query from the URL
109
53
  const query = new URLSearchParams(event.queryStringParameters).toString();
110
54
  event.path = query ? `${event.path}?${query}` : event.path;
@@ -115,25 +59,27 @@ const makeHandler = () =>
115
59
  base = `${protocol}://${host}`;
116
60
  }
117
61
  const { headers, ...result } = await bridge.launcher(event, context);
118
- /** @type import("@netlify/functions").HandlerResponse */
119
62
  // Convert all headers to multiValueHeaders
120
- const multiValueHeaders = {};
121
- for (const key of Object.keys(headers)) {
122
- if (Array.isArray(headers[key])) {
123
- multiValueHeaders[key] = headers[key];
124
- }
125
- else {
126
- multiValueHeaders[key] = [headers[key]];
127
- }
128
- }
63
+ const multiValueHeaders = getMultiValueHeaders(headers);
129
64
  if ((_b = (_a = multiValueHeaders['set-cookie']) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.includes('__prerender_bypass')) {
130
65
  delete multiValueHeaders.etag;
131
66
  multiValueHeaders['cache-control'] = ['no-cache'];
132
67
  }
133
68
  // Sending SWR headers causes undefined behaviour with the Netlify CDN
134
- if ((_d = (_c = multiValueHeaders['cache-control']) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.includes('stale-while-revalidate')) {
69
+ const cacheHeader = (_c = multiValueHeaders['cache-control']) === null || _c === void 0 ? void 0 : _c[0];
70
+ if (cacheHeader === null || cacheHeader === void 0 ? void 0 : cacheHeader.includes('stale-while-revalidate')) {
71
+ if (requestMode === 'odb') {
72
+ requestMode = 'isr';
73
+ const ttl = getMaxAge(cacheHeader);
74
+ // Long-expiry TTL is basically no TTL
75
+ if (ttl > 0 && ttl < ONE_YEAR_IN_SECONDS) {
76
+ result.ttl = ttl;
77
+ }
78
+ multiValueHeaders['x-rendered-at'] = [new Date().toISOString()];
79
+ }
135
80
  multiValueHeaders['cache-control'] = ['public, max-age=0, must-revalidate'];
136
81
  }
82
+ multiValueHeaders['x-render-mode'] = [requestMode];
137
83
  return {
138
84
  ...result,
139
85
  multiValueHeaders,
@@ -143,13 +89,10 @@ const makeHandler = () =>
143
89
  };
144
90
  const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '../../..' }) => `
145
91
  const { Server } = require("http");
146
- const { tmpdir } = require('os')
147
- const { promises, createWriteStream, existsSync } = require("fs");
148
- const { promisify } = require('util')
149
- const streamPipeline = promisify(require('stream').pipeline)
92
+ const { promises } = require("fs");
150
93
  // We copy the file here rather than requiring from the node module
151
94
  const { Bridge } = require("./bridge");
152
- const fetch = require('node-fetch')
95
+ const { augmentFsModule, getMaxAge, getMultiValueHeaders, getNextServer } = require('./handlerUtils')
153
96
 
154
97
  const { builder } = require("@netlify/functions");
155
98
  const { config } = require("${publishDir}/required-server-files.json")
@@ -160,7 +103,8 @@ try {
160
103
  const path = require("path");
161
104
  const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", config.target === "server" ? "server" : "serverless", "pages"));
162
105
  exports.handler = ${isODB
163
- ? `builder((${makeHandler().toString()})(config, "${appDir}", pageRoot, staticManifest));`
164
- : `(${makeHandler().toString()})(config, "${appDir}", pageRoot, staticManifest);`}
106
+ ? `builder((${makeHandler().toString()})(config, "${appDir}", pageRoot, staticManifest, 'odb'));`
107
+ : `(${makeHandler().toString()})(config, "${appDir}", pageRoot, staticManifest, 'ssr');`}
165
108
  `;
166
- module.exports = getHandler;
109
+ exports.getHandler = getHandler;
110
+ /* eslint-enable @typescript-eslint/no-var-requires */