当前位置: 首页 > news >正文

【Node.js】工具链与工程化

在这里插入图片描述

个人主页:Guiat
归属专栏:node.js

在这里插入图片描述

文章目录

  • 1. Node.js 工具链概述
    • 1.1 工具链的作用
    • 1.2 Node.js 工具链全景
  • 2. 包管理与依赖管理
    • 2.1 npm (Node Package Manager)
    • 2.2 yarn
    • 2.3 pnpm
    • 2.4 锁文件与依赖管理
    • 2.5 工作空间与 Monorepo
  • 3. 构建工具与打包
    • 3.1 Webpack
    • 3.2 Rollup
    • 3.3 esbuild
    • 3.4 Babel
    • 3.5 TypeScript 配置
    • 3.6 构建工具比较
  • 4. 代码质量与规范
    • 4.1 ESLint
    • 4.2 Prettier
    • 4.3 Git Hooks 与 Husky
    • 4.4 代码风格规范工作流
    • 4.5 TypeScript 检查
  • 5. 测试框架与测试策略
    • 5.1 Jest 测试框架
    • 5.2 API 测试与 Supertest
    • 5.3 单元测试与模拟
    • 5.4 测试覆盖率与报告
    • 5.5 持续集成与测试
  • 6. 自动化与 CI/CD 流程
    • 6.1 GitHub Actions
    • 6.2 Docker 与容器化

正文

1. Node.js 工具链概述

Node.js 工具链是一系列用于开发、测试、构建和部署 Node.js 应用程序的工具集合。完善的工具链可以显著提高开发效率,确保代码质量,简化部署流程。

1.1 工具链的作用

  • 自动化开发工作流程
  • 提高代码质量和一致性
  • 简化依赖管理
  • 优化构建过程
  • 加速测试和部署
  • 提高项目可维护性

1.2 Node.js 工具链全景

Node.js工具链
包管理工具
构建工具
测试框架
代码质量工具
版本控制
CI/CD工具
文档工具
部署与监控
npm
yarn
pnpm
Webpack
Rollup
Parcel
esbuild
Vite
Jest
Mocha
Jasmine
AVA
Cypress
ESLint
Prettier
TypeScript
Husky
SonarQube
Git
GitHub/GitLab
Conventional Commits
GitHub Actions
Jenkins
CircleCI
Travis CI
JSDoc
Swagger/OpenAPI
Docusaurus
Docker
PM2
Kubernetes
Grafana/Prometheus

2. 包管理与依赖管理

2.1 npm (Node Package Manager)

npm 是 Node.js 的默认包管理器,提供了安装、更新和管理依赖的功能。

# 初始化新项目
npm init -y# 安装依赖包
npm install express# 安装开发依赖
npm install --save-dev jest# 全局安装包
npm install -g nodemon# 更新依赖
npm update# 运行脚本
npm run start

package.json 详解:

{"name": "my-node-app","version": "1.0.0","description": "A Node.js application with complete toolchain","main": "src/index.js","type": "module","scripts": {"start": "node src/index.js","dev": "nodemon src/index.js","build": "webpack --config webpack.config.js","test": "jest --coverage","lint": "eslint src/**/*.js","format": "prettier --write \"src/**/*.{js,json}\"","prepare": "husky install"},"keywords": ["node", "express", "api"],"author": "Your Name","license": "MIT","dependencies": {"express": "^4.18.2","mongoose": "^7.5.0","dotenv": "^16.3.1","jsonwebtoken": "^9.0.1"},"devDependencies": {"jest": "^29.6.4","eslint": "^8.48.0","prettier": "^3.0.3","nodemon": "^3.0.1","webpack": "^5.88.2","webpack-cli": "^5.1.4","husky": "^8.0.3","lint-staged": "^14.0.1"},"engines": {"node": ">=18.0.0"}
}

2.2 yarn

Yarn 是 Facebook 开发的包管理器,提供了更快的安装速度和更好的依赖解决方案。

# 初始化项目
yarn init -y# 安装依赖
yarn add express# 安装开发依赖
yarn add --dev jest# 全局安装
yarn global add nodemon# 更新依赖
yarn upgrade# 运行脚本
yarn start

2.3 pnpm

