新增 vite-plugin 开源包

This commit is contained in:
神仙都没用 2024-04-23 17:26:21 +08:00
parent 5a51d07c6d
commit abf04749e8
47 changed files with 7338 additions and 4808 deletions

1210
build/cool/dist/eps.d.ts vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,39 +0,0 @@
import { join } from "path";
// 打包路径
export const DistPath = join(__dirname, "../dist");
// 实体描述
export const Entity = {
mapping: [
{
// 自定义匹配
custom: ({ propertyName, type }) => {
// status 原本是tinyint如果是1的话== true 是可以的,但是不能 === true请谨慎使用
if (propertyName === "status" && type == "tinyint") return "boolean";
// 如果没有返回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"]
}
]
};

View File

@ -1,14 +0,0 @@
import { base } from "./base";
import { virtual } from "./virtual";
import { demo } from "./demo";
export function cool(test?: boolean) {
return [
// 基础
base(),
// 虚拟模块
virtual(),
// demo 官方示例,代码片段
demo(test)
];
}

View File

@ -1,12 +0,0 @@
import fs from "fs";
export function createModule() {
let dirs: string[] = [];
try {
dirs = fs.readdirSync("./src/modules");
dirs = dirs.filter((e) => !e.includes("."));
} catch (err) {}
return { dirs };
}

View File

@ -1,67 +0,0 @@
import type { Plugin } from "vite";
import { createEps } from "./eps";
import { createModule } from "./module";
export function virtual(): Plugin {
const virtualModuleIds = ["virtual:eps", "virtual:module"];
// 首次启动加载 Eps
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();
});
},
async handleHotUpdate({ file, server }) {
// 代码保存时触发
if (!["dist"].some((e) => file.includes(e))) {
createEps().then((data) => {
// 通知客户端刷新
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 { service } = await createEps();
return `
export const eps = ${JSON.stringify({ service })}
`;
}
if (id === "\0virtual:module") {
const { dirs } = createModule();
return `
export const dirs = ${JSON.stringify(dirs)}
`;
}
}
};
}

View File

