金九银十,带你复盘大厂常问的项目难点(二)

简介: 金九银十,带你复盘大厂常问的项目难点

在qiankun中,如果实现组件在不同项目间的共享,有哪些解决方案?


在项目间共享组件时,可以考虑以下几种方式:

  1. 父子项目间的组件共享:主项目加载时,将组件挂载到全局对象(如window)上,在子项目中直接注册使用该组件。
  2. 子项目间的组件共享(弱依赖):通过主项目提供的全局变量,子项目挂载到全局对象上。子项目中的共享组件可以使用异步组件来实现,在加载组件前先检查全局对象中是否存在,存在则复用,否则加载组件。
  3. 子项目间的组件共享(强依赖):在主项目中通过loadMicroApp手动加载提供组件的子项目,确保先加载该子项目。在加载时,将组件挂载到全局对象上,并将loadMicroApp函数传递给子项目。子项目在需要使用共享组件的地方,手动加载提供组件的子项目,等待加载完成后即可获取组件。

需要注意的是,在使用异步组件或手动加载子项目时,可能会遇到样式加载的问题,可以尝试解决该问题。另外,如果共享的组件依赖全局插件(如storei18n),需要进行特殊处理以确保插件的正确初始化。


在qiankun中,应用之间如何复用依赖,除了npm包方案外?


  1. 在使用webpack构建的子项目中,要实现复用公共依赖,需要配置webpackexternals,将公共依赖指定为外部依赖,不打包进子项目的代码中。
  2. 子项目之间的依赖复用可以通过保证依赖的URL一致来实现。如果多个子项目都使用同一份CDN文件,加载时会先从缓存读取,避免重复加载。
  3. 子项目复用主项目的依赖可以通过给子项目的index.html中的公共依赖的scriptlink标签添加自定义属性ignore来实现。在qiankun运行子项目时,qiankun会忽略这些带有ignore属性的依赖,子项目独立运行时仍然可以加载这些依赖。
  4. 在使用qiankun微前端框架时,可能会出现子项目之间和主项目之间的全局变量冲突的问题。这是因为子项目不配置externals时,子项目的全局Vue变量不属于window对象,而qiankun在运行子项目时会先找子项目的window,再找父项目的window,导致全局变量冲突。
  5. 解决全局变量冲突的方案有三种:
  • 方案一是在注册子项目时,在beforeLoad钩子函数中处理全局变量,将子项目的全局Vue变量进行替换,以解决子项目独立运行时的全局变量冲突问题。
  • 方案二是通过主项目将依赖通过props传递给子项目,子项目在独立运行时使用传递过来的依赖,避免与主项目的全局变量冲突。
  • 方案三是修改主项目和子项目的依赖名称,使它们不会相互冲突,从而避免全局变量冲突的问题。


说说webpack5联邦模块在微前端的应用


Webpack 5 的联邦模块(Federation Module)是一个功能强大的特性,可以在微前端应用中实现模块共享和动态加载,从而提供更好的代码复用和可扩展性

1. 模块共享

Webpack 5 的联邦模块允许不同的微前端应用之间共享模块,避免重复加载和代码冗余。通过联邦模块,我们可以将一些公共的模块抽离成一个独立的模块,并在各个微前端应用中进行引用。这样可以节省资源,并提高应用的加载速度。

// main-app webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
  // ...其他配置
  plugins: [
    new HtmlWebpackPlugin(),
    new ModuleFederationPlugin({
      name: 'main_app',
      remotes: {
        shared_module: 'shared_module@http://localhost:8081/remoteEntry.js',
      },
    }),
  ],
};
// shared-module webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
  // ...其他配置
  plugins: [
    new ModuleFederationPlugin({
      name: 'shared_module',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/components/Button',
      },
    }),
  ],
};

在上述示例中,main-appshared-module 分别是两个微前端应用的 webpack 配置文件。通过 ModuleFederationPlugin 插件,shared-moduleButton 组件暴露给其他应用使用,而 main-app 则通过 remotes 配置引入了 shared-module

2. 动态加载

Webpack 5 联邦模块还支持动态加载模块,这对于微前端应用的按需加载和性能优化非常有用。通过动态加载,可以在需要时动态地加载远程模块,而不是在应用初始化时一次性加载所有模块。

// main-app
const remoteModule = () => import('shared_module/Button');
// ...其他代码
// 在需要的时候动态加载模块
remoteModule().then((module) => {
  // 使用加载的模块
  const Button = module.default;
  // ...
});

在上述示例中,main-app 使用 import() 函数动态加载 shared_module 中的 Button 组件。通过动态加载,可以在需要时异步地加载远程模块,并在加载完成后使用模块。

在微前端应用中可以实现模块共享和动态加载,提供了更好的代码复用和可扩展性。通过模块共享,可以避免重复加载和代码冗余,而动态加载则可以按需加载模块,提高应用的性能和用户体验。


