【Hbuilder】用Java开发两个生成代码插件,耗费了我一天半的时间
缘起
某一天在用Hbuilder编写vue页面的时候,发现无论是方法还是css,都需要自己定义后再去编写相应代码
有没有一个插件能自动根据template里的html代码来生成对应的scss层级结构和方法呢?
寻了半天无果,于是决定自己动手造一个轮子
以下是最终效果图
开发后的插件jar包
在Hbuilder里的配置
[{
"name": "格式化css",
"command": ["java", "-Dfile.encoding=utf-8", "-jar", "D:\\css-formatter.jar", "${fileDir}\\${fileName}"],
"type": "process",
"key": "ctrl+alt+c"
},
{
"name": "格式化js",
"command": ["java", "-Dfile.encoding=utf-8", "-jar", "D:\\js-formatter.jar", "${fileDir}\\${fileName}"],
"type": "process",
"key": "ctrl+alt+m"
}
]
唤出Hbuilder设置界面的方式为:
在任意一个vue文件上右键>外部命令/插件>自定义命令
将上面的内容粘贴并保存,然后将两个jar包放在D盘根目录下
jar包可在github项目主页找到,我已将两个插件开源
如果只是使用插件看到这里就可以,以下是各个插件核心代码讲解
scss代码生成插件源码讲解
扫盲:
Sass(scss)是成熟、稳定、强大的CSS预处理器
为Css提供了层级结构和变量定义
让css看起来更像一门编程语言(虽然并不是)
生成Scss核心逻辑
首先对template里的DOM树进行解析,生成对应的json文件(因为json和scss语法很接近)
然后利用jsoup解析文档,并替换style标签中的内容(需要用到正则)
最后得对html文档格式化,去除不必要的标签对
-
首先我们需要创建一个SpringBoot项目,但是不添加任何starter
-
在启动类CssFormatterApplication中获取传入的文件路径(文件路径是以命令行,也就是main方法的args方式传入)
//将启动参数存起来 public static String path; public static void main(String[] args) { log.info("项目启动..."); //判断是否传入文件路径 if(args.length==0){ log.info("找不到文件..."); log.info("终止程序!"); //退出程序 System.exit(-1); } log.info("文件路径为:"+args[0]); path = args[0]; SpringApplication.run(CssFormatterApplication.class, args); }
通过将传入的路径存入静态变量中为我们下一步做准备
-
利用@PostConstruct注解在项目启动时就去执行生成代码的操作
首先我们需要将文件路径下的文件读出来并交给jsoup解析,将template和style标签下的内容都解析出来
log.info("开始处理文件..."); log.info("获得文件路径为"+path); File file = new File(path); Document document = Jsoup.parse(file, "UTF-8"); Element template = document.getElementsByTag("template").get(0); Element style = document.getElementsByTag("style").get(0);
-
接下来就是检查用户有没有设置lang属性,并且对style标签中是否存在内容进行判断
//判断style有没有设置lang属性 String lang = style.attr("lang"); if(StringUtils.isEmpty(lang)){ log.info("用户没有设置scss属性"); style.attr("lang","scss"); } if(!StringUtils.isEmpty(style.text())){ log.info("style中已有内容,终止程序!"); //退出程序 System.exit(-1); }
-
接下来就是整个程序最复杂的步骤
因为我们不知道用户的标签嵌套了多少层,所以我们需要设计一个递归方法去递归template里的层级,并生成对应的json,有关于怎么写递归我会在后面的文章中讲到,这里直接上递归方法
/** * @Description TODO 生成Scss * @author LaoQin * @date 2020/03/20 * @param result 生成结果 * @param element 待生成元素 * @return void */ private JSONObject generateScss(JSONObject result, Element element) { Elements children = element.children(); for(Element child : children){ JSONObject childObj = generateScss(new JSONObject(), child); String classStr = child.attr("class"); if(StringUtils.isEmpty(classStr)){ //没有class默认用标签名 result.put(child.tagName(),childObj); continue; } String[] splitedClassStr = classStr.split(" "); result.put("."+splitedClassStr[0],childObj); } return result; }
-
然后我们就可以使用我们创建的递归方法了
String result = JSON.toJSONString(generateScss(new JSONObject(),template));
-
然后我们需要对多余标签和json进行过滤,让它成为一个标准的vue模板
/** * @Description TODO 过滤最后json结果 * @author LaoQin * @date 2020/03/20 * @param result 返回的结果 * @return java.lang.String */ private String resultFilter(String result) { return result.replace("\"","").replace(":","").replace(",",""); } /** * @Description TODO 过滤某特定标签 * @author LaoQin * @date 2020/03/20 * @param str 待过滤标签 * @param tagNames 过滤标签集合 * @return java.lang.String 过滤后的标签 */ private String tagFilter(String str,String ... tagNames){ for(String tagName: tagNames){ str = str.replace("<" + tagName + ">", "") .replace("</" + tagName + ">", ""); } return str; }
-
最后就是将其写入文件了
//转码 String encodedStr = new String(tempStr.getBytes(), "UTF-8"); FileWriter writer = new FileWriter(file); writer.write(encodedStr); writer.flush(); writer.close();
js代码生成插件源码讲解
核心思路
这个相对会简单一些,只需要分析代码里形如
@methodType="methodName"
的代码,然后替换到methods里面即可这个主要是考察正则表达式的编写
别的就不多说了,直接上整套代码
package com.zfbgt.js.formatter;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author LaoQin
* @Description TODO 启动类
* @date 2020/03/23
*/
@Slf4j
@SpringBootApplication
public class JsFormatterApplication {
/**
* @Description TODO 文件路径
* @author LaoQin
* @date 2020/03/23
*/
private static String path;
public static void main(String[] args) {
log.info("项目启动...");
//判断是否传入文件路径
if (args.length == 0) {
log.info("找不到文件...");
log.info("终止程序!");
//退出程序
System.exit(-1);
}
log.info("文件路径为:" + args[0]);
path = args[0];
SpringApplication.run(JsFormatterApplication.class, args);
}
/**
* @Description TODO 预编译Patterns
* @author LaoQin
* @date 2020/03/23
*/
private static Pattern SCRIPT_PATTERN = Pattern.compile("}$");
private static Pattern METHOD_PATTERN = Pattern.compile("(\\s+)(@)(\\w+)(=\")(.*)(\")");
private static Pattern INNER_METHOD_PATTERN = Pattern.compile("(.*)(\\s*)([:\\(])");
private static Pattern METHOD_TEMPLATE_PATTERN = Pattern.compile("methods\\s*:\\s*\\{[\\s\\S]*");
private static Pattern METHOD_NAME_PATTERN = Pattern.compile("(\\S*)\\(");
/**
* @Description TODO 预编译常量列表
* @author LaoQin
* @date 2020/03/23
*/
private static String METHODS = "methods";
private static String METHOD_TEMPLATE = ",methods:{}}";
@PostConstruct
public void formatFile() throws IOException {
log.info("开始处理文件...");
log.info("获得文件路径为" + path);
File file = new File(path);
Document document = Jsoup.parse(file, "UTF-8");
//获取模板代码
Element template = document.getElementsByTag("template").get(0);
//获取js代码
Element script = document.getElementsByTag("script").get(0);
//没有methods节点则创建节点
String replacedScript = script.html();
TreeSet<String> nowMethodSet = new TreeSet<>();
if (!script.html().contains(METHODS)) {
Matcher matcher = SCRIPT_PATTERN.matcher(script.html());
if (matcher.find()) {
replacedScript = matcher.replaceAll(METHOD_TEMPLATE);
}
}else {
//如果存在methods节点则维护一个现有method set
Matcher matcher = METHOD_TEMPLATE_PATTERN.matcher(replacedScript);
if(matcher.find()){
String methodTemplateGroup = matcher.group();
Matcher innerMatcher = INNER_METHOD_PATTERN.matcher(methodTemplateGroup);
while (innerMatcher.find()){
String trimStr = innerMatcher.group(1).trim();
if(trimStr.contains(":")){
trimStr = trimStr.split(":")[0].trim();
}
//除了methods本身以外的都加入nowMethodSet
if(!METHODS.equals(trimStr)){
nowMethodSet.add(trimStr);
}
}
}
}
//匹配方法的正则表达式
Matcher matcher = METHOD_PATTERN.matcher(template.html());
Set methodSet = new TreeSet<String>();
while (matcher.find()) {
//获取方法定义
String method = matcher.group(5);
Matcher methodNameMatcher = METHOD_NAME_PATTERN.matcher(method);
String methodName = null;
if(methodNameMatcher.find()){
methodName = methodNameMatcher.group(1);
}
if(nowMethodSet.contains(methodName==null?method:methodName)){
continue;
}
//补全方法定义
if(!method.contains("(")){
method+="()";
}
//补全方法体
method+="{}";
//添加至方法列表
methodSet.add(method);
}
//处理待添加队列
String methodStr = methodSet.toString();
String replacedMethodStr = methodStr.replace("[", "").replace("]", "");
if(nowMethodSet.size()>0){
replacedMethodStr = ","+replacedMethodStr;
}
replacedMethodStr+="}}";
StringBuffer sb = new StringBuffer(replacedScript);
sb.replace(sb.lastIndexOf("}"),sb.length(),"");
sb.replace(sb.lastIndexOf("}"),sb.length(),"");
sb.append(replacedMethodStr);
script.text(sb.toString().replaceAll("\n","&huanhang"));
//过滤多余标签
String tempStr = tagFilter(document.html(), "html","head","body");
tempStr = tempStr.replaceAll("&huanhang","\n").replaceAll(">",">");
FileWriter writer = new FileWriter(file);
writer.write(tempStr);
writer.flush();
writer.close();
}
/**
* @Description TODO 过滤某特定标签
* @author LaoQin
* @date 2020/03/20
* @param str 待过滤标签
* @param tagNames 过滤标签集合
* @return java.lang.String 过滤后的标签
*/
private String tagFilter(String str,String ... tagNames){
for(String tagName: tagNames){
str = str.replace("<" + tagName + ">", "")
.replace("</" + tagName + ">", "");
}
return str;
}
}
以上就是全部内容,感兴趣的小伙伴可以Git下来学习
也请大佬不吝赐教
~ from 老邋遢