babel各單元簡介&如何寫一個babel插件

Babel

babel是怎麼工做的?

parse->AST->transform->gengeratejavascript

如何編譯js->ASThtml

babel應用場景

語法糖的polyfilljava

代碼統一hacknode

相關概念介紹

babel-polyfill

依賴core-js,提供es*->es3的方法,只轉化語法,不轉換API(類Promise,WeakMap)git

babel-helper

babel-register(0.7.0-beta)

babel的基礎配置initgithub

利用pirate對require進行劫持,在hook中進行babel 原理見express

同時對babel後的code進行緩存,提升下次babel效率npm

function compile(code, filename) {
    ...

  let cacheKey = `${JSON.stringify(opts)}:${babel.version}`;

  const env = babel.getEnv(false);

  if (env) cacheKey += `:${env}`;

    //讀取緩存 根據mtime判斷是否須要從新babel
  if (cache) {
    const cached = cache[cacheKey];
    if (cached && cached.mtime === mtime(filename)) {
      return cached.code;
    }
  }

  const result = babel.transform(code, {
    ...opts,
    sourceMaps: opts.sourceMaps === undefined ? "both" : opts.sourceMaps,
    ast: false,
  });

  if (cache) {
    cache[cacheKey] = result;
    result.mtime = mtime(filename);
  }

  if (result.map) {
    if (Object.keys(maps).length === 0) {
      installSourceMapSupport();
    }
    maps[filename] = result.map;
  }

  return result.code;
}

//hook中傳入ext配置
function hookExtensions(exts) {
  if (piratesRevert) piratesRevert();
  piratesRevert = addHook(compile, { exts, ignoreNodeModules: false });
}

//入口函數
export default function register(opts?: Object = {}) {
  // Clone to avoid mutating the arguments object with the 'delete's below.
  opts = Object.assign({}, opts);
  if (opts.extensions) hookExtensions(opts.extensions);

  if (opts.cache === false && cache) {
    registerCache.clear();
    cache = null;
  } else if (opts.cache !== false && !cache) {
    registerCache.load();
    cache = registerCache.get();
  }
  
  ...
}

babel-core

提供基礎的transform方法json

如何寫一個babel插件

babel-plugin實際上是對code轉出的ast進行操做,segmentfault

準備工具

ast轉換工具

ast轉換可視化工具

ast的解構能夠類比成一個樹狀或者json嵌套結構,他的每一層結構均可以叫作一個節點,以下圖

babel提供一個visitor的方法,容許咱們在裏面指定咱們想要訪問的節點,而且能夠在命中該節點時作出自定義的的操做

實例分析

如今咱們有一個須要移除整個業務bundle包裏全部console.log的需求

1.那咱們首先要知道console.log實際在ast是怎樣的一個節點結構

形如

console.log('a')

實際ast的展示以下

對於各個節點具體含義,這裏不作細講,能夠參考文末的babel手冊

2.這裏直接貼上代碼講吧

module.exports = function (babel) {

    const { types: t, template } = babel;

    const visitor = {
            //須要訪問的節點名
            //訪問器默認會被注入兩個參數 path(類比成dom),state
        ExpressionStatement(path, state) {
            const node = path.node;
            //延當前節點向內部訪問,判斷是否符合console解析出的ast的特徵
            const expressionNode = keyPathVisitor(node, ['expression']);
            const isCallExpression = expressionNode.type === 'CallExpression';
            if (isCallExpression) {
                const objectName = keyPathVisitor(expressionNode, ['callee', 'object', 'name']);
                const prototypeName = keyPathVisitor(expressionNode, ['callee', 'property', 'name']);
                if (objectName === 'console' && prototypeName === 'log' && !MAC) {
                        //若是符合上述條件,直接移除該節點
                    path.remove();
                }
            }
        }
    };

    return {
        visitor
    };
};

3.進階版:若是咱們想在babel-plugin中新增代碼呢

差很少有三種方法

A:手動添加節點(很噁心~相信你不會想去了解)
B:先生成ast,直接path.insertBefore
C:使用babel-template
例子: 移除autobind裝飾器,並在constructor中自動bind this

注意點

1.由於babel判斷是否babel是根據modify time,因此babel插件寫完想實時生效,須要給當前的env加上 BABEL_DISABLE_CACHE

//babel-register/cache.js

function load() {
  if (process.env.BABEL_DISABLE_CACHE) return;
    
  process.on("exit", save);
  process.nextTick(save);
    
  if (!_fs2.default.existsSync(FILENAME)) return;
    
  try {
    data = JSON.parse(_fs2.default.readFileSync(FILENAME));
  } catch (err) {
    return;
  }
}

2.babel插件寫完後發佈npm時,記得必定要加上babel-plugin-前綴,由於配置在babelrc中的插件名都會被babel在加載時統一加上babel-plugin前綴,而後在模塊系統中去查找

題外話

如何實現給require加上hook

傳送門

參考文獻

Babel插件手冊

相關文章
相關標籤/搜索