WebAssembly 2.0:开启浏览器高性能计算新时代

探索WebAssembly 2.0的新特性,包括垃圾回收、线程并行、组件模型等,以及在AI推理、视频处理等场景的实战应用

Serverless架构与前端部署:现代化部署策略

Serverless架构概述 Serverless架构让开发者无需管理服务器即可部署应用,主要优势包括: 按需付费 - 只为实际使用的资源付费 自动扩展 - 根据流量自动调整 零运维 - 无需管理服务器 全球CDN - 内容分发网络加速 Vercel部署 项目配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // vercel.json { "buildCommand": "npm run build", "outputDirectory": ".output/public", "devCommand": "npm run dev", "installCommand": "npm install", "framework": "nuxt", "regions": ["hkg1", "sin1"], "headers": [ { "source": "/api/(.*)", "headers": [ { "key": "Access-Control-Allow-Origin", "value": "*" } ] } ], "rewrites": [ { "source": "/api/:path*", "destination": "/api/:path*" } ] } 环境变量 1 2 3 4 # 在Vercel控制台设置环境变量 # 或通过vercel CLI vercel env add DATABASE_URL production vercel env add API_KEY production Serverless函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // api/hello.js export default function handler(req, res) { const { name } = req.query res.status(200).json({ message: `Hello ${name || 'World'}!`, timestamp: Date.now() }) } // api/users/[id].js export default function handler(req, res) { const { id } = req.query switch (req.method) { case 'GET': res.status(200).json({ id, name: `User ${id}` }) break case 'PUT': res.status(200).json({ id, ...req.body }) break default: res.setHeader('Allow', ['GET', 'PUT']) res.status(405).end(`Method ${req.method} Not Allowed`) } } Netlify部署 netlify.toml配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [build] command = "npm run build" publish = "dist" [build.environment] NODE_VERSION = "18" [[redirects]] from = "/api/*" to = "/.netlify/functions/:splat" status = 200 [[redirects]] from = "/*" to = "/index.html" status = 200 [functions] directory = "netlify/functions" [[plugins]] package = "@netlify/plugin-lighthouse" Netlify Functions 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 // netlify/functions/hello.js exports.handler = async (event, context) => { const { name } = event.queryStringParameters || {} return { statusCode: 200, body: JSON.stringify({ message: `Hello ${name || 'World'}!` }), headers: { 'Content-Type': 'application/json' } } } // netlify/functions/users/[id].js exports.handler = async (event, context) => { const { id } = event.pathParameters switch (event.httpMethod) { case 'GET': return { statusCode: 200, body: JSON.stringify({ id, name: `User ${id}` }) } default: return { statusCode: 405, body: 'Method Not Allowed' } } } Cloudflare Workers Worker配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // worker.js export default { async fetch(request, env, ctx) { const url = new URL(request.url) // API路由 if (url.pathname.startsWith('/api/')) { return handleAPI(request, env) } // 静态资源 return env.ASSETS.fetch(request) } } async function handleAPI(request, env) { const url = new URL(request.url) const path = url.pathname.replace('/api/', '') switch (path) { case 'hello': return new Response(JSON.stringify({ message: 'Hello from Cloudflare!' }), { headers: { 'Content-Type': 'application/json' } }) default: return new Response('Not Found', { status: 404 }) } } wrangler.toml配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 name = "my-worker" main = "worker.js" compatibility_date = "2024-01-01" [env.production] routes = [ { pattern = "https://example.com/api/*", zone_name = "example.com" } ] [env.production.vars] ENVIRONMENT = "production" API_URL = "https://api.example.com" [[env.production.kv_namespaces]] binding = "KV" id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" CI/CD配置 GitHub Actions 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 # .github/workflows/deploy.yml name: Deploy on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Install dependencies run: npm ci - name: Run tests run: npm test - name: Build run: npm run build deploy-vercel: needs: test if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy to Vercel uses: amondnet/vercel-action@v20 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.ORG_ID }} vercel-project-id: ${{ secrets.PROJECT_ID }} vercel-args: '--prod' deploy-netlify: needs: test if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy to Netlify uses: nwtgck/actions-netlify@v1.2 with: publish-dir: './dist' production-branch: main github-token: ${{ secrets.GITHUB_TOKEN }} deploy-message: "Deploy from GitHub Actions" env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} GitLab CI 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # .gitlab-ci.yml stages: - test - build - deploy test: stage: test image: node:18 script: - npm ci - npm test build: stage: build image: node:18 script: - npm ci - npm run build artifacts: paths: - dist deploy: stage: deploy image: node:18 script: - npm install -g vercel - vercel --prod --token=$VERCEL_TOKEN only: - main Docker部署 Dockerfile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # 多阶段构建 FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # 生产镜像 FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --production COPY --from=builder /app/.output ./.output EXPOSE 3000 CMD ["node", ".output/server/index.mjs"] docker-compose.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 version: '3.8' services: app: build: . ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=${DATABASE_URL} restart: unless-stopped nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/nginx/ssl depends_on: - app 性能优化 CDN配置 1 2 3 4 5 6 7 8 9 10 11 12 13 // nuxt.config.ts export default defineNuxtConfig({ app: { cdnURL: 'https://cdn.example.com' }, nitro: { routeRules: { '/': { cache: { maxAge: 3600 } }, '/api/**': { cache: { maxAge: 60 } }, '/_nuxt/**': { cache: { maxAge: 31536000 } } } } }) 图片优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script setup> // 使用Nuxt Image组件 import { NuxtImg } from '#components' </script> <template> <NuxtImg src="/image.jpg" width="500" height="300" format="webp" loading="lazy" placeholder /> </template> 监控和日志 性能监控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 服务端性能追踪 export default defineEventHandler(async (event) => { const start = Date.now() // 处理请求 const result = await fetchData() const duration = Date.now() - start // 上报性能数据 $fetch('/api/analytics', { method: 'POST', body: { path: event.path, duration, timestamp: Date.now() } }) return result }) 错误追踪 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 集成Sentry import * as Sentry from '@sentry/node' Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, tracesSampleRate: 0.2 }) export default defineEventHandler(async (event) => { try { return await fetchData() } catch (error) { Sentry.captureException(error) throw error } }) 最佳实践 自动化部署 - 使用CI/CD自动部署 环境隔离 - 区分开发、测试、生产环境 监控告警 - 设置性能和错误监控 备份策略 - 定期备份重要数据 灾难恢复 - 准备回滚方案 总结 Serverless架构为前端部署提供了灵活、高效的解决方案。通过合理选择平台和配置CI/CD流程,可以显著提升部署效率和应用的稳定性。 ...

