自动化构建
构建工具的选择
从目前的实际业务角度去选择:
- UI库可以选择
rollup - 小型项目可以选择
vite - 大型项目可以选择
webpack vite - 自己的项目也可以选择
turbopack、rspack、swc等等 - 对于需要快速原型开发的项目,可以考虑使用Parcel,它的零配置特性非常适合快速启动项目。
- 对于大型单页应用(SPA),Next.js或Nuxt.js这样的框架也值得考虑,它们提供了开箱即用的构建优化。
确定构建的流程
需要一些什么样的
loader、plugin?你所用的
plugin、loader可以反映出你所设计的技术选型(css in js 、tailwind...)举例:
- 如果我需要二进制文件的加载或者拷贝,我应该使用?(file-loader、url-loader、copy-webpack-plugin)
- 如果我需要将静态资源打包到cdn,我应该使用?(cdn-loader、cdn-plugin)
- 考虑使用babel-loader进行JavaScript转译,以支持新的语言特性。
- 对于CSS,可以考虑使用PostCSS和Autoprefixer来处理浏览器兼容性问题。
确定构建产物输出
我们是需要单
chunk输出,还是多chunk输出?是否需要生成
sourcemap以便调试。是否需要生成压缩和非压缩版本的文件。
优化打包构建流程
优化打包体积? 使用bundle分析工具(如webpack-bundle-analyzer)来可视化包大小,找出可优化的地方。
- 代码拆分(Code Splitting)
- 描述:将代码分割成多个小块,按需加载。提取不怎么变动的公共模块(vendor chunk)
- 工具:Webpack 的 splitChunks、Rollup 的 output.splitting。
- 应用场景:适用于首屏加载时间要求较高的应用。
- 树摇(Tree Shaking)
- 描述:通过静态代码分析,去除无用的代码。
- 工具:Webpack 支持 ES6 模块的 Tree Shaking、Rollup 的 Tree Shaking。
- 应用场景:减少 commonjs 库的使用,尽可能使用 esm 的库,适用于使用 ES6 模块的应用。
- 压缩代码
- 描述:压缩代码,去除空白、注释等。
- 工具:Terser、UglifyJS、Rollup 的 terser 插件
- 应用场景:适用于所有构建场景,尤其是生产环境
- 按需加载(Lazy Loading)
- 描述:仅在需要时加载代码,而不是一开始就加载所有代码。
- 工具:Webpack 的 import()、React 的 React.lazy。
- 应用场景:适用于路由较多、功能复杂的单页应用(SPA)。
提升打包构建速度?
- 使用增量构建
- 描述:增量构建仅打包发生变化的文件,而不是每次都重新打包整个项目。
- 工具:大多数现代构建工具(如 Webpack、Rollup)支持增量构建。
- 应用场景:适用于开发阶段,当文件发生改动时快速反馈。
- 缓存机制
- 描述:缓存编译结果,避免重复编译相同的代码。
- 工具:Webpack 配置cache属性,使用dll plugin,Rollup 的 --watch 模式
- 应用场景:适用于所有构建场景,尤其是在频繁修改代码的情况下
- 优化依赖解析
- 描述:减少解析模块的时间,特别是在模块数量较多时。
- 工具:Webpack 的 resolve.alias、resolve.modules。
- 应用场景:适用于大型项目,模块依赖关系复杂的情况
- 使用并行构建(多线程)
- 描述:利用多核 CPU 并行执行构建任务
- 工具:Webpack 的 thread-loader、happy-pack(老项目) 插件
- 应用场景:适用于具有大量计算密集型任务的项目
- 优化加载路径
- 描述:减少加载路径,避免不必要的文件查找
- 工具:Webpack 可以使用 externals、Rollup 的 external,配合cdn引入来去除部分模块。
- 应用场景:适用于外部依赖项不需要打包的情况
- 云端构建
- 描述:借助turbo,rspack等构建工具,将构建任务提交到云端,由云端进行构建,减少本地构建压力。
- 工具:turbo、rspack、swc等构建工具。
- 应用场景:适用于需要构建速度和资源占用低,但需要构建结果的一致性,如 CI/CD 等场景。
一些vscode插件推荐
- Version Lens: 显示当前文件版本号,方便对比版本差异。
自动化测试
概念
为什么需要测试
测试一定是保证程序稳定健壮的手段
但是在我们平常的工作中,测试很难落地:
- 频繁变动的需求(产品)
- 人力成本(工时)
- 上层不重视(沟通)
单元测试的原理
通过确定测试用例,来编写测试代码,保证测试用例通过
//test.mjs
import chalk from 'chalk'
//一般测试框架(jest)需要做的事情
//描述测试场景
export function describe(desc, fn) {
console.log(chalk.red(desc))
fn()
}
//单元用例测试描述
export function it(desc, fn) {
console.log(chalk.blue(desc));
fn()
}
//断言库,做比对
export function expect(actual) {
return {
toBe(expected) {
if (actual !== expected) {
console.log(chalk.red(`预期和实际不相符,预期的值:${actual} ,实际的值:${expected}`));
throw new Error(`预期和实际不相符,预期的值:${actual} ,实际的值:${expected}`)
}
console.log(chalk.green('测试通过'));
}
}
}
//__home__.test.mjs
import { describe, it, expect } from './test.mjs'
function sum(a, b) {
return a + b
}
//测试用例
describe('测试 home 模块', () => {
it('测试 sum 函数', () => {
expect(sum(1, 1)).toBe(2)
expect(sum(1, 2)).toBe(3)
expect(sum(1, 3)).toBe(4)
expect(sum(1, 4)).toBe(9)
})
})测试分类
- 单元测试(Unit Test),我们可以理解为小的功能测试
- 集成测试(Integration Test),我们可以理解为小的模块测试
- UI 测试(UI Test),我们可以理解为从页面出发去测试整体功能(仅前端)
- E2E 测试(UI + 接口)
测试框架
- 测试框架:Jest、@vue/test-utils、Mocha、Jesmine、Tape、Tyu、Ava
- 测试运行工具:Karam
- 断言库:Assert、Should、Chai
- 测试辅助工具:Sinon
- 测试覆盖率工具:Istanbul
- 无头浏览器:puppeteer
Jest
常用API
describe(name, fn): 用于组织相关的测试用例。it(name, fn)或test(name, fn): 定义一个测试用例。expect(value): 创建一个断言。beforeEach(fn)和afterEach(fn): 在每个测试用例之前和之后运行的函数。beforeAll(fn)和afterAll(fn): 在所有测试用例之前和之后运行的函数。jest.fn(): 创建一个模拟函数。jest.spyOn(object, methodName): 创建一个对象方法的间谍。jest.mock(moduleName): 模拟一个模块。toBe(value): 断言值是否等于给定的值。toEqual(value): 断言值是否等于给定的值,并且类型也相同。
示例
假设我们有一个简单的计算器模块:
// calculator.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export function multiply(a, b) {
return a * b;
}
export function divide(a, b) {
if (b === 0) throw new Error("Cannot divide by zero");
return a / b;
}现在,让我们为这个计算器模块编写测试:
// calculator.test.js
import * as calculator from './calculator';
describe('Calculator', () => {
beforeAll(() => {
console.log('Starting Calculator tests');
});
afterAll(() => {
console.log('Finished Calculator tests');
});
beforeEach(() => {
jest.spyOn(console, 'log');
});
afterEach(() => {
console.log.mockRestore();
});
it('adds two numbers correctly', () => {
expect(calculator.add(2, 3)).toBe(5);
});
it('subtracts two numbers correctly', () => {
expect(calculator.subtract(5, 3)).toBe(2);
});
it('multiplies two numbers correctly', () => {
expect(calculator.multiply(2, 3)).toBe(6);
});
describe('divide', () => {
it('divides two numbers correctly', () => {
expect(calculator.divide(6, 2)).toBe(3);
});
it('throws an error when dividing by zero', () => {
expect(() => calculator.divide(6, 0)).toThrow('Cannot divide by zero');
});
});
it('logs a message', () => {
console.log('This is a test log');
expect(console.log).toHaveBeenCalledWith('This is a test log');
});
it('uses a mock function', () => {
const mockFn = jest.fn();
mockFn();
expect(mockFn).toHaveBeenCalled();
});
});持续集成 / 持续部署(CI/CD)
CI/CD 是一套自动化工具和技术,它可以帮助你自动化地构建、测试和部署你的应用程序。简单来说,CI/CD 可以分为两个部分:
- 持续集成(Continuous Integration):每次代码有新的提交,就会自动构建并测试代码,确保代码的质量。
- 持续部署(Continuous Deployment/Delivery):构建通过后,自动部署到生产环境或测试环境。
常见的 CI/CD 工具和服务包括 Jenkins、Travis CI、GitLab CI、GitHub Actions 等。
GitHub Actions
GitHub Actions 是 GitHub 提供的 CI/CD 服务,它允许你定义在代码仓库中发生特定事件时运行的任务。
详细步骤
创建 .github/workflows 目录 在你的仓库根目录下创建一个名为
.github/workflows的目录。这个目录是用来存放 GitHub Actions 的工作流文件的。编写工作流文件 在
.github/workflows目录下创建一个.yml文件,例如ci.yml。这个文件将描述工作流的各个步骤。 下面是一个简单的示例,演示如何设置一个简单的 CI/CD 流程:
name: CI/CD Pipeline
on:
push:
branches:
- main # 或者你希望触发的分支名称
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: 16 # 或者你希望使用的 Node.js 版本
- name: Install dependencies
run: |
npm install
- name: Build project
run: |
npm run build
- name: Deploy to production server
uses: appleboy/ssh-action@master
with:
host: your-production-server-host
username: your-username
password: your-password
port: 22
script: |
cd /path/to/your/deployment/directory
rsync -avz --delete ./* ${{ steps.deploy.outputs.user }}@$SERVER_IP:/path/to/your/deployment/directory- 解释工作流文件
name: 工作流的名称。on: 触发工作流的事件。在这个例子中,当有新的代码推送到 main 分支时,工作流会被触发。jobs: 定义的工作流中的任务。runs-on: 执行任务的操作系统环境。steps: 每个任务包含的具体步骤。
测试工作流
提交更改:将 .yml 文件提交到仓库。
查看工作流状态:前往 GitHub 仓库的 "Actions" 标签页,可以看到工作流的状态和输出。
自动化部署 在上面的例子中,我们使用了
appleboy/ssh-action来通过 SSH 连接到生产服务器,并将构建好的代码部署到服务器上。你需要替换相应的变量,例如host、username、password和script。
进阶技巧
- 环境变量:你可以使用 GitHub 的 Secret 功能来存储敏感信息,如数据库密码等。
- 并行执行:你可以设置多个任务并行执行,以加快构建速度。
- 条件执行:根据不同的条件执行不同的任务。
- 使用 Docker 部署 如果你想使用 Docker 来部署你的应用,可以修改工作流文件如下:
name: CI/CD Pipeline
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker
run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- name: Build and push Docker image
run: |
docker build -t your-docker-image-name .
docker push your-docker-image-name
- name: Deploy to production server
uses: appleboy/ssh-action@master
with:
host: your-production-server-host
username: your-username
password: your-password
port: 22
script: |
docker pull your-docker-image-name
docker stop your-service-container
docker rm your-service-container
docker run -d --name your-service-container your-docker-image-name如何生成一个新的 SSH 密钥对用于 GitHub Action
这个过程主要在你的本地计算机上完成,使用命令行工具。以下是详细步骤:
打开终端(在 Windows 上,你可以使用 Git Bash 或 Windows Subsystem for Linux)。
确保你当前不在任何 Git 仓库目录内(可以用 cd ~ 命令回到你的主目录)。
运行以下命令来生成新的 SSH 密钥对:
ssh-keygen -t rsa -b 4096 -C "actions@github.com" -f gh-pages-deploy这个命令的含义是:
- -t rsa: 指定密钥类型为 RSA
- -b 4096: 指定密钥长度为 4096 位
- -C "actions@github.com": 添加一个注释,帮助识别这个密钥的用途
- -f gh-pages-deploy: 指定输出文件名为 gh-pages-deploy
当提示输入密码时,直接按回车跳过。我们不需要为这个密钥设置密码。
命令执行完毕后,在当前目录下会生成两个文件:
- gh-pages-deploy:这是私钥文件
- gh-pages-deploy.pub:这是公钥文件
- 你可以用以下命令查看生成的文件
ls -l gh-pages-deploy*
dir gh-pages-deploy* //windows cmd- 查看公钥内容(后面会用到):
cat gh-pages-deploy.pub
type gh-pages-deploy.pub //windows cmd- 查看私钥内容(后面会用到):
cat gh-pages-deploy
type gh-pages-deploy //windows cmd现在你已经成功生成了一个新的 SSH 密钥对。公钥(.pub 文件)将被添加到 liuhuakawaii.github.io 仓库的部署密钥中,而私钥将被添加到 EnlightenCode 仓库的 secrets 中。
记住,这个密钥对是专门为 GitHub Actions 创建的,不要用于其他目的,也不要与他人分享私钥。
完成这些步骤后,你就可以继续设置 GitHub 仓库了,如我之前描述的那样:
将公钥(gh-pages-deploy.pub 的内容)添加到 liuhuakawaii.github.io 仓库的部署密钥中。
将私钥(gh-pages-deploy 的内容)添加到 EnlightenCode 仓库的 secrets 中,名称为 ACTIONS_DEPLOY_KEY。
这样,GitHub Actions 就能安全地访问和更新你的 liuhuakawaii.github.io 仓库了。