55a0d7227f04adcbf3f9406b7a37aaef7e04a2efbf2d3dda093d63c9b8f98e37cacb254160bf64c2d47ce4590ef76bce9dc62fd1779ab1f7e42baf8a131db0 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. var __defProp = Object.defineProperty;
  2. var __hasOwnProp = Object.prototype.hasOwnProperty;
  3. var __getOwnPropSymbols = Object.getOwnPropertySymbols;
  4. var __propIsEnum = Object.prototype.propertyIsEnumerable;
  5. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {enumerable: true, configurable: true, writable: true, value}) : obj[key] = value;
  6. var __assign = (a, b) => {
  7. for (var prop in b || (b = {}))
  8. if (__hasOwnProp.call(b, prop))
  9. __defNormalProp(a, prop, b[prop]);
  10. if (__getOwnPropSymbols)
  11. for (var prop of __getOwnPropSymbols(b)) {
  12. if (__propIsEnum.call(b, prop))
  13. __defNormalProp(a, prop, b[prop]);
  14. }
  15. return a;
  16. };
  17. // src/index.ts
  18. import path2 from "path";
  19. // src/utils.ts
  20. import fs from "fs";
  21. var toString = Object.prototype.toString;
  22. function is(val, type) {
  23. return toString.call(val) === `[object ${type}]`;
  24. }
  25. function isFunction(val) {
  26. return is(val, "Function") || is(val, "AsyncFunction");
  27. }
  28. function isArray(val) {
  29. return val && Array.isArray(val);
  30. }
  31. function isRegExp(val) {
  32. return is(val, "RegExp");
  33. }
  34. function sleep(time) {
  35. return new Promise((resolve) => {
  36. setTimeout(() => {
  37. resolve("");
  38. }, time);
  39. });
  40. }
  41. function fileExists(f) {
  42. try {
  43. fs.accessSync(f, fs.constants.W_OK);
  44. return true;
  45. } catch (error) {
  46. return false;
  47. }
  48. }
  49. // src/index.ts
  50. import {normalizePath} from "vite";
  51. // src/createMockServer.ts
  52. import path from "path";
  53. import fs2 from "fs";
  54. import chokidar from "chokidar";
  55. import chalk from "chalk";
  56. import url from "url";
  57. import fg from "fast-glob";
  58. import Mock from "mockjs";
  59. import {build} from "esbuild";
  60. import {pathToRegexp, match} from "path-to-regexp";
  61. import module from "module";
  62. var mockData = [];
  63. async function createMockServer(opt = {mockPath: "mock", configPath: "vite.mock.config"}) {
  64. opt = __assign({
  65. mockPath: "mock",
  66. watchFiles: true,
  67. supportTs: true,
  68. configPath: "vite.mock.config.ts",
  69. logger: true
  70. }, opt);
  71. if (mockData.length > 0)
  72. return;
  73. mockData = await getMockConfig(opt);
  74. await createWatch(opt);
  75. }
  76. async function requestMiddleware(opt) {
  77. const {logger = true} = opt;
  78. const middleware = async (req, res, next) => {
  79. let queryParams = {};
  80. if (req.url) {
  81. queryParams = url.parse(req.url, true);
  82. }
  83. const reqUrl = queryParams.pathname;
  84. const matchRequest = mockData.find((item) => {
  85. if (!reqUrl || !item || !item.url) {
  86. return false;
  87. }
  88. if (item.method && item.method.toUpperCase() !== req.method) {
  89. return false;
  90. }
  91. return pathToRegexp(item.url).test(reqUrl);
  92. });
  93. if (matchRequest) {
  94. const isGet = req.method && req.method.toUpperCase() === "GET";
  95. const {response, rawResponse, timeout, statusCode, url: url2} = matchRequest;
  96. if (timeout) {
  97. await sleep(timeout);
  98. }
  99. const urlMatch = match(url2, {decode: decodeURIComponent});
  100. let query = queryParams.query;
  101. if (reqUrl) {
  102. if (isGet && JSON.stringify(query) === "{}" || !isGet) {
  103. const params = urlMatch(reqUrl).params;
  104. if (JSON.stringify(params) !== "{}") {
  105. query = urlMatch(reqUrl).params || {};
  106. } else {
  107. query = queryParams.query || {};
  108. }
  109. }
  110. }
  111. const self = {req, res, parseJson: parseJson.bind(null, req)};
  112. if (isFunction(rawResponse)) {
  113. await rawResponse.bind(self)(req, res);
  114. } else {
  115. const body = await parseJson(req);
  116. res.setHeader("Content-Type", "application/json");
  117. res.statusCode = statusCode || 200;
  118. const mockResponse = isFunction(response) ? response.bind(self)({url: req.url, body, query, headers: req.headers}) : response;
  119. res.end(JSON.stringify(Mock.mock(mockResponse)));
  120. }
  121. logger && loggerOutput("request invoke", req.url);
  122. return;
  123. }
  124. next();
  125. };
  126. return middleware;
  127. }
  128. function createWatch(opt) {
  129. const {configPath, logger, watchFiles} = opt;
  130. if (!watchFiles) {
  131. return;
  132. }
  133. const {absConfigPath, absMockPath} = getPath(opt);
  134. if (process.env.VITE_DISABLED_WATCH_MOCK === "true") {
  135. return;
  136. }
  137. const watchDir = [];
  138. const exitsConfigPath = fs2.existsSync(absConfigPath);
  139. exitsConfigPath && configPath ? watchDir.push(absConfigPath) : watchDir.push(absMockPath);
  140. const watcher = chokidar.watch(watchDir, {
  141. ignoreInitial: true
  142. });
  143. watcher.on("all", async (event, file) => {
  144. logger && loggerOutput(`mock file ${event}`, file);
  145. mockData = await getMockConfig(opt);
  146. });
  147. }
  148. function cleanRequireCache(opt) {
  149. if (!require.cache) {
  150. return;
  151. }
  152. const {absConfigPath, absMockPath} = getPath(opt);
  153. Object.keys(require.cache).forEach((file) => {
  154. if (file === absConfigPath || file.indexOf(absMockPath) > -1) {
  155. delete require.cache[file];
  156. }
  157. });
  158. }
  159. function parseJson(req) {
  160. return new Promise((resolve) => {
  161. let body = "";
  162. let jsonStr = "";
  163. req.on("data", function(chunk) {
  164. body += chunk;
  165. });
  166. req.on("end", function() {
  167. try {
  168. jsonStr = JSON.parse(body);
  169. } catch (err) {
  170. jsonStr = "";
  171. }
  172. resolve(jsonStr);
  173. return;
  174. });
  175. });
  176. }
  177. async function getMockConfig(opt) {
  178. cleanRequireCache(opt);
  179. const {absConfigPath, absMockPath} = getPath(opt);
  180. const {ignore, configPath, logger} = opt;
  181. let ret = [];
  182. if (configPath && fs2.existsSync(absConfigPath)) {
  183. logger && loggerOutput(`load mock data from`, absConfigPath);
  184. ret = await resolveModule(absConfigPath);
  185. return ret;
  186. }
  187. const mockFiles = fg.sync(`**/*.{ts,js}`, {
  188. cwd: absMockPath
  189. }).filter((item) => {
  190. if (!ignore) {
  191. return true;
  192. }
  193. if (isFunction(ignore)) {
  194. return ignore(item);
  195. }
  196. if (isRegExp(ignore)) {
  197. return !ignore.test(path.basename(item));
  198. }
  199. return true;
  200. });
  201. try {
  202. ret = [];
  203. const resolveModulePromiseList = [];
  204. for (let index = 0; index < mockFiles.length; index++) {
  205. const mockFile = mockFiles[index];
  206. resolveModulePromiseList.push(resolveModule(path.join(absMockPath, mockFile)));
  207. }
  208. const loadAllResult = await Promise.all(resolveModulePromiseList);
  209. for (const resultModule of loadAllResult) {
  210. let mod = resultModule;
  211. if (!isArray(mod)) {
  212. mod = [mod];
  213. }
  214. ret = [...ret, ...mod];
  215. }
  216. } catch (error) {
  217. loggerOutput(`mock reload error`, error);
  218. ret = [];
  219. }
  220. return ret;
  221. }
  222. async function resolveModule(p) {
  223. const result = await build({
  224. entryPoints: [p],
  225. outfile: "out.js",
  226. write: false,
  227. platform: "node",
  228. bundle: true,
  229. format: "cjs",
  230. metafile: true,
  231. target: "es2015"
  232. });
  233. const {text} = result.outputFiles[0];
  234. return await loadConfigFromBundledFile(p, text);
  235. }
  236. function getPath(opt) {
  237. const {mockPath, configPath} = opt;
  238. const cwd = process.cwd();
  239. const absMockPath = path.join(cwd, mockPath || "");
  240. const absConfigPath = path.join(cwd, configPath || "");
  241. return {
  242. absMockPath,
  243. absConfigPath
  244. };
  245. }
  246. function loggerOutput(title, msg, type = "info") {
  247. const tag = type === "info" ? chalk.cyan.bold(`[vite:mock]`) : chalk.red.bold(`[vite:mock-server]`);
  248. return console.log(`${chalk.dim(new Date().toLocaleTimeString())} ${tag} ${chalk.green(title)} ${chalk.dim(msg)}`);
  249. }
  250. async function loadConfigFromBundledFile(fileName, bundledCode) {
  251. const extension = path.extname(fileName);
  252. const extensions = module.Module._extensions;
  253. let defaultLoader;
  254. const isJs = extension === ".js";
  255. if (isJs) {
  256. defaultLoader = extensions[extension];
  257. }
  258. extensions[extension] = (module2, filename) => {
  259. if (filename === fileName) {
  260. ;
  261. module2._compile(bundledCode, filename);
  262. } else {
  263. if (!isJs) {
  264. extensions[extension](module2, filename);
  265. } else {
  266. defaultLoader(module2, filename);
  267. }
  268. }
  269. };
  270. let config;
  271. try {
  272. if (isJs && require && require.cache) {
  273. delete require.cache[fileName];
  274. }
  275. const raw = require(fileName);
  276. config = raw.__esModule ? raw.default : raw;
  277. if (defaultLoader && isJs) {
  278. extensions[extension] = defaultLoader;
  279. }
  280. } catch (error) {
  281. console.error(error);
  282. }
  283. return config;
  284. }
  285. // src/index.ts
  286. (async () => {
  287. try {
  288. await import("mockjs");
  289. } catch (e) {
  290. throw new Error("vite-plugin-vue-mock requires mockjs to be present in the dependency tree.");
  291. }
  292. })();
  293. function getDefaultPath(supportTs = true) {
  294. return path2.resolve(process.cwd(), `src/main.${supportTs ? "ts" : "js"}`);
  295. }
  296. function viteMockServe(opt = {}) {
  297. let defaultPath = getDefaultPath();
  298. if (!fileExists(defaultPath)) {
  299. defaultPath = getDefaultPath(false);
  300. if (!fileExists(defaultPath)) {
  301. defaultPath = "";
  302. }
  303. }
  304. const defaultEnter = normalizePath(defaultPath);
  305. const {injectFile = defaultEnter} = opt;
  306. let isDev = false;
  307. let config;
  308. let needSourcemap = false;
  309. return {
  310. name: "vite:mock",
  311. enforce: "pre",
  312. configResolved(resolvedConfig) {
  313. config = resolvedConfig;
  314. isDev = config.command === "serve";
  315. needSourcemap = !!resolvedConfig.build.sourcemap;
  316. isDev && createMockServer(opt);
  317. },
  318. configureServer: async ({middlewares}) => {
  319. const {localEnabled = isDev} = opt;
  320. if (!localEnabled) {
  321. return;
  322. }
  323. const middleware = await requestMiddleware(opt);
  324. middlewares.use(middleware);
  325. },
  326. async transform(code, id) {
  327. if (isDev || !injectFile || !id.endsWith(injectFile)) {
  328. return null;
  329. }
  330. const {prodEnabled = true, injectCode = ""} = opt;
  331. if (!prodEnabled) {
  332. return null;
  333. }
  334. return {
  335. map: needSourcemap ? this.getCombinedSourcemap() : null,
  336. code: `${code}
  337. ${injectCode}`
  338. };
  339. }
  340. };
  341. }
  342. export {
  343. viteMockServe
  344. };