mirror of
https://gitee.com/newgateway/vtj.git
synced 2026-05-16 19:26:51 +08:00
feat: ✨ parser style
This commit is contained in:
@@ -1,22 +1,43 @@
|
||||
import { type BlockSchema, type NodeSchema, BlockModel } from '@vtj/core';
|
||||
import {
|
||||
type BlockSchema,
|
||||
type NodeSchema,
|
||||
type Dependencie,
|
||||
BlockModel
|
||||
} from '@vtj/core';
|
||||
import { tsFormatter } from '@vtj/coder';
|
||||
import { parseSFC, isJSCode } from '../shared';
|
||||
import { parseTemplate } from './template';
|
||||
import { parseScripts } from './scripts';
|
||||
import { parseScripts, type ImportStatement } from './scripts';
|
||||
import { parseStyle } from './style';
|
||||
import { patchCode } from './utils';
|
||||
|
||||
export interface ParseVueOptions {
|
||||
id: string;
|
||||
name: string;
|
||||
source: string;
|
||||
dependencies?: Dependencie[];
|
||||
}
|
||||
|
||||
export async function parseVue(options: ParseVueOptions) {
|
||||
const { id, name, source } = options;
|
||||
const { id, name, source, dependencies = [] } = options;
|
||||
const sfc = parseSFC(source);
|
||||
const { state, watch, lifeCycles, computed, methods, props, emits, inject } =
|
||||
parseScripts(sfc.script);
|
||||
const { nodes, slots, context } = parseTemplate(id, name, sfc.template);
|
||||
const { styles, css } = parseStyle(sfc.styles.join('\n'));
|
||||
const {
|
||||
state,
|
||||
watch,
|
||||
lifeCycles,
|
||||
computed,
|
||||
methods,
|
||||
props,
|
||||
emits,
|
||||
inject,
|
||||
handlers,
|
||||
imports
|
||||
} = parseScripts(sfc.script);
|
||||
const { nodes, slots, context } = parseTemplate(id, name, sfc.template, {
|
||||
handlers,
|
||||
styles
|
||||
});
|
||||
const dsl: BlockSchema = {
|
||||
id,
|
||||
name,
|
||||
@@ -29,16 +50,19 @@ export async function parseVue(options: ParseVueOptions) {
|
||||
methods,
|
||||
slots,
|
||||
emits,
|
||||
nodes
|
||||
nodes,
|
||||
css
|
||||
};
|
||||
|
||||
const computedKeys = Object.keys(computed || {});
|
||||
const { libs } = parseDeps(imports, dependencies);
|
||||
await walkDsl(dsl, async (node: NodeSchema) => {
|
||||
await walkNode(node, async (content: any) => {
|
||||
if (isJSCode(content)) {
|
||||
const options = {
|
||||
context,
|
||||
computed: [],
|
||||
libs: {}
|
||||
computed: computedKeys,
|
||||
libs
|
||||
};
|
||||
const code = await tsFormatter(content.value);
|
||||
content.value = patchCode(code, node.id as string, options);
|
||||
@@ -88,3 +112,28 @@ async function walkNode(node: NodeSchema, callback: (n: any) => Promise<void>) {
|
||||
};
|
||||
await walking(node);
|
||||
}
|
||||
|
||||
function parseDeps(
|
||||
imports: ImportStatement[] = [],
|
||||
dependencies: Dependencie[] = []
|
||||
) {
|
||||
const libs: Record<string, string> = {};
|
||||
const depsMap = dependencies.reduce(
|
||||
(prev, current) => {
|
||||
prev[current.package] = current.library;
|
||||
return prev;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
);
|
||||
for (const { from, imports: names } of imports) {
|
||||
names.forEach((name) => {
|
||||
const library = depsMap[from];
|
||||
if (library) {
|
||||
libs[name] = library;
|
||||
}
|
||||
});
|
||||
}
|
||||
return {
|
||||
libs
|
||||
};
|
||||
}
|
||||
|
||||
33
packages/parser/src/vue/style.ts
Normal file
33
packages/parser/src/vue/style.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import postcss from 'postcss';
|
||||
|
||||
export type CSSRules = Record<string, Record<string, string>>;
|
||||
|
||||
export interface ParseStyleResult {
|
||||
styles: CSSRules;
|
||||
css: string;
|
||||
}
|
||||
export function parseStyle(content: string) {
|
||||
const styles: CSSRules = {};
|
||||
const css: string[] = [];
|
||||
const root = postcss.parse(content);
|
||||
const classRegex = /^.[\w]+_[\w]{5,}/;
|
||||
for (const rule of root.nodes) {
|
||||
if (rule.type === 'rule') {
|
||||
const style: Record<string, string> = {};
|
||||
if (classRegex.test(rule.selector)) {
|
||||
rule.nodes.forEach((decl) => {
|
||||
if (decl.type === 'decl') {
|
||||
style[decl.prop] = decl.value;
|
||||
}
|
||||
});
|
||||
styles[rule.selector] = style;
|
||||
} else {
|
||||
css.push(rule.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
styles,
|
||||
css: css.join('\n')
|
||||
};
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
type NodeEvents,
|
||||
type JSExpression,
|
||||
type NodeDirective,
|
||||
type BlockSlot
|
||||
type BlockSlot,
|
||||
type JSFunction
|
||||
} from '@vtj/core';
|
||||
import { compileTemplate } from '@vue/compiler-sfc';
|
||||
import {
|
||||
@@ -20,13 +21,29 @@ import {
|
||||
import { uid } from '@vtj/base';
|
||||
import { isJSExpression, isNodeSchema } from '../shared';
|
||||
import { getJSExpression, getJSFunction } from './utils';
|
||||
import type { CSSRules } from './style';
|
||||
|
||||
let __slots: BlockSlot[] = [];
|
||||
let __context: Record<string, Set<string>> = {};
|
||||
let __handlers: Record<string, JSFunction> = {};
|
||||
let __styles: CSSRules = {};
|
||||
|
||||
export function parseTemplate(id: string, name: string, content: string = '') {
|
||||
export interface ParseTemplateOptions {
|
||||
handlers?: Record<string, JSFunction>;
|
||||
styles?: CSSRules;
|
||||
}
|
||||
|
||||
export function parseTemplate(
|
||||
id: string,
|
||||
name: string,
|
||||
content: string = '',
|
||||
options?: ParseTemplateOptions
|
||||
) {
|
||||
__slots = [];
|
||||
__context = {};
|
||||
__handlers = options?.handlers || {};
|
||||
__styles = options?.styles || {};
|
||||
|
||||
const result = compileTemplate({
|
||||
id,
|
||||
filename: name,
|
||||
@@ -64,7 +81,21 @@ function getProps(nodes: Array<AttributeNode | DirectiveNode>) {
|
||||
for (const item of nodes) {
|
||||
// 普通属性
|
||||
if (item.type === NodeTypes.ATTRIBUTE) {
|
||||
props[item.name] = item.value?.content || '';
|
||||
if (item.name === 'class') {
|
||||
const classValue = item.value?.content || '';
|
||||
const classRegex = /[\w]+_[\w]{5,}/;
|
||||
const selector = classValue.match(classRegex)?.[0] || '';
|
||||
const classes = classValue.split(' ').filter((n) => n !== selector);
|
||||
const style = __styles[`.${selector}`];
|
||||
if (style) {
|
||||
props.style = style;
|
||||
}
|
||||
if (classes.length) {
|
||||
props.class = classes.join(' ');
|
||||
}
|
||||
} else {
|
||||
props[item.name] = item.value?.content || '';
|
||||
}
|
||||
}
|
||||
|
||||
// 动态绑定的属性
|
||||
@@ -81,7 +112,10 @@ function getProps(nodes: Array<AttributeNode | DirectiveNode>) {
|
||||
return props;
|
||||
}
|
||||
|
||||
function getEvents(nodes: Array<AttributeNode | DirectiveNode>) {
|
||||
function getEvents(
|
||||
nodes: Array<AttributeNode | DirectiveNode>,
|
||||
handlers: Record<string, JSFunction> = {}
|
||||
) {
|
||||
const events: NodeEvents = {};
|
||||
for (const item of nodes) {
|
||||
// 动态绑定的属性
|
||||
@@ -97,11 +131,23 @@ function getEvents(nodes: Array<AttributeNode | DirectiveNode>) {
|
||||
},
|
||||
{} as Record<string, boolean>
|
||||
);
|
||||
events[item.arg.content] = {
|
||||
name: item.arg.content,
|
||||
handler: getJSFunction(`(${item.exp?.loc.source})`),
|
||||
modifiers
|
||||
};
|
||||
const code = item.exp?.loc.source || '';
|
||||
const regex = new RegExp(`${item.arg.content}_\[\\w\]\{5\,\}`);
|
||||
const name = code.match(regex)?.[0] || '';
|
||||
const handler = handlers[name];
|
||||
if (name && handler) {
|
||||
events[item.arg.content] = {
|
||||
name: item.arg.content,
|
||||
handler,
|
||||
modifiers
|
||||
};
|
||||
} else {
|
||||
events[item.arg.content] = {
|
||||
name: item.arg.content,
|
||||
handler: getJSFunction(`(${code})`),
|
||||
modifiers
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,7 +278,7 @@ function createNodeSchema(
|
||||
const dsl: NodeSchema = {
|
||||
name: node.tag,
|
||||
props: getProps(node.props),
|
||||
events: getEvents(node.props),
|
||||
events: getEvents(node.props, __handlers),
|
||||
directives: getDirectives(scope || node)
|
||||
};
|
||||
dsl.id = getNodeId(dsl);
|
||||
|
||||
Reference in New Issue
Block a user