@ -37,6 +37,7 @@
"xlsx": "^0.18.5"
},
"devDependencies": {
"@cool-vue/vite-plugin": "^7.0.1",
"@types/file-saver": "^2.0.7",
"@types/lodash-es": "^4.17.8",
"@types/mockjs": "^1.0.7",
@ -52,8 +53,6 @@
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "^9.17.0",
"glob": "^10.3.10",
"magic-string": "^0.30.3",
"prettier": "^3.1.0",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.66.1",

View File

@ -0,0 +1,12 @@
export default {
parser: "@typescript-eslint/parser",
extends: ["plugin:@typescript-eslint/recommended"],
parserOptions: {
project: "./tsconfig.json",
tsconfigRootDir: __dirname,
ecmaVersion: 2020,
sourceType: "module",
createDefaultProgram: true,
},
rules: {},
};

View File

@ -0,0 +1,8 @@
{
"tabWidth": 4,
"useTabs": true,
"semi": true,
"jsxBracketSameLine": true,
"singleQuote": false,
"printWidth": 100
}

789
packages/vite-plugin/dist/index.js vendored Normal file
View File

@ -0,0 +1,789 @@
(function (global, factory) {
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')) :
typeof define === 'function' && define.amd ? define(['exports', 'fs', 'path', 'axios', 'lodash', 'prettier', '@vue/compiler-sfc', 'magic-string', 'glob'], factory) :
(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));
})(this, (function (exports, fs, path, axios, lodash, prettier, compilerSfc, magicString, glob) { 'use strict';
const config = {
type: "",
reqUrl: "",
};
// 根目录
function rootDir(path$1) {
switch (config.type) {
case "app":
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) {
if (!fs.existsSync(path))
fs.mkdirSync(path);
}
// 读取文件
function readFile(path, json) {
try {
const content = fs.readFileSync(path, "utf8");
return json ? JSON.parse(content) : 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 mkdirs(path$1) {
const arr = path$1.split("/");
let p = "";
arr.forEach((e) => {
const t = path.join(p, e);
try {
fs.statSync(t);
}
catch (err) {
try {
fs.mkdirSync(t);
}
catch (error) {
console.error(error);
}
}
p = t;
});
return p;
}
function error(message) {
console.log("\x1B[31m%s\x1B[0m", message);
}
// 打包目录
const DistDir = path.join(__dirname, "../");
// 实体描述
const Entity = {
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"],
},
],
};
// eps 数据文件路径
const epsJsonPath = path.join(DistDir, "eps.json");
// eps 描述文件路径
const epsDtsPath = path.join(DistDir, "eps.d.ts");
let service = {};
let list = [];
let customList = [];
// 获取方法名
function getNames(v) {
return Object.keys(v).filter((e) => !["namespace", "permission"].includes(e));
}
// 获取数据
async function getData(data) {
// 自定义数据
if (!lodash.isEmpty(data)) {
customList = (data || []).map((e) => {
return {
...e,
isLocal: true,
};
});
}
// 本地文件
try {
list = readFile(epsJsonPath, true) || [];
}
catch (err) {
error(`[cool-eps] ${epsJsonPath} 文件异常, ${err.message}`);
}
// 请求地址
let url = config.reqUrl;
switch (config.type) {
case "app":
url += "/app/base/comm/eps";
break;
case "admin":
url += "/admin/base/open/eps";
break;
}
// 请求数据
await axios
.get(url, {
timeout: 5000,
})
.then((res) => {
const { code, data, message } = res.data;
if (code === 1000) {
if (!lodash.isEmpty(data) && data) {
lodash.merge(list, Object.values(data).flat());
}
}
else {
error(`[cool-eps] ${message}`);
}
})
.catch(() => {
error(`[cool-eps] 后端未启动 ➜ ${url}`);
});
// 合并自定义数据
if (lodash.isArray(customList)) {
customList.forEach((e) => {
const d = list.find((a) => e.prefix === a.prefix);
if (d) {
lodash.merge(d, e);
}
else {
list.push(e);
}
});
}
// 设置默认值
list.forEach((e) => {
if (!e.namespace) {
e.namespace = "";
}
if (!e.api) {
e.api = [];
}
});
}
// 创建 json 文件
function createJson() {
const d = 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,
};
}),
};
});
fs.createWriteStream(epsJsonPath, {
flags: "w",
}).write(JSON.stringify(d));
}
// 创建描述文件
async function createDescribe({ list, service }) {
// 获取类型
function getType({ propertyName, type }) {
for (const map of Entity.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;
}
// 创建 Entity
function createEntity() {
const t0 = [];
for (const item of list) {
if (!item.name)
continue;
const t = [`interface ${item.name} {`];
for (const col of item.columns || []) {
// 描述
t.push("\n");
t.push("/**\n");
t.push(` * ${col.comment}\n`);
t.push(" */\n");
t.push(`${col.propertyName}?: ${getType({
propertyName: col.propertyName,
type: col.type,
})};`);
}
t.push("\n");
t.push("/**\n");
t.push(` * 任意键值\n`);
t.push(" */\n");
t.push(`[key: string]: any;`);
t.push("}");
t0.push(t);
}
return t0.map((e) => e.join("")).join("\n\n");
}
// 创建 Service
function createDts() {
const t0 = [];
const t1 = [
`
type json = any;
type Service = {
request(options?: {
url: string;
method?: "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
data?: any;
params?: any;
headers?: {
[key: string]: any;
},
timeout?: number;
proxy?: boolean;
[key: string]: any;
}): Promise<any>;
`,
];
// 处理数据
function deep(d, k) {
if (!k)
k = "";
for (const i in d) {
const name = k + toCamel(firstUpperCase(i.replace(/[:]/g, "")));
if (d[i].namespace) {
// 查找配置
const item = list.find((e) => (e.prefix || "") === `/${d[i].namespace}`);
if (item) {
const t = [`interface ${name} {`];
t1.push(`${i}: ${name};`);
// 插入方法
if (item.api) {
// 权限列表
const permission = [];
item.api.forEach((a) => {
// 方法名
const n = toCamel(a.name || lodash.last(a.path.split("/")) || "").replace(/[:\/-]/g, "");
if (n) {
// 参数类型
let q = [];
// 参数列表
const { parameters = [] } = a.dts || {};
parameters.forEach((p) => {
if (p.description) {
q.push(`\n/** ${p.description} */\n`);
}
if (p.name.includes(":")) {
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.push("\n");
t.push("/**\n");
t.push(` * ${a.summary || n}\n`);
t.push(" */\n");
t.push(`${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")}): Promise<${res}>;`);
permission.push(n);
}
});
// 权限标识
t.push("\n");
t.push("/**\n");
t.push(" * 权限标识\n");
t.push(" */\n");
t.push(`permission: { ${permission
.map((e) => `${e}: string;`)
.join("\n")} };`);
// 权限状态
t.push("\n");
t.push("/**\n");
t.push(" * 权限状态\n");
t.push(" */\n");
t.push(`_permission: { ${permission
.map((e) => `${e}: boolean;`)
.join("\n")} };`);
// 请求
t.push("\n");
t.push("/**\n");
t.push(" * 请求\n");
t.push(" */\n");
t.push(`request: Service['request']`);
}
t.push("}");
t0.push(t);
}
}
else {
t1.push(`${i}: {`);
deep(d[i], name);
t1.push(`},`);
}
}
}
// 深度
deep(service);
// 结束
t1.push("}");
// 追加
t0.push(t1);
return t0.map((e) => e.join("")).join("\n\n");
}
// 文件内容
const text = `
declare namespace Eps {
${createEntity()}
${createDts()}
}
`;
// 文本内容
const content = await prettier.format(text, {
parser: "typescript",
useTabs: true,
tabWidth: 4,
endOfLine: "lf",
semi: true,
singleQuote: false,
printWidth: 100,
trailingComma: "none",
});
// 创建 eps 描述文件
fs.createWriteStream(epsDtsPath, {
flags: "w",
}).write(content);
}
// 创建 service
function createService() {
list.forEach((e) => {
// 分隔路径
const arr = e.prefix
.replace(/\//, "")
.replace(config.type, "")
.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] = {
namespace: e.prefix.substring(1, e.prefix.length),
permission: {},
};
}
// 创建方法
e.api.forEach((a) => {
// 方法名
const n = a.path.replace("/", "");
if (n && !/[-:]/g.test(n)) {
d[k][n] = a;
}
});
// 创建权限
getNames(d[k]).forEach((e) => {
d[k].permission[e] =
`${d[k].namespace.replace(`${config.type}/`, "")}/${e}`.replace(/\//g, ":");
});
}
}
}
deep(service, 0);
});
}
// 创建 eps
async function createEps(query) {
// 获取数据
await getData(query?.list || []);
// 创建 service
createService();
// 创建临时目录
createDir(DistDir);
// 创建 json 文件
createJson();
// 创建描述文件
createDescribe({ service, list });
return {
service,
list,
};
}
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 findFiles(dir) {
const res = [];
const dirs = fs.readdirSync(dir, {
withFileTypes: true,
});
for (const d of dirs) {
if (d.isDirectory()) {
res.push(...findFiles(dir + d.name + "/"));
}
else {
if (path.extname(d.name) == ".svg") {
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-${d.name.replace(".svg", "")}" ${content}>`;
})
.replace("</svg>", "</symbol>");
res.push(svg);
}
}
}
return res;
}
function createSvg(html) {
const res = findFiles(rootDir("./src/"));
return html.replace("<body>", `<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
${res.join("")}
</svg>`);
}
// 创建文件
async function createMenu(options) {
// 格式化内容
const content = await prettier.format(options.code, {
parser: "vue",
useTabs: true,
tabWidth: 4,
endOfLine: "lf",
semi: true,
jsxBracketSameLine: true,
singleQuote: false,
printWidth: 100,
trailingComma: "none",
});
// 目录路径
const dir = (options.viewPath || "").split("/");
// 文件名
const fname = dir.pop();
// 创建目录
const path$1 = mkdirs(rootDir(`./src/${dir.join("/")}`));
// 创建文件
fs.createWriteStream(path.join(path$1, fname || "demo"), {
flags: "w",
}).write(content);
}
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_createMenu":
await createMenu(body);
break;
// 创建描述文件
case "/__cool_eps":
await createEps(body);
break;
default:
return done({
code: 1001,
message: "未知请求",
});
}
done({
code: 1000,
});
}
else {
next();
}
});
},
transform(code, id) {
if (config.type == "admin") {
return createTag(code, id);
}
return code;
},
transformIndexHtml(html) {
if (config.type == "admin") {
return createSvg(html);
}
return html;
},
};
}
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 = {};
if (config.type == "app") {
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);
}
}
});
}
}
// 是否需要更新 pages.json
if (!lodash.isEqual(ctxData, 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("."));
}
return ctx;
}
async function virtual() {
const virtualModuleIds = ["virtual:eps", "virtual:ctx"];
const eps = await createEps();
const ctx = await createCtx();
return {
name: "vite-cool-virtual",
enforce: "pre",
handleHotUpdate({ file, server }) {
if (!["pages.json", "dist"].some((e) => file.includes(e))) {
createCtx();
createEps().then((data) => {
// 通知客户端刷新
server.ws.send({
type: "custom",
event: "eps-update",
data,
});
});
}
},
resolveId(id) {
if (virtualModuleIds.includes(id)) {
return "\0" + id;
}
},
load(id) {
if (id === "\0virtual:eps") {
return `
export const eps = ${JSON.stringify(eps)}
`;
}
if (id === "\0virtual:ctx") {
return `
export const ctx = ${JSON.stringify(ctx)}
`;
}
},
};
}
function cool(options) {
config.type = options.type;
config.reqUrl = options.proxy["/dev/"].target;
return [base(), virtual(), demo(options.demo)];
}
exports.cool = cool;
}));

View File

@ -0,0 +1,2 @@
import type { Plugin } from "vite";
export declare function base(): Plugin;

View File

@ -0,0 +1,5 @@
import type { Type } from "./types";
export declare const config: {
type: Type;
reqUrl: string;
};

View File

@ -0,0 +1,2 @@
import type { Ctx } from "../types";
export declare function createCtx(): Promise<Ctx.Data>;

View File

@ -0,0 +1,2 @@
import type { Plugin } from "vite";
export declare function demo(enable?: boolean): Plugin;

View File

@ -0,0 +1,7 @@
export declare const DistDir: string;
export declare const Entity: {
mapping: {
type: string;
test: string[];
}[];
};

View File

@ -0,0 +1,7 @@
import type { Eps } from "../types";
export declare function createEps(query?: {
list: any[];
}): Promise<{
service: {};
list: Eps.Entity[];
}>;

View File

@ -0,0 +1,6 @@
import type { Type } from "./types";
export declare function cool(options: {
type: Type;
proxy: any;
demo?: boolean;
}): (import("vite").Plugin<any> | Promise<import("vite").Plugin<any>>)[];

View File

@ -0,0 +1,4 @@
export declare function createMenu(options: {
viewPath: string;
code: string;
}): Promise<void>;

View File

@ -0,0 +1 @@
export declare function createSvg(html: string): string;