前端安全防护最佳实践:构建安全的Web应用

前端安全威胁概述 前端安全是Web应用的重要组成部分。主要的安全威胁包括: XSS(跨站脚本攻击) CSRF(跨站请求伪造) 点击劫持 中间人攻击 敏感信息泄露 XSS防护 内容安全策略(CSP) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // 通过HTTP头设置CSP // Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com // 在HTML中设置 <meta http-equiv="Content-Security-Policy" content=" default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.trusted.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.example.com; "> // Nuxt配置 export default defineNuxtConfig({ app: { head: { meta: [ { 'http-equiv': 'Content-Security-Policy', content: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" } ] } } }) 输入转义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // HTML转义函数 function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#039;") } // URL转义 function escapeUrl(unsafe) { return encodeURIComponent(unsafe) } // 使用DOM API而非innerHTML const div = document.createElement('div') div.textContent = userInput // 自动转义 document.body.appendChild(div) Vue自动转义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template> <!-- Vue自动转义 --> <div>{{ userInput }}</div> <!-- 使用v-html时需要特别小心 --> <div v-html="sanitizedHtml"></div> </template> <script setup> import DOMPurify from 'dompurify' const userInput = ref('<script>alert("XSS")<\/script>') // 使用DOMPurify清理HTML const sanitizedHtml = computed(() => { return DOMPurify.sanitize(userInput.value) }) </script> CSRF防护 CSRF Token 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 生成CSRF Token function generateCsrfToken() { const array = new Uint8Array(32) crypto.getRandomValues(array) return Array.from(array, b => b.toString(16).padStart(2, '0')).join('') } // 存储Token document.cookie = `csrf_token=${generateCsrfToken()}; SameSite=Strict; Secure` // 发送请求时携带Token fetch('/api/data', { method: 'POST', headers: { 'X-CSRF-Token': getCsrfToken() }, body: JSON.stringify(data) }) SameSite Cookie 1 2 3 4 5 // 设置SameSite属性 document.cookie = 'session_id=xxx; SameSite=Strict; Secure; HttpOnly' // 服务器端设置Cookie Set-Cookie: session_id=xxx; SameSite=Strict; Secure; HttpOnly 点击劫持防护 X-Frame-Options 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 通过HTTP头设置 X-Frame-Options: DENY X-Frame-Options: SAMEORIGIN // Nuxt配置 export default defineNuxtConfig({ routeRules: { '/**': { headers: { 'X-Frame-Options': 'DENY' } } } }) Frame Ancestors CSP 1 2 3 // 使用CSP frame-ancestors Content-Security-Policy: frame-ancestors 'none'; Content-Security-Policy: frame-ancestors 'self'; 敏感信息保护 不在本地存储敏感信息 1 2 3 4 5 6 7 8 9 10 11 12 // ❌ 不要存储密码 localStorage.setItem('password', password) // ❌ 不要存储敏感token localStorage.setItem('api_key', api_key) // ✅ 使用httpOnly cookie // 服务器端设置 Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict // ✅ 使用sessionStorage(关闭浏览器即清除) sessionStorage.setItem('temp_data', data) 安全的数据传输 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 始终使用HTTPS fetch('https://api.example.com/data', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) // 启用HSTS Strict-Transport-Security: max-age=31536000; includeSubDomains // 子域名资源限制 Content-Security-Policy: frame-ancestors 'self' https://trusted.example.com; 密码安全 前端密码验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function validatePassword(password) { const checks = { length: password.length >= 8, uppercase: /[A-Z]/.test(password), lowercase: /[a-z]/.test(password), numbers: /\d/.test(password), special: /[!@#$%^&*]/.test(password) } return { valid: Object.values(checks).every(Boolean), checks } } // 密码强度评估 function getPasswordStrength(password) { let score = 0 if (password.length >= 8) score++ if (password.length >= 12) score++ if (/[a-z]/.test(password)) score++ if (/[A-Z]/.test(password)) score++ if (/\d/.test(password)) score++ if (/[!@#$%^&*]/.test(password)) score++ return ['weak', 'fair', 'good', 'strong'][score] } 安全的密码输入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <template> <div class="password-input"> <input :type="showPassword ? 'text' : 'password'" v-model="password" @input="validate" /> <button @click="showPassword = !showPassword"> {{ showPassword ? 'Hide' : 'Show' }} </button> <div v-if="error" class="error">{{ error }}</div> </div> </template> <script setup> const password = ref('') const showPassword = ref(false) const error = ref('') function validate() { const result = validatePassword(password.value) if (!result.valid) { const failedChecks = Object.entries(result.checks) .filter(([, valid]) => !valid) .map(([check]) => check) error.value = `Password fails: ${failedChecks.join(', ')}` } else { error.value = '' } } </script> API安全 请求签名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 async function makeSecureRequest(url, data) { const timestamp = Date.now() const nonce = generateNonce() // 构建签名内容 const signatureContent = `${timestamp}${nonce}${JSON.stringify(data)}` const signature = await hmac(signatureContent, secretKey) return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Timestamp': timestamp, 'X-Nonce': nonce, 'X-Signature': signature }, body: JSON.stringify(data) }) } 响应数据验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 async function fetchAndValidate(url) { const response = await fetch(url) const data = await response.json() // 验证响应数据结构 const validatedData = validateResponseSchema(data, { required: ['id', 'name'], types: { id: 'number', name: 'string', email: 'string' } }) return validatedData } function validateResponseSchema(data, schema) { const errors = [] for (const field of schema.required) { if (!(field in data)) { errors.push(`Missing required field: ${field}`) } } for (const [field, type] of Object.entries(schema.types)) { if (field in data && typeof data[field] !== type) { errors.push(`Invalid type for ${field}: expected ${type}`) } } if (errors.length > 0) { throw new Error(`Validation error: ${errors.join(', ')}`) } return data } 第三方资源安全 Subresource Integrity (SRI) 1 2 3 4 5 6 <!-- 使用SRI确保CDN资源未被篡改 --> <script src="https://cdn.example.com/library.js" integrity="sha384-..." crossorigin="anonymous" ></script> 预加载安全检查 1 2 3 4 5 6 7 8 9 10 11 12 13 // 验证外部脚本 async function validateExternalScript(url) { const response = await fetch(url) const content = await response.text() // 检查是否包含可疑代码 if (content.includes('eval') || content.includes('innerHTML')) { console.warn('Potentially unsafe script detected') return false } return true } 安全审计 依赖检查 1 2 3 4 5 6 7 8 # 检查依赖漏洞 npm audit # 自动修复漏洞 npm audit fix # 使用Snyk进行深度扫描 npx snyk test 代码安全扫描 1 2 3 4 5 6 7 8 // ESLint安全规则 // .eslintrc.js module.exports = { extends: [ 'plugin:security/recommended' ], plugins: ['security'] } 安全监控 实时监控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 class SecurityMonitor { constructor() { this.init() } init() { // 监控XSS尝试 this.monitorXSS() // 监控异常请求 this.monitorRequests() // 监控控制台错误 this.monitorErrors() } monitorXSS() { const originalCreateElement = document.createElement document.createElement = function(tagName) { const element = originalCreateElement.call(this, tagName) if (tagName === 'script') { let originalSrc = '' Object.defineProperty(element, 'src', { get() { return originalSrc }, set(value) { // 检查脚本来源 if (!this.isTrustedSource(value)) { console.warn('Untrusted script source:', value) securityEvent('untrusted_script', { src: value }) } originalSrc = value } }) } return element } } monitorRequests() { const originalFetch = window.fetch window.fetch = function(...args) { const url = args[0] // 检查请求目标 if (!this.isTrustedDomain(url)) { console.warn('Request to untrusted domain:', url) securityEvent('untrusted_request', { url }) } return originalFetch.apply(this, args) } } monitorErrors() { window.addEventListener('error', (event) => { if (event.message.includes('Script error')) { securityEvent('possible_xss', { message: event.message, filename: event.filename }) } }) } isTrustedSource(url) { const trusted = ['https://cdn.trusted.com', 'https://api.trusted.com'] return trusted.some(domain => url.startsWith(domain)) } isTrustedDomain(url) { try { const urlObj = new URL(url, window.location.origin) return urlObj.origin === window.location.origin } catch { return false } } } 最佳实践总结 永远不要信任用户输入 - 始终验证和转义 使用HTTPS - 保护数据传输 设置CSP - 限制资源加载 使用HttpOnly Cookie - 防止XSS窃取 定期审计 - 检查依赖和代码安全性 总结 前端安全是一个持续的过程。通过实施这些安全措施,可以大大降低应用的安全风险,保护用户数据安全。 ...

Tailwind CSS性能优化完全指南

Tailwind CSS性能优化 Tailwind CSS通过JIT编译器和PurgeCSS提供了出色的性能优化能力。本文将详细介绍各种优化策略。 JIT模式优化 启用JIT模式 1 2 3 4 5 6 7 8 9 10 11 12 // tailwind.config.js module.exports = { mode: 'jit', content: [ './src/**/*.{html,js,jsx,ts,tsx,vue}', './public/**/*.html' ], theme: { extend: {} }, plugins: [] } JIT模式优势 按需生成 - 只生成使用的类 任意值支持 - 可以使用动态值 更快的构建 - 编译速度提升 更小的CSS - 最终CSS体积更小 PurgeCSS配置 内容路径配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // tailwind.config.js module.exports = { content: [ // 精确指定内容路径 './src/components/**/*.{vue,js}', './src/pages/**/*.{vue,js}', './src/layouts/**/*.{vue,js}', // 包括动态导入的文件 './src/**/*.vue', // 第三方库 './node_modules/@mycompany/ui-library/**/*.{js,jsx}' ], safelist: [ // 保留动态类名 { pattern: /bg-(red|blue|green)-500/, variants: ['hover', 'focus'] } ] } 安全列表策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // tailwind.config.js module.exports = { safelist: [ // 精确匹配 'bg-blue-500', 'text-center', // 正则模式 /^bg-/, /^text-/, /p-[0-9]+/, // 变体 { pattern: /bg-(red|blue|green)-500/, variants: ['hover', 'focus', 'active'] } ] } 动态类名处理 避免字符串拼接 1 2 3 4 5 6 7 8 9 <!-- ❌ 错误做法 --> <div :class="`text-${color}-500`"></div> <!-- ✅ 正确做法 --> <div :class="{ 'text-red-500': color === 'red', 'text-blue-500': color === 'blue', 'text-green-500': color === 'green' }"></div> 使用完整类名 1 2 3 4 5 6 7 8 9 10 11 <script setup> const sizes = { sm: 'text-sm px-2 py-1', md: 'text-md px-4 py-2', lg: 'text-lg px-6 py-3' } </script> <template> <button :class="sizes[size]">Click me</button> </template> CSS体积优化 禁用未使用的核心插件 1 2 3 4 5 6 7 8 9 10 // tailwind.config.js module.exports = { corePlugins: { // 禁用不需要的插件 float: false, objectFit: false, objectPosition: false, userSelect: false } } 自定义断点 1 2 3 4 5 6 7 8 9 10 11 12 // tailwind.config.js module.exports = { theme: { screens: { // 只使用需要的断点 'sm': '640px', 'md': '768px', 'lg': '1024px', 'xl': '1280px' } } } 运行时优化 懒加载组件 1 2 3 4 5 6 7 8 9 10 11 12 13 <script setup> import { defineAsyncComponent } from 'vue' const HeavyComponent = defineAsyncComponent(() => import('./HeavyComponent.vue') ) </script> <template> <Suspense> <component :is="HeavyComponent" /> </Suspense> </template> CSS提取 1 2 3 4 5 6 7 // vite.config.js export default { build: { cssCodeSplit: true, cssMinify: 'lightningcss' } } 性能分析 CSS体积分析 1 2 3 4 5 6 7 8 # 分析CSS体积 npx tailwindcss --help # 查看生成的CSS npx tailwindcss -i ./src/styles.css -o ./dist/output.css # 分析结果 npx tailwindcss -o build.css --minify 使用PurgeCSS分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // tailwind.config.js module.exports = { content: ['./src/**/*.{html,js}'], purge: { enabled: true, layers: ['components', 'utilities'], options: { // 生成分析报告 keyframes: true, fontFace: true, variables: true } } } 实用技巧 1. 使用@apply 1 2 3 4 5 6 7 8 9 10 /* 对于复杂的组件样式 */ @layer components { .card { @apply bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow; } .btn-primary { @apply bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded; } } 2. 主题定制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // tailwind.config.js module.exports = { theme: { extend: { colors: { // 自定义颜色 primary: { 50: '#f0f9ff', 100: '#e0f2fe', 500: '#0ea5e9', 900: '#0c4a6e' } }, spacing: { // 自定义间距 '128': '32rem', '144': '36rem' } } } } 3. 响应式设计 1 2 3 4 5 6 7 8 9 10 <template> <!-- 响应式类名 --> <div class=" text-sm md:text-base lg:text-lg p-2 md:p-4 lg:p-6 bg-white md:bg-gray-100 lg:bg-gray-200 "> Responsive content </div> </template> 最佳实践 按需引入 - 只使用需要的类 避免内联样式 - 使用Tailwind类 组件化 - 为复杂组件创建@apply规则 定期清理 - 使用PurgeCSS清理未使用的类 性能监控 - 监控CSS体积和加载时间 总结 Tailwind CSS提供了多种优化策略。通过合理配置JIT模式、PurgeCSS和动态类名处理,可以构建高性能的CSS应用。 ...

前端监控与错误追踪实战指南

前端监控系统概览 完整的前端监控系统应该包含: 性能监控 - 页面加载、资源、交互性能 错误追踪 - JavaScript错误、资源错误、网络错误 用户体验 - Core Web Vitals、用户行为 业务指标 - 转化率、留存率等 性能监控 Performance API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // 页面加载性能 function measurePagePerformance() { if (!window.performance) return const timing = window.performance.timing const navigation = performance.getEntriesByType('navigation')[0] const metrics = { // DNS查询时间 dns: timing.domainLookupEnd - timing.domainLookupStart, // TCP连接时间 tcp: timing.connectEnd - timing.connectStart, // 请求响应时间 request: timing.responseEnd - timing.requestStart, // DOM解析时间 domParse: timing.domComplete - timing.domLoading, // 首次内容绘制 fcp: navigation?.loadEventEnd, // 首次输入延迟 fid: 0, // 最大内容绘制 lcp: 0, // 首次字节时间 ttfb: timing.responseStart - timing.navigationStart, // 页面完全加载时间 loadTime: timing.loadEventEnd - timing.navigationStart } return metrics } Core Web Vitals 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // LCP (Largest Contentful Paint) function measureLCP() { new PerformanceObserver((list) => { const entries = list.getEntries() const lastEntry = entries[entries.length - 1] console.log('LCP:', lastEntry.startTime) }).observe({ entryTypes: ['largest-contentful-paint'] }) } // FID (First Input Delay) function measureFID() { new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log('FID:', entry.processingStart - entry.startTime) } }).observe({ entryTypes: ['first-input'] }) } // CLS (Cumulative Layout Shift) function measureCLS() { let clsValue = 0 new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (!entry.hadRecentInput) { clsValue += entry.value } } console.log('CLS:', clsValue) }).observe({ entryTypes: ['layout-shift'] }) } 资源加载监控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function monitorResources() { const resources = performance.getEntriesByType('resource') resources.forEach(resource => { const data = { name: resource.name, type: resource.initiatorType, duration: resource.duration, size: resource.transferSize, // 资源加载时间分解 dns: resource.domainLookupEnd - resource.domainLookupStart, tcp: resource.connectEnd - resource.connectStart, ssl: resource.secureConnectionStart > 0 ? resource.connectEnd - resource.secureConnectionStart : 0, ttfb: resource.responseStart - resource.requestStart, download: resource.responseEnd - resource.responseStart } // 检测慢资源 if (data.duration > 1000) { console.warn('Slow resource:', data) } }) } 错误追踪 全局错误捕获 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 // JavaScript错误 window.addEventListener('error', (event) => { const error = { message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, error: event.error?.stack, type: 'javascript', timestamp: Date.now(), url: window.location.href, userAgent: navigator.userAgent } sendErrorToServer(error) }) // Promise错误 window.addEventListener('unhandledrejection', (event) => { const error = { reason: event.reason, promise: event.promise, type: 'promise', timestamp: Date.now(), url: window.location.href } sendErrorToServer(error) }) // 资源加载错误 window.addEventListener('error', (event) => { if (event.target !== window) { const error = { tagName: event.target.tagName, src: event.target.src || event.target.href, type: 'resource', timestamp: Date.now() } sendErrorToServer(error) } }, true) Vue错误处理 1 2 3 4 5 6 7 8 9 10 11 12 13 // Vue应用全局错误处理 app.config.errorHandler = (err, instance, info) => { const error = { message: err.message, stack: err.stack, component: instance?.$options.name, info: info, type: 'vue', timestamp: Date.now() } sendErrorToServer(error) } 错误聚合上报 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 class ErrorTracker { constructor(config) { this.config = { batchSize: 10, flushInterval: 5000, ...config } this.errorQueue = [] this.startFlushTimer() } addError(error) { this.errorQueue.push({ ...error, timestamp: Date.now(), url: window.location.href, userAgent: navigator.userAgent, userId: this.getUserId() }) if (this.errorQueue.length >= this.config.batchSize) { this.flush() } } async flush() { if (this.errorQueue.length === 0) return const errors = [...this.errorQueue] this.errorQueue = [] try { await fetch('/api/errors', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ errors }) }) } catch (e) { console.error('Failed to send errors:', e) // 重新入队 this.errorQueue.unshift(...errors) } } startFlushTimer() { setInterval(() => { this.flush() }, this.config.flushInterval) } getUserId() { // 获取或生成用户ID let userId = localStorage.getItem('tracker_user_id') if (!userId) { userId = 'user_' + Date.now() + '_' + Math.random() localStorage.setItem('tracker_user_id', userId) } return userId } } 用户行为追踪 页面访问追踪 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 class PageTracker { constructor() { this.currentPage = null this.pageStartTime = null this.init() } init() { // 页面加载完成 window.addEventListener('load', () => { this.trackPageView() }) // SPA路由变化 this.trackRouteChanges() } trackPageView() { this.pageStartTime = Date.now() this.currentPage = { url: window.location.pathname, referrer: document.referrer, title: document.title, timestamp: this.pageStartTime } this.sendEvent('pageview', this.currentPage) } trackRouteChanges() { // 监听路由变化(需要根据具体框架实现) let lastUrl = location.href new MutationObserver(() => { const url = location.href if (url !== lastUrl) { lastUrl = url this.trackPageView() } }).observe(document, { subtree: true, childList: true }) } trackEvent(category, action, label, value) { this.sendEvent('event', { category, action, label, value, timestamp: Date.now() }) } sendEvent(type, data) { // 发送到分析服务器 fetch('/api/analytics', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type, data, userId: this.getUserId(), sessionId: this.getSessionId() }) }) } } 交互追踪 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class InteractionTracker { constructor() { this.init() } init() { // 点击追踪 document.addEventListener('click', (e) => { const target = e.target const data = { tagName: target.tagName, id: target.id, className: target.className, text: target.textContent?.slice(0, 50), x: e.clientX, y: e.clientY } this.track('click', data) }, true) // 滚动追踪 let scrollTimeout document.addEventListener('scroll', () => { clearTimeout(scrollTimeout) scrollTimeout = setTimeout(() => { this.track('scroll', { scrollDepth: this.getScrollDepth() }) }, 100) }) } getScrollDepth() { const scrollTop = window.pageYOffset const docHeight = document.documentElement.scrollHeight const winHeight = window.innerHeight return Math.round((scrollTop / (docHeight - winHeight)) * 100) } track(type, data) { // 发送交互数据 } } 完整监控方案 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 class MonitorSDK { constructor(config) { this.config = config this.errorTracker = new ErrorTracker(config.error) this.pageTracker = new PageTracker() this.interactionTracker = new InteractionTracker() } // 性能指标 startPerformanceMonitoring() { if ('PerformanceObserver' in window) { // 监控LCP this.observeLCP() // 监控FID this.observeFID() // 监控CLS this.observeCLS() } // 页面卸载时发送最终数据 window.addEventListener('beforeunload', () => { this.flush() }) } observeLCP() { new PerformanceObserver((list) => { const entries = list.getEntries() const lastEntry = entries[entries.length - 1] this.reportMetric('LCP', lastEntry.startTime) }).observe({ entryTypes: ['largest-contentful-paint'] }) } observeFID() { new PerformanceObserver((list) => { for (const entry of list.getEntries()) { this.reportMetric('FID', entry.processingStart - entry.startTime) } }).observe({ entryTypes: ['first-input'] }) } observeCLS() { let clsValue = 0 new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (!entry.hadRecentInput) { clsValue += entry.value } } this.reportMetric('CLS', clsValue) }).observe({ entryTypes: ['layout-shift'] }) } reportMetric(name, value) { // 上报性能指标 fetch('/api/metrics', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ metric: name, value, timestamp: Date.now(), url: window.location.href }) }) } flush() { this.errorTracker.flush() } } // 初始化 const monitor = new MonitorSDK({ error: { batchSize: 10, flushInterval: 5000 } }) monitor.startPerformanceMonitoring() 最佳实践 采样策略 - 高流量时采用采样 数据压缩 - 减少传输数据量 本地缓存 - 离线时缓存数据 隐私保护 - 遵守隐私法规 性能影响 - 最小化监控对性能的影响 总结 前端监控系统是保障应用质量的重要工具。通过全面的性能监控、错误追踪和用户行为分析,可以及时发现并解决问题,提升用户体验。 ...

WebCrypto API实战:前端加密与安全通信完全指南

WebCrypto API简介 WebCrypto API是现代浏览器提供的原生加密API,提供了安全的加密操作能力。与第三方库相比,它有以下优势: 浏览器原生支持,无需额外依赖 使用系统级加密库,性能更优 安全的密钥管理,密钥不暴露给JavaScript 同步和异步API支持 对称加密(AES) 生成密钥 1 2 3 4 5 6 7 8 9 10 11 12 // 生成AES密钥 async function generateAesKey() { const key = await crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ) return key } 加密数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 async function encryptData(data, key) { const encoder = new TextEncoder() const iv = crypto.getRandomValues(new Uint8Array(12)) const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, encoder.encode(data) ) return { ciphertext: new Uint8Array(encrypted), iv: iv } } 解密数据 1 2 3 4 5 6 7 8 9 10 11 12 13 async function decryptData(encryptedData, key, iv) { const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv }, key, encryptedData ) const decoder = new TextDecoder() return decoder.decode(decrypted) } 非对称加密(RSA) 生成密钥对 1 2 3 4 5 6 7 8 9 10 11 12 13 14 async function generateRsaKeyPair() { const keyPair = await crypto.subtle.generateKey( { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' }, true, ['encrypt', 'decrypt'] ) return keyPair } 导出和导入密钥 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 导出公钥 async function exportPublicKey(key) { const exported = await crypto.subtle.exportKey( 'spki', key ) return new Uint8Array(exported) } // 导入公钥 async function importPublicKey(keyData) { return await crypto.subtle.importKey( 'spki', keyData, { name: 'RSA-OAEP', hash: 'SHA-256' }, true, ['encrypt'] ) } 哈希算法 计算哈希 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 async function hashData(data, algorithm = 'SHA-256') { const encoder = new TextEncoder() const dataBuffer = encoder.encode(data) const hashBuffer = await crypto.subtle.digest( algorithm, dataBuffer ) const hashArray = Array.from(new Uint8Array(hashBuffer)) return hashArray.map(b => b.toString(16).padStart(2, '0')).join('') } // 使用 const hash = await hashData('Hello, WebCrypto!', 'SHA-256') console.log(hash) // 64位十六进制字符串 HMAC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 async function hmac(key, message) { const encoder = new TextEncoder() const cryptoKey = await crypto.subtle.importKey( 'raw', encoder.encode(key), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ) const signature = await crypto.subtle.sign( 'HMAC', cryptoKey, encoder.encode(message) ) const hashArray = Array.from(new Uint8Array(signature)) return hashArray.map(b => b.toString(16).padStart(2, '0')).join('') } 数字签名 生成签名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 async function signData(data, privateKey) { const encoder = new TextEncoder() const signature = await crypto.subtle.sign( { name: 'RSA-PSS', saltLength: 32 }, privateKey, encoder.encode(data) ) return new Uint8Array(signature) } 验证签名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 async function verifySignature(data, signature, publicKey) { const encoder = new TextEncoder() const result = await crypto.subtle.verify( { name: 'RSA-PSS', saltLength: 32 }, publicKey, signature, encoder.encode(data) ) return result } 密钥派生(PBKDF2) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 async function deriveKey(password, salt) { const encoder = new TextEncoder() const keyMaterial = await crypto.subtle.importKey( 'raw', encoder.encode(password), 'PBKDF2', false, ['deriveKey'] ) const key = await crypto.subtle.deriveKey( { name: 'PBKDF2', salt: encoder.encode(salt), iterations: 100000, hash: 'SHA-256' }, keyMaterial, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ) return key } 实战案例 端到端加密聊天 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 class E2EChat { constructor() { this.keyPair = null this.peerKeys = new Map() } async init() { // 生成密钥对 this.keyPair = await crypto.subtle.generateKey( { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' }, true, ['encrypt', 'decrypt'] ) } async getPublicKey() { const exported = await crypto.subtle.exportKey('spki', this.keyPair.publicKey) return btoa(String.fromCharCode(...new Uint8Array(exported))) } async addPeer(id, publicKeyData) { const binaryString = atob(publicKeyData) const bytes = new Uint8Array(binaryString.length) for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i) } const publicKey = await crypto.subtle.importKey( 'spki', bytes, { name: 'RSA-OAEP', hash: 'SHA-256' }, true, ['encrypt'] ) this.peerKeys.set(id, publicKey) } async sendMessage(peerId, message) { const publicKey = this.peerKeys.get(peerId) if (!publicKey) throw new Error('Peer not found') // 生成会话密钥 const sessionKey = await this.generateSessionKey() // 加密消息 const encryptedMessage = await this.encryptMessage(message, sessionKey) // 加密会话密钥 const encryptedKey = await this.encryptSessionKey(sessionKey, publicKey) return { message: encryptedMessage, key: encryptedKey } } async receiveMessage(data) { // 解密会话密钥 const sessionKey = await this.decryptSessionKey(data.key) // 解密消息 return await this.decryptMessage(data.message, sessionKey) } async generateSessionKey() { return await crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ) } async encryptMessage(message, key) { const encoder = new TextEncoder() const iv = crypto.getRandomValues(new Uint8Array(12)) const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, encoder.encode(message) ) return { data: btoa(String.fromCharCode(...new Uint8Array(encrypted))), iv: btoa(String.fromCharCode(...iv)) } } async decryptMessage(data, key) { const ciphertext = Uint8Array.from(atob(data.data), c => c.charCodeAt(0)) const iv = Uint8Array.from(atob(data.iv), c => c.charCodeAt(0)) const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv }, key, ciphertext ) const decoder = new TextDecoder() return decoder.decode(decrypted) } } 安全最佳实践 密钥管理 ...

