index.mjs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  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. if (isFunction(rawResponse)) {
  112. await rawResponse(req, res);
  113. } else {
  114. const body = await parseJson(req);
  115. const mockResponse = isFunction(response) ? response({url: req.url, body, query, headers: req.headers}) : response;
  116. res.setHeader("Content-Type", "application/json");
  117. res.statusCode = statusCode || 200;
  118. res.end(JSON.stringify(Mock.mock(mockResponse)));
  119. }
  120. logger && loggerOutput("request invoke", req.url);
  121. return;
  122. }
  123. next();
  124. };
  125. return middleware;
  126. }
  127. function createWatch(opt) {
  128. const {configPath, logger, watchFiles} = opt;
  129. if (!watchFiles) {
  130. return;
  131. }
  132. const {absConfigPath, absMockPath} = getPath(opt);
  133. if (process.env.VITE_DISABLED_WATCH_MOCK === "true") {
  134. return;
  135. }
  136. const watchDir = [];
  137. const exitsConfigPath = fs2.existsSync(absConfigPath);
  138. exitsConfigPath && configPath ? watchDir.push(absConfigPath) : watchDir.push(absMockPath);
  139. const watcher = chokidar.watch(watchDir, {
  140. ignoreInitial: true
  141. });
  142. watcher.on("all", async (event, file) => {
  143. logger && loggerOutput(`mock file ${event}`, file);
  144. mockData = await getMockConfig(opt);
  145. });
  146. }
  147. function cleanRequireCache(opt) {
  148. if (!require.cache) {
  149. return;
  150. }
  151. const {absConfigPath, absMockPath} = getPath(opt);
  152. Object.keys(require.cache).forEach((file) => {
  153. if (file === absConfigPath || file.indexOf(absMockPath) > -1) {
  154. delete require.cache[file];
  155. }
  156. });
  157. }
  158. function parseJson(req) {
  159. return new Promise((resolve) => {
  160. let body = "";
  161. let jsonStr = "";
  162. req.on("data", function(chunk) {
  163. body += chunk;
  164. });
  165. req.on("end", function() {
  166. try {
  167. jsonStr = JSON.parse(body);
  168. } catch (err) {
  169. jsonStr = "";
  170. }
  171. resolve(jsonStr);
  172. return;
  173. });
  174. });
  175. }
  176. async function getMockConfig(opt) {
  177. cleanRequireCache(opt);
  178. const {absConfigPath, absMockPath} = getPath(opt);
  179. const {ignore, configPath, logger} = opt;
  180. let ret = [];
  181. if (configPath && fs2.existsSync(absConfigPath)) {
  182. logger && loggerOutput(`load mock data from`, absConfigPath);
  183. ret = await resolveModule(absConfigPath);
  184. return ret;
  185. }
  186. const mockFiles = fg.sync(`**/*.{ts,js}`, {
  187. cwd: absMockPath
  188. }).filter((item) => {
  189. if (!ignore) {
  190. return true;
  191. }
  192. if (isFunction(ignore)) {
  193. return ignore(item);
  194. }
  195. if (isRegExp(ignore)) {
  196. return !ignore.test(path.basename(item));
  197. }
  198. return true;
  199. });
  200. try {
  201. ret = [];
  202. const resolveModulePromiseList = [];
  203. for (let index = 0; index < mockFiles.length; index++) {
  204. const mockFile = mockFiles[index];
  205. resolveModulePromiseList.push(resolveModule(path.join(absMockPath, mockFile)));
  206. }
  207. const loadAllResult = await Promise.all(resolveModulePromiseList);
  208. for (const resultModule of loadAllResult) {
  209. let mod = resultModule;
  210. if (!isArray(mod)) {
  211. mod = [mod];
  212. }
  213. ret = [...ret, ...mod];
  214. }
  215. } catch (error) {
  216. loggerOutput(`mock reload error`, error);
  217. ret = [];
  218. }
  219. return ret;
  220. }
  221. async function resolveModule(p) {
  222. const result = await build({
  223. entryPoints: [p],
  224. outfile: "out.js",
  225. write: false,
  226. platform: "node",
  227. bundle: true,
  228. format: "cjs",
  229. metafile: true,
  230. target: "es2015"
  231. });
  232. const {text} = result.outputFiles[0];
  233. return await loadConfigFromBundledFile(p, text);
  234. }
  235. function getPath(opt) {
  236. const {mockPath, configPath} = opt;
  237. const cwd = process.cwd();
  238. const absMockPath = path.join(cwd, mockPath || "");
  239. const absConfigPath = path.join(cwd, configPath || "");
  240. return {
  241. absMockPath,
  242. absConfigPath
  243. };
  244. }
  245. function loggerOutput(title, msg, type = "info") {
  246. const tag = type === "info" ? chalk.cyan.bold(`[vite:mock]`) : chalk.red.bold(`[vite:mock-server]`);
  247. return console.log(`${chalk.dim(new Date().toLocaleTimeString())} ${tag} ${chalk.green(title)} ${chalk.dim(msg)}`);
  248. }
  249. async function loadConfigFromBundledFile(fileName, bundledCode) {
  250. const extension = path.extname(fileName);
  251. const extensions = module.Module._extensions;
  252. let defaultLoader;
  253. const isJs = extension === ".js";
  254. if (isJs) {
  255. defaultLoader = extensions[extension];
  256. }
  257. extensions[extension] = (module2, filename) => {
  258. if (filename === fileName) {
  259. module2._compile(bundledCode, filename);
  260. } else {
  261. if (!isJs) {
  262. extensions[extension](module2, filename);
  263. } else {
  264. defaultLoader(module2, filename);
  265. }
  266. }
  267. };
  268. let config;
  269. try {
  270. if (isJs && require && require.cache) {
  271. delete require.cache[fileName];
  272. }
  273. const raw = require(fileName);
  274. config = raw.__esModule ? raw.default : raw;
  275. if (defaultLoader && isJs) {
  276. extensions[extension] = defaultLoader;
  277. }
  278. } catch (error) {
  279. console.error(error);
  280. }
  281. return config;
  282. }
  283. // src/index.ts
  284. (async () => {
  285. try {
  286. await import("mockjs");
  287. } catch (e) {
  288. throw new Error("vite-plugin-vue-mock requires mockjs to be present in the dependency tree.");
  289. }
  290. })();
  291. function getDefaultPath(supportTs = true) {
  292. return path2.resolve(process.cwd(), `src/main.${supportTs ? "ts" : "js"}`);
  293. }
  294. function viteMockServe(opt) {
  295. let defaultPath = getDefaultPath();
  296. if (!fileExists(defaultPath)) {
  297. defaultPath = getDefaultPath(false);
  298. if (!fileExists(defaultPath)) {
  299. defaultPath = "";
  300. }
  301. }
  302. const defaultEnter = normalizePath(defaultPath);
  303. const {injectFile = defaultEnter} = opt;
  304. let isDev = false;
  305. let config;
  306. let needSourcemap = false;
  307. return {
  308. name: "vite:mock",
  309. enforce: "pre",
  310. configResolved(resolvedConfig) {
  311. config = resolvedConfig;
  312. isDev = config.command === "serve";
  313. needSourcemap = !!resolvedConfig.build.sourcemap;
  314. isDev && createMockServer(opt);
  315. },
  316. configureServer: async ({middlewares}) => {
  317. const {localEnabled = isDev} = opt;
  318. if (!localEnabled) {
  319. return;
  320. }
  321. const middleware = await requestMiddleware(opt);
  322. middlewares.use(middleware);
  323. },
  324. async transform(code, id) {
  325. if (isDev || !injectFile || !id.endsWith(injectFile)) {
  326. return null;
  327. }
  328. const {prodEnabled = true, injectCode = ""} = opt;
  329. if (!prodEnabled) {
  330. return null;
  331. }
  332. return {
  333. map: needSourcemap ? this.getCombinedSourcemap() : null,
  334. code: `${code}
  335. ${injectCode}`
  336. };
  337. }
  338. };
  339. }
  340. export {
  341. viteMockServe
  342. };