Skip to content

Commit 1d50eef

Browse files
authored
fix(nextjs): Fix serverside transaction names on Windows (#9526)
1 parent 82e87dc commit 1d50eef

File tree

3 files changed

+117
-17
lines changed

3 files changed

+117
-17
lines changed

packages/nextjs/src/config/loaders/types.ts

-7
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@ export type LoaderThis<Options> = {
88
*/
99
resourcePath: string;
1010

11-
/**
12-
* Query at the end of resolved file name ("../some-folder/some-module?foobar" -> resourceQuery: "?foobar")
13-
*
14-
* https://webpack.js.org/api/loaders/#thisresourcequery
15-
*/
16-
resourceQuery: string;
17-
1811
/**
1912
* Function to add outside file used by loader to `watch` process
2013
*

packages/nextjs/src/config/loaders/wrappingLoader.ts

+12-10
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const serverComponentWrapperTemplateCode = fs.readFileSync(serverComponentWrappe
4040
const routeHandlerWrapperTemplatePath = path.resolve(__dirname, '..', 'templates', 'routeHandlerWrapperTemplate.js');
4141
const routeHandlerWrapperTemplateCode = fs.readFileSync(routeHandlerWrapperTemplatePath, { encoding: 'utf8' });
4242

43-
type LoaderOptions = {
43+
export type WrappingLoaderOptions = {
4444
pagesDir: string;
4545
appDir: string;
4646
pageExtensionRegex: string;
@@ -58,7 +58,7 @@ type LoaderOptions = {
5858
*/
5959
// eslint-disable-next-line complexity
6060
export default function wrappingLoader(
61-
this: LoaderThis<LoaderOptions>,
61+
this: LoaderThis<WrappingLoaderOptions>,
6262
userCode: string,
6363
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6464
userModuleSourceMap: any,
@@ -102,12 +102,11 @@ export default function wrappingLoader(
102102
}
103103
} else if (wrappingTargetKind === 'page' || wrappingTargetKind === 'api-route') {
104104
// Get the parameterized route name from this page's filepath
105-
const parameterizedPagesRoute = path.posix
106-
.normalize(
107-
path
108-
// Get the path of the file insde of the pages directory
109-
.relative(pagesDir, this.resourcePath),
110-
)
105+
const parameterizedPagesRoute = path
106+
// Get the path of the file insde of the pages directory
107+
.relative(pagesDir, this.resourcePath)
108+
// Replace all backslashes with forward slashes (windows)
109+
.replace(/\\/g, '/')
111110
// Add a slash at the beginning
112111
.replace(/(.*)/, '/$1')
113112
// Pull off the file extension
@@ -139,8 +138,11 @@ export default function wrappingLoader(
139138
templateCode = templateCode.replace(/__ROUTE__/g, parameterizedPagesRoute.replace(/\\/g, '\\\\'));
140139
} else if (wrappingTargetKind === 'server-component' || wrappingTargetKind === 'route-handler') {
141140
// Get the parameterized route name from this page's filepath
142-
const parameterizedPagesRoute = path.posix
143-
.normalize(path.relative(appDir, this.resourcePath))
141+
const parameterizedPagesRoute = path
142+
// Get the path of the file insde of the app directory
143+
.relative(appDir, this.resourcePath)
144+
// Replace all backslashes with forward slashes (windows)
145+
.replace(/\\/g, '/')
144146
// Add a slash at the beginning
145147
.replace(/(.*)/, '/$1')
146148
// Pull off the file name
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
const originalReadfileSync = fs.readFileSync;
5+
6+
jest.spyOn(fs, 'readFileSync').mockImplementation((filePath, options) => {
7+
if (filePath.toString().endsWith('/config/templates/apiWrapperTemplate.js')) {
8+
return originalReadfileSync(
9+
path.join(__dirname, '../../build/cjs/config/templates/apiWrapperTemplate.js'),
10+
options,
11+
);
12+
}
13+
14+
if (filePath.toString().endsWith('/config/templates/pageWrapperTemplate.js')) {
15+
return originalReadfileSync(
16+
path.join(__dirname, '../../build/cjs/config/templates/pageWrapperTemplate.js'),
17+
options,
18+
);
19+
}
20+
21+
if (filePath.toString().endsWith('/config/templates/middlewareWrapperTemplate.js')) {
22+
return originalReadfileSync(
23+
path.join(__dirname, '../../build/cjs/config/templates/middlewareWrapperTemplate.js'),
24+
options,
25+
);
26+
}
27+
28+
if (filePath.toString().endsWith('/config/templates/sentryInitWrapperTemplate.js')) {
29+
return originalReadfileSync(
30+
path.join(__dirname, '../../build/cjs/config/templates/sentryInitWrapperTemplate.js'),
31+
options,
32+
);
33+
}
34+
35+
if (filePath.toString().endsWith('/config/templates/serverComponentWrapperTemplate.js')) {
36+
return originalReadfileSync(
37+
path.join(__dirname, '../../build/cjs/config/templates/serverComponentWrapperTemplate.js'),
38+
options,
39+
);
40+
}
41+
42+
if (filePath.toString().endsWith('/config/templates/routeHandlerWrapperTemplate.js')) {
43+
return originalReadfileSync(
44+
path.join(__dirname, '../../build/cjs/config/templates/routeHandlerWrapperTemplate.js'),
45+
options,
46+
);
47+
}
48+
49+
return originalReadfileSync(filePath, options);
50+
});
51+
52+
import type { LoaderThis } from '../../src/config/loaders/types';
53+
import type { WrappingLoaderOptions } from '../../src/config/loaders/wrappingLoader';
54+
import wrappingLoader from '../../src/config/loaders/wrappingLoader';
55+
56+
const DEFAULT_PAGE_EXTENSION_REGEX = ['tsx', 'ts', 'jsx', 'js'].join('|');
57+
58+
const defaultLoaderThis = {
59+
addDependency: () => undefined,
60+
async: () => undefined,
61+
cacheable: () => undefined,
62+
};
63+
64+
describe('wrappingLoader', () => {
65+
it('should correctly wrap API routes on unix', async () => {
66+
const callback = jest.fn();
67+
68+
const userCode = `
69+
export default function handler(req, res) {
70+
res.json({ foo: "bar" });
71+
}
72+
`;
73+
const userCodeSourceMap = undefined;
74+
75+
const loaderPromise = new Promise<void>(resolve => {
76+
const loaderThis = {
77+
...defaultLoaderThis,
78+
resourcePath: '/my/pages/my/route.ts',
79+
callback: callback.mockImplementation(() => {
80+
resolve();
81+
}),
82+
getOptions() {
83+
return {
84+
pagesDir: '/my/pages',
85+
appDir: '/my/app',
86+
pageExtensionRegex: DEFAULT_PAGE_EXTENSION_REGEX,
87+
excludeServerRoutes: [],
88+
wrappingTargetKind: 'api-route',
89+
sentryConfigFilePath: '/my/sentry.server.config.ts',
90+
vercelCronsConfig: undefined,
91+
nextjsRequestAsyncStorageModulePath: '/my/request-async-storage.js',
92+
};
93+
},
94+
} satisfies LoaderThis<WrappingLoaderOptions>;
95+
96+
wrappingLoader.call(loaderThis, userCode, userCodeSourceMap);
97+
});
98+
99+
await loaderPromise;
100+
101+
expect(callback).toHaveBeenCalledWith(null, expect.stringContaining("'/my/route'"), expect.anything());
102+
});
103+
104+
it.todo('should correctly wrap API routes on unix');
105+
});

0 commit comments

Comments
 (0)