warehouse-mock 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # warehouse-mock
2
+
3
+ 一个专为 Vue 2 项目设计的 Webpack 插件,支持 RPC 风格接口 mock,零业务代码侵入,实时更新。
4
+
5
+ [![npm version](https://img.shields.io/npm/v/warehouse-mock.svg)](https://www.npmjs.com/package/warehouse-mock)
6
+ [![license](https://img.shields.io/npm/l/warehouse-mock.svg)](https://github.com/CodeMomentYY/warehouse-mock/blob/main/LICENSE)
7
+
8
+ ## ✨ 特性
9
+
10
+ - ✅ **RPC 风格接口支持**: 完美支持 `/api?user.account.getInfo` 格式
11
+ - ✅ **零业务代码侵入**: 只需修改配置文件,无需改动业务逻辑
12
+ - ✅ **实时更新**: 修改 Mock 数据后刷新页面即可,无需重启服务
13
+ - ✅ **按需 Mock**: 只拦截配置了 Mock 数据的接口,其他接口不受影响
14
+ - ✅ **TypeScript 编写**: 类型安全,易于维护
15
+ - ✅ **兼容性强**: 支持 Webpack 4/5,Vue CLI 3/4/5
16
+
17
+ ## 📦 安装
18
+
19
+ ```bash
20
+ npm install warehouse-mock --save-dev
21
+ ```
22
+
23
+ ## 🚀 快速开始
24
+
25
+ ### 1. 配置 vue.config.js
26
+
27
+ ```javascript
28
+ const WarehouseMockPlugin = require('warehouse-mock');
29
+ const webpack = require('webpack');
30
+ const path = require('path');
31
+
32
+ const isMock = process.env.MOCK === 'true';
33
+ const mockPlugin = isMock ? new WarehouseMockPlugin({
34
+ mockPath: path.resolve(__dirname, 'warehouseMock'),
35
+ }) : null;
36
+
37
+ module.exports = {
38
+ configureWebpack: config => {
39
+ if (isMock && mockPlugin) {
40
+ config.plugins.push(
41
+ new webpack.DefinePlugin({
42
+ 'process.env.VUE_APP_MOCK': JSON.stringify('true'),
43
+ })
44
+ );
45
+ config.plugins.push(mockPlugin);
46
+ }
47
+ },
48
+
49
+ devServer: {
50
+ // Vue CLI 5.x (Webpack 5)
51
+ setupMiddlewares: (middlewares, devServer) => {
52
+ if (isMock && mockPlugin) {
53
+ return mockPlugin.setupMiddlewares(middlewares, devServer);
54
+ }
55
+ return middlewares;
56
+ },
57
+
58
+ // Vue CLI 3.x/4.x (Webpack 4) 使用这个:
59
+ // before: isMock && mockPlugin ? (app) => mockPlugin.runBefore(app) : undefined,
60
+ }
61
+ };
62
+ ```
63
+
64
+ ### 2. 修改 API 配置
65
+
66
+ 在 `src/const/config.js` 中添加 Mock 模式判断:
67
+
68
+ ```javascript
69
+ const config = { env: process.env.VUE_APP_ENV || 'dev' };
70
+
71
+ // Mock 模式优先判断
72
+ if (process.env.VUE_APP_MOCK === 'true') {
73
+ Object.assign(config, {
74
+ API: '/mock-api',
75
+ });
76
+ } else if (config.env === 'dev') {
77
+ Object.assign(config, {
78
+ API: 'https://fat-api.hellobike.com/api',
79
+ });
80
+ }
81
+
82
+ export default config;
83
+ ```
84
+
85
+ ### 3. 添加 Mock 脚本
86
+
87
+ 在 `package.json` 中:
88
+
89
+ ```json
90
+ {
91
+ "scripts": {
92
+ "mock": "MOCK=true vue-cli-service serve"
93
+ }
94
+ }
95
+ ```
96
+
97
+ ### 4. 创建 Mock 数据
98
+
99
+ 创建 `warehouseMock` 目录并添加 Mock 数据文件:
100
+
101
+ **warehouseMock/user.account.getInfo.json**:
102
+ ```json
103
+ {
104
+ "code": 0,
105
+ "msg": "success",
106
+ "data": {
107
+ "userId": 12345,
108
+ "userName": "Mock 测试用户"
109
+ }
110
+ }
111
+ ```
112
+
113
+ ### 5. 启动
114
+
115
+ ```bash
116
+ npm run mock
117
+ ```
118
+
119
+ ## ⚙️ 配置选项
120
+
121
+ ```typescript
122
+ new WarehouseMockPlugin({
123
+ // 必填:Mock 数据存放目录
124
+ mockPath: string;
125
+
126
+ // 可选:需要拦截的 API 路径前缀,默认 ['/api', '/mock-api']
127
+ apiPrefixes?: string[];
128
+
129
+ // 可选:本地 API 路径前缀,默认 '/mock-api'
130
+ localApiPrefix?: string;
131
+ })
132
+ ```
133
+
134
+ ## 📝 Mock 文件命名规则
135
+
136
+ ### RPC 风格(推荐)
137
+
138
+ | 请求 URL | Mock 文件名 |
139
+ |---------|-----------|
140
+ | `/api?user.account.getInfo` | `user.account.getInfo.json` |
141
+ | `/api?user.taurus.pointInfo` | `user.taurus.pointInfo.json` |
142
+ | `/api?common.welfare.banner.query` | `common.welfare.banner.query.json` |
143
+
144
+ ### RESTful 风格
145
+
146
+ | 请求 URL | Mock 文件名(二选一) |
147
+ |---------|---------------------|
148
+ | `/api/user/info` | `api_user_info.json` (扁平化) |
149
+ | `/api/user/info` | `api/user/info.json` (嵌套) |
150
+
151
+ ## 🔍 调试工具
152
+
153
+ ### 查看可用 Mock 列表
154
+
155
+ 访问: `http://localhost:8080/__mock_list__`
156
+
157
+ ### 直接访问 Mock 数据
158
+
159
+ 访问: `http://localhost:8080/mock-api?user.account.getInfo`
160
+
161
+ ## 💡 为什么选择 warehouse-mock?
162
+
163
+ ### 适用场景
164
+
165
+ - ✅ 后端接口未就绪,需要前端先行开发
166
+ - ✅ 后端接口不稳定,需要本地 Mock 数据测试
167
+ - ✅ 需要模拟特殊场景(错误、超时、边界数据等)
168
+ - ✅ 使用 RPC 风格接口的项目(如 hellobike)
169
+
170
+ ### 对比其他方案
171
+
172
+ | 方案 | warehouse-mock | Mock.js | json-server |
173
+ |-----|---------------|---------|-------------|
174
+ | 零代码侵入 | ✅ | ❌ 需修改业务代码 | ❌ 需单独启动服务 |
175
+ | RPC 支持 | ✅ | ❌ | ❌ |
176
+ | 实时更新 | ✅ | ❌ | ✅ |
177
+ | TypeScript | ✅ | ❌ | ❌ |
178
+ | Webpack 集成 | ✅ | ❌ | ❌ |
179
+
180
+ ## 📚 完整示例
181
+
182
+ 查看完整的示例项目:[example-vue2](https://github.com/CodeMomentYY/warehouse-mock/tree/main/packages/example-vue2)
183
+
184
+ ## ❓ 常见问题
185
+
186
+ **Q: Mock 没有生效?**
187
+
188
+ 确保在 `src/const/config.js` 中添加了 Mock 模式判断,将 API 地址改为 `/mock-api`。
189
+
190
+ **Q: 修改 Mock 数据后需要重启服务吗?**
191
+
192
+ 不需要!直接刷新浏览器页面即可。
193
+
194
+ **Q: 可以只 Mock 部分接口吗?**
195
+
196
+ 可以!只有存在对应 JSON 文件的接口才会使用 Mock 数据。
197
+
198
+ ## 📄 许可证
199
+
200
+ MIT © CodeMomentYY
201
+
202
+ ## 🔗 相关链接
203
+
204
+ - [GitHub 仓库](https://github.com/CodeMomentYY/warehouse-mock)
205
+ - [问题反馈](https://github.com/CodeMomentYY/warehouse-mock/issues)
206
+ - [更新日志](https://github.com/CodeMomentYY/warehouse-mock/releases)
207
+
@@ -0,0 +1,33 @@
1
+ import { Compiler } from 'webpack';
2
+ interface MockPluginOptions {
3
+ mockPath: string;
4
+ apiPrefixes?: string[];
5
+ localApiPrefix?: string;
6
+ }
7
+ declare class WarehouseMockPlugin {
8
+ private options;
9
+ private resolvedMockPath;
10
+ constructor(options: MockPluginOptions);
11
+ /**
12
+ * 实时扫描 mock 目录,获取所有 mock 文件名列表
13
+ */
14
+ getMockFileList(): string[];
15
+ /**
16
+ * 获取本地代理路径前缀
17
+ */
18
+ getLocalApiPrefix(): string;
19
+ apply(compiler: Compiler): void;
20
+ /**
21
+ * 公共方法:设置中间件 (Webpack 5 / Vue CLI 5+)
22
+ */
23
+ setupMiddlewares(middlewares: any[], devServer: any, originalSetupMiddlewares?: Function): any;
24
+ /**
25
+ * 公共方法:设置中间件 (Webpack 4 / Vue CLI 3-4)
26
+ */
27
+ runBefore(app: any, server?: any, compiler?: any, originalBefore?: Function): void;
28
+ private getResolvedMockPath;
29
+ private ensureMockDirectory;
30
+ private createDemoFile;
31
+ private handleRequest;
32
+ }
33
+ export = WarehouseMockPlugin;
package/dist/index.js ADDED
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ const fs_1 = __importDefault(require("fs"));
6
+ const path_1 = __importDefault(require("path"));
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ class WarehouseMockPlugin {
9
+ constructor(options) {
10
+ this.resolvedMockPath = '';
11
+ this.options = Object.assign({ apiPrefixes: ['/api', '/mock-api'], localApiPrefix: '/mock-api' }, options);
12
+ }
13
+ /**
14
+ * 实时扫描 mock 目录,获取所有 mock 文件名列表
15
+ */
16
+ getMockFileList() {
17
+ const mockPath = this.getResolvedMockPath();
18
+ const fileList = [];
19
+ if (fs_1.default.existsSync(mockPath)) {
20
+ const files = fs_1.default.readdirSync(mockPath);
21
+ files.forEach((file) => {
22
+ if (file.endsWith('.json')) {
23
+ fileList.push(file.replace(/\.json$/, ''));
24
+ }
25
+ });
26
+ }
27
+ return fileList;
28
+ }
29
+ /**
30
+ * 获取本地代理路径前缀
31
+ */
32
+ getLocalApiPrefix() {
33
+ return this.options.localApiPrefix || '/mock-api';
34
+ }
35
+ apply(compiler) {
36
+ this.resolvedMockPath = path_1.default.resolve(compiler.context, this.options.mockPath);
37
+ // 确保 mock 目录存在
38
+ if (!fs_1.default.existsSync(this.resolvedMockPath)) {
39
+ try {
40
+ fs_1.default.mkdirSync(this.resolvedMockPath, { recursive: true });
41
+ console.log(chalk_1.default.green(`[WarehouseMock] Mock 目录已创建: ${this.resolvedMockPath}`));
42
+ this.createDemoFile(this.resolvedMockPath);
43
+ }
44
+ catch (err) {
45
+ console.error(chalk_1.default.red(`[WarehouseMock] 创建 Mock 目录失败: ${err}`));
46
+ }
47
+ }
48
+ const mockFileList = this.getMockFileList();
49
+ console.log(chalk_1.default.cyan(`[WarehouseMock] 已加载 ${mockFileList.length} 个 mock 文件: ${mockFileList.join(', ')}`));
50
+ const devServerOptions = compiler.options.devServer || {};
51
+ const originalSetupMiddlewares = devServerOptions.setupMiddlewares;
52
+ if (!compiler.options.devServer) {
53
+ compiler.options.devServer = {};
54
+ }
55
+ // 适用于 Webpack 5 (setupMiddlewares)
56
+ compiler.options.devServer.setupMiddlewares = (middlewares, devServer) => {
57
+ return this.setupMiddlewares(middlewares, devServer, originalSetupMiddlewares);
58
+ };
59
+ // 适用于 Webpack 4 (before)
60
+ const originalBefore = devServerOptions.before;
61
+ compiler.options.devServer.before = (app, server, compilerArg) => {
62
+ this.runBefore(app, server, compilerArg, originalBefore);
63
+ };
64
+ }
65
+ /**
66
+ * 公共方法:设置中间件 (Webpack 5 / Vue CLI 5+)
67
+ */
68
+ setupMiddlewares(middlewares, devServer, originalSetupMiddlewares) {
69
+ console.log(chalk_1.default.cyan('[WarehouseMock] Mock 服务已启动'));
70
+ const mockPath = this.getResolvedMockPath();
71
+ this.ensureMockDirectory(mockPath);
72
+ middlewares.unshift({
73
+ name: 'warehouse-mock',
74
+ middleware: (req, res, next) => this.handleRequest(req, res, next, mockPath),
75
+ });
76
+ if (originalSetupMiddlewares) {
77
+ return originalSetupMiddlewares(middlewares, devServer);
78
+ }
79
+ return middlewares;
80
+ }
81
+ /**
82
+ * 公共方法:设置中间件 (Webpack 4 / Vue CLI 3-4)
83
+ */
84
+ runBefore(app, server, compiler, originalBefore) {
85
+ console.log(chalk_1.default.cyan('[WarehouseMock] Mock 服务已启动 (before hook)'));
86
+ const mockPath = this.getResolvedMockPath();
87
+ this.ensureMockDirectory(mockPath);
88
+ app.use((req, res, next) => this.handleRequest(req, res, next, mockPath));
89
+ if (originalBefore) {
90
+ originalBefore(app, server, compiler);
91
+ }
92
+ }
93
+ getResolvedMockPath() {
94
+ if (this.resolvedMockPath) {
95
+ return this.resolvedMockPath;
96
+ }
97
+ return path_1.default.isAbsolute(this.options.mockPath)
98
+ ? this.options.mockPath
99
+ : path_1.default.resolve(process.cwd(), this.options.mockPath);
100
+ }
101
+ ensureMockDirectory(mockPath) {
102
+ if (!fs_1.default.existsSync(mockPath)) {
103
+ try {
104
+ fs_1.default.mkdirSync(mockPath, { recursive: true });
105
+ console.log(chalk_1.default.green(`[WarehouseMock] Mock 目录已创建: ${mockPath}`));
106
+ this.createDemoFile(mockPath);
107
+ }
108
+ catch (err) {
109
+ console.error(chalk_1.default.red(`[WarehouseMock] 创建 Mock 目录失败: ${err}`));
110
+ }
111
+ }
112
+ }
113
+ createDemoFile(mockPath) {
114
+ const demoFilePath = path_1.default.join(mockPath, 'demo.json');
115
+ const demoContent = {
116
+ code: 0,
117
+ msg: 'success',
118
+ data: {
119
+ message: '这是自动生成的 demo 数据',
120
+ id: 1,
121
+ name: 'Demo User',
122
+ description: 'Warehouse Mock 自动创建的示例文件',
123
+ },
124
+ };
125
+ try {
126
+ fs_1.default.writeFileSync(demoFilePath, JSON.stringify(demoContent, null, 2));
127
+ console.log(chalk_1.default.green(`[WarehouseMock] 已创建示例 Mock 文件: ${demoFilePath}`));
128
+ }
129
+ catch (e) {
130
+ console.error(chalk_1.default.yellow(`[WarehouseMock] 创建示例文件失败: ${e}`));
131
+ }
132
+ }
133
+ handleRequest(req, res, next, mockPath) {
134
+ var _a, _b;
135
+ const url = req.path || ((_a = req.url) === null || _a === void 0 ? void 0 : _a.split('?')[0]);
136
+ if (!url) {
137
+ return next();
138
+ }
139
+ // ============ 特殊端点:实时返回 mock 文件列表 ============
140
+ if (url === '/__mock_list__') {
141
+ const fileList = this.getMockFileList();
142
+ res.setHeader('Content-Type', 'application/json');
143
+ res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
144
+ res.end(JSON.stringify({
145
+ mockList: fileList,
146
+ localApiPrefix: this.getLocalApiPrefix(),
147
+ }));
148
+ return;
149
+ }
150
+ // 安全检查:防止目录遍历
151
+ if (url.includes('..')) {
152
+ return next();
153
+ }
154
+ // 检查是否在允许的 API 前缀范围内
155
+ const { apiPrefixes } = this.options;
156
+ const isApiRequest = (_b = apiPrefixes === null || apiPrefixes === void 0 ? void 0 : apiPrefixes.some((prefix) => url.startsWith(prefix))) !== null && _b !== void 0 ? _b : true;
157
+ if (!isApiRequest) {
158
+ return next();
159
+ }
160
+ let filePath = '';
161
+ let matched = false;
162
+ let matchedName = '';
163
+ // 1. 优先尝试 Query String 匹配 (RPC 风格接口,如 /api?user.taurus.pointInfo)
164
+ if (req.url && req.url.includes('?')) {
165
+ const queryPart = req.url.split('?')[1];
166
+ if (queryPart) {
167
+ const params = new URLSearchParams(queryPart);
168
+ for (const key of params.keys()) {
169
+ // 尝试匹配 key.json (例如 user.taurus.pointInfo.json)
170
+ const queryFilePath = path_1.default.join(mockPath, `${key}.json`);
171
+ if (fs_1.default.existsSync(queryFilePath) && fs_1.default.statSync(queryFilePath).isFile()) {
172
+ filePath = queryFilePath;
173
+ matchedName = key;
174
+ matched = true;
175
+ break;
176
+ }
177
+ // 也尝试匹配 value (例如 method=user.taurus.pointInfo)
178
+ const value = params.get(key);
179
+ if (value) {
180
+ const valueFilePath = path_1.default.join(mockPath, `${value}.json`);
181
+ if (fs_1.default.existsSync(valueFilePath) && fs_1.default.statSync(valueFilePath).isFile()) {
182
+ filePath = valueFilePath;
183
+ matchedName = value;
184
+ matched = true;
185
+ break;
186
+ }
187
+ }
188
+ }
189
+ }
190
+ }
191
+ // 2. 尝试扁平化命名 (将路径中的 / 替换为 _)
192
+ if (!matched) {
193
+ const flatName = url.replace(/^\//, '').replace(/\//g, '_') || 'index';
194
+ const flatFilePath = path_1.default.join(mockPath, `${flatName}.json`);
195
+ if (fs_1.default.existsSync(flatFilePath) && fs_1.default.statSync(flatFilePath).isFile()) {
196
+ filePath = flatFilePath;
197
+ matchedName = flatName;
198
+ matched = true;
199
+ }
200
+ }
201
+ // 3. 尝试嵌套目录结构匹配 (向后兼容)
202
+ if (!matched) {
203
+ const nestedFilePath = path_1.default.join(mockPath, url + '.json');
204
+ if (fs_1.default.existsSync(nestedFilePath) && fs_1.default.statSync(nestedFilePath).isFile()) {
205
+ filePath = nestedFilePath;
206
+ matchedName = url;
207
+ matched = true;
208
+ }
209
+ else {
210
+ const indexFilePath = path_1.default.join(mockPath, url, 'index.json');
211
+ if (fs_1.default.existsSync(indexFilePath) && fs_1.default.statSync(indexFilePath).isFile()) {
212
+ filePath = indexFilePath;
213
+ matchedName = url + '/index';
214
+ matched = true;
215
+ }
216
+ }
217
+ }
218
+ if (matched) {
219
+ console.log(chalk_1.default.green(`[WarehouseMock] 拦截: ${matchedName} -> ${path_1.default.basename(filePath)}`));
220
+ try {
221
+ const data = fs_1.default.readFileSync(filePath, 'utf-8');
222
+ try {
223
+ const jsonData = JSON.parse(data);
224
+ res.setHeader('Content-Type', 'application/json');
225
+ res.end(JSON.stringify(jsonData));
226
+ }
227
+ catch (jsonErr) {
228
+ console.warn(chalk_1.default.yellow(`[WarehouseMock] 无效的 JSON 文件: ${filePath}`));
229
+ res.setHeader('Content-Type', 'text/plain');
230
+ res.end(data);
231
+ }
232
+ return;
233
+ }
234
+ catch (e) {
235
+ console.error(chalk_1.default.red(`[WarehouseMock] 读取 Mock 文件失败: ${e}`));
236
+ res.statusCode = 500;
237
+ res.end('Mock Read Error');
238
+ return;
239
+ }
240
+ }
241
+ next();
242
+ }
243
+ }
244
+ module.exports = WarehouseMockPlugin;
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "warehouse-mock",
3
+ "version": "1.0.0",
4
+ "description": "一个专为 Vue 2 项目设计的 Webpack 插件,支持 RPC 风格接口 mock,零业务代码侵入,实时更新",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "webpack",
17
+ "plugin",
18
+ "mock",
19
+ "vue",
20
+ "rpc",
21
+ "api",
22
+ "typescript",
23
+ "dev-server",
24
+ "hellobike"
25
+ ],
26
+ "author": "CodeMomentYY",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/CodeMomentYY/warehouse-mock.git",
31
+ "directory": "packages/mock-webpack-plugin"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/CodeMomentYY/warehouse-mock/issues"
35
+ },
36
+ "homepage": "https://github.com/CodeMomentYY/warehouse-mock#readme",
37
+ "dependencies": {
38
+ "chalk": "^4.1.2",
39
+ "chokidar": "^3.5.3"
40
+ },
41
+ "devDependencies": {
42
+ "typescript": "^5.0.0",
43
+ "webpack": "^5.0.0",
44
+ "webpack-dev-server": "^4.0.0",
45
+ "@types/webpack": "^5.0.0",
46
+ "@types/webpack-dev-server": "^4.0.0",
47
+ "@types/node": "^18.0.0"
48
+ },
49
+ "peerDependencies": {
50
+ "webpack": "^4.0.0 || ^5.0.0"
51
+ },
52
+ "engines": {
53
+ "node": ">=12.0.0"
54
+ }
55
+ }
56
+