@netlify/plugin-nextjs 4.0.0-beta.11 → 4.0.0-beta.12
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/helpers/config.js +2 -2
- package/lib/helpers/files.js +50 -1
- package/lib/helpers/verification.js +2 -1
- package/lib/index.js +7 -2
- package/lib/templates/getHandler.js +17 -13
- package/lib/templates/handlerUtils.js +31 -1
- package/package.json +10 -7
package/lib/helpers/config.js
CHANGED
|
@@ -102,7 +102,7 @@ exports.generateRedirects = async ({ netlifyConfig, basePath, i18n }) => {
|
|
|
102
102
|
// ISR redirects are handled by the regular function. Forced to avoid pre-rendered pages
|
|
103
103
|
...isrRedirects.map((redirect) => ({
|
|
104
104
|
from: `${basePath}${redirect}`,
|
|
105
|
-
to: HANDLER_FUNCTION_PATH,
|
|
105
|
+
to: process.env.EXPERIMENTAL_ODB_TTL ? ODB_FUNCTION_PATH : HANDLER_FUNCTION_PATH,
|
|
106
106
|
status: 200,
|
|
107
107
|
force: true,
|
|
108
108
|
})),
|
|
@@ -158,7 +158,7 @@ exports.configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) =>
|
|
|
158
158
|
(_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
|
|
159
159
|
netlifyConfig.functions[functionName].node_bundler = 'nft';
|
|
160
160
|
(_b = netlifyConfig.functions[functionName]).included_files || (_b.included_files = []);
|
|
161
|
-
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) => `!${slash(path)}`));
|
|
161
|
+
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(path)}`));
|
|
162
162
|
const nextRoot = resolveModuleRoot('next');
|
|
163
163
|
if (nextRoot) {
|
|
164
164
|
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`);
|
package/lib/helpers/files.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable max-lines */
|
|
2
2
|
const { cpus } = require('os');
|
|
3
3
|
const { yellowBright } = require('chalk');
|
|
4
|
-
const { existsSync, readJson, move, cpSync, copy, writeJson } = require('fs-extra');
|
|
4
|
+
const { existsSync, readJson, move, cpSync, copy, writeJson, readFile, writeFile } = require('fs-extra');
|
|
5
5
|
const globby = require('globby');
|
|
6
6
|
const { outdent } = require('outdent');
|
|
7
7
|
const pLimit = require('p-limit');
|
|
@@ -178,6 +178,55 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
};
|
|
181
|
+
const patchFile = async ({ file, from, to }) => {
|
|
182
|
+
if (!existsSync(file)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const content = await readFile(file, 'utf8');
|
|
186
|
+
if (content.includes(to)) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const newContent = content.replace(from, to);
|
|
190
|
+
await writeFile(`${file}.orig`, content);
|
|
191
|
+
await writeFile(file, newContent);
|
|
192
|
+
};
|
|
193
|
+
const getServerFile = (root) => {
|
|
194
|
+
let serverFile;
|
|
195
|
+
try {
|
|
196
|
+
serverFile = require.resolve('next/dist/server/next-server', { paths: [root] });
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Ignore
|
|
200
|
+
}
|
|
201
|
+
if (!serverFile) {
|
|
202
|
+
try {
|
|
203
|
+
// eslint-disable-next-line node/no-missing-require
|
|
204
|
+
serverFile = require.resolve('next/dist/next-server/server/next-server', { paths: [root] });
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Ignore
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return serverFile;
|
|
211
|
+
};
|
|
212
|
+
exports.patchNextFiles = async (root) => {
|
|
213
|
+
const serverFile = getServerFile(root);
|
|
214
|
+
console.log(`Patching ${serverFile}`);
|
|
215
|
+
if (serverFile) {
|
|
216
|
+
await patchFile({
|
|
217
|
+
file: serverFile,
|
|
218
|
+
from: `let ssgCacheKey = `,
|
|
219
|
+
to: `let ssgCacheKey = process.env._BYPASS_SSG || `,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
exports.unpatchNextFiles = async (root) => {
|
|
224
|
+
const serverFile = getServerFile(root);
|
|
225
|
+
const origFile = `${serverFile}.orig`;
|
|
226
|
+
if (existsSync(origFile)) {
|
|
227
|
+
await move(origFile, serverFile, { overwrite: true });
|
|
228
|
+
}
|
|
229
|
+
};
|
|
181
230
|
exports.movePublicFiles = async ({ appDir, publish }) => {
|
|
182
231
|
const publicDir = join(appDir, 'public');
|
|
183
232
|
if (existsSync(publicDir)) {
|
|
@@ -18,7 +18,8 @@ exports.verifyNetlifyBuildVersion = ({ IS_LOCAL, NETLIFY_BUILD_VERSION, failBuil
|
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
exports.checkForOldFunctions = async ({ functions }) => {
|
|
21
|
-
const
|
|
21
|
+
const allOldFunctions = await functions.list();
|
|
22
|
+
const oldFunctions = allOldFunctions.filter(({ name }) => name.startsWith('next_'));
|
|
22
23
|
if (oldFunctions.length !== 0) {
|
|
23
24
|
console.log(yellowBright(outdent `
|
|
24
25
|
We have found the following functions in your site that seem to be left over from the old Next.js plugin (v3). We have guessed this because the name starts with "next_".
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ const { join, relative } = require('path');
|
|
|
2
2
|
const { ODB_FUNCTION_NAME } = require('./constants');
|
|
3
3
|
const { restoreCache, saveCache } = require('./helpers/cache');
|
|
4
4
|
const { getNextConfig, configureHandlerFunctions, generateRedirects } = require('./helpers/config');
|
|
5
|
-
const { moveStaticPages, movePublicFiles } = require('./helpers/files');
|
|
5
|
+
const { moveStaticPages, movePublicFiles, patchNextFiles, unpatchNextFiles } = require('./helpers/files');
|
|
6
6
|
const { generateFunctions, setupImageFunction, generatePagesResolver } = require('./helpers/functions');
|
|
7
7
|
const { verifyNetlifyBuildVersion, checkNextSiteHasBuilt, checkForRootPublish, logBetaMessage, checkZipSize, checkForOldFunctions, } = require('./helpers/verification');
|
|
8
8
|
/** @type import("@netlify/build").NetlifyPlugin */
|
|
@@ -26,6 +26,9 @@ module.exports = {
|
|
|
26
26
|
await generateFunctions(constants, appDir);
|
|
27
27
|
await generatePagesResolver({ netlifyConfig, target, constants });
|
|
28
28
|
await movePublicFiles({ appDir, publish });
|
|
29
|
+
if (process.env.EXPERIMENTAL_ODB_TTL) {
|
|
30
|
+
await patchNextFiles(basePath);
|
|
31
|
+
}
|
|
29
32
|
if (process.env.EXPERIMENTAL_MOVE_STATIC_PAGES) {
|
|
30
33
|
console.log("The flag 'EXPERIMENTAL_MOVE_STATIC_PAGES' is no longer required, as it is now the default. To disable this behavior, set the env var 'SERVE_STATIC_FILES_FROM_ORIGIN' to 'true'");
|
|
31
34
|
}
|
|
@@ -39,10 +42,12 @@ module.exports = {
|
|
|
39
42
|
i18n,
|
|
40
43
|
});
|
|
41
44
|
},
|
|
42
|
-
async onPostBuild({ netlifyConfig, utils: { cache, functions }, constants: { FUNCTIONS_DIST } }) {
|
|
45
|
+
async onPostBuild({ netlifyConfig, utils: { cache, functions, failBuild }, constants: { FUNCTIONS_DIST } }) {
|
|
43
46
|
await saveCache({ cache, publish: netlifyConfig.build.publish });
|
|
44
47
|
await checkForOldFunctions({ functions });
|
|
45
48
|
await checkZipSize(join(FUNCTIONS_DIST, `${ODB_FUNCTION_NAME}.zip`));
|
|
49
|
+
const { basePath } = await getNextConfig({ publish: netlifyConfig.build.publish, failBuild });
|
|
50
|
+
await unpatchNextFiles(basePath);
|
|
46
51
|
},
|
|
47
52
|
onEnd() {
|
|
48
53
|
logBetaMessage();
|
|
@@ -3,7 +3,7 @@ const { Server } = require('http');
|
|
|
3
3
|
const { tmpdir } = require('os');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { Bridge } = require('@vercel/node/dist/bridge');
|
|
6
|
-
const { downloadFile } = require('./handlerUtils');
|
|
6
|
+
const { downloadFile, getMaxAge, getMultiValueHeaders } = require('./handlerUtils');
|
|
7
7
|
const makeHandler = () =>
|
|
8
8
|
// We return a function and then call `toString()` on it to serialise it as the launcher function
|
|
9
9
|
// eslint-disable-next-line max-params
|
|
@@ -14,6 +14,9 @@ const makeHandler = () =>
|
|
|
14
14
|
require.resolve('./pages.js');
|
|
15
15
|
}
|
|
16
16
|
catch { }
|
|
17
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
18
|
+
process.env._BYPASS_SSG = 'true';
|
|
19
|
+
const ONE_YEAR_IN_SECONDS = 31536000;
|
|
17
20
|
// We don't want to write ISR files to disk in the lambda environment
|
|
18
21
|
conf.experimental.isrFlushToDisk = false;
|
|
19
22
|
// Set during the request as it needs the host header. Hoisted so we can define the function once
|
|
@@ -97,6 +100,7 @@ const makeHandler = () =>
|
|
|
97
100
|
bridge.listen();
|
|
98
101
|
return async (event, context) => {
|
|
99
102
|
var _a, _b, _c;
|
|
103
|
+
let requestMode = mode;
|
|
100
104
|
// Ensure that paths are encoded - but don't double-encode them
|
|
101
105
|
event.path = new URL(event.path, event.rawUrl).pathname;
|
|
102
106
|
// Next expects to be able to parse the query from the URL
|
|
@@ -111,15 +115,7 @@ const makeHandler = () =>
|
|
|
111
115
|
const { headers, ...result } = await bridge.launcher(event, context);
|
|
112
116
|
/** @type import("@netlify/functions").HandlerResponse */
|
|
113
117
|
// Convert all headers to multiValueHeaders
|
|
114
|
-
const multiValueHeaders =
|
|
115
|
-
for (const key of Object.keys(headers)) {
|
|
116
|
-
if (Array.isArray(headers[key])) {
|
|
117
|
-
multiValueHeaders[key] = headers[key];
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
multiValueHeaders[key] = [headers[key]];
|
|
121
|
-
}
|
|
122
|
-
}
|
|
118
|
+
const multiValueHeaders = getMultiValueHeaders(headers);
|
|
123
119
|
if ((_b = (_a = multiValueHeaders['set-cookie']) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.includes('__prerender_bypass')) {
|
|
124
120
|
delete multiValueHeaders.etag;
|
|
125
121
|
multiValueHeaders['cache-control'] = ['no-cache'];
|
|
@@ -127,10 +123,18 @@ const makeHandler = () =>
|
|
|
127
123
|
// Sending SWR headers causes undefined behaviour with the Netlify CDN
|
|
128
124
|
const cacheHeader = (_c = multiValueHeaders['cache-control']) === null || _c === void 0 ? void 0 : _c[0];
|
|
129
125
|
if (cacheHeader === null || cacheHeader === void 0 ? void 0 : cacheHeader.includes('stale-while-revalidate')) {
|
|
130
|
-
|
|
126
|
+
if (requestMode === 'odb' && process.env.EXPERIMENTAL_ODB_TTL) {
|
|
127
|
+
requestMode = 'isr';
|
|
128
|
+
const ttl = getMaxAge(cacheHeader);
|
|
129
|
+
// Long-expiry TTL is basically no TTL
|
|
130
|
+
if (ttl > 0 && ttl < ONE_YEAR_IN_SECONDS) {
|
|
131
|
+
result.ttl = ttl;
|
|
132
|
+
}
|
|
133
|
+
multiValueHeaders['x-rendered-at'] = [new Date().toISOString()];
|
|
134
|
+
}
|
|
131
135
|
multiValueHeaders['cache-control'] = ['public, max-age=0, must-revalidate'];
|
|
132
136
|
}
|
|
133
|
-
multiValueHeaders['x-render-mode'] = [
|
|
137
|
+
multiValueHeaders['x-render-mode'] = [requestMode];
|
|
134
138
|
return {
|
|
135
139
|
...result,
|
|
136
140
|
multiValueHeaders,
|
|
@@ -144,7 +148,7 @@ const { tmpdir } = require('os')
|
|
|
144
148
|
const { promises, existsSync } = require("fs");
|
|
145
149
|
// We copy the file here rather than requiring from the node module
|
|
146
150
|
const { Bridge } = require("./bridge");
|
|
147
|
-
const { downloadFile } = require('./handlerUtils')
|
|
151
|
+
const { downloadFile, getMaxAge, getMultiValueHeaders } = require('./handlerUtils')
|
|
148
152
|
|
|
149
153
|
const { builder } = require("@netlify/functions");
|
|
150
154
|
const { config } = require("${publishDir}/required-server-files.json")
|
|
@@ -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.downloadFile = void 0;
|
|
6
|
+
exports.getMultiValueHeaders = exports.getMaxAge = exports.downloadFile = void 0;
|
|
7
7
|
const fs_1 = require("fs");
|
|
8
8
|
const http_1 = __importDefault(require("http"));
|
|
9
9
|
const https_1 = __importDefault(require("https"));
|
|
@@ -34,3 +34,33 @@ const downloadFile = async (url, destination) => {
|
|
|
34
34
|
});
|
|
35
35
|
};
|
|
36
36
|
exports.downloadFile = downloadFile;
|
|
37
|
+
const getMaxAge = (header) => {
|
|
38
|
+
const parts = header.split(',');
|
|
39
|
+
let maxAge;
|
|
40
|
+
for (const part of parts) {
|
|
41
|
+
const [key, value] = part.split('=');
|
|
42
|
+
if ((key === null || key === void 0 ? void 0 : key.trim()) === 's-maxage') {
|
|
43
|
+
maxAge = value === null || value === void 0 ? void 0 : value.trim();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (maxAge) {
|
|
47
|
+
const result = Number.parseInt(maxAge);
|
|
48
|
+
return Number.isNaN(result) ? 0 : result;
|
|
49
|
+
}
|
|
50
|
+
return 0;
|
|
51
|
+
};
|
|
52
|
+
exports.getMaxAge = getMaxAge;
|
|
53
|
+
const getMultiValueHeaders = (headers) => {
|
|
54
|
+
const multiValueHeaders = {};
|
|
55
|
+
for (const key of Object.keys(headers)) {
|
|
56
|
+
const header = headers[key];
|
|
57
|
+
if (Array.isArray(header)) {
|
|
58
|
+
multiValueHeaders[key] = header;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
multiValueHeaders[key] = [header];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return multiValueHeaders;
|
|
65
|
+
};
|
|
66
|
+
exports.getMultiValueHeaders = getMultiValueHeaders;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/plugin-nextjs",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.12",
|
|
4
4
|
"description": "Run Next.js seamlessly on Netlify",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
"manifest.yml"
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
|
-
"build:demo": "next build
|
|
11
|
+
"build:demo": "next build demos/default",
|
|
12
12
|
"cy:open": "cypress open --config-file cypress/config/all.json",
|
|
13
|
-
"cy:run": "cypress run --config-file
|
|
14
|
-
"dev:demo": "next dev
|
|
13
|
+
"cy:run": "cypress run --config-file ../../cypress/config/ci.json",
|
|
14
|
+
"dev:demo": "next dev demos/default",
|
|
15
15
|
"format": "run-s format:check-fix:*",
|
|
16
16
|
"format:ci": "run-s format:check:*",
|
|
17
17
|
"format:check-fix:lint": "run-e format:check:lint format:fix:lint",
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
"publish:test": "npm test",
|
|
27
27
|
"test": "run-s build build:demo test:jest",
|
|
28
28
|
"test:jest": "jest",
|
|
29
|
+
"test:jest:update": "jest --updateSnapshot",
|
|
30
|
+
"test:update": "run-s build build:demo test:jest:update",
|
|
29
31
|
"prepare": "npm run build",
|
|
30
32
|
"clean": "rimraf lib",
|
|
31
33
|
"build": "tsc",
|
|
@@ -72,8 +74,9 @@
|
|
|
72
74
|
"devDependencies": {
|
|
73
75
|
"@babel/core": "^7.15.8",
|
|
74
76
|
"@babel/preset-env": "^7.15.8",
|
|
75
|
-
"@
|
|
76
|
-
"@netlify/
|
|
77
|
+
"@babel/preset-typescript": "^7.16.0",
|
|
78
|
+
"@netlify/build": "^19.0.7",
|
|
79
|
+
"@netlify/eslint-config-node": "^3.3.9",
|
|
77
80
|
"@testing-library/cypress": "^8.0.1",
|
|
78
81
|
"@types/fs-extra": "^9.0.13",
|
|
79
82
|
"@types/jest": "^27.0.2",
|
|
@@ -81,7 +84,7 @@
|
|
|
81
84
|
"babel-jest": "^27.2.5",
|
|
82
85
|
"cpy": "^8.1.2",
|
|
83
86
|
"cypress": "^9.0.0",
|
|
84
|
-
"eslint-config-next": "^
|
|
87
|
+
"eslint-config-next": "^12.0.0",
|
|
85
88
|
"husky": "^4.3.0",
|
|
86
89
|
"jest": "^27.0.0",
|
|
87
90
|
"netlify-plugin-cypress": "^2.2.0",
|