"又发错环境了!"周四下午,测试同学小李急匆匆地找到我。原来是开发人员手动部署时,不小心把测试代码发布到了生产环境。这已经是本月第二次类似的事故了。
回想起每次发布时的场景:手动打包、手动上传、手动替换文件...每一步都战战兢兢,生怕出错。作为技术负责人,我决定要彻底改变这种状况,建立一套可靠的自动化部署流程。
现状问题
首先梳理了当前部署流程中的痛点:
- 人工操作步骤多,容易出错
- 环境配置混乱,经常配错
- 发布耗时长,经常加班
- 回滚困难,出问题很被动
- 缺乏发布记录,无法追溯
就像是一场没有彩排的演出,每次都提心吊胆。我们需要把这个过程变得像工厂的流水线一样可靠。
自动化方案
1. 构建脚本
首先是统一的构建脚本:
// scripts/build.js
const webpack = require('webpack')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base')
const envConfigs = {dev: require('./webpack.dev'),test: require('./webpack.test'),prod: require('./webpack.prod')
}async function build(env) {// 合并配置const config = merge(baseConfig, envConfigs[env], {mode: env === 'dev' ? 'development' : 'production'})// 执行构建const compiler = webpack(config)const stats = await new Promise((resolve, reject) => {compiler.run((err, stats) => {if (err) reject(err)else resolve(stats)})})// 输出构建信息console.log(stats.toString({colors: true,modules: false,children: false}))// 验证构建产物await validateBuild()
}// 构建产物验证
async function validateBuild() {const files = await fs.readdir('./dist')// 检查必要文件const required = ['index.html', 'main.js', 'vendor.js']const missing = required.filter(file => !files.includes(file))if (missing.length) {throw new Error(`构建产物缺失: ${missing.join(', ')}`)}// 检查文件大小const stats = await Promise.all(files.map(async file => ({file,size: (await fs.stat(`./dist/${file}`)).size})))// 超出限制告警const limit = 1024 * 1024 // 1MBstats.forEach(({ file, size }) => {if (size > limit) {console.warn(`文件过大: ${file} (${(size / 1024 / 1024).toFixed(2)}MB)`)}})
}
2. 环境配置
然后是规范的环境配置管理:
// config/index.js
const configs = {dev: {api: 'http://dev-api.example.com',cdn: 'http://dev-cdn.example.com',features: {logger: true,mock: true}},test: {api: 'http://test-api.example.com',cdn: 'http://test-cdn.example.com',features: {logger: true,mock: false}},prod: {api: 'https://api.example.com',cdn: 'https://cdn.example.com',features: {logger: false,mock: false}}
}// 环境变量注入
function injectEnv(env) {const config = configs[env]// 写入环境变量文件const content = Object.entries(config).map(([key, value]) => `VITE_${key.toUpperCase()}=${JSON.stringify(value)}`).join('\n')fs.writeFileSync('.env.local', content)
}// 环境变量验证
function validateEnv() {const required = ['VITE_API', 'VITE_CDN']const missing = required.filter(key => !process.env[key])if (missing.length) {throw new Error(`环境变量缺失: ${missing.join(', ')}`)}
}
3. 自动化部署
最关键的是自动化部署流程:
# .github/workflows/deploy.yml
name: Deploy
on:push:branches: [main, test]pull_request:branches: [main]jobs:deploy:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v2- name: Setup Node.jsuses: actions/setup-node@v2with:node-version: '16'- name: Install Dependenciesrun: npm ci- name: Type Checkrun: npm run type-check- name: Testrun: npm test- name: Buildrun: |if [[ $GITHUB_REF == refs/heads/main ]]; thennpm run build:prodelif [[ $GITHUB_REF == refs/heads/test ]]; thennpm run build:testfi- name: Deployuses: azure/webapps-deploy@v2with:app-name: ${{ secrets.AZURE_WEBAPP_NAME }}publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}package: ./dist- name: Notifyif: always()uses: actions/github-script@v6with:script: |const { repo, owner } = context.repoconst run_id = context.runIdconst message = `部署 ${context.sha.slice(0, 7)} 完成分支: ${context.ref}状态: ${context.job.status}详情: https://github.com/${owner}/${repo}/actions/runs/${run_id}`await github.rest.issues.createComment({owner,repo,issue_number: context.issue.number,body: message})
4. 灰度发布
为了更安全的发布,我们实现了灰度发布机制:
// server/canary.js
class CanaryRelease {constructor(options = {}) {this.ratio = options.ratio || 0.1 // 灰度比例this.rules = options.rules || [] // 灰度规则this.versions = new Map() // 版本映射}// 添加新版本addVersion(version, url) {this.versions.set(version, url)}// 获取用户版本getVersion(req) {// 优先匹配规则for (const rule of this.rules) {if (rule.test(req)) {return rule.version}}// 按比例灰度const random = Math.random()if (random < this.ratio) {return Array.from(this.versions.keys()).pop() // 最新版本}return Array.from(this.versions.keys())[0] // 稳定版本}// 中间件middleware() {return (req, res, next) => {const version = this.getVersion(req)const url = this.versions.get(version)if (url) {res.redirect(url)} else {next()}}}
}// 使用示例
const canary = new CanaryRelease({ratio: 0.2,rules: [{test: req => req.query.version === 'new',version: 'v2'},{test: req => req.cookies.beta === 'true',version: 'v2'}]
})canary.addVersion('v1', 'https://stable.example.com')
canary.addVersion('v2', 'https://canary.example.com')app.use(canary.middleware())
实践效果
通过这套自动化部署流程:
- 部署时间从 1 小时减少到 5 分钟
- 人工操作失误完全消除
- 发布过程可追溯、可回滚
- 新版本平滑过渡,用户无感知
最让我印象深刻的是测试同学的反馈:"现在再也不用担心环境被搞混了!"
经验总结
前端部署就像是一条生产线,需要严格的流程和质量控制。关键是要:
环境隔离 - 不同环境配置要严格分开流程自动化 - 减少人工操作降低风险质量把控 - 层层把关确保代码质量监控反馈 - 及时发现和解决问题
写在最后
前端部署不仅仅是把代码放到服务器上,而是一个需要精心设计的完整流程。就像那句话说的:"磨刀不误砍柴工",投入时间建设自动化流程,最终会让团队受益良多。
有什么问题欢迎在评论区讨论,让我们一起探讨前端部署的最佳实践!
如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~