跳转到内容

其他3.0

vue如何使用Jest测试

Vue + Jest 单元测试完整指南

一、环境配置

1.1 安装核心依赖

bash
npm install --save-dev jest @vue/test-utils vue-jest babel-jest @babel/core @babel/preset-env

1.2 配置文件

jest.config.js

javascript
module.exports = {
  moduleFileExtensions: ['js', 'json', 'vue'],
  transform: {
    '^.+\\.js$': 'babel-jest',
    '^.+\\.vue$': 'vue-jest'
  },
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  testMatch: [
    '**/tests/unit/**/*.spec.js'
  ],
  collectCoverage: true,
  coverageReporters: ['html', 'text-summary']
}

babel.config.js

javascript
module.exports = {
  presets: [
    ['@babel/preset-env', { targets: { node: 'current' } }]
  ]
}

二、组件测试基础

2.1 组件挂载测试

javascript
import { mount } from '@vue/test-utils'
import Button from '@/components/Button.vue'

describe('Button.vue', () => {
  test('renders button with correct text', () => {
    const wrapper = mount(Button, {
      propsData: {
        text: 'Click me'
      }
    })
  
    expect(wrapper.text()).toContain('Click me')
    expect(wrapper.classes()).toContain('btn-primary')
  })
})

2.2 Props 测试

javascript
test('accepts valid props', () => {
  const wrapper = mount(Button, {
    propsData: {
      text: 'Submit',
      type: 'submit',
      disabled: true
    }
  })
  
  expect(wrapper.attributes('type')).toBe('submit')
  expect(wrapper.attributes('disabled')).toBe('disabled')
})

三、交互测试

3.1 点击事件测试

javascript
test('emits click event when clicked', async () => {
  const wrapper = mount(Button)
  
  await wrapper.trigger('click')
  expect(wrapper.emitted().click).toBeTruthy()
  expect(wrapper.emitted().click.length).toBe(1)
})

3.2 表单输入测试

javascript
test('updates v-model on input', async () => {
  const wrapper = mount(InputField, {
    propsData: {
      value: 'initial'
    }
  })
  
  const input = wrapper.find('input')
  await input.setValue('new value')
  
  expect(wrapper.emitted().input[0]).toEqual(['new value'])
})

四、异步行为测试

4.1 API调用测试

javascript
import axios from 'axios'
import UserList from '@/components/UserList.vue'

jest.mock('axios')

test('fetches users on mount', async () => {
  const users = [{ id: 1, name: 'John' }]
  axios.get.mockResolvedValue({ data: users })
  
  const wrapper = mount(UserList)
  await wrapper.vm.$nextTick()
  
  expect(axios.get).toHaveBeenCalledWith('/api/users')
  expect(wrapper.vm.users).toEqual(users)
})

4.2 定时器测试

javascript
jest.useFakeTimers()

test('updates count after delay', () => {
  const wrapper = mount(DelayedCounter)
  
  wrapper.find('button').trigger('click')
  jest.runAllTimers()
  
  expect(wrapper.vm.count).toBe(1)
})

五、路由和Store测试

5.1 Vue Router测试

javascript
import { createLocalVue, mount } from '@vue/test-utils'
import VueRouter from 'vue-router'

const localVue = createLocalVue()
localVue.use(VueRouter)

test('navigates to about page', async () => {
  const router = new VueRouter({ routes })
  const wrapper = mount(NavBar, { localVue, router })
  
  await wrapper.find('[data-test="about-link"]').trigger('click')
  expect(router.currentRoute.path).toBe('/about')
})

5.2 Vuex测试

javascript
import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'

const localVue = createLocalVue()
localVue.use(Vuex)

test('commits mutation on button click', () => {
  const mutations = {
    increment: jest.fn()
  }
  const store = new Vuex.Store({ mutations })
  
  const wrapper = mount(Counter, { localVue, store })
  wrapper.find('button').trigger('click')
  
  expect(mutations.increment).toHaveBeenCalled()
})

六、快照测试

6.1 组件快照

javascript
test('matches snapshot', () => {
  const wrapper = mount(MyComponent)
  expect(wrapper.html()).toMatchSnapshot()
})

