index.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('fs'), require('path'), require('axios'), require('lodash'), require('prettier'), require('@vue/compiler-sfc'), require('magic-string'), require('glob')) :
  3. typeof define === 'function' && define.amd ? define(['exports', 'fs', 'path', 'axios', 'lodash', 'prettier', '@vue/compiler-sfc', 'magic-string', 'glob'], factory) :
  4. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.index = {}, global.fs, global.path, global.axios, global.lodash, global.prettier, global.compilerSfc, global.magicString, global.glob));
  5. })(this, (function (exports, fs, path, axios, lodash, prettier, compilerSfc, magicString, glob) { 'use strict';
  6. const config = {
  7. type: "admin",
  8. reqUrl: "",
  9. demo: false,
  10. eps: {
  11. api: "",
  12. dist: "./build/cool",
  13. mapping: [
  14. {
  15. // 自定义匹配
  16. custom: ({ propertyName, type }) => {
  17. // 如果没有,返回null或者不返回,则继续遍历其他匹配规则
  18. return null;
  19. },
  20. },
  21. {
  22. type: "string",
  23. test: ["varchar", "text", "simple-json"],
  24. },
  25. {
  26. type: "string[]",
  27. test: ["simple-array"],
  28. },
  29. {
  30. type: "Date",
  31. test: ["datetime", "date"],
  32. },
  33. {
  34. type: "number",
  35. test: ["tinyint", "int", "decimal"],
  36. },
  37. {
  38. type: "BigInt",
  39. test: ["bigint"],
  40. },
  41. ],
  42. },
  43. };
  44. // 根目录
  45. function rootDir(path$1) {
  46. switch (config.type) {
  47. case "app":
  48. return path.join(process.env.UNI_INPUT_DIR, path$1);
  49. default:
  50. return path.join(process.cwd(), path$1);
  51. }
  52. }
  53. // 首字母大写
  54. function firstUpperCase(value) {
  55. return value.replace(/\b(\w)(\w*)/g, function ($0, $1, $2) {
  56. return $1.toUpperCase() + $2;
  57. });
  58. }
  59. // 横杠转驼峰
  60. function toCamel(str) {
  61. return str.replace(/([^-])(?:-+([^-]))/g, function ($0, $1, $2) {
  62. return $1 + $2.toUpperCase();
  63. });
  64. }
  65. // 创建目录
  66. function createDir(path, recursive) {
  67. try {
  68. if (!fs.existsSync(path))
  69. fs.mkdirSync(path, { recursive });
  70. }
  71. catch (err) { }
  72. }
  73. // 读取文件
  74. function readFile(path, json) {
  75. try {
  76. const content = fs.readFileSync(path, "utf8");
  77. return json ? JSON.parse(content) : content;
  78. }
  79. catch (err) { }
  80. return "";
  81. }
  82. // 写入文件
  83. function writeFile(path, data) {
  84. try {
  85. return fs.writeFileSync(path, data);
  86. }
  87. catch (err) { }
  88. return "";
  89. }
  90. // 解析body
  91. function parseJson(req) {
  92. return new Promise((resolve) => {
  93. let d = "";
  94. req.on("data", function (chunk) {
  95. d += chunk;
  96. });
  97. req.on("end", function () {
  98. try {
  99. resolve(JSON.parse(d));
  100. }
  101. catch {
  102. resolve({});
  103. }
  104. });
  105. });
  106. }
  107. function error(message) {
  108. console.log("\x1B[31m%s\x1B[0m", message);
  109. }
  110. let service = {};
  111. let list = [];
  112. let customList = [];
  113. // 获取请求地址
  114. function getEpsUrl() {
  115. let url = config.eps.api;
  116. if (!url) {
  117. url = config.type;
  118. }
  119. switch (url) {
  120. case "app":
  121. url = "/app/base/comm/eps";
  122. break;
  123. case "admin":
  124. url = "/admin/base/open/eps";
  125. break;
  126. }
  127. return url;
  128. }
  129. // 获取路径
  130. function getEpsPath(filename) {
  131. return path.join(config.type == "admin" ? config.eps.dist : rootDir(config.eps.dist), filename || "");
  132. }
  133. // 获取方法名
  134. function getNames(v) {
  135. return Object.keys(v).filter((e) => !["namespace", "permission"].includes(e));
  136. }
  137. // 获取数据
  138. async function getData(data) {
  139. // 自定义数据
  140. if (!lodash.isEmpty(data)) {
  141. customList = (data || []).map((e) => {
  142. return {
  143. ...e,
  144. isLocal: true,
  145. };
  146. });
  147. }
  148. // 本地文件
  149. const epsPath = getEpsPath("eps.json");
  150. try {
  151. list = readFile(epsPath, true) || [];
  152. }
  153. catch (err) {
  154. error(`[cool-eps] ${epsPath} 文件异常, ${err.message}`);
  155. }
  156. // 请求地址
  157. const url = config.reqUrl + getEpsUrl();
  158. // 请求数据
  159. await axios
  160. .get(url, {
  161. timeout: 5000,
  162. })
  163. .then((res) => {
  164. const { code, data, message } = res.data;
  165. if (code === 1000) {
  166. if (!lodash.isEmpty(data) && data) {
  167. lodash.merge(list, Object.values(data).flat());
  168. }
  169. }
  170. else {
  171. error(`[cool-eps] ${message}`);
  172. }
  173. })
  174. .catch(() => {
  175. error(`[cool-eps] 后端未启动 ➜ ${url}`);
  176. });
  177. // 合并自定义数据
  178. if (lodash.isArray(customList)) {
  179. customList.forEach((e) => {
  180. const d = list.find((a) => e.prefix === a.prefix);
  181. if (d) {
  182. lodash.merge(d, e);
  183. }
  184. else {
  185. list.push(e);
  186. }
  187. });
  188. }
  189. // 设置默认值
  190. list.forEach((e) => {
  191. if (!e.namespace) {
  192. e.namespace = "";
  193. }
  194. if (!e.api) {
  195. e.api = [];
  196. }
  197. });
  198. }
  199. // 创建 json 文件
  200. function createJson() {
  201. const arr = list.map((e) => {
  202. return {
  203. prefix: e.prefix,
  204. name: e.name || "",
  205. api: e.api.map((e) => {
  206. return {
  207. name: e.name,
  208. method: e.method,
  209. path: e.path,
  210. };
  211. }),
  212. };
  213. });
  214. const content = JSON.stringify(arr);
  215. const local_content = readFile(getEpsPath("eps.json"));
  216. // 是否需要更新
  217. const isUpdate = content != local_content;
  218. if (isUpdate) {
  219. fs.createWriteStream(getEpsPath("eps.json"), {
  220. flags: "w",
  221. }).write(content);
  222. }
  223. return isUpdate;
  224. }
  225. // 创建描述文件
  226. async function createDescribe({ list, service }) {
  227. // 获取类型
  228. function getType({ propertyName, type }) {
  229. for (const map of config.eps.mapping) {
  230. if (map.custom) {
  231. const resType = map.custom({ propertyName, type });
  232. if (resType)
  233. return resType;
  234. }
  235. if (map.test) {
  236. if (map.test.includes(type))
  237. return map.type;
  238. }
  239. }
  240. return type;
  241. }
  242. // 创建 Entity
  243. function createEntity() {
  244. const t0 = [];
  245. const arr = [];
  246. for (const item of list) {
  247. if (!item.name)
  248. continue;
  249. const t = [`interface ${item.name} {`];
  250. for (const col of item.columns || []) {
  251. // 描述
  252. t.push("\n");
  253. t.push("/**\n");
  254. t.push(` * ${col.comment}\n`);
  255. t.push(" */\n");
  256. t.push(`${col.propertyName}?: ${getType({
  257. propertyName: col.propertyName,
  258. type: col.type,
  259. })};`);
  260. }
  261. t.push("\n");
  262. t.push("/**\n");
  263. t.push(` * 任意键值\n`);
  264. t.push(" */\n");
  265. t.push(`[key: string]: any;`);
  266. t.push("}");
  267. if (!arr.includes(item.name)) {
  268. arr.push(item.name);
  269. t0.push(t);
  270. }
  271. }
  272. return t0.map((e) => e.join("")).join("\n\n");
  273. }
  274. // 创建 Service
  275. function createDts() {
  276. const t0 = [];
  277. const t1 = [
  278. `
  279. type json = any;
  280. type Service = {
  281. request(options?: {
  282. url: string;
  283. method?: "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
  284. data?: any;
  285. params?: any;
  286. headers?: {
  287. [key: string]: any;
  288. },
  289. timeout?: number;
  290. proxy?: boolean;
  291. [key: string]: any;
  292. }): Promise<any>;
  293. `,
  294. ];
  295. // 处理数据
  296. function deep(d, k) {
  297. if (!k)
  298. k = "";
  299. for (const i in d) {
  300. const name = k + toCamel(firstUpperCase(i.replace(/[:]/g, "")));
  301. if (d[i].namespace) {
  302. // 查找配置
  303. const item = list.find((e) => (e.prefix || "") === `/${d[i].namespace}`);
  304. if (item) {
  305. const t = [`interface ${name} {`];
  306. t1.push(`${i}: ${name};`);
  307. // 插入方法
  308. if (item.api) {
  309. // 权限列表
  310. const permission = [];
  311. item.api.forEach((a) => {
  312. // 方法名
  313. const n = toCamel(a.name || lodash.last(a.path.split("/")) || "").replace(/[:\/-]/g, "");
  314. if (n) {
  315. // 参数类型
  316. let q = [];
  317. // 参数列表
  318. const { parameters = [] } = a.dts || {};
  319. parameters.forEach((p) => {
  320. if (p.description) {
  321. q.push(`\n/** ${p.description} */\n`);
  322. }
  323. if (p.name.includes(":")) {
  324. return false;
  325. }
  326. const a = `${p.name}${p.required ? "" : "?"}`;
  327. const b = `${p.schema.type || "string"}`;
  328. q.push(`${a}: ${b},`);
  329. });
  330. if (lodash.isEmpty(q)) {
  331. q = ["any"];
  332. }
  333. else {
  334. q.unshift("{");
  335. q.push("}");
  336. }
  337. // 返回类型
  338. let res = "";
  339. // 实体名
  340. const en = item.name || "any";
  341. switch (a.path) {
  342. case "/page":
  343. res = `
  344. {
  345. pagination: { size: number; page: number; total: number; [key: string]: any };
  346. list: ${en} [];
  347. [key: string]: any;
  348. }
  349. `;
  350. break;
  351. case "/list":
  352. res = `${en} []`;
  353. break;
  354. case "/info":
  355. res = en;
  356. break;
  357. default:
  358. res = "any";
  359. break;
  360. }
  361. // 描述
  362. t.push("\n");
  363. t.push("/**\n");
  364. t.push(` * ${a.summary || n}\n`);
  365. t.push(" */\n");
  366. t.push(`${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")}): Promise<${res}>;`);
  367. if (!permission.includes(n)) {
  368. permission.push(n);
  369. }
  370. }
  371. });
  372. // 权限标识
  373. t.push("\n");
  374. t.push("/**\n");
  375. t.push(" * 权限标识\n");
  376. t.push(" */\n");
  377. t.push(`permission: { ${permission
  378. .map((e) => `${e}: string;`)
  379. .join("\n")} };`);
  380. // 权限状态
  381. t.push("\n");
  382. t.push("/**\n");
  383. t.push(" * 权限状态\n");
  384. t.push(" */\n");
  385. t.push(`_permission: { ${permission
  386. .map((e) => `${e}: boolean;`)
  387. .join("\n")} };`);
  388. // 请求
  389. t.push("\n");
  390. t.push("/**\n");
  391. t.push(" * 请求\n");
  392. t.push(" */\n");
  393. t.push(`request: Service['request']`);
  394. }
  395. t.push("}");
  396. t0.push(t);
  397. }
  398. }
  399. else {
  400. t1.push(`${i}: {`);
  401. deep(d[i], name);
  402. t1.push(`},`);
  403. }
  404. }
  405. }
  406. // 深度
  407. deep(service);
  408. // 结束
  409. t1.push("}");
  410. // 追加
  411. t0.push(t1);
  412. return t0.map((e) => e.join("")).join("\n\n");
  413. }
  414. // 文件内容
  415. const text = `
  416. declare namespace Eps {
  417. ${createEntity()}
  418. ${createDts()}
  419. }
  420. `;
  421. // 文本内容
  422. const content = await prettier.format(text, {
  423. parser: "typescript",
  424. useTabs: true,
  425. tabWidth: 4,
  426. endOfLine: "lf",
  427. semi: true,
  428. singleQuote: false,
  429. printWidth: 100,
  430. trailingComma: "none",
  431. });
  432. const local_content = readFile(getEpsPath("eps.d.ts"));
  433. // 是否需要更新
  434. if (content != local_content) {
  435. // 创建 eps 描述文件
  436. fs.createWriteStream(getEpsPath("eps.d.ts"), {
  437. flags: "w",
  438. }).write(content);
  439. }
  440. }
  441. // 创建 service
  442. function createService() {
  443. // 路径第一层作为 id 标识
  444. const id = getEpsUrl().split("/")[1];
  445. list.forEach((e) => {
  446. // 请求地址
  447. const path = e.prefix[0] == "/" ? e.prefix.substring(1, e.prefix.length) : e.prefix;
  448. // 分隔路径
  449. const arr = path.replace(id, "").split("/").filter(Boolean).map(toCamel);
  450. // 遍历
  451. function deep(d, i) {
  452. const k = arr[i];
  453. if (k) {
  454. // 是否最后一个
  455. if (arr[i + 1]) {
  456. if (!d[k]) {
  457. d[k] = {};
  458. }
  459. deep(d[k], i + 1);
  460. }
  461. else {
  462. // 不存在则创建
  463. if (!d[k]) {
  464. d[k] = {
  465. namespace: path,
  466. permission: {},
  467. };
  468. }
  469. // 创建方法
  470. e.api.forEach((a) => {
  471. // 方法名
  472. const n = a.path.replace("/", "");
  473. if (n && !/[-:]/g.test(n)) {
  474. d[k][n] = a;
  475. }
  476. });
  477. // 创建权限
  478. getNames(d[k]).forEach((i) => {
  479. d[k].permission[i] = `${d[k].namespace.replace(`${id}/`, "")}/${i}`.replace(/\//g, ":");
  480. });
  481. }
  482. }
  483. }
  484. deep(service, 0);
  485. });
  486. }
  487. // 创建 eps
  488. async function createEps(query) {
  489. // 获取数据
  490. await getData(query?.list || []);
  491. // 创建 service
  492. createService();
  493. // 创建目录
  494. createDir(getEpsPath(), true);
  495. // 创建 json 文件
  496. const isUpdate = createJson();
  497. // 创建描述文件
  498. createDescribe({ service, list });
  499. return {
  500. service,
  501. list,
  502. isUpdate,
  503. };
  504. }
  505. function createTag(code, id) {
  506. if (/\.vue$/.test(id)) {
  507. let s;
  508. const str = () => s || (s = new magicString(code));
  509. const { descriptor } = compilerSfc.parse(code);
  510. if (!descriptor.script && descriptor.scriptSetup) {
  511. const res = compilerSfc.compileScript(descriptor, { id });
  512. const { name, lang } = res.attrs;
  513. str().appendLeft(0, `<script lang="${lang}">
  514. import { defineComponent } from 'vue'
  515. export default defineComponent({
  516. name: "${name}"
  517. })
  518. <\/script>`);
  519. return {
  520. map: str().generateMap(),
  521. code: str().toString()
  522. };
  523. }
  524. }
  525. return null;
  526. }
  527. function findFiles(dir) {
  528. const res = [];
  529. const dirs = fs.readdirSync(dir, {
  530. withFileTypes: true,
  531. });
  532. for (const d of dirs) {
  533. if (d.isDirectory()) {
  534. res.push(...findFiles(dir + d.name + "/"));
  535. }
  536. else {
  537. if (path.extname(d.name) == ".svg") {
  538. const svg = fs.readFileSync(dir + d.name)
  539. .toString()
  540. .replace(/(\r)|(\n)/g, "")
  541. .replace(/<svg([^>+].*?)>/, (_, $2) => {
  542. let width = 0;
  543. let height = 0;
  544. let content = $2.replace(/(width|height)="([^>+].*?)"/g, (_, s2, s3) => {
  545. if (s2 === "width") {
  546. width = s3;
  547. }
  548. else if (s2 === "height") {
  549. height = s3;
  550. }
  551. return "";
  552. });
  553. if (!/(viewBox="[^>+].*?")/g.test($2)) {
  554. content += `viewBox="0 0 ${width} ${height}"`;
  555. }
  556. return `<symbol id="icon-${d.name.replace(".svg", "")}" ${content}>`;
  557. })
  558. .replace("</svg>", "</symbol>");
  559. res.push(svg);
  560. }
  561. }
  562. }
  563. return res;
  564. }
  565. function createSvg(html) {
  566. const res = findFiles(rootDir("./src/"));
  567. return html.replace("<body>", `<body>
  568. <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
  569. ${res.join("")}
  570. </svg>`);
  571. }
  572. // 创建文件
  573. async function createMenu(options) {
  574. // 格式化内容
  575. const content = await prettier.format(options.code, {
  576. parser: "vue",
  577. useTabs: true,
  578. tabWidth: 4,
  579. endOfLine: "lf",
  580. semi: true,
  581. jsxBracketSameLine: true,
  582. singleQuote: false,
  583. printWidth: 100,
  584. trailingComma: "none",
  585. });
  586. // 目录路径
  587. const dir = (options.viewPath || "").split("/");
  588. // 文件名
  589. const fname = dir.pop();
  590. // 源码路径
  591. const srcPath = `./src/${dir.join("/")}`;
  592. // 创建目录
  593. createDir(srcPath, true);
  594. // 创建文件
  595. fs.createWriteStream(path.join(srcPath, fname || "demo"), {
  596. flags: "w",
  597. }).write(content);
  598. }
  599. function base() {
  600. return {
  601. name: "vite-cool-base",
  602. enforce: "pre",
  603. configureServer(server) {
  604. server.middlewares.use(async (req, res, next) => {
  605. function done(data) {
  606. res.writeHead(200, { "Content-Type": "text/html;charset=UTF-8" });
  607. res.end(JSON.stringify(data));
  608. }
  609. if (req.originalUrl?.includes("__cool")) {
  610. const body = await parseJson(req);
  611. switch (req.url) {
  612. // 快速创建菜单
  613. case "/__cool_createMenu":
  614. await createMenu(body);
  615. break;
  616. // 创建描述文件
  617. case "/__cool_eps":
  618. await createEps(body);
  619. break;
  620. default:
  621. return done({
  622. code: 1001,
  623. message: "未知请求",
  624. });
  625. }
  626. done({
  627. code: 1000,
  628. });
  629. }
  630. else {
  631. next();
  632. }
  633. });
  634. },
  635. transform(code, id) {
  636. if (config.type == "admin") {
  637. return createTag(code, id);
  638. }
  639. return code;
  640. },
  641. transformIndexHtml(html) {
  642. if (config.type == "admin") {
  643. return createSvg(html);
  644. }
  645. return html;
  646. },
  647. };
  648. }
  649. function demo(enable) {
  650. const virtualModuleIds = ["virtual:demo"];
  651. return {
  652. name: "vite-cool-demo",
  653. enforce: "pre",
  654. resolveId(id) {
  655. if (virtualModuleIds.includes(id)) {
  656. return "\0" + id;
  657. }
  658. },
  659. async load(id) {
  660. if (id === "\0virtual:demo") {
  661. const demo = {};
  662. if (enable) {
  663. const files = await glob.glob(rootDir("./src/modules/demo/views/crud/components") + "/**", {
  664. stat: true,
  665. withFileTypes: true,
  666. });
  667. for (const file of files) {
  668. if (file.isFile()) {
  669. const p = path.join(file.path, file.name);
  670. demo[p
  671. .replace(/\\/g, "/")
  672. .split("src/modules/demo/views/crud/components/")[1]] = fs.readFileSync(p, "utf-8");
  673. }
  674. }
  675. }
  676. return `
  677. export const demo = ${JSON.stringify(demo)};
  678. `;
  679. }
  680. },
  681. };
  682. }
  683. async function createCtx() {
  684. let ctx = {};
  685. if (config.type == "app") {
  686. const manifest = readFile(rootDir("manifest.json"), true);
  687. // 文件路径
  688. const ctxPath = rootDir("pages.json");
  689. // 页面配置
  690. ctx = readFile(ctxPath, true);
  691. // 原数据,做更新比较用
  692. const ctxData = lodash.cloneDeep(ctx);
  693. // 删除临时页面
  694. ctx.pages = ctx.pages?.filter((e) => !e.isTemp);
  695. ctx.subPackages = ctx.subPackages?.filter((e) => !e.isTemp);
  696. // 加载 uni_modules 配置文件
  697. const files = await glob.glob(rootDir("uni_modules") + "/**/pages_init.json", {
  698. stat: true,
  699. withFileTypes: true,
  700. });
  701. for (const file of files) {
  702. if (file.isFile()) {
  703. const { pages = [], subPackages = [] } = readFile(path.join(file.path, file.name), true);
  704. // 合并到 pages 中
  705. [...pages, ...subPackages].forEach((e) => {
  706. e.isTemp = true;
  707. const isSub = !!e.root;
  708. const d = isSub
  709. ? ctx.subPackages?.find((a) => a.root == e.root)
  710. : ctx.pages?.find((a) => a.path == e.path);
  711. if (d) {
  712. lodash.assign(d, e);
  713. }
  714. else {
  715. if (isSub) {
  716. ctx.subPackages?.unshift(e);
  717. }
  718. else {
  719. ctx.pages?.unshift(e);
  720. }
  721. }
  722. });
  723. }
  724. }
  725. // 排序后检测,避免加载顺序问题
  726. function order(d) {
  727. return {
  728. pages: lodash.orderBy(d.pages, "path"),
  729. subPackages: lodash.orderBy(d.subPackages, "root"),
  730. };
  731. }
  732. // 是否需要更新 pages.json
  733. if (!lodash.isEqual(order(ctxData), order(ctx))) {
  734. console.log("[cool-ctx] pages updated");
  735. writeFile(ctxPath, JSON.stringify(ctx, null, 4));
  736. }
  737. // appid
  738. ctx.appid = manifest.appid;
  739. }
  740. if (config.type == "admin") {
  741. const list = fs.readdirSync(rootDir("./src/modules"));
  742. ctx.modules = list.filter((e) => !e.includes("."));
  743. }
  744. return ctx;
  745. }
  746. async function virtual() {
  747. const virtualModuleIds = ["virtual:eps", "virtual:ctx"];
  748. return {
  749. name: "vite-cool-virtual",
  750. enforce: "pre",
  751. configureServer(server) {
  752. server.middlewares.use(async (req, res, next) => {
  753. // 页面刷新时触发
  754. if (req.url == "/@vite/client") {
  755. // 重新加载虚拟模块
  756. virtualModuleIds.forEach((vm) => {
  757. const mod = server.moduleGraph.getModuleById(`\0${vm}`);
  758. if (mod) {
  759. server.moduleGraph.invalidateModule(mod);
  760. }
  761. });
  762. }
  763. next();
  764. });
  765. },
  766. handleHotUpdate({ file, server }) {
  767. // 文件修改时触发
  768. if (!["pages.json", "dist", "build/cool", "eps.json", "eps.d.ts"].some((e) => file.includes(e))) {
  769. createCtx();
  770. createEps().then((data) => {
  771. if (data.isUpdate) {
  772. // 通知客户端刷新
  773. (server.hot || server.ws).send({
  774. type: "custom",
  775. event: "eps-update",
  776. data,
  777. });
  778. }
  779. });
  780. }
  781. },
  782. resolveId(id) {
  783. if (virtualModuleIds.includes(id)) {
  784. return "\0" + id;
  785. }
  786. },
  787. async load(id) {
  788. if (id === "\0virtual:eps") {
  789. const eps = await createEps();
  790. return `
  791. export const eps = ${JSON.stringify(eps)}
  792. `;
  793. }
  794. if (id === "\0virtual:ctx") {
  795. const ctx = await createCtx();
  796. return `
  797. export const ctx = ${JSON.stringify(ctx)}
  798. `;
  799. }
  800. },
  801. };
  802. }
  803. function cool(options) {
  804. // 应用类型,admin | app
  805. config.type = options.type;
  806. // 请求地址
  807. config.reqUrl = options.proxy["/dev/"].target;
  808. // Eps
  809. if (options.eps) {
  810. const { dist, mapping, api } = options.eps;
  811. // 类型
  812. if (api) {
  813. config.eps.api = api;
  814. }
  815. // 输出目录
  816. if (dist) {
  817. config.eps.dist = dist;
  818. }
  819. // 匹配规则
  820. if (mapping) {
  821. lodash.merge(config.eps.mapping, mapping);
  822. }
  823. }
  824. return [base(), virtual(), demo(options.demo)];
  825. }
  826. exports.cool = cool;
  827. }));