View File

@ -0,0 +1,4 @@
export declare function createTag(code: string, id: string): {
map: any;
code: any;
} | null;

View File

@ -0,0 +1,10 @@
export declare function rootDir(path: string): string;
export declare function firstUpperCase(value: string): string;
export declare function toCamel(str: string): string;
export declare function createDir(path: string): void;
export declare function readFile(path: string, json?: boolean): any;
export declare function writeFile(path: string, data: any): void | "";
export declare function parseJson(req: any): Promise<any>;
export declare function mkdirs(path: string): string;
export declare function error(message: string): void;
export declare function success(message: string): void;

View File

@ -0,0 +1,2 @@
import type { Plugin } from "vite";
export declare function virtual(): Promise<Plugin>;

1
packages/vite-plugin/eps.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="./src/dist/eps" />

View File

@ -0,0 +1,33 @@
{
"name": "@cool-vue/vite-plugin",
"version": "7.0.1",
"description": "cool-admin/cool-uni builder",
"main": "/dist/index.js",
"scripts": {
"build": "rollup --c --bundleConfigAsCjs"
},
"keywords": [],
"author": "cool",
"license": "ISC",
"types": "./dist/types/src/index.d.ts",
"devDependencies": {
"@rollup/plugin-typescript": "^11.1.6",
"@types/lodash": "^4.17.0",
"@types/node": "^20.12.7",
"@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^7.7.1",
"eslint": "^9.1.1",
"rollup": "^4.16.2",
"tslib": "^2.6.2",
"typescript": "^5.4.5",
"vite": "^5.2.10"
},
"dependencies": {
"@vue/compiler-sfc": "^3.4.24",
"axios": "^1.6.8",
"glob": "^10.3.12",
"lodash": "^4.17.21",
"magic-string": "^0.30.10",
"prettier": "^3.2.5"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
import typescript from "@rollup/plugin-typescript";
import { defineConfig } from "rollup";
export default defineConfig({
input: ["src/index.ts"],
output: {
name: "index",
format: "umd",
file: "dist/index.js",
},
plugins: [
typescript({
module: "esnext",
exclude: ["./node_modules/**"],
}),
],
});

View File

@ -1,9 +1,10 @@
import type { Plugin } from "vite";
import { createSvg } from "./svg";
import { createTag } from "./tag";
import { createEps } from "./eps";
import { createMenu } from "./menu";
import { parseJson } from "./utils";
import { createTag } from "./tag";
import { createSvg } from "./svg";
import { createMenu } from "./menu";
import { config } from "./config";
export function base(): Plugin {
return {
@ -16,7 +17,7 @@ export function base(): Plugin {
res.end(JSON.stringify(data));
}
if (req.url?.includes("__cool")) {
if (req.originalUrl?.includes("__cool")) {
const body = await parseJson(req);
switch (req.url) {
@ -33,12 +34,12 @@ export function base(): Plugin {
default:
return done({
code: 1001,
message: "未知请求"
message: "未知请求",
});
}
done({
code: 1000
code: 1000,
});
} else {
next();
@ -46,10 +47,18 @@ export function base(): Plugin {
});
},
transform(code, id) {
return createTag(code, id);
if (config.type == "admin") {
return createTag(code, id);
}
return code;
},
transformIndexHtml(html) {
return createSvg(html);
}
if (config.type == "admin") {
return createSvg(html);
}
return html;
},
};
}

View File

@ -0,0 +1,6 @@
import type { Type } from "./types";
export const config = {
type: "" as Type,
reqUrl: "",
};

View File

@ -0,0 +1,80 @@
import { join } from "path";
import { readFile, rootDir, writeFile } from "../utils";
import { glob } from "glob";
import { assign, cloneDeep, isEqual } from "lodash";
import type { Ctx } from "../types";
import { config } from "../config";
import fs from "fs";
export async function createCtx() {
let ctx: Ctx.Data = {};
if (config.type == "app") {
const manifest = readFile(rootDir("manifest.json"), true);
// 文件路径
const ctxPath = rootDir("pages.json");
// 页面配置
ctx = readFile(ctxPath, true);
// 原数据,做更新比较用
const ctxData = cloneDeep(ctx);
// 删除临时页面
ctx.pages = ctx.pages?.filter((e) => !e.isTemp);
ctx.subPackages = ctx.subPackages?.filter((e) => !e.isTemp);
// 加载 uni_modules 配置文件
const files = await glob(rootDir("uni_modules") + "/**/pages_init.json", {
stat: true,
withFileTypes: true,
});
for (const file of files) {
if (file.isFile()) {
const { pages = [], subPackages = [] }: Ctx.Data = readFile(
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) {
assign(d, e);
} else {
if (isSub) {
ctx.subPackages?.unshift(e);
} else {
ctx.pages?.unshift(e);
}
}
});
}
}
// 是否需要更新 pages.json
if (!isEqual(ctxData, 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("."));
}
return ctx;
}

View File

@ -2,6 +2,7 @@ import type { Plugin } from "vite";
import { glob } from "glob";
import path from "path";
import { readFileSync } from "fs";
import { rootDir } from "./utils";
export function demo(enable?: boolean): Plugin {
const virtualModuleIds = ["virtual:demo"];
@ -16,13 +17,16 @@ export function demo(enable?: boolean): Plugin {
},
async load(id) {
if (id === "\0virtual:demo") {
const demo = {};
const demo: any = {};
if (enable) {
const files = await glob("./src/modules/demo/views/crud/components/**", {
stat: true,
withFileTypes: true
});
const files = await glob(
rootDir("./src/modules/demo/views/crud/components") + "/**",
{
stat: true,
withFileTypes: true,
},
);
for (const file of files) {
if (file.isFile()) {
@ -41,6 +45,6 @@ export function demo(enable?: boolean): Plugin {
export const demo = ${JSON.stringify(demo)};
`;
}
}
},
};
}

1380
packages/vite-plugin/src/dist/eps.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
import { join } from "path";
// 打包目录
export const DistDir = join(__dirname, "../");
// 实体描述
export const Entity = {
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"],
},
],
};

View File

@ -1,66 +1,83 @@
import { createDir, error, firstUpperCase, readFile, toCamel } from "../utils";
import { join } from "path";
import { Entity, DistPath } from "./config";
import { Entity, DistDir } from "./config";
import axios from "axios";
import { isArray, isEmpty, last, merge, unionBy } from "lodash-es";
import { isArray, isEmpty, last, merge } from "lodash";
import { createWriteStream } from "fs";
import prettier from "prettier";
import { proxy } from "../../../src/config/proxy";
import { config } from "../config";
import type { Eps } from "../types";
// eps 数据文件路径
const epsJsonPath = join(DistDir, "eps.json");
// eps 描述文件路径
const epsDtsPath = join(DistDir, "eps.d.ts");
let service = {};
let list: Eps.Entity[] = [];
let customList: Eps.Entity[] = [];
// 获取方法名
function getNames(v: any) {
return Object.keys(v).filter((e) => !["namespace", "permission"].includes(e));
}
// 数据
const service = {};
let list: Eps.Entity[] = [];
let localList: Eps.Entity[] = [];
// 获取数据
async function getData(temps?: Eps.Entity[]) {
// 记录本地数据
if (!isEmpty(temps)) {
localList = (temps || []).map((e) => {
async function getData(data?: Eps.Entity[]) {
// 自定义数据
if (!isEmpty(data)) {
customList = (data || []).map((e) => {
return {
...e,
isLocal: true
isLocal: true,
};
});
}
// 本地文件
try {
list = JSON.parse(readFile(join(DistPath, "eps.json")) || "[]");
list = readFile(epsJsonPath, true) || [];
} catch (err: any) {
error(`[cool-eps] ${join(DistPath, "eps.json")} 文件异常, ${err.message}`);
error(`[cool-eps] ${epsJsonPath} 文件异常, ${err.message}`);
}
// 远程地址
const url = proxy["/dev/"].target + "/admin/base/open/eps";
// 请求地址
let url = config.reqUrl;
switch (config.type) {
case "app":
url += "/app/base/comm/eps";
break;
case "admin":
url += "/admin/base/open/eps";
break;
}
// 请求数据
await axios
.get(url, {
timeout: 5000
timeout: 5000,
})
.then((res) => {
const { code, data } = res.data;
const { code, data, message } = res.data;
if (code === 1000) {
if (!isEmpty(data) && data) {
merge(list, Object.values(data).flat() as Eps.Entity[]);
}
} else {
error(`[cool-eps] ${message}`);
}
})
.catch(() => {
error(`[cool-eps] 后端未启动 ➜ ${url}`);
});
// 合并本地数据
if (isArray(localList)) {
localList.forEach((e) => {
// 合并自定义数据
if (isArray(customList)) {
customList.forEach((e) => {
const d = list.find((a) => e.prefix === a.prefix);
if (d) {
@ -71,29 +88,36 @@ async function getData(temps?: Eps.Entity[]) {
});
}
list = unionBy(list, "prefix");
// 设置默认值
list.forEach((e) => {
if (!e.namespace) {
e.namespace = "";
}
if (!e.api) {
e.api = [];
}
});
}
// 创建 json 文件
function createJson() {
const d = list
.filter((e) => !e.isLocal)
.map((e) => {
return {
prefix: e.prefix,
name: e.name || "",
api: e.api.map((e) => {
return {
name: e.name,
method: e.method,
path: e.path
};
})
};
});
const d = 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,
};
}),
};
});
createWriteStream(join(DistPath, "eps.json"), {
flags: "w"
createWriteStream(epsJsonPath, {
flags: "w",
}).write(JSON.stringify(d));
}
@ -102,10 +126,10 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
// 获取类型
function getType({ propertyName, type }: any) {
for (const map of Entity.mapping) {
if (map.custom) {
const resType = map.custom({ propertyName, type });
if (resType) return resType;
}
// if (map.custom) {
// const resType = map.custom({ propertyName, type });
// if (resType) return resType;
// }
if (map.test) {
if (map.test.includes(type)) return map.type;
}
@ -120,7 +144,6 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
for (const item of list) {
if (!item.name) continue;
const t = [`interface ${item.name} {`];
for (const col of item.columns || []) {
// 描述
t.push("\n");
@ -130,8 +153,8 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
t.push(
`${col.propertyName}?: ${getType({
propertyName: col.propertyName,
type: col.type
})};`
type: col.type,
})};`,
);
}
t.push("\n");
@ -167,7 +190,7 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
proxy?: boolean;
[key: string]: any;
}): Promise<any>;
`
`,
];
// 处理数据
@ -193,9 +216,9 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
item.api.forEach((a) => {
// 方法名
const n = (a.name || last(a.path.split("/")) || "").replace(
/[:\/]/g,
""
const n = toCamel(a.name || last(a.path.split("/")) || "").replace(
/[:\/-]/g,
"",
);
if (n) {
@ -217,7 +240,7 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
const a = `${p.name}${p.required ? "" : "?"}`;
const b = `${p.schema.type || "string"}`;
q.push(`"${a}": ${b},`);
q.push(`${a}: ${b},`);
});
if (isEmpty(q)) {
@ -264,14 +287,12 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
t.push(" */\n");
t.push(
`"${n}"(data${q.length == 1 ? "?" : ""}: ${q.join(
""
)}): Promise<${res}>;`
`${n}(data${q.length == 1 ? "?" : ""}: ${q.join(
"",
)}): Promise<${res}>;`,
);
if (!permission.includes(n)) {
permission.push(n);
}
permission.push(n);
}
});
@ -282,8 +303,8 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
t.push(" */\n");
t.push(
`permission: { ${permission
.map((e) => `"${e}": string;`)
.join("\n")} };`
.map((e) => `${e}: string;`)
.join("\n")} };`,
);
// 权限状态
@ -293,8 +314,8 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
t.push(" */\n");
t.push(
`_permission: { ${permission
.map((e) => `"${e}": boolean;`)
.join("\n")} };`
.map((e) => `${e}: boolean;`)
.join("\n")} };`,
);
// 请求
@ -345,12 +366,12 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
semi: true,
singleQuote: false,
printWidth: 100,
trailingComma: "none"
trailingComma: "none",
});
// 创建 eps 描述文件
createWriteStream(join(DistPath, "eps.d.ts"), {
flags: "w"
createWriteStream(epsDtsPath, {
flags: "w",
}).write(content);
}
@ -360,7 +381,7 @@ function createService() {
// 分隔路径
const arr = e.prefix
.replace(/\//, "")
.replace("admin", "")
.replace(config.type, "")
.split("/")
.filter(Boolean)
.map(toCamel);
@ -382,32 +403,27 @@ function createService() {
if (!d[k]) {
d[k] = {
namespace: e.prefix.substring(1, e.prefix.length),
permission: {}
permission: {},
};
}
// 创建方法
e.api.forEach((a) => {
// 方法名
let n = a.path.replace("/", "");
if (n) {
// 示例 /info/:id
if (n.includes("/:")) {
a.path = a.path.split("/:")[0];
n = n.split("/:")[0];
}
const n = a.path.replace("/", "");
if (n && !/[-:]/g.test(n)) {
d[k][n] = a;
}
});
// 创建权限
getNames(d[k]).forEach((e) => {
d[k].permission[e] = `${d[k].namespace.replace("admin/", "")}/${e}`.replace(
/\//g,
":"
);
d[k].permission[e] =
`${d[k].namespace.replace(`${config.type}/`, "")}/${e}`.replace(
/\//g,
":",
);
});
}
}
@ -426,7 +442,7 @@ export async function createEps(query?: { list: any[] }) {
createService();
// 创建临时目录
createDir(DistPath);
createDir(DistDir);
// 创建 json 文件
createJson();
@ -436,6 +452,6 @@ export async function createEps(query?: { list: any[] }) {
return {
service,
list
list,
};
}

View File

@ -0,0 +1,12 @@
import { base } from "./base";
import { config } from "./config";
import { demo } from "./demo";
import type { Type } from "./types";
import { virtual } from "./virtual";
export function cool(options: { type: Type; proxy: any; demo?: boolean }) {
config.type = options.type;
config.reqUrl = options.proxy["/dev/"].target;
return [base(), virtual(), demo(options.demo)];
}

View File

@ -1,7 +1,7 @@
import { createWriteStream } from "fs";
import prettier from "prettier";
import { join } from "path";
import { mkdirs } from "../utils";
import { mkdirs, rootDir } from "../utils";
// 创建文件
export async function createMenu(options: { viewPath: string; code: string }) {
@ -15,7 +15,7 @@ export async function createMenu(options: { viewPath: string; code: string }) {
jsxBracketSameLine: true,
singleQuote: false,
printWidth: 100,
trailingComma: "none"
trailingComma: "none",
});
// 目录路径
@ -25,10 +25,10 @@ export async function createMenu(options: { viewPath: string; code: string }) {
const fname = dir.pop();
// 创建目录
const path = mkdirs(`./src/${dir.join("/")}`);
const path = mkdirs(rootDir(`./src/${dir.join("/")}`));
// 创建文件
createWriteStream(join(path, fname || "demo"), {
flags: "w"
flags: "w",
}).write(content);
}

View File

@ -1,10 +1,11 @@
import { readFileSync, readdirSync } from "fs";
import { extname } from "path";
import { rootDir } from "../utils";
function findFiles(dir: string): string[] {
const res: string[] = [];
const dirs = readdirSync(dir, {
withFileTypes: true
withFileTypes: true,
});
for (const d of dirs) {
if (d.isDirectory()) {
@ -26,7 +27,7 @@ function findFiles(dir: string): string[] {
height = s3;
}
return "";
}
},
);
if (!/(viewBox="[^>+].*?")/g.test($2)) {
content += `viewBox="0 0 ${width} ${height}"`;
@ -42,13 +43,13 @@ function findFiles(dir: string): string[] {
}
export function createSvg(html: string) {
const res = findFiles("./src/");
const res = findFiles(rootDir("./src/"));
return html.replace(
"<body>",
`<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
${res.join("")}
</svg>`
</svg>`,
);
}

View File

@ -1,3 +1,5 @@
export type Type = "app" | "admin";
export namespace Eps {
interface Entity {
api: {
@ -31,3 +33,27 @@ export namespace Eps {
[key: string]: any;
}
}
export namespace Ctx {
type Pages = {
path?: string;
style?: {
[key: string]: any;
};
[key: string]: any;
}[];
type SubPackages = {
root?: string;
pages?: Pages;
[key: string]: any;
}[];
interface Data {
appid?: string;
pages?: Pages;
subPackages?: SubPackages;
modules?: string[];
[key: string]: any;
}
}

View File

@ -1,6 +1,17 @@
import fs from "fs";
import { join } from "path";
import dayjs from "dayjs";
import { config } from "../config";
// 根目录
export function rootDir(path: string) {
switch (config.type) {
case "app":
return join(process.env.UNI_INPUT_DIR!, path);
default:
return join(process.cwd(), path);
}
}
// 首字母大写
export function firstUpperCase(value: string): string {
@ -22,10 +33,20 @@ export function createDir(path: string) {
}
// 读取文件
export function readFile(name: string) {
export function readFile(path: string, json?: boolean) {
try {
return fs.readFileSync(name, "utf8");
} catch (e) {}
const content = fs.readFileSync(path, "utf8");
return json ? JSON.parse(content) : content;
} catch (err) {}
return "";
}
// 写入文件
export function writeFile(path: string, data: any) {
try {
return fs.writeFileSync(path, data);
} catch (err) {}
return "";
}
@ -34,7 +55,7 @@ export function readFile(name: string) {
export function parseJson(req: any): Promise<any> {
return new Promise((resolve) => {
let d = "";
req.on("data", function (chunk: Buffer) {
req.on("data", function (chunk: any) {
d += chunk;
});
req.on("end", function () {
@ -71,5 +92,9 @@ export function mkdirs(path: string) {
}
export function error(message: string) {
console.log("\x1B[31m%s\x1B[0m", `${dayjs().format("HH:mm:ss")} ${message || ""}`);
console.log("\x1B[31m%s\x1B[0m", message);
}
export function success(message: string) {
console.log("\x1B[32m%s\x1B[0m", message);
}

View File

@ -0,0 +1,46 @@
import type { Plugin } from "vite";
import { createEps } from "./eps";
import { createCtx } from "./ctx";
export async function virtual(): Promise<Plugin> {
const virtualModuleIds: string[] = ["virtual:eps", "virtual:ctx"];
const eps = await createEps();
const ctx = await createCtx();
return {
name: "vite-cool-virtual",
enforce: "pre",
handleHotUpdate({ file, server }) {
if (!["pages.json", "dist"].some((e) => file.includes(e))) {
createCtx();
createEps().then((data) => {
// 通知客户端刷新
server.ws.send({
type: "custom",
event: "eps-update",
data,
});
});
}
},
resolveId(id) {
if (virtualModuleIds.includes(id)) {
return "\0" + id;
}
},
load(id) {
if (id === "\0virtual:eps") {
return `
export const eps = ${JSON.stringify(eps)}
`;
}
if (id === "\0virtual:ctx") {
return `
export const ctx = ${JSON.stringify(ctx)}
`;
}
},
};
}

View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"rootDir": "./",
"types": ["node"],
"target": "ESNext",
"module": "CommonJS",
"declaration": true,
"outDir": "types",
"strict": true,
"esModuleInterop": true,
"pretty": true,
"resolveJsonModule": true,
"typeRoots": ["./node_modules/@types/", "./src/types/"]
},
"include": ["src", "types.d.ts"],
"exclude": ["dist"]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
import { type Module } from "../types";
import { Module } from "../types";
import { hmr } from "../hooks";
import { dirs } from "virtual:module";
import { ctx } from "virtual:ctx";
// 模块列表
const list: Module[] = hmr.getData("modules", []);
@ -8,7 +8,7 @@ const list: Module[] = hmr.getData("modules", []);
// 模块对象
const module = {
list,
dirs,
dirs: ctx.modules,
req: Promise.resolve(),
get(name: string): Module {
return this.list.find((e) => e.name == name)!;

2
src/env.d.ts vendored
View File

@ -1,5 +1,5 @@
/// <reference types="@cool-vue/crud/index.d.ts" />
/// <reference types="../build/cool/dist/eps.d.ts" />
/// <reference types="@cool-vue/vite-plugin/eps.d.ts" />
interface ImportMetaEnv {
readonly VITE_NAME: string;

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@ import vueJsx from "@vitejs/plugin-vue-jsx";
import compression from "vite-plugin-compression";
import { visualizer } from "rollup-plugin-visualizer";
import { proxy } from "./src/config/proxy";
import { cool } from "./build/cool";
import { cool } from "@cool-vue/vite-plugin";
function resolve(dir: string) {
return path.resolve(__dirname, ".", dir);
@ -22,7 +22,11 @@ export default ({ mode }: ConfigEnv): UserConfig => {
vue(),
compression(),
vueJsx(),
cool(false), // 是否测试模式
cool({
type: "admin",
proxy,
demo: false // 是否测试模式
}),
visualizer({
open: false,
gzipSize: true,