构建vscode的vue组件代码补全插件以及上传

构建vscode的vue组件代码补全插件以及上传

大家好,又见面了,我是你们的朋友全栈君。

1.安装工具

安装vscode Generator

npm install -g yo generator-code

2.构建初始项目

yo code

  • 选择New Code Snippets

  • 根据提示完成后续配置填写

  • 完成后自动生成一个snippets初始项目,项目内容如下

  • snippets插件不同于其他插件,此插件关键内容就是一个json文件,内容格式如下

  • 照着葫芦画瓢就行。

"Affix": {

        "prefix": "Affix",

        "body": ["<affix", ":offsetTop =\"offsetTop\"", ":offsetBottom =\"offsetBottom\"", "></affix>"],

        "description": "affix组件配置参数:"

    }

复制代码
  • 效果:

  • 回车后自动填充代码片段

3.snippet.json自动生成

扩展require方法

  我们需要做的就是把每一个组件的信息拿出来,按snippet的格式输入到snippet.json文件中去,如何从组件库中提取每一个组件对应的props呢,当然不是手工收集这种蠢蠢的方式,程序员的方式当然是用代码工具避免重复劳动。我的想法是写一个工具方法从组件中获取props,然后在node环境中执行,并生成最终的snippet.json文件。我们知道require一个模块时,会返回到export中的对象,这样就能拿到props了。


const component = require("./src/components/alert/index.js");

console.log(component);

复制代码
  • 执行 node snippetDemo.js,第一个问题出现了

  node对ES6是部分支持的,在node环境中并不支持ES6模块,这个很容易可以找到解决方案,这边用的是babel-register,安装后直接require(“babel-register”)。再次执行,这次报错不一样了,由于组件是vue单文件组件的形式,node环境中并不能编译通过,因此在template部分报了错。

  平时做web开发的时候都是先使用vue-loader将.vue编译成js, 那有没有一种方式可以在require的时候动态编译将.vue编译成js呢。当然有的,先去深入了解require原理。

require模块的过程:Module._load(“a.js”) –> var module = new Module(); –> module.load(“a.js”) –> module._compile()

Module.prototype.require = function(path) {
  return Module._load(path, this);
};
Module._load = function(request, parent, isMain) {

  var filename = Module._resolveFilename(request, parent);
  
  // 判断是否为内置模块
  if (NativeModule.exists(filename)) {
    return NativeModule.require(filename);
  }

  // 生成模块实例入缓存
  var module = new Module(filename, parent);
  Module._cache[filename] = module;

  // 加载模块
  try {
    module.load(filename);
  }

  // 输出模块的exports属性
  return module.exports;
};
复制代码
  • module.load方法如下,加载模块时先确定模块的后缀名,然后执行相应文件的加载方法
Module.prototype.load = function(filename) {
  var extension = path.extname(filename) || '.js';
  if (!Module._extensions[extension]) extension = '.js';
  Module._extensions[extension](this, filename);
  this.loaded = true;
};
复制代码
  • js文件的extension方法定义
Module._extensions['.js'] = function(module, filename) {
  var content = fs.readFileSync(filename, 'utf8');
  module._compile(stripBOM(content), filename);
};
复制代码

  从上面的代码可以看出require一个js文件时,实际上io读取文件后会通过moudle.load的方法加载文件,然后依次执行_extension里挂载的方法,读取文件字符串然后执行_compile。如果在module._compile之前多做一步,将.vue文件解析成js文件,那么就可以实现require的时候动态编译vue文件,实现我需要的功能了。

  • 因此我写了一个工具模块,定义了一个register方法

function register(options) {

  require.extensions[VUE_EXT] = (module, file) => {

    let fileString = fs.readFileSync(file, 'utf8');

    let script = compile(fileString, file);

    console.log(script);

    return module._compile(script, file);

  };

  return true;

}

复制代码
  • 其中compile部分代码如下

function compile(content, file) {

  let vue = {};

  let selections = ['script', 'template', 'style'];

  var parts = vueCompiler.parseComponent(content, {

    pad: "space"

  });

  for (let section of selections) {

    let tempPart = parts[section];

    let content = getContent(tempPart, path.dirname(file));

    vue[section] = content;

  }

  let result = require('babel-core').transform(vue.script, {

    plugins: ['transform-es2015-modules-commonjs']

  });

  vue.script = result.code + injectTemplate(vue.template);

  return vue.script;

}

function getContent(part, filePath) {

  if(!part){

    return "";

  }

  return part.src ?

    loadSrc(part.src, filePath) :

    part.content

}



