Kaynağa Gözat

feat: 依赖升级

f-dev 2 hafta önce
ebeveyn
işleme
a49983e8c5
6 değiştirilmiş dosya ile 337 ekleme ve 236 silme
  1. 1 1
      .nvmrc
  2. 6 6
      package.json
  3. 290 226
      pnpm-lock.yaml
  4. 3 1
      src/firebase.tsx
  5. 15 0
      src/utils/storage/index.ts
  6. 22 2
      vite.config.ts

+ 1 - 1
.nvmrc

@@ -1 +1 @@
-18.20.8
+22

+ 6 - 6
package.json

@@ -71,8 +71,8 @@
     "@types/ramda": "^0.30.2",
     "@types/react": "^18.3.28",
     "@types/react-dom": "^18.3.7",
-    "@vitejs/plugin-legacy": "6.1.1",
-    "@vitejs/plugin-react": "^4.3.4",
+    "@vitejs/plugin-legacy": "7.2.1",
+    "@vitejs/plugin-react": "^4.7.0",
     "autoprefixer": "10.4.17",
     "boxen": "^8.0.1",
     "consola": "^3.4.2",
@@ -105,16 +105,16 @@
     "tailwindcss": "3.4.1",
     "typescript": "^5.7.3",
     "typescript-eslint": "^8.58.1",
-    "vite": "^6.2.2",
+    "vite": "^7.3.6",
     "vite-plugin-compression": "^0.5.1",
     "vite-plugin-remove-console": "^2.2.0",
     "vite-plugin-style-import": "^2.0.0",
