refactor: prefer instrumentation-client (#34009)

This commit is contained in:
Stephen Zhou
2026-03-25 09:54:25 +08:00
committed by GitHub
parent 3f13db11c8
commit 844b880d19
12 changed files with 118 additions and 142 deletions

View File

@@ -1,66 +0,0 @@
'use client'
// Polyfill for Array.prototype.toSpliced (ES2023, Chrome 110+)
if (!Array.prototype.toSpliced) {
// eslint-disable-next-line no-extend-native
Array.prototype.toSpliced = function <T>(this: T[], start: number, deleteCount?: number, ...items: T[]): T[] {
const copy = this.slice()
// When deleteCount is undefined (omitted), delete to end; otherwise let splice handle coercion
if (deleteCount === undefined)
copy.splice(start, copy.length - start, ...items)
else
copy.splice(start, deleteCount, ...items)
return copy
}
}
class StorageMock {
data: Record<string, string>
constructor() {
this.data = {} as Record<string, string>
}
setItem(name: string, value: string) {
this.data[name] = value
}
getItem(name: string) {
return this.data[name] || null
}
removeItem(name: string) {
delete this.data[name]
}
clear() {
this.data = {}
}
}
let localStorage, sessionStorage
try {
localStorage = globalThis.localStorage
sessionStorage = globalThis.sessionStorage
}
catch {
localStorage = new StorageMock()
sessionStorage = new StorageMock()
}
Object.defineProperty(globalThis, 'localStorage', {
value: localStorage,
})
Object.defineProperty(globalThis, 'sessionStorage', {
value: sessionStorage,
})
const BrowserInitializer = ({
children,
}: { children: React.ReactElement }) => {
return children
}
export default BrowserInitializer

View File

@@ -1,16 +0,0 @@
'use client'
import { IS_DEV } from '@/config'
import { env } from '@/env'
import dynamic from '@/next/dynamic'
const SentryInitializer = dynamic(() => import('./sentry-initializer'), { ssr: false })
const LazySentryInitializer = () => {
if (IS_DEV || !env.NEXT_PUBLIC_SENTRY_DSN)
return null
return <SentryInitializer />
}
export default LazySentryInitializer

View File

@@ -1,27 +0,0 @@
'use client'
import * as Sentry from '@sentry/react'
import { useEffect } from 'react'
import { IS_DEV } from '@/config'
import { env } from '@/env'
const SentryInitializer = () => {
useEffect(() => {
const SENTRY_DSN = env.NEXT_PUBLIC_SENTRY_DSN
if (!IS_DEV && SENTRY_DSN) {
Sentry.init({
dsn: SENTRY_DSN,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
})
}
}, [])
return null
}
export default SentryInitializer

View File

@@ -9,14 +9,12 @@ import { getLocaleOnServer } from '@/i18n-config/server'
import { ToastProvider } from './components/base/toast' import { ToastProvider } from './components/base/toast'
import { ToastHost } from './components/base/ui/toast' import { ToastHost } from './components/base/ui/toast'
import { TooltipProvider } from './components/base/ui/tooltip' import { TooltipProvider } from './components/base/ui/tooltip'
import BrowserInitializer from './components/browser-initializer'
import { AgentationLoader } from './components/devtools/agentation-loader' import { AgentationLoader } from './components/devtools/agentation-loader'
import { ReactScanLoader } from './components/devtools/react-scan/loader' import { ReactScanLoader } from './components/devtools/react-scan/loader'
import LazySentryInitializer from './components/lazy-sentry-initializer'
import { I18nServerProvider } from './components/provider/i18n-server' import { I18nServerProvider } from './components/provider/i18n-server'
import RoutePrefixHandle from './routePrefixHandle' import RoutePrefixHandle from './routePrefixHandle'
import './styles/globals.css' import './styles/globals.css'
import './styles/markdown.scss' import './styles/markdown.css'
export const viewport: Viewport = { export const viewport: Viewport = {
width: 'device-width', width: 'device-width',
@@ -56,7 +54,6 @@ const LocaleLayout = async ({
className="h-full select-auto" className="h-full select-auto"
{...datasetMap} {...datasetMap}
> >
<LazySentryInitializer />
<div className="isolate h-full"> <div className="isolate h-full">
<JotaiProvider> <JotaiProvider>
<ThemeProvider <ThemeProvider
@@ -67,20 +64,18 @@ const LocaleLayout = async ({
enableColorScheme={false} enableColorScheme={false}
> >
<NuqsAdapter> <NuqsAdapter>
<BrowserInitializer> <TanstackQueryInitializer>
<TanstackQueryInitializer> <I18nServerProvider>
<I18nServerProvider> <ToastHost timeout={5000} limit={3} />
<ToastHost timeout={5000} limit={3} /> <ToastProvider>
<ToastProvider> <GlobalPublicStoreProvider>
<GlobalPublicStoreProvider> <TooltipProvider delay={300} closeDelay={200}>
<TooltipProvider delay={300} closeDelay={200}> {children}
{children} </TooltipProvider>
</TooltipProvider> </GlobalPublicStoreProvider>
</GlobalPublicStoreProvider> </ToastProvider>
</ToastProvider> </I18nServerProvider>
</I18nServerProvider> </TanstackQueryInitializer>
</TanstackQueryInitializer>
</BrowserInitializer>
</NuqsAdapter> </NuqsAdapter>
</ThemeProvider> </ThemeProvider>
</JotaiProvider> </JotaiProvider>

View File

@@ -1,5 +1,5 @@
@use '../../themes/markdown-light'; @import '../../themes/markdown-light.css';
@use '../../themes/markdown-dark'; @import '../../themes/markdown-dark.css';
.markdown-body { .markdown-body {
-ms-text-size-adjust: 100%; -ms-text-size-adjust: 100%;

View File

@@ -0,0 +1,81 @@
import { IS_DEV } from '@/config'
import { env } from '@/env'
async function main() {
// Polyfill for Array.prototype.toSpliced (ES2023, Chrome 110+)
if (!Array.prototype.toSpliced) {
// eslint-disable-next-line no-extend-native
Array.prototype.toSpliced = function <T>(this: T[], start: number, deleteCount?: number, ...items: T[]): T[] {
const copy = this.slice()
// When deleteCount is undefined (omitted), delete to end; otherwise let splice handle coercion
if (deleteCount === undefined)
copy.splice(start, copy.length - start, ...items)
else
copy.splice(start, deleteCount, ...items)
return copy
}
}
if (!('localStorage' in globalThis) || !('sessionStorage' in globalThis)) {
class StorageMock {
data: Record<string, string>
constructor() {
this.data = {} as Record<string, string>
}
setItem(name: string, value: string) {
this.data[name] = value
}
getItem(name: string) {
return this.data[name] || null
}
removeItem(name: string) {
delete this.data[name]
}
clear() {
this.data = {}
}
}
let localStorage, sessionStorage
try {
localStorage = globalThis.localStorage
sessionStorage = globalThis.sessionStorage
}
catch {
localStorage = new StorageMock()
sessionStorage = new StorageMock()
}
Object.defineProperty(globalThis, 'localStorage', {
value: localStorage,
})
Object.defineProperty(globalThis, 'sessionStorage', {
value: sessionStorage,
})
}
const SENTRY_DSN = env.NEXT_PUBLIC_SENTRY_DSN
if (!IS_DEV && SENTRY_DSN) {
const Sentry = await import('@sentry/react')
Sentry.init({
dsn: SENTRY_DSN,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
})
}
}
main()

View File

@@ -241,7 +241,7 @@
"tsx": "4.21.0", "tsx": "4.21.0",
"typescript": "5.9.3", "typescript": "5.9.3",
"uglify-js": "3.19.3", "uglify-js": "3.19.3",
"vinext": "0.0.34", "vinext": "https://pkg.pr.new/vinext@b6a2cac",
"vite": "npm:@voidzero-dev/vite-plus-core@0.1.13", "vite": "npm:@voidzero-dev/vite-plus-core@0.1.13",
"vite-plugin-inspect": "11.3.3", "vite-plugin-inspect": "11.3.3",
"vite-plus": "0.1.13", "vite-plus": "0.1.13",

View File

@@ -0,0 +1,7 @@
import path from 'node:path'
export const rootClientInjectTargetRelativePath = 'instrumentation-client.ts'
export const getRootClientInjectTarget = (projectRoot: string): string => {
return path.resolve(projectRoot, rootClientInjectTargetRelativePath)
}

11
web/pnpm-lock.yaml generated
View File

@@ -606,8 +606,8 @@ importers:
specifier: 3.19.3 specifier: 3.19.3
version: 3.19.3 version: 3.19.3
vinext: vinext:
specifier: 0.0.34 specifier: https://pkg.pr.new/vinext@b6a2cac
version: 0.0.34(1a91bf00ec5f7fb5f0ffb625316f9d01) version: https://pkg.pr.new/vinext@b6a2cac(1a91bf00ec5f7fb5f0ffb625316f9d01)
vite: vite:
specifier: npm:@voidzero-dev/vite-plus-core@0.1.13 specifier: npm:@voidzero-dev/vite-plus-core@0.1.13
version: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' version: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)'
@@ -7770,8 +7770,9 @@ packages:
vfile@6.0.3: vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
vinext@0.0.34: vinext@https://pkg.pr.new/vinext@b6a2cac:
resolution: {integrity: sha512-igBP6UrL/tEtT73hPFt6EKmHTKd4lsSZ8luV5oQjmDWPcgGew3FaSt/7vo6qK42mj7yfg5MjvmQ2VmQrm2z8Cw==} resolution: {integrity: sha512-/Jm507qqC1dCOhCaorb9H8/I5JEqkcsiUJw0Wgprg7Znym4eyLUvcWcRLVyM9z22Tm0+O1PugcSDA8oNvbqPuQ==, tarball: https://pkg.pr.new/vinext@b6a2cac}
version: 0.0.5
engines: {node: '>=22'} engines: {node: '>=22'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@@ -15977,7 +15978,7 @@ snapshots:
'@types/unist': 3.0.3 '@types/unist': 3.0.3
vfile-message: 4.0.3 vfile-message: 4.0.3
vinext@0.0.34(1a91bf00ec5f7fb5f0ffb625316f9d01): vinext@https://pkg.pr.new/vinext@b6a2cac(1a91bf00ec5f7fb5f0ffb625316f9d01):
dependencies: dependencies:
'@unpic/react': 1.0.2(next@16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@unpic/react': 1.0.2(next@16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@vercel/og': 0.8.6 '@vercel/og': 0.8.6

View File

@@ -197,6 +197,7 @@ const config = {
path.resolve(_dirname, './app/components/base/button/index.css'), path.resolve(_dirname, './app/components/base/button/index.css'),
path.resolve(_dirname, './app/components/base/modal/index.css'), path.resolve(_dirname, './app/components/base/modal/index.css'),
path.resolve(_dirname, './app/components/base/premium-badge/index.css'), path.resolve(_dirname, './app/components/base/premium-badge/index.css'),
path.resolve(_dirname, './app/components/base/segmented-control/index.css'),
]), ]),
], ],
// https://github.com/tailwindlabs/tailwindcss/discussions/5969 // https://github.com/tailwindlabs/tailwindcss/discussions/5969

View File

@@ -1,4 +1,3 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import vinext from 'vinext' import vinext from 'vinext'
@@ -6,11 +5,12 @@ import Inspect from 'vite-plugin-inspect'
import { defineConfig } from 'vite-plus' import { defineConfig } from 'vite-plus'
import { createCodeInspectorPlugin, createForceInspectorClientInjectionPlugin } from './plugins/vite/code-inspector' import { createCodeInspectorPlugin, createForceInspectorClientInjectionPlugin } from './plugins/vite/code-inspector'
import { customI18nHmrPlugin } from './plugins/vite/custom-i18n-hmr' import { customI18nHmrPlugin } from './plugins/vite/custom-i18n-hmr'
import { getRootClientInjectTarget } from './plugins/vite/inject-target'
import { nextStaticImageTestPlugin } from './plugins/vite/next-static-image-test' import { nextStaticImageTestPlugin } from './plugins/vite/next-static-image-test'
const projectRoot = path.dirname(fileURLToPath(import.meta.url)) const projectRoot = fileURLToPath(new URL('.', import.meta.url))
const isCI = !!process.env.CI const isCI = !!process.env.CI
const browserInitializerInjectTarget = path.resolve(projectRoot, 'app/components/browser-initializer.tsx') const rootClientInjectTarget = getRootClientInjectTarget(projectRoot)
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
const isTest = mode === 'test' const isTest = mode === 'test'
@@ -39,17 +39,17 @@ export default defineConfig(({ mode }) => {
: [ : [
Inspect(), Inspect(),
createCodeInspectorPlugin({ createCodeInspectorPlugin({
injectTarget: browserInitializerInjectTarget, injectTarget: rootClientInjectTarget,
}), }),
createForceInspectorClientInjectionPlugin({ createForceInspectorClientInjectionPlugin({
injectTarget: browserInitializerInjectTarget, injectTarget: rootClientInjectTarget,
projectRoot, projectRoot,
}), }),
react(), react(),
vinext({ react: false }), vinext({ react: false }),
customI18nHmrPlugin({ injectTarget: browserInitializerInjectTarget }), customI18nHmrPlugin({ injectTarget: rootClientInjectTarget }),
// reactGrabOpenFilePlugin({ // reactGrabOpenFilePlugin({
// injectTarget: browserInitializerInjectTarget, // injectTarget: rootClientInjectTarget,
// projectRoot, // projectRoot,
// }), // }),
], ],