前端好库:在编译期用 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)」一样,让娇贵的程序员去重复写一百行相似的代码是不可能的。

但有时候,重 复 劳 动 不 可 避,咋办?

就,自 动 编 程,咯!


分享到:


相關文章: