一文读懂Babel原理及用途,及如何进行插件开发

前置阅读:Babel 插件手册javascript

目录

  • Babel介绍
  • Babel用途
  • Babel怎么转义代码的?
  • Babel开发API
  • Babel的内置功能
  • Babel plugin的例子

Babel介绍

Babel 的前身是 6to5,6to5 是 2014 年 发布的,主要功能是 就是 ES6 转成 ES5。后更名babel。css

Babel 指的是 通天塔,是巴比伦文明里面的 通天塔 当时地上的人们都说同一种语言,当人们离开东方以后,他们来到了示拿之地。在那里,人们千方百计烧砖好让他们可以造出一座城和一座高耸入云的塔来传播本身的名声,以避免他们分散到世界各地。上帝来到人间后看到了这座城和这座塔,说一群只说一种语言的人之后便没有他们作不成的事了;因而上帝将他们的语言打乱,这样他们就不能听懂对方说什么了,还把他们分散到了世界各地,这座城市也中止了修建。这座城市就被称为“巴别城”。 -- 《创世记》java

Babel用途

1. 转译 esnext、typescript 等到目标环境支持的 jsnode

高级语言到到低级语言叫编译,高级语言到高级语言叫转译react

2. 代码转换git

taro就是用babel作语法转译的,后面还有一个例子。github

3. 代码分析web

模块分析、tree-shaking 、 代码压缩、linter等typescript

Babel怎么转译代码的?

1.jpg

对源码字符串进行 parse,生成 AST,把对代码的修改转为对 AST 的增删改,转换完 AST 以后再打印成目标代码字符串。json

Babel插件开发的API

Babel 包

babel转译的不一样阶段使用了不一样的api

1.parse 阶段使用@babel/parser,做用是把源码转成 AST

require('@babel/parser').parse(source, {

sourceType: 'module',

plugins: ['jsx', 'flow', 'classProperties', 'decorator', 'decorators-legacy'],

});

复制代码

plugins: 指定jsx、typescript、flow 等插件来解析对应的语法

sourceType: module、script、unambiguous 3个取值,module 是解析 es module 语法

Babel的AST

Babel AST树查看

2.transform 阶段使用 @babel/traverse,能够遍历 AST,并调用 visitor 函数修改 AST,修改 AST 涉及到 AST 的判断、建立、修改等,这时候就须要 @babel/types 了,当须要批量建立 AST 的时候可使用 @babel/template 来简化 AST 建立逻辑。

require('@babel/traverse').default

function traverse(ast,vistor) //vistor能够是函数能够是对象,函数就是enter节点时调用的函数vistor名字为节点名称,对象就是enterexit调用的函数。tips多个节点能够'FunctionDeclaration|VariableDeclaration' 复制代码
require('@babel/traverse').default(ast, {

Program(path,state) {

}

})

复制代码
// path api

{

"parent": {...},

"node": {...},

"hub": {...},

"contexts": [],

"data": {},

"shouldSkip": false,

"shouldStop": false,

"removed": false,

"state": null,

"opts": null,

"skipKeys": null,

"parentPath": null,

"context": null,

"container": null,

"listKey": null,

"inList": false,

"parentKey": null,

"key": null,

"scope": null,

"type": null,

"typeAnnotation": null,

get(key)

set(key, node)

inList()

getSibling(key)

getNextSibling()

getPrevSibling()

getAllPrevSiblings()

getAllNextSiblings()

isXxx(opts)

assertXxx(opts)

find(callback)

findParent(callback)

insertBefore(nodes)

insertAfter(nodes)

replaceWith(replacement)

replaceWithMultiple(nodes)

replaceWithSourceString(replacement)

remove()

traverse(visitor, state)

skip()

stop()

}

复制代码

2.jpg

@babel/types 用于建立、判断 AST 节点,提供了 xxx、isXxx、assertXxx 的 api

@babel/template例子

3.generate 阶段会把 AST 打印为目标代码字符串,同时生成 sourcemap,须要 @babel/generate包,中途遇到错误想打印代码位置的时候,使用@babel/code-frame

const { code,map } = generator(ast, { sourceMaps: true });

//设置sourceMaps为true才有sourceMaps

复制代码

@babel/code-frame例子

Babel的内置功能

1.首先咱们看看babel须要作的功能

从高版本语法和 api 转换成低版本的语法并自动 polyfill 缺乏的 api。

babel转换

a. TC39 是制定 javascript 语言标准的组织,每一年都会公布加入到语言标准的特性,[es2015]262.ecma-international.org/6.0/)、es201… 等。

b. 提议

  • 阶段 0 : 只是一个想法,可能用 babel plugin 实现

  • 阶段 1: 值得继续的建议

  • 阶段 2: 创建 spec

  • 阶段 3: 完成 spec 而且在浏览器实现

  • 阶段 4: 会加入到下一年的 es20xx spec

c.react、flow、typescript

因为这些要转换的语法

babel7 内置的实现这些特性的插件分为 syntax、transform、proposal 3类,syntax声明要转译的语法,transform对AST的转换,proposal未加入语言标准的特性的 AST 转换,当插件太多不少共同的逻辑须要共享因此这部分在helper中实现。

Babel 包

正由于babel的插件太多,配置困难,用门面模式设计了presets让配置简单。

不一样版本的语言标准支持: preset-es201五、preset-es2016 等,babel7 后用 preset-env 代替

未加入标准的语言特性的支持: 用于 stage0、stage一、stage2 的特性,babel7 后单独引入 proposal plugin

用于 react、jsx、flow 的支持:分别封装相应的插件为 preset-react、preset-jsx、preset-flow,直接使用对应 preset 便可

d.每一个特性的实现用一个 babel 插件实现,当 babel 插件多了,天然会有一些共同的逻辑。这部分逻辑怎么共享呢?

babel 设计了插件之间共享逻辑的机制,就是 helper。

helper 分为两种:

  • 一种是注入到 AST 的运行时用的全局函数(如class)

  • 一种是操做 AST 的工具函数,好比变量提高这种通用逻辑(如常量定义)

babel runtime 里面放运行时加载的模块,会被打包工具打包到产物中。分为:regenerator、corejs、helper。

  • corejs 这就是新的 api 的 polyfill,分为 2 和 3 两个版本,3 才实现了实例方法的polyfill

  • regenerator 是 facebook 实现的 aync 的 runtime 库,babel 使用 regenerator-runtime来支持实现 async await 的支持。

  • helper 是 babel 作语法转换时用到的函数,好比 _typeof、_extends 等

2.babel的使用

"presets": [

[

"es2015",

{

"modules": false

}

],

"react",

"stage-2"

]

//babel6

复制代码
"presets": [

[

"@babel/preset-env",

{

"targets": "> 0.25%, not dead",

"useBuiltIns": "usage",

"corejs": 3

}

]

],

"plugins": [[

"@babel/plugin-transform-runtime",

{

"corejs": 3

}

]]

//babel7

复制代码

这里咱们发现一个问题为何有targets的目标浏览器呢?若是没有这个有可能你目标浏览器的语言原本就支持你却增长了babel增长了包的大小

babel/preset-env会致使使用到新特性的地方注入 helper 到 AST 中,@babel/plugin-transform-runtime的用处是把这些抽离出来模块引入。

babel 的 runtime 代码包括 2 部分:

一部分是 polyfill 用的,也就是 corejs 和 regenerator(实现 async await),这俩都是第三方的实现,babel 作了集成

另外一部分就是转换代码的时候的 helper,好比实现继承、实现 class 的那些代码,这部分默认是注入到代码里的

若是使用了 @babel/plugin-transform-runtime,会作两个改动:

  1. 把 helper 部分的代码从注入的方式改成从 @babel/runtime 包引入的方式

  2. polyfill 部分的代码也再也不是全局引入,会改成模块引入。

因此 transform runtime 的好处就有两个:

  1. 抽离重复注入的 helper 代码,减小产物体积

  2. polyfill 不污染全局

可是也有相对应的问题

babel 会按照以下顺序处理插件和 preset:

  1. 先应用 plugin,再应用 preset

  2. plugin 从前到后,preset 从后到前

这样就会形成,尚未到targets就执行了@babel/plugin-transform-runtime,不须要的babel包被增长了,这个babel8会解决这个问题。

最后说下上回文涛分享ts的时候关于用@babel/preset-typescript、ts-loader选择的一个问题

babel 是单文件编译的,每一个文件处理方式都同样。ts-loader 在内部是调用了 TypeScript 的官方编译器 – tsc, tsc 是多文件一块儿编译的,由于在编译过程当中会解析模块语法,去作类型推导和检查,这是二者流程上最大的区别。因此 tsc 不可避免的在多文件的时候会卡,而 babel 就和规模没有关系。 babel 不会解析 ts 类型,因此 namespace合并、const enum 这种须要解析类型内容的就不支持。总体来讲 babel 编译 ts 代码会快不少。并且 babel 生成的代码也能根据 targets 按需编译以及引入 polyfill,而 tsc 不支持。须要类型检查单独跑 tsc --noEmit 就行。

// tsconfig.json

{

...

"compilerOptions":{

"noEmit":true // 不输出文件,只作类型检查

}

}

复制代码

babel 编译 typescript 是更好的选择

Babel plugin的例子

先看下需求

!Wslq5ub7vhIy2RT11.png

1.小程序的页面预览页面要在b端再写一遍

2.ui后面改动也要俩边同时改动(有可能漏改忘改)

taro的c端代码要应用到web端

利用babel编译到b端解决代码公用问题,利用postcss解决样式问题

第一个版本

无mobx只是组件传值 修改2倍距离的问题

第二个版本

有mobx 优化css vh等

在了解下taro转换的思路

4.png

5.jpg

taro组件库的设计

taro的目标实现 React、Vue 等框架均可以使用的组件库。

Web Components

taro跨端组件库设计

参考连接

相关文章
相关标签/搜索