TypeScript类型体操进阶:掌握高级类型系统

TypeScript类型系统进阶 TypeScript的类型系统非常强大,掌握高级类型特性可以帮助我们写出更安全、更优雅的代码。 条件类型 基础条件类型 1 2 3 4 5 6 7 8 9 10 // 类似三元表达式 type IsString<T> = T extends string ? true : false type Test1 = IsString<string> // true type Test2 = IsString<number> // false // 嵌套条件类型 type NonNullable<T> = T extends null | undefined ? never : T type Test3 = NonNullable<string | null> // string 分布式条件类型 1 2 3 4 5 6 7 8 9 // 联合类型会分布式应用条件类型 type ToArray<T> = T extends any ? T[] : never type Test4 = ToArray<string | number> // string[] | number[] // 阻止分布 type ToArrayNonDistributive<T> = [T] extends [any] ? T[] : never type Test5 = ToArrayNonDistributive<string | number> // (string | number)[] 类型推断 1 2 3 4 5 6 7 8 9 10 // Infer关键字 type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never type Fn = (x: number) => string type Test6 = ReturnType<Fn> // string // 解包Promise type UnwrapPromise<T> = T extends Promise<infer U> ? U : T type Test7 = UnwrapPromise<Promise<string>> // string 映射类型 基础映射类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 将所有属性变为可选 type Partial<T> = { [P in keyof T]?: T[P] } // 将所有属性变为必需 type Required<T> = { [P in keyof T]-?: T[P] } // 只读 type Readonly<T> = { readonly [P in keyof T]: T[P] } 高级映射类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 只保留特定类型 type OnlyStrings<T> = { [P in keyof T as T[P] extends string ? P : never]: T[P] } interface User { name: string age: number email: string } type Test8 = OnlyStrings<User> // { name: string; email: string } // 条件映射 type Getters<T> = { [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P] } type Test9 = Getters<{ name: string; age: number }> // { getName: () => string; getAge: () => number } 模板字面量类型 基础用法 1 2 3 4 type Greeting = `hello ${string}` const test: Greeting = 'hello world' // ✓ const test2: Greeting = 'hi world' // ✗ 高级应用 1 2 3 4 5 6 7 8 9 10 // 事件名称类型 type EventName<T extends string> = `on${Capitalize<T>}` type Test10 = EventName<'click'> // 'onClick' // 路径类型 type Path<T extends string> = `/${T}` | `${Path<T>}/${T}` // 组合类型 type CssValue<T extends number> = `${T}px` | `${T}%` | `${t}em` 递归类型 深度只读 1 2 3 4 5 6 7 8 9 10 11 12 13 type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P] } interface Config { database: { host: string port: number } } type Test11 = DeepReadonly<Config> // 所有嵌套属性都变为只读 深度必需 1 2 3 type DeepRequired<T> = { [P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P] } 品牌类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 创建唯一类型 type Brand<T, B> = T & { __brand: B } type USD = Brand<number, 'USD'> type EUR = Brand<number, 'EUR'> const usd: USD = 100 as USD const eur: EUR = 100 as EUR // 不能直接赋值 // eur = usd // ✗ 类型错误 function addUSD(a: USD, b: USD): USD { return (a + b) as USD } 函数类型高级应用 函数重载 1 2 3 4 5 6 7 8 function process(input: string): string function process(input: number): number function process(input: string | number): string | number { return input } const result1 = process('hello') // string const result2 = process(123) // number 参数类型推断 1 2 3 4 5 6 7 8 9 10 // 元组转函数参数 type TupleToFn<T extends any[]> = T extends [...infer Args, infer R] ? (...args: Args) => R : never type Test12 = TupleToFn<[string, number, boolean]> // 反向推断 type FnToTuple<T extends (...args: any[]) => any> = T extends (...args: infer A) => any ? A : never 类型守卫 typeof守卫 1 2 3 4 5 6 7 8 9 10 function isString(value: unknown): value is string { return typeof value === 'string' } function process(value: unknown) { if (isString(value)) { // 这里value的类型是string console.log(value.toUpperCase()) } } in守卫 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface Cat { meow(): void } interface Dog { bark(): void } function makeSound(animal: Cat | Dog) { if ('meow' in animal) { animal.meow() } else { animal.bark() } } instanceof守卫 1 2 3 4 5 6 7 8 9 10 11 12 13 class Error1 extends Error { code = 'ERROR_1' } class Error2 extends Error { code = 'ERROR_2' } function handleError(error: Error) { if (error instanceof Error1) { console.log(error.code) } } 高级工具类型 OmitByType 1 2 3 4 5 6 7 8 9 10 11 12 type OmitByType<T, U> = { [P in keyof T as T[P] extends U ? never : P]: T[P] } interface Test { name: string age: number active: boolean } type Test13 = OmitByType<Test, boolean> // { name: string; age: number } DeepPick 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type DeepPick<T, K extends string> = K extends `${infer Key}.${infer Rest}` ? Key extends keyof T ? { [P in Key]: DeepPick<T[P], Rest> } : never : K extends keyof T ? { [P in K]: T[K] } : never interface User { profile: { name: string email: string } settings: { theme: string } } type Test14 = DeepPick<User, 'profile.name'> // { profile: { name: string } } 实战案例 表单类型定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type FieldProps<T> = { value: T onChange: (value: T) => void error?: string } type FormFields<T> = { [K in keyof T]: FieldProps<T[K]> } interface LoginData { email: string password: string remember: boolean } type LoginForm = FormFields<LoginData> API响应类型 1 2 3 4 5 6 type ApiResponse<T, E = Error> = | { success: true; data: T } | { success: false; error: E } type UserResponse = ApiResponse<User> type ErrorResponse = ApiResponse<never, NetworkError> 最佳实践 优先使用类型推导 - 让TypeScript自动推导类型 避免使用any - 使用unknown代替 合理使用泛型 - 提高代码复用性 类型守卫 - 缩小类型范围 品牌类型 - 创建语义化类型 总结 TypeScript类型系统非常强大,掌握高级类型特性可以帮助我们构建更安全的代码。 ...

