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();
複製程式碼