07-技术讲解
# 公共表单模块技术讲解
# 什么问题
大型的管理系统,表单页的内容也会非常的多。
- 数据丢失风险
- 表单数据恢复
- 跨标签页通信
- 去除本地冗余数据
将核心功能逻辑封装成公共模块,同时在 Vue 和 React 中使用
# 解决思路
抽离一个类出来。在类中实现:
- 将当前表单数据保存到 localStorage
- 从 localStorage 加载数据并同步到表单数据
- 通过 BroadcastChannel 广播数据
- 清除本地的数据
- 各种细节处理
发布前的准备工作。
- 代码测试
- 代码的丑化和压缩
发布到 npm 上面。
# 解决细节
# 1. 封装类
- FormStorage:负责主要的功能实现
- 数据存储、加载、更新、清除
- 函数防抖
- 数据广播
- FormStorageManager:负责在第一次初始化的时候注册事件。
# 2. 测试
使用 Vitest 对 FormStorage 做单元测试。
首先第一步安装 Vitest:
npm install --save-dev vitest @testing-library/react @testing-library/jest-dom
接下来创建 vitest.config.js 文件:
/// <reference types="vitest" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './vitest.setup.js',
},
});
在根目录下创建一个 vitest.setup.js 文件,用于设置 Vitest 的初始配置:
import '@testing-library/jest-dom';
然后在 utils 文件夹中创建一个 FormStorage.test.js 文件:
import { describe, it, expect, beforeEach, vi } from 'vitest';
import FormStorage from './FormStorage';
// 模拟 BroadcastChannel
// 由于 Vitest 不支持 BroadcastChannel,所以我们需要创建一个全局的 BroadcastChannel 模拟对象。
global.BroadcastChannel = class {
constructor(name) {
this.name = name;
this.onmessage = null;
}
postMessage(message) {
if (this.onmessage) {
this.onmessage({ data: message });
}
}
close() {}
};
describe('FormStorage', () => {
let formData;
// 在每个测试之前清除 localStorage 并重置表单数据。
beforeEach(() => {
formData = { value: { username: '', gender: 'secret' } };
localStorage.clear();
});
// 书写各种测试用例
it('初始化测试', () => {
// 测试 init 方法是否正确从 localStorage 加载数据。
localStorage.setItem('formData_page1Form', JSON.stringify({ username: '张三', gender: 'male' }));
const formStorage = new FormStorage('page1Form', formData);
formStorage.init();
expect(formData.value).toEqual({ username: 'John', gender: 'male' });
});
it('保存数据测试', () => {
// 测试 saveData 方法是否正确保存数据到 localStorage
const formStorage = new FormStorage('page1Form', formData);
formData.value.username = 'John';
formStorage.saveData();
const savedData = JSON.parse(localStorage.getItem('formData_page1Form'));
expect(savedData).toEqual({ username: 'John', gender: 'secret' });
});
// 更多测试用例...
});
最后添加一个 test 脚本到 package.json:
"scripts": {
"test": "vitest"
}
然后运行测试跑这些测试用例即可:
npm run test
# 3. 压缩
使用 rollup 来做打包。rollup 有这么一些特点:
- Tree Shaking:可以自动去除未使用的代码。这对于公共库特别重要,因为它能够确保打包后的代码尽可能小,从而提高加载性能和使用效率。
- 输出格式多样:rollup 支持多种输出格式,例如 CommonJS、ESM、UMD 等。这使得一个库可以在不同的运行环境(Node.js、浏览器、模块打包器等)中使用,增加了库的兼容性和灵活性。
- 插件系统:rollup 提供了丰富的插件生态系统,可以方便地进行代码转换、压缩、文件处理等操作。例如,我使用了 @rollup/plugin-node-resolve 和 @rollup/plugin-commonjs 插件来处理模块解析和转换,并使用 rollup-plugin-terser 对代码进行压缩,确保生成的包体积尽可能小。
- 代码优化:rollup 能够对代码进行细粒度的优化,例如去除重复的依赖、压缩代码、内联代码等。这些优化能够显著减少打包后文件的大小,提高代码的执行效率。
首先仍然是安装依赖:
npm install --save-dev rollup rollup-plugin-terser @rollup/plugin-node-resolve @rollup/plugin-commonjs
然后在项目根目录下创建一个 rollup.config.js 的配置文件:
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
export default {
input: 'src/utils/FormStorage.js', // 输入文件路径
// 打包输出的不同格式
output: [
{
file: 'dist/FormStorage.cjs.js', // CommonJS 产物路径
format: 'cjs',
sourcemap: true,
},
{
file: 'dist/FormStorage.esm.js', // ES 模块产物路径
format: 'esm',
sourcemap: true,
},
{
file: 'dist/FormStorage.umd.js', // UMD 产物路径
format: 'umd',
name: 'FormStorage',
sourcemap: true,
},
],
plugins: [
resolve(), // 使 Rollup 能够找到 node_modules 中的模块
commonjs(), // 使 Rollup 能够转换 CommonJS 模块为 ES6
terser(), // 压缩代码
],
};
之后更新 package.json,在该文件中定义入口文件路径:
{
"name": "formstorage",
"version": "1.0.0",
"main": "dist/FormStorage.cjs.js", // CommonJS 入口
"module": "dist/FormStorage.esm.js", // ES 模块入口
"unpkg": "dist/FormStorage.umd.js", // UMD 入口
"scripts": {
"build": "rollup -c",
"test": "vitest"
},
"devDependencies": {
"rollup": "^2.42.4",
"rollup-plugin-terser": "^7.0.2",
"@rollup/plugin-node-resolve": "^13.0.6",
"@rollup/plugin-commonjs": "^20.0.0",
"vitest": "^0.0.127",
"@testing-library/react": "^12.1.2",
"@testing-library/jest-dom": "^5.16.3"
}
}
最后运行 npm run build 进行打包。
# 4. npm 发布
打包完成后,整体的项目目录如下:
/vue-project
├── dist
│ ├── FormStorage.cjs.js
│ ├── FormStorage.esm.js
│ └── FormStorage.umd.js
├── node_modules
├── public
├── src
│ ├── assets
│ ├── router
│ ├── utils
│ │ ├── FormStorage.js
│ │ └── FormStorage.test.js
│ ├── views
│ ├── App.vue
│ └── main.js
├── .eslintrc.cjs
├── .gitignore
├── .npmignore
├── .prettierrc.json
├── index.html
├── jsconfig.json
├── package-lock.json
├── package.json
├── README.md
├── rollup.config.js
├── vite.config.js
└── vitest.setup.js
在发布到 npm 时,通常只发布打包后的 dist 目录和必要的配置文件,以减小包的体积并确保用户能够正确使用你的库。
这里有两种方式:
- 白名单
在 package.json 文件中配置 files 字段,如下所示:
{
"name": "formstorage",
"version": "1.0.0",
"main": "dist/FormStorage.cjs.js",
"module": "dist/FormStorage.esm.js",
"unpkg": "dist/FormStorage.umd.js",
"scripts": {
"build": "rollup -c",
"test": "vitest"
},
"files": [
"dist"
],
// ...
}
只有 files 字段对应的目录和文件才会被上传至 npm
- 黑名单
在项目根目录下创建一个 .npmignore 文件,用于排除不需要的文件和目录:
/src
/public
/node_modules
/rollup.config.js
/vitest.setup.js
/vitest.config.js
.eslintrc.cjs
.prettierrc.json
jsconfig.json
vite.config.js
推荐使用白名单的方式,因为黑名单每次新增文件或目录后都需要更新 .npmignore 文件。
最后登录 npm,然后使用 npm publish 进行包的发布即可。
-EOF-