安裝vscode Generator前端
npm install -g yo generator-codevue
yo codenode
選擇New Code Snippetsgit
根據提示完成後續配置填寫程序員
完成後自動生成一個snippets初始項目,項目內容以下es6
"Affix": {
"prefix": "Affix",
"body": ["<affix", ":offsetTop =\"offsetTop\"", ":offsetBottom =\"offsetBottom\"", "></affix>"],
"description": "affix組件配置參數:"
}
複製代碼
咱們須要作的就是把每個組件的信息拿出來,按snippet的格式輸入到snippet.json文件中去,如何從組件庫中提取每個組件對應的props呢,固然不是手工收集這種蠢蠢的方式,程序員的方式固然是用代碼工具避免重複勞動。個人想法是寫一個工具方法從組件中獲取props,而後在node環境中執行,並生成最終的snippet.json文件。咱們知道require一個模塊時,會返回到export中的對象,這樣就能拿到props了。github
const component = require("./src/components/alert/index.js");
console.log(component);
複製代碼
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.prototype.load = function(filename) {
var extension = path.extname(filename) || '.js';
if (!Module._extensions[extension]) extension = '.js';
Module._extensions[extension](this, filename);
this.loaded = true;
};
複製代碼
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文件,實現我須要的功能了。
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;
}
複製代碼
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部分分別提取出來。
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);
}
複製代碼
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);
複製代碼
到此給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格式
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;
}
複製代碼
//生成文件,並填入以前讀取的文件內容
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');
})()
})
}
複製代碼
最後是插件的上傳,關於註冊,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();
複製代碼