-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathproxyLoader.ts
89 lines (77 loc) · 4.53 KB
/
proxyLoader.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import { escapeStringForRegex } from '@sentry/utils';
import * as fs from 'fs';
import * as path from 'path';
import { rollupize } from './rollup';
import { LoaderThis } from './types';
type LoaderOptions = {
pagesDir: string;
pageExtensionRegex: string;
};
/**
* Replace the loaded file with a proxy module "wrapping" the original file. In the proxy, the original file is loaded,
* any data-fetching functions (`getInitialProps`, `getStaticProps`, and `getServerSideProps`) it contains are wrapped,
* and then everything is re-exported.
*/
export default async function proxyLoader(this: LoaderThis<LoaderOptions>, userCode: string): Promise<string> {
// We know one or the other will be defined, depending on the version of webpack being used
const { pagesDir, pageExtensionRegex } = 'getOptions' in this ? this.getOptions() : this.query;
// Get the parameterized route name from this page's filepath
const parameterizedRoute = path
// Get the path of the file insde of the pages directory
.relative(pagesDir, this.resourcePath)
// Add a slash at the beginning
.replace(/(.*)/, '/$1')
// Pull off the file extension
.replace(new RegExp(`\\.(${pageExtensionRegex})`), '')
// Any page file named `index` corresponds to root of the directory its in, URL-wise, so turn `/xyz/index` into
// just `/xyz`
.replace(/\/index$/, '')
// In case all of the above have left us with an empty string (which will happen if we're dealing with the
// homepage), sub back in the root route
.replace(/^$/, '/');
// We don't want to wrap twice (or infinitely), so in the proxy we add this query string onto references to the
// wrapped file, so that we know that it's already been processed. (Adding this query string is also necessary to
// convince webpack that it's a different file than the one it's in the middle of loading now, so that the originals
// themselves will have a chance to load.)
if (this.resourceQuery.includes('__sentry_wrapped__')) {
return userCode;
}
const templateFile = parameterizedRoute.startsWith('/api')
? 'apiProxyLoaderTemplate.js'
: 'pageProxyLoaderTemplate.js';
const templatePath = path.resolve(__dirname, `../templates/${templateFile}`);
let templateCode = fs.readFileSync(templatePath).toString();
// Make sure the template is included when runing `webpack watch`
this.addDependency(templatePath);
// Inject the route into the template
templateCode = templateCode.replace(/__ROUTE__/g, parameterizedRoute);
// Fill in the path to the file we're wrapping and save the result as a temporary file in the same folder (so that
// relative imports and exports are calculated correctly).
//
// TODO: We're saving the filled-in template to disk, however temporarily, because Rollup expects a path to a code
// file, not code itself. There is a rollup plugin which can fake this (`@rollup/plugin-virtual`) but the virtual file
// seems to be inside of a virtual directory (in other words, one level down from where you'd expect it) and that
// messes up relative imports and exports. Presumably there's a way to make it work, though, and if we can, it would
// be cleaner than having to first write and then delete a temporary file each time we run this loader.
templateCode = templateCode.replace(/__RESOURCE_PATH__/g, this.resourcePath);
const tempFilePath = path.resolve(path.dirname(this.resourcePath), `temp${Math.random()}.js`);
fs.writeFileSync(tempFilePath, templateCode);
// Run the proxy module code through Rollup, in order to split the `export * from '<wrapped file>'` out into
// individual exports (which nextjs seems to require), then delete the tempoary file.
let proxyCode = await rollupize(tempFilePath, this.resourcePath);
fs.unlinkSync(tempFilePath);
if (!proxyCode) {
// We will already have thrown a warning in `rollupize`, so no need to do it again here
return userCode;
}
// Add a query string onto all references to the wrapped file, so that webpack will consider it different from the
// non-query-stringged version (which we're already in the middle of loading as we speak), and load it separately from
// this. When the second load happens this loader will run again, but we'll be able to see the query string and will
// know to immediately return without processing. This avoids an infinite loop.
const resourceFilename = path.basename(this.resourcePath);
proxyCode = proxyCode.replace(
new RegExp(`/${escapeStringForRegex(resourceFilename)}'`, 'g'),
`/${resourceFilename}?__sentry_wrapped__'`,
);
return proxyCode;
}