Skip to content

fix(nextjs): Make Next.js types isomorphic #6707

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ module.exports = [
},
{
name: '@sentry/nextjs Client - Webpack (gzipped + minified)',
path: 'packages/nextjs/build/esm/index.client.js',
path: 'packages/nextjs/build/esm/client/index.js',
import: '{ init }',
gzip: true,
limit: '57 KB',
Expand Down
11 changes: 5 additions & 6 deletions packages/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
"engines": {
"node": ">=8"
},
"main": "build/cjs/index.server.js",
"module": "build/esm/index.server.js",
"browser": "build/esm/index.client.js",
"types": "build/types/index.server.d.ts",
"main": "build/cjs/index.js",
"module": "build/esm/index.js",
"browser": "build/esm/client/index.js",
"types": "build/types/index.types.d.ts",
"publishConfig": {
"access": "public"
},
Expand All @@ -31,7 +31,6 @@
"tslib": "^1.9.3"
},
"devDependencies": {
"@sentry/nextjs": "7.30.0",
"@types/webpack": "^4.41.31",
"eslint-plugin-react": "^7.31.11",
"next": "10.1.3"
Expand All @@ -56,7 +55,7 @@
"build:rollup:watch": "nodemon --ext ts --watch src scripts/buildRollup.ts",
"build:types:watch": "tsc -p tsconfig.types.json --watch",
"build:npm": "ts-node ../../scripts/prepack.ts && npm pack ./build",
"circularDepCheck": "madge --circular src/index.client.ts && madge --circular --exclude 'config/types\\.ts' src/index.server.ts # see https://github.com/pahen/madge/issues/306",
"circularDepCheck": "madge --circular src/index.ts && madge --circular src/client/index.ts && madge --circular src/index.types.ts",
"clean": "rimraf build coverage sentry-nextjs-*.tgz",
"fix": "run-s fix:eslint fix:prettier",
"fix:eslint": "eslint . --format stylish --fix",
Expand Down
4 changes: 2 additions & 2 deletions packages/nextjs/rollup.npm.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ export default [
makeBaseNPMConfig({
// We need to include `instrumentServer.ts` separately because it's only conditionally required, and so rollup
// doesn't automatically include it when calculating the module dependency tree.
entrypoints: ['src/index.server.ts', 'src/index.client.ts', 'src/config/webpack.ts'],
entrypoints: ['src/index.ts', 'src/client/index.ts', 'src/config/webpack.ts'],

// prevent this internal nextjs code from ending up in our built package (this doesn't happen automatially because
// the name doesn't match an SDK dependency)
packageSpecificConfig: { external: ['next/router'] },
packageSpecificConfig: { external: ['next/router', 'next/constants'] },
}),
),
...makeNPMConfigVariants(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { RewriteFrames } from '@sentry/integrations';
import { configureScope, init as reactInit, Integrations } from '@sentry/react';
import { BrowserOptions, configureScope, init as reactInit, Integrations } from '@sentry/react';
import { BrowserTracing, defaultRequestInstrumentationOptions, hasTracingEnabled } from '@sentry/tracing';
import { EventProcessor } from '@sentry/types';

import { nextRouterInstrumentation } from './performance/client';
import { buildMetadata } from './utils/metadata';
import { NextjsOptions } from './utils/nextjsOptions';
import { applyTunnelRouteOption } from './utils/tunnelRoute';
import { addOrUpdateIntegration } from './utils/userIntegrations';
import { buildMetadata } from '../common/metadata';
import { addOrUpdateIntegration } from '../common/userIntegrations';
import { nextRouterInstrumentation } from './performance';
import { applyTunnelRouteOption } from './tunnelRoute';

export * from '@sentry/react';
export { nextRouterInstrumentation } from './performance/client';
export { captureUnderscoreErrorException } from './utils/_error';
export { nextRouterInstrumentation } from './performance';
export { captureUnderscoreErrorException } from '../common/_error';

export { Integrations };

Expand All @@ -35,7 +34,7 @@ const globalWithInjectedValues = global as typeof global & {
};

/** Inits the Sentry NextJS SDK on the browser with the React SDK. */
export function init(options: NextjsOptions): void {
export function init(options: BrowserOptions): void {
applyTunnelRouteOption(options);
buildMetadata(options, ['nextjs', 'react']);
options.environment = options.environment || process.env.NODE_ENV;
Expand All @@ -52,7 +51,7 @@ export function init(options: NextjsOptions): void {
});
}

function addClientIntegrations(options: NextjsOptions): void {
function addClientIntegrations(options: BrowserOptions): void {
let integrations = options.integrations || [];

// This value is injected at build time, based on the output directory specified in the build config. Though a default
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { BrowserOptions } from '@sentry/react';
import { dsnFromString, logger } from '@sentry/utils';

import { NextjsOptions } from './nextjsOptions';

const globalWithInjectedValues = global as typeof global & {
__sentryRewritesTunnelPath__?: string;
};

/**
* Applies the `tunnel` option to the Next.js SDK options based on `withSentryConfig`'s `tunnelRoute` option.
*/
export function applyTunnelRouteOption(options: NextjsOptions): void {
export function applyTunnelRouteOption(options: BrowserOptions): void {
const tunnelRouteOption = globalWithInjectedValues.__sentryRewritesTunnelPath__;
if (tunnelRouteOption && options.dsn) {
const dsnComponents = dsnFromString(options.dsn);
Expand Down
2 changes: 2 additions & 0 deletions packages/nextjs/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type { SentryWebpackPluginOptions } from './types';
export { withSentryConfig } from './withSentryConfig';
3 changes: 2 additions & 1 deletion packages/nextjs/src/config/templates/apiWrapperTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
// @ts-ignore See above
// eslint-disable-next-line import/no-unresolved
import * as origModule from '__SENTRY_WRAPPING_TARGET__';
// eslint-disable-next-line import/no-extraneous-dependencies
import * as Sentry from '@sentry/nextjs';
import type { PageConfig } from 'next';

// We import this from `wrappers` rather than directly from `next` because our version can work simultaneously with
// multiple versions of next. See note in `wrappers/types` for more.
import type { NextApiHandler } from '../wrappers';
import type { NextApiHandler } from '../../server/types';

type NextApiModule = (
| {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// @ts-ignore See above
// eslint-disable-next-line import/no-unresolved
import * as wrapee from '__SENTRY_WRAPPING_TARGET__';
// eslint-disable-next-line import/no-extraneous-dependencies
import * as Sentry from '@sentry/nextjs';
import type { GetServerSideProps, GetStaticProps, NextPage as NextPageComponent } from 'next';

Expand Down
5 changes: 3 additions & 2 deletions packages/nextjs/src/config/withSentryConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NEXT_PHASE_DEVELOPMENT_SERVER, NEXT_PHASE_PRODUCTION_BUILD } from '../utils/phases';
import { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_BUILD } from 'next/constants';

import type {
ExportedNextConfig,
NextConfigFunction,
Expand Down Expand Up @@ -50,7 +51,7 @@ function getFinalConfigObject(
// In order to prevent all of our build-time code from being bundled in people's route-handling serverless functions,
// we exclude `webpack.ts` and all of its dependencies from nextjs's `@vercel/nft` filetracing. We therefore need to
// make sure that we only require it at build time or in development mode.
if (phase === NEXT_PHASE_PRODUCTION_BUILD || phase === NEXT_PHASE_DEVELOPMENT_SERVER) {
if (phase === PHASE_PRODUCTION_BUILD || phase === PHASE_DEVELOPMENT_SERVER) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { constructWebpackConfigFunction } = require('./webpack');
return {
Expand Down
9 changes: 0 additions & 9 deletions packages/nextjs/src/config/wrappers/index.ts

This file was deleted.

2 changes: 2 additions & 0 deletions packages/nextjs/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './config';
export * from './server';
32 changes: 32 additions & 0 deletions packages/nextjs/src/index.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* eslint-disable import/export */

// We export everything from both the client part of the SDK and from the server part. Some of the exports collide,
// which is not allowed, unless we redifine the colliding exports in this file - which we do below.
export * from './config';
export * from './client';
export * from './server';

import type { Integration, Options, StackParser } from '@sentry/types';

import type { BrowserOptions } from './client';
import * as clientSdk from './client';
import type { NodeOptions } from './server';
import * as serverSdk from './server';

/** Initializes Sentry Next.js SDK */
export declare function init(options: Options | BrowserOptions | NodeOptions): void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: Can this just be BrowserOptions | NodeOptions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I would also do it that way but we defined it as such up until now and didn't want this PR to turn into a breaking change.


// We export a merged Integrations object so that users can (at least typing-wise) use all integrations everywhere.
export const Integrations = { ...clientSdk.Integrations, ...serverSdk.Integrations };

export declare const defaultIntegrations: Integration[];
export declare const defaultStackParser: StackParser;

// This variable is not a runtime variable but just a type to tell typescript that the methods below can either come
// from the client SDK or from the server SDK. TypeScript is smart enough to understand that these resolve to the same
// methods from `@sentry/core`.
declare const runtime: 'client' | 'server';

export const close = runtime === 'client' ? clientSdk.close : serverSdk.close;
export const flush = runtime === 'client' ? clientSdk.flush : serverSdk.flush;
export const lastEventId = runtime === 'client' ? clientSdk.lastEventId : serverSdk.lastEventId;
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import { Carrier, getHubFromCarrier, getMainCarrier } from '@sentry/core';
import { RewriteFrames } from '@sentry/integrations';
import { configureScope, getCurrentHub, init as nodeInit, Integrations } from '@sentry/node';
import { configureScope, getCurrentHub, init as nodeInit, Integrations, NodeOptions } from '@sentry/node';
import { hasTracingEnabled } from '@sentry/tracing';
import { EventProcessor } from '@sentry/types';
import { escapeStringForRegex, logger } from '@sentry/utils';
import * as domainModule from 'domain';
import * as path from 'path';

import { buildMetadata } from '../common/metadata';
import { addOrUpdateIntegration, IntegrationWithExclusionOption } from '../common/userIntegrations';
import { isBuild } from './utils/isBuild';
import { buildMetadata } from './utils/metadata';
import { NextjsOptions } from './utils/nextjsOptions';
import { addOrUpdateIntegration, IntegrationWithExclusionOption } from './utils/userIntegrations';

export * from '@sentry/node';
export { captureUnderscoreErrorException } from './utils/_error';

// Exporting the Replay integration also from index.server.ts because TS only recognizes types from index.server.ts
// If we didn't export this, TS would complain that it can't find `Sentry.Replay` in the package,
// causing a build failure, when users initialize Replay in their sentry.client.config.js/ts file.
export { Replay } from './index.client';
export { captureUnderscoreErrorException } from '../common/_error';

// Here we want to make sure to only include what doesn't have browser specifics
// because or SSR of next.js we can only use this.
Expand All @@ -40,7 +34,7 @@ export const IS_BUILD = isBuild();
const IS_VERCEL = !!process.env.VERCEL;

/** Inits the Sentry NextJS SDK on node. */
export function init(options: NextjsOptions): void {
export function init(options: NodeOptions): void {
if (__DEBUG_BUILD__ && options.debug) {
logger.enable();
}
Expand Down Expand Up @@ -107,7 +101,7 @@ function sdkAlreadyInitialized(): boolean {
return !!hub.getClient();
}

function addServerIntegrations(options: NextjsOptions): void {
function addServerIntegrations(options: NodeOptions): void {
let integrations = options.integrations || [];

// This value is injected at build time, based on the output directory specified in the build config. Though a default
Expand Down Expand Up @@ -152,15 +146,10 @@ const deprecatedIsBuild = (): boolean => isBuild();
// eslint-disable-next-line deprecation/deprecation
export { deprecatedIsBuild as isBuild };

export type { SentryWebpackPluginOptions } from './config/types';
export { withSentryConfig } from './config/withSentryConfig';
export {
withSentryGetServerSideProps,
withSentryGetStaticProps,
withSentryServerSideGetInitialProps,
withSentryServerSideAppGetInitialProps,
withSentryServerSideDocumentGetInitialProps,
withSentryServerSideErrorGetInitialProps,
withSentryAPI,
withSentry,
} from './config/wrappers';
export { withSentryGetStaticProps } from './withSentryGetStaticProps';
export { withSentryServerSideGetInitialProps } from './withSentryServerSideGetInitialProps';
export { withSentryServerSideAppGetInitialProps } from './withSentryServerSideAppGetInitialProps';
export { withSentryServerSideDocumentGetInitialProps } from './withSentryServerSideDocumentGetInitialProps';
export { withSentryServerSideErrorGetInitialProps } from './withSentryServerSideErrorGetInitialProps';
export { withSentryGetServerSideProps } from './withSentryGetServerSideProps';
export { withSentry, withSentryAPI } from './withSentryAPI';
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { NEXT_PHASE_PRODUCTION_BUILD } from './phases';
import { PHASE_PRODUCTION_BUILD } from 'next/constants';

/**
* Decide if the currently running process is part of the build phase or happening at runtime.
*/
export function isBuild(): boolean {
return process.env.NEXT_PHASE === NEXT_PHASE_PRODUCTION_BUILD;
return process.env.NEXT_PHASE === PHASE_PRODUCTION_BUILD;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@
import * as domain from 'domain';
import { IncomingMessage, ServerResponse } from 'http';

import { platformSupportsStreaming } from '../../utils/platformSupportsStreaming';
import { autoEndTransactionOnResponseEnd, flushQueue } from './utils/responseEnd';
import { platformSupportsStreaming } from './platformSupportsStreaming';
import { autoEndTransactionOnResponseEnd, flushQueue } from './responseEnd';

declare module 'http' {
interface IncomingMessage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {
} from '@sentry/utils';
import * as domain from 'domain';

import { formatAsCode, nextLogger } from '../../utils/nextLogger';
import { platformSupportsStreaming } from '../../utils/platformSupportsStreaming';
import type { AugmentedNextApiRequest, AugmentedNextApiResponse, NextApiHandler, WrappedNextApiHandler } from './types';
import { formatAsCode, nextLogger } from './utils/nextLogger';
import { platformSupportsStreaming } from './utils/platformSupportsStreaming';
import { autoEndTransactionOnResponseEnd, finishTransaction, flushQueue } from './utils/responseEnd';

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import { hasTracingEnabled } from '@sentry/tracing';
import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils';
import { GetServerSideProps } from 'next';

import { isBuild } from '../../utils/isBuild';
import { getTransactionFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher } from './wrapperUtils';
import { isBuild } from './utils/isBuild';
import {
getTransactionFromRequest,
withErrorInstrumentation,
withTracedServerSideDataFetcher,
} from './utils/wrapperUtils';

/**
* Create a wrapped version of the user's exported `getServerSideProps` function
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { GetStaticProps } from 'next';

import { isBuild } from '../../utils/isBuild';
import { callDataFetcherTraced, withErrorInstrumentation } from './wrapperUtils';
import { isBuild } from './utils/isBuild';
import { callDataFetcherTraced, withErrorInstrumentation } from './utils/wrapperUtils';

type Props = { [key: string]: unknown };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import { hasTracingEnabled } from '@sentry/tracing';
import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils';
import App from 'next/app';

import { isBuild } from '../../utils/isBuild';
import { getTransactionFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher } from './wrapperUtils';
import { isBuild } from './utils/isBuild';
import {
getTransactionFromRequest,
withErrorInstrumentation,
withTracedServerSideDataFetcher,
} from './utils/wrapperUtils';

type AppGetInitialProps = typeof App['getInitialProps'];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { hasTracingEnabled } from '@sentry/tracing';
import Document from 'next/document';

import { isBuild } from '../../utils/isBuild';
import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './wrapperUtils';
import { isBuild } from './utils/isBuild';
import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils';

type DocumentGetInitialProps = typeof Document.getInitialProps;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils';
import { NextPageContext } from 'next';
import { ErrorProps } from 'next/error';

import { isBuild } from '../../utils/isBuild';
import { getTransactionFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher } from './wrapperUtils';
import { isBuild } from './utils/isBuild';
import {
getTransactionFromRequest,
withErrorInstrumentation,
withTracedServerSideDataFetcher,
} from './utils/wrapperUtils';

type ErrorGetInitialProps = (context: NextPageContext) => Promise<ErrorProps>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import { hasTracingEnabled } from '@sentry/tracing';
import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils';
import { NextPage } from 'next';

import { isBuild } from '../../utils/isBuild';
import { getTransactionFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher } from './wrapperUtils';
import { isBuild } from './utils/isBuild';
import {
getTransactionFromRequest,
withErrorInstrumentation,
withTracedServerSideDataFetcher,
} from './utils/wrapperUtils';

type GetInitialProps = Required<NextPage>['getInitialProps'];

Expand Down
Loading