"又发错环境了!"周四下午,测试同学小李急匆匆地找到我。原来是开发人员手动部署时,不小心把测试代码发布到了生产环境。这已经是本月第二次类似的事故了。
回想起每次发布时的场景:手动打包、手动上传、手动替换文件...每一步都战战兢兢,生怕出错。作为技术负责人,我决定要彻底改变这种状况,建立一套可靠的自动化部署流程。
现状问题
首先梳理了当前部署流程中的痛点:
- 人工操作步骤多,容易出错
- 环境配置混乱,经常配错
- 发布耗时长,经常加班
- 回滚困难,出问题很被动
- 缺乏发布记录,无法追溯
就像是一场没有彩排的演出,每次都提心吊胆。我们需要把这个过程变得像工厂的流水线一样可靠。
自动化方案
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 // 1MB
stats.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-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install Dependencies
run: npm ci
- name: Type Check
run: npm run type-check
- name: Test
run: npm test
- name: Build
run: |
if [[ $GITHUB_REF == refs/heads/main ]]; then
npm run build:prod
elif [[ $GITHUB_REF == refs/heads/test ]]; then
npm run build:test
fi
- name: Deploy
uses: azure/webapps-deploy@v2
with:
app-name: ${{ secrets.AZURE_WEBAPP_NAME }}
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
package: ./dist
- name: Notify
if: always()
uses: actions/github-script@v6
with:
script: |
const { repo, owner } = context.repo
const run_id = context.runId
const 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 分钟
- 人工操作失误完全消除
- 发布过程可追溯、可回滚
- 新版本平滑过渡,用户无感知
最让我印象深刻的是测试同学的反馈:"现在再也不用担心环境被搞混了!"
经验总结
前端部署就像是一条生产线,需要严格的流程和质量控制。关键是要:
环境隔离 - 不同环境配置要严格分开流程自动化 - 减少人工操作降低风险质量把控 - 层层把关确保代码质量监控反馈 - 及时发现和解决问题
写在最后
前端部署不仅仅是把代码放到服务器上,而是一个需要精心设计的完整流程。就像那句话说的:"磨刀不误砍柴工",投入时间建设自动化流程,最终会让团队受益良多。
有什么问题欢迎在评论区讨论,让我们一起探讨前端部署的最佳实践!
如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~