@netlify/plugin-nextjs 4.0.0-beta.6 → 4.0.0-rc.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 CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  # Essential Next.js Build Plugin (beta)
4
4
 
5
-
6
- :warning: This is the beta version of the Essential Next.js plugin. For the stable version, see [Essential Next.js plugin v3](https://github.com/netlify/netlify-plugin-nextjs/tree/v3#readme) :warning:
5
+ :warning: This is the beta version of the Essential Next.js plugin. For the stable version, refer to
6
+ [Essential Next.js plugin v3](https://github.com/netlify/netlify-plugin-nextjs/tree/v3#readme) :warning:
7
7
 
8
8
  <p align="center">
9
9
  <a aria-label="npm version" href="https://www.npmjs.com/package/@netlify/plugin-nextjs">
@@ -16,16 +16,19 @@
16
16
 
17
17
  ## What's new in this version
18
18
 
19
- Version 4 is a complete rewrite of the Essential Next.js plugin. For full details of everything that's new, see [the v4 release notes](https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/release-notes/v4.md)
19
+ Version 4 is a complete rewrite of the Essential Next.js plugin. For full details of everything that's new, check out
20
+ [the v4 release notes](https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/release-notes/v4.md)
20
21
 
21
22
  ## Installing the beta
22
23
 
23
-
24
24
  - Install the module:
25
+
25
26
  ```shell
26
27
  npm install -D @netlify/plugin-nextjs@beta
27
28
  ```
28
- - Change the `publish` directory to `.next` and add the plugin to `netlify.toml` if not already installed:
29
+
30
+ - Change the `publish` directory to `.next` and add the plugin to `netlify.toml` if not already installed:
31
+
29
32
  ```toml
30
33
  [build]
31
34
  publish = ".next"
@@ -34,14 +37,27 @@ publish = ".next"
34
37
  package = "@netlify/plugin-nextjs"
35
38
  ```
36
39
 
37
- If you previously set `target: "serverless"` or a custom `distDir` in your `next.config.js`, or set `node_bundler` or `external_node_modules` in your `netlify.toml` these are no longer needed and can be removed.
40
+ If you previously set a custom `distDir` in your `next.config.js`, or set `node_bundler` or `external_node_modules` in
41
+ your `netlify.toml` these are no longer needed and can be removed.
42
+
43
+ The `serverless` and `experimental-serverless-trace` targets are deprecated in Next 12, and all builds with this plugin
44
+ will now use the default `server` target. If you previously set the target in your `next.config.js`, you should remove
45
+ it.
46
+
47
+ If you are using a monorepo you will need to change `publish` to point to the full path to the built `.next` directory,
48
+ which may be in a subdirectory. If you have changed your `distDir` then it will need to match that.
38
49
 
39
- The `serverless` and `experimental-serverless-trace` targets are deprecated in Next 12, and all builds with this plugin will now use the default `server` target.
50
+ If you are using Nx, then you will need to point `publish` to the folder inside `dist`, e.g. `dist/apps/myapp/.next`.
40
51
 
41
- If you are using a monorepo you will need to change `publish` to point to the full path to the built `.next` directory, which may be in a subdirectory. If you have changed your `distDir` then it will need to match that.
52
+ If you currently use redirects or rewrites on your site, see
53
+ [the Rewrites and Redirects guide](https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/redirects-rewrites.md)
54
+ for information on changes to how they are handled in this version.
42
55
 
43
- If you are using Nx, then you will need to point `publish` to the folder inside `dist`, e.g. `dist/apps/myapp/.next`.
56
+ If you want to use Next 12's beta Middleware feature, this will mostly work as expected but please
57
+ [read the docs on some caveats and workarounds](https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/middleware.md)
58
+ that are currently needed.
44
59
 
45
60
  ## Beta feedback
46
61
 
47
- Please share any thoughts, feedback or questions about the beta [in our discussion](https://github.com/netlify/netlify-plugin-nextjs/discussions/706).
62
+ Please share any thoughts, feedback or questions about the beta
63
+ [in our discussion](https://github.com/netlify/netlify-plugin-nextjs/discussions/706).
package/lib/constants.js CHANGED
@@ -1,8 +1,11 @@
1
- const HANDLER_FUNCTION_NAME = '___netlify-handler';
2
- const ODB_FUNCTION_NAME = '___netlify-odb-handler';
3
- const IMAGE_FUNCTION_NAME = '_ipx';
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ 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
- const HIDDEN_PATHS = [
8
+ exports.HIDDEN_PATHS = [
6
9
  '/cache/*',
7
10
  '/server/*',
8
11
  '/serverless/*',
@@ -13,9 +16,9 @@ const HIDDEN_PATHS = [
13
16
  '/react-loadable-manifest.json',
14
17
  '/BUILD_ID',
15
18
  ];
16
- module.exports = {
17
- HIDDEN_PATHS,
18
- IMAGE_FUNCTION_NAME,
19
- HANDLER_FUNCTION_NAME,
20
- ODB_FUNCTION_NAME,
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;
@@ -1,6 +1,9 @@
1
- const { posix: { join }, } = require('path');
2
- exports.restoreCache = async ({ cache, publish }) => {
3
- const cacheDir = join(publish, 'cache');
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.posix.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.saveCache = async ({ cache, publish }) => {
12
- const cacheDir = join(publish, 'cache');
13
- const buildManifest = join(publish, 'build-manifest.json');
14
+ exports.restoreCache = restoreCache;
15
+ const saveCache = async ({ cache, publish }) => {
16
+ const cacheDir = path_1.posix.join(publish, 'cache');
17
+ const buildManifest = path_1.posix.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;
@@ -1,92 +1,22 @@
1
- // @ts-check
2
- const { readJSON, existsSync } = require('fs-extra');
3
- const { join, dirname, relative } = require('pathe');
4
- const slash = require('slash');
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 { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, HIDDEN_PATHS } = require('../constants');
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
- const staticManifest = join(netlifyConfig.build.publish, 'static-manifest.json');
63
- if (process.env.EXPERIMENTAL_MOVE_STATIC_PAGES && existsSync(staticManifest)) {
64
- // Static page files need to have a forced redirect for preview mode. Otherwise it's non-forced
65
- const staticFiles = await readJSON(staticManifest);
66
- netlifyConfig.redirects.push(...staticFiles.map((file) => ({
67
- from: `${basePath}/${file}`,
68
- to: HANDLER_FUNCTION_PATH,
69
- status: 200,
70
- force: true,
71
- conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
72
- })));
73
- }
74
- // This is only used in prod, so dev uses `next dev` directly
75
- netlifyConfig.redirects.push({ from: `${basePath}/_next/static/*`, to: `/static/:splat`, status: 200 }, {
76
- from: `${basePath}/*`,
77
- to: HANDLER_FUNCTION_PATH,
78
- status: 200,
79
- conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
80
- }, ...redirects.map((redirect) => ({
81
- from: `${basePath}${redirect}`,
82
- to: ODB_FUNCTION_PATH,
83
- status: 200,
84
- })), { from: `${basePath}/*`, to: HANDLER_FUNCTION_PATH, status: 200 });
85
- };
86
- exports.getNextConfig = async function getNextConfig({ publish, failBuild = defaultFailBuild }) {
14
+ const getNextConfig = async function getNextConfig({ publish, failBuild = defaultFailBuild, }) {
87
15
  try {
88
- const { config, appDir, ignore } = await readJSON(join(publish, 'required-server-files.json'));
16
+ const { config, appDir, ignore } = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(publish, 'required-server-files.json'));
89
17
  if (!config) {
18
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
19
+ // @ts-ignore
90
20
  return failBuild('Error loading your Next config');
91
21
  }
92
22
  return { ...config, appDir, ignore };
@@ -95,26 +25,27 @@ exports.getNextConfig = async function getNextConfig({ publish, failBuild = defa
95
25
  return failBuild('Error loading your Next config', { error });
96
26
  }
97
27
  };
28
+ exports.getNextConfig = getNextConfig;
98
29
  const resolveModuleRoot = (moduleName) => {
99
30
  try {
100
- return dirname(relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })));
31
+ return (0, pathe_1.dirname)((0, pathe_1.relative)(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })));
101
32
  }
102
33
  catch (error) {
103
34
  return null;
104
35
  }
105
36
  };
106
37
  const DEFAULT_EXCLUDED_MODULES = ['sharp', 'electron'];
107
- exports.configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) => {
38
+ const configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) => {
108
39
  var _a;
109
40
  /* eslint-disable no-underscore-dangle */
110
41
  (_a = netlifyConfig.functions)._ipx || (_a._ipx = {});
111
42
  netlifyConfig.functions._ipx.node_bundler = 'nft';
112
- [HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => {
43
+ [constants_1.HANDLER_FUNCTION_NAME, constants_1.ODB_FUNCTION_NAME].forEach((functionName) => {
113
44
  var _a, _b;
114
45
  (_a = netlifyConfig.functions)[functionName] || (_a[functionName] = { included_files: [], external_node_modules: [] });
115
46
  netlifyConfig.functions[functionName].node_bundler = 'nft';
116
47
  (_b = netlifyConfig.functions[functionName]).included_files || (_b.included_files = []);
117
- 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)}`));
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) => `!${(0, slash_1.default)(path)}`));
118
49
  const nextRoot = resolveModuleRoot('next');
119
50
  if (nextRoot) {
120
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`);
@@ -127,3 +58,4 @@ exports.configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) =>
127
58
  });
128
59
  });
129
60
  };
61
+ exports.configureHandlerFunctions = configureHandlerFunctions;
@@ -1,51 +1,258 @@
1
- // @ts-check
1
+ /* eslint-disable max-lines */
2
2
  const { cpus } = require('os');
3
- const { existsSync, readJson, move, cpSync, copy, writeJson } = require('fs-extra');
3
+ const { yellowBright } = require('chalk');
4
+ const { existsSync, readJson, move, copy, writeJson, readFile, writeFile, ensureDir, readFileSync, } = require('fs-extra');
4
5
  const globby = require('globby');
6
+ const { outdent } = require('outdent');
5
7
  const pLimit = require('p-limit');
6
8
  const { join } = require('pathe');
7
9
  const slash = require('slash');
8
10
  const TEST_ROUTE = /(|\/)\[[^/]+?](\/|\.html|$)/;
9
11
  const isDynamicRoute = (route) => TEST_ROUTE.test(route);
10
- exports.moveStaticPages = async ({ netlifyConfig, target, i18n, failBuild }) => {
12
+ const stripLocale = (rawPath, locales = []) => {
13
+ const [locale, ...segments] = rawPath.split('/');
14
+ if (locales.includes(locale)) {
15
+ return segments.join('/');
16
+ }
17
+ return rawPath;
18
+ };
19
+ const matchMiddleware = (middleware, filePath) => (middleware === null || middleware === void 0 ? void 0 : middleware.includes('')) ||
20
+ (middleware === null || middleware === void 0 ? void 0 : middleware.find((middlewarePath) => filePath === middlewarePath || filePath === `${middlewarePath}.html` || filePath.startsWith(`${middlewarePath}/`)));
21
+ const matchesRedirect = (file, redirects) => {
22
+ if (!Array.isArray(redirects)) {
23
+ return false;
24
+ }
25
+ return redirects.some((redirect) => {
26
+ if (!redirect.regex || redirect.internal) {
27
+ return false;
28
+ }
29
+ // Strips the extension from the file path
30
+ return new RegExp(redirect.regex).test(`/${file.slice(0, -5)}`);
31
+ });
32
+ };
33
+ const matchesRewrite = (file, rewrites) => {
34
+ if (Array.isArray(rewrites)) {
35
+ return matchesRedirect(file, rewrites);
36
+ }
37
+ if (!Array.isArray(rewrites === null || rewrites === void 0 ? void 0 : rewrites.beforeFiles)) {
38
+ return false;
39
+ }
40
+ return matchesRedirect(file, rewrites.beforeFiles);
41
+ };
42
+ exports.matchesRedirect = matchesRedirect;
43
+ exports.matchesRewrite = matchesRewrite;
44
+ exports.matchMiddleware = matchMiddleware;
45
+ exports.stripLocale = stripLocale;
46
+ exports.isDynamicRoute = isDynamicRoute;
47
+ // eslint-disable-next-line max-lines-per-function
48
+ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => {
11
49
  console.log('Moving static page files to serve from CDN...');
12
- const root = join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless', 'pages');
50
+ const outputDir = join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless');
51
+ const root = join(outputDir, 'pages');
52
+ const buildId = readFileSync(join(netlifyConfig.build.publish, 'BUILD_ID'), 'utf8').trim();
53
+ const dataDir = join('_next', 'data', buildId);
54
+ await ensureDir(dataDir);
55
+ // Load the middleware manifest so we can check if a file matches it before moving
56
+ let middleware;
57
+ const manifestPath = join(outputDir, 'middleware-manifest.json');
58
+ if (existsSync(manifestPath)) {
59
+ const manifest = await readJson(manifestPath);
60
+ if (manifest === null || manifest === void 0 ? void 0 : manifest.middleware) {
61
+ middleware = Object.keys(manifest.middleware).map((path) => path.slice(1));
62
+ }
63
+ }
64
+ const prerenderManifest = await readJson(join(netlifyConfig.build.publish, 'prerender-manifest.json'));
65
+ const { redirects, rewrites } = await readJson(join(netlifyConfig.build.publish, 'routes-manifest.json'));
66
+ const isrFiles = new Set();
67
+ Object.entries(prerenderManifest.routes).forEach(([route, { initialRevalidateSeconds }]) => {
68
+ if (initialRevalidateSeconds) {
69
+ // Find all files used by ISR routes
70
+ const trimmedPath = route.slice(1);
71
+ isrFiles.add(`${trimmedPath}.html`);
72
+ isrFiles.add(`${trimmedPath}.json`);
73
+ }
74
+ });
13
75
  const files = [];
76
+ const filesManifest = {};
14
77
  const moveFile = async (file) => {
78
+ const isData = file.endsWith('.json');
15
79
  const source = join(root, file);
80
+ const target = isData ? join(dataDir, file) : file;
16
81
  files.push(file);
17
- const dest = join(netlifyConfig.build.publish, file);
18
- await move(source, dest);
82
+ filesManifest[file] = target;
83
+ const dest = join(netlifyConfig.build.publish, target);
84
+ try {
85
+ await move(source, dest);
86
+ }
87
+ catch (error) {
88
+ console.warn('Error moving file', source, error);
89
+ }
19
90
  };
20
91
  // Move all static files, except error documents and nft manifests
21
92
  const pages = await globby(['**/*.{html,json}', '!**/(500|404|*.js.nft).{html,json}'], {
22
93
  cwd: root,
23
94
  dot: true,
24
95
  });
96
+ const matchingMiddleware = new Set();
97
+ const matchedPages = new Set();
98
+ const matchedRedirects = new Set();
99
+ const matchedRewrites = new Set();
25
100
  // Limit concurrent file moves to number of cpus or 2 if there is only 1
26
101
  const limit = pLimit(Math.max(2, cpus().length));
27
102
  const promises = pages.map(async (rawPath) => {
28
103
  const filePath = slash(rawPath);
104
+ // Don't move ISR files, as they're used for the first request
105
+ if (isrFiles.has(filePath)) {
106
+ return;
107
+ }
29
108
  if (isDynamicRoute(filePath)) {
30
109
  return;
31
110
  }
111
+ if (matchesRedirect(filePath, redirects)) {
112
+ matchedRedirects.add(filePath);
113
+ return;
114
+ }
115
+ if (matchesRewrite(filePath, rewrites)) {
116
+ matchedRewrites.add(filePath);
117
+ return;
118
+ }
119
+ // Middleware matches against the unlocalised path
120
+ const unlocalizedPath = stripLocale(rawPath, i18n === null || i18n === void 0 ? void 0 : i18n.locales);
121
+ const middlewarePath = matchMiddleware(middleware, unlocalizedPath);
122
+ // 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
123
+ if (middlewarePath) {
124
+ matchingMiddleware.add(middlewarePath);
125
+ matchedPages.add(rawPath);
126
+ return;
127
+ }
32
128
  return limit(moveFile, filePath);
33
129
  });
34
130
  await Promise.all(promises);
35
131
  console.log(`Moved ${files.length} files`);
132
+ if (matchedPages.size !== 0) {
133
+ console.log(yellowBright(outdent `
134
+ 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.
135
+ This is fine, but we're letting you know because it may not be what you expect.
136
+ `));
137
+ console.log(outdent `
138
+ The following middleware matched statically-rendered pages:
139
+
140
+ ${yellowBright([...matchingMiddleware].map((mid) => `- /${mid}/_middleware`).join('\n'))}
141
+
142
+ ────────────────────────────────────────────────────────────────
143
+ `);
144
+ // There could potentially be thousands of matching pages, so we don't want to spam the console with this
145
+ if (matchedPages.size < 50) {
146
+ console.log(outdent `
147
+ The following files matched middleware and were not moved to the CDN:
148
+
149
+ ${yellowBright([...matchedPages].map((mid) => `- ${mid}`).join('\n'))}
150
+
151
+ ────────────────────────────────────────────────────────────────
152
+ `);
153
+ }
154
+ }
155
+ if (matchedRedirects.size !== 0 || matchedRewrites.size !== 0) {
156
+ console.log(yellowBright(outdent `
157
+ 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.
158
+ `));
159
+ if (matchedRedirects.size < 50 && matchedRedirects.size !== 0) {
160
+ console.log(outdent `
161
+ The following files matched redirects and were not moved to the CDN:
162
+
163
+ ${yellowBright([...matchedRedirects].map((mid) => `- ${mid}`).join('\n'))}
164
+
165
+ ────────────────────────────────────────────────────────────────
166
+ `);
167
+ }
168
+ if (matchedRewrites.size < 50 && matchedRewrites.size !== 0) {
169
+ console.log(outdent `
170
+ The following files matched beforeFiles rewrites and were not moved to the CDN:
171
+
172
+ ${yellowBright([...matchedRewrites].map((mid) => `- ${mid}`).join('\n'))}
173
+
174
+ ────────────────────────────────────────────────────────────────
175
+ `);
176
+ }
177
+ }
36
178
  // Write the manifest for use in the serverless functions
37
- await writeJson(join(netlifyConfig.build.publish, 'static-manifest.json'), files);
179
+ await writeJson(join(netlifyConfig.build.publish, 'static-manifest.json'), Object.entries(filesManifest));
38
180
  if (i18n === null || i18n === void 0 ? void 0 : i18n.defaultLocale) {
39
181
  // Copy the default locale into the root
40
182
  const defaultLocaleDir = join(netlifyConfig.build.publish, i18n.defaultLocale);
41
183
  if (existsSync(defaultLocaleDir)) {
42
184
  await copy(defaultLocaleDir, `${netlifyConfig.build.publish}/`);
43
185
  }
186
+ const defaultLocaleIndex = join(netlifyConfig.build.publish, `${i18n.defaultLocale}.html`);
187
+ const indexHtml = join(netlifyConfig.build.publish, 'index.html');
188
+ if (existsSync(defaultLocaleIndex) && !existsSync(indexHtml)) {
189
+ try {
190
+ await copy(defaultLocaleIndex, indexHtml, { overwrite: false });
191
+ await copy(join(netlifyConfig.build.publish, `${i18n.defaultLocale}.json`), join(netlifyConfig.build.publish, 'index.json'), { overwrite: false });
192
+ }
193
+ catch { }
194
+ }
195
+ }
196
+ };
197
+ const patchFile = async ({ file, from, to }) => {
198
+ if (!existsSync(file)) {
199
+ return;
200
+ }
201
+ const content = await readFile(file, 'utf8');
202
+ if (content.includes(to)) {
203
+ return;
204
+ }
205
+ const newContent = content.replace(from, to);
206
+ await writeFile(`${file}.orig`, content);
207
+ await writeFile(file, newContent);
208
+ };
209
+ const getServerFile = (root) => {
210
+ let serverFile;
211
+ try {
212
+ serverFile = require.resolve('next/dist/server/next-server', { paths: [root] });
213
+ }
214
+ catch {
215
+ // Ignore
216
+ }
217
+ if (!serverFile) {
218
+ try {
219
+ // eslint-disable-next-line node/no-missing-require
220
+ serverFile = require.resolve('next/dist/next-server/server/next-server', { paths: [root] });
221
+ }
222
+ catch {
223
+ // Ignore
224
+ }
225
+ }
226
+ return serverFile;
227
+ };
228
+ exports.patchNextFiles = async (root) => {
229
+ const serverFile = getServerFile(root);
230
+ console.log(`Patching ${serverFile}`);
231
+ if (serverFile) {
232
+ await patchFile({
233
+ file: serverFile,
234
+ from: `let ssgCacheKey = `,
235
+ to: `let ssgCacheKey = process.env._BYPASS_SSG || `,
236
+ });
237
+ }
238
+ };
239
+ exports.unpatchNextFiles = async (root) => {
240
+ const serverFile = getServerFile(root);
241
+ const origFile = `${serverFile}.orig`;
242
+ if (existsSync(origFile)) {
243
+ await move(origFile, serverFile, { overwrite: true });
44
244
  }
45
245
  };
46
- exports.movePublicFiles = async ({ appDir, publish }) => {
47
- const publicDir = join(appDir, 'public');
246
+ exports.movePublicFiles = async ({ appDir, outdir, publish }) => {
247
+ // `outdir` is a config property added when using Next.js with Nx. It's typically
248
+ // a relative path outside of the appDir, e.g. '../../dist/apps/<app-name>', and
249
+ // the parent directory of the .next directory.
250
+ // If it exists, copy the files from the public folder there in order to include
251
+ // any files that were generated during the build. Otherwise, copy the public
252
+ // directory from the original app directory.
253
+ const publicDir = outdir ? join(appDir, outdir, 'public') : join(appDir, 'public');
48
254
  if (existsSync(publicDir)) {
49
255
  await copy(publicDir, `${publish}/`);
50
256
  }
51
257
  };
258
+ /* eslint-enable max-lines */
@@ -1,56 +1,61 @@
1
- const { copyFile, ensureDir, writeFile, writeJSON } = require('fs-extra');
2
- const { join, relative } = require('pathe');
3
- const { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, IMAGE_FUNCTION_NAME } = require('../constants');
4
- const getHandler = require('../templates/getHandler');
5
- const { getPageResolver } = require('../templates/getPageResolver');
6
- const DEFAULT_FUNCTIONS_SRC = 'netlify/functions';
7
- exports.generateFunctions = async ({ FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }, appDir) => {
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 = (0, pathe_1.join)(process.cwd(), functionsDir, constants_1.HANDLER_FUNCTION_NAME);
13
+ const publishDir = (0, pathe_1.relative)(functionDir, (0, 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 (0, getHandler_1.getHandler)({ isODB, publishDir, appDir: (0, pathe_1.relative)(functionDir, appDir) });
16
+ await (0, fs_extra_1.ensureDir)((0, pathe_1.join)(functionsDir, func));
17
+ await (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsDir, func, `${func}.js`), handlerSource);
18
+ await (0, fs_extra_1.copyFile)(bridgeFile, (0, pathe_1.join)(functionsDir, func, 'bridge.js'));
19
+ await (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), (0, 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
- exports.generatePagesResolver = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC }, netlifyConfig, target, }) => {
29
+ const generatePagesResolver = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = constants_1.DEFAULT_FUNCTIONS_SRC }, netlifyConfig, target, }) => {
26
30
  const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC;
27
- const jsSource = await getPageResolver({
31
+ const jsSource = await (0, getPageResolver_1.getPageResolver)({
28
32
  netlifyConfig,
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 (0, fs_extra_1.writeFile)((0, pathe_1.join)(functionsPath, constants_1.ODB_FUNCTION_NAME, 'pages.js'), jsSource);
36
+ await (0, fs_extra_1.writeFile)((0, 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
- exports.setupImageFunction = async ({ constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC }, imageconfig = {}, netlifyConfig, basePath, }) => {
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 = (0, pathe_1.join)(functionsPath, constants_1.IMAGE_FUNCTION_NAME);
44
+ await (0, fs_extra_1.ensureDir)(functionDirectory);
45
+ await (0, fs_extra_1.writeJSON)((0, 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 (0, fs_extra_1.copyFile)((0, pathe_1.join)(__dirname, '..', '..', 'lib', 'templates', 'ipx.js'), (0, 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;