阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 生产构建问题解决指南

生产构建问题解决指南

作者:陈川 阅读数:33814人阅读 分类: 构建工具

构建速度慢

Vite.js 使用原生 ES 模块加载机制,开发环境下构建速度极快。但生产构建时仍可能遇到速度问题,主要原因包括:

  1. 依赖项过多:项目依赖的第三方库过多会增加构建时间
  2. 未优化的静态资源:大尺寸图片/未压缩的字体文件等
  3. 复杂的代码分割策略:不合理的分割导致额外构建开销

优化方案:

// vite.config.js
export default defineConfig({
  build: {
    // 启用多线程压缩
    minify: 'terser',
    // 调整块大小警告限制
    chunkSizeWarningLimit: 1000,
    // 配置rollup选项
    rollupOptions: {
      output: {
        // 手动拆分包
        manualChunks(id) {
          if (id.includes('node_modules')) {
            return 'vendor'
          }
        }
      }
    }
  }
})

针对图片资源,建议使用:

npm install vite-plugin-imagemin -D

然后配置:

import viteImagemin from 'vite-plugin-imagemin'

export default defineConfig({
  plugins: [
    viteImagemin({
      gifsicle: { optimizationLevel: 3 },
      mozjpeg: { quality: 75 },
      pngquant: { quality: [0.8, 0.9] },
      svgo: {
        plugins: [{ removeViewBox: false }]
      }
    })
  ]
})

内存溢出问题

处理大型项目时可能遇到内存不足错误,典型表现为:

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

解决方案:

  1. 增加Node.js内存限制:
# 在package.json中修改构建命令
"scripts": {
  "build": "node --max-old-space-size=8192 node_modules/vite/bin/vite.js build"
}
  1. 优化依赖项:
// vite.config.js
export default defineConfig({
  optimizeDeps: {
    include: ['only-necessary-packages'],
    exclude: ['heavy-dependencies']
  }
})
  1. 分阶段构建大型项目:
# 先构建核心部分
vite build --mode core
# 再构建附加模块
vite build --mode additional

路径别名解析失败

配置的路径别名在生产构建后失效,常见于:

// vite.config.js
resolve: {
  alias: {
    '@': path.resolve(__dirname, './src')
  }
}

确保同时配置了TypeScript路径映射(tsconfig.json):

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

对于JS项目,需要在构建后处理路径问题:

// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        chunkFileNames: 'assets/[name]-[hash].js',
        entryFileNames: 'assets/[name]-[hash].js',
        assetFileNames: 'assets/[name]-[hash].[ext]'
      }
    }
  }
})

CSS 相关构建问题

预处理器错误

使用Sass/Less时可能遇到:

Error: Can't resolve 'sass' in '/project/path'

确保已安装必要依赖:

npm install -D sass less

配置示例:

