Node.js项目CI/CD #
本节通过一个完整的Node.js项目案例,演示如何构建CI/CD流水线。
项目结构 #
text
nodejs-app/
├── src/
│ ├── index.js
│ └── routes/
├── test/
│ └── index.test.js
├── Dockerfile
├── package.json
├── .npmrc
└── Jenkinsfile
Jenkinsfile完整示例 #
groovy
pipeline {
agent any
environment {
APP_NAME = 'nodejs-app'
DOCKER_REGISTRY = 'registry.example.com'
NODE_VERSION = '18'
}
tools {
nodejs "NodeJS ${NODE_VERSION}"
}
options {
timeout(time: 20, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '20'))
timestamps()
}
stages {
stage('Checkout') {
steps {
checkout scm
sh 'git log -1 --pretty=format:"%h - %an, %ar : %s"'
}
}
stage('Setup') {
steps {
sh 'node --version'
sh 'npm --version'
}
}
stage('Install Dependencies') {
steps {
sh 'npm ci'
}
}
stage('Lint') {
steps {
sh 'npm run lint'
}
}
stage('Test') {
steps {
sh 'npm test'
}
post {
always {
junit 'test-results/*.xml'
}
}
}
stage('Coverage') {
steps {
sh 'npm run coverage'
}
post {
always {
publishHTML target: [
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
]
}
}
}
stage('Build') {
steps {
sh 'npm run build'
}
}
stage('Docker Build') {
steps {
script {
docker.build("${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER}")
}
}
}
stage('Docker Push') {
steps {
withCredentials([usernamePassword(
credentialsId: 'docker-credentials',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh """
docker login -u ${DOCKER_USER} -p ${DOCKER_PASS} ${DOCKER_REGISTRY}
docker push ${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER}
"""
}
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
sh """
kubectl set image deployment/${APP_NAME} \
${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} \
-n production
"""
}
}
}
post {
always {
cleanWs()
}
success {
slackSend(
channel: '#builds',
color: 'good',
message: "Build Success: ${JOB_NAME} #${BUILD_NUMBER}"
)
}
failure {
slackSend(
channel: '#builds',
color: 'danger',
message: "Build Failed: ${JOB_NAME} #${BUILD_NUMBER}"
)
}
}
}
Dockerfile #
dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
EXPOSE 3000
USER node
CMD ["node", "dist/index.js"]
package.json #
json
{
"name": "nodejs-app",
"version": "1.0.0",
"scripts": {
"start": "node src/index.js",
"build": "tsc",
"test": "jest --coverage",
"lint": "eslint src/**/*.ts",
"coverage": "jest --coverage --coverageReporters=html"
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"@types/express": "^4.17.17",
"@types/jest": "^29.5.0",
"@types/node": "^18.15.0",
"eslint": "^8.36.0",
"jest": "^29.5.0",
"typescript": "^5.0.0"
}
}
Jest配置 #
javascript
module.exports = {
testEnvironment: 'node',
coverageDirectory: 'coverage',
collectCoverageFrom: [
'src/**/*.js',
'!src/**/*.test.js'
],
testMatch: [
'**/test/**/*.test.js'
]
};
多阶段构建优化 #
groovy
stage('Docker Build') {
steps {
script {
// 使用构建缓存
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials') {
def image = docker.build("${APP_NAME}:${BUILD_NUMBER}",
"--build-arg BUILDKIT_INLINE_CACHE=1 " +
"--cache-from ${DOCKER_REGISTRY}/${APP_NAME}:latest ."
)
image.push()
}
}
}
}
小结 #
- 使用npm管理依赖
- 集成ESLint代码检查
- Jest测试和覆盖率报告
- Docker多阶段构建优化
- Slack通知集成
最后更新:2026-03-28