@netlify/plugin-nextjs 4.36.0 → 4.37.0
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.
- package/README.md +1 -1
- package/lib/constants.js +5 -3
- package/lib/helpers/analysis.js +23 -18
- package/lib/helpers/compiler.js +1 -0
- package/lib/helpers/config.js +19 -4
- package/lib/helpers/files.js +21 -10
- package/lib/helpers/flags.js +22 -0
- package/lib/helpers/functions.js +150 -20
- package/lib/helpers/matchers.js +8 -4
- package/lib/helpers/pack.js +41 -0
- package/lib/helpers/redirects.js +12 -5
- package/lib/helpers/utils.js +16 -4
- package/lib/helpers/verification.js +5 -4
- package/lib/index.js +16 -6
- package/lib/templates/getApiHandler.js +24 -17
- package/lib/templates/getHandler.js +16 -17
- package/lib/templates/getPageResolver.js +1 -1
- package/lib/templates/handlerUtils.js +1 -34
- package/lib/templates/server.js +78 -71
- package/package.json +4 -4
- package/src/templates/edge/shims.js +27 -3
package/README.md
CHANGED
package/lib/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DIVIDER = exports.LAMBDA_MAX_SIZE = exports.MINIMUM_REVALIDATE_SECONDS = exports.DYNAMIC_PARAMETER_REGEX = exports.OPTIONAL_CATCH_ALL_REGEX = exports.CATCH_ALL_REGEX = exports.DEFAULT_FUNCTIONS_SRC = exports.HANDLER_FUNCTION_PATH = exports.ODB_FUNCTION_PATH = exports.HIDDEN_PATHS = exports.IMAGE_FUNCTION_TITLE = exports.ODB_FUNCTION_TITLE = exports.HANDLER_FUNCTION_TITLE = exports.NEXT_PLUGIN = exports.NEXT_PLUGIN_NAME = exports.IMAGE_FUNCTION_NAME = exports.ODB_FUNCTION_NAME = exports.HANDLER_FUNCTION_NAME = void 0;
|
|
3
|
+
exports.DIVIDER = exports.LAMBDA_MAX_SIZE = exports.LAMBDA_WARNING_SIZE = exports.MINIMUM_REVALIDATE_SECONDS = exports.DYNAMIC_PARAMETER_REGEX = exports.OPTIONAL_CATCH_ALL_REGEX = exports.CATCH_ALL_REGEX = exports.DEFAULT_FUNCTIONS_SRC = exports.HANDLER_FUNCTION_PATH = exports.ODB_FUNCTION_PATH = exports.HIDDEN_PATHS = exports.IMAGE_FUNCTION_TITLE = exports.ODB_FUNCTION_TITLE = exports.HANDLER_FUNCTION_TITLE = exports.NEXT_PLUGIN = exports.NEXT_PLUGIN_NAME = exports.IMAGE_FUNCTION_NAME = exports.ODB_FUNCTION_NAME = exports.HANDLER_FUNCTION_NAME = void 0;
|
|
4
4
|
exports.HANDLER_FUNCTION_NAME = '___netlify-handler';
|
|
5
5
|
exports.ODB_FUNCTION_NAME = '___netlify-odb-handler';
|
|
6
6
|
exports.IMAGE_FUNCTION_NAME = '_ipx';
|
|
@@ -29,8 +29,10 @@ exports.CATCH_ALL_REGEX = /\/\[\.{3}(.*)](.json)?$/;
|
|
|
29
29
|
exports.OPTIONAL_CATCH_ALL_REGEX = /\/\[{2}\.{3}(.*)]{2}(.json)?$/;
|
|
30
30
|
exports.DYNAMIC_PARAMETER_REGEX = /\/\[(.*?)]/g;
|
|
31
31
|
exports.MINIMUM_REVALIDATE_SECONDS = 60;
|
|
32
|
-
// 50MB, which is the
|
|
33
|
-
exports.
|
|
32
|
+
// 50MB, which is the warning max
|
|
33
|
+
exports.LAMBDA_WARNING_SIZE = 1024 * 1024 * 50;
|
|
34
|
+
// 250MB, which is the hard max
|
|
35
|
+
exports.LAMBDA_MAX_SIZE = 1024 * 1024 * 250;
|
|
34
36
|
exports.DIVIDER = `
|
|
35
37
|
────────────────────────────────────────────────────────────────
|
|
36
38
|
`;
|
package/lib/helpers/analysis.js
CHANGED
|
@@ -26,6 +26,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
26
26
|
exports.extractConfigFromFile = exports.validateConfigValue = exports.isEdgeConfig = void 0;
|
|
27
27
|
const fs_1 = __importStar(require("fs"));
|
|
28
28
|
const pathe_1 = require("pathe");
|
|
29
|
+
const utils_1 = require("./utils");
|
|
29
30
|
const isEdgeConfig = (config) => ['experimental-edge', 'edge'].includes(config);
|
|
30
31
|
exports.isEdgeConfig = isEdgeConfig;
|
|
31
32
|
const validateConfigValue = (config, apiFilePath) => {
|
|
@@ -61,29 +62,33 @@ let hasWarnedAboutNextVersion = false;
|
|
|
61
62
|
/**
|
|
62
63
|
* Uses Next's swc static analysis to extract the config values from a file.
|
|
63
64
|
*/
|
|
64
|
-
const extractConfigFromFile = async (apiFilePath) => {
|
|
65
|
+
const extractConfigFromFile = async (apiFilePath, appDir) => {
|
|
65
66
|
if (!apiFilePath || !(0, fs_1.existsSync)(apiFilePath)) {
|
|
66
67
|
return {};
|
|
67
68
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
const extractConstValueModulePath = (0, utils_1.findModuleFromBase)({
|
|
70
|
+
paths: [appDir],
|
|
71
|
+
candidates: ['next/dist/build/analysis/extract-const-value'],
|
|
72
|
+
});
|
|
73
|
+
const parseModulePath = (0, utils_1.findModuleFromBase)({
|
|
74
|
+
paths: [appDir],
|
|
75
|
+
candidates: ['next/dist/build/analysis/parse-module'],
|
|
76
|
+
});
|
|
77
|
+
if (!extractConstValueModulePath || !parseModulePath) {
|
|
78
|
+
if (!hasWarnedAboutNextVersion) {
|
|
79
|
+
console.log("This version of Next.js doesn't support advanced API routes. Skipping...");
|
|
80
|
+
hasWarnedAboutNextVersion = true;
|
|
75
81
|
}
|
|
82
|
+
// Old Next.js version
|
|
83
|
+
return {};
|
|
76
84
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return {};
|
|
85
|
-
}
|
|
86
|
-
throw error;
|
|
85
|
+
if (!extractConstValue && extractConstValueModulePath) {
|
|
86
|
+
// eslint-disable-next-line import/no-dynamic-require
|
|
87
|
+
extractConstValue = require(extractConstValueModulePath);
|
|
88
|
+
}
|
|
89
|
+
if (!parseModule && parseModulePath) {
|
|
90
|
+
// eslint-disable-next-line prefer-destructuring, @typescript-eslint/no-var-requires, import/no-dynamic-require
|
|
91
|
+
parseModule = require(parseModulePath).parseModule;
|
|
87
92
|
}
|
|
88
93
|
const { extractExportedConstValue, UnsupportedValueError } = extractConstValue;
|
|
89
94
|
const fileContent = await fs_1.default.promises.readFile(apiFilePath, 'utf8');
|
package/lib/helpers/compiler.js
CHANGED
package/lib/helpers/config.js
CHANGED
|
@@ -9,6 +9,7 @@ const fs_extra_1 = require("fs-extra");
|
|
|
9
9
|
const pathe_1 = require("pathe");
|
|
10
10
|
const slash_1 = __importDefault(require("slash"));
|
|
11
11
|
const constants_1 = require("../constants");
|
|
12
|
+
const flags_1 = require("./flags");
|
|
12
13
|
const utils_1 = require("./utils");
|
|
13
14
|
const ROUTES_MANIFEST_FILE = 'routes-manifest.json';
|
|
14
15
|
const defaultFailBuild = (message, { error }) => {
|
|
@@ -64,8 +65,8 @@ const hasManuallyAddedModule = ({ netlifyConfig, moduleName, }) =>
|
|
|
64
65
|
Object.values(netlifyConfig.functions).some(({ included_files = [] }) => included_files.some((inc) => inc.includes(`node_modules/${moduleName}`)));
|
|
65
66
|
exports.hasManuallyAddedModule = hasManuallyAddedModule;
|
|
66
67
|
/* eslint-enable camelcase */
|
|
67
|
-
const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [], }) => {
|
|
68
|
-
var _a;
|
|
68
|
+
const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [], apiLambdas, featureFlags, }) => {
|
|
69
|
+
var _a, _b, _c;
|
|
69
70
|
const config = await (0, exports.getRequiredServerFiles)(publish);
|
|
70
71
|
const files = config.files || [];
|
|
71
72
|
const cssFilesToInclude = files.filter((f) => f.startsWith(`${publish}/static/css/`));
|
|
@@ -75,7 +76,7 @@ const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [],
|
|
|
75
76
|
}
|
|
76
77
|
// If the user has manually added the module to included_files, then don't exclude it
|
|
77
78
|
const excludedModules = DEFAULT_EXCLUDED_MODULES.filter((moduleName) => !(0, exports.hasManuallyAddedModule)({ netlifyConfig, moduleName }));
|
|
78
|
-
|
|
79
|
+
const configureFunction = (functionName) => {
|
|
79
80
|
var _a, _b;
|
|
80
81
|
(_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
|
|
81
82
|
netlifyConfig.functions[functionName].node_bundler = 'nft';
|
|
@@ -91,7 +92,21 @@ const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [],
|
|
|
91
92
|
netlifyConfig.functions[functionName].included_files.push(`!${moduleRoot}/**/*`);
|
|
92
93
|
}
|
|
93
94
|
});
|
|
94
|
-
}
|
|
95
|
+
};
|
|
96
|
+
configureFunction(constants_1.HANDLER_FUNCTION_NAME);
|
|
97
|
+
configureFunction(constants_1.ODB_FUNCTION_NAME);
|
|
98
|
+
if ((0, flags_1.splitApiRoutes)(featureFlags)) {
|
|
99
|
+
for (const apiLambda of apiLambdas) {
|
|
100
|
+
const { functionName, includedFiles } = apiLambda;
|
|
101
|
+
(_b = netlifyConfig.functions)[functionName] || (_b[functionName] = { included_files: [] });
|
|
102
|
+
netlifyConfig.functions[functionName].node_bundler = 'none';
|
|
103
|
+
(_c = netlifyConfig.functions[functionName]).included_files || (_c.included_files = []);
|
|
104
|
+
netlifyConfig.functions[functionName].included_files.push(...includedFiles);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
configureFunction('_api_*');
|
|
109
|
+
}
|
|
95
110
|
};
|
|
96
111
|
exports.configureHandlerFunctions = configureHandlerFunctions;
|
|
97
112
|
const buildHeader = (buildHeaderParams) => {
|
package/lib/helpers/files.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.movePublicFiles = exports.unpatchNextFiles = exports.unpatchFile = exports.patchNextFiles = exports.getDependenciesOfFile = exports.getSourceFileForPage = exports.moveStaticPages = exports.getMiddleware = exports.matchesRewrite = exports.matchesRedirect = exports.matchMiddleware = exports.stripLocale = exports.isDynamicRoute = void 0;
|
|
6
|
+
exports.movePublicFiles = exports.unpatchNextFiles = exports.unpatchFile = exports.patchNextFiles = exports.getDependenciesOfFile = exports.getSourceFileForPage = exports.getServerFile = exports.moveStaticPages = exports.getMiddleware = exports.matchesRewrite = exports.matchesRedirect = exports.matchMiddleware = exports.stripLocale = exports.isDynamicRoute = void 0;
|
|
7
7
|
const os_1 = require("os");
|
|
8
8
|
const chalk_1 = require("chalk");
|
|
9
9
|
const fs_extra_1 = require("fs-extra");
|
|
@@ -16,7 +16,6 @@ const constants_1 = require("../constants");
|
|
|
16
16
|
const edge_1 = require("./edge");
|
|
17
17
|
const utils_1 = require("./utils");
|
|
18
18
|
const TEST_ROUTE = /(|\/)\[[^/]+?](\/|\.html|$)/;
|
|
19
|
-
const SOURCE_FILE_EXTENSIONS = ['js', 'jsx', 'ts', 'tsx'];
|
|
20
19
|
const isDynamicRoute = (route) => TEST_ROUTE.test(route);
|
|
21
20
|
exports.isDynamicRoute = isDynamicRoute;
|
|
22
21
|
const stripLocale = (rawPath, locales = []) => {
|
|
@@ -107,7 +106,6 @@ const moveStaticPages = async ({ netlifyConfig, target, i18n, basePath, }) => {
|
|
|
107
106
|
filesManifest[file] = targetPath;
|
|
108
107
|
const dest = (0, pathe_1.join)(netlifyConfig.build.publish, targetPath);
|
|
109
108
|
try {
|
|
110
|
-
console.log(`Moving ${source} to ${dest}`);
|
|
111
109
|
await (0, fs_extra_1.move)(source, dest);
|
|
112
110
|
}
|
|
113
111
|
catch (error) {
|
|
@@ -279,16 +277,24 @@ const getServerFile = (root, includeBase = true) => {
|
|
|
279
277
|
}
|
|
280
278
|
return (0, utils_1.findModuleFromBase)({ candidates, paths: [root] });
|
|
281
279
|
};
|
|
280
|
+
exports.getServerFile = getServerFile;
|
|
281
|
+
// Next.js already defines a default `pageExtensions` array in its `required-server-files.json` file
|
|
282
|
+
// In case it gets `undefined`, this is a fallback
|
|
283
|
+
const SOURCE_FILE_EXTENSIONS = ['js', 'jsx', 'ts', 'tsx'];
|
|
282
284
|
/**
|
|
283
285
|
* Find the source file for a given page route
|
|
284
286
|
*/
|
|
285
|
-
const getSourceFileForPage = (page, roots) => {
|
|
287
|
+
const getSourceFileForPage = (page, roots, pageExtensions = SOURCE_FILE_EXTENSIONS) => {
|
|
286
288
|
for (const root of roots) {
|
|
287
|
-
for (const extension of
|
|
289
|
+
for (const extension of pageExtensions) {
|
|
288
290
|
const file = (0, pathe_1.join)(root, `${page}.${extension}`);
|
|
289
291
|
if ((0, fs_extra_1.existsSync)(file)) {
|
|
290
292
|
return file;
|
|
291
293
|
}
|
|
294
|
+
const fileAtFolderIndex = (0, pathe_1.join)(root, page, `index.${extension}`);
|
|
295
|
+
if ((0, fs_extra_1.existsSync)(fileAtFolderIndex)) {
|
|
296
|
+
return fileAtFolderIndex;
|
|
297
|
+
}
|
|
292
298
|
}
|
|
293
299
|
}
|
|
294
300
|
console.log('Could not find source file for page', page);
|
|
@@ -302,7 +308,7 @@ const getDependenciesOfFile = async (file) => {
|
|
|
302
308
|
if (!(0, fs_extra_1.existsSync)(nft)) {
|
|
303
309
|
return [];
|
|
304
310
|
}
|
|
305
|
-
const dependencies = await (0, fs_extra_1.readJson)(nft, 'utf8');
|
|
311
|
+
const dependencies = (await (0, fs_extra_1.readJson)(nft, 'utf8'));
|
|
306
312
|
return dependencies.files.map((dep) => (0, pathe_1.resolve)((0, pathe_1.dirname)(file), dep));
|
|
307
313
|
};
|
|
308
314
|
exports.getDependenciesOfFile = getDependenciesOfFile;
|
|
@@ -312,6 +318,11 @@ const baseServerReplacements = [
|
|
|
312
318
|
`checkIsManualRevalidate(req, this.renderOpts.previewProps)`,
|
|
313
319
|
`checkIsManualRevalidate(process.env._REVALIDATE_SSG ? { headers: { 'x-prerender-revalidate': this.renderOpts.previewProps.previewModeId } } : req, this.renderOpts.previewProps)`,
|
|
314
320
|
],
|
|
321
|
+
// In https://github.com/vercel/next.js/pull/47803 checkIsManualRevalidate was renamed to checkIsOnDemandRevalidate
|
|
322
|
+
[
|
|
323
|
+
`checkIsOnDemandRevalidate(req, this.renderOpts.previewProps)`,
|
|
324
|
+
`checkIsOnDemandRevalidate(process.env._REVALIDATE_SSG ? { headers: { 'x-prerender-revalidate': this.renderOpts.previewProps.previewModeId } } : req, this.renderOpts.previewProps)`,
|
|
325
|
+
],
|
|
315
326
|
// ensure ISR 404 pages send the correct SWR cache headers
|
|
316
327
|
[`private: isPreviewMode || is404Page && cachedData`, `private: isPreviewMode && cachedData`],
|
|
317
328
|
];
|
|
@@ -334,7 +345,7 @@ const nextServerReplacements = [
|
|
|
334
345
|
],
|
|
335
346
|
];
|
|
336
347
|
const patchNextFiles = async (root) => {
|
|
337
|
-
const baseServerFile = getServerFile(root);
|
|
348
|
+
const baseServerFile = (0, exports.getServerFile)(root);
|
|
338
349
|
console.log(`Patching ${baseServerFile}`);
|
|
339
350
|
if (baseServerFile) {
|
|
340
351
|
await patchFile({
|
|
@@ -342,7 +353,7 @@ const patchNextFiles = async (root) => {
|
|
|
342
353
|
replacements: baseServerReplacements,
|
|
343
354
|
});
|
|
344
355
|
}
|
|
345
|
-
const nextServerFile = getServerFile(root, false);
|
|
356
|
+
const nextServerFile = (0, exports.getServerFile)(root, false);
|
|
346
357
|
console.log(`Patching ${nextServerFile}`);
|
|
347
358
|
if (nextServerFile) {
|
|
348
359
|
await patchFile({
|
|
@@ -360,9 +371,9 @@ const unpatchFile = async (file) => {
|
|
|
360
371
|
};
|
|
361
372
|
exports.unpatchFile = unpatchFile;
|
|
362
373
|
const unpatchNextFiles = async (root) => {
|
|
363
|
-
const baseServerFile = getServerFile(root);
|
|
374
|
+
const baseServerFile = (0, exports.getServerFile)(root);
|
|
364
375
|
await (0, exports.unpatchFile)(baseServerFile);
|
|
365
|
-
const nextServerFile = getServerFile(root, false);
|
|
376
|
+
const nextServerFile = (0, exports.getServerFile)(root, false);
|
|
366
377
|
if (nextServerFile !== baseServerFile) {
|
|
367
378
|
await (0, exports.unpatchFile)(nextServerFile);
|
|
368
379
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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.splitApiRoutes = void 0;
|
|
7
|
+
const destr_1 = __importDefault(require("destr"));
|
|
8
|
+
/**
|
|
9
|
+
* If this flag is enabled, we generate individual Lambda functions for API Routes.
|
|
10
|
+
* They're packed together in 50mb chunks to avoid hitting the Lambda size limit.
|
|
11
|
+
*
|
|
12
|
+
* To prevent bundling times from rising,
|
|
13
|
+
* we use the "none" bundling strategy where we fully rely on Next.js' `.nft.json` files.
|
|
14
|
+
* This should to a significant speedup, but is still experimental.
|
|
15
|
+
*
|
|
16
|
+
* If disabled, we bundle all API Routes into a single function.
|
|
17
|
+
* This is can lead to large bundle sizes.
|
|
18
|
+
*
|
|
19
|
+
* Disabled by default. Can be overriden using the NEXT_SPLIT_API_ROUTES env var.
|
|
20
|
+
*/
|
|
21
|
+
const splitApiRoutes = (featureFlags) => { var _a, _b; return (_b = (_a = (0, destr_1.default)(process.env.NEXT_SPLIT_API_ROUTES)) !== null && _a !== void 0 ? _a : featureFlags.next_split_api_routes) !== null && _b !== void 0 ? _b : false; };
|
|
22
|
+
exports.splitApiRoutes = splitApiRoutes;
|
package/lib/helpers/functions.js
CHANGED
|
@@ -3,38 +3,43 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.warnOnApiRoutes = exports.getExtendedApiRouteConfigs = exports.getApiRouteConfigs = exports.setupImageFunction = exports.generatePagesResolver = exports.generateFunctions = void 0;
|
|
6
|
+
exports.warnOnApiRoutes = exports.packSingleFunction = exports.getExtendedApiRouteConfigs = exports.getApiRouteConfigs = exports.getAPILambdas = exports.getAPIPRouteCommonDependencies = exports.traceNPMPackage = exports.setupImageFunction = exports.generatePagesResolver = exports.generateFunctions = void 0;
|
|
7
7
|
const node_bridge_1 = __importDefault(require("@vercel/node-bridge"));
|
|
8
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
9
|
const destr_1 = __importDefault(require("destr"));
|
|
10
10
|
const fs_extra_1 = require("fs-extra");
|
|
11
11
|
const outdent_1 = require("outdent");
|
|
12
12
|
const pathe_1 = require("pathe");
|
|
13
|
+
const tiny_glob_1 = __importDefault(require("tiny-glob"));
|
|
13
14
|
const constants_1 = require("../constants");
|
|
14
15
|
const getApiHandler_1 = require("../templates/getApiHandler");
|
|
15
16
|
const getHandler_1 = require("../templates/getHandler");
|
|
16
17
|
const getPageResolver_1 = require("../templates/getPageResolver");
|
|
17
18
|
const analysis_1 = require("./analysis");
|
|
19
|
+
const config_1 = require("./config");
|
|
18
20
|
const files_1 = require("./files");
|
|
19
21
|
const functionsMetaData_1 = require("./functionsMetaData");
|
|
22
|
+
const pack_1 = require("./pack");
|
|
20
23
|
const utils_1 = require("./utils");
|
|
21
|
-
const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir,
|
|
24
|
+
const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir, apiLambdas) => {
|
|
22
25
|
const publish = (0, pathe_1.resolve)(PUBLISH_DIR);
|
|
23
26
|
const functionsDir = (0, pathe_1.resolve)(INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC);
|
|
24
27
|
const functionDir = (0, pathe_1.join)(functionsDir, constants_1.HANDLER_FUNCTION_NAME);
|
|
25
28
|
const publishDir = (0, pathe_1.relative)(functionDir, publish);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
const nextServerModuleAbsoluteLocation = (0, files_1.getServerFile)(appDir, false);
|
|
30
|
+
const nextServerModuleRelativeLocation = nextServerModuleAbsoluteLocation
|
|
31
|
+
? (0, pathe_1.relative)(functionDir, nextServerModuleAbsoluteLocation)
|
|
32
|
+
: undefined;
|
|
33
|
+
for (const apiLambda of apiLambdas) {
|
|
34
|
+
const { functionName, routes, type, includedFiles } = apiLambda;
|
|
35
|
+
const apiHandlerSource = (0, getApiHandler_1.getApiHandler)({
|
|
36
|
+
// most api lambdas serve multiple routes, but scheduled functions need to be in separate lambdas.
|
|
37
|
+
// so routes[0] is safe to access.
|
|
38
|
+
schedule: type === "experimental-scheduled" /* ApiRouteType.SCHEDULED */ ? routes[0].config.schedule : undefined,
|
|
34
39
|
publishDir,
|
|
35
40
|
appDir: (0, pathe_1.relative)(functionDir, appDir),
|
|
41
|
+
nextServerModuleRelativeLocation,
|
|
36
42
|
});
|
|
37
|
-
const functionName = (0, utils_1.getFunctionNameForPage)(route, config.type === "experimental-background" /* ApiRouteType.BACKGROUND */);
|
|
38
43
|
await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir, functionName));
|
|
39
44
|
// write main API handler file
|
|
40
45
|
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, `${functionName}.js`), apiHandlerSource);
|
|
@@ -43,15 +48,28 @@ const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS
|
|
|
43
48
|
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'server.js'), (0, pathe_1.join)(functionsDir, functionName, 'server.js'));
|
|
44
49
|
await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, pathe_1.join)(functionsDir, functionName, 'handlerUtils.js'));
|
|
45
50
|
const resolveSourceFile = (file) => (0, pathe_1.join)(publish, 'server', file);
|
|
51
|
+
// TODO: this should be unneeded once we use the `none` bundler everywhere
|
|
46
52
|
const resolverSource = await (0, getPageResolver_1.getResolverForSourceFiles)({
|
|
47
53
|
functionsDir,
|
|
48
54
|
// These extra pages are always included by Next.js
|
|
49
|
-
sourceFiles: [
|
|
55
|
+
sourceFiles: [
|
|
56
|
+
...routes.map((route) => route.compiled),
|
|
57
|
+
'pages/_app.js',
|
|
58
|
+
'pages/_document.js',
|
|
59
|
+
'pages/_error.js',
|
|
60
|
+
].map(resolveSourceFile),
|
|
50
61
|
});
|
|
51
62
|
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, 'pages.js'), resolverSource);
|
|
63
|
+
const nfInternalFiles = await (0, tiny_glob_1.default)((0, pathe_1.join)(functionsDir, functionName, '**'));
|
|
64
|
+
includedFiles.push(...nfInternalFiles);
|
|
52
65
|
}
|
|
53
66
|
const writeHandler = async (functionName, functionTitle, isODB) => {
|
|
54
|
-
const handlerSource =
|
|
67
|
+
const handlerSource = (0, getHandler_1.getHandler)({
|
|
68
|
+
isODB,
|
|
69
|
+
publishDir,
|
|
70
|
+
appDir: (0, pathe_1.relative)(functionDir, appDir),
|
|
71
|
+
nextServerModuleRelativeLocation,
|
|
72
|
+
});
|
|
55
73
|
await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir, functionName));
|
|
56
74
|
// write main handler file (standard or ODB)
|
|
57
75
|
await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, functionName, `${functionName}.js`), handlerSource);
|
|
@@ -133,31 +151,143 @@ const setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIO
|
|
|
133
151
|
}
|
|
134
152
|
};
|
|
135
153
|
exports.setupImageFunction = setupImageFunction;
|
|
154
|
+
const traceRequiredServerFiles = async (publish) => {
|
|
155
|
+
const { files, relativeAppDir, config: { experimental: { outputFileTracingRoot }, }, } = await (0, config_1.getRequiredServerFiles)(publish);
|
|
156
|
+
const appDirRoot = (0, pathe_1.join)(outputFileTracingRoot, relativeAppDir);
|
|
157
|
+
const absoluteFiles = files.map((file) => (0, pathe_1.join)(appDirRoot, file));
|
|
158
|
+
absoluteFiles.push((0, pathe_1.join)(publish, 'required-server-files.json'));
|
|
159
|
+
return absoluteFiles;
|
|
160
|
+
};
|
|
161
|
+
const traceNextServer = async (publish) => {
|
|
162
|
+
const nextServerDeps = await (0, files_1.getDependenciesOfFile)((0, pathe_1.join)(publish, 'next-server.js'));
|
|
163
|
+
// during testing, i've seen `next-server` contain only one line.
|
|
164
|
+
// this is a sanity check to make sure we're getting all the deps.
|
|
165
|
+
if (nextServerDeps.length < 10) {
|
|
166
|
+
console.error(nextServerDeps);
|
|
167
|
+
throw new Error("next-server.js.nft.json didn't contain all dependencies.");
|
|
168
|
+
}
|
|
169
|
+
const filtered = nextServerDeps.filter((f) => {
|
|
170
|
+
// NFT detects a bunch of large development files that we don't need.
|
|
171
|
+
if (f.endsWith('.development.js'))
|
|
172
|
+
return false;
|
|
173
|
+
// not needed for API Routes!
|
|
174
|
+
if (f.endsWith('node_modules/sass/sass.dart.js'))
|
|
175
|
+
return false;
|
|
176
|
+
return true;
|
|
177
|
+
});
|
|
178
|
+
return filtered;
|
|
179
|
+
};
|
|
180
|
+
const traceNPMPackage = async (packageName, publish) => {
|
|
181
|
+
try {
|
|
182
|
+
return await (0, tiny_glob_1.default)((0, pathe_1.join)((0, pathe_1.dirname)(require.resolve(packageName, { paths: [publish] })), '**', '*'), {
|
|
183
|
+
absolute: true,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
if (process.env.NODE_ENV === 'test') {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
exports.traceNPMPackage = traceNPMPackage;
|
|
194
|
+
const getAPIPRouteCommonDependencies = async (publish) => {
|
|
195
|
+
const deps = await Promise.all([
|
|
196
|
+
traceRequiredServerFiles(publish),
|
|
197
|
+
traceNextServer(publish),
|
|
198
|
+
// used by our own bridge.js
|
|
199
|
+
(0, exports.traceNPMPackage)('follow-redirects', publish),
|
|
200
|
+
]);
|
|
201
|
+
return deps.flat(1);
|
|
202
|
+
};
|
|
203
|
+
exports.getAPIPRouteCommonDependencies = getAPIPRouteCommonDependencies;
|
|
204
|
+
const sum = (arr) => arr.reduce((v, current) => v + current, 0);
|
|
205
|
+
// TODO: cache results
|
|
206
|
+
const getBundleWeight = async (patterns) => {
|
|
207
|
+
const sizes = await Promise.all(patterns.flatMap(async (pattern) => {
|
|
208
|
+
const files = await (0, tiny_glob_1.default)(pattern);
|
|
209
|
+
return Promise.all(files.map(async (file) => {
|
|
210
|
+
const fStat = await (0, fs_extra_1.stat)(file);
|
|
211
|
+
if (fStat.isFile()) {
|
|
212
|
+
return fStat.size;
|
|
213
|
+
}
|
|
214
|
+
return 0;
|
|
215
|
+
}));
|
|
216
|
+
}));
|
|
217
|
+
return sum(sizes.flat(1));
|
|
218
|
+
};
|
|
219
|
+
const MB = 1024 * 1024;
|
|
220
|
+
const getAPILambdas = async (publish, baseDir, pageExtensions) => {
|
|
221
|
+
const commonDependencies = await (0, exports.getAPIPRouteCommonDependencies)(publish);
|
|
222
|
+
const threshold = 50 * MB - (await getBundleWeight(commonDependencies));
|
|
223
|
+
const apiRoutes = await (0, exports.getApiRouteConfigs)(publish, baseDir, pageExtensions);
|
|
224
|
+
const packFunctions = async (routes, type) => {
|
|
225
|
+
const weighedRoutes = await Promise.all(routes.map(async (route) => ({ value: route, weight: await getBundleWeight(route.includedFiles) })));
|
|
226
|
+
const bins = (0, pack_1.pack)(weighedRoutes, threshold);
|
|
227
|
+
return bins.map((bin, index) => ({
|
|
228
|
+
functionName: bin.length === 1 ? bin[0].functionName : `api-${index}`,
|
|
229
|
+
routes: bin,
|
|
230
|
+
includedFiles: [...commonDependencies, ...routes.flatMap((route) => route.includedFiles)],
|
|
231
|
+
type,
|
|
232
|
+
}));
|
|
233
|
+
};
|
|
234
|
+
const standardFunctions = apiRoutes.filter((route) => !(0, analysis_1.isEdgeConfig)(route.config.runtime) &&
|
|
235
|
+
route.config.type !== "experimental-background" /* ApiRouteType.BACKGROUND */ &&
|
|
236
|
+
route.config.type !== "experimental-scheduled" /* ApiRouteType.SCHEDULED */);
|
|
237
|
+
const scheduledFunctions = apiRoutes.filter((route) => route.config.type === "experimental-scheduled" /* ApiRouteType.SCHEDULED */);
|
|
238
|
+
const backgroundFunctions = apiRoutes.filter((route) => route.config.type === "experimental-background" /* ApiRouteType.BACKGROUND */);
|
|
239
|
+
const scheduledLambdas = scheduledFunctions.map(exports.packSingleFunction);
|
|
240
|
+
const [standardLambdas, backgroundLambdas] = await Promise.all([
|
|
241
|
+
packFunctions(standardFunctions),
|
|
242
|
+
packFunctions(backgroundFunctions, "experimental-background" /* ApiRouteType.BACKGROUND */),
|
|
243
|
+
]);
|
|
244
|
+
return [...standardLambdas, ...backgroundLambdas, ...scheduledLambdas];
|
|
245
|
+
};
|
|
246
|
+
exports.getAPILambdas = getAPILambdas;
|
|
136
247
|
/**
|
|
137
248
|
* Look for API routes, and extract the config from the source file.
|
|
138
249
|
*/
|
|
139
|
-
const getApiRouteConfigs = async (publish,
|
|
250
|
+
const getApiRouteConfigs = async (publish, appDir, pageExtensions) => {
|
|
140
251
|
const pages = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(publish, 'server', 'pages-manifest.json'));
|
|
141
252
|
const apiRoutes = Object.keys(pages).filter((page) => page.startsWith('/api/'));
|
|
142
253
|
// two possible places
|
|
143
254
|
// Ref: https://nextjs.org/docs/advanced-features/src-directory
|
|
144
|
-
const pagesDir = (0, pathe_1.join)(
|
|
145
|
-
const srcPagesDir = (0, pathe_1.join)(
|
|
255
|
+
const pagesDir = (0, pathe_1.join)(appDir, 'pages');
|
|
256
|
+
const srcPagesDir = (0, pathe_1.join)(appDir, 'src', 'pages');
|
|
146
257
|
return await Promise.all(apiRoutes.map(async (apiRoute) => {
|
|
147
|
-
const filePath = (0, files_1.getSourceFileForPage)(apiRoute, [pagesDir, srcPagesDir]);
|
|
148
|
-
|
|
258
|
+
const filePath = (0, files_1.getSourceFileForPage)(apiRoute, [pagesDir, srcPagesDir], pageExtensions);
|
|
259
|
+
const config = await (0, analysis_1.extractConfigFromFile)(filePath, appDir);
|
|
260
|
+
const functionName = (0, utils_1.getFunctionNameForPage)(apiRoute, config.type === "experimental-background" /* ApiRouteType.BACKGROUND */);
|
|
261
|
+
const compiled = pages[apiRoute];
|
|
262
|
+
const compiledPath = (0, pathe_1.join)(publish, 'server', compiled);
|
|
263
|
+
const routeDependencies = await (0, files_1.getDependenciesOfFile)(compiledPath);
|
|
264
|
+
const includedFiles = [compiledPath, ...routeDependencies];
|
|
265
|
+
return {
|
|
266
|
+
functionName,
|
|
267
|
+
route: apiRoute,
|
|
268
|
+
config,
|
|
269
|
+
compiled,
|
|
270
|
+
includedFiles,
|
|
271
|
+
};
|
|
149
272
|
}));
|
|
150
273
|
};
|
|
151
274
|
exports.getApiRouteConfigs = getApiRouteConfigs;
|
|
152
275
|
/**
|
|
153
276
|
* Looks for extended API routes (background and scheduled functions) and extract the config from the source file.
|
|
154
277
|
*/
|
|
155
|
-
const getExtendedApiRouteConfigs = async (publish,
|
|
156
|
-
const settledApiRoutes = await (0, exports.getApiRouteConfigs)(publish,
|
|
278
|
+
const getExtendedApiRouteConfigs = async (publish, appDir, pageExtensions) => {
|
|
279
|
+
const settledApiRoutes = await (0, exports.getApiRouteConfigs)(publish, appDir, pageExtensions);
|
|
157
280
|
// We only want to return the API routes that are background or scheduled functions
|
|
158
281
|
return settledApiRoutes.filter((apiRoute) => apiRoute.config.type !== undefined);
|
|
159
282
|
};
|
|
160
283
|
exports.getExtendedApiRouteConfigs = getExtendedApiRouteConfigs;
|
|
284
|
+
const packSingleFunction = (func) => ({
|
|
285
|
+
functionName: func.functionName,
|
|
286
|
+
includedFiles: func.includedFiles,
|
|
287
|
+
routes: [func],
|
|
288
|
+
type: func.config.type,
|
|
289
|
+
});
|
|
290
|
+
exports.packSingleFunction = packSingleFunction;
|
|
161
291
|
/**
|
|
162
292
|
* Warn the user of the caveats if they're using background or scheduled API routes
|
|
163
293
|
*/
|
package/lib/helpers/matchers.js
CHANGED
|
@@ -10,7 +10,7 @@ const stripLookahead = (regex) => {
|
|
|
10
10
|
}
|
|
11
11
|
try {
|
|
12
12
|
// Parse the regexp into an AST
|
|
13
|
-
const re = (0, regexp_tree_1.transform)(
|
|
13
|
+
const re = (0, regexp_tree_1.transform)(new RegExp(regex), {
|
|
14
14
|
Assertion(path) {
|
|
15
15
|
// Remove the lookahead
|
|
16
16
|
if (path.node.kind === 'Lookahead') {
|
|
@@ -30,8 +30,12 @@ exports.stripLookahead = stripLookahead;
|
|
|
30
30
|
// The Go regexp lib has alternative syntax for named capture groups
|
|
31
31
|
const transformCaptureGroups = (regex) => regex.replace(/\(\?<\w+>/, '(');
|
|
32
32
|
exports.transformCaptureGroups = transformCaptureGroups;
|
|
33
|
-
const
|
|
34
|
-
const
|
|
33
|
+
const LOCALIZED_REGEX_PREFIX_13_1 = '(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]{1,}))';
|
|
34
|
+
const OPTIONAL_REGEX_PREFIX_13_1 = '(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]{1,}))?';
|
|
35
|
+
const LOCALIZED_REGEX_PREFIX_13_3 = '(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/((?!_next\\/)[^/.]{1,}))';
|
|
36
|
+
const OPTIONAL_REGEX_PREFIX_13_3 = '(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/((?!_next\\/)[^/.]{1,}))?';
|
|
35
37
|
// Make the locale section of the matcher regex optional
|
|
36
|
-
const makeLocaleOptional = (regex) => regex
|
|
38
|
+
const makeLocaleOptional = (regex) => regex
|
|
39
|
+
.replace(LOCALIZED_REGEX_PREFIX_13_1, OPTIONAL_REGEX_PREFIX_13_1)
|
|
40
|
+
.replace(LOCALIZED_REGEX_PREFIX_13_3, OPTIONAL_REGEX_PREFIX_13_3);
|
|
37
41
|
exports.makeLocaleOptional = makeLocaleOptional;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pack = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Naïve linear packing algorithm.
|
|
6
|
+
* Takes items with weights, and packs them into boxes of a given threshold.
|
|
7
|
+
* If an item weight exceeds the threshold, it is put into a box of its own.
|
|
8
|
+
*
|
|
9
|
+
* We're using this to combine many API Routes into fewer Lambda functions.
|
|
10
|
+
*
|
|
11
|
+
* This does not compute an optimal solution.
|
|
12
|
+
* For that, we'd take the full dependency graph into account
|
|
13
|
+
* and try to pack routes with intersecting dependencies together.
|
|
14
|
+
* But since most of the lambda bundle consists of node_modules,
|
|
15
|
+
* that probably won't help much.
|
|
16
|
+
* In the future, we might think about using some graph-based analysis here!
|
|
17
|
+
* Too complicated for now.
|
|
18
|
+
*/
|
|
19
|
+
const pack = (items, threshold) => {
|
|
20
|
+
const result = [];
|
|
21
|
+
let currentBox = [];
|
|
22
|
+
let currentWeight = 0;
|
|
23
|
+
const sortedDescending = items.sort((a, b) => b.weight - a.weight);
|
|
24
|
+
for (const item of sortedDescending) {
|
|
25
|
+
const fitsInCurrentBox = currentWeight + item.weight <= threshold;
|
|
26
|
+
if (fitsInCurrentBox) {
|
|
27
|
+
currentBox.push(item.value);
|
|
28
|
+
currentWeight += item.weight;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
if (currentBox.length !== 0)
|
|
32
|
+
result.push(currentBox);
|
|
33
|
+
currentBox = [item.value];
|
|
34
|
+
currentWeight = item.weight;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (currentBox.length !== 0)
|
|
38
|
+
result.push(currentBox);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
exports.pack = pack;
|
package/lib/helpers/redirects.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateRedirects = exports.generateStaticRedirects = void 0;
|
|
6
|
+
exports.generateRedirects = exports.generateDynamicRewrites = exports.generateStaticRedirects = void 0;
|
|
4
7
|
const chalk_1 = require("chalk");
|
|
8
|
+
const destr_1 = __importDefault(require("destr"));
|
|
5
9
|
const fs_extra_1 = require("fs-extra");
|
|
6
10
|
const outdent_1 = require("outdent");
|
|
7
11
|
const pathe_1 = require("pathe");
|
|
@@ -160,7 +164,9 @@ const generateDynamicRewrites = ({ dynamicRoutes, prerenderedDynamicRoutes, midd
|
|
|
160
164
|
withData: true,
|
|
161
165
|
}));
|
|
162
166
|
}
|
|
163
|
-
else if (prerenderedDynamicRoutes[route.page].fallback === false &&
|
|
167
|
+
else if (prerenderedDynamicRoutes[route.page].fallback === false &&
|
|
168
|
+
!is404Isr &&
|
|
169
|
+
!(0, destr_1.default)(process.env.LEGACY_FALLBACK_FALSE)) {
|
|
164
170
|
dynamicRewrites.push(...(0, utils_1.redirectsForNext404Route)({ route: route.page, buildId, basePath, i18n }));
|
|
165
171
|
}
|
|
166
172
|
else {
|
|
@@ -177,7 +183,8 @@ const generateDynamicRewrites = ({ dynamicRoutes, prerenderedDynamicRoutes, midd
|
|
|
177
183
|
dynamicRewrites,
|
|
178
184
|
};
|
|
179
185
|
};
|
|
180
|
-
|
|
186
|
+
exports.generateDynamicRewrites = generateDynamicRewrites;
|
|
187
|
+
const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath, trailingSlash, appDir }, buildId, apiLambdas, }) => {
|
|
181
188
|
const { dynamicRoutes: prerenderedDynamicRoutes, routes: prerenderedStaticRoutes } = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(netlifyConfig.build.publish, 'prerender-manifest.json'));
|
|
182
189
|
const { dynamicRoutes, staticRoutes } = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(netlifyConfig.build.publish, 'routes-manifest.json'));
|
|
183
190
|
netlifyConfig.redirects.push(...generateHiddenPathRedirects({ basePath }));
|
|
@@ -187,7 +194,7 @@ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath,
|
|
|
187
194
|
// This is only used in prod, so dev uses `next dev` directly
|
|
188
195
|
netlifyConfig.redirects.push(
|
|
189
196
|
// API routes always need to be served from the regular function
|
|
190
|
-
...(0, utils_1.getApiRewrites)(basePath,
|
|
197
|
+
...(0, utils_1.getApiRewrites)(basePath, apiLambdas),
|
|
191
198
|
// Preview mode gets forced to the function, to bypass pre-rendered pages, but static files need to be skipped
|
|
192
199
|
...(await (0, utils_1.getPreviewRewrites)({ basePath, appDir })));
|
|
193
200
|
const middleware = await (0, files_1.getMiddleware)(netlifyConfig.build.publish);
|
|
@@ -215,7 +222,7 @@ const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath,
|
|
|
215
222
|
netlifyConfig.redirects.push(...(0, utils_1.redirectsForNextRoute)({ route: route.page, buildId, basePath, to: constants_1.HANDLER_FUNCTION_PATH, i18n }));
|
|
216
223
|
});
|
|
217
224
|
// Add rewrites for all dynamic routes (both SSR and ISR)
|
|
218
|
-
const { dynamicRewrites, dynamicRoutesThatMatchMiddleware } = generateDynamicRewrites({
|
|
225
|
+
const { dynamicRewrites, dynamicRoutesThatMatchMiddleware } = (0, exports.generateDynamicRewrites)({
|
|
219
226
|
dynamicRoutes,
|
|
220
227
|
prerenderedDynamicRoutes,
|
|
221
228
|
middleware,
|
package/lib/helpers/utils.js
CHANGED
|
@@ -105,8 +105,8 @@ const redirectsForNextRouteWithData = ({ route, dataRoute, basePath, to, status
|
|
|
105
105
|
force,
|
|
106
106
|
}));
|
|
107
107
|
exports.redirectsForNextRouteWithData = redirectsForNextRouteWithData;
|
|
108
|
-
const getApiRewrites = (basePath,
|
|
109
|
-
const apiRewrites =
|
|
108
|
+
const getApiRewrites = (basePath, apiLambdas) => {
|
|
109
|
+
const apiRewrites = apiLambdas.flatMap((lambda) => lambda.routes.map((apiRoute) => {
|
|
110
110
|
const [from] = (0, exports.toNetlifyRoute)(`${basePath}${apiRoute.route}`);
|
|
111
111
|
// Scheduled functions can't be invoked directly, so we 404 them.
|
|
112
112
|
if (apiRoute.config.type === "experimental-scheduled" /* ApiRouteType.SCHEDULED */) {
|
|
@@ -114,10 +114,10 @@ const getApiRewrites = (basePath, apiRoutes) => {
|
|
|
114
114
|
}
|
|
115
115
|
return {
|
|
116
116
|
from,
|
|
117
|
-
to: `/.netlify/functions/${
|
|
117
|
+
to: `/.netlify/functions/${lambda.functionName}`,
|
|
118
118
|
status: 200,
|
|
119
119
|
};
|
|
120
|
-
});
|
|
120
|
+
}));
|
|
121
121
|
return [
|
|
122
122
|
...apiRewrites,
|
|
123
123
|
{
|
|
@@ -169,6 +169,18 @@ const findModuleFromBase = ({ paths, candidates }) => {
|
|
|
169
169
|
// Ignore the error
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
+
// if we couldn't find a module from paths, let's try to resolve from here
|
|
173
|
+
for (const candidate of candidates) {
|
|
174
|
+
try {
|
|
175
|
+
const modulePath = require.resolve(candidate);
|
|
176
|
+
if (modulePath) {
|
|
177
|
+
return modulePath;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// Ignore the error
|
|
182
|
+
}
|
|
183
|
+
}
|
|
172
184
|
return null;
|
|
173
185
|
};
|
|
174
186
|
exports.findModuleFromBase = findModuleFromBase;
|
|
@@ -102,7 +102,7 @@ const checkForRootPublish = ({ publish, failBuild, }) => {
|
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
104
|
exports.checkForRootPublish = checkForRootPublish;
|
|
105
|
-
const checkZipSize = async (file, maxSize = constants_1.LAMBDA_MAX_SIZE) => {
|
|
105
|
+
const checkZipSize = async (file, maxSize = constants_1.LAMBDA_MAX_SIZE, warningSize = constants_1.LAMBDA_WARNING_SIZE) => {
|
|
106
106
|
// Requires contacting the Netlify Support team to fully enable.
|
|
107
107
|
// Enabling this without contacting them can result in failed deploys.
|
|
108
108
|
if ((0, utils_1.isBundleSizeCheckDisabled)()) {
|
|
@@ -114,12 +114,13 @@ const checkZipSize = async (file, maxSize = constants_1.LAMBDA_MAX_SIZE) => {
|
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
116
|
const fileSize = await fs_1.promises.stat(file).then(({ size }) => size);
|
|
117
|
-
if (fileSize <
|
|
117
|
+
if (fileSize < warningSize) {
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
// We don't fail the build, because the actual hard max size is larger so it might still succeed
|
|
121
|
-
console.log((0, chalk_1.
|
|
122
|
-
The function zip ${(0, chalk_1.
|
|
121
|
+
console.log((0, chalk_1.yellowBright)((0, outdent_1.outdent) `
|
|
122
|
+
The function zip ${(0, chalk_1.blueBright)((0, path_1.relative)(process.cwd(), file))} size is ${(0, pretty_bytes_1.default)(fileSize)}, which is larger than the recommended maximum size of ${(0, pretty_bytes_1.default)(warningSize)}.
|
|
123
|
+
This will fail the build if the unzipped size is bigger than the maximum size of ${(0, pretty_bytes_1.default)(maxSize)}.
|
|
123
124
|
There are a few reasons this could happen. You may have accidentally bundled a large dependency, or you might have a
|
|
124
125
|
large number of pre-rendered pages included.
|
|
125
126
|
`));
|
package/lib/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const config_1 = require("./helpers/config");
|
|
|
14
14
|
const dev_1 = require("./helpers/dev");
|
|
15
15
|
const edge_1 = require("./helpers/edge");
|
|
16
16
|
const files_1 = require("./helpers/files");
|
|
17
|
+
const flags_1 = require("./helpers/flags");
|
|
17
18
|
const functions_1 = require("./helpers/functions");
|
|
18
19
|
const redirects_1 = require("./helpers/redirects");
|
|
19
20
|
const utils_1 = require("./helpers/utils");
|
|
@@ -37,13 +38,14 @@ const plugin = {
|
|
|
37
38
|
// eslint-disable-next-line unicorn/consistent-destructuring
|
|
38
39
|
netlifyConfig.build.environment.NEXT_PRIVATE_TARGET = 'server';
|
|
39
40
|
},
|
|
40
|
-
|
|
41
|
+
// eslint-disable-next-line max-lines-per-function
|
|
42
|
+
async onBuild({ constants, netlifyConfig, utils: { build: { failBuild }, }, featureFlags = {}, }) {
|
|
41
43
|
if ((0, utils_1.shouldSkip)()) {
|
|
42
44
|
return;
|
|
43
45
|
}
|
|
44
46
|
const { publish } = netlifyConfig.build;
|
|
45
47
|
(0, verification_1.checkNextSiteHasBuilt)({ publish, failBuild });
|
|
46
|
-
const { appDir, basePath, i18n, images, target, ignore, trailingSlash, outdir, experimental, routesManifest } = await (0, config_1.getNextConfig)({
|
|
48
|
+
const { appDir, basePath, i18n, images, target, ignore, trailingSlash, outdir, experimental, routesManifest, pageExtensions, } = await (0, config_1.getNextConfig)({
|
|
47
49
|
publish,
|
|
48
50
|
failBuild,
|
|
49
51
|
});
|
|
@@ -90,10 +92,18 @@ const plugin = {
|
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
const buildId = (0, fs_extra_1.readFileSync)((0, path_1.join)(publish, 'BUILD_ID'), 'utf8').trim();
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
const apiLambdas = (0, flags_1.splitApiRoutes)(featureFlags)
|
|
96
|
+
? await (0, functions_1.getAPILambdas)(publish, appDir, pageExtensions)
|
|
97
|
+
: await (0, functions_1.getExtendedApiRouteConfigs)(publish, appDir, pageExtensions).then((extendedRoutes) => extendedRoutes.map(functions_1.packSingleFunction));
|
|
98
|
+
await (0, functions_1.generateFunctions)(constants, appDir, apiLambdas);
|
|
96
99
|
await (0, functions_1.generatePagesResolver)(constants);
|
|
100
|
+
await (0, config_1.configureHandlerFunctions)({
|
|
101
|
+
netlifyConfig,
|
|
102
|
+
ignore,
|
|
103
|
+
publish: (0, path_1.relative)(process.cwd(), publish),
|
|
104
|
+
apiLambdas,
|
|
105
|
+
featureFlags,
|
|
106
|
+
});
|
|
97
107
|
await (0, files_1.movePublicFiles)({ appDir, outdir, publish, basePath });
|
|
98
108
|
await (0, files_1.patchNextFiles)(appDir);
|
|
99
109
|
if (!(0, destr_1.default)(process.env.SERVE_STATIC_FILES_FROM_ORIGIN)) {
|
|
@@ -115,7 +125,7 @@ const plugin = {
|
|
|
115
125
|
netlifyConfig,
|
|
116
126
|
nextConfig: { basePath, i18n, trailingSlash, appDir },
|
|
117
127
|
buildId,
|
|
118
|
-
|
|
128
|
+
apiLambdas,
|
|
119
129
|
});
|
|
120
130
|
await (0, edge_1.writeEdgeFunctions)({ netlifyConfig, routesManifest });
|
|
121
131
|
},
|
|
@@ -9,9 +9,10 @@ const path = require('path');
|
|
|
9
9
|
// eslint-disable-next-line n/prefer-global/url, n/prefer-global/url-search-params
|
|
10
10
|
const { URLSearchParams, URL } = require('url');
|
|
11
11
|
const { Bridge } = require('@vercel/node-bridge/bridge');
|
|
12
|
-
const { getMultiValueHeaders
|
|
12
|
+
const { getMultiValueHeaders } = require('./handlerUtils');
|
|
13
|
+
const { getNetlifyNextServer } = require('./server');
|
|
13
14
|
// We return a function and then call `toString()` on it to serialise it as the launcher function
|
|
14
|
-
const
|
|
15
|
+
const makeApiHandler = ({ conf, app, pageRoot, NextServer }) => {
|
|
15
16
|
var _a;
|
|
16
17
|
// Change working directory into the site root, unless using Nx, which moves the
|
|
17
18
|
// dist directory and handles this itself
|
|
@@ -25,8 +26,7 @@ const makeHandler = (conf, app, pageRoot, page) => {
|
|
|
25
26
|
require.resolve('./pages.js');
|
|
26
27
|
}
|
|
27
28
|
catch { }
|
|
28
|
-
|
|
29
|
-
;
|
|
29
|
+
const NetlifyNextServer = getNetlifyNextServer(NextServer);
|
|
30
30
|
(_a = process.env).NODE_ENV || (_a.NODE_ENV = 'production');
|
|
31
31
|
// We don't want to write ISR files to disk in the lambda environment
|
|
32
32
|
conf.experimental.isrFlushToDisk = false;
|
|
@@ -39,20 +39,22 @@ const makeHandler = (conf, app, pageRoot, page) => {
|
|
|
39
39
|
// We memoize this because it can be shared between requests, but don't instantiate it until
|
|
40
40
|
// the first request because we need the host and port.
|
|
41
41
|
let bridge;
|
|
42
|
-
const getBridge = (event) => {
|
|
42
|
+
const getBridge = (event, context) => {
|
|
43
43
|
if (bridge) {
|
|
44
44
|
return bridge;
|
|
45
45
|
}
|
|
46
|
+
const { clientContext: { custom: customContext }, } = context;
|
|
46
47
|
// Scheduled functions don't have a URL, but we need to give one so Next knows the route to serve
|
|
47
48
|
const url = event.rawUrl ? new URL(event.rawUrl) : new URL(path, process.env.URL || 'http://n');
|
|
48
49
|
const port = Number.parseInt(url.port) || 80;
|
|
49
|
-
const
|
|
50
|
-
const nextServer = new NextServer({
|
|
50
|
+
const nextServer = new NetlifyNextServer({
|
|
51
51
|
conf,
|
|
52
52
|
dir,
|
|
53
53
|
customServer: false,
|
|
54
54
|
hostname: url.hostname,
|
|
55
55
|
port,
|
|
56
|
+
}, {
|
|
57
|
+
revalidateToken: customContext === null || customContext === void 0 ? void 0 : customContext.odb_refresh_hooks,
|
|
56
58
|
});
|
|
57
59
|
const requestHandler = nextServer.getRequestHandler();
|
|
58
60
|
const server = new Server(async (req, res) => {
|
|
@@ -70,13 +72,11 @@ const makeHandler = (conf, app, pageRoot, page) => {
|
|
|
70
72
|
};
|
|
71
73
|
return async function handler(event, context) {
|
|
72
74
|
// Ensure that paths are encoded - but don't double-encode them
|
|
73
|
-
event.path =
|
|
75
|
+
event.path = new URL(event.rawUrl).pathname;
|
|
74
76
|
// Next expects to be able to parse the query from the URL
|
|
75
77
|
const query = new URLSearchParams(event.queryStringParameters).toString();
|
|
76
78
|
event.path = query ? `${event.path}?${query}` : event.path;
|
|
77
|
-
|
|
78
|
-
event.headers['x-matched-path'] = page;
|
|
79
|
-
const { headers, ...result } = await getBridge(event).launcher(event, context);
|
|
79
|
+
const { headers, ...result } = await getBridge(event, context).launcher(event, context);
|
|
80
80
|
// Convert all headers to multiValueHeaders
|
|
81
81
|
const multiValueHeaders = getMultiValueHeaders(headers);
|
|
82
82
|
multiValueHeaders['cache-control'] = ['public, max-age=0, must-revalidate'];
|
|
@@ -91,22 +91,29 @@ const makeHandler = (conf, app, pageRoot, page) => {
|
|
|
91
91
|
/**
|
|
92
92
|
* Handlers for API routes are simpler than page routes, but they each have a separate one
|
|
93
93
|
*/
|
|
94
|
-
const getApiHandler = ({
|
|
95
|
-
// This is a string, but if you have the right editor plugin it should format as js
|
|
94
|
+
const getApiHandler = ({ schedule, publishDir = '../../../.next', appDir = '../../..', nextServerModuleRelativeLocation, }) =>
|
|
95
|
+
// This is a string, but if you have the right editor plugin it should format as js (e.g. bierner.comment-tagged-templates in VS Code)
|
|
96
96
|
(0, outdent_1.outdent /* javascript */) `
|
|
97
|
+
process.env.NODE_ENV = 'production';
|
|
98
|
+
if (!${JSON.stringify(nextServerModuleRelativeLocation)}) {
|
|
99
|
+
throw new Error('Could not find Next.js server')
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
const { Server } = require("http");
|
|
98
103
|
// We copy the file here rather than requiring from the node module
|
|
99
104
|
const { Bridge } = require("./bridge");
|
|
100
|
-
const { getMultiValueHeaders
|
|
105
|
+
const { getMultiValueHeaders } = require('./handlerUtils')
|
|
106
|
+
const { getNetlifyNextServer } = require('./server')
|
|
107
|
+
const NextServer = require(${JSON.stringify(nextServerModuleRelativeLocation)}).default
|
|
101
108
|
|
|
102
|
-
${
|
|
109
|
+
${schedule ? `const { schedule } = require("@netlify/functions")` : ''}
|
|
103
110
|
|
|
104
111
|
|
|
105
112
|
const { config } = require("${publishDir}/required-server-files.json")
|
|
106
113
|
let staticManifest
|
|
107
114
|
const path = require("path");
|
|
108
115
|
const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", "server"));
|
|
109
|
-
const handler = (${
|
|
110
|
-
exports.handler = ${
|
|
116
|
+
const handler = (${makeApiHandler.toString()})({ conf: config, app: "${appDir}", pageRoot, NextServer })
|
|
117
|
+
exports.handler = ${schedule ? `schedule(${JSON.stringify(schedule)}, handler);` : 'handler'}
|
|
111
118
|
`;
|
|
112
119
|
exports.getApiHandler = getApiHandler;
|
|
@@ -11,10 +11,10 @@ const path = require('path');
|
|
|
11
11
|
const { URLSearchParams, URL } = require('url');
|
|
12
12
|
const { Bridge } = require('@vercel/node-bridge/bridge');
|
|
13
13
|
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getPrefetchResponse, normalizePath, } = require('./handlerUtils');
|
|
14
|
-
const {
|
|
14
|
+
const { getNetlifyNextServer } = require('./server');
|
|
15
15
|
// We return a function and then call `toString()` on it to serialise it as the launcher function
|
|
16
|
-
// eslint-disable-next-line max-
|
|
17
|
-
const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') => {
|
|
16
|
+
// eslint-disable-next-line max-lines-per-function
|
|
17
|
+
const makeHandler = ({ conf, app, pageRoot, NextServer, staticManifest = [], mode = 'ssr' }) => {
|
|
18
18
|
var _a;
|
|
19
19
|
// Change working directory into the site root, unless using Nx, which moves the
|
|
20
20
|
// dist directory and handles this itself
|
|
@@ -28,6 +28,7 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
|
|
|
28
28
|
require.resolve('./pages.js');
|
|
29
29
|
}
|
|
30
30
|
catch { }
|
|
31
|
+
const NetlifyNextServer = getNetlifyNextServer(NextServer);
|
|
31
32
|
const ONE_YEAR_IN_SECONDS = 31536000;
|
|
32
33
|
(_a = process.env).NODE_ENV || (_a.NODE_ENV = 'production');
|
|
33
34
|
// We don't want to write ISR files to disk in the lambda environment
|
|
@@ -59,7 +60,7 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
|
|
|
59
60
|
hostname: url.hostname,
|
|
60
61
|
port,
|
|
61
62
|
}, {
|
|
62
|
-
revalidateToken: customContext.odb_refresh_hooks,
|
|
63
|
+
revalidateToken: customContext === null || customContext === void 0 ? void 0 : customContext.odb_refresh_hooks,
|
|
63
64
|
});
|
|
64
65
|
const requestHandler = nextServer.getRequestHandler();
|
|
65
66
|
const server = new Server(async (req, res) => {
|
|
@@ -86,13 +87,6 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
|
|
|
86
87
|
// Next expects to be able to parse the query from the URL
|
|
87
88
|
const query = new URLSearchParams(event.queryStringParameters).toString();
|
|
88
89
|
event.path = query ? `${event.path}?${query}` : event.path;
|
|
89
|
-
const graphToken = event.netlifyGraphToken;
|
|
90
|
-
if (graphToken && requestMode !== 'ssr') {
|
|
91
|
-
// Prefix with underscore to help us determine the origin of the token
|
|
92
|
-
// allows us to write better error messages
|
|
93
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
94
|
-
process.env._NETLIFY_GRAPH_TOKEN = graphToken;
|
|
95
|
-
}
|
|
96
90
|
const { headers, ...result } = await getBridge(event, context).launcher(event, context);
|
|
97
91
|
// Convert all headers to multiValueHeaders
|
|
98
92
|
const multiValueHeaders = getMultiValueHeaders(headers);
|
|
@@ -137,15 +131,20 @@ const makeHandler = (conf, app, pageRoot, staticManifest = [], mode = 'ssr') =>
|
|
|
137
131
|
};
|
|
138
132
|
};
|
|
139
133
|
};
|
|
140
|
-
const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '../../..' }) =>
|
|
141
|
-
// This is a string, but if you have the right editor plugin it should format as js
|
|
134
|
+
const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '../../..', nextServerModuleRelativeLocation, }) =>
|
|
135
|
+
// This is a string, but if you have the right editor plugin it should format as js (e.g. bierner.comment-tagged-templates in VS Code)
|
|
142
136
|
(0, outdent_1.outdent /* javascript */) `
|
|
137
|
+
if (!${JSON.stringify(nextServerModuleRelativeLocation)}) {
|
|
138
|
+
throw new Error('Could not find Next.js server')
|
|
139
|
+
}
|
|
140
|
+
|
|
143
141
|
const { Server } = require("http");
|
|
144
142
|
const { promises } = require("fs");
|
|
145
143
|
// We copy the file here rather than requiring from the node module
|
|
146
144
|
const { Bridge } = require("./bridge");
|
|
147
|
-
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getPrefetchResponse,
|
|
148
|
-
const {
|
|
145
|
+
const { augmentFsModule, getMaxAge, getMultiValueHeaders, getPrefetchResponse, normalizePath } = require('./handlerUtils')
|
|
146
|
+
const { getNetlifyNextServer } = require('./server')
|
|
147
|
+
const NextServer = require(${JSON.stringify(nextServerModuleRelativeLocation)}).default
|
|
149
148
|
|
|
150
149
|
${isODB ? `const { builder } = require("@netlify/functions")` : ''}
|
|
151
150
|
const { config } = require("${publishDir}/required-server-files.json")
|
|
@@ -156,7 +155,7 @@ const getHandler = ({ isODB = false, publishDir = '../../../.next', appDir = '..
|
|
|
156
155
|
const path = require("path");
|
|
157
156
|
const pageRoot = path.resolve(path.join(__dirname, "${publishDir}", "server"));
|
|
158
157
|
exports.handler = ${isODB
|
|
159
|
-
? `builder((${makeHandler.toString()})(config, "${appDir}", pageRoot, staticManifest, 'odb'));`
|
|
160
|
-
: `(${makeHandler.toString()})(config, "${appDir}", pageRoot, staticManifest, 'ssr');`}
|
|
158
|
+
? `builder((${makeHandler.toString()})({ conf: config, app: "${appDir}", pageRoot, NextServer, staticManifest, mode: 'odb' }));`
|
|
159
|
+
: `(${makeHandler.toString()})({ conf: config, app: "${appDir}", pageRoot, NextServer, staticManifest, mode: 'ssr' });`}
|
|
161
160
|
`;
|
|
162
161
|
exports.getHandler = getHandler;
|
|
@@ -33,7 +33,7 @@ const getResolverForDependencies = ({ dependencies, functionDir, }) => {
|
|
|
33
33
|
// This file is purely to allow nft to know about these pages.
|
|
34
34
|
exports.resolvePages = () => {
|
|
35
35
|
try {
|
|
36
|
-
${pageFiles.join('\n ')}
|
|
36
|
+
${pageFiles.sort().join('\n ')}
|
|
37
37
|
} catch {}
|
|
38
38
|
}
|
|
39
39
|
`;
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.localizeDataRoute = exports.localizeRoute = exports.unlocalizeRoute = exports.normalizeRoute = exports.netlifyApiFetch = exports.normalizePath = exports.getPrefetchResponse = exports.
|
|
6
|
+
exports.localizeDataRoute = exports.localizeRoute = exports.unlocalizeRoute = exports.normalizeRoute = exports.netlifyApiFetch = exports.normalizePath = exports.getPrefetchResponse = exports.augmentFsModule = exports.getMultiValueHeaders = exports.getMaxAge = exports.downloadFile = void 0;
|
|
7
7
|
const fs_1 = require("fs");
|
|
8
8
|
const os_1 = require("os");
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
@@ -139,39 +139,6 @@ const augmentFsModule = ({ promises, staticManifest, pageRoot, getBase, }) => {
|
|
|
139
139
|
});
|
|
140
140
|
};
|
|
141
141
|
exports.augmentFsModule = augmentFsModule;
|
|
142
|
-
/**
|
|
143
|
-
* Next.js has an annoying habit of needing deep imports, but then moving those in patch releases. This is our abstraction.
|
|
144
|
-
*/
|
|
145
|
-
const getNextServer = () => {
|
|
146
|
-
let NextServer;
|
|
147
|
-
try {
|
|
148
|
-
// next >= 11.0.1. Yay breaking changes in patch releases!
|
|
149
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
150
|
-
NextServer = require('next/dist/server/next-server').default;
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
if (!error.message.includes("Cannot find module 'next/dist/server/next-server'")) {
|
|
154
|
-
// A different error, so rethrow it
|
|
155
|
-
throw error;
|
|
156
|
-
}
|
|
157
|
-
// Probably an old version of next, so fall through and find it elsewhere.
|
|
158
|
-
}
|
|
159
|
-
if (!NextServer) {
|
|
160
|
-
try {
|
|
161
|
-
// next < 11.0.1
|
|
162
|
-
// eslint-disable-next-line n/no-missing-require, import/no-unresolved, @typescript-eslint/no-var-requires
|
|
163
|
-
NextServer = require('next/dist/next-server/server/next-server').default;
|
|
164
|
-
}
|
|
165
|
-
catch (error) {
|
|
166
|
-
if (!error.message.includes("Cannot find module 'next/dist/next-server/server/next-server'")) {
|
|
167
|
-
throw error;
|
|
168
|
-
}
|
|
169
|
-
throw new Error('Could not find Next.js server');
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return NextServer;
|
|
173
|
-
};
|
|
174
|
-
exports.getNextServer = getNextServer;
|
|
175
142
|
/**
|
|
176
143
|
* Prefetch requests are used to check for middleware redirects, and shouldn't trigger SSR.
|
|
177
144
|
*/
|
package/lib/templates/server.js
CHANGED
|
@@ -1,81 +1,88 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.getNetlifyNextServer = void 0;
|
|
4
4
|
const handlerUtils_1 = require("./handlerUtils");
|
|
5
|
-
const
|
|
6
|
-
class NetlifyNextServer extends NextServer {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
getRequestHandler() {
|
|
19
|
-
const handler = super.getRequestHandler();
|
|
20
|
-
return async (req, res, parsedUrl) => {
|
|
21
|
-
// preserve the URL before Next.js mutates it for i18n
|
|
22
|
-
const { url, headers } = req;
|
|
23
|
-
// handle the original res.revalidate() request
|
|
24
|
-
await handler(req, res, parsedUrl);
|
|
25
|
-
// handle on-demand revalidation by purging the ODB cache
|
|
26
|
-
if (res.statusCode === 200 && headers['x-prerender-revalidate'] && this.netlifyConfig.revalidateToken) {
|
|
27
|
-
await this.netlifyRevalidate(url);
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
async netlifyRevalidate(route) {
|
|
32
|
-
try {
|
|
33
|
-
// call netlify API to revalidate the path
|
|
34
|
-
const result = await (0, handlerUtils_1.netlifyApiFetch)({
|
|
35
|
-
endpoint: `sites/${process.env.SITE_ID}/refresh_on_demand_builders`,
|
|
36
|
-
payload: {
|
|
37
|
-
paths: this.getNetlifyPathsForRoute(route),
|
|
38
|
-
domain: this.hostname,
|
|
39
|
-
},
|
|
40
|
-
token: this.netlifyConfig.revalidateToken,
|
|
41
|
-
method: 'POST',
|
|
42
|
-
});
|
|
43
|
-
if (!result.ok) {
|
|
44
|
-
throw new Error(result.message);
|
|
45
|
-
}
|
|
5
|
+
const getNetlifyNextServer = (NextServer) => {
|
|
6
|
+
class NetlifyNextServer extends NextServer {
|
|
7
|
+
constructor(options, netlifyConfig) {
|
|
8
|
+
super(options);
|
|
9
|
+
this.netlifyConfig = netlifyConfig;
|
|
10
|
+
// copy the prerender manifest so it doesn't get mutated by Next.js
|
|
11
|
+
const manifest = this.getPrerenderManifest();
|
|
12
|
+
this.netlifyPrerenderManifest = {
|
|
13
|
+
...manifest,
|
|
14
|
+
routes: { ...manifest.routes },
|
|
15
|
+
dynamicRoutes: { ...manifest.dynamicRoutes },
|
|
16
|
+
};
|
|
46
17
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
18
|
+
getRequestHandler() {
|
|
19
|
+
const handler = super.getRequestHandler();
|
|
20
|
+
return async (req, res, parsedUrl) => {
|
|
21
|
+
// preserve the URL before Next.js mutates it for i18n
|
|
22
|
+
const { url, headers } = req;
|
|
23
|
+
if (headers['x-prerender-revalidate'] && this.netlifyConfig.revalidateToken) {
|
|
24
|
+
// handle on-demand revalidation by purging the ODB cache
|
|
25
|
+
await this.netlifyRevalidate(url);
|
|
26
|
+
res = res;
|
|
27
|
+
res.statusCode = 200;
|
|
28
|
+
res.setHeader('x-nextjs-cache', 'REVALIDATED');
|
|
29
|
+
res.send();
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return handler(req, res, parsedUrl);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
50
35
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
36
|
+
async netlifyRevalidate(route) {
|
|
37
|
+
try {
|
|
38
|
+
// call netlify API to revalidate the path
|
|
39
|
+
const result = await (0, handlerUtils_1.netlifyApiFetch)({
|
|
40
|
+
endpoint: `sites/${process.env.SITE_ID}/refresh_on_demand_builders`,
|
|
41
|
+
payload: {
|
|
42
|
+
paths: this.getNetlifyPathsForRoute(route),
|
|
43
|
+
domain: this.hostname,
|
|
44
|
+
},
|
|
45
|
+
token: this.netlifyConfig.revalidateToken,
|
|
46
|
+
method: 'POST',
|
|
47
|
+
});
|
|
48
|
+
if (!result.ok) {
|
|
49
|
+
throw new Error(result.message);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.log(`Error revalidating ${route}:`, error.message);
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
61
56
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// replace the dynamic segments with the actual values
|
|
71
|
-
const interpolatedDataRoute = dataRoute.replace(/\[(.*?)]/g, () => matches.shift());
|
|
72
|
-
const normalizedDataRoute = i18n
|
|
73
|
-
? (0, handlerUtils_1.localizeDataRoute)(interpolatedDataRoute, normalizedRoute)
|
|
74
|
-
: interpolatedDataRoute;
|
|
57
|
+
getNetlifyPathsForRoute(route) {
|
|
58
|
+
const { i18n } = this.nextConfig;
|
|
59
|
+
const { routes, dynamicRoutes } = this.netlifyPrerenderManifest;
|
|
60
|
+
// matches static routes
|
|
61
|
+
const normalizedRoute = (0, handlerUtils_1.normalizeRoute)(i18n ? (0, handlerUtils_1.localizeRoute)(route, i18n) : route);
|
|
62
|
+
if (normalizedRoute in routes) {
|
|
63
|
+
const { dataRoute } = routes[normalizedRoute];
|
|
64
|
+
const normalizedDataRoute = i18n ? (0, handlerUtils_1.localizeDataRoute)(dataRoute, normalizedRoute) : dataRoute;
|
|
75
65
|
return [route, normalizedDataRoute];
|
|
76
66
|
}
|
|
67
|
+
// matches dynamic routes
|
|
68
|
+
const unlocalizedRoute = i18n ? (0, handlerUtils_1.unlocalizeRoute)(normalizedRoute, i18n) : normalizedRoute;
|
|
69
|
+
for (const dynamicRoute in dynamicRoutes) {
|
|
70
|
+
const { dataRoute, routeRegex } = dynamicRoutes[dynamicRoute];
|
|
71
|
+
const matches = unlocalizedRoute.match(routeRegex);
|
|
72
|
+
if ((matches === null || matches === void 0 ? void 0 : matches.length) > 0) {
|
|
73
|
+
// remove the first match, which is the full route
|
|
74
|
+
matches.shift();
|
|
75
|
+
// replace the dynamic segments with the actual values
|
|
76
|
+
const interpolatedDataRoute = dataRoute.replace(/\[(.*?)]/g, () => matches.shift());
|
|
77
|
+
const normalizedDataRoute = i18n
|
|
78
|
+
? (0, handlerUtils_1.localizeDataRoute)(interpolatedDataRoute, normalizedRoute)
|
|
79
|
+
: interpolatedDataRoute;
|
|
80
|
+
return [route, normalizedDataRoute];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`not an ISR route`);
|
|
77
84
|
}
|
|
78
|
-
throw new Error(`not an ISR route`);
|
|
79
85
|
}
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
return NetlifyNextServer;
|
|
87
|
+
};
|
|
88
|
+
exports.getNetlifyNextServer = getNetlifyNextServer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/plugin-nextjs",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.37.0",
|
|
4
4
|
"description": "Run Next.js seamlessly on Netlify",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@netlify/esbuild": "0.14.39",
|
|
15
|
-
"@netlify/functions": "^1.
|
|
15
|
+
"@netlify/functions": "^1.6.0",
|
|
16
16
|
"@netlify/ipx": "^1.4.0",
|
|
17
17
|
"@vercel/node-bridge": "^2.1.0",
|
|
18
18
|
"chalk": "^4.1.2",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@delucis/if-env": "^1.1.2",
|
|
40
|
-
"@netlify/build": "^29.
|
|
40
|
+
"@netlify/build": "^29.11.4",
|
|
41
41
|
"@types/fs-extra": "^9.0.13",
|
|
42
42
|
"@types/jest": "^27.4.1",
|
|
43
43
|
"@types/merge-stream": "^1.1.2",
|
|
44
44
|
"@types/node": "^17.0.25",
|
|
45
|
-
"next": "^13.
|
|
45
|
+
"next": "^13.3.0",
|
|
46
46
|
"npm-run-all": "^4.1.5",
|
|
47
47
|
"typescript": "^4.6.3"
|
|
48
48
|
},
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
// deno-lint-ignore-file
|
|
2
|
+
// deno-lint-ignore-file prefer-const no-unused-vars
|
|
3
3
|
import { decode as _base64Decode } from 'https://deno.land/std@0.175.0/encoding/base64.ts'
|
|
4
|
-
import
|
|
4
|
+
import BufferCompat from 'https://deno.land/std@0.175.0/node/buffer.ts'
|
|
5
|
+
import EventsCompat from 'https://deno.land/std@0.175.0/node/events.ts'
|
|
6
|
+
import AsyncHooksCompat from 'https://deno.land/std@0.175.0/node/async_hooks.ts'
|
|
7
|
+
import AssertCompat from 'https://deno.land/std@0.175.0/node/assert.ts'
|
|
8
|
+
import UtilCompat from 'https://deno.land/std@0.175.0/node/util.ts'
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* These are the shims, polyfills and other kludges to make Next.js work in standards-compliant runtime.
|
|
@@ -18,7 +22,7 @@ globalThis.EdgeRuntime = 'netlify-edge'
|
|
|
18
22
|
let _ENTRIES = {}
|
|
19
23
|
|
|
20
24
|
// Next.js expects this as a global
|
|
21
|
-
globalThis.AsyncLocalStorage =
|
|
25
|
+
globalThis.AsyncLocalStorage = AsyncHooksCompat.AsyncLocalStorage
|
|
22
26
|
|
|
23
27
|
// Next.js uses this extension to the Headers API implemented by Cloudflare workerd
|
|
24
28
|
if (!('getAll' in Headers.prototype)) {
|
|
@@ -48,6 +52,26 @@ const fetch /* type {typeof globalThis.fetch} */ = async (url, init) => {
|
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
// Shim native modules that Vercel makes available
|
|
56
|
+
if (typeof require === 'undefined') {
|
|
57
|
+
globalThis.require = (name) => {
|
|
58
|
+
switch (name.replace(/^node:/, '')) {
|
|
59
|
+
case 'buffer':
|
|
60
|
+
return BufferCompat
|
|
61
|
+
case 'events':
|
|
62
|
+
return EventsCompat
|
|
63
|
+
case 'async_hooks':
|
|
64
|
+
return AsyncHooksCompat
|
|
65
|
+
case 'assert':
|
|
66
|
+
return AssertCompat
|
|
67
|
+
case 'util':
|
|
68
|
+
return UtilCompat
|
|
69
|
+
default:
|
|
70
|
+
throw new ReferenceError(`Native module not found: ${name}`)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
51
75
|
// Next edge runtime uses "self" as a function-scoped global-like object, but some of the older polyfills expect it to equal globalThis
|
|
52
76
|
// See https://nextjs.org/docs/basic-features/supported-browsers-features#polyfills
|
|
53
77
|
const self = { ...globalThis, fetch }
|