说说qiankun的资源加载机制(import-html-entry)


qiankun import-html-entry 是qiankun 框架中用于加载子应用的 HTML 入口文件的工具函数。它提供了一种方便的方式来动态加载和解析子应用的 HTML 入口文件,并返回一个可以加载子应用的 JavaScript 模块。

具体而言,import-html-entry 实现了以下功能:

  1. 加载 HTML 入口文件:import-html-entry 会通过创建一个 <link> 标签来加载子应用的 HTML 入口文件。这样可以确保子应用的资源得到正确加载,并在加载完成后进行处理。
  2. 解析 HTML 入口文件:一旦 HTML 入口文件加载完成,import-html-entry 将解析该文件的内容,提取出子应用的 JavaScript 和 CSS 资源的 URL。
  3. 动态加载 JavaScript 和 CSS 资源:import-html-entry 使用动态创建 <script><link> 标签的方式,按照正确的顺序加载子应用的 JavaScript 和 CSS 资源。
  4. 创建沙箱环境:在加载子应用的 JavaScript 资源时,import-html-entry 会创建一个沙箱环境(sandbox),用于隔离子应用的全局变量和运行环境,防止子应用之间的冲突和污染。
  5. 返回子应用的入口模块:最后,import-html-entry 返回一个可以加载子应用的 JavaScript 模块。这个模块通常是一个包含子应用初始化代码的函数,可以在主应用中调用以加载和启动子应用。

通过使用 qiankun import-html-entry,开发者可以方便地将子应用的 HTML 入口文件作为模块加载,并获得一个可以加载和启动子应用的函数,简化了子应用的加载和集成过程。


说说现有的几种微前端框架,它们的优缺点?


以下是对各个微前端框架优缺点的总结:

  1. qiankun 方案优点
  • 降低了应用改造的成本,通过html entry的方式引入子应用;
  • 提供了完备的沙箱方案,包括js沙箱和css沙箱;
  • 支持静态资源预加载能力。
  1. 缺点
  • 适配成本较高,包括工程化、生命周期、静态资源路径、路由等方面的适配;
  • css沙箱的严格隔离可能引发问题,js沙箱在某些场景下执行性能下降;
  • 无法同时激活多个子应用,不支持子应用保活;
  • 不支持vite等esmodule脚本运行。
  1. micro-app 方案优点
  • 使用 webcomponent 加载子应用,更优雅;
  • 复用经过大量项目验证过的 qiankun 沙箱机制,提高了框架的可靠性;
  • 支持子应用保活;
  • 降低了子应用改造的成本,提供了静态资源预加载能力。
  1. 缺点
  • 接入成本虽然降低,但路由依然存在依赖;
  • 多应用激活后无法保持各子应用的路由状态,刷新后全部丢失;
  • css 沙箱无法完全隔离,js 沙箱做全局变量查找缓存,性能有所优化;
  • 支持 vite 运行,但必须使用 plugin 改造子应用,且 js 代码没办法做沙箱隔离;
  • 对于不支持 webcomponent 的浏览器没有做降级处理。
  1. EMP 方案优点
  • webpack 联邦编译可以保证所有子应用依赖解耦;
  • 支持应用间去中心化的调用、共享模块;
  • 支持模块远程 ts 支持。
  1. 缺点
  • 对 webpack 强依赖,对于老旧项目不友好;
  • 没有有效的 css 沙箱和 js 沙箱,需要靠用户自觉;
  • 子应用保活、多应用激活无法实现;
  • 主、子应用的路由可能发生冲突。
  1. 无界方案优点
  • 基于 webcomponent 容器和 iframe 沙箱,充分解决了适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite框架支持、应用共享等问题。
  1. 缺点
  • 在继承了iframe优点的同时,缺点依旧还是存在


组件库


为什么需要二次封装组件库?


实际工作中,我们在项目中需要自定义主题色更改按钮样式自定义图标,自定义table组件等等,这些都可以基于antd组件库进行二次封装,减少重复工作,提升开发效率。

所以我们在封装的时候按照下面这四个原则进行思考就行了,另外本身封装组件库对于项目来说也是没有任何风险,因为一开始我们把PropsType直接进行转发,内部再进行增加业务的功能,这样就是达到完全的解耦

  • 统一风格:在一个大的项目或者多个相关的项目中,保持一致的界面风格和交互方式是非常重要的。通过二次封装,我们可以定义统一的样式和行为,减少不一致性。
  • 降低维护成本:当底层的组件库更新时,我们可能需要在项目的多个地方进行修改。但是如果我们有了自己的封装,只需要在封装层面进行更新即可,这大大降低了维护成本。
  • 增加定制功能:有些时候,我们需要在原有组件库的基础上增加一些特定的功能,如特定的验证、错误处理等。二次封装提供了这样的可能。
  • 提高开发效率:在一些常用的功能(如表单验证、全局提示等)上,二次封装可以提供更方便的API,提高开发效率。