pnpm 是一个快速、节省磁盘空间的包管理器,采用了内容寻址存储方式。

# 安装 pnpm
npm install -g pnpm# 初始化项目
pnpm init# 安装依赖
pnpm add express# 安装开发依赖
pnpm add -D jest# 更新依赖
pnpm update# 运行脚本
pnpm start

2.4 锁文件与依赖管理

graph TDA[依赖管理] --> B[锁文件]A --> C[语义化版本]A --> D[monorepo]B --> B1[package-lock.json]B --> B2[yarn.lock]B --> B3[pnpm-lock.yaml]C --> C1[主版本.次版本.修订版本]C --> C2[^主版本.次版本.修订版本]C --> C3[~主版本.次版本.修订版本]D --> D1[Lerna]D --> D2[Nx]D --> D3[Turborepo]style A fill:#66CDAAstyle B fill:#87CEFAstyle C fill:#87CEFAstyle D fill:#87CEFA

语义化版本控制:

  • 主版本号:不兼容的 API 更改
  • 次版本号:向后兼容的功能性新增
  • 修订版本号:向后兼容的问题修正

前缀含义:

  • ^:允许升级到任何保持主版本相同的版本
  • ~:允许升级到任何保持主版本和次版本相同的版本
  • 无前缀:精确匹配版本

2.5 工作空间与 Monorepo

// package.json (workspaces 示例)
{"name": "monorepo-project","private": true,"workspaces": ["packages/*"],"scripts": {"start": "node scripts/start.js","test": "jest"}
}
// Lerna 配置文件 (lerna.json)
{"version": "independent","npmClient": "yarn","useWorkspaces": true,"packages": ["packages/*"],"command": {"publish": {"conventionalCommits": true,"message": "chore(release): publish"}}
}

3. 构建工具与打包

3.1 Webpack

Webpack 是一个静态模块打包器,可以处理 JavaScript、CSS、图像等资源。