-    "vite-plugin-top-level-await": "1.4.4",
-    "vite-plugin-wasm": "3.5.0"
+    "vite-plugin-top-level-await": "1.6.0",
+    "vite-plugin-wasm": "3.6.0"
   },
   "packageManager": "pnpm@10.6.3",
   "engines": {
-    "node": ">=18.20.7",
+    "node": ">=22.12.0",
     "pnpm": ">=9"
   },
   "lint-staged": {

Dosya farkı çok büyük olduğundan ihmal edildi
+ 290 - 226
pnpm-lock.yaml


+ 3 - 1
src/firebase.tsx

@@ -5,7 +5,9 @@ let app: FirebaseApp;
 let analytics: Analytics;
 
 // Initialize Firebase
-if (import.meta.env.VITE_ENABLE_FIREBASE === 'true') {
+// typeof window 守卫:getAnalytics 依赖 window,预渲染(SSG)在 Node 端执行时即使误开了
+// VITE_ENABLE_FIREBASE 也不会崩;hydrate 后在浏览器端正常初始化。
+if (typeof window !== 'undefined' && import.meta.env.VITE_ENABLE_FIREBASE === 'true') {
     console.log('Firebase 初始化开始');
 
     // Firebase 配置

+ 15 - 0
src/utils/storage/index.ts

@@ -43,6 +43,13 @@ interface StorageData<T = any> {
     expire: number;
 }
 
+/**
+ * 是否处于可访问 localStorage 的浏览器环境。
+ * 预渲染(SSG)在 Node 端执行,无 window/localStorage,此时所有 storage 操作应安全降级为空操作/返回 null,
+ * 而非抛错。hydrate 后回到浏览器环境即恢复正常读写。
+ */
+const canUseStorage = () => typeof window !== 'undefined' && !!window.localStorage;
+
 function shouldEncryptKey(functionOptions?: StorageOptions, instanceOptions?: StorageOptions) {
     if (globalConfig.security.enableStorageEncryption) {
         return functionOptions?.encryptKey ?? instanceOptions?.encryptKey ?? false;
@@ -74,6 +81,7 @@ export function createLocalStorage({
      * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
      */
     function set<T = any>(key: string, data: T, opts?: StorageOptions) {
+        if (!canUseStorage()) return;
         const expire = opts?.expire ?? confExpire;
         const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
         const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData });
@@ -97,6 +105,7 @@ export function createLocalStorage({
      * @returns
      */
     function get<T = any>(key: string, opts?: StorageOptions) {
+        if (!canUseStorage()) return null;
         const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
         const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData });
 
@@ -131,6 +140,7 @@ export function createLocalStorage({
      * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
      */
     function remove(key: string, opts?: StorageOptions) {
+        if (!canUseStorage()) return;
         const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
         const finalKey = isEncryptKey ? encryptKey(key) : key;
         window.localStorage.removeItem(finalKey);
@@ -140,6 +150,7 @@ export function createLocalStorage({
      * 清除 localStorage 中的所有数据。**慎用**
      */
     function clear() {
+        if (!canUseStorage()) return;
         window.localStorage.clear();
     }
 
@@ -163,6 +174,7 @@ export function createSessionStorage({
      * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
      */
     function set<T = any>(key: string, data: T, opts?: StorageOptions) {
+        if (!canUseStorage()) return;
         const expire = opts?.expire ?? confExpire;
         const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
         const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData });
@@ -186,6 +198,7 @@ export function createSessionStorage({
      * @returns
      */
     function get<T = any>(key: string, opts?: StorageOptions) {
+        if (!canUseStorage()) return null;
         const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
         const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData });
 
@@ -220,6 +233,7 @@ export function createSessionStorage({
      * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
      */
     function remove(key: string, opts?: StorageOptions) {
+        if (!canUseStorage()) return;
         const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
         const finalKey = isEncryptKey ? encryptKey(key) : key;
         window.sessionStorage.removeItem(finalKey);
@@ -229,6 +243,7 @@ export function createSessionStorage({
      * 清除 sessionStorage 中的所有数据。**慎用**
      */
     function clear() {
+        if (!canUseStorage()) return;
         window.sessionStorage.clear();
     }
 

+ 22 - 2
vite.config.ts

@@ -15,11 +15,31 @@ export default defineConfig(({ mode }) => {
     const env = wrapperEnv(loadEnv(mode, root));
     const isProd = mode === 'production';
 
+    // @vitejs/plugin-legacy 在 config 阶段会无条件把 build.target 覆盖成含具体浏览器版本(如 safari13)的
+    // esbuild target。而 vite-plugin-top-level-await(brotli-wasm 依赖顶层 await)在转换 TLA 产物时会读取
+    // 这个 target,esbuild 0.28 无法把 TLA 生成的解构赋值降级到任何具体 safari 目标(safari13/14 均报
+    // "Transforming destructuring ... not supported yet",仅纯 ES 版本如 es2022 可通过)。
+    //
+    // 此内联插件把现代 chunk 的 build.target 钉为 es2022:
+    //   - enforce: 'post' 且排在 topLevelAwait() 之前 → 两个 post 插件间按数组顺序执行,
+    //     故本插件的 config hook 先于 TLA 运行,TLA 备份到的即 es2022,转换得以通过。
+    //   - 现代浏览器(safari13+/chrome87+)原生支持解构,es2022 产物运行无碍;
+    //     真正的旧浏览器仍由 plugin-legacy 的 legacy chunk(babel 全量转译)兜底。
+    const pinModernTargetPlugin = {
+        name: 'pin-modern-target-es2022',
+        enforce: 'post' as const,
+        config(config: { build?: { target?: unknown } }) {
+            config.build = config.build || {};
+            config.build.target = 'es2022';
+        },
+    };
+
     return {
         base: env.VITE_BUILD_PUBLIC_PATH,
         plugins: [
             react(),
             wasm(),
+            pinModernTargetPlugin,
             topLevelAwait(),
             legacy({
                 targets: [
@@ -92,8 +112,8 @@ export default defineConfig(({ mode }) => {
         build: {
             outDir: 'dist',
             sourcemap: !isProd,
-            // 为了处理 brotli-wasm 的引用问题,target 已改为使用 plugin-legacy 的 targets 控制
-            // target: ['es2015', 'chrome87', 'safari13', 'firefox78', 'edge88'],
+            // build.target 由上方 pinModernTargetPlugin 在 config 阶段统一钉为 'es2022'(详见该插件注释),
+            // 此处不再设置,避免与之冲突。
             // 与 plugin-legacy targets 对齐,使 CSS 压缩/转换兼容同一批浏览器(plugin-legacy 不处理 CSS)
             cssTarget: ['chrome87', 'safari13', 'firefox78', 'edge88'],
             rollupOptions: {

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor