资讯专栏INFORMATION COLUMN

babel各单元简介&如何写一个babel插件

peixn / 2513人阅读

摘要:是怎么工作的如何编译应用场景语法糖的代码统一相关概念介绍依赖,提供的方法,只转化语法,不转换类,的基础配置利用对进行劫持,在中进行原理见同时对后的进行缓存,提高下次效率读取缓存根据判断是否需要重新中传入配置入口函数提供

Babel babel是怎么工作的?

parse->AST->transform->gengerate

如何编译js->AST

babel应用场景

语法糖的polyfill

代码统一hack

相关概念介绍 babel-polyfill

依赖core-js,提供es*->es3的方法,只转化语法,不转换API(类Promise,WeakMap)

babel-helper babel-register(0.7.0-beta)

babel的基础配置init

利用pirate对require进行劫持,在hook中进行babel 原理见

同时对babel后的code进行缓存,提高下次babel效率

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方法

如何写一个babel插件

babel-plugin其实是对code转出的ast进行操作,

准备工具

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插件手册

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93618.html

相关文章

  • Babel 插件原理的理解与深入

    摘要:抽象语法树是怎么生成的谈到这点,就要说到计算机是怎么读懂我们的代码的。需要注意什么状态状态是抽象语法树转换的敌人,状态管理会不断牵扯我们的精力,而且几乎所有你对状态的假设,总是会有一些未考虑到的语法最终证明你的假设是错误的。 现在谈到 babel 肯定大家都不会感觉到陌生,虽然日常开发中很少会直接接触到它,但它已然成为了前端开发中不可或缺的工具,不仅可以让开发者可以立即使用 ES 规范...

    draveness 评论0 收藏0
  • babel插件入门-AST

    摘要:是一个对象,它表示两个节点之间的连接。接着返回一个对象,其属性是这个插件的主要节点访问者。所以上面的执行方式是运行引入了自定义插件的打包文件现在为明显减小,自定义插件成功插件文件目录觉得好玩就关注一下欢迎大家收藏写评论 目录 Babel简介 Babel运行原理 AST解析 AST转换 写一个Babel插件 Babel简介 Babel 是一个 JavaScript 编译器,它能将es...

    sanyang 评论0 收藏0
  • 2020年如何一个现代的JavaScript库

    摘要:我写过一些开源项目,在开源方面有一些经验,最近开到了阮老师的微博,深有感触,现在一个开源项目涉及的东西确实挺多的,特别是对于新手来说非常不友好最近我写了一个,旨在从多方面快速帮大家搭建一个标准的库,本文将已为例,介绍写一个开源库的知识 我写过一些开源项目,在开源方面有一些经验,最近开到了阮老师的微博,深有感触,现在一个开源项目涉及的东西确实挺多的,特别是对于新手来说非常不友好 show...

    joyqi 评论0 收藏0
  • 基于 Gulp + Browserify 构建 ES6 环境下的自动化前端项目

    摘要:本文特此给大家介绍下如何使用配合来构建基于的前端项目。最后,在目录下会生成最终的项目文件。执行单元测试本例中使用进行单元测试。 随着React、Angular2、Redux等前沿的前端框架越来越流行,使用webpack、gulp等工具构建前端自动化项目也随之变得越来越重要。鉴于目前业界普遍更流行使用webpack来构建es6(ECMAScript 2015)前端项目,网上的相关教程也比...

    yuanxin 评论0 收藏0
  • Next.js踩坑入门系列(二)— 添加Antd && CSS

    摘要:踩坑入门系列一二添加三目录重构再谈路由陆续更新个人对于脚手架的有一种执念,如果搭建出来就是一个首页标签跳转,实在不是我这个处女座的风格,因此第二步我就想引用框架,相信很多使用的开发者用的也都是这个框架吧。 Next.js踩坑入门系列 (一) Hello Next.js (二) 添加Antd && CSS (三) 目录重构&&再谈路由 陆续更新... 个人对于脚手架的UI有一种执...

    lifesimple 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<