请结合一个组件库设计的过程,谈谈前端工程化的思想


当我们结合一个组件库设计的过程来谈论前端工程化的思想时,需要理清这些要点:


1. 使用 Lerna 进行多包管理:通过 Lerna 来管理多个包(组件),实现组件级别的解耦、独立版本控制、按需加载等特性。


# 安装 Lerna
npm install -g lerna
# 初始化一个 Lerna 仓库
lerna init
# 创建 "Button" 组件包
lerna create button --yes


2. 规范化提交:使用规范化的提交信息可以提高 Git 日志的可读性,并且可以通过 conventional commits 自动生成 CHANGELOG。可以使用 commitizen、commitlint 等工具来配置。


# 安装相关工具
npm install commitizen cz-conventional-changelog --save-dev
// package.json
{
  "scripts": {
    "commit": "git-cz"
  },
  "config": {
    "commitizen": {
      "path": "cz-conventional-changelog"
    }
  }
}


3. 代码规范化:通过 ESLint、Prettier 等工具实现代码规范化和格式化,并封装为自己的规范预设。


# 安装相关工具
npm install eslint prettier eslint-plugin-prettier eslint-config-prettier --save-dev
// .eslintrc.js
module.exports = {
  extends: ['eslint:recommended', 'plugin:prettier/recommended'],
};
// .prettierrc.js
module.exports = {
  singleQuote: true,
  trailingComma: 'es5',
};


4. 组件开发调试:需要考虑热更新编译、软链接引用等问题,以方便在开发过程中进行组件的调试。


// packages/button/src/Button.js
import React from 'react';
const Button = ({ type = 'primary', onClick, children }) => {
  return (
    <button className={`button ${type}`} onClick={onClick}>
      {children}
    </button>
  );
};
export default Button;


5. 文档站点:可以基于 dumi 搭建文档站点,并实现 CDN 加速、增量发布等优化。可以使用 surge 实现 PR 预览。


<!-- packages/button/docs/index.md -->
# Button
A simple button component.
## Usage
import { Button } from 'button-library';
const MyComponent = () => {
  return <Button onClick={() => alert('Button clicked!')}>Click Me</Button>;
};
### Props
| Name     | Type                   | Default | Description                   |
| -------- | ---------------------- | ------- | ----------------------------- |
| type     | `primary` \| `secondary` | `primary` | The type of the button. |
| onClick  | `function`             |         | Event handler for click event. |


6. 单元测试:需要考虑 jest、enzyme 等工具的配合使用,生成测试覆盖率报告。

# 安装相关工具
npm install jest enzyme enzyme-adapter-react-16 react-test-renderer --save-dev
// packages/button/src/Button.test.js
import React from 'react';
import { mount } from 'enzyme';
import Button from './Button';
describe('Button', () => {
  it('renders without crashing', () => {
    const wrapper = mount(<Button>Click Me</Button>);
    expect(wrapper.exists()).toBe(true);
  });
  it('calls onClick function when clicked', () => {
    const onClickMock = jest.fn();
    const wrapper = mount(<Button onClick={onClickMock}>Click Me</Button>);
    wrapper.find('button').simulate('click');
    expect(onClickMock).toHaveBeenCalledTimes(1);
  });
});


7. 按需加载:需要配合 babel-plugin-import 实现按需加载,即在编译时修改导入路径来实现组件的按需加载。


# 安装相关工具
npm install babel-plugin-import --save-dev
// .babelrc
{
  "plugins": [
    [
      "import",
      {
        "libraryName": "button-library",
        "style": "css"
      }
    ]
  ]
}


8. 组件设计:需要考虑响应式、主题、国际化、TypeScript 支持等问题,以保证组件的灵活性和可扩展性。


// packages/button/src/Button.js
import React from 'react';
import PropTypes from 'prop-types';
const Button = ({ type = 'primary', onClick, children }) => {
  return (
    <button className={`button ${type}`} onClick={onClick}>
      {children}
    </button>
  );
};
Button.propTypes = {
  type: PropTypes.oneOf(['primary', 'secondary']),
  onClick: PropTypes.func,
  children: PropTypes.node.isRequired,
};
export default Button;


9. 发布前的自动化脚本:需要编写自动化脚本来规范发布流程,确保发布的一致性和可靠性。


// package.json
{
  "scripts": {
    "prepublish": "npm run lint && npm run test",
    "lint": "eslint .",
    "test": "jest"
  }
}


10. 发布后的处理:考虑补丁升级、文档站点同步发布等问题,以便及时修复问题并提供最新的文档。