function loadSrc(src, filePath) {

  var dir = path.dirname(filePath)

  var srcPath = path.resolve(dir, src);

  try {

    return fs.readFileSync(srcPath, 'utf-8')

  } catch (e) {

    console.log("fail to load");

  }

}

复制代码

  主要用了vue-template-compiler这个模块,可以将vue单文件中的template,script,style部分分别提取出来。

  • 将template部分注入

function injectTemplate(template) {

  let js = [

    '',

    'var __vue__options__ = (module.exports.__esModule) ?',

    'module.exports.default : module.exports;',

    '__vue__options__.template = ' + JSON.stringify(template) + ';',

    '',

  ];

  return js.join(os.EOL);

}

复制代码
  • 为解决import问题,先使用babel的transform-es2015-modules-commonjs插件将es6模块转成commonjs模块

let result = require('babel-core').transform(vue.script, {

    plugins: ['transform-es2015-modules-commonjs']

  });

复制代码
  • 然后将最后的script代码放到module._compile中去执行。

  • 引入将刚写的这个模块试用一下


require("babel-register");

require("vue-register").register();

const component = require("./src/components/affix/index.js");

console.log(component);

复制代码

  • 已经可以获取到vue组件中的export部分,从中可以提取到props部分。

  到此给require添加钩子实现动态编译vue文件的功能已经完成了,babel-register也是用了这种方式使得require文件时动态使用babel编译。

