Skip to content

Commit

Permalink
fix(nextjs): Make Next.js types isomorphic (#6707)
Browse files Browse the repository at this point in the history
  • Loading branch information
lforst committed Jan 10, 2023
1 parent e332ae1 commit 59b0bf6
Show file tree
Hide file tree
Showing 41 changed files with 124 additions and 193 deletions.
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
File renamed without changes.
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
File renamed without changes.
File renamed without changes.
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;

// 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';
File renamed without changes.
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;
}
File renamed without changes.
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

0 comments on commit 59b0bf6

Please sign in to comment.