Nuxt 4服务端渲染性能优化实战指南

Nuxt 4 SSR优化概览 Nuxt 4基于Vue 3和Nitro构建,提供了强大的SSR能力。本文将深入分析各种性能优化技术。 服务端缓存策略 routeRules缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // nuxt.config.ts export default defineNuxtConfig({ routeRules: { // 静态页面缓存1小时 '/': { isr: 3600 }, // API缓存 '/api/**': { cors: true, headers: { 'cache-control': 'max-age=60' } }, // 动态路由ISR '/blog/**': { isr: true }, // 完全静态 '/about': { isr: false }, // SPA模式 '/app/**': { ssr: false } } }) 服务端数据缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // server/api/data.ts export default defineEventHandler(async (event) => { // 使用Nitro存储缓存 const storage = useStorage('redis') const cached = await storage.getItem('expensive-data') if (cached) { return cached } const data = await fetchExpensiveData() await storage.setItem('expensive-data', data, { ttl: 3600 }) return data }) useAsyncData缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 <script setup lang="ts"> // 缓存API响应 const { data, refresh } = await useAsyncData( 'user-profile', () => $fetch('/api/user/profile'), { transform: (data) => data.result, // 5分钟缓存 getCachedData: (key) => useNuxtData(key).data, dedupe: 'cancel' } ) </script> 流式渲染 使用Suspense流式渲染 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <!-- app.vue --> <template> <div> <NuxtPage /> </div> </template> <!-- pages/index.vue --> <template> <div> <!-- 立即渲染部分 --> <header>Header</header> <!-- 异步组件流式加载 --> <Suspense> <template #default> <AsyncComponent /> </template> <template #fallback> <LoadingSkeleton /> </template> </Suspense> <footer>Footer</footer> </div> </template> Nitro流式响应 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // server/api/stream.ts export default defineEventHandler(async (event) => { const stream = new ReadableStream({ async start(controller) { for (let i = 0; i < 10; i++) { const data = `chunk ${i}\n` controller.enqueue(new TextEncoder().encode(data)) await new Promise(resolve => setTimeout(resolve, 100)) } controller.close() } }) return sendStream(event, stream) }) Hydration优化 懒加载Hydration 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template> <div> <!-- 关键内容立即hydration --> <CriticalContent /> <!-- 非关键内容延迟hydration --> <LazyNonCritical @vue:mounted="hydrateComponent" /> </div> </template> <script setup> function hydrateComponent() { // 组件mounted后才hydration } </script> 选择性Hydration 1 2 3 4 5 6 7 8 9 10 <script setup> // 只hydration需要的部分 const { data } = await useAsyncData('posts', () => $fetch('/api/posts'), { // 只在客户端获取 server: false } ) </script> 性能监控 服务端性能追踪 1 2 3 4 5 6 7 8 9 // server/middleware/performance.ts export default defineEventHandler((event) => { const start = Date.now() event.node.res.on('finish', () => { const duration = Date.now() - start console.log(`[Performance] ${event.node.req.url} - ${duration}ms`) }) }) 使用Nitro钩子 1 2 3 4 5 6 7 // server/plugins/performance.ts export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('renderResponse', (response, { event }) => { const timing = Date.now() - event.context.startTime event.node.res.setHeader('X-Response-Time', `${timing}ms`) }) }) 数据获取优化 并行数据获取 1 2 3 4 5 6 7 8 <script setup lang="ts"> // 并行获取多个数据 const [user, posts, comments] = await Promise.all([ $fetch('/api/user'), $fetch('/api/posts'), $fetch('/api/comments') ]) </script> useLazyAsyncData延迟加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script setup lang="ts"> // 不阻塞导航 const { data, pending } = useLazyAsyncData( 'dashboard', () => $fetch('/api/dashboard') ) </script> <template> <div> <h1>Dashboard</h1> <div v-if="pending">Loading...</div> <div v-else>{{ data }}</div> </div> </template> 预渲染策略 静态生成 1 2 3 4 5 # 生成静态站点 nuxt generate # 仅预渲染特定路由 nuxt generate --prerender /about /contact 增量静态再生 1 2 3 4 5 6 7 8 9 10 11 // nuxt.config.ts export default defineNuxtConfig({ routeRules: { '/blog/**': { // ISR:每小时重新生成 isr: 3600, // 允许预渲染 prerender: true } } }) 代码分割优化 动态导入 1 2 3 4 5 6 7 8 9 10 11 12 <script setup> // 组件懒加载 const HeavyComponent = defineAsyncComponent(() => import('~/components/HeavyComponent.vue') ) </script> <template> <Suspense> <component :is="HeavyComponent" /> </Suspense> </template> 路由级代码分割 1 2 3 4 5 6 7 // nuxt.config.ts export default defineNuxtConfig({ experimental: { // 启用路由级代码分割 typedPages: true } }) 生产环境优化 构建优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // nuxt.config.ts export default defineNuxtConfig({ vite: { build: { // Rollup配置 rollupOptions: { output: { // 手动代码分割 manualChunks: { 'vue-vendor': ['vue', 'vue-router', '@vue/runtime-core'], 'ui-vendor': ['@headlessui/vue', 'heroicons'] } } } } }, // 压缩 nitro: { minify: true } }) CDN配置 1 2 3 4 5 6 7 8 9 10 11 // nuxt.config.ts export default defineNuxtConfig({ app: { // 静态资源CDN cdnURL: 'https://cdn.example.com' }, // 压缩中间件 nitro: { compressPublicAssets: true } }) 总结 Nuxt 4提供了完整的SSR优化工具链。通过合理使用缓存、流式渲染、Hydration优化等技术,可以显著提升应用性能。 ...

