@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.
- package/lib/constants.js +19 -10
- package/lib/helpers/cache.js +11 -6
- package/lib/helpers/config.js +20 -77
- package/lib/helpers/files.js +221 -42
- package/lib/helpers/functions.js +36 -30
- package/lib/helpers/redirects.js +130 -0
- package/lib/helpers/utils.js +32 -0
- package/lib/helpers/verification.js +70 -40
- package/lib/index.js +39 -29
- package/lib/templates/getHandler.js +38 -94
- package/lib/templates/getPageResolver.js +20 -11
- package/lib/templates/handlerUtils.js +162 -0
- package/lib/templates/ipx.js +11 -7
- package/package.json +14 -12
package/lib/constants.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"use strict";
|
|
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_NAME = exports.ODB_FUNCTION_NAME = exports.HANDLER_FUNCTION_NAME = void 0;
|
|
4
|
+
exports.HANDLER_FUNCTION_NAME = '___netlify-handler';
|
|
5
|
+
exports.ODB_FUNCTION_NAME = '___netlify-odb-handler';
|
|
6
|
+
exports.IMAGE_FUNCTION_NAME = '_ipx';
|
|
4
7
|
// These are paths in .next that shouldn't be publicly accessible
|
|
5
|
-
|
|
8
|
+
exports.HIDDEN_PATHS = [
|
|
6
9
|
'/cache/*',
|
|
7
10
|
'/server/*',
|
|
8
11
|
'/serverless/*',
|
|
@@ -13,9 +16,15 @@ const HIDDEN_PATHS = [
|
|
|
13
16
|
'/react-loadable-manifest.json',
|
|
14
17
|
'/BUILD_ID',
|
|
15
18
|
];
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
exports.ODB_FUNCTION_PATH = `/.netlify/builders/${exports.ODB_FUNCTION_NAME}`;
|
|
20
|
+
exports.HANDLER_FUNCTION_PATH = `/.netlify/functions/${exports.HANDLER_FUNCTION_NAME}`;
|
|
21
|
+
exports.DEFAULT_FUNCTIONS_SRC = 'netlify/functions';
|
|
22
|
+
exports.CATCH_ALL_REGEX = /\/\[\.{3}(.*)](.json)?$/;
|
|
23
|
+
exports.OPTIONAL_CATCH_ALL_REGEX = /\/\[{2}\.{3}(.*)]{2}(.json)?$/;
|
|
24
|
+
exports.DYNAMIC_PARAMETER_REGEX = /\/\[(.*?)]/g;
|
|
25
|
+
exports.MINIMUM_REVALIDATE_SECONDS = 60;
|
|
26
|
+
// 50MB, which is the documented max, though the hard max seems to be higher
|
|
27
|
+
exports.LAMBDA_MAX_SIZE = 1024 * 1024 * 50;
|
|
28
|
+
exports.DIVIDER = `
|
|
29
|
+
────────────────────────────────────────────────────────────────
|
|
30
|
+
`;
|
package/lib/helpers/cache.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
exports
|
|
3
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.saveCache = exports.restoreCache = void 0;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const restoreCache = async ({ cache, publish }) => {
|
|
6
|
+
const cacheDir = path_1.join(publish, 'cache');
|
|
4
7
|
if (await cache.restore(cacheDir)) {
|
|
5
8
|
console.log('Next.js cache restored.');
|
|
6
9
|
}
|
|
@@ -8,9 +11,10 @@ exports.restoreCache = async ({ cache, publish }) => {
|
|
|
8
11
|
console.log('No Next.js cache to restore.');
|
|
9
12
|
}
|
|
10
13
|
};
|
|
11
|
-
exports.
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
+
exports.restoreCache = restoreCache;
|
|
15
|
+
const saveCache = async ({ cache, publish }) => {
|
|
16
|
+
const cacheDir = path_1.join(publish, 'cache');
|
|
17
|
+
const buildManifest = path_1.join(publish, 'build-manifest.json');
|
|
14
18
|
if (await cache.save(cacheDir, { digests: [buildManifest] })) {
|
|
15
19
|
console.log('Next.js cache saved.');
|
|
16
20
|
}
|
|
@@ -18,3 +22,4 @@ exports.saveCache = async ({ cache, publish }) => {
|
|
|
18
22
|
console.log('No Next.js cache to save.');
|
|
19
23
|
}
|
|
20
24
|
};
|
|
25
|
+
exports.saveCache = saveCache;
|
package/lib/helpers/config.js
CHANGED
|
@@ -1,81 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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.configureHandlerFunctions = exports.getNextConfig = void 0;
|
|
7
|
+
const fs_extra_1 = require("fs-extra");
|
|
8
|
+
const pathe_1 = require("pathe");
|
|
9
|
+
const slash_1 = __importDefault(require("slash"));
|
|
10
|
+
const constants_1 = require("../constants");
|
|
5
11
|
const defaultFailBuild = (message, { error }) => {
|
|
6
12
|
throw new Error(`${message}\n${error && error.stack}`);
|
|
7
13
|
};
|
|
8
|
-
const {
|
|
9
|
-
const ODB_FUNCTION_PATH = `/.netlify/builders/${ODB_FUNCTION_NAME}`;
|
|
10
|
-
const HANDLER_FUNCTION_PATH = `/.netlify/functions/${HANDLER_FUNCTION_NAME}`;
|
|
11
|
-
const CATCH_ALL_REGEX = /\/\[\.{3}(.*)](.json)?$/;
|
|
12
|
-
const OPTIONAL_CATCH_ALL_REGEX = /\/\[{2}\.{3}(.*)]{2}(.json)?$/;
|
|
13
|
-
const DYNAMIC_PARAMETER_REGEX = /\/\[(.*?)]/g;
|
|
14
|
-
const getNetlifyRoutes = (nextRoute) => {
|
|
15
|
-
let netlifyRoutes = [nextRoute];
|
|
16
|
-
// If the route is an optional catch-all route, we need to add a second
|
|
17
|
-
// Netlify route for the base path (when no parameters are present).
|
|
18
|
-
// The file ending must be present!
|
|
19
|
-
if (OPTIONAL_CATCH_ALL_REGEX.test(nextRoute)) {
|
|
20
|
-
let netlifyRoute = nextRoute.replace(OPTIONAL_CATCH_ALL_REGEX, '$2');
|
|
21
|
-
// When optional catch-all route is at top-level, the regex on line 19 will
|
|
22
|
-
// create an empty string, but actually needs to be a forward slash
|
|
23
|
-
if (netlifyRoute === '')
|
|
24
|
-
netlifyRoute = '/';
|
|
25
|
-
// When optional catch-all route is at top-level, the regex on line 19 will
|
|
26
|
-
// create an incorrect route for the data route. For example, it creates
|
|
27
|
-
// /_next/data/%BUILDID%.json, but NextJS looks for
|
|
28
|
-
// /_next/data/%BUILDID%/index.json
|
|
29
|
-
netlifyRoute = netlifyRoute.replace(/(\/_next\/data\/[^/]+).json/, '$1/index.json');
|
|
30
|
-
// Add second route to the front of the array
|
|
31
|
-
netlifyRoutes.unshift(netlifyRoute);
|
|
32
|
-
}
|
|
33
|
-
// Replace catch-all, e.g., [...slug]
|
|
34
|
-
netlifyRoutes = netlifyRoutes.map((route) => route.replace(CATCH_ALL_REGEX, '/:$1/*'));
|
|
35
|
-
// Replace optional catch-all, e.g., [[...slug]]
|
|
36
|
-
netlifyRoutes = netlifyRoutes.map((route) => route.replace(OPTIONAL_CATCH_ALL_REGEX, '/*'));
|
|
37
|
-
// Replace dynamic parameters, e.g., [id]
|
|
38
|
-
netlifyRoutes = netlifyRoutes.map((route) => route.replace(DYNAMIC_PARAMETER_REGEX, '/:$1'));
|
|
39
|
-
return netlifyRoutes;
|
|
40
|
-
};
|
|
41
|
-
exports.generateRedirects = async ({ netlifyConfig, basePath, i18n }) => {
|
|
42
|
-
const { dynamicRoutes } = await readJSON(join(netlifyConfig.build.publish, 'prerender-manifest.json'));
|
|
43
|
-
const redirects = [];
|
|
44
|
-
netlifyConfig.redirects.push(...HIDDEN_PATHS.map((path) => ({
|
|
45
|
-
from: `${basePath}${path}`,
|
|
46
|
-
to: '/404.html',
|
|
47
|
-
status: 404,
|
|
48
|
-
force: true,
|
|
49
|
-
})));
|
|
50
|
-
const dynamicRouteEntries = Object.entries(dynamicRoutes);
|
|
51
|
-
dynamicRouteEntries.sort((a, b) => a[0].localeCompare(b[0]));
|
|
52
|
-
dynamicRouteEntries.forEach(([route, { dataRoute, fallback }]) => {
|
|
53
|
-
// Add redirects if fallback is "null" (aka blocking) or true/a string
|
|
54
|
-
if (fallback === false) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
redirects.push(...getNetlifyRoutes(route), ...getNetlifyRoutes(dataRoute));
|
|
58
|
-
});
|
|
59
|
-
if (i18n) {
|
|
60
|
-
netlifyConfig.redirects.push({ from: `${basePath}/:locale/_next/static/*`, to: `/static/:splat`, status: 200 });
|
|
61
|
-
}
|
|
62
|
-
// This is only used in prod, so dev uses `next dev` directly
|
|
63
|
-
netlifyConfig.redirects.push({ from: `${basePath}/_next/static/*`, to: `/static/:splat`, status: 200 }, {
|
|
64
|
-
from: `${basePath}/*`,
|
|
65
|
-
to: HANDLER_FUNCTION_PATH,
|
|
66
|
-
status: 200,
|
|
67
|
-
conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
|
|
68
|
-
force: true,
|
|
69
|
-
}, ...redirects.map((redirect) => ({
|
|
70
|
-
from: `${basePath}${redirect}`,
|
|
71
|
-
to: ODB_FUNCTION_PATH,
|
|
72
|
-
status: 200,
|
|
73
|
-
})), { from: `${basePath}/*`, to: HANDLER_FUNCTION_PATH, status: 200 });
|
|
74
|
-
};
|
|
75
|
-
exports.getNextConfig = async function getNextConfig({ publish, failBuild = defaultFailBuild }) {
|
|
14
|
+
const getNextConfig = async function getNextConfig({ publish, failBuild = defaultFailBuild, }) {
|
|
76
15
|
try {
|
|
77
|
-
const { config, appDir, ignore } = await readJSON(join(publish, 'required-server-files.json'));
|
|
16
|
+
const { config, appDir, ignore } = await fs_extra_1.readJSON(pathe_1.join(publish, 'required-server-files.json'));
|
|
78
17
|
if (!config) {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
19
|
+
// @ts-ignore
|
|
79
20
|
return failBuild('Error loading your Next config');
|
|
80
21
|
}
|
|
81
22
|
return { ...config, appDir, ignore };
|
|
@@ -84,26 +25,27 @@ exports.getNextConfig = async function getNextConfig({ publish, failBuild = defa
|
|
|
84
25
|
return failBuild('Error loading your Next config', { error });
|
|
85
26
|
}
|
|
86
27
|
};
|
|
28
|
+
exports.getNextConfig = getNextConfig;
|
|
87
29
|
const resolveModuleRoot = (moduleName) => {
|
|
88
30
|
try {
|
|
89
|
-
return dirname(relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })));
|
|
31
|
+
return pathe_1.dirname(pathe_1.relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })));
|
|
90
32
|
}
|
|
91
33
|
catch (error) {
|
|
92
34
|
return null;
|
|
93
35
|
}
|
|
94
36
|
};
|
|
95
37
|
const DEFAULT_EXCLUDED_MODULES = ['sharp', 'electron'];
|
|
96
|
-
|
|
38
|
+
const configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) => {
|
|
97
39
|
var _a;
|
|
98
40
|
/* eslint-disable no-underscore-dangle */
|
|
99
41
|
(_a = netlifyConfig.functions)._ipx || (_a._ipx = {});
|
|
100
42
|
netlifyConfig.functions._ipx.node_bundler = 'nft';
|
|
101
|
-
[HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => {
|
|
43
|
+
[constants_1.HANDLER_FUNCTION_NAME, constants_1.ODB_FUNCTION_NAME].forEach((functionName) => {
|
|
102
44
|
var _a, _b;
|
|
103
45
|
(_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
|
|
104
46
|
netlifyConfig.functions[functionName].node_bundler = 'nft';
|
|
105
47
|
(_b = netlifyConfig.functions[functionName]).included_files || (_b.included_files = []);
|
|
106
|
-
netlifyConfig.functions[functionName].included_files.push(`${publish}/server/**`, `${publish}/serverless/**`, `${publish}/*.json`, `${publish}/BUILD_ID`, `${publish}/static/chunks/webpack-middleware*.js`, `!${publish}/server/**/*.js.nft.json`, ...ignore.map((path) => `!${
|
|
48
|
+
netlifyConfig.functions[functionName].included_files.push('.env', '.env.local', '.env.production', '.env.production.local', `${publish}/server/**`, `${publish}/serverless/**`, `${publish}/*.json`, `${publish}/BUILD_ID`, `${publish}/static/chunks/webpack-middleware*.js`, `!${publish}/server/**/*.js.nft.json`, ...ignore.map((path) => `!${slash_1.default(path)}`));
|
|
107
49
|
const nextRoot = resolveModuleRoot('next');
|
|
108
50
|
if (nextRoot) {
|
|
109
51
|
netlifyConfig.functions[functionName].included_files.push(`!${nextRoot}/dist/server/lib/squoosh/**/*.wasm`, `!${nextRoot}/dist/next-server/server/lib/squoosh/**/*.wasm`, `!${nextRoot}/dist/compiled/webpack/bundle4.js`, `!${nextRoot}/dist/compiled/webpack/bundle5.js`, `!${nextRoot}/dist/compiled/terser/bundle.min.js`);
|
|
@@ -116,3 +58,4 @@ exports.configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) =>
|
|
|
116
58
|
});
|
|
117
59
|
});
|
|
118
60
|
};
|
|
61
|
+
exports.configureHandlerFunctions = configureHandlerFunctions;
|
package/lib/helpers/files.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
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.movePublicFiles = exports.unpatchNextFiles = exports.patchNextFiles = exports.moveStaticPages = exports.matchesRewrite = exports.matchesRedirect = exports.matchMiddleware = exports.stripLocale = exports.isDynamicRoute = void 0;
|
|
7
|
+
/* eslint-disable max-lines */
|
|
8
|
+
const os_1 = require("os");
|
|
9
|
+
const chalk_1 = require("chalk");
|
|
10
|
+
const fs_extra_1 = require("fs-extra");
|
|
11
|
+
const globby_1 = __importDefault(require("globby"));
|
|
12
|
+
const outdent_1 = require("outdent");
|
|
13
|
+
const p_limit_1 = __importDefault(require("p-limit"));
|
|
14
|
+
const pathe_1 = require("pathe");
|
|
15
|
+
const slash_1 = __importDefault(require("slash"));
|
|
16
|
+
const constants_1 = require("../constants");
|
|
10
17
|
const TEST_ROUTE = /(|\/)\[[^/]+?](\/|\.html|$)/;
|
|
11
18
|
const isDynamicRoute = (route) => TEST_ROUTE.test(route);
|
|
19
|
+
exports.isDynamicRoute = isDynamicRoute;
|
|
12
20
|
const stripLocale = (rawPath, locales = []) => {
|
|
13
21
|
const [locale, ...segments] = rawPath.split('/');
|
|
14
22
|
if (locales.includes(locale)) {
|
|
@@ -16,48 +24,112 @@ const stripLocale = (rawPath, locales = []) => {
|
|
|
16
24
|
}
|
|
17
25
|
return rawPath;
|
|
18
26
|
};
|
|
19
|
-
|
|
27
|
+
exports.stripLocale = stripLocale;
|
|
28
|
+
const matchMiddleware = (middleware, filePath) => (middleware === null || middleware === void 0 ? void 0 : middleware.includes('')) ||
|
|
20
29
|
(middleware === null || middleware === void 0 ? void 0 : middleware.find((middlewarePath) => filePath === middlewarePath || filePath === `${middlewarePath}.html` || filePath.startsWith(`${middlewarePath}/`)));
|
|
21
30
|
exports.matchMiddleware = matchMiddleware;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
const matchesRedirect = (file, redirects) => {
|
|
32
|
+
if (!Array.isArray(redirects)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return redirects.some((redirect) => {
|
|
36
|
+
if (!redirect.regex || redirect.internal) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
// Strips the extension from the file path
|
|
40
|
+
return new RegExp(redirect.regex).test(`/${file.slice(0, -5)}`);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
exports.matchesRedirect = matchesRedirect;
|
|
44
|
+
const matchesRewrite = (file, rewrites) => {
|
|
45
|
+
if (Array.isArray(rewrites)) {
|
|
46
|
+
return exports.matchesRedirect(file, rewrites);
|
|
47
|
+
}
|
|
48
|
+
if (!Array.isArray(rewrites === null || rewrites === void 0 ? void 0 : rewrites.beforeFiles)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return exports.matchesRedirect(file, rewrites.beforeFiles);
|
|
52
|
+
};
|
|
53
|
+
exports.matchesRewrite = matchesRewrite;
|
|
54
|
+
// eslint-disable-next-line max-lines-per-function
|
|
55
|
+
const moveStaticPages = async ({ netlifyConfig, target, i18n, }) => {
|
|
25
56
|
console.log('Moving static page files to serve from CDN...');
|
|
26
|
-
const outputDir = join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless');
|
|
27
|
-
const root = join(outputDir, 'pages');
|
|
57
|
+
const outputDir = pathe_1.join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless');
|
|
58
|
+
const root = pathe_1.join(outputDir, 'pages');
|
|
59
|
+
const buildId = fs_extra_1.readFileSync(pathe_1.join(netlifyConfig.build.publish, 'BUILD_ID'), 'utf8').trim();
|
|
60
|
+
const dataDir = pathe_1.join('_next', 'data', buildId);
|
|
61
|
+
await fs_extra_1.ensureDir(dataDir);
|
|
28
62
|
// Load the middleware manifest so we can check if a file matches it before moving
|
|
29
63
|
let middleware;
|
|
30
|
-
const manifestPath = join(outputDir, 'middleware-manifest.json');
|
|
31
|
-
if (existsSync(manifestPath)) {
|
|
32
|
-
const manifest = await readJson(manifestPath);
|
|
64
|
+
const manifestPath = pathe_1.join(outputDir, 'middleware-manifest.json');
|
|
65
|
+
if (fs_extra_1.existsSync(manifestPath)) {
|
|
66
|
+
const manifest = await fs_extra_1.readJson(manifestPath);
|
|
33
67
|
if (manifest === null || manifest === void 0 ? void 0 : manifest.middleware) {
|
|
34
68
|
middleware = Object.keys(manifest.middleware).map((path) => path.slice(1));
|
|
35
69
|
}
|
|
36
70
|
}
|
|
71
|
+
const prerenderManifest = await fs_extra_1.readJson(pathe_1.join(netlifyConfig.build.publish, 'prerender-manifest.json'));
|
|
72
|
+
const { redirects, rewrites } = await fs_extra_1.readJson(pathe_1.join(netlifyConfig.build.publish, 'routes-manifest.json'));
|
|
73
|
+
const isrFiles = new Set();
|
|
74
|
+
const shortRevalidateRoutes = [];
|
|
75
|
+
Object.entries(prerenderManifest.routes).forEach(([route, { initialRevalidateSeconds }]) => {
|
|
76
|
+
if (initialRevalidateSeconds) {
|
|
77
|
+
// Find all files used by ISR routes
|
|
78
|
+
const trimmedPath = route.slice(1);
|
|
79
|
+
isrFiles.add(`${trimmedPath}.html`);
|
|
80
|
+
isrFiles.add(`${trimmedPath}.json`);
|
|
81
|
+
if (initialRevalidateSeconds < constants_1.MINIMUM_REVALIDATE_SECONDS) {
|
|
82
|
+
shortRevalidateRoutes.push({ Route: route, Revalidate: initialRevalidateSeconds });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
37
86
|
const files = [];
|
|
87
|
+
const filesManifest = {};
|
|
38
88
|
const moveFile = async (file) => {
|
|
39
|
-
const
|
|
89
|
+
const isData = file.endsWith('.json');
|
|
90
|
+
const source = pathe_1.join(root, file);
|
|
91
|
+
const targetFile = isData ? pathe_1.join(dataDir, file) : file;
|
|
40
92
|
files.push(file);
|
|
41
|
-
|
|
42
|
-
|
|
93
|
+
filesManifest[file] = targetFile;
|
|
94
|
+
const dest = pathe_1.join(netlifyConfig.build.publish, targetFile);
|
|
95
|
+
try {
|
|
96
|
+
await fs_extra_1.move(source, dest);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.warn('Error moving file', source, error);
|
|
100
|
+
}
|
|
43
101
|
};
|
|
44
102
|
// Move all static files, except error documents and nft manifests
|
|
45
|
-
const pages = await
|
|
103
|
+
const pages = await globby_1.default(['**/*.{html,json}', '!**/(500|404|*.js.nft).{html,json}'], {
|
|
46
104
|
cwd: root,
|
|
47
105
|
dot: true,
|
|
48
106
|
});
|
|
49
107
|
const matchingMiddleware = new Set();
|
|
50
108
|
const matchedPages = new Set();
|
|
109
|
+
const matchedRedirects = new Set();
|
|
110
|
+
const matchedRewrites = new Set();
|
|
51
111
|
// Limit concurrent file moves to number of cpus or 2 if there is only 1
|
|
52
|
-
const limit =
|
|
53
|
-
const promises = pages.map(
|
|
54
|
-
const filePath =
|
|
55
|
-
|
|
112
|
+
const limit = p_limit_1.default(Math.max(2, os_1.cpus().length));
|
|
113
|
+
const promises = pages.map((rawPath) => {
|
|
114
|
+
const filePath = slash_1.default(rawPath);
|
|
115
|
+
// Don't move ISR files, as they're used for the first request
|
|
116
|
+
if (isrFiles.has(filePath)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (exports.isDynamicRoute(filePath)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (exports.matchesRedirect(filePath, redirects)) {
|
|
123
|
+
matchedRedirects.add(filePath);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (exports.matchesRewrite(filePath, rewrites)) {
|
|
127
|
+
matchedRewrites.add(filePath);
|
|
56
128
|
return;
|
|
57
129
|
}
|
|
58
130
|
// Middleware matches against the unlocalised path
|
|
59
|
-
const unlocalizedPath = stripLocale(rawPath, i18n === null || i18n === void 0 ? void 0 : i18n.locales);
|
|
60
|
-
const middlewarePath = matchMiddleware(middleware, unlocalizedPath);
|
|
131
|
+
const unlocalizedPath = exports.stripLocale(rawPath, i18n === null || i18n === void 0 ? void 0 : i18n.locales);
|
|
132
|
+
const middlewarePath = exports.matchMiddleware(middleware, unlocalizedPath);
|
|
61
133
|
// If a file matches middleware it can't be offloaded to the CDN, and needs to stay at the origin to be served by next/server
|
|
62
134
|
if (middlewarePath) {
|
|
63
135
|
matchingMiddleware.add(middlewarePath);
|
|
@@ -69,36 +141,143 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
|
|
|
69
141
|
await Promise.all(promises);
|
|
70
142
|
console.log(`Moved ${files.length} files`);
|
|
71
143
|
if (matchedPages.size !== 0) {
|
|
72
|
-
console.log(yellowBright(outdent `
|
|
73
|
-
Skipped moving ${matchedPages.size} ${matchedPages.size === 1 ? 'file because it matches' : 'files because they match'} middleware, so cannot be deployed to the CDN and will be served from the origin instead.
|
|
144
|
+
console.log(chalk_1.yellowBright(outdent_1.outdent `
|
|
145
|
+
Skipped moving ${matchedPages.size} ${matchedPages.size === 1 ? 'file because it matches' : 'files because they match'} middleware, so cannot be deployed to the CDN and will be served from the origin instead.
|
|
146
|
+
This is fine, but we're letting you know because it may not be what you expect.
|
|
74
147
|
`));
|
|
75
|
-
console.log(outdent `
|
|
148
|
+
console.log(outdent_1.outdent `
|
|
76
149
|
The following middleware matched statically-rendered pages:
|
|
77
150
|
|
|
78
|
-
${yellowBright([...matchingMiddleware].map((mid) => `- /${mid}/_middleware`).join('\n'))}
|
|
151
|
+
${chalk_1.yellowBright([...matchingMiddleware].map((mid) => `- /${mid}/_middleware`).join('\n'))}
|
|
152
|
+
${constants_1.DIVIDER}
|
|
79
153
|
`);
|
|
80
154
|
// There could potentially be thousands of matching pages, so we don't want to spam the console with this
|
|
81
155
|
if (matchedPages.size < 50) {
|
|
82
|
-
console.log(outdent `
|
|
156
|
+
console.log(outdent_1.outdent `
|
|
83
157
|
The following files matched middleware and were not moved to the CDN:
|
|
84
158
|
|
|
85
|
-
${yellowBright([...matchedPages].map((mid) => `- ${mid}`).join('\n'))}
|
|
159
|
+
${chalk_1.yellowBright([...matchedPages].map((mid) => `- ${mid}`).join('\n'))}
|
|
160
|
+
${constants_1.DIVIDER}
|
|
161
|
+
`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (matchedRedirects.size !== 0 || matchedRewrites.size !== 0) {
|
|
165
|
+
console.log(chalk_1.yellowBright(outdent_1.outdent `
|
|
166
|
+
Skipped moving ${matchedRedirects.size + matchedRewrites.size} files because they match redirects or beforeFiles rewrites, so cannot be deployed to the CDN and will be served from the origin instead.
|
|
167
|
+
`));
|
|
168
|
+
if (matchedRedirects.size < 50 && matchedRedirects.size !== 0) {
|
|
169
|
+
console.log(outdent_1.outdent `
|
|
170
|
+
The following files matched redirects and were not moved to the CDN:
|
|
171
|
+
|
|
172
|
+
${chalk_1.yellowBright([...matchedRedirects].map((mid) => `- ${mid}`).join('\n'))}
|
|
173
|
+
${constants_1.DIVIDER}
|
|
174
|
+
`);
|
|
175
|
+
}
|
|
176
|
+
if (matchedRewrites.size < 50 && matchedRewrites.size !== 0) {
|
|
177
|
+
console.log(outdent_1.outdent `
|
|
178
|
+
The following files matched beforeFiles rewrites and were not moved to the CDN:
|
|
179
|
+
|
|
180
|
+
${chalk_1.yellowBright([...matchedRewrites].map((mid) => `- ${mid}`).join('\n'))}
|
|
181
|
+
${constants_1.DIVIDER}
|
|
86
182
|
`);
|
|
87
183
|
}
|
|
88
184
|
}
|
|
89
185
|
// Write the manifest for use in the serverless functions
|
|
90
|
-
await writeJson(join(netlifyConfig.build.publish, 'static-manifest.json'),
|
|
186
|
+
await fs_extra_1.writeJson(pathe_1.join(netlifyConfig.build.publish, 'static-manifest.json'), Object.entries(filesManifest));
|
|
91
187
|
if (i18n === null || i18n === void 0 ? void 0 : i18n.defaultLocale) {
|
|
92
188
|
// Copy the default locale into the root
|
|
93
|
-
const defaultLocaleDir = join(netlifyConfig.build.publish, i18n.defaultLocale);
|
|
94
|
-
if (existsSync(defaultLocaleDir)) {
|
|
95
|
-
await copy(defaultLocaleDir, `${netlifyConfig.build.publish}/`);
|
|
189
|
+
const defaultLocaleDir = pathe_1.join(netlifyConfig.build.publish, i18n.defaultLocale);
|
|
190
|
+
if (fs_extra_1.existsSync(defaultLocaleDir)) {
|
|
191
|
+
await fs_extra_1.copy(defaultLocaleDir, `${netlifyConfig.build.publish}/`);
|
|
192
|
+
}
|
|
193
|
+
const defaultLocaleIndex = pathe_1.join(netlifyConfig.build.publish, `${i18n.defaultLocale}.html`);
|
|
194
|
+
const indexHtml = pathe_1.join(netlifyConfig.build.publish, 'index.html');
|
|
195
|
+
if (fs_extra_1.existsSync(defaultLocaleIndex) && !fs_extra_1.existsSync(indexHtml)) {
|
|
196
|
+
try {
|
|
197
|
+
await fs_extra_1.copy(defaultLocaleIndex, indexHtml, { overwrite: false });
|
|
198
|
+
await fs_extra_1.copy(pathe_1.join(netlifyConfig.build.publish, `${i18n.defaultLocale}.json`), pathe_1.join(netlifyConfig.build.publish, 'index.json'), { overwrite: false });
|
|
199
|
+
}
|
|
200
|
+
catch { }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (shortRevalidateRoutes.length !== 0) {
|
|
204
|
+
console.log(outdent_1.outdent `
|
|
205
|
+
The following routes use "revalidate" values of under ${constants_1.MINIMUM_REVALIDATE_SECONDS} seconds, which is not supported.
|
|
206
|
+
They will use a revalidate time of ${constants_1.MINIMUM_REVALIDATE_SECONDS} seconds instead.
|
|
207
|
+
`);
|
|
208
|
+
console.table(shortRevalidateRoutes);
|
|
209
|
+
// TODO: add these docs
|
|
210
|
+
// console.log(
|
|
211
|
+
// outdent`
|
|
212
|
+
// For more information, see https://ntl.fyi/next-revalidate-time
|
|
213
|
+
// ${DIVIDER}
|
|
214
|
+
// `,
|
|
215
|
+
// )
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
exports.moveStaticPages = moveStaticPages;
|
|
219
|
+
const patchFile = async ({ file, from, to }) => {
|
|
220
|
+
if (!fs_extra_1.existsSync(file)) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const content = await fs_extra_1.readFile(file, 'utf8');
|
|
224
|
+
if (content.includes(to)) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const newContent = content.replace(from, to);
|
|
228
|
+
await fs_extra_1.writeFile(`${file}.orig`, content);
|
|
229
|
+
await fs_extra_1.writeFile(file, newContent);
|
|
230
|
+
};
|
|
231
|
+
const getServerFile = (root) => {
|
|
232
|
+
let serverFile;
|
|
233
|
+
try {
|
|
234
|
+
serverFile = require.resolve('next/dist/server/next-server', { paths: [root] });
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Ignore
|
|
238
|
+
}
|
|
239
|
+
if (!serverFile) {
|
|
240
|
+
try {
|
|
241
|
+
// eslint-disable-next-line node/no-missing-require
|
|
242
|
+
serverFile = require.resolve('next/dist/next-server/server/next-server', { paths: [root] });
|
|
96
243
|
}
|
|
244
|
+
catch {
|
|
245
|
+
// Ignore
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return serverFile;
|
|
249
|
+
};
|
|
250
|
+
const patchNextFiles = async (root) => {
|
|
251
|
+
const serverFile = getServerFile(root);
|
|
252
|
+
console.log(`Patching ${serverFile}`);
|
|
253
|
+
if (serverFile) {
|
|
254
|
+
await patchFile({
|
|
255
|
+
file: serverFile,
|
|
256
|
+
from: `let ssgCacheKey = `,
|
|
257
|
+
to: `let ssgCacheKey = process.env._BYPASS_SSG || `,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
exports.patchNextFiles = patchNextFiles;
|
|
262
|
+
const unpatchNextFiles = async (root) => {
|
|
263
|
+
const serverFile = getServerFile(root);
|
|
264
|
+
const origFile = `${serverFile}.orig`;
|
|
265
|
+
if (fs_extra_1.existsSync(origFile)) {
|
|
266
|
+
await fs_extra_1.move(origFile, serverFile, { overwrite: true });
|
|
97
267
|
}
|
|
98
268
|
};
|
|
99
|
-
exports.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
269
|
+
exports.unpatchNextFiles = unpatchNextFiles;
|
|
270
|
+
const movePublicFiles = async ({ appDir, outdir, publish, }) => {
|
|
271
|
+
// `outdir` is a config property added when using Next.js with Nx. It's typically
|
|
272
|
+
// a relative path outside of the appDir, e.g. '../../dist/apps/<app-name>', and
|
|
273
|
+
// the parent directory of the .next directory.
|
|
274
|
+
// If it exists, copy the files from the public folder there in order to include
|
|
275
|
+
// any files that were generated during the build. Otherwise, copy the public
|
|
276
|
+
// directory from the original app directory.
|
|
277
|
+
const publicDir = outdir ? pathe_1.join(appDir, outdir, 'public') : pathe_1.join(appDir, 'public');
|
|
278
|
+
if (fs_extra_1.existsSync(publicDir)) {
|
|
279
|
+
await fs_extra_1.copy(publicDir, `${publish}/`);
|
|
103
280
|
}
|
|
104
281
|
};
|
|
282
|
+
exports.movePublicFiles = movePublicFiles;
|
|
283
|
+
/* eslint-enable max-lines */
|
package/lib/helpers/functions.js
CHANGED
|
@@ -1,56 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupImageFunction = exports.generatePagesResolver = exports.generateFunctions = void 0;
|
|
4
|
+
const fs_extra_1 = require("fs-extra");
|
|
5
|
+
const pathe_1 = require("pathe");
|
|
6
|
+
const constants_1 = require("../constants");
|
|
7
|
+
const getHandler_1 = require("../templates/getHandler");
|
|
8
|
+
const getPageResolver_1 = require("../templates/getPageResolver");
|
|
9
|
+
const generateFunctions = async ({ FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir) => {
|
|
8
10
|
const functionsDir = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
|
|
9
11
|
const bridgeFile = require.resolve('@vercel/node/dist/bridge');
|
|
10
|
-
const functionDir = join(process.cwd(), functionsDir, HANDLER_FUNCTION_NAME);
|
|
11
|
-
const publishDir = relative(functionDir, join(process.cwd(), PUBLISH_DIR));
|
|
12
|
+
const functionDir = pathe_1.join(process.cwd(), functionsDir, constants_1.HANDLER_FUNCTION_NAME);
|
|
13
|
+
const publishDir = pathe_1.relative(functionDir, pathe_1.join(process.cwd(), PUBLISH_DIR));
|
|
12
14
|
const writeHandler = async (func, isODB) => {
|
|
13
|
-
const handlerSource = await getHandler({ isODB, publishDir, appDir: relative(functionDir, appDir) });
|
|
14
|
-
await ensureDir(join(functionsDir, func));
|
|
15
|
-
await writeFile(join(functionsDir, func, `${func}.js`), handlerSource);
|
|
16
|
-
await copyFile(bridgeFile, join(functionsDir, func, 'bridge.js'));
|
|
15
|
+
const handlerSource = await getHandler_1.getHandler({ isODB, publishDir, appDir: pathe_1.relative(functionDir, appDir) });
|
|
16
|
+
await fs_extra_1.ensureDir(pathe_1.join(functionsDir, func));
|
|
17
|
+
await fs_extra_1.writeFile(pathe_1.join(functionsDir, func, `${func}.js`), handlerSource);
|
|
18
|
+
await fs_extra_1.copyFile(bridgeFile, pathe_1.join(functionsDir, func, 'bridge.js'));
|
|
19
|
+
await fs_extra_1.copyFile(pathe_1.join(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), pathe_1.join(functionsDir, func, 'handlerUtils.js'));
|
|
17
20
|
};
|
|
18
|
-
await writeHandler(HANDLER_FUNCTION_NAME, false);
|
|
19
|
-
await writeHandler(ODB_FUNCTION_NAME, true);
|
|
21
|
+
await writeHandler(constants_1.HANDLER_FUNCTION_NAME, false);
|
|
22
|
+
await writeHandler(constants_1.ODB_FUNCTION_NAME, true);
|
|
20
23
|
};
|
|
24
|
+
exports.generateFunctions = generateFunctions;
|
|
21
25
|
/**
|
|
22
26
|
* Writes a file in each function directory that contains references to every page entrypoint.
|
|
23
27
|
* This is just so that the nft bundler knows about them. We'll eventually do this better.
|
|
24
28
|
*/
|
|
25
|
-
|
|
29
|
+
const generatePagesResolver = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC, PUBLISH_DIR }, target, }) => {
|
|
26
30
|
const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
|
|
27
|
-
const jsSource = await getPageResolver({
|
|
28
|
-
|
|
31
|
+
const jsSource = await getPageResolver_1.getPageResolver({
|
|
32
|
+
publish: PUBLISH_DIR,
|
|
29
33
|
target,
|
|
30
34
|
});
|
|
31
|
-
await writeFile(join(functionsPath, ODB_FUNCTION_NAME, 'pages.js'), jsSource);
|
|
32
|
-
await writeFile(join(functionsPath, HANDLER_FUNCTION_NAME, 'pages.js'), jsSource);
|
|
35
|
+
await fs_extra_1.writeFile(pathe_1.join(functionsPath, constants_1.ODB_FUNCTION_NAME, 'pages.js'), jsSource);
|
|
36
|
+
await fs_extra_1.writeFile(pathe_1.join(functionsPath, constants_1.HANDLER_FUNCTION_NAME, 'pages.js'), jsSource);
|
|
33
37
|
};
|
|
38
|
+
exports.generatePagesResolver = generatePagesResolver;
|
|
34
39
|
// Move our next/image function into the correct functions directory
|
|
35
|
-
|
|
40
|
+
const setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC }, imageconfig = {}, netlifyConfig, basePath, }) => {
|
|
36
41
|
const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
|
|
37
|
-
const functionName = `${IMAGE_FUNCTION_NAME}.js`;
|
|
38
|
-
const functionDirectory = join(functionsPath, IMAGE_FUNCTION_NAME);
|
|
39
|
-
await ensureDir(functionDirectory);
|
|
40
|
-
await writeJSON(join(functionDirectory, 'imageconfig.json'), {
|
|
42
|
+
const functionName = `${constants_1.IMAGE_FUNCTION_NAME}.js`;
|
|
43
|
+
const functionDirectory = pathe_1.join(functionsPath, constants_1.IMAGE_FUNCTION_NAME);
|
|
44
|
+
await fs_extra_1.ensureDir(functionDirectory);
|
|
45
|
+
await fs_extra_1.writeJSON(pathe_1.join(functionDirectory, 'imageconfig.json'), {
|
|
41
46
|
...imageconfig,
|
|
42
|
-
basePath: [basePath, IMAGE_FUNCTION_NAME].join('/'),
|
|
47
|
+
basePath: [basePath, constants_1.IMAGE_FUNCTION_NAME].join('/'),
|
|
43
48
|
});
|
|
44
|
-
await copyFile(join(__dirname, '..', 'templates', 'ipx.js'), join(functionDirectory, functionName));
|
|
49
|
+
await fs_extra_1.copyFile(pathe_1.join(__dirname, '..', '..', 'lib', 'templates', 'ipx.js'), pathe_1.join(functionDirectory, functionName));
|
|
45
50
|
const imagePath = imageconfig.path || '/_next/image';
|
|
46
51
|
netlifyConfig.redirects.push({
|
|
47
52
|
from: `${imagePath}*`,
|
|
48
53
|
query: { url: ':url', w: ':width', q: ':quality' },
|
|
49
|
-
to: `${basePath}/${IMAGE_FUNCTION_NAME}/w_:width,q_:quality/:url`,
|
|
54
|
+
to: `${basePath}/${constants_1.IMAGE_FUNCTION_NAME}/w_:width,q_:quality/:url`,
|
|
50
55
|
status: 301,
|
|
51
56
|
}, {
|
|
52
|
-
from: `${basePath}/${IMAGE_FUNCTION_NAME}/*`,
|
|
53
|
-
to: `/.netlify/builders/${IMAGE_FUNCTION_NAME}`,
|
|
57
|
+
from: `${basePath}/${constants_1.IMAGE_FUNCTION_NAME}/*`,
|
|
58
|
+
to: `/.netlify/builders/${constants_1.IMAGE_FUNCTION_NAME}`,
|
|
54
59
|
status: 200,
|
|
55
60
|
});
|
|
56
61
|
if (basePath) {
|
|
@@ -62,3 +67,4 @@ exports.setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCT
|
|
|
62
67
|
});
|
|
63
68
|
}
|
|
64
69
|
};
|
|
70
|
+
exports.setupImageFunction = setupImageFunction;
|