From a8f06a864749cf025fa86f11b37f036ce1c5e35d Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 3 Feb 2022 10:33:02 +0000 Subject: [PATCH 1/2] chore: add blocking ISR demo --- .../withFallbackBlocking/[id].js | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 demos/default/pages/getStaticProps/withRevalidate/withFallbackBlocking/[id].js diff --git a/demos/default/pages/getStaticProps/withRevalidate/withFallbackBlocking/[id].js b/demos/default/pages/getStaticProps/withRevalidate/withFallbackBlocking/[id].js new file mode 100644 index 0000000000..38058f5d42 --- /dev/null +++ b/demos/default/pages/getStaticProps/withRevalidate/withFallbackBlocking/[id].js @@ -0,0 +1,45 @@ +import Link from 'next/link' + +const Show = ({ show, time }) => ( +
+

This page uses getStaticProps() to pre-fetch a TV show.

+

Ids 1 and 2 are prerendered

+
+ +

Show #{show.id}

+

{show.name}

+

Rendered at {time}

+
+ + + Go back home + +
+) + +export async function getStaticPaths() { + // Set the paths we want to pre-render + const paths = [{ params: { id: '1' } }, { params: { id: '2' } }] + + // We'll pre-render only these paths at build time. + + return { paths, fallback: 'blocking' } +} + +export async function getStaticProps({ params }) { + // The ID to render + const { id } = params + + const res = await fetch(`https://api.tvmaze.com/shows/${id}`) + const data = await res.json() + const time = new Date().toLocaleTimeString() + return { + props: { + show: data, + time, + }, + revalidate: 60, + } +} + +export default Show From c934ad1963a77952edc9065fa34bcdcd88f9e591 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 3 Feb 2022 11:41:51 +0000 Subject: [PATCH 2/2] fix: ensure all files are patched correctly --- src/helpers/files.ts | 50 +++++++++++++++---------- src/helpers/utils.ts | 17 +++++++++ test/__snapshots__/index.js.snap | 64 ++++++++++++++++++++++++++++++++ test/index.js | 29 +++++++++++++-- 4 files changed, 137 insertions(+), 23 deletions(-) diff --git a/src/helpers/files.ts b/src/helpers/files.ts index 90d0eaa79e..757f5abca1 100644 --- a/src/helpers/files.ts +++ b/src/helpers/files.ts @@ -15,6 +15,7 @@ import { MINIMUM_REVALIDATE_SECONDS, DIVIDER } from '../constants' import { NextConfig } from './config' import { Rewrites, RoutesManifest } from './types' +import { findModuleFromBase } from './utils' const TEST_ROUTE = /(|\/)\[[^/]+?](\/|\.html|$)/ @@ -272,47 +273,56 @@ export const moveStaticPages = async ({ } } -const patchFile = async ({ file, from, to }: { file: string; from: string; to: string }): Promise => { +/** + * Attempt to patch a source file, preserving a backup + */ +const patchFile = async ({ file, from, to }: { file: string; from: string; to: string }): Promise => { if (!existsSync(file)) { - return + console.warn('File was not found') + + return false } const content = await readFile(file, 'utf8') if (content.includes(to)) { - return + console.log('File already patched') + return false } const newContent = content.replace(from, to) + if (newContent === content) { + console.warn('File was not changed') + return false + } await writeFile(`${file}.orig`, content) await writeFile(file, newContent) + console.log('Done') + return true } +/** + * The file we need has moved around a bit over the past few versions, + * so we iterate through the options until we find it + */ const getServerFile = (root) => { - let serverFile - try { - serverFile = require.resolve('next/dist/server/next-server', { paths: [root] }) - } catch { - // Ignore - } - if (!serverFile) { - try { - // eslint-disable-next-line node/no-missing-require - serverFile = require.resolve('next/dist/next-server/server/next-server', { paths: [root] }) - } catch { - // Ignore - } - } - return serverFile + const candidates = [ + 'next/dist/server/base-server', + 'next/dist/server/next-server', + 'next/dist/next-server/server/next-server', + ] + + return findModuleFromBase({ candidates, paths: [root] }) } -export const patchNextFiles = async (root: string): Promise => { +export const patchNextFiles = (root: string): Promise | boolean => { const serverFile = getServerFile(root) console.log(`Patching ${serverFile}`) if (serverFile) { - await patchFile({ + return patchFile({ file: serverFile, from: `let ssgCacheKey = `, to: `let ssgCacheKey = process.env._BYPASS_SSG || `, }) } + return false } export const unpatchNextFiles = async (root: string): Promise => { diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index cb46b75ebe..f4a94ab5df 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -157,3 +157,20 @@ export const shouldSkip = (): boolean => process.env.NEXT_PLUGIN_FORCE_RUN === '0' || process.env.NETLIFY_NEXT_PLUGIN_SKIP === 'true' || process.env.NETLIFY_NEXT_PLUGIN_SKIP === '1' + +/** + * Given an array of base paths and candidate modules, return the first one that exists + */ +export const findModuleFromBase = ({ paths, candidates }): string | null => { + for (const candidate of candidates) { + try { + const modulePath = require.resolve(candidate, { paths }) + if (modulePath) { + return modulePath + } + } catch (error) { + console.error(error) + } + } + return null +} diff --git a/test/__snapshots__/index.js.snap b/test/__snapshots__/index.js.snap index 91e49adb40..8f2e0888af 100644 --- a/test/__snapshots__/index.js.snap +++ b/test/__snapshots__/index.js.snap @@ -29,6 +29,7 @@ exports.resolvePages = () => { require.resolve('../../../.next/server/pages/getStaticProps/withFallbackBlocking/[id].js') require.resolve('../../../.next/server/pages/getStaticProps/withRevalidate/[id].js') require.resolve('../../../.next/server/pages/getStaticProps/withRevalidate/withFallback/[id].js') + require.resolve('../../../.next/server/pages/getStaticProps/withRevalidate/withFallbackBlocking/[id].js') require.resolve('../../../.next/server/pages/index.js') require.resolve('../../../.next/server/pages/middle/_middleware.js') require.resolve('../../../.next/server/pages/previewTest.js') @@ -67,6 +68,7 @@ exports.resolvePages = () => { require.resolve('../../../.next/server/pages/getStaticProps/withFallbackBlocking/[id].js') require.resolve('../../../.next/server/pages/getStaticProps/withRevalidate/[id].js') require.resolve('../../../.next/server/pages/getStaticProps/withRevalidate/withFallback/[id].js') + require.resolve('../../../.next/server/pages/getStaticProps/withRevalidate/withFallbackBlocking/[id].js') require.resolve('../../../.next/server/pages/index.js') require.resolve('../../../.next/server/pages/middle/_middleware.js') require.resolve('../../../.next/server/pages/previewTest.js') @@ -105,6 +107,7 @@ exports.resolvePages = () => { require.resolve('../../../web/.next/server/pages/getStaticProps/withFallbackBlocking/[id].js') require.resolve('../../../web/.next/server/pages/getStaticProps/withRevalidate/[id].js') require.resolve('../../../web/.next/server/pages/getStaticProps/withRevalidate/withFallback/[id].js') + require.resolve('../../../web/.next/server/pages/getStaticProps/withRevalidate/withFallbackBlocking/[id].js') require.resolve('../../../web/.next/server/pages/index.js') require.resolve('../../../web/.next/server/pages/middle/_middleware.js') require.resolve('../../../web/.next/server/pages/previewTest.js') @@ -143,6 +146,7 @@ exports.resolvePages = () => { require.resolve('../../../web/.next/server/pages/getStaticProps/withFallbackBlocking/[id].js') require.resolve('../../../web/.next/server/pages/getStaticProps/withRevalidate/[id].js') require.resolve('../../../web/.next/server/pages/getStaticProps/withRevalidate/withFallback/[id].js') + require.resolve('../../../web/.next/server/pages/getStaticProps/withRevalidate/withFallbackBlocking/[id].js') require.resolve('../../../web/.next/server/pages/index.js') require.resolve('../../../web/.next/server/pages/middle/_middleware.js') require.resolve('../../../web/.next/server/pages/previewTest.js') @@ -647,6 +651,30 @@ Array [ "status": 200, "to": "/.netlify/builders/___netlify-odb-handler", }, + Object { + "force": true, + "from": "/_next/data/build-id/en/getStaticProps/withRevalidate/withFallbackBlocking/1.json", + "status": 200, + "to": "/.netlify/builders/___netlify-odb-handler", + }, + Object { + "force": true, + "from": "/getStaticProps/withRevalidate/withFallbackBlocking/1", + "status": 200, + "to": "/.netlify/builders/___netlify-odb-handler", + }, + Object { + "force": true, + "from": "/_next/data/build-id/en/getStaticProps/withRevalidate/withFallbackBlocking/2.json", + "status": 200, + "to": "/.netlify/builders/___netlify-odb-handler", + }, + Object { + "force": true, + "from": "/getStaticProps/withRevalidate/withFallbackBlocking/2", + "status": 200, + "to": "/.netlify/builders/___netlify-odb-handler", + }, Object { "force": false, "from": "/_next/data/build-id/en/index.json", @@ -1367,6 +1395,42 @@ Array [ "status": 200, "to": "/.netlify/builders/___netlify-odb-handler", }, + Object { + "force": false, + "from": "/_next/data/build-id/en/getStaticProps/withRevalidate/withFallbackBlocking/:id.json", + "status": 200, + "to": "/.netlify/builders/___netlify-odb-handler", + }, + Object { + "force": false, + "from": "/getStaticProps/withRevalidate/withFallbackBlocking/:id", + "status": 200, + "to": "/.netlify/builders/___netlify-odb-handler", + }, + Object { + "force": false, + "from": "/_next/data/build-id/es/getStaticProps/withRevalidate/withFallbackBlocking/:id.json", + "status": 200, + "to": "/.netlify/builders/___netlify-odb-handler", + }, + Object { + "force": false, + "from": "/es/getStaticProps/withRevalidate/withFallbackBlocking/:id", + "status": 200, + "to": "/.netlify/builders/___netlify-odb-handler", + }, + Object { + "force": false, + "from": "/_next/data/build-id/fr/getStaticProps/withRevalidate/withFallbackBlocking/:id.json", + "status": 200, + "to": "/.netlify/builders/___netlify-odb-handler", + }, + Object { + "force": false, + "from": "/fr/getStaticProps/withRevalidate/withFallbackBlocking/:id", + "status": 200, + "to": "/.netlify/builders/___netlify-odb-handler", + }, Object { "force": false, "from": "/_next/data/build-id/en/getStaticProps/withRevalidate/:id.json", diff --git a/test/index.js b/test/index.js index 7539a494c3..04f27bf565 100644 --- a/test/index.js +++ b/test/index.js @@ -1,4 +1,4 @@ -const { writeJSON, unlink, existsSync, readFileSync, copy, ensureDir, readJson, writeFile } = require('fs-extra') +const { writeJSON, unlink, existsSync, readFileSync, copy, ensureDir, readJson } = require('fs-extra') const path = require('path') const process = require('process') const os = require('os') @@ -10,10 +10,16 @@ const plugin = require('../src') const { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME } = require('../src/constants') const { join } = require('pathe') -const { matchMiddleware, stripLocale, matchesRedirect, matchesRewrite } = require('../src/helpers/files') +const { + matchMiddleware, + stripLocale, + matchesRedirect, + matchesRewrite, + patchNextFiles, + unpatchNextFiles, +} = require('../src/helpers/files') const { dirname } = require('path') const { getProblematicUserRewrites } = require('../src/helpers/verification') -const { outdent } = require('outdent') const FIXTURES_DIR = `${__dirname}/fixtures` const SAMPLE_PROJECT_DIR = `${__dirname}/../demos/default` @@ -659,6 +665,23 @@ describe('utility functions', () => { expect(matchesRewrite(path, REWRITES)).toBeTruthy() }) }) + + test('patches Next server files', async () => { + const root = path.resolve(dirname(__dirname)) + await copy(join(root, 'package.json'), path.join(process.cwd(), 'package.json')) + await ensureDir(path.join(process.cwd(), 'node_modules')) + await copy(path.join(root, 'node_modules', 'next'), path.join(process.cwd(), 'node_modules', 'next')) + + expect(await patchNextFiles(process.cwd())).toBeTruthy() + const serverFile = path.resolve(process.cwd(), 'node_modules', 'next', 'dist', 'server', 'base-server.js') + const patchedData = await readFileSync(serverFile, 'utf8') + expect(patchedData.includes('_BYPASS_SSG')).toBeTruthy() + + await unpatchNextFiles(process.cwd()) + + const unPatchedData = await readFileSync(serverFile, 'utf8') + expect(unPatchedData.includes('_BYPASS_SSG')).toBeFalsy() + }) }) describe('function helpers', () => {