6.2 动态内容处理

javascript
test('matches snapshot with props', () => {
  const wrapper = mount(MyComponent, {
    propsData: {
      title: 'Dynamic Title'
    }
  })
  
  expect(wrapper.html()).toMatchSnapshot()
})

七、测试覆盖率

7.1 配置覆盖率收集

javascript
// jest.config.js
module.exports = {
  collectCoverageFrom: [
    'src/**/*.{js,vue}',
    '!src/main.js',
    '!src/router/index.js'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
}

7.2 查看报告

bash
npm test -- --coverage

生成的报告会显示:

  • 行覆盖率(Lines)
  • 函数覆盖率(Functions)
  • 分支覆盖率(Branches)
  • 语句覆盖率(Statements)

八、高级技巧

8.1 自定义匹配器

javascript
// setupTests.js
expect.extend({
  toBeBetween(received, floor, ceiling) {
    const pass = received >= floor && received <= ceiling
    return {
      message: () => `expected ${received} ${pass ? 'not ' : ''}to be between ${floor} and ${ceiling}`,
      pass
    }
  }
})

// 使用
test('price should be between 10 and 20', () => {
  expect(15).toBeBetween(10, 20)
})

8.2 组件扩展测试

javascript
test('extends base component', () => {
  const BaseComponent = {
    template: '<div><slot /></div>'
  }
  
  const wrapper = mount({
    extends: BaseComponent,
    template: '<div>Extended</div>'
  })
  
  expect(wrapper.text()).toBe('Extended')
})

九、常见问题解决

9.1 样式导入问题

javascript
// jest.config.js
moduleNameMapper: {
  '\\.(css|less|scss)$': 'identity-obj-proxy'
}

需要额外安装:

bash
npm install --save-dev identity-obj-proxy

9.2 第三方库未转换

javascript
// jest.config.js
transformIgnorePatterns: [
  '/node_modules/(?!lodash-es|other-es-lib)'
]

十、测试策略建议

  1. 测试重点

    • 组件渲染输出
    • 用户交互行为
    • Props验证
    • 自定义事件
    • 与Vuex/Router的集成
  2. 避免过度测试

    • 不测试第三方库
    • 不测试实现细节
    • 不追求100%覆盖率
  3. 最佳实践

通过以上配置和方法,可以构建健壮的Vue组件测试套件。建议将测试集成到CI/CD流程中,确保每次提交都通过测试验证。对于大型项目,可采用测试金字塔策略:70%单元测试,20%集成测试,10%E2E测试。

netlify如何实现自动构建部署,原理是什么

Netlify 的自动构建部署系统基于 Git 工作流和现代 CI/CD 理念,其核心原理可分为以下几个关键部分:

一、自动化构建部署流程原理

  1. Git 集成触发机制
  1. 关键技术组件
组件功能说明
构建探测器自动识别项目类型(React/Vue等)并选择最优构建命令
智能缓存系统持久化缓存node_modules等目录加速后续构建
原子化部署先完整构建再切换流量,避免半成品发布
边缘网络同步通过自有CDN在30秒内将资源分发到全球85+节点

二、具体实现机制

  1. 构建环境准备

容器配置示例

yaml
# 虚拟构建环境规格
os: Ubuntu 20.04
cpu: 2 cores
memory: 4GB
storage: 10GB (临时空间)
预装软件: Node.js, Python, Ruby, Go等

依赖缓存原理

bash
# 缓存目录结构
~/cache/
   ├── node_modules/  # npm依赖
   ├── .cache/        # 构建工具缓存(如Webpack)
   └── assets/        # 编译过的静态资源
  1. 构建过程优化

并行构建阶段

关键优化技术

  • 增量构建:通过git diff识别变更文件
  • 依赖预加载:利用package-lock.json哈希值匹配缓存
  • 构建管道:允许自定义build.lifecycle hooks
  1. 部署过程

原子发布流程

  1. 将构建产物打包为不可变的.deploy
  2. 生成唯一部署ID(如deploy-123)
  3. 同步到所有边缘节点后切换路由

零停机回滚

bash
# 回滚到指定版本
netlify deploy --prod --alias deploy-456

三、配置文件解析

  1. netlify.toml 示例
toml
[build]
  command = "npm run build"
  publish = "dist"
  functions = "src/functions"

[build.environment]
  NODE_VERSION = "16"
  API_KEY = "${{ secrets.API_KEY }}"

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
  1. 环境变量管理
bash
# 优先级顺序
1. UI设置的环境变量(加密存储)
2. netlify.toml中的变量
3. 构建系统默认变量(如DEPLOY_URL)

四、与其他平台的差异优势

特性Netlify传统CI/CD
配置复杂度零配置自动识别需要编写YAML/pipeline文件
部署速度平均45秒完成构建+部署通常2-5分钟
回滚机制一键回退到任意历史版本需要手动重新部署旧版本
预览环境每个PR自动生成独立URL需要额外配置
成本免费套餐包含100GB带宽通常按构建分钟计费

五、高级功能原理

  1. 即时预览(Deploy Previews)
  1. 边缘函数(Edge Functions)

部署架构

用户请求 -> 最近边缘节点 -> 
  ├── 静态资源: 直接返回
  └── 动态路由: 执行边缘函数(全球<50ms延迟)

函数示例

javascript
// geo-redirect.js
export default async (request) => {
  const country = request.headers.get('x-country')
  if (country === 'JP') {
    return Response.redirect('/ja', 302)
  }
  return new Response('Hello World')
}

六、性能数据参考

典型构建时间

项目规模冷启动构建热缓存构建
小型(1-10文件)90s25s
中型(SPA应用)150s60s
大型(100+文件)300s120s

全球CDN性能

  • 首字节时间(TTFB): <100ms
  • 静态资源加载: <500ms(全球平均)
  • 缓存命中率: 98.5%

七、最佳实践建议

  1. 缓存策略

    toml
    [[headers]]
      for = "/assets/*"
      [headers.values]
        Cache-Control = "public, max-age=31536000, immutable"
  2. 构建优化

    bash
    # 跳过不必要的步骤
    NETLIFY_USE_YARN=1  # 强制使用yarn加速安装
  3. 监控集成

    javascript
    // 注入性能监控
    if (process.env.CONTEXT === 'production') {
      import('./analytics.js')
    }

Netlify 的自动化构建部署系统通过深度 Git 集成、智能缓存管理和全球边缘网络,实现了从代码提交到生产部署的无缝流水线。其核心技术优势在于将复杂的 CI/CD 流程抽象为开发者友好的简单操作,同时保持企业级的安全性和可靠性。

Netlify为什么不会处理单页面应用的非首页路由,原理是什么

Netlify 对单页面应用(SPA)非首页路由的特殊处理机制源于其静态资源托管的核心设计理念,其原理和工作方式如下:

一、问题本质:静态托管 vs 动态路由

  1. 传统静态网站行为
请求路径         服务器行为
/             → 返回 index.html
/about        → 尝试返回 about.html
  1. SPA 特殊需求
所有路由路径都应返回 index.html
由前端路由库(如vue-router)处理实际导航

二、Netlify 的默认处理原理

  1. 静态资源匹配规则
  • 精确匹配优先:当请求 /about 时:
    1. 先查找是否存在 about.htmlabout/index.html
    2. 若不存在 → 返回 404 错误
  1. 与 SPA 需求的冲突

三、解决方案技术实现

  1. 重定向规则配置

_redirects 文件方案

/*    /index.html   200

netlify.toml 配置:

toml
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200
  1. 底层工作原理

四、技术细节深度解析

  1. 重定向规则类型对比
状态码语义浏览器行为SPA适用性
301永久重定向更新地址栏不适用
302临时重定向更新地址栏不适用
200重写(rewrite)保持原URL完美适用
404未找到显示错误页不适用
  1. 缓存行为影响
  • 200重写:CDN会缓存index.html但保留原始URL
  • 无需担心:因为SPA的index.html通常很小(~1KB)
  1. 性能开销分析
方案额外延迟CDN友好度实现复杂度
默认4040ms高(需配置)
200重写<1ms
服务端渲染(SSR)100-300ms

五、现代框架的自动适配

  1. 框架内置配置
  • Create React App/Vue CLI:构建时自动生成_redirects文件
  • Next.js/Nuxt.js:根据输出模式自动配置(静态导出时需特殊处理)
  1. 典型框架处理
javascript
// vue-router 示例
const router = createRouter({
  history: createWebHistory(),
  routes // 前端控制路由匹配
})

// 依赖Netlify返回index.html才能生效

六、高级场景解决方案

  1. 混合路由场景
toml
# netlify.toml
[[redirects]]
  from = "/api/*"
  to = "https://api.example.com/:splat"
  status = 200

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200
  1. 自定义404页面
html
<!-- public/404.html -->
<script>
  sessionStorage.setItem('redirect', location.pathname)
  location.replace('/?redirect=' + encodeURIComponent(location.pathname))
</script>

七、为什么Netlify不默认处理SPA路由?

  1. 设计哲学

    • 保持静态托管的纯粹性
    • 避免对特殊用例做假设
  2. 技术考量

    • 部分传统网站需要真实的404行为
    • 允许更灵活的路由控制(如部分路由需要后端处理)
  3. 性能权衡

    • 无条件返回index.html会增加边缘节点计算开销
    • 精确匹配静态文件更符合CDN最佳实践

最佳实践建议

  1. 始终显式配置重定向

    bash
    # 对于Vue/React项目
    echo "/* /index.html 200" > public/_redirects
  2. 测试方法

    bash
    curl -I https://yoursite.com/non-existent-route
    # 应返回200而非404
  3. 与History模式配合

    javascript
    // vue-router配置
    const router = createRouter({
      history: createWebHistory(),
      routes: [...]
    })

通过理解这些机制,开发者可以更好地利用Netlify部署SPA应用,同时保持对路由行为的完全控制。

如何用qiankun实现微前端项目

使用 qiankun 实现微前端的完整指南

一、基础架构搭建

1.1 主应用配置

安装依赖

bash
npm install qiankun -S

主应用初始化(Vue示例)

javascript
// main.js
import { registerMicroApps, start } from 'qiankun';

const apps = [
  {
    name: 'vue-app', // 微应用名称
    entry: '//localhost:7101', // 微应用入口
    container: '#subapp-container', // 容器ID
    activeRule: '/vue', // 激活路由
    props: { // 传递给微应用的props
      authToken: 'main-app-token'
    }
  },
  {
    name: 'react-app',
    entry: '//localhost:7102',
    container: '#subapp-container',
    activeRule: '/react',
  }
];

registerMicroApps(apps);

// 启动qiankun
start({
  sandbox: {
    strictStyleIsolation: true // 严格的样式隔离
  },
  prefetch: 'all' // 预加载策略
});

1.2 微应用改造

Vue微应用配置

javascript
// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

// main.js
import './public-path';
import Vue from 'vue';
import App from './App.vue';
import router from './router';

let instance = null;

function render(props = {}) {
  const { container } = props;
  instance = new Vue({
    router,
    render: h => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
}

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

// qiankun生命周期钩子
export async function bootstrap() {
  console.log('[vue] app bootstraped');
}

export async function mount(props) {
  console.log('[vue] props from main framework', props);
  render(props);
}

export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
}

Webpack配置调整

javascript
// vue.config.js
module.exports = {
  devServer: {
    port: 7101,
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  },
  configureWebpack: {
    output: {
      library: `vue-app`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${name}`,
    }
  }
}

二、核心功能实现

2.1 路由系统集成

主应用路由配置

javascript
// 主应用router.js
const routes = [
  {
    path: '/vue/*',
    name: 'vue-app',
    component: () => import('@/components/MicroContainer.vue')
  },
  {
    path: '/react/*',
    name: 'react-app',
    component: () => import('@/components/MicroContainer.vue')
  }
]

微应用路由适配

javascript
// 微应用router.js
const router = new VueRouter({
  base: window.__POWERED_BY_QIANKUN__ ? '/vue/' : '/',
  mode: 'history',
  routes
})

2.2 样式隔离方案

qiankun提供的隔离方式

javascript
start({
  sandbox: {
    strictStyleIsolation: true, // Shadow DOM隔离
    experimentalStyleIsolation: true // 动态样式表隔离
  }
})

手动处理冲突

css
/* 微应用中使用scoped */
.app-container {
  /* 添加应用前缀 */
}

三、高级功能实现

3.1 应用间通信

使用initGlobalState

javascript
// 主应用初始化state
import { initGlobalState } from 'qiankun';

const actions = initGlobalState({
  user: { name: 'admin' },
  token: 'xxxx'
});

// 主应用监听变化
actions.onGlobalStateChange((state, prev) => {
  console.log('全局状态变更:', state, prev);
});

// 微应用获取通信方法
export function mount(props) {
  props.onGlobalStateChange((state, prev) => {
    // 响应状态变化
  });
  
  props.setGlobalState({ 
    token: 'new-token' 
  });
}

3.2 资源预加载

javascript
// 主应用启动配置
start({
  prefetch: 'all' // 可选值: true | 'all' | string[] | function
});

// 手动预加载
import { prefetchApps } from 'qiankun';

prefetchApps([
  { name: 'vue-app', entry: '//localhost:7101' },
  { name: 'react-app', entry: '//localhost:7102' }
]);

四、部署方案

4.1 生产环境配置

微应用独立部署

https://cdn.example.com/vue-app/
https://cdn.example.com/react-app/

主应用entry配置

javascript
const apps = [
  {
    name: 'vue-app',
    entry: process.env.NODE_ENV === 'production' 
      ? 'https://cdn.example.com/vue-app/' 
      : '//localhost:7101',
    // ...其他配置
  }
]

4.2 Nginx反向代理

nginx
server {
    listen 80;
    server_name main-app.com;
  
    location / {
        root /path/to/main-app;
        try_files $uri $uri/ /index.html;
    }
  
    location /vue-app/ {
        proxy_pass https://cdn.example.com/vue-app/;
        proxy_set_header Host $host;
    }
}

五、常见问题解决方案

5.1 静态资源加载问题

解决方案

javascript
// 微应用webpack配置
output: {
  publicPath: process.env.NODE_ENV === 'production' 
    ? 'https://cdn.example.com/vue-app/' 
    : '/'
}

5.2 跨域问题处理

开发环境配置

javascript
// 微应用devServer
devServer: {
  headers: {
    'Access-Control-Allow-Origin': '*'
  }
}

5.3 第三方脚本冲突

解决方案

javascript
// 主应用start配置
start({
  sandbox: {
    excludeAssetFilter: url => {
      // 排除特定脚本的沙箱隔离
      return url.includes('jquery.js');
    }
  }
})

六、最佳实践建议

  1. 版本管理策略

    • 主应用与微应用独立发布
    • 建立版本兼容矩阵
  2. 性能优化

    javascript
    // 按需加载微应用
    registerMicroApps([...], {
      beforeLoad: app => {
        console.log('before load', app.name);
        return Promise.resolve();
      }
    });
  3. 错误监控

    javascript
    import { addErrorHandler } from 'qiankun';
    
    addErrorHandler(error => {
      console.error('微应用错误:', error);
      // 上报错误到监控系统
    });
  4. 测试策略

    • 主应用集成测试
    • 微应用独立测试
    • 联合E2E测试

七、完整技术栈示例

组成部分推荐技术方案
主应用框架Vue 3 + Vue Router 4
微应用框架Vue 2/3, React 16+, Angular
构建工具Webpack 5 + Module Federation
CI/CDJenkins + Docker
监控系统Sentry + ELK

通过以上配置和方案,可以实现一个功能完善的企业级微前端架构。qiankun的核心优势在于:

  1. 技术栈无关:支持多种前端框架混合使用
  2. 样式隔离:完善的沙箱机制避免冲突
  3. 简单易用:API设计简洁,接入成本低
  4. 生产就绪:蚂蚁金服大规模验证

实际项目中建议根据团队规模和技术栈选择合适的微应用拆分粒度,通常按照业务域划分微应用边界最为合理。