// webpack.config.js 基本配置
const path = require('path');
const nodeExternals = require('webpack-node-externals');module.exports = {target: 'node',mode: process.env.NODE_ENV || 'development',entry: './src/index.js',output: {path: path.resolve(__dirname, 'dist'),filename: 'bundle.js',clean: true},externals: [nodeExternals()],module: {rules: [{test: /\.js$/,exclude: /node_modules/,use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env']}}},{test: /\.json$/,loader: 'json-loader',type: 'javascript/auto'}]},resolve: {extensions: ['.js', '.json'],alias: {'@': path.resolve(__dirname, 'src')}},optimization: {minimize: process.env.NODE_ENV === 'production'},devtool: process.env.NODE_ENV === 'production' ? 'source-map' : 'eval-source-map'
};

3.2 Rollup

Rollup 专注于 JavaScript 库构建,特别适合生成更小、更高效的包。

// rollup.config.js 基本配置
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import json from '@rollup/plugin-json';
import { terser } from 'rollup-plugin-terser';export default {input: 'src/index.js',output: [{file: 'dist/bundle.cjs.js',format: 'cjs',sourcemap: true},{file: 'dist/bundle.esm.js',format: 'esm',sourcemap: true}],plugins: [resolve({preferBuiltins: true}),commonjs(),json(),babel({babelHelpers: 'bundled',exclude: 'node_modules/**'}),process.env.NODE_ENV === 'production' && terser()],external: ['express', 'mongoose', 'dotenv']
};

3.3 esbuild

esbuild 是一个极快的 JavaScript 打包器和压缩器。

// esbuild.config.js
const esbuild = require('esbuild');esbuild.build({entryPoints: ['src/index.js'],bundle: true,platform: 'node',target: ['node16'],outfile: 'dist/bundle.js',minify: process.env.NODE_ENV === 'production',sourcemap: true,external: ['express', 'mongoose', 'dotenv']
}).catch(() => process.exit(1));

3.4 Babel

Babel 是一个 JavaScript 编译器,用于将现代 JavaScript 代码转换为向后兼容的版本。

// babel.config.js
module.exports = {presets: [['@babel/preset-env', {targets: {node: '16'}}]],plugins: ['@babel/plugin-transform-runtime','@babel/plugin-proposal-optional-chaining','@babel/plugin-proposal-nullish-coalescing-operator']
};

3.5 TypeScript 配置

// tsconfig.json
{"compilerOptions": {"target": "ES2020","module": "NodeNext","moduleResolution": "NodeNext","lib": ["ES2020"],"outDir": "./dist","rootDir": "./src","strict": true,"esModuleInterop": true,"skipLibCheck": true,"forceConsistentCasingInFileNames": true,"resolveJsonModule": true,"declaration": true,"sourceMap": true,"baseUrl": ".","paths": {"@/*": ["src/*"]}},"include": ["src/**/*"],"exclude": ["node_modules", "dist", "**/*.test.ts"]
}

3.6 构建工具比较

工具优势适用场景
Webpack生态丰富,功能强大,社区支持好复杂应用,需要处理多种资源
Rollup生成体积小的包,输出多种格式库开发,需要 ESM 和 CJS 双格式
esbuild极快的构建速度开发环境快速构建,简单项目
Parcel零配置,开箱即用快速原型开发,不想编写配置
Vite快速开发服务器,模块热替换现代应用开发,优先考虑开发体验

4. 代码质量与规范

4.1 ESLint

ESLint 是一个静态代码分析工具,用于识别和报告 JavaScript 代码中的问题。

// .eslintrc.js
module.exports = {env: {node: true,es2021: true,jest: true},extends: ['eslint:recommended','plugin:node/recommended'],parserOptions: {ecmaVersion: 'latest',sourceType: 'module'},rules: {'indent': ['error', 2],'linebreak-style': ['error', 'unix'],'quotes': ['error', 'single'],'semi': ['error', 'always'],'no-unused-vars': ['warn', { 'argsIgnorePattern': '^_' }],'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off','no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off','node/no-unsupported-features/es-syntax': ['error',{ 'ignores': ['modules'] }]}
};

4.2 Prettier

Prettier 是一个代码格式化工具,确保代码风格统一。

// .prettierrc
{"semi": true,"singleQuote": true,"tabWidth": 2,"printWidth": 100,"trailingComma": "es5","arrowParens": "avoid","endOfLine": "lf"
}

与 ESLint 集成:

npm install --save-dev eslint-config-prettier eslint-plugin-prettier
// .eslintrc.js with Prettier integration
module.exports = {extends: ['eslint:recommended','plugin:node/recommended','plugin:prettier/recommended'],// other configurations...
};

4.3 Git Hooks 与 Husky

Husky 可以帮助在 Git 生命周期的关键点上运行脚本。

# 安装 Husky
npm install --save-dev husky# 启用 Git hooks
npx husky install# 添加 pre-commit hook
npx husky add .husky/pre-commit "npm run lint-staged"
// package.json
{"scripts": {"prepare": "husky install"},"lint-staged": {"*.js": ["eslint --fix","prettier --write"],"*.{json,md}": ["prettier --write"]}
}

4.4 代码风格规范工作流

失败
通过
完成
开发人员提交代码
Husky拦截
lint-staged运行
ESLint检查
显示错误
Prettier格式化
提交代码

4.5 TypeScript 检查

# 安装 TypeScript
npm install --save-dev typescript @types/node# 生成 tsconfig.json
npx tsc --init

与 ESLint 集成:

npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
// .eslintrc.js for TypeScript
module.exports = {parser: '@typescript-eslint/parser',extends: ['eslint:recommended','plugin:@typescript-eslint/recommended','plugin:prettier/recommended'],plugins: ['@typescript-eslint'],// other configurations...
};

5. 测试框架与测试策略

5.1 Jest 测试框架

Jest 是一个零配置的 JavaScript 测试框架,适用于大多数 JavaScript 项目。

// jest.config.js
module.exports = {testEnvironment: 'node',coverageDirectory: 'coverage',collectCoverageFrom: ['src/**/*.{js,ts}','!src/**/*.d.ts','!src/**/*.test.{js,ts}','!src/**/index.{js,ts}'],coverageThreshold: {global: {branches: 80,functions: 80,lines: 80,statements: 80}},moduleNameMapper: {'^@/(.*)$': '<rootDir>/src/$1'},testMatch: ['**/__tests__/**/*.test.[jt]s?(x)','**/?(*.)+(spec|test).[jt]s?(x)'],testPathIgnorePatterns: ['/node_modules/','/dist/']
};

示例测试文件:

// src/utils/calculator.test.js
const { add, subtract, multiply, divide } = require('./calculator');describe('Calculator', () => {describe('add', () => {test('adds two positive numbers', () => {expect(add(2, 3)).toBe(5);});test('adds a positive and a negative number', () => {expect(add(2, -3)).toBe(-1);});});describe('subtract', () => {test('subtracts two positive numbers', () => {expect(subtract(5, 3)).toBe(2);});test('subtracts a negative from a positive number', () => {expect(subtract(5, -3)).toBe(8);});});describe('multiply', () => {test('multiplies two positive numbers', () => {expect(multiply(2, 3)).toBe(6);});test('multiplies a positive and a negative number', () => {expect(multiply(2, -3)).toBe(-6);});});describe('divide', () => {test('divides two positive numbers', () => {expect(divide(6, 3)).toBe(2);});test('throws an error when dividing by zero', () => {expect(() => divide(6, 0)).toThrow('Cannot divide by zero');});});
});

5.2 API 测试与 Supertest

// src/app.test.js
const request = require('supertest');
const app = require('./app');
const mongoose = require('mongoose');// 连接测试数据库
beforeAll(async () => {await mongoose.connect(process.env.MONGO_URI_TEST);
});// 断开数据库连接
afterAll(async () => {await mongoose.connection.close();
});// 清理测试数据
afterEach(async () => {await mongoose.connection.db.dropDatabase();
});describe('User API', () => {describe('POST /api/users', () => {test('should create a new user', async () => {const userData = {username: 'testuser',email: 'test@example.com',password: 'password123'};const response = await request(app).post('/api/users').send(userData);expect(response.status).toBe(201);expect(response.body).toHaveProperty('user');expect(response.body.user).toHaveProperty('id');expect(response.body.user.username).toBe(userData.username);expect(response.body.user.email).toBe(userData.email);expect(response.body.user).not.toHaveProperty('password');});test('should not create user with duplicate email', async () => {const userData = {username: 'testuser',email: 'test@example.com',password: 'password123'};// 创建第一个用户await request(app).post('/api/users').send(userData);// 尝试创建重复的用户const response = await request(app).post('/api/users').send({username: 'anotheruser',email: 'test@example.com', // 相同的邮箱password: 'password456'});expect(response.status).toBe(400);expect(response.body).toHaveProperty('error');});});
});

5.3 单元测试与模拟

// src/services/userService.test.js
const userService = require('./userService');
const User = require('../models/User');// 模拟 User 模型
jest.mock('../models/User');describe('User Service', () => {beforeEach(() => {jest.clearAllMocks();});describe('createUser', () => {test('should create and return a new user', async () => {// 准备测试数据const userData = {username: 'testuser',email: 'test@example.com',password: 'password123'};// 模拟 User.create 方法User.create.mockResolvedValue({_id: '507f1f77bcf86cd799439011',username: userData.username,email: userData.email,createdAt: new Date().toISOString()});// 调用被测试的方法const user = await userService.createUser(userData);// 断言 User.create 被正确调用expect(User.create).toHaveBeenCalledWith(expect.objectContaining({username: userData.username,email: userData.email}));// 断言返回值expect(user).toHaveProperty('_id');expect(user.username).toBe(userData.username);expect(user.email).toBe(userData.email);});test('should throw an error if user creation fails', async () => {// 准备测试数据const userData = {username: 'testuser',email: 'test@example.com',password: 'password123'};// 模拟 User.create 抛出错误const errorMessage = 'Failed to create user';User.create.mockRejectedValue(new Error(errorMessage));// 断言方法抛出错误await expect(userService.createUser(userData)).rejects.toThrow(errorMessage);});});
});

5.4 测试覆盖率与报告

# 运行测试并生成覆盖率报告
npm test -- --coverage
测试策略
单元测试
集成测试
端到端测试
性能测试
Jest
Supertest
Cypress
k6

5.5 持续集成与测试

# .github/workflows/test.yml
name: Teston:push:branches: [ main, develop ]pull_request:branches: [ main, develop ]jobs:test:runs-on: ubuntu-latestservices:mongodb:image: mongo:4.4ports:- 27017:27017strategy:matrix:node-version: [16.x, 18.x]steps:- uses: actions/checkout@v3- name: Use Node.js ${{ matrix.node-version }}uses: actions/setup-node@v3with:node-version: ${{ matrix.node-version }}cache: 'npm'- name: Install dependenciesrun: npm ci- name: Run lintingrun: npm run lint- name: Run tests with coveragerun: npm test -- --coverageenv:MONGO_URI_TEST: mongodb://localhost:27017/test-db- name: Upload coverage to Codecovuses: codecov/codecov-action@v3with:token: ${{ secrets.CODECOV_TOKEN }}

6. 自动化与 CI/CD 流程

6.1 GitHub Actions

# .github/workflows/ci.yml
name: CI/CDon:push:branches: [ main ]pull_request:branches: [ main ]jobs:build:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v3- name: Use Node.jsuses: actions/setup-node@v3with:node-version: '18.x'cache: 'npm'- name: Install dependenciesrun: npm ci- name: Lintrun: npm run lint- name: Testrun: npm test- name: Buildrun: npm run build- name: Upload build artifactsuses: actions/upload-artifact@v3with:name: buildpath: distdeploy:needs: buildif: github.event_name == 'push' && github.ref == 'refs/heads/main'runs-on: ubuntu-lateststeps:- uses: actions/checkout@v3- name: Download build artifactsuses: actions/download-artifact@v3with:name: buildpath: dist- name: Set up Docker Buildxuses: docker/setup-buildx-action@v2- name: Login to Docker Hubuses: docker/login-action@v2with:username: ${{ secrets.DOCKER_HUB_USERNAME }}password: ${{ secrets.DOCKER_HUB_TOKEN }}- name: Build and push Docker imageuses: docker/build-push-action@v4with:context: .push: truetags: username/my-node-app:latest- name: Deploy to productionuses: appleboy/ssh-action@masterwith:host: ${{ secrets.SSH_HOST }}username: ${{ secrets.SSH_USERNAME }}key: ${{ secrets.SSH_PRIVATE_KEY }}script: |cd /path/to/productiondocker-compose pulldocker-compose up -d

6.2 Docker 与容器化

# Dockerfile
FROM node:18-alpine as builderWORKDIR /appCOPY package*.json ./
RUN npm ciCOPY . .
RUN npm run buildFROM node:18-alpineWORKDIR /appCOPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modulesENV NODE_ENV=production
ENV PORT=3000EXPOSE 3000
CMD ["node", "dist/index.js"]

结语
感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述

相关文章:

  • VR 航天科普,沉浸式体验宇宙奥秘​
  • iOS工厂模式
  • 基于 ZigBee 的 LED 路灯智能控制器的设计
  • 杨校老师竞赛课之青科赛GOC5-6年级组模拟题
  • ESP32-S3 使用SPI-TFT_eSPI与ST7789驱动通信
  • HTTP Digest 认证:原理剖析与服务端实现详解
  • Ubuntu下误删除分区的补救
  • 排序和排列——蓝桥杯备考
  • 供应链管理:联合国/我国 41个产业/工业大类包括什么/代表公司
  • 【C++】控制台小游戏
  • HarmonyOS NEXT~鸿蒙系统下的Cordova框架应用开发指南
  • 人工智能在工业自动化中的应用与未来趋势
  • 【IDEA问题】springboot本地启动应用报错:程序包不存在;找不到符号
  • Vue:axios(GET请求)
  • leetcode 148. Sort List
  • ThreadLocal线程本地变量在dubbo服务使用时候遇到的一个坑
  • 深入剖析 5G 核心网中的 PLMN
  • Android 直播播放器FFmpeg静态库编译实战指南(NDK r21b)
  • 彩礼的异化:婚姻市场中的资本规训与性别政治批判
  • 什么是VR实景?有哪些高价值场景?
  • 梵克雅宝官网中国官方网站/开网店怎么开 新手无货源
  • 杭州商标设计/seo服务 收费
  • 珠海网站建设哪个好薇/西安网站推广助理
  • 婚庆网站制作/网站如何快速被百度收录
  • 个人网站做交易类的赚钱吗/苏州网站建设费用
  • 如何自己做软件网站/营销方案怎么写?