12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412 |
- (function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('fs'), require('path'), require('prettier'), require('axios'), require('lodash'), require('@vue/compiler-sfc'), require('magic-string'), require('glob'), require('node:util'), require('svgo'), require('postcss-value-parser')) :
- typeof define === 'function' && define.amd ? define(['exports', 'fs', 'path', 'prettier', 'axios', 'lodash', '@vue/compiler-sfc', 'magic-string', 'glob', 'node:util', 'svgo', 'postcss-value-parser'], factory) :
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.index = {}, global.fs, global.path, global.prettier, global.axios, global.lodash, global.compilerSfc, global.magicString, global.glob, global.util, global.svgo, global.valueParser));
- })(this, (function (exports, fs, path, prettier, axios, lodash, compilerSfc, magicString, glob, util, svgo, valueParser) { 'use strict';
- const config = {
- type: "admin",
- reqUrl: "",
- eps: {
- enable: true,
- api: "",
- dist: "./build/cool",
- mapping: [
- {
- // 自定义匹配
- custom: ({ propertyName, type }) => {
- // 如果没有,返回null或者不返回,则继续遍历其他匹配规则
- return null;
- },
- },
- {
- type: "string",
- test: ["varchar", "text", "simple-json"],
- },
- {
- type: "string[]",
- test: ["simple-array"],
- },
- {
- type: "Date",
- test: ["datetime", "date"],
- },
- {
- type: "number",
- test: ["tinyint", "int", "decimal"],
- },
- {
- type: "BigInt",
- test: ["bigint"],
- },
- ],
- },
- svg: {
- skipNames: ["base"],
- },
- };
- // 根目录
- function rootDir(path$1) {
- switch (config.type) {
- case "app":
- case "uniapp-x":
- return path.join(process.env.UNI_INPUT_DIR, path$1);
- default:
- return path.join(process.cwd(), path$1);
- }
- }
- // 首字母大写
- function firstUpperCase(value) {
- return value.replace(/\b(\w)(\w*)/g, function ($0, $1, $2) {
- return $1.toUpperCase() + $2;
- });
- }
- // 横杠转驼峰
- function toCamel(str) {
- return str.replace(/([^-])(?:-+([^-]))/g, function ($0, $1, $2) {
- return $1 + $2.toUpperCase();
- });
- }
- // 创建目录
- function createDir(path, recursive) {
- try {
- if (!fs.existsSync(path))
- fs.mkdirSync(path, { recursive });
- }
- catch (err) { }
- }
- // 读取文件
- function readFile(path, json) {
- try {
- const content = fs.readFileSync(path, "utf8");
- return json
- ? JSON.parse(content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, ""))
- : content;
- }
- catch (err) { }
- return "";
- }
- // 写入文件
- function writeFile(path, data) {
- try {
- return fs.writeFileSync(path, data);
- }
- catch (err) { }
- return "";
- }
- // 解析body
- function parseJson(req) {
- return new Promise((resolve) => {
- let d = "";
- req.on("data", function (chunk) {
- d += chunk;
- });
- req.on("end", function () {
- try {
- resolve(JSON.parse(d));
- }
- catch {
- resolve({});
- }
- });
- });
- }
- // 格式化内容
- function formatContent(content, options) {
- return prettier.format(content, {
- parser: "typescript",
- useTabs: true,
- tabWidth: 4,
- endOfLine: "lf",
- semi: true,
- ...options,
- });
- }
- function error(message) {
- console.log("\x1B[31m%s\x1B[0m", message);
- }
- const service = {};
- let list = [];
- // 获取请求地址
- function getEpsUrl() {
- let url = config.eps.api;
- if (!url) {
- url = config.type;
- }
- switch (url) {
- case "app":
- case "uniapp-x":
- url = "/app/base/comm/eps";
- break;
- case "admin":
- url = "/admin/base/open/eps";
- break;
- }
- return url;
- }
- // 获取路径
- function getEpsPath(filename) {
- return path.join(config.type == "admin" ? config.eps.dist : rootDir(config.eps.dist), filename || "");
- }
- // 获取方法名
- function getNames(v) {
- return Object.keys(v).filter((e) => !["namespace", "permission"].includes(e));
- }
- // 找字段
- function findColumns(sources, item) {
- const columns = [item.columns, item.pageColumns].flat().filter(Boolean);
- return (sources || [])
- .map((e) => columns.find((c) => c.source == e))
- .filter(Boolean);
- }
- // 格式化代码
- async function formatCode(text) {
- return prettier
- .format(text, {
- parser: "typescript",
- useTabs: true,
- tabWidth: 4,
- endOfLine: "lf",
- semi: true,
- singleQuote: false,
- printWidth: 100,
- trailingComma: "none",
- })
- .catch((err) => {
- console.log(err);
- error(`[cool-eps] Failed to format /build/cool/eps.d.ts. Please delete the file and try again`);
- return null;
- });
- }
- // 获取数据
- async function getData() {
- // 读取本地数据
- list = readFile(getEpsPath("eps.json"), true) || [];
- // 请求地址
- const url = config.reqUrl + getEpsUrl();
- // 请求数据
- await axios
- .get(url, {
- timeout: 5000,
- })
- .then((res) => {
- const { code, data, message } = res.data;
- if (code === 1000) {
- if (!lodash.isEmpty(data) && data) {
- list = lodash.values(data).flat();
- }
- }
- else {
- error(`[cool-eps] ${message || "Failed to fetch data"}`);
- }
- })
- .catch(() => {
- error(`[cool-eps] API service is not running → ${url}`);
- });
- // 初始化处理
- list.forEach((e) => {
- if (!e.namespace) {
- e.namespace = "";
- }
- if (!e.api) {
- e.api = [];
- }
- if (!e.columns) {
- e.columns = [];
- }
- if (!e.search) {
- e.search = {
- fieldEq: findColumns(e.pageQueryOp?.fieldEq, e),
- fieldLike: findColumns(e.pageQueryOp?.fieldLike, e),
- keyWordLikeFields: findColumns(e.pageQueryOp?.keyWordLikeFields, e),
- };
- }
- });
- }
- // 创建 json 文件
- function createJson() {
- const arr = list.map((e) => {
- return {
- prefix: e.prefix,
- name: e.name || "",
- api: e.api.map((e) => {
- return {
- name: e.name,
- method: e.method,
- path: e.path,
- };
- }),
- search: e.search,
- };
- });
- const content = JSON.stringify(arr);
- const local_content = readFile(getEpsPath("eps.json"));
- // 是否需要更新
- const isUpdate = content != local_content;
- if (isUpdate) {
- fs.createWriteStream(getEpsPath("eps.json"), {
- flags: "w",
- }).write(content);
- }
- return isUpdate;
- }
- // 创建描述文件
- async function createDescribe({ list, service }) {
- // 获取类型
- function getType({ propertyName, type }) {
- for (const map of config.eps.mapping) {
- if (map.custom) {
- const resType = map.custom({ propertyName, type });
- if (resType)
- return resType;
- }
- if (map.test) {
- if (map.test.includes(type))
- return map.type;
- }
- }
- return type;
- }
- // 格式化方法名
- function formatName(name) {
- return (name || "").replace(/[:,\s,\/,-]/g, "");
- }
- // 检查方法名,包含特殊字符则忽略
- function checkName(name) {
- return name && !["{", "}", ":"].some((e) => name.includes(e));
- }
- // 创建 Entity
- function createEntity() {
- const ignore = [];
- let t0 = "";
- for (const item of list) {
- if (!checkName(item.name))
- continue;
- let t = `interface ${formatName(item.name)} {`;
- // 合并多个列
- const columns = [];
- [item.columns, item.pageColumns]
- .flat()
- .filter(Boolean)
- .forEach((e) => {
- const d = columns.find((c) => c.source == e.source);
- if (!d) {
- columns.push(e);
- }
- });
- for (const col of columns || []) {
- t += `
- /**
- * ${col.comment}
- */
- ${col.propertyName}?: ${getType({
- propertyName: col.propertyName,
- type: col.type,
- })}
- `;
- }
- t += `
- /**
- * 任意键值
- */
- [key: string]: any;
- }
- `;
- if (!ignore.includes(item.name)) {
- ignore.push(item.name);
- t0 += t + "\n\n";
- }
- }
- return t0;
- }
- // 创建 Controller
- async function createController() {
- let controller = "";
- let chain = "";
- // 处理数据
- function deep(d, k) {
- if (!k)
- k = "";
- for (const i in d) {
- const name = k + toCamel(firstUpperCase(formatName(i)));
- // 检查方法名
- if (!checkName(name))
- continue;
- if (d[i].namespace) {
- // 查找配置
- const item = list.find((e) => (e.prefix || "") === `/${d[i].namespace}`);
- if (item) {
- let t = `interface ${name} {`;
- // 插入方法
- if (item.api) {
- // 权限列表
- const permission = [];
- item.api.forEach((a) => {
- // 方法名
- const n = toCamel(formatName(a.name || lodash.last(a.path.split("/"))));
- // 检查方法名
- if (!checkName(n))
- return;
- if (n) {
- // 参数类型
- let q = [];
- // 参数列表
- const { parameters = [] } = a.dts || {};
- parameters.forEach((p) => {
- if (p.description) {
- q.push(`\n/** ${p.description} */\n`);
- }
- // 检查参数名
- if (!checkName(p.name)) {
- return false;
- }
- const a = `${p.name}${p.required ? "" : "?"}`;
- const b = `${p.schema.type || "string"}`;
- q.push(`${a}: ${b},`);
- });
- if (lodash.isEmpty(q)) {
- q = ["any"];
- }
- else {
- q.unshift("{");
- q.push("}");
- }
- // 返回类型
- let res = "";
- // 实体名
- const en = item.name || "any";
- switch (a.path) {
- case "/page":
- res = `
- {
- pagination: { size: number; page: number; total: number; [key: string]: any; };
- list: ${en} [];
- [key: string]: any;
- }
- `;
- break;
- case "/list":
- res = `${en} []`;
- break;
- case "/info":
- res = en;
- break;
- default:
- res = "any";
- break;
- }
- // 描述
- t += `
- /**
- * ${a.summary || n}
- */
- ${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")}): Promise<${res}>;
- `;
- if (!permission.includes(n)) {
- permission.push(n);
- }
- }
- });
- // 权限标识
- t += `
- /**
- * 权限标识
- */
- permission: { ${permission.map((e) => `${e}: string;`).join("\n")} };
- `;
- // 权限状态
- t += `
- /**
- * 权限状态
- */
- _permission: { ${permission.map((e) => `${e}: boolean;`).join("\n")} };
- `;
- t += `
- request: Service['request']
- `;
- }
- t += "}\n\n";
- controller += t;
- chain += `${formatName(i)}: ${name};`;
- }
- }
- else {
- chain += `${formatName(i)}: {`;
- deep(d[i], name);
- chain += "},";
- }
- }
- }
- // 遍历
- deep(service);
- return `
- type json = any;
- ${controller}
- interface Service {
- /**
- * 基础请求
- */
- request(options?: {
- url: string;
- method?: "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
- data?: any;
- params?: any;
- headers?: any,
- timeout?: number;
- proxy?: boolean;
- [key: string]: any;
- }): Promise<any>;
- ${chain}
- }
- ${await createDict()}
- `;
- }
- // 文件内容
- let text = `
- ${createEntity()}
- ${await createController()}
- `;
- // 文件名
- let name = "eps.d.ts";
- if (config.type == "uniapp-x") {
- name = "eps.uts";
- text = text
- .replaceAll("interface ", "export interface ")
- .replaceAll("type Dict", "export type Dict")
- .replaceAll("[key: string]: any;", "");
- }
- else {
- text = `
- declare namespace Eps {
- ${text}
- }
- `;
- }
- // 文本内容
- const content = await formatCode(text);
- const local_content = readFile(getEpsPath(name));
- // 是否需要更新
- if (content && content != local_content) {
- // 创建 eps 描述文件
- fs.createWriteStream(getEpsPath(name), {
- flags: "w",
- }).write(content);
- }
- }
- // 创建 service
- function createService() {
- // 路径第一层作为 id 标识
- const id = getEpsUrl().split("/")[1];
- list.forEach((e) => {
- // 请求地址
- const path = e.prefix[0] == "/" ? e.prefix.substring(1, e.prefix.length) : e.prefix;
- // 分隔路径
- const arr = path.replace(id, "").split("/").filter(Boolean).map(toCamel);
- // 遍历
- function deep(d, i) {
- const k = arr[i];
- if (k) {
- // 是否最后一个
- if (arr[i + 1]) {
- if (!d[k]) {
- d[k] = {};
- }
- deep(d[k], i + 1);
- }
- else {
- // 不存在则创建
- if (!d[k]) {
- d[k] = {
- permission: {},
- };
- }
- if (!d[k].namespace) {
- d[k].namespace = path;
- }
- // 创建权限
- if (d[k].namespace) {
- getNames(d[k]).forEach((i) => {
- d[k].permission[i] =
- `${d[k].namespace.replace(`${id}/`, "")}/${i}`.replace(/\//g, ":");
- });
- }
- // 创建搜索
- d[k].search = e.search;
- // 创建方法
- e.api.forEach((a) => {
- // 方法名
- const n = a.path.replace("/", "");
- if (n && !/[-:]/g.test(n)) {
- d[k][n] = a;
- }
- });
- }
- }
- }
- deep(service, 0);
- });
- }
- // 创建 dict
- async function createDict() {
- let p = "";
- switch (config.type) {
- case "app":
- case "uniapp-x":
- p = "/app";
- break;
- case "admin":
- p = "/admin";
- break;
- }
- const url = config.reqUrl + p + "/dict/info/types";
- const text = await axios
- .get(url)
- .then((res) => {
- const { code, data } = res.data;
- if (code === 1000) {
- let v = "string";
- if (!lodash.isEmpty(data)) {
- v = data.map((e) => `"${e.key}"`).join(" | ");
- }
- return `type DictKey = ${v}`;
- }
- })
- .catch(() => {
- error(`[cool-eps] Error:${url}`);
- });
- return text || "";
- }
- // 创建 eps
- async function createEps() {
- if (config.eps.enable) {
- // 获取数据
- await getData();
- // 创建 service
- createService();
- // 创建目录
- createDir(getEpsPath(), true);
- // 创建 json 文件
- const isUpdate = createJson();
- // 创建描述文件
- createDescribe({ service, list });
- return {
- service,
- list,
- isUpdate,
- };
- }
- else {
- return {
- service: {},
- list: [],
- };
- }
- }
- function getPlugin(name) {
- let code = readFile(rootDir(`./src/plugins/${name}/config.ts`));
- // 设置插件配置
- const set = (key, value) => {
- const regex = new RegExp(`(return\\s*{[^}]*?\\b${key}\\b\\s*:\\s*)([^,}]+)`);
- if (regex.test(code)) {
- code = code.replace(regex, `$1${JSON.stringify(value)}`);
- }
- else {
- const insertPos = code.indexOf("return {") + 8;
- code =
- code.slice(0, insertPos) +
- `\n ${key}: ${JSON.stringify(value)},` +
- code.slice(insertPos);
- }
- };
- // 保存插件配置
- const save = async () => {
- const content = await formatContent(code);
- writeFile(rootDir(`./src/plugins/${name}/config.ts`), content);
- };
- return {
- set,
- save,
- };
- }
- // 修改插件
- async function updatePlugin(options) {
- const plugin = getPlugin(options.name);
- if (options.enable !== undefined) {
- plugin.set("enable", options.enable);
- }
- await plugin.save();
- }
- function getPath() {
- return rootDir(`.${config.type == "admin" ? "/src" : ""}/config/proxy.ts`);
- }
- async function updateProxy(data) {
- let code = readFile(getPath());
- const regex = /const\s+value\s*=\s*['"]([^'"]+)['"]/;
- if (regex.test(code)) {
- code = code.replace(regex, `const value = '${data.name}'`);
- }
- writeFile(getPath(), code);
- }
- function getProxyTarget(proxy) {
- const code = readFile(getPath());
- const regex = /const\s+value\s*=\s*['"]([^'"]+)['"]/;
- const match = code.match(regex);
- if (match) {
- const value = match[1];
- try {
- const { target, rewrite } = proxy[`/${value}/`];
- return target + rewrite(`/${value}`);
- }
- catch (err) {
- error(`[cool-proxy] Error:${value} → ` + getPath());
- return "";
- }
- }
- }
- // 创建文件
- async function createFile(data) {
- const list = lodash.isArray(data) ? data : [data];
- for (const item of list) {
- const { path: path$1, code } = item;
- // 格式化内容
- const content = await formatContent(code, {
- parser: "vue",
- });
- // 目录路径
- const dir = (path$1 || "").split("/");
- // 文件名
- const fname = dir.pop();
- // 源码路径
- const srcPath = `./src/${dir.join("/")}`;
- // 创建目录
- createDir(srcPath, true);
- // 创建文件
- fs.createWriteStream(path.join(srcPath, fname), {
- flags: "w",
- }).write(content);
- }
- }
- function createTag(code, id) {
- if (/\.vue$/.test(id)) {
- let s;
- const str = () => s || (s = new magicString(code));
- const { descriptor } = compilerSfc.parse(code);
- if (!descriptor.script && descriptor.scriptSetup) {
- const res = compilerSfc.compileScript(descriptor, { id });
- const { name, lang } = res.attrs;
- str().appendLeft(0, `<script lang="${lang}">
- import { defineComponent } from 'vue'
- export default defineComponent({
- name: "${name}"
- })
- <\/script>`);
- return {
- map: str().generateMap(),
- code: str().toString(),
- };
- }
- }
- return null;
- }
- function base() {
- return {
- name: "vite-cool-base",
- enforce: "pre",
- configureServer(server) {
- server.middlewares.use(async (req, res, next) => {
- function done(data) {
- res.writeHead(200, { "Content-Type": "text/html;charset=UTF-8" });
- res.end(JSON.stringify(data));
- }
- if (req.originalUrl?.includes("__cool")) {
- const body = await parseJson(req);
- switch (req.url) {
- // 创建文件
- case "/__cool_createFile":
- await createFile(body);
- break;
- // 创建 eps 文件
- case "/__cool_eps":
- await createEps();
- break;
- // 更新插件
- case "/__cool_updatePlugin":
- await updatePlugin(body);
- break;
- // 设置代理
- case "/__cool_updateProxy":
- await updateProxy(body);
- break;
- default:
- return done({
- code: 1001,
- message: "Unknown request",
- });
- }
- done({
- code: 1000,
- });
- }
- else {
- next();
- }
- });
- },
- transform(code, id) {
- if (config.nameTag) {
- return createTag(code, id);
- }
- return code;
- },
- };
- }
- function demo(enable) {
- const virtualModuleIds = ["virtual:demo"];
- return {
- name: "vite-cool-demo",
- enforce: "pre",
- resolveId(id) {
- if (virtualModuleIds.includes(id)) {
- return "\0" + id;
- }
- },
- async load(id) {
- if (id === "\0virtual:demo") {
- const demo = {};
- if (enable) {
- const files = await glob.glob(rootDir("./src/modules/demo/views/crud/components") + "/**", {
- stat: true,
- withFileTypes: true,
- });
- for (const file of files) {
- if (file.isFile()) {
- const p = path.join(file.path, file.name);
- demo[p
- .replace(/\\/g, "/")
- .split("src/modules/demo/views/crud/components/")[1]] = fs.readFileSync(p, "utf-8");
- }
- }
- }
- return `
- export const demo = ${JSON.stringify(demo)};
- `;
- }
- },
- };
- }
- async function createCtx() {
- let ctx = {
- serviceLang: "Node",
- };
- if (config.type == "app" || config.type == "uniapp-x") {
- const manifest = readFile(rootDir("manifest.json"), true);
- // 文件路径
- const ctxPath = rootDir("pages.json");
- // 页面配置
- ctx = readFile(ctxPath, true);
- // 原数据,做更新比较用
- const ctxData = lodash.cloneDeep(ctx);
- // 删除临时页面
- ctx.pages = ctx.pages?.filter((e) => !e.isTemp);
- ctx.subPackages = ctx.subPackages?.filter((e) => !e.isTemp);
- // 加载 uni_modules 配置文件
- const files = await glob.glob(rootDir("uni_modules") + "/**/pages_init.json", {
- stat: true,
- withFileTypes: true,
- });
- for (const file of files) {
- if (file.isFile()) {
- const { pages = [], subPackages = [] } = readFile(path.join(file.path, file.name), true);
- // 合并到 pages 中
- [...pages, ...subPackages].forEach((e) => {
- e.isTemp = true;
- const isSub = !!e.root;
- const d = isSub
- ? ctx.subPackages?.find((a) => a.root == e.root)
- : ctx.pages?.find((a) => a.path == e.path);
- if (d) {
- lodash.assign(d, e);
- }
- else {
- if (isSub) {
- ctx.subPackages?.unshift(e);
- }
- else {
- ctx.pages?.unshift(e);
- }
- }
- });
- }
- }
- // 排序后检测,避免加载顺序问题
- function order(d) {
- return {
- pages: lodash.orderBy(d.pages, "path"),
- subPackages: lodash.orderBy(d.subPackages, "root"),
- };
- }
- // 是否需要更新 pages.json
- if (!util.isDeepStrictEqual(order(ctxData), order(ctx))) {
- console.log("[cool-ctx] pages updated");
- writeFile(ctxPath, JSON.stringify(ctx, null, 4));
- }
- // appid
- ctx.appid = manifest.appid;
- }
- if (config.type == "admin") {
- const list = fs.readdirSync(rootDir("./src/modules"));
- ctx.modules = list.filter((e) => !e.includes("."));
- await axios
- .get(config.reqUrl + "/admin/base/comm/program", {
- timeout: 5000,
- })
- .then((res) => {
- const { code, data, message } = res.data;
- if (code === 1000) {
- ctx.serviceLang = data || "Node";
- }
- else {
- error(`[cool-ctx] ${message}`);
- }
- })
- .catch((err) => {
- // console.error(['[cool-ctx] ', err.message])
- });
- }
- return ctx;
- }
- let svgIcons = [];
- function findSvg(dir) {
- const arr = [];
- const dirs = fs.readdirSync(dir, {
- withFileTypes: true,
- });
- // 获取当前目录的模块名
- const moduleName = dir.match(/[/\\](?:src[/\\](?:plugins|modules)[/\\])([^/\\]+)/)?.[1] || "";
- for (const d of dirs) {
- if (d.isDirectory()) {
- arr.push(...findSvg(dir + d.name + "/"));
- }
- else {
- if (path.extname(d.name) == ".svg") {
- const baseName = path.basename(d.name, ".svg");
- // 判断是否需要跳过拼接模块名
- let shouldSkip = config.svg.skipNames?.includes(moduleName);
- // 跳过包含icon-
- if (baseName.includes("icon-")) {
- shouldSkip = true;
- }
- const iconName = shouldSkip ? baseName : `${moduleName}-${baseName}`;
- svgIcons.push(iconName);
- const svg = fs.readFileSync(dir + d.name)
- .toString()
- .replace(/(\r)|(\n)/g, "")
- .replace(/<svg([^>+].*?)>/, (_, $2) => {
- let width = 0;
- let height = 0;
- let content = $2.replace(/(width|height)="([^>+].*?)"/g, (_, s2, s3) => {
- if (s2 === "width") {
- width = s3;
- }
- else if (s2 === "height") {
- height = s3;
- }
- return "";
- });
- if (!/(viewBox="[^>+].*?")/g.test($2)) {
- content += `viewBox="0 0 ${width} ${height}"`;
- }
- return `<symbol id="icon-${iconName}" ${content}>`;
- })
- .replace("</svg>", "</symbol>");
- arr.push(svg);
- }
- }
- }
- return arr;
- }
- function compilerSvg() {
- svgIcons = [];
- return findSvg(rootDir("./src/"))
- .map((e) => {
- return svgo.optimize(e)?.data || e;
- })
- .join("");
- }
- async function createSvg() {
- const html = compilerSvg();
- const code = `
- if (typeof window !== 'undefined') {
- function loadSvg() {
- const svgDom = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- svgDom.style.position = 'absolute';
- svgDom.style.width = '0';
- svgDom.style.height = '0';
- svgDom.setAttribute('xmlns','http://www.w3.org/2000/svg');
- svgDom.setAttribute('xmlns:link','http://www.w3.org/1999/xlink');
- svgDom.innerHTML = '${html}';
- document.body.insertBefore(svgDom, document.body.firstChild);
- }
- loadSvg();
- }
- `;
- return { code, svgIcons };
- }
- async function virtual() {
- const virtualModuleIds = [
- "virtual:eps",
- "virtual:ctx",
- "virtual:svg-register",
- "virtual:svg-icons",
- ];
- createEps();
- return {
- name: "vite-cool-virtual",
- enforce: "pre",
- configureServer(server) {
- server.middlewares.use(async (req, res, next) => {
- // 页面刷新时触发
- if (req.url == "/@vite/client") {
- // 重新加载虚拟模块
- virtualModuleIds.forEach((vm) => {
- const mod = server.moduleGraph.getModuleById(`\0${vm}`);
- if (mod) {
- server.moduleGraph.invalidateModule(mod);
- }
- });
- }
- next();
- });
- },
- handleHotUpdate({ file, server }) {
- // 文件修改时触发
- if (!["pages.json", "dist", "build/cool", "eps.json", "eps.d.ts"].some((e) => file.includes(e))) {
- createCtx();
- createEps().then((data) => {
- if (data.isUpdate) {
- // 通知客户端刷新
- (server.hot || server.ws).send({
- type: "custom",
- event: "eps-update",
- data,
- });
- }
- });
- }
- },
- resolveId(id) {
- if (virtualModuleIds.includes(id)) {
- return "\0" + id;
- }
- },
- async load(id) {
- if (id === "\0virtual:eps") {
- const eps = await createEps();
- return `
- export const eps = ${JSON.stringify(eps)}
- `;
- }
- if (id === "\0virtual:ctx") {
- const ctx = await createCtx();
- return `
- export const ctx = ${JSON.stringify(ctx)}
- `;
- }
- if (id == "\0virtual:svg-register") {
- const { code } = await createSvg();
- return code;
- }
- if (id == "\0virtual:svg-icons") {
- const { svgIcons } = await createSvg();
- return `
- export const svgIcons = ${JSON.stringify(svgIcons)}
- `;
- }
- },
- };
- }
- // @ts-ignore
- /**
- * Tailwind CSS 特殊字符映射表
- * 用于将类名中的特殊字符转换为安全字符,避免编译或运行时冲突
- */
- const TAILWIND_SAFE_CHAR_MAP = {
- "[": "-",
- "]": "-",
- "(": "-",
- ")": "-",
- "#": "-h-",
- "!": "-i-",
- "/": "-s-",
- ":": "-c-",
- ",": "-2c-",
- };
- /**
- * Tailwind CSS 常用类名前缀集合
- * 按功能分类,便于维护和扩展
- */
- const TAILWIND_CLASS_PREFIXES = [
- // 间距
- "p-",
- "px-",
- "py-",
- "pt-",
- "pr-",
- "pb-",
- "pl-",
- "m-",
- "mx-",
- "my-",
- "mt-",
- "mr-",
- "mb-",
- "ml-",
- "gap-",
- "gap-x-",
- "gap-y-",
- "space-x-",
- "space-y-",
- "inset-",
- "top-",
- "right-",
- "bottom-",
- "left-",
- // 尺寸
- "w-",
- "h-",
- "min-w-",
- "min-h-",
- "max-w-",
- "max-h-",
- // 排版
- "text-",
- "font-",
- "leading-",
- "tracking-",
- "indent-",
- // 边框
- "border-",
- "border-t-",
- "border-r-",
- "border-b-",
- "border-l-",
- "rounded-",
- "rounded-t-",
- "rounded-r-",
- "rounded-b-",
- "rounded-l-",
- "rounded-tl-",
- "rounded-tr-",
- "rounded-br-",
- "rounded-bl-",
- // 效果
- "shadow-",
- "blur-",
- "brightness-",
- "contrast-",
- "drop-shadow-",
- "grayscale-",
- "hue-rotate-",
- "invert-",
- "saturate-",
- "sepia-",
- "backdrop-blur-",
- "backdrop-brightness-",
- "backdrop-contrast-",
- "backdrop-grayscale-",
- "backdrop-hue-rotate-",
- "backdrop-invert-",
- "backdrop-opacity-",
- "backdrop-saturate-",
- "backdrop-sepia-",
- // 动画
- "transition-",
- "duration-",
- "delay-",
- "animate-",
- // 变换
- "translate-x-",
- "translate-y-",
- "rotate-",
- "scale-",
- "scale-x-",
- "scale-y-",
- "skew-x-",
- "skew-y-",
- "origin-",
- // 布局
- "columns-",
- "break-after-",
- "break-before-",
- "break-inside-",
- // Flexbox 和 Grid
- "basis-",
- "grow-",
- "shrink-",
- "grid-cols-",
- "grid-rows-",
- "col-span-",
- "row-span-",
- "col-start-",
- "col-end-",
- "row-start-",
- "row-end-",
- // SVG
- "stroke-",
- "stroke-w-",
- "fill-",
- ];
- /**
- * Tailwind CSS 颜色变量映射
- * 用于移除不需要的 CSS 变量声明
- */
- const TAILWIND_COLOR_VARS = {
- "--tw-text-opacity": 1,
- "--tw-bg-opacity": 1,
- };
- /**
- * 转换类名中的特殊字符为安全字符
- * @param value 原始类名或值
- * @param isSelector 是否为选择器(true)或普通值(false)
- * @returns 转换后的安全字符串
- */
- function toSafeTailwindClass(value, isSelector = false) {
- // 处理任意值语法(如 w-[100px])
- const arbitrary = value.match(/^(.+?)-\[(.*?)\]$/);
- if (arbitrary) {
- if (isSelector)
- return value;
- const [, prefix, content] = arbitrary;
- const safePrefix = toSafeTailwindClass(prefix, isSelector);
- const safeContent = content.replace(/[^\d.\w]/g, "-");
- return `${safePrefix}-${safeContent}`;
- }
- let safeValue = value;
- // 移除转义字符
- if (safeValue.includes("\\")) {
- safeValue = safeValue.replace(/\\/g, "");
- }
- // 替换特殊字符
- for (const [char, rep] of Object.entries(TAILWIND_SAFE_CHAR_MAP)) {
- const reg = new RegExp("\\" + char, "g");
- if (reg.test(safeValue)) {
- safeValue = safeValue.replace(reg, rep);
- }
- }
- return safeValue;
- }
- /**
- * 将现代 rgb 格式(如 rgb(234 179 8 / 0.1))转换为标准 rgba 格式
- * @param value rgb 字符串
- * @returns 标准 rgba 字符串
- */
- function rgbToRgba(value) {
- const match = value.match(/rgb\(([\d\s]+)\/\s*([\d.]+)\)/);
- if (match) {
- const [, rgb, alpha] = match;
- const [r, g, b] = rgb.split(/\s+/);
- return `rgba(${r}, ${g}, ${b}, ${alpha})`;
- }
- return value;
- }
- /**
- * PostCSS 插件:将 rem 单位转换为 rpx,并处理 Tailwind 特殊字符
- * @param options 配置项
- * @returns PostCSS 插件对象
- */
- function postcssRemToRpx(options) {
- return {
- postcssPlugin: "vite-cool-uniappx-remToRpx",
- prepare() {
- const handledSelectors = new Set();
- const { remUnit = 16, remPrecision = 6, rpxRatio = 2 } = options;
- const factor = remUnit * rpxRatio;
- return {
- Rule(rule) {
- const sel = rule.selector;
- if (handledSelectors.has(sel))
- return;
- const safeSel = toSafeTailwindClass(sel, true);
- if (safeSel !== sel) {
- rule.selector = safeSel;
- handledSelectors.add(sel);
- }
- },
- Declaration(decl) {
- if (decl.value.includes("/* no-rem */"))
- return;
- if (TAILWIND_COLOR_VARS[decl.prop]) {
- decl.remove();
- return;
- }
- if (decl.value.includes("rgb(") && decl.value.includes("/")) {
- decl.value = rgbToRgba(decl.value);
- }
- if (decl.value.includes("rpx") && decl.parent.selector.includes("text-")) {
- decl.prop = "font-size";
- }
- const parsed = valueParser(decl.value);
- let changed = false;
- parsed.walk((node) => {
- if (node.type === "word") {
- // rem 转 rpx
- const unit = valueParser.unit(node.value);
- if (unit?.unit === "rem") {
- const num = unit.number;
- const precision = (num.split(".")[1] || "").length;
- const rpxVal = (parseFloat(num) * factor)
- .toFixed(precision || remPrecision)
- .replace(/\.?0+$/, "");
- node.value = `${rpxVal}rpx`;
- changed = true;
- }
- // 特殊字符处理
- if (node.value.includes(".") || /[[\]()#!/:,]/.test(node.value)) {
- const safe = toSafeTailwindClass(node.value, true);
- if (safe !== node.value) {
- node.value = safe;
- changed = true;
- }
- }
- }
- // 处理 var(--tw-xxx)
- if (node.type === "function" && node.value === "var") {
- if (node.nodes.length > 0 && node.nodes[0].value.startsWith("--tw-")) {
- node.type = "word";
- node.value = TAILWIND_COLOR_VARS[node.nodes[0].value];
- changed = true;
- }
- }
- });
- if (changed) {
- decl.value = parsed.toString();
- }
- },
- };
- },
- };
- }
- postcssRemToRpx.postcss = true;
- /**
- * Vite 插件:自动转换 .uvue 文件中的 Tailwind 类名为安全字符
- * 并自动注入 rem 转 rpx 的 PostCSS 插件
- * @param options 配置项
- * @returns Vite 插件对象
- */
- function tailwindTransformPlugin(options = {}) {
- const merged = {
- remUnit: 16,
- remPrecision: 6,
- rpxRatio: 2,
- ...options,
- };
- return {
- name: "vite-cool-uniappx-tailwind",
- enforce: "pre",
- config() {
- return {
- css: {
- postcss: {
- plugins: [postcssRemToRpx(merged)],
- },
- },
- };
- },
- transform(code, id) {
- if (!id.includes(".uvue"))
- return null;
- let resultCode = code;
- const tplMatch = resultCode.match(/<template>([\s\S]*?)<\/template>/);
- if (!tplMatch?.[1])
- return null;
- let tpl = tplMatch[1];
- const tplOrigin = tpl;
- TAILWIND_CLASS_PREFIXES.forEach((prefix) => {
- for (const [char, rep] of Object.entries(TAILWIND_SAFE_CHAR_MAP)) {
- const reg = new RegExp(`(${prefix}[^\\s'"]*?\\${char}[^\\s'"]*?)`, "g");
- const matches = [...tpl.matchAll(reg)];
- matches.forEach((m) => {
- const raw = m[1];
- const safe = raw.replace(new RegExp("\\" + char, "g"), rep);
- if (process.env.NODE_ENV === "development") {
- console.log(`类名转换: ${raw} → ${safe}`);
- }
- tpl = tpl.replace(raw, safe);
- });
- }
- });
- if (tpl !== tplOrigin) {
- resultCode = resultCode.replace(tplMatch[0], `<template>${tpl}</template>`);
- return {
- code: resultCode,
- map: { mappings: "" },
- };
- }
- return null;
- },
- };
- }
- /**
- * uniappX 入口,自动注入 Tailwind 类名转换插件
- * @param options 配置项
- * @returns Vite 插件数组
- */
- function uniappX(options) {
- if (config.type == "uniapp-x") {
- return [tailwindTransformPlugin(options?.tailwind)];
- }
- return [];
- }
- function cool(options) {
- // 应用类型,admin | app
- config.type = options.type;
- // 请求地址
- config.reqUrl = getProxyTarget(options.proxy);
- // 是否开启名称标签
- config.nameTag = options.nameTag ?? true;
- // svg
- if (options.svg) {
- lodash.assign(config.svg, options.svg);
- }
- // Eps
- if (options.eps) {
- const { dist, mapping, api, enable = true } = options.eps;
- // 是否开启
- config.eps.enable = enable;
- // 类型
- if (api) {
- config.eps.api = api;
- }
- // 输出目录
- if (dist) {
- config.eps.dist = dist;
- }
- // 匹配规则
- if (mapping) {
- lodash.merge(config.eps.mapping, mapping);
- }
- }
- return [base(), virtual(), uniappX(), demo(options.demo)];
- }
- exports.cool = cool;
- }));
|