mock-switch-webpack-plugin 1.5.2 → 1.5.4

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.
Files changed (3) hide show
  1. package/README.md +40 -10
  2. package/dist/index.js +1 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -135,21 +135,51 @@ module.exports = function(app) {
135
135
 
136
136
  ### 方式三:Vue CLI 项目
137
137
 
138
+ Vue CLI 项目需要在 vue\.config\.js 中同时配置 **插件实例** 和 **devServer** 中间件 两部分。
139
+
138
140
  **vue\.config\.js**
139
141
 
140
142
  ```javascript
141
- const MockSwitchWebpackPlugin = require('mock-switch-webpack-plugin');
143
+ const { defineConfig } = require("@vue/cli-service");
144
+ const path = require("path");
145
+ const MockSwitchWebpackPlugin = require("mock-switch-webpack-plugin");
142
146
 
143
- module.exports = {
147
+ // 1. 创建插件实例(共享同一份配置)
148
+ const mockPlugin = new MockSwitchWebpackPlugin({
149
+ // 配置项
150
+ });
151
+
152
+ module.exports = defineConfig({
153
+ transpileDependencies: true,
154
+
155
+ // 2. 配置插件:注入客户端 UI 代码
144
156
  configureWebpack: {
145
- plugins: [
146
- new MockSwitchWebpackPlugin({
147
- // 配置项
148
- })
149
- ]
150
- }
151
- };
157
+ plugins: [mockPlugin],
158
+ },
159
+
160
+ // 3. 配置 devServer:注册服务端路由(在 Express 服务上注册 Mock 数据返回、代理转发等逻辑)
161
+ devServer: {
162
+ setupMiddlewares: (middlewares, devServer) => {
163
+ // 获取 Express app 实例
164
+ const app = devServer.app;
165
+
166
+ // 注册中间件
167
+ mockPlugin.registerRoutesWithBodyParser(app);
168
+
169
+ return middlewares;
170
+ },
171
+ },
172
+ });
152
173
  ```
174
+ > 💡 **setupMiddlewares 作用说明**:
175
+ >
176
+ > - Vue CLI 内部对 devServer 配置有独立的处理流程,插件通过 compiler.options.devServer 修改的配置会被 Vue CLI 覆盖,导致自动注册的中间件不生效;
177
+ >
178
+ > - 因此需要在 setupMiddlewares 钩子中手动调用 registerRoutesWithBodyParser() 来完成路由注册;
179
+ >
180
+ > - 这是 Vue CLI 环境下的标准做法,确保 Mock 路由能正确接管 API 请求;
181
+ >
182
+ >
153
183
 
154
184
  ### 启动开发服务
155
185
 
@@ -158,7 +188,7 @@ npm run start
158
188
  ```
159
189
 
160
190
  - 打开浏览器,你会看到右下角的悬浮切换按钮
161
- - 点击按钮展开控制面板,实现环境实时切换 🎉
191
+ - 点击按钮展开控制面板,进行环境实时切换 🎉🎉🎉
162
192
 
163
193
  ## 配置说明
164
194
  ### 完整配置项
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- (()=>{var e={497(e,t,o){const n="undefined"!=typeof require?require:o(589),r=n("path"),s=n("fs"),i=n("chokidar");let a=null;const c="__MOCK_SWITCH_INSTANCE__";function d(){return global[c]||(global[c]={currentEnv:"mock"}),global[c]}class p{constructor(e={}){var t,o,n,s,i,a;const c=e.environments||{},p={};p.mock={name:"🟢 "+((null===(t=c.mock)||void 0===t?void 0:t.name)||"Mock环境"),target:(null===(o=c.mock)||void 0===o?void 0:o.target)??null},p.dev={name:"🟡 "+((null===(n=c.dev)||void 0===n?void 0:n.name)||"开发环境"),target:(null===(s=c.dev)||void 0===s?void 0:s.target)??null},p.prod={name:"🔴 "+((null===(i=c.prod)||void 0===i?void 0:i.name)||"生产环境"),target:(null===(a=c.prod)||void 0===a?void 0:a.target)??null},this.options={mockDir:r.resolve(process.cwd(),"./mock"),apiPrefix:"/api",environments:p,hotReload:!0,autoRegisterMiddleware:!0,...e,environments:p};const l=d();this.currentEnv=l.currentEnv,this.watcher=null,console.log("%s","\n╔══════════════════════════════════════════════════════════════╗\n║ Mock Environment Switch Toolkit ║\n║ ║\n║ Frontend API Mock & Proxy Management ║\n║ One-Click Environment Switching ║\n║ ║\n╚══════════════════════════════════════════════════════════════╝\n ")}apply(e){const{webpack:t}=e,{RawSource:o}=t.sources;if(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){const t=process.env.VUE_CLI_SERVICE||(e.options.plugins||[]).some(e=>e.constructor&&"VueLoaderPlugin"===e.constructor.name);e.hooks.afterEnvironment.tap("MockSwitchWebpackPlugin",()=>{e.options.devServer||(e.options.devServer={});const o=e.options.devServer;o.historyApiFallback||(o.historyApiFallback={}),"object"==typeof o.historyApiFallback&&(o.historyApiFallback.rewrites=o.historyApiFallback.rewrites||[],o.historyApiFallback.rewrites.unshift({from:/^\/(__mock_switch__|api)\//,to:e=>e.parsedUrl.pathname}));const n=this;if(t){console.log("%s","[Mock Switch] Vue CLI 环境,自动注册中间件");const e=o.setupMiddlewares;o.setupMiddlewares=function(t,o){return n.registerRoutesWithBodyParser(o.app),"function"==typeof e?e(t,o):t}}else if("function"!=typeof o.setupMiddlewares&&o.setupMiddlewares)if("function"!=typeof o.onBeforeSetupMiddleware&&o.onBeforeSetupMiddleware)if("function"!=typeof o.before&&o.before)console.log("%s","[Mock Switch] 请使用 createMockMiddleware() 手动注册");else{const e=o.before;o.before=(t,o,r)=>{n.registerRoutesWithBodyParser(t),"function"==typeof e&&e(t,o,r)}}else{const e=o.onBeforeSetupMiddleware;o.onBeforeSetupMiddleware=t=>{n.registerRoutesWithBodyParser(t.app),"function"==typeof e&&e(t)}}else{const e=o.setupMiddlewares;o.setupMiddlewares=(t,o)=>(t.unshift((e,t,o)=>{if(e.path.startsWith("/__mock_switch__"))return n.handleControlApi(e,t,o);if(e.path.startsWith(n.options.apiPrefix))switch(n.setNoCacheHeaders(t),n.currentEnv){case"mock":return n.parseBodyThenMock(e,t,o);case"dev":case"prod":return n.serveProxyRequestDirect(e,t,o);default:return o()}o()}),"function"==typeof e?e(t,o):t)}})}this.options.hotReload&&this.startFileWatcher()}registerRoutesWithBodyParser(e){const t=this;try{const t=n("express");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,d().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,d().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,d().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,o){const r=this,s=r.options.environments[r.currentEnv];if(!s||!s.target)return o();const{createProxyMiddleware:i}=n("http-proxy-middleware"),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,o)}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(){s.existsSync(this.options.mockDir)||s.mkdirSync(this.options.mockDir,{recursive:!0})}startFileWatcher(){this.watcher||(this.watcher=i.watch(this.options.mockDir,{ignored:/(^|[\/\\])\../,persistent:!0,ignoreInitial:!0}),this.watcher.on("change",e=>{n.cache[n.resolve(e)]&&delete n.cache[n.resolve(e)]}))}async serveMockFile(e,t,o){const r=this,i=r.findMockFile(e.path,e.method);if(i&&s.existsSync(i))try{n.cache[n.resolve(i)]&&delete n.cache[n.resolve(i)];let o=n(i);"function"==typeof o&&(o=await o(e,t));const s=o._delay||0;return delete o._delay,void setTimeout(()=>{r.setNoCacheHeaders(t),t.setHeader("Content-Type","application/json"),t.json(o)},s)}catch(e){}r.serveProxyRequestDirect(e,t,o)}async serveProxyRequest(e,t,o){const r=this,s=r.options.environments[r.currentEnv];if(!s||!s.target)return o();const{createProxyMiddleware:i}=n("http-proxy-middleware"),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,o)}findMockFile(e,t){const o=this,n=e.replace(o.options.apiPrefix,""),i=[r.join(o.options.mockDir,`${t.toUpperCase()}${n.replace(/\//g,"_")}.json`),r.join(o.options.mockDir,`${n.replace(/\//g,"_")}.json`),r.join(o.options.mockDir,`${t.toUpperCase()}${n.replace(/\//g,"_")}.js`),r.join(o.options.mockDir,`${n.replace(/\//g,"_")}.js`),r.join(o.options.mockDir,`${n}.json`),r.join(o.options.mockDir,`${n}.js`)];for(const e of i)if(s.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:skyblue!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;display:flex!important;align-items:center!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环境切换"><?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1781758652061" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3587" width="27" height="27" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M128.07168 808.96V399.70816h3.328L306.13504 808.96h84.04992l174.73536-409.25184h3.328V808.96H665.6V215.04H551.60832L350.23872 680.8576h-3.328L144.71168 215.04H30.72v593.92z" fill="#000000" p-id="3588"></path><path d="M737.28 460.8h276.48v112.64H737.28z" fill="#000000" p-id="3589"></path><path d="M931.84 327.68v481.28H819.2V327.68z" fill="#000000" p-id="3590"></path></svg><span class="mock-switch-status mock" id="mockSwitchStatus"></span></button><div class="mock-switch-dropdown" id="mockSwitchDropdown"><div class="mock-switch-header"><?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1781760143547" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4350" width="35" height="20" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M693.686857 834.56c17.188571-0.950857 38.912-0.950857 65.097143 0h208.018286a34.779429 34.779429 0 0 1 0 69.595429h-209.554286l-1.353143 62.72a20.845714 20.845714 0 0 1-36.461714 13.385142l-72.96-82.285714a37.632 37.632 0 0 1-10.605714-27.465143v-1.170285c0-19.2 15.616-34.742857 34.816-34.742858h23.003428z" fill="#e6e6e6" p-id="4351"></path><path d="M512 42.678857c259.218286 0 469.321143 210.102857 469.321143 469.321143 0 14.189714-0.621714 28.196571-1.828572 42.057143-1.645714 17.956571-17.115429 31.085714-35.108571 31.085714h-6.217143c-22.966857 0-40.118857-20.918857-38.4-43.776a389.705143 389.705143 0 0 0-10.313143-123.245714H709.12a1084.854857 1084.854857 0 0 1 0 187.721143l-2.121143 16.603428a73.142857 73.142857 0 0 1-72.557714 63.890286h-227.328c24.064 128.621714 70.729143 214.564571 104.886857 214.564571 6.034286 0 22.381714-1.097143 38.070857-2.194285a41.801143 41.801143 0 0 1 43.593143 31.414857c5.449143 21.723429-7.168 44.946286-29.257143 48.64-14.08 2.340571-27.062857 2.56-52.406857 2.56-259.218286 0-469.321143-210.102857-469.321143-469.321143S252.781714 42.678857 512 42.678857z m-347.721143 643.657143a390.144 390.144 0 0 0 223.158857 194.194286c-27.428571-50.541714-49.078857-117.394286-62.244571-194.194286h-160.914286z m-29.732571-268.178286a389.705143 389.705143 0 0 0 0 187.684572H314.88a1084.489143 1084.489143 0 0 1 0-187.684572H134.582857z m261.229714 0a982.089143 982.089143 0 0 0 0 187.684572h232.448a980.845714 980.845714 0 0 0 0-187.684572h-232.448z m-8.338286-274.651428a390.144 390.144 0 0 0-223.158857 194.194285h160.914286c13.165714-76.873143 34.816-143.689143 62.244571-194.194285zM512 123.099429c-34.157714 0-80.822857 85.942857-104.886857 214.564571h209.810286C592.859429 209.042286 546.194286 123.099429 512 123.099429z m124.598857 20.406857c27.428571 50.505143 49.042286 117.321143 62.208 194.194285h160.914286a390.070857 390.070857 0 0 0-223.085714-194.194285z" fill="#e6e6e6" p-id="4352"></path><path d="M903.606857 636.818286a20.845714 20.845714 0 0 1 15.616 7.021714l71.789714 80.932571c7.314286 7.570286 10.861714 17.554286 10.569143 27.465143v1.206857a34.779429 34.779429 0 0 1-34.742857 34.742858h-296.155428a34.779429 34.779429 0 0 1 0-69.558858h212.077714v-60.964571a20.845714 20.845714 0 0 1 20.845714-20.845714z" fill="#e6e6e6" p-id="4353"></path></svg>环境切换面板</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=p,e.exports.default=p,e.exports.createMockMiddleware=function(e,t={}){return a||(a=new p({...t,autoRegisterMiddleware:!1,hotReload:!0}),a.registerRoutesWithBodyParser(e),a)}},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}},t={};function o(n){var r=t[n];if(void 0!==r)return r.exports;var s=t[n]={exports:{}};return e[n](s,s.exports,o),s.exports}o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var n=o(497);module.exports=n})();
1
+ (()=>{var e={497(e,t,o){const n="undefined"!=typeof require?require:o(589),r=n("path"),s=n("fs"),i=n("chokidar");let a=null;const c="__MOCK_SWITCH_INSTANCE__";function d(){return global[c]||(global[c]={currentEnv:"mock"}),global[c]}class p{constructor(e={}){var t,o,n,s,i,a;const c=e.environments||{},p={};p.mock={name:"🟢 "+((null===(t=c.mock)||void 0===t?void 0:t.name)||"Mock环境"),target:(null===(o=c.mock)||void 0===o?void 0:o.target)??null},p.dev={name:"🟡 "+((null===(n=c.dev)||void 0===n?void 0:n.name)||"开发环境"),target:(null===(s=c.dev)||void 0===s?void 0:s.target)??null},p.prod={name:"🔴 "+((null===(i=c.prod)||void 0===i?void 0:i.name)||"生产环境"),target:(null===(a=c.prod)||void 0===a?void 0:a.target)??null},this.options={mockDir:r.resolve(process.cwd(),"./mock"),apiPrefix:"/api",environments:p,hotReload:!0,autoRegisterMiddleware:!0,...e,environments:p};const l=d();this.currentEnv=l.currentEnv,this.watcher=null,console.log("%s","\n╔══════════════════════════════════════════════════════════════╗\n║ Mock Environment Switch Toolkit ║\n║ ║\n║ Frontend API Mock & Proxy Management ║\n║ One-Click Environment Switching ║\n║ ║\n╚══════════════════════════════════════════════════════════════╝\n ")}apply(e){const{webpack:t}=e,{RawSource:o}=t.sources;if(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){if(process.env.VUE_CLI_SERVICE||(e.options.plugins||[]).some(e=>e.constructor&&"VueLoaderPlugin"===e.constructor.name))return void console.log("%s","[Mock Switch] 检测到 Vue CLI 环境,请在 devServer.setupMiddlewares 中手动注册");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=n("express");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,d().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,d().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,d().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,o){const r=this,s=r.options.environments[r.currentEnv];if(!s||!s.target)return o();const{createProxyMiddleware:i}=n("http-proxy-middleware"),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,o)}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(){s.existsSync(this.options.mockDir)||s.mkdirSync(this.options.mockDir,{recursive:!0})}startFileWatcher(){this.watcher||(this.watcher=i.watch(this.options.mockDir,{ignored:/(^|[\/\\])\../,persistent:!0,ignoreInitial:!0}),this.watcher.on("change",e=>{n.cache[n.resolve(e)]&&delete n.cache[n.resolve(e)]}))}async serveMockFile(e,t,o){const r=this,i=r.findMockFile(e.path,e.method);if(i&&s.existsSync(i))try{n.cache[n.resolve(i)]&&delete n.cache[n.resolve(i)];let o=n(i);"function"==typeof o&&(o=await o(e,t));const s=o._delay||0;return delete o._delay,void setTimeout(()=>{r.setNoCacheHeaders(t),t.setHeader("Content-Type","application/json"),t.json(o)},s)}catch(e){}r.serveProxyRequestDirect(e,t,o)}async serveProxyRequest(e,t,o){const r=this,s=r.options.environments[r.currentEnv];if(!s||!s.target)return o();const{createProxyMiddleware:i}=n("http-proxy-middleware"),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,o)}findMockFile(e,t){const o=this,n=e.replace(o.options.apiPrefix,""),i=[r.join(o.options.mockDir,`${t.toUpperCase()}${n.replace(/\//g,"_")}.json`),r.join(o.options.mockDir,`${n.replace(/\//g,"_")}.json`),r.join(o.options.mockDir,`${t.toUpperCase()}${n.replace(/\//g,"_")}.js`),r.join(o.options.mockDir,`${n.replace(/\//g,"_")}.js`),r.join(o.options.mockDir,`${n}.json`),r.join(o.options.mockDir,`${n}.js`)];for(const e of i)if(s.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:skyblue!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;display:flex!important;align-items:center!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环境切换"><?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1781758652061" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3587" width="27" height="27" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M128.07168 808.96V399.70816h3.328L306.13504 808.96h84.04992l174.73536-409.25184h3.328V808.96H665.6V215.04H551.60832L350.23872 680.8576h-3.328L144.71168 215.04H30.72v593.92z" fill="#000000" p-id="3588"></path><path d="M737.28 460.8h276.48v112.64H737.28z" fill="#000000" p-id="3589"></path><path d="M931.84 327.68v481.28H819.2V327.68z" fill="#000000" p-id="3590"></path></svg><span class="mock-switch-status mock" id="mockSwitchStatus"></span></button><div class="mock-switch-dropdown" id="mockSwitchDropdown"><div class="mock-switch-header"><?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1781760143547" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4350" width="35" height="20" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M693.686857 834.56c17.188571-0.950857 38.912-0.950857 65.097143 0h208.018286a34.779429 34.779429 0 0 1 0 69.595429h-209.554286l-1.353143 62.72a20.845714 20.845714 0 0 1-36.461714 13.385142l-72.96-82.285714a37.632 37.632 0 0 1-10.605714-27.465143v-1.170285c0-19.2 15.616-34.742857 34.816-34.742858h23.003428z" fill="#e6e6e6" p-id="4351"></path><path d="M512 42.678857c259.218286 0 469.321143 210.102857 469.321143 469.321143 0 14.189714-0.621714 28.196571-1.828572 42.057143-1.645714 17.956571-17.115429 31.085714-35.108571 31.085714h-6.217143c-22.966857 0-40.118857-20.918857-38.4-43.776a389.705143 389.705143 0 0 0-10.313143-123.245714H709.12a1084.854857 1084.854857 0 0 1 0 187.721143l-2.121143 16.603428a73.142857 73.142857 0 0 1-72.557714 63.890286h-227.328c24.064 128.621714 70.729143 214.564571 104.886857 214.564571 6.034286 0 22.381714-1.097143 38.070857-2.194285a41.801143 41.801143 0 0 1 43.593143 31.414857c5.449143 21.723429-7.168 44.946286-29.257143 48.64-14.08 2.340571-27.062857 2.56-52.406857 2.56-259.218286 0-469.321143-210.102857-469.321143-469.321143S252.781714 42.678857 512 42.678857z m-347.721143 643.657143a390.144 390.144 0 0 0 223.158857 194.194286c-27.428571-50.541714-49.078857-117.394286-62.244571-194.194286h-160.914286z m-29.732571-268.178286a389.705143 389.705143 0 0 0 0 187.684572H314.88a1084.489143 1084.489143 0 0 1 0-187.684572H134.582857z m261.229714 0a982.089143 982.089143 0 0 0 0 187.684572h232.448a980.845714 980.845714 0 0 0 0-187.684572h-232.448z m-8.338286-274.651428a390.144 390.144 0 0 0-223.158857 194.194285h160.914286c13.165714-76.873143 34.816-143.689143 62.244571-194.194285zM512 123.099429c-34.157714 0-80.822857 85.942857-104.886857 214.564571h209.810286C592.859429 209.042286 546.194286 123.099429 512 123.099429z m124.598857 20.406857c27.428571 50.505143 49.042286 117.321143 62.208 194.194285h160.914286a390.070857 390.070857 0 0 0-223.085714-194.194285z" fill="#e6e6e6" p-id="4352"></path><path d="M903.606857 636.818286a20.845714 20.845714 0 0 1 15.616 7.021714l71.789714 80.932571c7.314286 7.570286 10.861714 17.554286 10.569143 27.465143v1.206857a34.779429 34.779429 0 0 1-34.742857 34.742858h-296.155428a34.779429 34.779429 0 0 1 0-69.558858h212.077714v-60.964571a20.845714 20.845714 0 0 1 20.845714-20.845714z" fill="#e6e6e6" p-id="4353"></path></svg>环境切换面板</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=p,e.exports.default=p,e.exports.createMockMiddleware=function(e,t={}){return a||(a=new p({...t,autoRegisterMiddleware:!1,hotReload:!0}),a.registerRoutesWithBodyParser(e),a)}},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}},t={};function o(n){var r=t[n];if(void 0!==r)return r.exports;var s=t[n]={exports:{}};return e[n](s,s.exports,o),s.exports}o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var n=o(497);module.exports=n})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mock-switch-webpack-plugin",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "前端Mock神器!浏览器一键切换Mock/开发/生产环境的Webpack插件,支持接口代理、Mock数据热更新、POST请求自动转发等特性,零配置接入React/Vue等所有Webpack项目,彻底告别需手动修改代码来切换环境的低效开发方式。",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {