Skip to content

Commit

Permalink
fix(nextjs): Fix middleware detection logic (#9637)
Browse files Browse the repository at this point in the history
  • Loading branch information
lforst committed Nov 23, 2023
1 parent 08818d8 commit 8c9ff6b
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 15 deletions.
14 changes: 12 additions & 2 deletions packages/nextjs/src/config/loaders/wrappingLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ const routeHandlerWrapperTemplatePath = path.resolve(__dirname, '..', 'templates
const routeHandlerWrapperTemplateCode = fs.readFileSync(routeHandlerWrapperTemplatePath, { encoding: 'utf8' });

export type WrappingLoaderOptions = {
pagesDir: string;
appDir: string;
pagesDir: string | undefined;
appDir: string | undefined;
pageExtensionRegex: string;
excludeServerRoutes: Array<RegExp | string>;
wrappingTargetKind: 'page' | 'api-route' | 'middleware' | 'server-component' | 'sentry-init' | 'route-handler';
Expand Down Expand Up @@ -101,6 +101,11 @@ export default function wrappingLoader(
return;
}
} else if (wrappingTargetKind === 'page' || wrappingTargetKind === 'api-route') {
if (pagesDir === undefined) {
this.callback(null, userCode, userModuleSourceMap);
return;
}

// Get the parameterized route name from this page's filepath
const parameterizedPagesRoute = path
// Get the path of the file insde of the pages directory
Expand Down Expand Up @@ -137,6 +142,11 @@ export default function wrappingLoader(
// Inject the route and the path to the file we're wrapping into the template
templateCode = templateCode.replace(/__ROUTE__/g, parameterizedPagesRoute.replace(/\\/g, '\\\\'));
} else if (wrappingTargetKind === 'server-component' || wrappingTargetKind === 'route-handler') {
if (appDir === undefined) {
this.callback(null, userCode, userModuleSourceMap);
return;
}

// Get the parameterized route name from this page's filepath
const parameterizedPagesRoute = path
// Get the path of the file insde of the app directory
Expand Down
35 changes: 23 additions & 12 deletions packages/nextjs/src/config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,26 +98,31 @@ export function constructWebpackConfigFunction(
],
});

let pagesDirPath: string;
let pagesDirPath: string | undefined;
const maybePagesDirPath = path.join(projectDir, 'pages');
const maybeSrcPagesDirPath = path.join(projectDir, 'src', 'pages');
if (fs.existsSync(maybePagesDirPath) && fs.lstatSync(maybePagesDirPath).isDirectory()) {
pagesDirPath = path.join(projectDir, 'pages');
} else {
pagesDirPath = path.join(projectDir, 'src', 'pages');
pagesDirPath = maybePagesDirPath;
} else if (fs.existsSync(maybeSrcPagesDirPath) && fs.lstatSync(maybeSrcPagesDirPath).isDirectory()) {
pagesDirPath = maybeSrcPagesDirPath;
}

let appDirPath: string;
let appDirPath: string | undefined;
const maybeAppDirPath = path.join(projectDir, 'app');
const maybeSrcAppDirPath = path.join(projectDir, 'src', 'app');
if (fs.existsSync(maybeAppDirPath) && fs.lstatSync(maybeAppDirPath).isDirectory()) {
appDirPath = path.join(projectDir, 'app');
} else {
appDirPath = path.join(projectDir, 'src', 'app');
appDirPath = maybeAppDirPath;
} else if (fs.existsSync(maybeSrcAppDirPath) && fs.lstatSync(maybeSrcAppDirPath).isDirectory()) {
appDirPath = maybeSrcAppDirPath;
}

const apiRoutesPath = path.join(pagesDirPath, 'api');
const apiRoutesPath = pagesDirPath ? path.join(pagesDirPath, 'api') : undefined;

const middlewareJsPath = path.join(pagesDirPath, '..', 'middleware.js');
const middlewareTsPath = path.join(pagesDirPath, '..', 'middleware.ts');
const middlewareLocationFolder = pagesDirPath
? path.join(pagesDirPath, '..')
: appDirPath
? path.join(appDirPath, '..')
: projectDir;

// Default page extensions per https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161
const pageExtensions = userNextConfig.pageExtensions || ['tsx', 'ts', 'jsx', 'js'];
Expand Down Expand Up @@ -151,6 +156,7 @@ export function constructWebpackConfigFunction(
const isPageResource = (resourcePath: string): boolean => {
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
return (
pagesDirPath !== undefined &&
normalizedAbsoluteResourcePath.startsWith(pagesDirPath + path.sep) &&
!normalizedAbsoluteResourcePath.startsWith(apiRoutesPath + path.sep) &&
dotPrefixedPageExtensions.some(ext => normalizedAbsoluteResourcePath.endsWith(ext))
Expand All @@ -167,7 +173,10 @@ export function constructWebpackConfigFunction(

const isMiddlewareResource = (resourcePath: string): boolean => {
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
return normalizedAbsoluteResourcePath === middlewareJsPath || normalizedAbsoluteResourcePath === middlewareTsPath;
return (
normalizedAbsoluteResourcePath.startsWith(middlewareLocationFolder + path.sep) &&
!!normalizedAbsoluteResourcePath.match(/[\\/]middleware\.(js|jsx|ts|tsx)$/)
);
};

const isServerComponentResource = (resourcePath: string): boolean => {
Expand All @@ -176,6 +185,7 @@ export function constructWebpackConfigFunction(
// ".js, .jsx, or .tsx file extensions can be used for Pages"
// https://beta.nextjs.org/docs/routing/pages-and-layouts#pages:~:text=.js%2C%20.jsx%2C%20or%20.tsx%20file%20extensions%20can%20be%20used%20for%20Pages.
return (
appDirPath !== undefined &&
normalizedAbsoluteResourcePath.startsWith(appDirPath + path.sep) &&
!!normalizedAbsoluteResourcePath.match(/[\\/](page|layout|loading|head|not-found)\.(js|jsx|tsx)$/)
);
Expand All @@ -184,6 +194,7 @@ export function constructWebpackConfigFunction(
const isRouteHandlerResource = (resourcePath: string): boolean => {
const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath);
return (
appDirPath !== undefined &&
normalizedAbsoluteResourcePath.startsWith(appDirPath + path.sep) &&
!!normalizedAbsoluteResourcePath.match(/[\\/]route\.(js|jsx|ts|tsx)$/)
);
Expand Down
25 changes: 24 additions & 1 deletion packages/nextjs/test/config/loaders.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// mock helper functions not tested directly in this file
import './mocks';

import * as fs from 'fs';

import type { ModuleRuleUseProperty, WebpackModuleRule } from '../../src/config/types';
import {
clientBuildContext,
Expand All @@ -11,6 +13,9 @@ import {
} from './fixtures';
import { materializeFinalWebpackConfig } from './testUtils';

const existsSyncSpy = jest.spyOn(fs, 'existsSync');
const lstatSyncSpy = jest.spyOn(fs, 'lstatSync');

type MatcherResult = { pass: boolean; message: () => string };

expect.extend({
Expand Down Expand Up @@ -85,6 +90,7 @@ describe('webpack loaders', () => {
});
});

// For these tests we assume that we have an app and pages folder in {rootdir}/src
it.each([
{
resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/pages/testPage.tsx',
Expand Down Expand Up @@ -139,8 +145,9 @@ describe('webpack loaders', () => {
resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/middleware.ts',
expectedWrappingTargetKind: 'middleware',
},
// Since we assume we have a pages file in src middleware will only be included in the build if it is also in src
{
resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/middleware.tsx',
resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/middleware.tsx',
expectedWrappingTargetKind: undefined,
},
{
Expand Down Expand Up @@ -182,6 +189,22 @@ describe('webpack loaders', () => {
])(
'should apply the right wrappingTargetKind with wrapping loader ($resourcePath)',
async ({ resourcePath, expectedWrappingTargetKind }) => {
// We assume that we have an app and pages folder in {rootdir}/src
existsSyncSpy.mockImplementation(path => {
if (
path.toString().startsWith('/Users/Maisey/projects/squirrelChasingSimulator/app') ||
path.toString().startsWith('/Users/Maisey/projects/squirrelChasingSimulator/pages')
) {
return false;
}
return true;
});

// @ts-expect-error Too lazy to mock the entire thing
lstatSyncSpy.mockImplementation(() => ({
isDirectory: () => true,
}));

const finalWebpackConfig = await materializeFinalWebpackConfig({
exportedNextConfig,
incomingWebpackConfig: serverWebpackConfig,
Expand Down

0 comments on commit 8c9ff6b

Please sign in to comment.