11. 制定 Contributing 文档:制定 Contributing 文档可以降低开源社区贡献的门槛,并确保社区成员了解如何参与项目。处理 issues 和 PR 需要有专人负责。


如何对一个组件库进行测试?


首先需要明确,组件库的测试大致可以分为两类:一类是针对组件本身的功能和性能的测试(例如,单元测试、性能测试),另一类是针对组件在集成环境下的行为和性能的测试(例如,集成测试、系统测试)。

1. 功能测试(单元测试)

通常来说,组件的功能测试可以通过单元测试来完成。单元测试的目的是验证组件的单个功能是否按照预期工作。这通常可以通过编写测试用例来完成,每个测试用例针对一个特定的功能。

import { Button } from '../src/Button';
test('Button should do something', () => {
    const component = new YourComponent();
    // your test logic here
    expect(component.doSomething()).toBe('expected result');
});

2. 边界测试

边界测试是一种特殊的功能测试,用于检查组件在输入或输出达到极限或边界条件时的行为。

test('Button should handle boundary condition', () => {
    const component = new YourComponent();
    // test with boundary value
    expect(component.handleBoundaryCondition('boundary value')).toBe('expected result');
});

3. 响应测试

响应测试通常涉及到 UI 组件在不同的设备或屏幕尺寸下的行为。这可能需要使用端到端(E2E)测试工具,如 Puppeteer、Cypress 等。

import { test } from '@playwright/test';
test('Button should be responsive', async ({ page }) => {
    await page.goto('http://localhost:3000/your-component');
    const component = await page.$('#your-component-id');
    expect(await component.isVisible()).toBe(true);
    // Simulate a mobile device
    await page.setViewportSize({ width: 375, height: 812 });
    // Check the component under this condition
    // your test logic here
});

4. 交互测试

交互测试也可以通过端到端(E2E)测试工具来完成。

test('Button should handle interactions', async ({ page }) => {
    await page.goto('http://localhost:3000/your-component');
    const component = await page.$('#your-component-id');
    // Simulate a click event
    await component.click();
    // Check the result of the interaction
    // your test logic here
});

5. 异常测试

异常测试用于验证组件在遇到错误或非法输入时能否正确处理。这通常可以通过在测试用例中模拟错误条件来完成。

test('Button should handle errors', () => {
    const component = new YourComponent();
    // Test with illegal argument
    expect(() => {
        component.doSomething('illegal argument');
    }).toThrow('Expected error message');
});

6. 性能测试

性能测试用于验证组件的性能,例如,加载速度、内存消耗等。

import { performance } from 'perf_hooks';
test('Button should have good performance', () => {
    const start = performance.now();
    const component = new YourComponent();
    component.doSomething();
    const end = performance.now();
    const duration = end - start;
    expect(duration).toBeLessThan(50);  // Expect the operation to finish within 50 ms
});

7. 自动化测试

单元测试、集成测试和系统测试都可以通过自动化测试工具进行。例如,Jest 和 Mocha 可以用于自动化运行 JavaScript 单元测试,Puppeteer 和 Selenium 可以用于自动化运行端到端测试。

module.exports = {
    roots: ['<rootDir>/src'],
    testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'],
    transform: {
        '^.+\\.(ts|tsx)$': 'ts-jest'
    }
};

目录
相关文章
|
10月前
|
Java 调度
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day13。
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day13。
62 0
|
7月前
|
Cloud Native 算法 程序员
面试流程解析:从初面到终面,程序员需要注意什么
面试流程解析:从初面到终面,程序员需要注意什么
83 0
|
8月前
|
存储 缓存 前端开发
金九银十,带你复盘大厂常问的项目难点(三)
金九银十,带你复盘大厂常问的项目难点
48 0
|
8月前
|
前端开发 JavaScript 小程序
金九银十,带你复盘大厂常问的项目难点(四)
金九银十,带你复盘大厂常问的项目难点
54 0
|
8月前
|
移动开发 前端开发 JavaScript
金九银十,带你复盘大厂常问的项目难点(一)
金九银十,带你复盘大厂常问的项目难点
150 0
|
9月前
|
XML Java 数据库连接
备战金九银十:Java核心技术面试题100+(含大厂面试整体及解析)
一线互联网公司工作了几年,我作为求职者参加了不少面试,也作为面试官面试了很多同学,整理这份面试指南,一方面是帮助大家更好的准备面试,有的放矢,另一方面也是对自己知识框架做一个体系化的梳理。
|
10月前
|
Java 索引
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day14
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day14
98 0
|
缓存 Oracle NoSQL
【面试1v1实景模拟】Spring事务经典面试场景,全方位解读面试官心理,助你面试入坑~
【面试1v1实景模拟】Spring事务经典面试场景,全方位解读面试官心理,助你面试入坑~
130 0
|
安全 Java
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day11
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day11
127 0
|
Java 程序员
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day09
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day09
96 0
【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day09