export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`
      },
      less: {
        math: 'always',
        globalVars: {
          primary: '#1890ff'
        }
      }
    }
  }
})

CSS 代码分割异常

默认情况下Vite会提取CSS到单独文件,但可能遇到:

  1. 样式丢失
  2. 加载顺序错乱

强制内联CSS:

export default defineConfig({
  build: {
    cssCodeSplit: false
  }
})

或精确控制CSS输出:

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        assetFileNames: (assetInfo) => {
          if (assetInfo.name.endsWith('.css')) {
            return 'static/css/[name]-[hash][extname]'
          }
          return 'static/[ext]/[name]-[hash][extname]'
        }
      }
    }
  }
})

环境变量问题

生产环境变量未正确加载是常见问题:

# .env.production
VITE_API_BASE=https://api.example.com

确保:

  1. 变量以VITE_前缀开头
  2. 构建时指定正确模式:
vite build --mode production

访问变量:

console.log(import.meta.env.VITE_API_BASE)

处理未定义变量:

// src/utils/env.js
export function getEnv(key) {
  const value = import.meta.env[key]
  if (value === undefined) {
    throw new Error(`Environment variable ${key} is required`)
  }
  return value
}

静态资源处理

资源路径错误

构建后图片/字体等资源404:

<!-- 错误示例 -->
<img src="@/assets/logo.png" />

正确方式:

import logo from '@/assets/logo.png'

// 在模板中使用
<img :src="logo" />

或配置公共资源目录:

export default defineConfig({
  publicDir: 'public',
  build: {
    assetsInlineLimit: 4096 // 小于4KB的资源内联为base64
  }
})

特殊资源处理

处理Web Worker:

// worker.js
self.onmessage = (e) => {
  console.log('Worker received:', e.data)
  self.postMessage('Message processed')
}

// 主线程
const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })

处理WebAssembly:

import init from './module.wasm?init'

init().then((instance) => {
  instance.exports.exported_func()
})

多页面应用配置

构建多页面应用需要特殊配置:

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        about: resolve(__dirname, 'about.html'),
        contact: resolve(__dirname, 'contact.html')
      }
    }
  }
})

目录结构建议:

project/
├── vite.config.js
├── index.html
├── about.html
├── contact.html
└── src/
    ├── main.js
    ├── about/
    │   └── about.js
    └── contact/
        └── contact.js

自定义构建输出

修改默认构建输出结构:

export default defineConfig({
  build: {
    outDir: 'dist',
    assetsDir: 'static',
    emptyOutDir: true,
    target: 'es2015',
    sourcemap: true,
    manifest: true
  }
})

生成构建分析报告:

npm install rollup-plugin-visualizer -D

配置:

import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    visualizer({
      open: true,
      gzipSize: true,
      brotliSize: true
    })
  ]
})

服务端渲染(SSR)构建

SSR构建需要特殊处理:

export default defineConfig({
  build: {
    ssr: true,
    rollupOptions: {
      input: 'src/entry-server.js',
      output: {
        format: 'cjs',
        dir: 'dist/server'
      }
    }
  }
})

客户端构建:

export default defineConfig({
  build: {
    ssrManifest: true,
    rollupOptions: {
      input: 'src/entry-client.js',
      output: {
        dir: 'dist/client'
      }
    }
  }
})

处理SSR外部化:

export default defineConfig({
  ssr: {
    noExternal: ['need-to-bundle-packages'],
    external: ['should-not-bundle-packages']
  }
})

兼容性问题处理

浏览器兼容性

配置目标环境:

export default defineConfig({
  build: {
    target: ['es2015', 'chrome58', 'firefox57', 'safari11']
  }
})

使用Polyfill:

npm install @vitejs/plugin-legacy -D

配置:

import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11'],
      polyfills: ['es.promise', 'es.array.iterator']
    })
  ]
})

Node.js特定代码

处理process.env

export default defineConfig({
  define: {
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
  }
})

处理Node.js核心模块:

export default defineConfig({
  resolve: {
    alias: {
      path: 'path-browserify',
      stream: 'stream-browserify'
    }
  }
})

部署相关问题

静态资源404

部署到子路径时:

export default defineConfig({
  base: '/sub-path/'
})

Nginx配置示例:

location /sub-path/ {
  alias /path/to/dist/;
  try_files $uri $uri/ /sub-path/index.html;
}

历史路由模式

配置Vue Router:

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
})

对应Nginx配置:

location / {
  try_files $uri $uri/ /index.html;
}

缓存策略

配置哈希文件名:

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        assetFileNames: 'assets/[name]-[hash].[ext]',
        chunkFileNames: 'assets/[name]-[hash].js',
        entryFileNames: 'assets/[name]-[hash].js'
      }
    }
  }
})

配置长期缓存:

location /assets {
  expires 1y;
  add_header Cache-Control "public, immutable";
}

性能优化进阶

预加载关键资源

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router', 'pinia'],
          utils: ['lodash', 'axios', 'date-fns']
        }
      }
    }
  }
})

异步组件加载

Vue示例:

const Home = defineAsyncComponent(() => import('./views/Home.vue'))

React示例:

const Home = lazy(() => import('./views/Home'))

按需加载库

以Ant Design Vue为例:

import { createApp } from 'vue'
import { Button, Modal } from 'ant-design-vue'

const app = createApp()
app.use(Button).use(Modal)

配置babel-plugin-import:

// vite.config.js
import vitePluginImp from 'vite-plugin-imp'

export default defineConfig({
  plugins: [
    vitePluginImp({
      libList: [
        {
          libName: 'ant-design-vue',
          style: (name) => `ant-design-vue/es/${name}/style/css`
        }
      ]
    })
  ]
})

调试生产构建

生成sourcemap

export default defineConfig({
  build: {
    sourcemap: true
  }
})

分析构建产物

使用webpack-bundle-analyzer的Vite替代方案:

npm install rollup-plugin-analyzer -D

配置:

import analyzer from 'rollup-plugin-analyzer'

export default defineConfig({
  plugins: [
    analyzer({
      summaryOnly: true,
      limit: 20
    })
  ]
})

本地预览生产构建

npm install serve -g
serve -s dist

或使用Vite预览:

export default defineConfig({
  preview: {
    port: 5000,
    strictPort: true,
    headers: {
      'Cache-Control': 'no-store'
    }
  }
})

运行:

vite preview

插件开发问题

自定义插件常见问题:

// my-plugin.js
export default function myPlugin() {
  return {
    name: 'my-plugin',
    config(config) {
      // 修改配置
      return {
        resolve: {
          alias: {
            ...config.resolve?.alias,
            '@components': '/src/components'
          }
        }
      }
    },
    transform(code, id) {
      // 转换代码
      if (id.endsWith('.custom')) {
        return code.replace(/__VERSION__/g, '1.0.0')
      }
    }
  }
}

处理插件顺序:

export default defineConfig({
  plugins: [
    vue(),
    myPlugin(), // 自定义插件通常放在框架插件之后
    legacy()
  ]
})

TypeScript 相关问题

类型检查

单独运行类型检查:

npm install vue-tsc -D

package.json脚本:

{
  "scripts": {
    "type-check": "vue-tsc --noEmit"
  }
}

路径别名类型

确保tsconfig.json配置:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "~/*": ["public/*"]
    }
  }
}

自定义类型

声明环境变量类型:

// src/env.d.ts
interface ImportMetaEnv {
  readonly VITE_API_BASE: string
  readonly VITE_APP_TITLE: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

扩展组件类型:

// src/components.d.ts
declare module '*.vue' {
  import { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

微前端集成

作为子应用

配置独立开发:

export default defineConfig({
  server: {
    port: 3001,
    cors: true,
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  },
  build: {
    lib: {
      entry: 'src/main.ts',
      name: 'myApp',
      fileName: 'my-app'
    }
  }
})

集成qiankun

修改入口文件:

// src/main.ts
let app: App

function render(props: any) {
  const { container } = props
  app = createApp(App)
  app.mount(container?.querySelector('#app') || '#app')
}

// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
  render({})
}

// qiankun生命周期
export async function bootstrap() {
  console.log('app bootstrap')
}

export async function mount(props: any) {
  render(props)
}

export async function unmount() {
  app?.unmount()
}

资源加载问题

配置publicPath:

export default defineConfig({
  base: process.env.NODE_ENV === 'production' ? 'http://your-cdn.com/sub-app/' : '/'
})

持续集成/持续部署

GitHub Actions示例

name: Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '16'
      - run: npm install
      - run: npm run build
      - uses: actions/upload-artifact@v2
        with:
          name: dist
          path: dist

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v2
        with:
          name: dist
      - uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./dist

Docker部署

Dockerfile示例:

# 构建阶段
FROM node:16-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# 生产阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

对应nginx.conf:

server {
  listen 80;
  server_name localhost;
  
  location / {
    root /usr/share/nginx/html;
    index index.html;
    try_files $uri $uri/ /index.html;
  }
  
  gzip on;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