UNPKG

@vtj/coder

Version:

VTJ 是一款基于 Vue3 + Typescript 的低代码页面可视化设计器。内置低代码引擎、渲染器和代码生成器,面向前端开发者,开箱即用。 无缝嵌入本地开发工程,不改变前端开发流程和编码习惯。

717 lines (715 loc) 19.9 kB
import { mitt as K, dedupArray as w, toArray as ee, kebabCase as I, camelCase as v, isPlainObject as te, template as U, cloneDeep as ne } from "@vtj/base"; import { format as B } from "prettier/standalone"; import * as se from "prettier/plugins/html"; import * as D from "prettier/plugins/babel"; import * as V from "prettier/plugins/postcss"; import * as H from "prettier/plugins/estree"; /**! * Copyright (c) 2025, VTJ.PRO All rights reserved. * @name @vtj/coder * @author CHC chenhuachun1549@dingtalk.com * @version 0.11.15 * @license <a href="https://vtj.pro/license.html">MIT License</a> */ const Le = "0.11.15"; /**! * Copyright (c) 2025, VTJ.PRO All rights reserved. * @name @vtj/core * @author CHC chenhuachun1549@dingtalk.com * @version 0.11.15 * @license <a href="https://vtj.pro/license.html">MIT License</a> */ const re = [ "slot", "template", "component", "img", "div", "p", "h1", "h2", "h3", "span", "a" ]; K(); const R = { arrowParens: "always", bracketSpacing: !0, bracketSameLine: !0, endOfLine: "lf", htmlWhitespaceSensitivity: "css", insertPragma: !1, jsxBracketSameLine: !0, jsxSingleQuote: !0, printWidth: 80, proseWrap: "preserve", quoteProps: "as-needed", requirePragma: !1, semi: !0, singleQuote: !0, tabWidth: 2, trailingComma: "none", useTabs: !1, vueIndentScriptAndStyle: !0 }; async function M(t, e) { return e ? t : await B(t, { parser: "vue", ...R, plugins: [se, D, H, V] }); } async function ie(t, e) { return e ? t : await B(t, { parser: "babel-ts", ...R, plugins: [D, H] }); } async function W(t, e) { return e ? t : B(t, { parser: "scss", ...R, plugins: [V] }); } function S(t) { return t && t.type === "JSExpression"; } function N(t) { return typeof t == "object" && t && t.type === "JSFunction"; } function $(t) { return S(t) || N(t); } function q(t) { return t.replace(new RegExp("this.", "g"), ""); } function J(t) { return t.replace(/this\.context\??\./g, ""); } function f(t, e = !0, n = !0, s = []) { let r = $(t) ? t.value : e ? JSON.stringify(t) : t; return r = k(r, s), n ? q(J(r)) : J(r); } function k(t, e = []) { let n = t; for (const s of e) n = n.replace( new RegExp(`this.${s}.value`, "g"), `this.${s}` ); return n; } function Q(t) { let e = t.trim(); if (e = /^\((\(|async|function)/.test(e) ? e.substring(1, e.length - 1) : e, e.startsWith("{")) return e; if (e.startsWith("async function")) e = e.replace(/^async function/, "async"); else if (e.startsWith("function")) e = e.replace(/^function/, ""); else { const r = /^(async\s)?\([\w]*\)\s+\=\>\s([\w\W]+)/, i = e.match(r); i && i[2] && (i[2].startsWith("{") || (e = e.replace(i[2], `{ return ${i[2]} }`))), e = e.replace("=>", ""); } return e; } function oe(t = {}) { return Object.entries(t).map(([e, n]) => `"${e}": ${f(n)}`); } function G(t = {}, e = !1) { const n = Object.keys(t); return e ? n.map((s) => "." + s) : n; } function ce(t) { let e = ""; for (var n in t) if (t.hasOwnProperty(n)) { var s = t[n]; e += n + ": " + s + ";"; } return e; } function ae(t = [], e = []) { return t.filter((n) => !e.includes(n)); } class ue { constructor(e, n) { this.dsl = e, this.dependencies = n, this.libraryRegex = this.collectLibrary(), this.walk(e), this.walkNodes(e), this.members = this.getLibraryMember(); } /** * { 'element-plus': ['ElButton', 'ElInput' ...] } */ imports = {}; context = {}; style = {}; members = []; urlSchemas = {}; blockPlugins = {}; libraryRegex = []; collectLibrary() { return this.dependencies.filter((e) => !!e.library).map((e) => new RegExp(`(this.\\$libs.${e.library}.([\\w]+))`, "g")); } /** * 收集 import 信息 * @param regexMatchItem ex: this.$libs.ElementPlus.ElButton * @returns ex: { name: 'ElButton', path: 'this.$libs.ElementPlus.', library: 'ElementPlus' } */ collectImport(e) { const n = e.split("."); if (n.length === 4) { const s = n.pop(), r = n.join(".") + ".", i = n.pop(); if (s && i) { const c = this.dependencies.find( (a) => a.library === i )?.package; c && (this.imports[c] || (this.imports[c] = /* @__PURE__ */ new Set())).add(s); } return { name: s, path: r, library: i }; } return null; } // 代码中包含依赖库的引用,需要从代码中移除 replaceLibraryPath(e) { const { libraryRegex: n } = this; let s = e.value; for (const r of n) { const i = e.value?.match(r) || []; for (const c of i) { const a = this.collectImport(c); if (a) { const p = a.path.replace(/\$/g, "\\$"); s = s.replace(new RegExp(p, "g"), ""); } } } return s; } walk(e) { const n = (s) => { if (!s || typeof s != "object") return; if (Array.isArray(s)) { for (let i of s) n(i); return; } const r = Object.values(s); for (const i of r) $(i) ? i.value = this.replaceLibraryPath(i) : n(i); }; n(e); } getLibraryMember(e = []) { let n = [...e]; const s = { ...this.imports }; delete s["uni-h5"], delete s["@dcloudio/uni-h5"], delete s["uni-ui"], delete s["@dcloudio/uni-ui"]; for (const r of Object.values(s)) n = n.concat(Array.from(r)); return w(n); } collectContext(e, n) { const s = new Set(n?.id ? this.context[n.id] : []), r = (e.directives || []).find((a) => a.name === "vFor"); let i = new Set(Array.from(s)); if (r) { const { item: a = "item", index: p = "index" } = r.iterator || {}; i = /* @__PURE__ */ new Set([a, p, ...Array.from(i)]); } const c = e.slot; if (c) { const a = typeof c == "string" ? [] : c.params || [], p = a.length ? a : [`scope_${n?.id}`]; i = /* @__PURE__ */ new Set([...p, ...Array.from(i)]); } this.context[e.id] = i; } collectStyle(e) { e.id && e.props?.style && Object.keys(e.props.style).length && !$(e.props.style) && (this.style[`.${e.name}_${e.id}`] = e.props.style); } collectUrlSchema(e) { typeof e.from == "object" && e.from.type === "UrlSchema" && (this.urlSchemas[e.name] = e.from); } collectBlockPlugin(e) { typeof e.from == "object" && e.from.type === "Plugin" && (this.blockPlugins[e.name] = e.from); } walkNodes(e) { const n = (s, r) => { this.collectContext(s, r), this.collectStyle(s), this.collectUrlSchema(s), this.collectBlockPlugin(s), Array.isArray(s.children) && s.children.forEach((i) => n(i, s)); }; Array.isArray(e.nodes) && e.nodes.forEach((s) => n(s)); } } function le(t = {}) { return Object.entries(t).map(([e, n]) => { const s = f(n, !1); return `${e}:${s}`; }); } function pe(t = []) { return t.map((e) => `${e.name}: { from: '${e.from || e.name}', default: ${f(e.default, !0, !1)} }`); } function fe(t = []) { const e = (n) => n ? `[${ee(n).map((i) => i.replace(/\'|\"/gi, "")).join(",")}]` : void 0; return t.map((n) => typeof n == "string" ? `${n}: {}` : ($(n.default) && !n.default.value && (n.default.value = "undefined"), `${n.name}: { type:${e(n.type)}, required: ${f(!!n.required, !0, !1)}, default: ${f(n.default, !0, !1)} }`)); } function me(t = []) { return t.map((e) => `'${typeof e == "string" ? e : e.name}'`); } function A(t = {}, e = []) { return Object.entries(t).map(([n, s]) => { let r = Q(f(s, !1, !1)); return r = k(r, e), r.startsWith("async") ? `async ${n}${r.replace(/^async/, "")}` : `${n}${r}`; }); } function he(t = [], e = []) { const n = t.reduce( (i, c) => (c.id && N(c.source) && (i[`watcher_${c.id}`] = c.source), i), {} ), s = A(n, e), r = t.map((i) => `watcher_${i.id}: { deep: ${i.deep}, immediate:${i.immediate}, handler${Q(i.handler.value)} }`); return { computed: s, watches: r }; } function de(t = {}) { return Object.values(t).map((e) => { if (e.type === "mock") { const n = N(e.mockTemplate) && e.mockTemplate.value || "(params) => ({})"; return `async ${e.name}(...args:any[]) { const mock = this.provider.createMock(${n}) return await mock.apply(this, args); }`; } else { const n = N(e.transform) && e.transform.value || "(res) => res"; return `async ${e.name}(...args:any[]) { return await this.provider.apis['${e.ref}'].apply(this, args).then(${n}); }`; } }); } const ye = [ "img", "input", "br", "hr", "area", "base", "col", "embed", "link", "meta", "param", "source", "track", "wbr" ], L = [ "vIf", "vShow", "vModel", "vFor", "vBind", "vHtml" ]; function z(t, e, n = [], s = {}, r) { const i = []; let c = {}, a = []; const p = []; let o = []; return $e(t).forEach((u) => { const m = []; for (const d of u.children) { let { id: b, name: g, invisible: F, from: y } = d; if (F) continue; const C = ve(g, e, y); C && a.push(C), X(y) && o.push({ id: y.id, name: g }); const { props: P, events: O, handlers: x } = we( d, b, d.props, d.events, s, n ), T = Ce( d.directives, n, p ).join(" "), j = d.children ? Oe( d.children, n, e, s, d ) : ""; Object.assign(c, x); let E = ""; typeof j == "string" ? E = j : (E = (j?.nodes || []).join(` `), Object.assign(c, j?.methods || {}), a = a.concat(j?.components || []), o = o.concat(j?.importBlocks || [])); const _ = ["@dcloudio/uni-h5", "@dcloudio/uni-ui"].includes( y ) ? I(g) : Y(y) || Z(y) ? "component" : g; m.push( ye.includes(_) ? `<${_} ${T} ${P} ${O} />` : `<${_} ${T} ${P} ${O}>${E ? ` ` + E.trim() : ""}</${_}>` ); } const h = xe(u.slot, m.join(` `), r?.id); i.push(h); }), { nodes: i, methods: c, directives: ge(p), components: w(a), importBlocks: w(o, "id") }; } function ge(t) { return w(t).map((e) => `${e.startsWith("v") ? e.substring(1) : e}:${e}`); } function $e(t = []) { const e = /* @__PURE__ */ new Map(); for (const n of t) { const s = typeof n.slot == "string" ? n.slot : n.slot?.name, r = e.get(s); r ? r.children.push(n) : e.set(s, { slot: n.slot, children: [n] }); } return e; } function ve(t, e, n) { if (re.includes(t)) return null; const s = e.get(t); if (s && s.alias) { const r = s.parent ? `${s.parent}.${s.alias}` : s.alias; return `${t}: ${r}`; } return X(n) || s ? t : null; } function X(t) { return !!t && typeof t == "object" && t.type === "Schema"; } function Y(t) { return typeof t == "object" && t.type === "UrlSchema"; } function Z(t) { return typeof t == "object" && t.type === "Plugin"; } function be(t, e, n = []) { return t === "style" ? $(e) ? `:style="${f({ ...e, value: k(e.value, n) })}"` : "" : t === "__class" && $(e) ? `:class="${f({ ...e, value: k(e.value, n) })}"` : typeof e == "string" ? `${t}="${e}"` : $(e) ? `:${t}="${f({ ...e, value: k(e.value, n) })}"` : te(e) ? `:${t}='{${oe( e ).join(", ")}}'` : `:${t}='${JSON.stringify(e)}'`; } function je(t, e = {}, n = []) { if (!!Object.keys(e.style || {}).length) { const i = `${t.name}_${t.id}`; e.class ? typeof e.class == "string" ? e.class = [e.class, i].join(" ") : (e.__class = e.class, e.class = i) : e.class = i, $(e.style) || delete e.style; } const r = t.from; return (Y(r) || Z(r)) && (e.is = { type: "JSExpression", value: t.name }), Object.entries(e).map(([i, c]) => be(i, c, n)); } function Se(t, e, n, s, r) { const i = G(e.modifiers, !0); return r ? `@${t}${i.join("")}="${n}"` : s && s.length > 0 ? `@${t}${i.join("")}="(...args:any[]) => ${n}"` : `@${t}${i.join("")}="${n}"`; } function ke(t, e = {}, n = {}) { const s = {}, r = Array.from(n[t] || /* @__PURE__ */ new Set([])), i = r.length ? `({${r.join(", ")}}, args)` : ""; return { binders: Object.entries(e).map(([a, p]) => { const o = p.handler.value.startsWith("this."), l = o ? q(p.handler.value) : `${v(a)}_${t}${i}`; return o || (s[l] = r.length ? { type: "JSFunction", value: `{ return (${p.handler.value}).apply(this, args); }` } : p.handler), Se(a, p, l, r, o); }), handlers: s }; } function we(t, e, n = {}, s = {}, r = {}, i) { const { binders: c, handlers: a } = ke(e, s, r); return { props: je(t, n, i).join(" "), handlers: a, binders: c, events: c.join(" ") }; } function Ce(t = [], e = [], n = []) { const s = [], { vIf: r, vShow: i, vModels: c, vFor: a, vBind: p, vHtml: o, customDirectives: l } = Pe(t); if (r && s.push(`v-if="${f(r.value, !0, !0, e)}"`), i && s.push( `v-show="${f(i.value, !0, !0, e)}"` ), p && s.push( `v-bind="${f(p.value, !0, !0, e)}"` ), c.forEach((u) => { const m = G(u.modifiers, !0), h = u.arg ? S(u.arg) ? `:[${f(u.arg, !0, !0, e)}]` : `:${u.arg}` : ""; s.push( `v-model${h}${m}="${f(u.value, !0, !0, e)}"` ); }), a) { const { item: u, index: m } = { item: "item", index: "index", ...a.iterator }; s.push( `v-for="(${u}, ${m}) in ${f(a.value, !0, !0, e)}"` ); } return o && s.push( `v-html="${f(o.value, !0, !0, e)}"` ), l && l.length && l.forEach((u) => { if (!u.name) return; let m = "", h = ""; S(u.name) ? (h = f(u.name, !0, !0, e), n.push(h)) : h = u.name; const d = h?.startsWith("v") ? I(h) : I("v-" + h); if (m += d, u.arg && (S(u.arg) ? m += `:[${f(u.name, !0, !0, e)}]` : m += `:${u.arg}`), u.modifiers) { const b = Object.keys(u.modifiers); b.length && (m += b.map((g) => "." + g)); } u.value ? s.push( `${m}="${f(u.value, !0, !0, e)}"` ) : s.push(m); }), s; } function Pe(t = []) { const e = t.filter( (o) => L.includes(o.name) ), n = t.filter( (o) => !L.includes(o.name) ), s = e.find( (o) => v(o.name) === "vIf" ), r = e.find( (o) => v(o.name) === "vFor" ), i = e.find( (o) => v(o.name) === "vShow" ), c = e.find( (o) => v(o.name) === "vBind" ), a = e.find( (o) => v(o.name) === "vHtml" ), p = e.filter( (o) => v(o.name) === "vModel" ); return { vIf: s, vFor: r, vShow: i, vModels: p, vBind: c, vHtml: a, customDirectives: n }; } function Oe(t, e, n, s, r) { return typeof t == "string" ? t : S(t) ? `{{ ${f(t, !1, !0, e)} }}` : Array.isArray(t) ? z(t, n, e, s, r) : ""; } function xe(t, e, n) { if (!t) return e; const s = typeof t == "string" ? { name: t, params: [] } : { params: [], ...t }; return `<template ${`#${s.name}="${s.params?.length > 0 ? `{${s.params?.join(",")}}` : `scope_${n}`}"`}> ${e} </template>`; } function Ee(t, e = [], n = [], s = {}, r = "web") { const i = [ "@dcloudio/uni-h5", "uni-h5", "@dcloudio/uni-ui", "uni-ui" ], c = { vue: ["defineComponent", "reactive"] }, a = []; for (const o of e) { const l = t.get(o.split(":")[0]); if (l && l.package) { const u = c[l.package] ?? (c[l.package] = []), m = l.parent || (l.alias || "").split(".")[0] || l.name; u.push(m), r === "uniapp" && i.includes(l.package) && a.push(m); } } for (const [o, l] of Object.entries(s)) (c[o] ?? (c[o] = [])).push(...Array.from(l)), r === "uniapp" && i.includes(o) && a.push(...Array.from(l)); return { imports: Object.entries(c).filter(([o, l]) => r === "uniapp" ? !i.includes(o) && !!l.length : !!l.length).map(([o, l]) => `import { ${w(l).join( "," )}} from '${o}';`).concat(n), uniComponents: a }; } function _e(t = {}) { const e = []; for (const [n, s] of Object.entries(t)) e.push(` ${n} { ${ce(s)} } `); return e.join(` `); } function Ae(t = {}) { const e = []; return Object.entries(t).forEach(([n, s]) => { e.push( `const ${n} = provider.defineUrlSchemaComponent('${s.url}');` ); }), e; } function Ne(t = {}) { const e = []; return Object.entries(t).forEach(([n, s]) => { e.push( `const ${n} = provider.definePluginComponent(${JSON.stringify(s)});` ); }), e; } function Fe(t, e, n = "web") { const { dsl: s } = t, r = Object.keys(s.computed || {}), i = A(s.lifeCycles, r), c = A(s.computed, r), a = he(s.watch, r), p = de(s.dataSources), { methods: o, nodes: l, components: u, importBlocks: m, directives: h } = z( s.nodes || [], e, r, t.context ), d = [...c, ...a.computed], b = A( { ...o, ...s.methods || {} }, r ), g = m.map((x) => `import ${x.name} from './${x.id}.vue';`); let { imports: F, uniComponents: y } = Ee( e, u, g, t.imports, n ); const C = Object.keys({ ...t.urlSchemas, ...t.blockPlugins }), P = Ae(t.urlSchemas), O = Ne(t.blockPlugins); return { id: s.id, version: s.__VERSION__, name: s.name, state: le(s.state).join(","), inject: pe(s.inject).join(","), props: fe(s.props).join(","), emits: me(s.emits).join(","), watch: a.watches.join(","), lifeCycles: i.join(","), computed: d.join(","), methods: [...p, ...b].join(","), imports: ` ` + F.join(` `), components: ae(u, y).join(","), directives: h.join(","), returns: t.members.join(","), template: l.join(` `), css: s.css || "", style: _e(t.style), urlSchemas: P.join(` `), blockPlugins: O.join(` `), asyncComponents: C.join(","), uniComponents: y, renderer: n === "uniapp" ? "@vtj/uni-app" : "@vtj/renderer" }; } const Ie = ` // @ts-nocheck <%= imports %> import { useProvider } from '<%= renderer %>'; export default defineComponent({ name: '<%= name %>', <% if(inject) { %> inject: { <%= inject %>}, <% } %> <% if(components) { %> components: { <%= components %> }, <% } %> <% if(directives) { %> directives: { <%= directives %> }, <% } %> <% if(props) { %> props: { <%= props %> }, <% } %> <% if(emits) {%> emits: [<%= emits %>], <% } %> setup(props) { const provider = useProvider({ id: '<%= id %>', version: '<%= version %>' }); const state = reactive<Record<string, any>>({ <%= state %> }); <%= urlSchemas %> <%= blockPlugins %> return { state, props, provider <% if(asyncComponents) { %>, <%= asyncComponents %> <% }%> <% if(returns) { %>, <%= returns %> <% } %> }; }, <% if(computed) { %> computed: { <%= computed %> }, <% } %> <% if(methods) { %> methods: { <%= methods %> }, <% } %> <% if(watch) { %> watch: { <%= watch %> }, <% } %> <%= lifeCycles %> }); `.replace(/(\n|\r|\t)/g, ""), Be = ` <template> <%= template %> </template> <script lang="ts"> <%= script %> <\/script> <style lang="scss" scoped> <%= css %> <%= style %> </style> `, Re = U(Ie), Te = U(Be); async function Ue(t, e = /* @__PURE__ */ new Map(), n = [], s = "web", r) { const i = new ue(ne(t), n), c = Fe(i, e, s), a = Re(c), p = Te({ template: c.template, css: await W(c.css, r), script: await ie(a, r), style: await W(c.style, r) }); return await M(p, r).catch((o) => (o.content = p, Promise.reject(o))); } async function De(t) { const e = ` <template> <div> <h3>源码模式页面</h3> <div>文件路径:/.vtj/vue/${t.id}.vue</div> </div> </template> <script lang="ts" setup> <\/script> <style scoped lang="scss"> </style> `; return await M(e); } export { Le as VTJ_CODER_VERSION, De as createEmptyPage, W as cssFormatter, Ue as generator, ie as tsFormatter, M as vueFormatter };