mock-switch-webpack-plugin 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lebron_shi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,305 @@
1
+ # Webpack Mock Switch Plugin
2
+
3
+ <div align="center">
4
+
5
+ ![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)
6
+ ![Webpack](https://img.shields.io/badge/webpack-%3E%3D5.0.0-green.svg)
7
+ ![License](https://img.shields.io/badge/license-MIT-yellow.svg)
8
+
9
+ **基于 webpack5 的开发环境接口 Mock 一键切换插件**
10
+
11
+ </div>
12
+
13
+ ## ✨ 特性
14
+
15
+ ### 🎯 核心功能
16
+ - **🔄 浏览器端一键切换** - 页面内悬浮面板,Mock / 测试 / 生产环境无缝切换
17
+ - **🔥 Mock 数据热更新** - 修改 Mock 文件立即生效,无需重启服务
18
+ - **📝 自动记录真实接口** - 代理请求时自动记录返回,一键保存为 Mock 文件
19
+ - **📂 自动路径匹配** - 根据接口路径自动查找对应 Mock 文件
20
+ - **⚡ 零侵入式设计** - 纯 webpack 插件,无需修改业务代码
21
+
22
+ ### 🎨 界面预览
23
+ - 右下角悬浮切换按钮,状态指示灯(绿=Mock/黄=测试/红=生产)
24
+ - 优雅的下拉面板,环境列表 + 已记录接口
25
+ - 点击即可保存真实接口返回为 Mock 文件
26
+
27
+ ## 🆚 与其他方案对比
28
+
29
+ | 功能 | webpack-mock-switch-plugin | mocker-api | webpack-api-mocker | mockjs |
30
+ |------|---------------------------|------------|-------------------|--------|
31
+ | 浏览器端一键切换 | ✅ | ❌ | ❌ | ❌ |
32
+ | Mock数据热更新 | ✅ | ✅ | ✅ | ❌ |
33
+ | 自动记录真实接口 | ✅ | ❌ | ❌ | ❌ |
34
+ | 一键保存为Mock | ✅ | ❌ | ❌ | ❌ |
35
+ | 自动路径匹配 | ✅ | ✅ | ✅ | ❌ |
36
+ | Webpack5支持 | ✅ | ✅ | ⚠️ | ❌ |
37
+ | 零侵入集成 | ✅ | ⚠️ | ⚠️ | ❌ |
38
+ | 动态Mock函数 | ✅ | ✅ | ✅ | ✅ |
39
+ | 网络延迟模拟 | ✅ | ✅ | ❌ | ✅ |
40
+
41
+ > **为什么选择我们?** 现有方案都只解决了部分问题,本插件一站式解决「环境切换麻烦、Mock数据难维护、需要手动写Mock」三大痛点。
42
+
43
+ ## 📦 安装
44
+
45
+ ```bash
46
+ npm install webpack-mock-switch-plugin --save-dev
47
+ # or
48
+ yarn add webpack-mock-switch-plugin -D
49
+ # or
50
+ pnpm add webpack-mock-switch-plugin -D
51
+ ```
52
+
53
+ ## 🚀 快速开始
54
+
55
+ ### 1. 配置 webpack.config.js
56
+
57
+ ```javascript
58
+ const WebpackMockSwitchPlugin = require('webpack-mock-switch-plugin');
59
+
60
+ module.exports = {
61
+ // ... 其他配置
62
+ plugins: [
63
+ new WebpackMockSwitchPlugin({
64
+ // Mock文件目录(相对项目根目录)
65
+ mockDir: './mock',
66
+ // 接口前缀,只拦截该前缀的请求
67
+ apiPrefix: '/api',
68
+ // 环境配置
69
+ environments: {
70
+ mock: { name: 'Mock环境', target: null },
71
+ test: { name: '测试环境', target: 'http://localhost:3000' },
72
+ prod: { name: '生产环境', target: 'https://api.yourdomain.com' }
73
+ },
74
+ // 自动记录真实接口返回
75
+ autoRecord: true,
76
+ // 开启热更新
77
+ hotReload: true
78
+ })
79
+ ],
80
+ devServer: {
81
+ // 插件会自动注入中间件,无需额外配置
82
+ hot: true,
83
+ open: true
84
+ }
85
+ };
86
+ ```
87
+
88
+ ### 2. 创建 Mock 文件
89
+
90
+ 在项目根目录创建 `mock` 文件夹,按以下命名规则创建文件:
91
+
92
+ ```
93
+ mock/
94
+ ├── GET_user_list.json # GET /api/user/list
95
+ ├── POST_user_login.js # POST /api/user/login
96
+ ├── GET_user_1_detail.json # GET /api/user/1/detail
97
+ └── config.js # 其他配置
98
+ ```
99
+
100
+ #### 静态 Mock (JSON)
101
+
102
+ ```json
103
+ // mock/GET_user_list.json
104
+ {
105
+ "code": 0,
106
+ "message": "success",
107
+ "data": {
108
+ "list": [
109
+ { "id": 1, "name": "张三" },
110
+ { "id": 2, "name": "李四" }
111
+ ]
112
+ },
113
+ "_delay": 500
114
+ }
115
+ ```
116
+
117
+ #### 动态 Mock (JS 函数)
118
+
119
+ ```javascript
120
+ // mock/POST_user_login.js
121
+ module.exports = function(req, res) {
122
+ const { username, password } = req.body;
123
+
124
+ if (username === 'admin' && password === '123456') {
125
+ return {
126
+ code: 0,
127
+ message: '登录成功',
128
+ data: { token: 'mock_' + Date.now() }
129
+ };
130
+ }
131
+
132
+ return {
133
+ code: 401,
134
+ message: '用户名或密码错误'
135
+ };
136
+ };
137
+ ```
138
+
139
+ ### 3. 启动开发服务
140
+
141
+ ```bash
142
+ npm run start
143
+ ```
144
+
145
+ 打开浏览器,你会看到右下角的悬浮切换按钮 🎉
146
+
147
+ ## ⚙️ 配置选项
148
+
149
+ | 参数 | 类型 | 默认值 | 说明 |
150
+ |------|------|--------|------|
151
+ | `mockDir` | `string` | `./mock` | Mock 文件存放目录 |
152
+ | `apiPrefix` | `string` | `/api` | 需要拦截的接口前缀 |
153
+ | `environments` | `object` | 见下方 | 环境配置列表 |
154
+ | `autoRecord` | `boolean` | `true` | 是否自动记录真实接口 |
155
+ | `hotReload` | `boolean` | `true` | 是否开启 Mock 热更新 |
156
+ | `port` | `number` | `38080` | 控制面板端口 |
157
+
158
+ ### environments 默认配置
159
+
160
+ ```javascript
161
+ {
162
+ mock: { name: 'Mock环境', target: null }, // 本地Mock
163
+ test: { name: '测试环境', target: 'http://localhost:3000' },
164
+ prod: { name: '生产环境', target: 'https://api.example.com' }
165
+ }
166
+ ```
167
+
168
+ ## 📖 使用指南
169
+
170
+ ### 1. 环境切换
171
+
172
+ 1. 点击右下角悬浮按钮打开面板
173
+ 2. 点击对应环境即可切换
174
+ 3. 状态指示灯会变色(绿/黄/红)
175
+ 4. 后续请求自动走对应环境
176
+
177
+ ### 2. 自动记录 & 保存 Mock
178
+
179
+ 1. 切换到测试/生产环境
180
+ 2. 正常调用接口(插件会自动记录返回)
181
+ 3. 打开面板,在「已记录的接口」列表中
182
+ 4. 点击「保存」按钮,自动生成 Mock 文件
183
+
184
+ ### 3. Mock 文件匹配规则
185
+
186
+ 插件会按以下优先级查找 Mock 文件:
187
+
188
+ 1. `{METHOD}_{path}.json` - 如 `GET_user_list.json`
189
+ 2. `{path}.json` - 如 `user_list.json`
190
+ 3. `{METHOD}_{path}.js` - 如 `GET_user_list.js`
191
+ 4. `{path}.js` - 如 `user_list.js`
192
+ 5. 目录结构匹配 - 如 `/mock/user/list.json`
193
+
194
+ > 💡 提示:路径中的 `/` 会自动转换为 `_`
195
+
196
+ ### 4. 高级功能
197
+
198
+ #### 模拟网络延迟
199
+
200
+ 在 JSON 中添加 `_delay` 字段:
201
+
202
+ ```json
203
+ {
204
+ "code": 0,
205
+ "data": {},
206
+ "_delay": 1000 // 延迟1秒返回
207
+ }
208
+ ```
209
+
210
+ #### 动态 Mock 函数
211
+
212
+ JS 文件导出函数,可访问 req/res 对象:
213
+
214
+ ```javascript
215
+ module.exports = async (req, res) => {
216
+ // 获取查询参数
217
+ const { page, size } = req.query;
218
+
219
+ // 获取请求体
220
+ const body = req.body;
221
+
222
+ // 自定义响应头
223
+ res.setHeader('X-Custom-Header', 'mock');
224
+
225
+ return {
226
+ code: 0,
227
+ data: { page, size }
228
+ };
229
+ };
230
+ ```
231
+
232
+ ## 🔧 打包发布流程
233
+
234
+ ### 1. 发布到 npm
235
+
236
+ ```bash
237
+ # 1. 登录 npm
238
+ npm login
239
+
240
+ # 2. 检查 package.json 配置
241
+ # - name: 包名(需唯一)
242
+ # - version: 语义化版本号
243
+ # - main: 入口文件
244
+
245
+ # 3. 发布
246
+ npm publish
247
+
248
+ # 发布私有包
249
+ npm publish --access public
250
+ ```
251
+
252
+ ### 2. 版本管理
253
+
254
+ ```bash
255
+ # 补丁版本更新 (1.0.0 -> 1.0.1)
256
+ npm version patch
257
+
258
+ # 次版本更新 (1.0.0 -> 1.1.0)
259
+ npm version minor
260
+
261
+ # 主版本更新 (1.0.0 -> 2.0.0)
262
+ npm version major
263
+ ```
264
+
265
+ ### 3. 本地测试
266
+
267
+ ```bash
268
+ # 在插件目录
269
+ npm link
270
+
271
+ # 在测试项目
272
+ npm link webpack-mock-switch-plugin
273
+ ```
274
+
275
+ ## ❓ 常见问题
276
+
277
+ ### Q: 为什么切换环境后需要刷新页面?
278
+ A: 已发出的请求不会改变,新请求会走新环境。建议切换后刷新页面确保一致性。
279
+
280
+ ### Q: Mock 热更新不生效?
281
+ A: 检查 `hotReload: true`,确保 Mock 文件在配置的 `mockDir` 目录下。
282
+
283
+ ### Q: 如何处理 body 参数?
284
+ A: 需在 devServer 中配置 `devMiddleware: { writeToDisk: true }` 或添加 body-parser 中间件。
285
+
286
+ ### Q: 支持 RESTful 动态路由吗?
287
+ A: 目前支持精确匹配,动态路由建议使用 JS Mock 函数处理。
288
+
289
+ ## 📝 更新日志
290
+
291
+ ### v1.0.0 (2024-06-15)
292
+ - ✅ 首次发布
293
+ - ✅ 浏览器端环境切换面板
294
+ - ✅ Mock 数据热更新
295
+ - ✅ 真实接口自动记录
296
+ - ✅ 一键保存为 Mock 文件
297
+ - ✅ 自动路径匹配
298
+
299
+ ## 🤝 贡献
300
+
301
+ 欢迎提交 Issue 和 Pull Request!
302
+
303
+ ## 📄 License
304
+
305
+ MIT © Webpack Mock Switch Team
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ (()=>{var e={497(e,t,o){const n=o(928),r=o(896),s=o(231);let i=null;const a="__MOCK_SWITCH_INSTANCE__";function c(){return global[a]||(global[a]={currentEnv:"mock"}),global[a]}class d{constructor(e={}){var t,o,r,s,i,a;const d=e.environments||{},p={};p.mock={name:"🟢 "+((null===(t=d.mock)||void 0===t?void 0:t.name)||"Mock环境"),target:(null===(o=d.mock)||void 0===o?void 0:o.target)??null},p.dev={name:"🟡 "+((null===(r=d.dev)||void 0===r?void 0:r.name)||"开发环境"),target:(null===(s=d.dev)||void 0===s?void 0:s.target)??"http://localhost:3000"},p.prod={name:"🔴 "+((null===(i=d.prod)||void 0===i?void 0:i.name)||"生产环境"),target:(null===(a=d.prod)||void 0===a?void 0:a.target)??"https://api.example.com"},this.options={mockDir:n.resolve(process.cwd(),"./mock"),apiPrefix:"/api",environments:p,hotReload:!0,autoRegisterMiddleware:!0,...e,environments:p};const m=c();this.currentEnv=m.currentEnv,this.watcher=null,console.log("%s","\n╔══════════════════════════════════════════════════════════════╗\n║ Webpack Mock Switch ║\n║ ║\n║ Development API Mock & Proxy Management ║\n║ One-Click Environment Switching ║\n║ ║\n╚══════════════════════════════════════════════════════════════╝\n")}apply(e){const{webpack:t}=e,{RawSource:o}=t.sources;this.ensureMockDir(),e.hooks.emit.tap("MockSwitchWebpackPlugin",t=>{if("development"!==e.options.mode)return;const n=this.generateClientCode();for(const[e,r]of Object.entries(t.assets))if(e.endsWith(".js")&&!e.includes(".hot-update"))try{const s=r.source()+"\n;"+n;t.assets[e]=new o(s)}catch(e){}}),this.options.autoRegisterMiddleware&&e.hooks.afterEnvironment.tap("MockSwitchWebpackPlugin",()=>{e.options.devServer||(e.options.devServer={});const t=e.options.devServer;t.historyApiFallback||(t.historyApiFallback={}),"object"==typeof t.historyApiFallback&&(t.historyApiFallback.rewrites=t.historyApiFallback.rewrites||[],t.historyApiFallback.rewrites.unshift({from:/^\/(__mock_switch__|api)\//,to:e=>e.parsedUrl.pathname}));const o=this;if("function"!=typeof t.setupMiddlewares&&t.setupMiddlewares)if("function"!=typeof t.onBeforeSetupMiddleware&&t.onBeforeSetupMiddleware)if("function"!=typeof t.before&&t.before)console.log("%s","[Mock Switch] 请使用 createMockMiddleware() 手动注册");else{const e=t.before;t.before=(t,n,r)=>{o.registerRoutesWithBodyParser(t),"function"==typeof e&&e(t,n,r)}}else{const e=t.onBeforeSetupMiddleware;t.onBeforeSetupMiddleware=t=>{o.registerRoutesWithBodyParser(t.app),"function"==typeof e&&e(t)}}else{const e=t.setupMiddlewares;t.setupMiddlewares=(t,n)=>(t.unshift((e,t,n)=>{if(e.path.startsWith("/__mock_switch__"))return o.handleControlApi(e,t,n);if(e.path.startsWith(o.options.apiPrefix))switch(o.setNoCacheHeaders(t),o.currentEnv){case"mock":return o.parseBodyThenMock(e,t,n);case"dev":case"prod":return o.serveProxyRequestDirect(e,t,n);default:return n()}n()}),"function"==typeof e?e(t,n):t)}}),this.options.hotReload&&this.startFileWatcher()}registerRoutesWithBodyParser(e){const t=this;try{const t=o(Object(function(){var e=new Error("Cannot find module 'express'");throw e.code="MODULE_NOT_FOUND",e}()));e.use(t.json({limit:"50mb"})),e.use(t.urlencoded({extended:!0,limit:"50mb"}))}catch(e){console.log("%s","[Mock Switch] 使用内置body解析")}e.options("/__mock_switch__/*",(e,o)=>{t.setNoCacheHeaders(o),o.setHeader("Access-Control-Allow-Origin","*"),o.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),o.setHeader("Access-Control-Allow-Headers","Content-Type"),o.sendStatus(200)}),e.get("/__mock_switch__/env",(e,o)=>{t.setNoCacheHeaders(o),o.json({currentEnv:t.currentEnv,environments:Object.entries(t.options.environments).map(([e,t])=>({key:e,name:t.name,target:t.target}))})}),e.post("/__mock_switch__/env",(e,o)=>{t.setNoCacheHeaders(o);try{const{env:n}=e.body||{};t.options.environments[n]?(t.currentEnv=n,c().currentEnv=n,o.json({success:!0,currentEnv:t.currentEnv})):o.status(400).json({success:!1,error:"无效环境"})}catch(e){o.status(400).json({success:!1,error:e.message})}}),e.all(`${t.options.apiPrefix}/*`,(e,o,n)=>{switch(t.setNoCacheHeaders(o),t.currentEnv){case"mock":t.serveMockFile(e,o,n);break;case"dev":case"prod":t.serveProxyRequest(e,o,n);break;default:n()}})}handleControlApi(e,t,o){const n=this;if(n.setNoCacheHeaders(t),t.setHeader("Access-Control-Allow-Origin","*"),t.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type"),"OPTIONS"===e.method)return t.sendStatus(200);if("/__mock_switch__/env"===e.path&&"GET"===e.method)return t.json({currentEnv:n.currentEnv,environments:Object.entries(n.options.environments).map(([e,t])=>({key:e,name:t.name,target:t.target}))});if("/__mock_switch__/env"===e.path&&"POST"===e.method){if(e.body){const{env:o}=e.body;return n.options.environments[o]?(n.currentEnv=o,c().currentEnv=o,t.json({success:!0,currentEnv:n.currentEnv})):t.status(400).json({success:!1,error:"无效环境"})}let o="";return e.on("data",e=>o+=e),void e.on("end",()=>{try{const{env:e}=JSON.parse(o);n.options.environments[e]?(n.currentEnv=e,c().currentEnv=e,t.json({success:!0,currentEnv:n.currentEnv})):t.status(400).json({success:!1,error:"无效环境"})}catch(e){t.status(400).json({success:!1,error:e.message})}})}o()}parseBodyThenMock(e,t,o){const n=this;if(e.body)n.serveMockFile(e,t,o);else if("POST"===e.method||"PUT"===e.method||"PATCH"===e.method){let r="";e.on("data",e=>r+=e),e.on("end",()=>{try{e.body=r?JSON.parse(r):{}}catch(t){e.body={}}n.serveMockFile(e,t,o)})}else e.body={},n.serveMockFile(e,t,o)}serveProxyRequestDirect(e,t,n){const r=this,s=r.options.environments[r.currentEnv];if(!s||!s.target)return n();const{createProxyMiddleware:i}=o(16),a=i({target:s.target,changeOrigin:!0,pathRewrite:{[`^${r.options.apiPrefix}`]:""},onProxyReq:(e,t,o)=>{if(t.body&&Object.keys(t.body).length>0){const o=JSON.stringify(t.body);e.setHeader("Content-Type","application/json"),e.setHeader("Content-Length",Buffer.byteLength(o)),e.write(o)}},onProxyRes:(e,t,o)=>{delete e.headers.etag,delete e.headers["last-modified"]},onError:(e,t,o)=>{o.status(500).json({error:"代理请求失败",message:e.message,target:s.target})}});a(e,t,n)}setNoCacheHeaders(e){e.setHeader("Cache-Control","no-cache, no-store, must-revalidate, max-age=0"),e.setHeader("Pragma","no-cache"),e.setHeader("Expires","0"),e.setHeader("Access-Control-Allow-Origin","*"),e.removeHeader("ETag"),e.removeHeader("Last-Modified")}ensureMockDir(){r.existsSync(this.options.mockDir)||r.mkdirSync(this.options.mockDir,{recursive:!0})}startFileWatcher(){this.watcher||(this.watcher=s.watch(this.options.mockDir,{ignored:/(^|[\/\\])\../,persistent:!0,ignoreInitial:!0}),this.watcher.on("change",e=>{o.c[o(589).resolve(e)]&&delete o.c[o(589).resolve(e)]}))}async serveMockFile(e,t,n){const s=this,i=s.findMockFile(e.path,e.method);if(i&&r.existsSync(i))try{o.c[o(589).resolve(i)]&&delete o.c[o(589).resolve(i)];let n=o(589)(i);"function"==typeof n&&(n=await n(e,t));const r=n._delay||0;return delete n._delay,void setTimeout(()=>{s.setNoCacheHeaders(t),t.setHeader("Content-Type","application/json"),t.json(n)},r)}catch(e){}s.serveProxyRequestDirect(e,t,n)}async serveProxyRequest(e,t,n){const r=this,s=r.options.environments[r.currentEnv];if(!s||!s.target)return n();const{createProxyMiddleware:i}=o(16),a=i({target:s.target,changeOrigin:!0,pathRewrite:{[`^${r.options.apiPrefix}`]:""},onProxyReq:(e,t,o)=>{if(t.body&&Object.keys(t.body).length>0){const o=JSON.stringify(t.body);e.setHeader("Content-Type","application/json"),e.setHeader("Content-Length",Buffer.byteLength(o)),e.write(o)}},onProxyRes:(e,t,o)=>{delete e.headers.etag,delete e.headers["last-modified"]}});a(e,t,n)}findMockFile(e,t){const o=this,s=e.replace(o.options.apiPrefix,""),i=[n.join(o.options.mockDir,`${t.toUpperCase()}${s.replace(/\//g,"_")}.json`),n.join(o.options.mockDir,`${s.replace(/\//g,"_")}.json`),n.join(o.options.mockDir,`${t.toUpperCase()}${s.replace(/\//g,"_")}.js`),n.join(o.options.mockDir,`${s.replace(/\//g,"_")}.js`),n.join(o.options.mockDir,`${s}.json`),n.join(o.options.mockDir,`${s}.js`)];for(const e of i)if(r.existsSync(e))return e;return null}generateClientCode(){return';(function(){if(typeof window!==\'undefined\'&&!window.__MOCK_SWITCH_INITIALIZED__){window.__MOCK_SWITCH_INITIALIZED__=!0;const e=document.createElement("style");e.textContent=".mock-switch-panel{position:fixed!important;right:20px!important;bottom:20px!important;z-index:999999!important;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',sans-serif!important}.mock-switch-btn{width:56px!important;height:56px!important;border-radius:50%!important;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%)!important;border:3px solid white!important;cursor:pointer!important;box-shadow:0 8px 25px rgba(102,126,234,.5)!important;display:flex!important;align-items:center!important;justify-content:center!important;color:white!important;font-size:24px!important;position:relative!important;animation:mockPulse 2s infinite!important}.mock-switch-btn:hover{transform:scale(1.15)!important}@keyframes mockPulse{0%,100%{box-shadow:0 8px 25px rgba(102,126,234,.5)}50%{box-shadow:0 8px 35px rgba(102,126,234,.7)}}.mock-switch-dropdown{position:absolute!important;bottom:70px!important;right:0!important;background:white!important;border-radius:16px!important;box-shadow:0 15px 50px rgba(0,0,0,.25)!important;min-width:260px!important;display:none!important;animation:mockSlideUp .25s ease}.mock-switch-dropdown.show{display:block!important}@keyframes mockSlideUp{from{opacity:0;transform:translateY(20px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.mock-switch-header{padding:18px 24px!important;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%)!important;color:white!important;font-weight:600!important}.mock-switch-env-item{padding:14px 24px!important;cursor:pointer!important;display:flex!important;align-items:center!important;gap:14px!important;transition:all .2s ease}.mock-switch-env-item:hover{background:#f8f9fa!important}.mock-switch-env-item.active{background:#f0f4ff!important;color:#667eea!important}.mock-switch-env-item.active .dot{background:#667eea!important;box-shadow:0 0 0 4px rgba(102,126,234,.2)!important}.mock-switch-env-item .dot{width:10px!important;height:10px!important;border-radius:50%!important;background:#ddd!important;transition:all .2s ease}.mock-switch-status{position:absolute!important;top:-2px!important;right:-2px!important;width:18px!important;height:18px!important;border-radius:50%!important;border:3px solid white!important}.mock-switch-status.mock{background:#10b981!important}.mock-switch-status.dev{background:#f59e0b!important}.mock-switch-status.prod{background:#ef4444!important}",document.head.appendChild(e);const t=document.createElement("div");t.className="mock-switch-panel",t.innerHTML=\'<button class="mock-switch-btn" id="mockSwitchToggle" title="Mock环境切换">🎭<span class="mock-switch-status mock" id="mockSwitchStatus"></span></button><div class="mock-switch-dropdown" id="mockSwitchDropdown"><div class="mock-switch-header">🎯 环境切换面板</div><div id="mockSwitchEnvList"></div></div>\',document.body.appendChild(t);let n="mock";async function o(){try{const e=await fetch("/__mock_switch__/env?t="+Date.now(),{cache:"no-store"}),t=await e.json();n=t.currentEnv,i(t.environments),s()}catch(e){}}function i(e){const t=document.getElementById("mockSwitchEnvList");t.innerHTML=e.map(e=>\'<div class="mock-switch-env-item \'+(e.key===n?"active":"")+\'" data-env="\'+e.key+\'"><span class="dot"></span><span>\'+e.name+"</span></div>").join(""),t.querySelectorAll(".mock-switch-env-item").forEach(t=>{t.addEventListener("click",()=>c(t.dataset.env))})}async function c(e){try{await fetch("/__mock_switch__/env",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({env:e})}),n=e,s(),o()}catch(e){}}function s(){const e=document.getElementById("mockSwitchStatus");e.className="mock-switch-status "+n}document.getElementById("mockSwitchToggle").addEventListener("click",e=>{e.stopPropagation();document.getElementById("mockSwitchDropdown").classList.toggle("show")}),document.addEventListener("click",()=>{document.getElementById("mockSwitchDropdown").classList.remove("show")}),document.getElementById("mockSwitchDropdown").addEventListener("click",e=>{e.stopPropagation()}),o()}})();'}}e.exports=d,e.exports.default=d,e.exports.createMockMiddleware=function(e,t={}){return i||(i=new d({...t,autoRegisterMiddleware:!1,hotReload:!0}),i.registerRoutesWithBodyParser(e),i)}},589(e){function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=589,e.exports=t},231(e){"use strict";e.exports=require("chokidar")},16(e){"use strict";e.exports=require("http-proxy-middleware")},896(e){"use strict";e.exports=require("fs")},928(e){"use strict";e.exports=require("path")}},t={};function o(n){var r=t[n];if(void 0!==r)return r.exports;var s=t[n]={id:n,loaded:!1,exports:{}};return e[n](s,s.exports,o),s.loaded=!0,s.exports}o.c=t,o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var n=o(497);module.exports=n})();
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "mock-switch-webpack-plugin",
3
+ "version": "1.0.0",
4
+ "description": "前端Mock神器!浏览器端一键切换Mock/开发/生产环境的Webpack插件,支持接口代理、Mock数据热更新、POST请求自动转发等特性,零配置接入React/Vue等所有Webpack项目,彻底告别手动修改代码切换环境的低效开发方式",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "webpack --mode production",
8
+ "prepublishOnly": "npm run build"
9
+ },
10
+ "keywords": [
11
+ "环境切换",
12
+ "mock-switch",
13
+ "mock",
14
+ "接口模拟",
15
+ "proxy",
16
+ "hot-mock",
17
+ "前端接口代理",
18
+ "Mock一键切换",
19
+ "webpack-mock",
20
+ "env-switch",
21
+ "Mock热更新",
22
+ "前端Mock神器"
23
+ ],
24
+ "author": "lebron_shi",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/shilimingY/mock-switch-webpack-plugin.git"
29
+ },
30
+ "engines": {
31
+ "node": ">=14.0.0"
32
+ },
33
+ "peerDependencies": {
34
+ "webpack": ">=5.0.0"
35
+ },
36
+ "dependencies": {
37
+ "chokidar": "^3.6.0",
38
+ "http-proxy-middleware": "^2.0.6"
39
+ },
40
+ "devDependencies": {
41
+ "@babel/core": "^7.24.0",
42
+ "@babel/preset-env": "^7.24.0",
43
+ "babel-loader": "^9.1.3",
44
+ "webpack": "^5.90.0",
45
+ "webpack-cli": "^5.1.4",
46
+ "webpack-node-externals": "^3.0.0"
47
+ },
48
+ "files": ["dist/"]
49
+ }