构建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源码

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • ubuntu中科大镜像源_中国科技大学开源镜像

    ubuntu中科大镜像源_中国科技大学开源镜像#vim/etc/apt/sources.list//修改网络镜像源文件debhttps://mirrors.ustc.edu.cn/ubuntu/bionicmainrestrict

    2022年8月2日
    37
  • 7本免费的Java电子书和教程

    7本免费的Java电子书和教程本文由ImportNew-唐小娟翻译自Javapapers。欢迎加入翻译小组。转载请见文末要求。1.ThinkinginJava(ThirdEdition)本书的作者是BruceEckel,它一直都是Java最畅销的免费电子书。这本书可以帮助你系统的学习Java,里面包含有很多好的代码示例。第三版仍旧是免费的,直到第四版才开始收费,不过仍旧值得买一本收藏。Think…

    2022年7月8日
    27
  • 对dropout的理解详细版[通俗易懂]

    对dropout的理解详细版[通俗易懂]dropout可以让模型训练时,随机让网络的某些节点不工作(输出置零),也不更新权重(但会保存下来,下次训练得要用,只是本次训练不参与bp传播),其他过程不变。我们通常设定一个dropoutradio=p,即每个输出节点以概率p置0(不工作,权重不更新),假设每个输出都是独立的,每个输出都服从二项伯努利分布p(1-p),则大约认为训练时,只使用了(1-p)比例的输出,相当于每次训练一个子网络。测…

    2022年5月2日
    73
  • 什么叫侧面指纹识别_新科技?侧面指纹解锁有什么不同?

    什么叫侧面指纹识别_新科技?侧面指纹解锁有什么不同?原标题:新科技?侧面指纹解锁有什么不同?手机的时代更新的太快,以前诺基亚的密码解锁,到后来安卓苹果的指纹解锁,虹膜解锁,面部识别解锁,各式各样的解锁方式。不过今天说的主题也是指纹解锁,不过不是以往的正面home键,也不是背面指纹解锁器,而是侧面指纹解锁。不得不承认,智能交互时代不断刷新人们的生活,智能手机行业也发展得如火如荼。各智能手机厂商凭借在手机里边加入各种“黑科技”,用自己独特的风格来吸引消…

    2022年6月15日
    70
  • Py||Large integer multiplication

    Py||Large integer multiplication题目描述AlargeintegerisanintegerthatfarexceedstherangeofintegertypesrepresentedbythePythonlanguage,suchas10tothepowerof100.Pleasecalculatethemultiplyresultoftwolargein…

    2022年7月25日
    9
  • 区间dp入门_状压dp

    区间dp入门_状压dp一.什么是区间dp?顾名思义:区间dp就是在区间上进行动态规划,求解一段区间上的最优解。主要是通过合并小区间的最优解进而得出整个大区间上最优解的dp算法。二.核心思路既然让我求解在一个区间上的最优解,那么我把这个区间分割成一个个小区间,求解每个小区间的最优解,再合并小区间得到大区间即可。所以在代码实现上,我可以枚举区间长度len为每次分割成的小区间长度(由短到长不断合并),内层枚举该长度下可以的…

    2025年11月13日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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