使用字符串读取

  当我使用写好的工具去require所有的组件时,又出现了别的问题~

  我们的前端组件库某些组件依赖了一些辅助工具函数,有些工具函数使用了window对象,而在node环境中是没有window对象的。到此为止,这条路走不通了,而且这样也获取不到每一个props属性的注释,只能换条路走。

  我想到的是使用fs.readFileSync拿到组件代码字符串,然后匹配props,获取到完整的props字符串,并执行props字符串代码得到props对象。困扰我很久的问题就是匹配到”props:{“开始,那怎么匹配结束的”}”,不知道这样的正则怎么写,我最终用了最low的方式,从”props:{“开始遍历,记录”{“和”}”的个数,直到遇到和第一个”{“匹配的”}”。同时顺便获取了这串props字符串中的所有注释,以作为snippets中的description。


//从代码string中获取props

let getProps = (str) => {

        var lIndex = 0,

            RIndex = 0,

            sp = str.split(/props\s*:\s*{/)[1],

            i = 0;

        if (!sp) {

            return {}

        }

        while (lIndex >= RIndex) {

            lIndex += sp[i] === "{" ? 1 : 0;

            RIndex += sp[i] === "}" ? 1 : 0;

            i++;

        }

        var propString = '{' + sp.substring(0, i - 1) + '}';

        return {

            propsData: eval('(' + propString + ')'),

            description: propString.match(/(?:^|\n|\r)\s*\/\/.*(?:\r|\n|$)/g) || []

        }

    }

复制代码

注意:使用eval(‘(‘ + propString + ‘)’)可以强制将括号内的表达式转化为对象,而不是作为语句来执行。

  • 获取到props之后,按snippets.json的格式输出

    //循环读取所有组件的props,输出snippets格式

let readProps = (componentMap) => {

    let snippets = {};

    var ComponentNames = Object.keys(componentMap);

    ComponentNames.forEach(name => {

        var fileString = fs.readFileSync(componentMap[name], {

            encoding: 'utf8'

        });

        var parts = vueCompiler.parseComponent(fileString, {

            pad: "space"

        });

        var tempContent = fileString;

        if (parts && parts.script) {

            tempContent = parts.script.content;

        }

        let props = {};

        try {

            props = getProps(tempContent);

        } catch (err) {

            // console.error(name,err);       

        }

        let propsDescription = props.description ? props.description.join(",").replace(/\/\//g, "") : "";

        let a = [];

        for (let key in props.propsData) {

            if (props.propsData[key].type !== Boolean) {

                a.push(`:${key} ="${key}"`);

            }

        }

        const kebabName = hyphenate(name);

        snippets[name] = {

            prefix: name,

            body: [

                `<${kebabName}`,

                ...a,

                `></${kebabName}>`

            ],

            description: `${kebabName}组件配置参数:${propsDescription}`,

        }

    });

    return snippets;

}

复制代码
  • 然后将生成的内容写入snippets插件项目中的snippets.json中

//生成文件,并填入之前读取的文件内容

let writeFile = (file) => {

        return new Promise((res, rej) => {

            (async function () {

                await fs.writeFile("plugin/spui-snippets-master/snippets/snippets.json", JSON.stringify(file), (err) => {

                    if (err) rej(err)

                })

                res('success');

            })()

        })

    }

复制代码

4.发布插件

  最后是插件的上传,关于注册,token的申请等直接参考官方文档https://code.visualstudio.com/docs/extensions/publish-extension。全局安装vsce,然后在插件目录下执行 vsce publish就可以上传插件。我考虑将插件的上传加入插件snippets.json的构建流程中,最终实现的效果是执行node a.js可以一键完成props读取,snippets.json的构建,snippet插件的上传。

  这里使用了node中的child_process模块衍生子进程,使用exec方法完成publish这个子进程操作。 exec接收三个参数:(command[, options][, callback]),command为shell命令,在这边执行发布命令’vsce publish minor -p <我的token>’,通过options参数中的cwd设置子进程的当前工作目录,process.cwd()是父进程的当前目录,通过拼接将子进程的工作目录设置到snippet插件目录下。


//发布插件

let publishExtensions = () => {

    return new Promise((res, rej) => {

        var cmdStr = 'vsce publish minor -p <我的token>';

        var cmdOption = {

            cwd: process.cwd() + "/plugin/spui-snippets-master"

        }

        exec(cmdStr, cmdOption, function (err, stdout, stderr) {

            if (err) {

                console.log(err);

            } else {

                res('success');

            }

        });

    })

}

复制代码

  最终调用系列方法


async function creatSnippets() {

    try {

        let componentsMap = Object.assign(fileDisplay('./src/components'), fileDisplay('./src/b-component'));

        await writeFile(readProps(componentsMap));

        console.log(`Successfully created snippets`);

        await publishExtensions();

        return console.log(`Successfully publish snippets`);

    } catch (err) {

        console.error(err);

    }

}

creatSnippets();

复制代码

vue-register源码

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/107544.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 一线工程师告诉你嵌入式真实现状与发展前景「建议收藏」

    个人说明:本人并不是年薪百万的技术大牛,但总算是一名合格的嵌入式工程师,现在某企业担任嵌入式软件工程师开发一职,以下观点可能会带有片面或者分析不全,但却是一名一线企业嵌入式软件开发者真实感受和所得,希望能帮助那些有需要的人,我明白年轻人出来打拼都不容易。为何要写这片文章?小生也是过来人,踩过你们踩过的坑百度搜索“嵌入式”、“嵌入式开发”、“嵌入式发展前景”等字眼,出来的都是一大堆培训机构,…

  • stimulsoft mysql_Stimulsoft Server

    stimulsoft mysql_Stimulsoft ServerStimulsoftReports.Server是一个客户服务器系统,允许您轻松、高效地使用报表实现完整的工作周期,包括从执行信息处理任务的设计和自动化到为用户准备便利的结果演示。快速和现代化的StimulsoftReports技术实现了报表模块的功能,并且灵活性和可靠性都经过了测试。StimulsoftReports.Server的特性概括:使用图形元素和不同的图表可以将通用数据源转换为明亮和…

  • 镁光闪存颗粒对照表_内存颗粒型号识别

    镁光闪存颗粒对照表_内存颗粒型号识别容量/MBSamsung三星ETRON钰创Zentel力积Hynix海力士Elpida尔必达2MBN/AEM636165TS-6GN/AN/A8MBK4S641632N-EM638165TS-6GA3V64S39FTPHY57V641620E/FTEDS6416AHTA-16MBK4S2816320-EM639165TS-6GA3V28S40FTPHY57V1262GF/TR-60/70EDS…

  • java删除linux文件_Java删除文件

    java删除linux文件_Java删除文件//将缓冲文件夹中的文件删除Strings=”D:\\txt\\inBuffer\\”+fileInfo[0]+”\\”+fileID;//文件的绝对路径Filefile=newFile(s);if(file.exists()){booleand=file.delete();if(d){System.out.print(“删除成功!”);}else{System.out…

  • Python将数据写入txt文件_python将内容写入txt文件

    Python将数据写入txt文件_python将内容写入txt文件一、读写txt文件1、打开txt文件Note=open(‘x.txt’,mode=’w’)函数=open(x.扩展名,mode=模式)模式种类:w只能操作写入(如果而文件中有数据,再次写入内容,会把原来的覆盖掉)r只能读取a向文件追加w+可读可写r+可读可写a+可读可追加wb+写入进制数据2、向文件中写入数据第一种写入方式:write写入Note.write…

  • php设置header参数_php发送get请求

    php设置header参数_php发送get请求voidheader(stringstring[,boolreplace[,inthttp_response_code]])voidheader(stringstring[,boolreplace[,inthttp_response_code]])header()是用来发送HTTPHeader的。replace是个可选的参数,指示是否替代一个先期相

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号