11.1 Vue SSR生态系统概览
11.1.1 核心工具链
// Vue SSR生态系统工具链
const vueSSREcosystem = {
// 核心框架
core: {
vue: '^2.6.14',
'vue-server-renderer': '^2.6.14',
'vue-router': '^3.5.4',
vuex: '^3.6.2'
},
// 构建工具
build: {
webpack: '^4.46.0',
'vue-loader': '^15.9.8',
'vue-template-compiler': '^2.6.14',
'webpack-merge': '^5.8.0',
'webpack-node-externals': '^3.0.0'
},
// 开发工具
development: {
'webpack-dev-server': '^4.7.4',
'webpack-hot-middleware': '^2.25.1',
'friendly-errors-webpack-plugin': '^1.7.0',
'vue-devtools': 'latest'
},
// UI框架
ui: {
'element-ui': '^2.15.6',
vuetify: '^2.6.1',
'ant-design-vue': '^1.7.8',
'bootstrap-vue': '^2.21.2'
},
// 工具库
utilities: {
axios: '^0.26.1',
lodash: '^4.17.21',
moment: '^2.29.1',
'vue-meta': '^2.4.0',
'vue-i18n': '^8.27.0'
},
// 测试工具
testing: {
jest: '^27.5.1',
'@vue/test-utils': '^1.3.0',
'vue-jest': '^3.0.7',
supertest: '^6.2.2'
},
// 部署工具
deployment: {
pm2: '^5.1.2',
docker: 'latest',
nginx: 'latest',
'serve-static': '^1.14.2'
}
}
11.1.2 框架对比分析
// SSR框架对比
const ssrFrameworks = {
nuxt: {
description: 'Vue.js的通用应用框架',
pros: [
'零配置开始',
'自动路由生成',
'内置模块系统',
'强大的插件生态',
'优秀的开发体验'
],
cons: [
'学习曲线',
'框架约束较多',
'定制化程度有限'
],
useCase: '快速开发、标准化项目'
},
vuePress: {
description: 'Vue驱动的静态网站生成器',
pros: [
'专为文档优化',
'优秀的SEO',
'插件丰富',
'主题系统'
],
cons: [
'主要用于静态内容',
'动态功能有限'
],
useCase: '文档网站、博客'
},
quasar: {
description: '基于Vue的跨平台框架',
pros: [
'一套代码多平台',
'丰富的组件库',
'性能优秀',
'CLI工具强大'
],
cons: [
'学习成本高',
'生态相对较小'
],
useCase: '跨平台应用开发'
},
customSSR: {
description: '自定义Vue SSR解决方案',
pros: [
'完全控制',
'高度定制',
'性能优化空间大',
'技术栈灵活'
],
cons: [
'开发成本高',
'维护复杂',
'需要深度技术知识'
],
useCase: '复杂业务需求、性能要求极高'
}
}
11.2 Nuxt.js深入解析
11.2.1 Nuxt.js项目结构
// nuxt.config.js
export default {
// 全局页面头部
head: {
title: 'Vue SSR电商平台',
htmlAttrs: {
lang: 'zh-CN'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: 'Vue SSR电商平台演示' },
{ name: 'format-detection', content: 'telephone=no' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
// 全局CSS
css: [
'element-ui/lib/theme-chalk/index.css',
'@/assets/css/main.scss'
],
// 插件
plugins: [
'@/plugins/element-ui.js',
'@/plugins/axios.js',
{ src: '@/plugins/localStorage.js', mode: 'client' }
],
// 自动导入组件
components: true,
// 开发工具模块
buildModules: [
'@nuxtjs/eslint-module',
'@nuxtjs/stylelint-module'
],
// 模块
modules: [
'@nuxtjs/axios',
'@nuxtjs/pwa',
'@nuxtjs/auth-next',
'@nuxtjs/i18n'
],
// Axios模块配置
axios: {
baseURL: process.env.API_BASE_URL || 'http://localhost:3001/api'
},
// PWA模块配置
pwa: {
manifest: {
lang: 'zh-CN',
name: 'Vue SSR电商平台',
short_name: 'SSR商城',
description: 'Vue SSR电商平台演示应用'
},
workbox: {
runtimeCaching: [
{
urlPattern: '/api/.*',
handler: 'NetworkFirst',
method: 'GET'
},
{
urlPattern: '/images/.*',
handler: 'CacheFirst'
}
]
}
},
// 认证模块配置
auth: {
strategies: {
local: {
token: {
property: 'token',
global: true,
required: true,
type: 'Bearer'
},
user: {
property: 'user',
autoFetch: true
},
endpoints: {
login: { url: '/auth/login', method: 'post' },
logout: { url: '/auth/logout', method: 'post' },
user: { url: '/auth/user', method: 'get' }
}
}
},
redirect: {
login: '/login',
logout: '/',
callback: '/login',
home: '/'
}
},
// 国际化配置
i18n: {
locales: [
{ code: 'en', name: 'English', file: 'en.js' },
{ code: 'zh', name: '中文', file: 'zh.js' }
],
defaultLocale: 'zh',
langDir: 'locales/',
strategy: 'prefix_except_default'
},
// 构建配置
build: {
transpile: [/^element-ui/],
// 优化配置
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
enforce: true
}
}
}
},
// Webpack扩展
extend(config, { isDev, isClient }) {
if (isDev && isClient) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/
})
}
}
},
// 服务器配置
server: {
port: process.env.PORT || 3000,
host: process.env.HOST || 'localhost'
},
// 环境变量
env: {
API_BASE_URL: process.env.API_BASE_URL || 'http://localhost:3001/api'
},
// 渲染配置
render: {
// 启用HTTP/2推送
http2: {
push: true
},
// 静态资源配置
static: {
maxAge: 1000 * 60 * 60 * 24 * 7 // 7天
},
// 压缩配置
compressor: {
threshold: 0
}
}
}
11.2.2 Nuxt.js插件开发
// plugins/api.js
export default function ({ $axios, redirect, store }) {
// 请求拦截器
$axios.onRequest(config => {
console.log('Making request to ' + config.url)
// 添加认证头
const token = store.getters['auth/token']
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器
$axios.onResponse(response => {
return response.data
})
// 错误处理
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 401) {
// 未授权,重定向到登录页
store.dispatch('auth/logout')
redirect('/login')
} else if (code === 403) {
// 禁止访问
redirect('/403')
} else if (code === 404) {
// 页面不存在
redirect('/404')
} else if (code >= 500) {
// 服务器错误
redirect('/500')
}
return Promise.reject(error)
})
}
// plugins/global-components.js
import Vue from 'vue'
import LoadingSpinner from '@/components/common/LoadingSpinner.vue'
import ErrorMessage from '@/components/common/ErrorMessage.vue'
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
// 注册全局组件
Vue.component('LoadingSpinner', LoadingSpinner)
Vue.component('ErrorMessage', ErrorMessage)
Vue.component('ConfirmDialog', ConfirmDialog)
// 全局混入
Vue.mixin({
methods: {
// 格式化价格
formatPrice(price, currency = 'USD') {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency
}).format(price)
},
// 格式化日期
formatDate(date, options = {}) {
const defaultOptions = {
year: 'numeric',
month: 'long',
day: 'numeric'
}
return new Intl.DateTimeFormat('zh-CN', {
...defaultOptions,
...options
}).format(new Date(date))
},
// 截断文本
truncateText(text, length = 100) {
if (!text) return ''
return text.length > length ? text.substring(0, length) + '...' : text
},
// 防抖函数
debounce(func, wait = 300) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
}
})
11.2.3 Nuxt.js中间件
// middleware/auth.js
export default function ({ store, redirect, route }) {
// 检查用户是否已认证
if (!store.getters['auth/isAuthenticated']) {
// 保存当前路由,登录后重定向
const redirectPath = route.fullPath !== '/login' ? route.fullPath : '/'
return redirect(`/login?redirect=${encodeURIComponent(redirectPath)}`)
}
}
// middleware/admin.js
export default function ({ store, redirect, error }) {
// 检查用户是否为管理员
const user = store.getters['auth/user']
if (!user || user.role !== 'admin') {
error({
statusCode: 403,
message: '访问被拒绝:需要管理员权限'
})
}
}
// middleware/guest.js
export default function ({ store, redirect }) {
// 如果用户已登录,重定向到首页
if (store.getters['auth/isAuthenticated']) {
return redirect('/')
}
}
11.3 开发工具与调试
11.3.1 Vue DevTools集成
// plugins/vue-devtools.js
import Vue from 'vue'
if (process.client && process.env.NODE_ENV === 'development') {
// 启用Vue DevTools
Vue.config.devtools = true
// 性能追踪
Vue.config.performance = true
// 自定义DevTools钩子
if (window.__VUE_DEVTOOLS_GLOBAL_HOOK__) {
window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue = Vue
}
// SSR调试信息
Vue.mixin({
beforeCreate() {
if (this.$ssrContext) {
console.log('SSR Context:', this.$ssrContext.url)
}
}
})
}
11.3.2 性能监控工具
// utils/performance.js
class PerformanceMonitor {
constructor() {
this.metrics = new Map()
this.observers = []
this.init()
}
init() {
if (typeof window === 'undefined') return
// 监听页面加载性能
this.observePageLoad()
// 监听资源加载
this.observeResources()
// 监听用户交互
this.observeInteractions()
// 监听Vue组件性能
this.observeVueComponents()
}
// 页面加载性能
observePageLoad() {
window.addEventListener('load', () => {
setTimeout(() => {
const navigation = performance.getEntriesByType('navigation')[0]
this.recordMetric('page_load', {
dns: navigation.domainLookupEnd - navigation.domainLookupStart,
tcp: navigation.connectEnd - navigation.connectStart,
request: navigation.responseStart - navigation.requestStart,
response: navigation.responseEnd - navigation.responseStart,
dom: navigation.domContentLoadedEventEnd - navigation.responseEnd,
load: navigation.loadEventEnd - navigation.loadEventStart,
total: navigation.loadEventEnd - navigation.navigationStart
})
}, 0)
})
}
// 资源加载性能
observeResources() {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.entryType === 'resource') {
this.recordMetric('resource_load', {
name: entry.name,
type: this.getResourceType(entry.name),
duration: entry.duration,
size: entry.transferSize,
cached: entry.transferSize === 0
})
}
})
})
observer.observe({ entryTypes: ['resource'] })
this.observers.push(observer)
}
// 用户交互性能
observeInteractions() {
// First Input Delay (FID)
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
this.recordMetric('first_input_delay', {
delay: entry.processingStart - entry.startTime,
duration: entry.duration,
target: entry.target?.tagName
})
})
})
observer.observe({ entryTypes: ['first-input'] })
this.observers.push(observer)
}
// Vue组件性能
observeVueComponents() {
if (typeof Vue === 'undefined') return
Vue.mixin({
beforeCreate() {
this._performanceStart = performance.now()
},
mounted() {
const duration = performance.now() - this._performanceStart
if (duration > 16) { // 超过一帧的时间
this.$performanceMonitor?.recordMetric('component_mount', {
component: this.$options.name || 'Anonymous',
duration,
route: this.$route?.path
})
}
}
})
}
// 记录指标
recordMetric(type, data) {
const timestamp = Date.now()
if (!this.metrics.has(type)) {
this.metrics.set(type, [])
}
this.metrics.get(type).push({
...data,
timestamp,
url: window.location.href,
userAgent: navigator.userAgent
})
// 发送到分析服务
this.sendToAnalytics(type, data)
}
// 发送分析数据
sendToAnalytics(type, data) {
// 批量发送,避免频繁请求
if (!this._analyticsQueue) {
this._analyticsQueue = []
}
this._analyticsQueue.push({ type, data, timestamp: Date.now() })
// 延迟发送
if (!this._analyticsTimer) {
this._analyticsTimer = setTimeout(() => {
this.flushAnalytics()
}, 5000)
}
}
// 批量发送分析数据
async flushAnalytics() {
if (!this._analyticsQueue || this._analyticsQueue.length === 0) return
try {
await fetch('/api/analytics/performance', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
metrics: this._analyticsQueue,
session: this.getSessionId()
})
})
} catch (error) {
console.error('Failed to send analytics:', error)
} finally {
this._analyticsQueue = []
this._analyticsTimer = null
}
}
// 获取资源类型
getResourceType(url) {
if (url.match(/\.(js|mjs)$/)) return 'script'
if (url.match(/\.(css)$/)) return 'stylesheet'
if (url.match(/\.(png|jpg|jpeg|gif|svg|webp)$/)) return 'image'
if (url.match(/\.(woff|woff2|ttf|eot)$/)) return 'font'
return 'other'
}
// 获取会话ID
getSessionId() {
let sessionId = sessionStorage.getItem('performance_session_id')
if (!sessionId) {
sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
sessionStorage.setItem('performance_session_id', sessionId)
}
return sessionId
}
// 获取性能报告
getReport() {
const report = {
timestamp: Date.now(),
url: window.location.href,
metrics: {}
}
for (const [type, entries] of this.metrics.entries()) {
report.metrics[type] = {
count: entries.length,
latest: entries[entries.length - 1],
average: this.calculateAverage(entries)
}
}
return report
}
// 计算平均值
calculateAverage(entries) {
if (entries.length === 0) return 0
const numericFields = ['duration', 'delay', 'total', 'size']
const averages = {}
numericFields.forEach(field => {
const values = entries
.map(entry => entry[field])
.filter(value => typeof value === 'number')
if (values.length > 0) {
averages[field] = values.reduce((sum, value) => sum + value, 0) / values.length
}
})
return averages
}
// 清理资源
destroy() {
this.observers.forEach(observer => observer.disconnect())
this.observers = []
this.metrics.clear()
if (this._analyticsTimer) {
clearTimeout(this._analyticsTimer)
this.flushAnalytics()
}
}
}
// 创建全局实例
const performanceMonitor = new PerformanceMonitor()
// Vue插件
export default {
install(Vue) {
Vue.prototype.$performanceMonitor = performanceMonitor
// 全局混入
Vue.mixin({
beforeDestroy() {
// 组件销毁时记录生命周期
if (this._performanceStart) {
const lifetime = performance.now() - this._performanceStart
if (lifetime > 1000) { // 超过1秒的组件
performanceMonitor.recordMetric('component_lifetime', {
component: this.$options.name || 'Anonymous',
lifetime,
route: this.$route?.path
})
}
}
}
})
}
}
export { performanceMonitor }
11.3.3 错误监控与日志
// utils/error-tracker.js
class ErrorTracker {
constructor(options = {}) {
this.options = {
apiEndpoint: '/api/errors',
maxErrors: 50,
enableConsoleLog: true,
enableRemoteLog: true,
...options
}
this.errors = []
this.init()
}
init() {
// 全局错误处理
if (typeof window !== 'undefined') {
window.addEventListener('error', this.handleError.bind(this))
window.addEventListener('unhandledrejection', this.handlePromiseRejection.bind(this))
}
// Vue错误处理
if (typeof Vue !== 'undefined') {
Vue.config.errorHandler = this.handleVueError.bind(this)
}
}
// 处理JavaScript错误
handleError(event) {
const error = {
type: 'javascript',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
}
this.recordError(error)
}
// 处理Promise拒绝
handlePromiseRejection(event) {
const error = {
type: 'promise_rejection',
message: event.reason?.message || 'Unhandled Promise Rejection',
stack: event.reason?.stack,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
}
this.recordError(error)
}
// 处理Vue错误
handleVueError(err, vm, info) {
const error = {
type: 'vue',
message: err.message,
stack: err.stack,
component: vm?.$options.name || 'Anonymous',
lifecycle: info,
props: vm?.$props,
route: vm?.$route?.path,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
}
this.recordError(error)
}
// 记录错误
recordError(error) {
// 添加到本地错误列表
this.errors.push(error)
// 限制错误数量
if (this.errors.length > this.options.maxErrors) {
this.errors.shift()
}
// 控制台输出
if (this.options.enableConsoleLog) {
console.error('Error tracked:', error)
}
// 发送到远程服务
if (this.options.enableRemoteLog) {
this.sendToRemote(error)
}
}
// 发送到远程服务
async sendToRemote(error) {
try {
await fetch(this.options.apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
error,
context: this.getContext()
})
})
} catch (err) {
console.error('Failed to send error to remote:', err)
}
}
// 获取上下文信息
getContext() {
return {
timestamp: Date.now(),
url: window.location.href,
referrer: document.referrer,
userAgent: navigator.userAgent,
viewport: {
width: window.innerWidth,
height: window.innerHeight
},
screen: {
width: screen.width,
height: screen.height
},
connection: navigator.connection ? {
effectiveType: navigator.connection.effectiveType,
downlink: navigator.connection.downlink
} : null
}
}
// 手动记录错误
captureError(error, context = {}) {
const errorData = {
type: 'manual',
message: error.message || error,
stack: error.stack,
context,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent
}
this.recordError(errorData)
}
// 记录用户操作
captureUserAction(action, data = {}) {
const actionData = {
type: 'user_action',
action,
data,
timestamp: Date.now(),
url: window.location.href
}
// 这里可以发送到分析服务
console.log('User action:', actionData)
}
// 获取错误报告
getErrorReport() {
return {
errors: this.errors,
summary: {
total: this.errors.length,
byType: this.groupErrorsByType(),
recent: this.errors.slice(-10)
}
}
}
// 按类型分组错误
groupErrorsByType() {
const groups = {}
this.errors.forEach(error => {
const type = error.type
if (!groups[type]) {
groups[type] = 0
}
groups[type]++
})
return groups
}
// 清除错误
clearErrors() {
this.errors = []
}
}
// 创建全局实例
const errorTracker = new ErrorTracker()
// Vue插件
export default {
install(Vue, options = {}) {
const tracker = new ErrorTracker(options)
Vue.prototype.$errorTracker = tracker
// 全局混入
Vue.mixin({
methods: {
$captureError(error, context) {
tracker.captureError(error, context)
},
$captureAction(action, data) {
tracker.captureUserAction(action, data)
}
}
})
}
}
export { errorTracker }
11.4 构建优化工具
11.4.1 Webpack Bundle分析
// build/analyzer.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin')
class BuildAnalyzer {
constructor(options = {}) {
this.options = {
analyze: process.env.ANALYZE === 'true',
speed: process.env.SPEED === 'true',
duplicates: process.env.CHECK_DUPLICATES === 'true',
...options
}
}
// 获取分析插件
getPlugins() {
const plugins = []
// Bundle分析
if (this.options.analyze) {
plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html',
generateStatsFile: true,
statsFilename: 'bundle-stats.json'
})
)
}
// 重复包检查
if (this.options.duplicates) {
plugins.push(
new DuplicatePackageCheckerPlugin({
verbose: true,
emitError: false,
showHelp: true,
strict: false
})
)
}
return plugins
}
// 包装配置以进行速度测量
wrapConfig(config) {
if (this.options.speed) {
const smp = new SpeedMeasurePlugin({
outputFormat: 'humanVerbose',
granularLoaderData: true
})
return smp.wrap(config)
}
return config
}
// 生成优化建议
generateOptimizationSuggestions(stats) {
const suggestions = []
const assets = stats.assets || []
const chunks = stats.chunks || []
// 检查大文件
const largeAssets = assets.filter(asset => asset.size > 250000) // 250KB
if (largeAssets.length > 0) {
suggestions.push({
type: 'large_assets',
message: `发现${largeAssets.length}个大文件,建议进行代码分割或压缩`,
assets: largeAssets.map(asset => ({
name: asset.name,
size: this.formatSize(asset.size)
}))
})
}
// 检查重复模块
const modules = stats.modules || []
const moduleMap = new Map()
modules.forEach(module => {
const name = module.name || module.identifier
if (name) {
const count = moduleMap.get(name) || 0
moduleMap.set(name, count + 1)
}
})
const duplicates = Array.from(moduleMap.entries())
.filter(([name, count]) => count > 1)
.map(([name, count]) => ({ name, count }))
if (duplicates.length > 0) {
suggestions.push({
type: 'duplicate_modules',
message: `发现${duplicates.length}个重复模块,建议优化依赖`,
modules: duplicates
})
}
// 检查未使用的代码
const unusedExports = this.findUnusedExports(stats)
if (unusedExports.length > 0) {
suggestions.push({
type: 'unused_exports',
message: `发现${unusedExports.length}个未使用的导出,建议清理`,
exports: unusedExports
})
}
return suggestions
}
// 查找未使用的导出
findUnusedExports(stats) {
// 这里需要更复杂的分析逻辑
// 简化版本,实际应该使用AST分析
return []
}
// 格式化文件大小
formatSize(bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB']
if (bytes === 0) return '0 Bytes'
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]
}
// 生成性能报告
generatePerformanceReport(stats) {
const report = {
timestamp: new Date().toISOString(),
buildTime: stats.time,
assets: {
total: stats.assets?.length || 0,
totalSize: stats.assets?.reduce((sum, asset) => sum + asset.size, 0) || 0,
largest: stats.assets?.reduce((largest, asset) =>
asset.size > (largest?.size || 0) ? asset : largest, null
)
},
chunks: {
total: stats.chunks?.length || 0,
entry: stats.chunks?.filter(chunk => chunk.entry).length || 0,
async: stats.chunks?.filter(chunk => !chunk.entry).length || 0
},
modules: {
total: stats.modules?.length || 0,
cached: stats.modules?.filter(module => module.cached).length || 0
},
optimizations: this.generateOptimizationSuggestions(stats)
}
return report
}
}
module.exports = BuildAnalyzer
11.4.2 性能优化配置
// build/optimization.js
const TerserPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const CompressionPlugin = require('compression-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
class OptimizationConfig {
constructor(isProd = false) {
this.isProd = isProd
}
// 获取优化配置
getOptimization() {
const config = {
// 代码分割
splitChunks: {
chunks: 'all',
cacheGroups: {
// 第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10,
reuseExistingChunk: true
},
// Vue相关
vue: {
test: /[\\/]node_modules[\\/](vue|vue-router|vuex)[\\/]/,
name: 'vue',
chunks: 'all',
priority: 20
},
// UI库
ui: {
test: /[\\/]node_modules[\\/](element-ui|ant-design-vue|vuetify)[\\/]/,
name: 'ui',
chunks: 'all',
priority: 15
},
// 工具库
utils: {
test: /[\\/]node_modules[\\/](lodash|moment|axios)[\\/]/,
name: 'utils',
chunks: 'all',
priority: 12
},
// 公共代码
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5,
reuseExistingChunk: true,
enforce: true
}
}
},
// 运行时chunk
runtimeChunk: {
name: 'runtime'
}
}
// 生产环境优化
if (this.isProd) {
config.minimizer = [
// JS压缩
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: false,
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log']
},
mangle: {
safari10: true
},
output: {
comments: false,
ascii_only: true
}
}
}),
// CSS压缩
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
safe: true,
autoprefixer: { disable: true },
mergeLonghand: false,
discardComments: {
removeAll: true
}
}
})
]
}
return config
}
// 获取性能优化插件
getPlugins() {
const plugins = []
if (this.isProd) {
// 清理输出目录
plugins.push(new CleanWebpackPlugin())
// Gzip压缩
plugins.push(
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8
})
)
// Brotli压缩
plugins.push(
new CompressionPlugin({
filename: '[path][base].br',
algorithm: 'brotliCompress',
test: /\.(js|css|html|svg)$/,
compressionOptions: {
level: 11
},
threshold: 8192,
minRatio: 0.8
})
)
}
return plugins
}
// 获取模块解析优化
getResolve() {
return {
// 模块查找优化
modules: [
'node_modules',
path.resolve(__dirname, '../client'),
path.resolve(__dirname, '../shared')
],
// 别名配置
alias: {
'@': path.resolve(__dirname, '../client'),
'~': path.resolve(__dirname, '../server'),
'shared': path.resolve(__dirname, '../shared'),
'vue$': 'vue/dist/vue.runtime.esm.js'
},
// 扩展名优化
extensions: ['.js', '.vue', '.json'],
// 主字段优化
mainFields: ['browser', 'module', 'main'],
// 符号链接优化
symlinks: false
}
}
// 获取缓存配置
getCacheConfig() {
return {
type: 'filesystem',
cacheDirectory: path.resolve(__dirname, '../node_modules/.cache/webpack'),
buildDependencies: {
config: [__filename]
}
}
}
}
module.exports = OptimizationConfig
11.5 本章小结
本章深入介绍了Vue SSR的生态系统与工具链,包括:
核心要点
生态系统概览
- 核心工具链组成
- 框架对比分析
- 技术选型指导
Nuxt.js深入解析
- 项目配置与结构
- 插件开发
- 中间件使用
开发工具与调试
- Vue DevTools集成
- 性能监控工具
- 错误追踪系统
构建优化工具
- Bundle分析
- 性能优化配置
- 自动化优化
最佳实践
工具选择
- 根据项目需求选择合适的框架
- 合理配置开发工具
- 建立完善的监控体系
性能优化
- 持续监控应用性能
- 定期分析构建产物
- 优化关键性能指标
开发效率
- 自动化开发流程
- 统一代码规范
- 完善错误处理
练习作业
- 配置完整的Nuxt.js项目
- 实现性能监控系统
- 搭建错误追踪服务
- 优化构建配置
下一章我们将学习Vue SSR的最佳实践与案例分析。