diff --git a/package-lock.json b/package-lock.json index c4d49c0b2c..1450f9072d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "fs-extra": "^10.0.0", "moize": "^6.1.0", "outdent": "^0.8.0", + "pathe": "^0.2.0", "semver": "^7.3.5", "slash": "^3.0.0", "tiny-glob": "^0.2.9" @@ -27,12 +28,12 @@ "@testing-library/cypress": "^8.0.1", "@types/jest": "^27.0.2", "@types/mocha": "^9.0.0", - "babel-jest": "^27.3.0", + "babel-jest": "^27.2.5", "cpy": "^8.1.2", "cypress": "^8.5.0", "eslint-config-next": "^11.0.0", "husky": "^4.3.0", - "jest": "^27.3.0", + "jest": "^27.0.0", "netlify-plugin-cypress": "^2.2.0", "next": "^11.1.2", "npm-run-all": "^4.1.5", @@ -14569,6 +14570,11 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-0.2.0.tgz", + "integrity": "sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==" + }, "node_modules/pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -29829,6 +29835,11 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pathe": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-0.2.0.tgz", + "integrity": "sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==" + }, "pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", diff --git a/package.json b/package.json index e2ec0530ef..a14e47154a 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "fs-extra": "^10.0.0", "moize": "^6.1.0", "outdent": "^0.8.0", + "pathe": "^0.2.0", "semver": "^7.3.5", "slash": "^3.0.0", "tiny-glob": "^0.2.9" diff --git a/src/helpers/config.js b/src/helpers/config.js index bc703e3527..89b4153568 100644 --- a/src/helpers/config.js +++ b/src/helpers/config.js @@ -1,7 +1,6 @@ // @ts-check -const { join } = require('path') - const { readJSON } = require('fs-extra') +const { join, dirname, relative } = require('pathe') const defaultFailBuild = (message, { error }) => { throw new Error(`${message}\n${error && error.stack}`) @@ -100,17 +99,29 @@ exports.generateRedirects = async ({ netlifyConfig, basePath, i18n }) => { exports.getNextConfig = async function getNextConfig({ publish, failBuild = defaultFailBuild }) { try { - const { config, appDir } = await readJSON(join(publish, 'required-server-files.json')) + const { config, appDir, ignore } = await readJSON(join(publish, 'required-server-files.json')) if (!config) { return failBuild('Error loading your Next config') } - return { ...config, appDir } + return { ...config, appDir, ignore } } catch (error) { return failBuild('Error loading your Next config', { error }) } } -exports.configureHandlerFunctions = ({ netlifyConfig, publish }) => { +const resolveModuleRoot = (moduleName) => { + try { + return dirname(relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] }))) + } catch (error) { + return null + } +} + +exports.configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) => { + /* eslint-disable no-underscore-dangle */ + netlifyConfig.functions._ipx ||= {} + netlifyConfig.functions._ipx.node_bundler = 'esbuild' + /* eslint-enable no-underscore-dangle */ ;[HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => { netlifyConfig.functions[functionName] ||= { included_files: [], external_node_modules: [] } netlifyConfig.functions[functionName].node_bundler = 'nft' @@ -120,16 +131,23 @@ exports.configureHandlerFunctions = ({ netlifyConfig, publish }) => { `${publish}/serverless/**`, `${publish}/*.json`, `${publish}/BUILD_ID`, - '!node_modules/@next/swc-*/**/*', - '!node_modules/next/dist/compiled/@ampproject/toolbox-optimizer/**/*', - '!node_modules/next/dist/pages/**/*', - `!node_modules/next/dist/server/lib/squoosh/**/*.wasm`, - `!node_modules/next/dist/next-server/server/lib/squoosh/**/*.wasm`, - '!node_modules/next/dist/compiled/webpack/(bundle4|bundle5).js', - '!node_modules/react/**/*.development.js', - '!node_modules/react-dom/**/*.development.js', - '!node_modules/use-subscription/**/*.development.js', - '!node_modules/sharp/**/*', + ...ignore.map((path) => `!${path}`), ) + + const nextRoot = resolveModuleRoot('next') + if (nextRoot) { + 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`, + ) + } + + const sharpRoot = resolveModuleRoot('sharp') + if (sharpRoot) { + netlifyConfig.functions[functionName].included_files.push(`!${sharpRoot}/**/*`) + } }) } diff --git a/src/index.js b/src/index.js index 1f7a9c8621..0ff438e780 100644 --- a/src/index.js +++ b/src/index.js @@ -43,11 +43,11 @@ module.exports = { checkNextSiteHasBuilt({ publish, failBuild }) - const { appDir, basePath, i18n, images, target } = await getNextConfig({ publish, failBuild }) + const { appDir, basePath, i18n, images, target, ignore } = await getNextConfig({ publish, failBuild }) verifyBuildTarget(target) - configureHandlerFunctions({ netlifyConfig, publish: relative(process.cwd(), publish) }) + configureHandlerFunctions({ netlifyConfig, ignore, publish: relative(process.cwd(), publish) }) await generateFunctions(constants, appDir) await generatePagesResolver({ netlifyConfig, target, constants }) diff --git a/src/templates/getHandler.js b/src/templates/getHandler.js index 4b346ec8f1..b2cc76e148 100644 --- a/src/templates/getHandler.js +++ b/src/templates/getHandler.js @@ -7,26 +7,33 @@ const makeHandler = () => // We return a function and then call `toString()` on it to serialise it as the launcher function (conf, app) => { + // This is just so nft knows about the page entrypoints + try { + // eslint-disable-next-line node/no-missing-require + require.resolve('./pages.js') + } catch {} + let NextServer try { // next >= 11.0.1. Yay breaking changes in patch releases! NextServer = require('next/dist/server/next-server').default - } catch { + } catch (error) { + if (!error.message.includes("Cannot find module 'next/dist/server/next-server'")) { + // A different error, so rethrow it + throw error + } // Probably an old version of next } - // This is just so nft knows about the page entrypoints - try { - // eslint-disable-next-line node/no-missing-require - require.resolve('./pages.js') - } catch {} - if (!NextServer) { try { // next < 11.0.1 // eslint-disable-next-line node/no-missing-require, import/no-unresolved NextServer = require('next/dist/next-server/server/next-server').default - } catch { + } catch (error) { + if (!error.message.includes("Cannot find module 'next/dist/next-server/server/next-server'")) { + throw error + } throw new Error('Could not find Next.js server') } } @@ -66,11 +73,7 @@ const makeHandler = } } - if ( - multiValueHeaders['set-cookie'] && - multiValueHeaders['set-cookie'][0] && - multiValueHeaders['set-cookie'][0].includes('__prerender_bypass') - ) { + if (multiValueHeaders['set-cookie']?.[0]?.includes('__prerender_bypass')) { delete multiValueHeaders.etag multiValueHeaders['cache-control'] = ['no-cache'] } diff --git a/test/index.js b/test/index.js index 20169c97cc..816d71f7f6 100644 --- a/test/index.js +++ b/test/index.js @@ -1,7 +1,7 @@ -const { writeJSON, unlink, existsSync, readFileSync, copy } = require('fs-extra') +const { writeJSON, unlink, existsSync, readFileSync, copy, ensureDir } = require('fs-extra') const path = require('path') const process = require('process') - +const os = require('os') const cpy = require('cpy') const { dir: getTmpDir } = require('tmp-promise') @@ -42,9 +42,18 @@ const onBuildHasRun = (netlifyConfig) => // Move .next from sample project to current directory const moveNextDist = async function () { + await stubModules(['next', 'sharp']) await copy(path.join(SAMPLE_PROJECT_DIR, '.next'), path.join(process.cwd(), '.next')) } +const stubModules = async function (modules) { + for (const mod of modules) { + const dir = path.join(process.cwd(), 'node_modules', mod) + await ensureDir(dir) + await writeJSON(path.join(dir, 'package.json'), { name: mod }) + } +} + // Copy fixture files to the current directory const useFixture = async function (fixtureName) { const fixtureDir = `${FIXTURES_DIR}/${fixtureName}` @@ -185,19 +194,19 @@ describe('onBuild()', () => { '.next/serverless/**', '.next/*.json', '.next/BUILD_ID', - '!node_modules/@next/swc-*/**/*', - '!node_modules/next/dist/compiled/@ampproject/toolbox-optimizer/**/*', - '!node_modules/next/dist/pages/**/*', + '!../node_modules/next/dist/compiled/@ampproject/toolbox-optimizer/**/*', `!node_modules/next/dist/server/lib/squoosh/**/*.wasm`, `!node_modules/next/dist/next-server/server/lib/squoosh/**/*.wasm`, - '!node_modules/next/dist/compiled/webpack/(bundle4|bundle5).js', - '!node_modules/react/**/*.development.js', - '!node_modules/react-dom/**/*.development.js', - '!node_modules/use-subscription/**/*.development.js', + '!node_modules/next/dist/compiled/webpack/bundle4.js', + '!node_modules/next/dist/compiled/webpack/bundle5.js', + '!node_modules/next/dist/compiled/terser/bundle.min.js', '!node_modules/sharp/**/*', ] - expect(netlifyConfig.functions[HANDLER_FUNCTION_NAME].included_files).toEqual(includes) - expect(netlifyConfig.functions[ODB_FUNCTION_NAME].included_files).toEqual(includes) + // Relative paths in Windows are different + if (os.platform() !== 'win32') { + expect(netlifyConfig.functions[HANDLER_FUNCTION_NAME].included_files).toEqual(includes) + expect(netlifyConfig.functions[ODB_FUNCTION_NAME].included_files).toEqual(includes) + } expect(netlifyConfig.functions[HANDLER_FUNCTION_NAME].node_bundler).toEqual('nft') expect(netlifyConfig.functions[ODB_FUNCTION_NAME].node_bundler).toEqual('nft') })