Vue 3.5新特性深度解析:响应式系统的重大升级

引言 Vue 3.5作为Vue 3系列的最新版本,带来了许多令人兴奋的新特性和性能优化。本文将深入分析这些新特性的技术细节,并通过实际代码示例展示如何在项目中应用它们。 核心新特性概览 响应式系统优化 - 内存占用降低,性能提升 Props解构 - 更简洁的API defineModel改进 - 简化双向绑定 Suspense增强 - 更好的异步组件处理 SSR改进 - 服务端渲染性能提升 TypeScript支持 - 更好的类型推导 响应式系统优化 内存优化 Vue 3.5对响应式系统进行了底层重构,显著降低了内存占用: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <script setup> import { ref, reactive, computed } from 'vue' // Vue 3.5: 内存占用优化 const items = ref(new Array(10000).fill(0).map((_, i) => ({ id: i, name: `Item ${i}`, value: Math.random() })) // 计算属性缓存优化 const total = computed(() => { return items.value.reduce((sum, item) => sum + item.value, 0) }) // 响应式Map/Set性能提升 const mapData = reactive(new Map()) const setData = reactive(new Set()) </script> Proxy优化 1 2 3 4 5 6 7 8 9 10 11 12 13 // Vue 3.5的Proxy实现更加高效 const state = reactive({ nested: { deeply: { value: 1 } } }) // 触发追踪优化 watchEffect(() => { console.log(state.nested.deeply.value) // 更少的依赖追踪开销 }) Props解构 基础用法 Vue 3.5中最具实用性的特性之一: ...

WebAssembly在前端工程中的实践:突破JavaScript性能瓶颈

引言 随着Web应用的复杂度不断提升,JavaScript在处理计算密集型任务时逐渐暴露出性能瓶颈。WebAssembly(Wasm)作为一种新型的二进制指令格式,为Web平台带来了接近原生的执行性能。本文将结合实际项目经验,深入探讨WebAssembly在前端工程中的应用实践。 WebAssembly的核心优势 1. 近原生执行性能 WebAssembly采用二进制格式,体积小、解析快,执行速度接近原生代码。以下是性能对比数据(基于斐波那契数列计算): 1 2 3 4 5 6 // JavaScript实现 function fibonacci(n) { return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); } // WebAssembly (Rust编译) 可提升10-50倍性能 2. 多语言支持 开发者可以使用Rust、C++、Go等多种语言编写Wasm模块: 1 2 3 4 5 6 7 8 9 // Rust实现斐波那契 #[wasm_bindgen] pub fn fibonacci(n: u32) -> u32 { match n { 0 => 0, 1 => 1, _ => fibonacci(n - 1) + fibonacci(n - 2), } } 3. 安全隔离 Wasm运行在沙箱环境中,内存隔离确保了安全性。 ...