前端好庫:在編譯期用 preval.macro 生成代碼

你有時候會不會希望…代碼能寫它自己?


前端好庫:在編譯期用 preval.macro 生成代碼

寫能寫代碼的代碼:元編程

我們都聽說過,在 C++ 裡有模板元編程,可以在編譯期生成代碼。這樣我們只需要寫很少的代碼,就能四兩撥千斤,一言可為天下法,生成連篇累牘的代碼。

轉念一想,現代的 JS 也需要經過 babel 來編譯,那我們是不是也能在編譯期做一些好玩的事情呢?


前端好庫:在編譯期用 preval.macro 生成代碼

這位小夥也是這麼想的,他開發了 babel-plugin-macros 這個 babel 插件,讓你可以在編譯期運行 JS,也就是執行「宏」,然後又開發了 preval.macro 這個宏,可以在編譯期生成代碼。

聽起來是不是特別「元(meta)」?(經常思考這種問題的人髮際線都蠻高的…)


前端好庫:在編譯期用 preval.macro 生成代碼

模板元編程…好!

例如我們要寫一個能運行在瀏覽器上的 npm 包,所以裡面不能出現 const fs = require('fs');

那麼我們可以選擇在編譯期使用這些 nodejs 才有的功能,等發佈了,到了瀏覽器上,這些「宏」會通通不見:

<code>// @flowimport preval from 'preval.macro';// 這些是在編譯期執行的代碼,到了瀏覽器上運行時執行的時候,它們就已經消失了const importExport = `  const fs = require('fs');  const path = require('path');  module.exports = fs.readFileSync(path.join(__dirname, '`;const tail = ".txt'), 'utf8')";// 盤古詞典export const pangu: string = preval`${importExport}pangu${tail}`;// 擴展詞典(用於調整原盤古詞典)export const panguExtend1: string = preval`${importExport}panguExtend1${tail}`;export const panguExtend2: string = preval`${importExport}panguExtend2${tail}`;/<code>

可以看到 importExport 本來是一個字符串,使用 preval 這個「模板字符串標籤」之後,在編譯期就自動生成了相應代碼。

就好像編譯器幫我們把代碼複製黏貼了多份再執行一樣。


還有其它用法,例如我們想要引用一個 JSON 文件裡的一個字段,但是不希望 webpack 把整個 JSON 文件都打包進去,我們就可以在編譯期導入這個 JSON,取出要用的字段:

<code>const APIBase: string = preval`  const config = require('../../config.js');  module.exports = config.API;`;/<code>


類似地,還有 codegen.macro,也可以在編譯期執行代碼。它與 preval.macro 的區別在於,preval 最後留給運行時的是一個值,不會留下一絲代碼;而 codegen 會留下一串運行時能執行的代碼,而這串代碼是生成出來的。

例如我們想要讀取一個JSON 配置文件,動態生成一大堆 export const 語句的話:

<code>import codegen from 'codegen.macro';// 把公式變量預先用 v 函數調用一遍,方便引用,減少代碼量codegen`const fs = require('fs');const formulasJSON = JSON.parse(fs.readFileSync(require.resolve('../formulas.json'), 'utf8'));const variableNames = [  ...Object.keys(formulasJSON),  ...Object.keys(formulasJSON).map(name => '上期' + name),  ...Object.keys(formulasJSON).map(name => '上上期' + name),];module.exports = variableNames  .map(    variableName =>      'export const ' + variableName.replace(/[()、:()]/g, '') + ' = loader => loader.load("' + variableName + '")'  )  .join(';');`;/<code>

是不是很神奇?就像大猩猩測試( Gorilla Testing )會對一個功能或模塊進行重複「上百次」的測試, 人類根本受不了這樣子的測試方式,所以大猩猩測試的另一個別名是「令人沮喪的測試(Frustrating Testing)」一樣,讓嬌貴的程序員去重複寫一百行相似的代碼是不可能的。

但有時候,重 復 勞 動 不 可 避,咋辦?

就,自 動 編 程,咯!


分享到:


相關文章: