From 844b880d1943e9ae53b6386f4d9b3f50f5a3783e Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Wed, 25 Mar 2026 09:54:25 +0800 Subject: [PATCH] refactor: prefer instrumentation-client (#34009) --- .../instrumentation-client.spec.ts} | 0 web/app/components/browser-initializer.tsx | 66 --------------- .../components/lazy-sentry-initializer.tsx | 16 ---- web/app/components/sentry-initializer.tsx | 27 ------- web/app/layout.tsx | 31 +++---- .../styles/{markdown.scss => markdown.css} | 4 +- web/instrumentation-client.ts | 81 +++++++++++++++++++ web/package.json | 2 +- web/plugins/vite/inject-target.ts | 7 ++ web/pnpm-lock.yaml | 11 +-- web/tailwind-common-config.ts | 1 + web/vite.config.ts | 14 ++-- 12 files changed, 118 insertions(+), 142 deletions(-) rename web/{app/components/__tests__/browser-initializer.spec.ts => __tests__/instrumentation-client.spec.ts} (100%) delete mode 100644 web/app/components/browser-initializer.tsx delete mode 100644 web/app/components/lazy-sentry-initializer.tsx delete mode 100644 web/app/components/sentry-initializer.tsx rename web/app/styles/{markdown.scss => markdown.css} (99%) create mode 100644 web/instrumentation-client.ts create mode 100644 web/plugins/vite/inject-target.ts diff --git a/web/app/components/__tests__/browser-initializer.spec.ts b/web/__tests__/instrumentation-client.spec.ts similarity index 100% rename from web/app/components/__tests__/browser-initializer.spec.ts rename to web/__tests__/instrumentation-client.spec.ts diff --git a/web/app/components/browser-initializer.tsx b/web/app/components/browser-initializer.tsx deleted file mode 100644 index c2194ca8d4..0000000000 --- a/web/app/components/browser-initializer.tsx +++ /dev/null @@ -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 (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 - - constructor() { - this.data = {} as Record - } - - 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 diff --git a/web/app/components/lazy-sentry-initializer.tsx b/web/app/components/lazy-sentry-initializer.tsx deleted file mode 100644 index 8c29ca4f9a..0000000000 --- a/web/app/components/lazy-sentry-initializer.tsx +++ /dev/null @@ -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 -} - -export default LazySentryInitializer diff --git a/web/app/components/sentry-initializer.tsx b/web/app/components/sentry-initializer.tsx deleted file mode 100644 index 00c3af37c1..0000000000 --- a/web/app/components/sentry-initializer.tsx +++ /dev/null @@ -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 diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 1cf1bb0d94..f08ce9bb49 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -9,14 +9,12 @@ import { getLocaleOnServer } from '@/i18n-config/server' import { ToastProvider } from './components/base/toast' import { ToastHost } from './components/base/ui/toast' import { TooltipProvider } from './components/base/ui/tooltip' -import BrowserInitializer from './components/browser-initializer' import { AgentationLoader } from './components/devtools/agentation-loader' import { ReactScanLoader } from './components/devtools/react-scan/loader' -import LazySentryInitializer from './components/lazy-sentry-initializer' import { I18nServerProvider } from './components/provider/i18n-server' import RoutePrefixHandle from './routePrefixHandle' import './styles/globals.css' -import './styles/markdown.scss' +import './styles/markdown.css' export const viewport: Viewport = { width: 'device-width', @@ -56,7 +54,6 @@ const LocaleLayout = async ({ className="h-full select-auto" {...datasetMap} > -
- - - - - - - - {children} - - - - - - + + + + + + + {children} + + + + + diff --git a/web/app/styles/markdown.scss b/web/app/styles/markdown.css similarity index 99% rename from web/app/styles/markdown.scss rename to web/app/styles/markdown.css index 69fec3bbc3..368c3718af 100644 --- a/web/app/styles/markdown.scss +++ b/web/app/styles/markdown.css @@ -1,5 +1,5 @@ -@use '../../themes/markdown-light'; -@use '../../themes/markdown-dark'; +@import '../../themes/markdown-light.css'; +@import '../../themes/markdown-dark.css'; .markdown-body { -ms-text-size-adjust: 100%; diff --git a/web/instrumentation-client.ts b/web/instrumentation-client.ts new file mode 100644 index 0000000000..4c9e6a10e7 --- /dev/null +++ b/web/instrumentation-client.ts @@ -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 (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 + + constructor() { + this.data = {} as Record + } + + 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() diff --git a/web/package.json b/web/package.json index ee8dbb466e..7ce1d2a530 100644 --- a/web/package.json +++ b/web/package.json @@ -241,7 +241,7 @@ "tsx": "4.21.0", "typescript": "5.9.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-plugin-inspect": "11.3.3", "vite-plus": "0.1.13", diff --git a/web/plugins/vite/inject-target.ts b/web/plugins/vite/inject-target.ts new file mode 100644 index 0000000000..845f949174 --- /dev/null +++ b/web/plugins/vite/inject-target.ts @@ -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) +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index f0a3054947..933870a79b 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -606,8 +606,8 @@ importers: specifier: 3.19.3 version: 3.19.3 vinext: - specifier: 0.0.34 - version: 0.0.34(1a91bf00ec5f7fb5f0ffb625316f9d01) + specifier: https://pkg.pr.new/vinext@b6a2cac + version: https://pkg.pr.new/vinext@b6a2cac(1a91bf00ec5f7fb5f0ffb625316f9d01) vite: 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)' @@ -7770,8 +7770,9 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vinext@0.0.34: - resolution: {integrity: sha512-igBP6UrL/tEtT73hPFt6EKmHTKd4lsSZ8luV5oQjmDWPcgGew3FaSt/7vo6qK42mj7yfg5MjvmQ2VmQrm2z8Cw==} + vinext@https://pkg.pr.new/vinext@b6a2cac: + resolution: {integrity: sha512-/Jm507qqC1dCOhCaorb9H8/I5JEqkcsiUJw0Wgprg7Znym4eyLUvcWcRLVyM9z22Tm0+O1PugcSDA8oNvbqPuQ==, tarball: https://pkg.pr.new/vinext@b6a2cac} + version: 0.0.5 engines: {node: '>=22'} hasBin: true peerDependencies: @@ -15977,7 +15978,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vinext@0.0.34(1a91bf00ec5f7fb5f0ffb625316f9d01): + vinext@https://pkg.pr.new/vinext@b6a2cac(1a91bf00ec5f7fb5f0ffb625316f9d01): 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) '@vercel/og': 0.8.6 diff --git a/web/tailwind-common-config.ts b/web/tailwind-common-config.ts index 73b5a1c2ce..fd0d3a34d9 100644 --- a/web/tailwind-common-config.ts +++ b/web/tailwind-common-config.ts @@ -197,6 +197,7 @@ const config = { path.resolve(_dirname, './app/components/base/button/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/segmented-control/index.css'), ]), ], // https://github.com/tailwindlabs/tailwindcss/discussions/5969 diff --git a/web/vite.config.ts b/web/vite.config.ts index 665d2d0a5f..617cae9ab5 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,4 +1,3 @@ -import path from 'node:path' import { fileURLToPath } from 'node:url' import react from '@vitejs/plugin-react' import vinext from 'vinext' @@ -6,11 +5,12 @@ import Inspect from 'vite-plugin-inspect' import { defineConfig } from 'vite-plus' import { createCodeInspectorPlugin, createForceInspectorClientInjectionPlugin } from './plugins/vite/code-inspector' import { customI18nHmrPlugin } from './plugins/vite/custom-i18n-hmr' +import { getRootClientInjectTarget } from './plugins/vite/inject-target' 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 browserInitializerInjectTarget = path.resolve(projectRoot, 'app/components/browser-initializer.tsx') +const rootClientInjectTarget = getRootClientInjectTarget(projectRoot) export default defineConfig(({ mode }) => { const isTest = mode === 'test' @@ -39,17 +39,17 @@ export default defineConfig(({ mode }) => { : [ Inspect(), createCodeInspectorPlugin({ - injectTarget: browserInitializerInjectTarget, + injectTarget: rootClientInjectTarget, }), createForceInspectorClientInjectionPlugin({ - injectTarget: browserInitializerInjectTarget, + injectTarget: rootClientInjectTarget, projectRoot, }), react(), vinext({ react: false }), - customI18nHmrPlugin({ injectTarget: browserInitializerInjectTarget }), + customI18nHmrPlugin({ injectTarget: rootClientInjectTarget }), // reactGrabOpenFilePlugin({ - // injectTarget: browserInitializerInjectTarget, + // injectTarget: rootClientInjectTarget, // projectRoot, // }), ],