構建vscode的vue組件代碼補全插件以及上傳

1.安裝工具

安裝vscode Generator前端

npm install -g yo generator-codevue

2.構建初始項目

yo codenode

  • 選擇New Code Snippetsgit

  • 根據提示完成後續配置填寫程序員

  • 完成後自動生成一個snippets初始項目,項目內容以下es6

  • 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了。github

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

  平時作web開發的時候都是先使用vue-loader將.vue編譯成js, 那有沒有一種方式能夠在require的時候動態編譯將.vue編譯成js呢。固然有的,先去深刻了解require原理。shell

require模塊的過程:Module._load("a.js") --> var module = new Module(); --> module.load("a.js") --> module._compile()npm

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源碼

相關文章
相關標籤/搜索