Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ES6 to CommonJs以及深入了解Babel #15

Open
AnnVoV opened this issue Jul 28, 2018 · 0 comments
Open

ES6 to CommonJs以及深入了解Babel #15

AnnVoV opened this issue Jul 28, 2018 · 0 comments
Labels

Comments

@AnnVoV
Copy link
Owner

AnnVoV commented Jul 28, 2018

参考资料:
1.babel到底将代码转换成什么鸟样?
lcxfs1991/blog#9
2.babel 在线编译器
https://www.babeljs.cn/repl
3.你真的会用babel 吗?
sunyongjian/blog#30

背景

Node's module.exports 与 ES6 的export default 的区别是什么?如果想在Node中使用es6 的模块写法如何做到?

例子

'use strict'
class Animal {
  constructor(name) {
    this.name = name;
  }
  sayName() {
    return `My name is ${this.name}`;
  }
}

module.exports = Animal;

// 这样写就不行
export default Animal;

解释

1.Node.js 中并不支持es6的语法,所以需要借助babel这
种工具将es6 的module转换为CommonJs(babel转换后的代码是遵循commonJs规范的)
【拓展】: es6 经过babel 转换得到了遵循CommonJs的es5, 借助webpack(webpack本身也遵循CommonJs规范) 打包工具,生成能在浏览器中运行的es5代码
那babel 把es6 转换为commonJs 本质底层做了什么呢?

当 export 文件时

// input 
export const foo = 42;
export default 21;

// output
'use strict';
Object.defineProperty(exports, '__esModule', {
  value: true
});
var foo = exports.foo = 42;
exports.default = 21;

(1)我们看到在exports 对象上添加了__esModule 属性值为true来标记是否经过了babel的处理
(2)我们看到default 变成了exports的一个属性

当 import 文件时

import bar from './input'
console.log(bar)

babel 将会转换为下面的代码

var _input = require('./input')
var _input2 = _interoRequireDefault(_input);
function _interoRequireDefault(obj) {
  return obj && obj.__esModule?obj:{default: obj};
}
console.log(_input2.default);

最终我们看到import 的内容都是挂在default上的

拓展深入babel

参考资料:
1.探索babel和babel插件是怎么工作的
https://segmentfault.com/a/1190000013261724#articleHeader2
2.Babylon AST node types
https://github.com/babel/babylon/blob/master/ast/spec.md#variabledeclaration
3.深入理解Babel原理及其使用,babel把es6转换成es5的原理是什么?(重要)
http://www.fly63.com/article/detial/197
4.Understanding ASTs by Building Your Own Babel Plugin
https://www.sitepoint.com/understanding-asts-building-babel-plugin/
5.AST Explorer
https://astexplorer.net/
6.babylon 文档
https://github.com/babel/babylon/blob/master/ast/spec.md#variabledeclaration

babel的包构成

核心包

  • babel-core:babel转译器本身,提供了babel的转译API,如babel.transform等,用于对代码进行转译。像webpack的babel-loader就是调用这些API来完成转译过程的。
  • babylon:js的词法解析器用于生成ast
  • babel-traverse:用于遍历AST
  • babel-generator:根据AST生成代码

功能包

  • babel-types:用于检验、构建和改变AST树的节点
  • babel-template:辅助函数,用于从字符串形式的代码来构建AST树节点
  • babel-helpers:一系列预制的babel-template函数,用于提供给一些plugins使用
  • babel-code-frames:用于生成错误信息,打印出错误点源代码帧以及指出出错位置
  • babel-plugin-xxx:babel转译过程中使用到的插件,其中babel-plugin-transform-xxx是transform步骤使用的
  • babel-preset-xxx:transform阶段使用到的一系列的plugin(官方提供的预设插件集)
  • babel-polyfill:JS标准新增的原生对象和API的shim,实现上仅仅是core-js和regenerator-runtime两个包的封装
  • babel-runtime:功能类似babel-polyfill,一般用于library或plugin中,因为它不会污染全局作用域
工具包
  • babel-cli:babel的命令行工具,通过命令行对js代码进行转译
  • babel-register:通过绑定node.js的require来自动转译require引用的js代码文件

babel 配置

如果是以命令行方式使用babel,那么babel的设置就以命令行参数的形式带过去;
还可以在package.json里在babel字段添加设置;
但是建议还是使用一个单独的.babelrc文件,把babel的设置都放置在这里,所有babel API的options(除了回调函数之外)都能够支持,具体的options见babel的API options文档

babel 工作原理

ES6代码输入 ==》 babylon进行解析 ==》 得到AST
==》 plugin用babel-traverse对AST树进行遍历转译 ==》 得到新的AST树
==》 用babel-generator通过AST树生成ES5代码
(即分为 parsing --> transforming --> generating 三个阶段)

如何写一个babel 插件

重点参考了这篇文章:
https://segmentfault.com/a/1190000013261724#articleHeader2

我们写插件,主要就是利用babel在遍历AST树的过程中提供的钩子,来改造AST去执行我们想要的函数

babel 为我们提供了一个visitor对象,通过visitor对象,我们可以访问到我们代码的任何一个部分。visitor在遍历到对应节点执行对应函数时候会给我们传入path参数,path参数是表示两个节点之间连接的对象,而不是当前节点
看下面这个例子,我们要写一个插件,把代码中的log替换为console.log

const code = `
    const a = 3 * 103.5 * 0.8
    log(a)
    const b = a + 105 - 12
    log(b)
`
const visitor = {
    CallExpression(path) {
        // 这里判断一下如果不是log的函数执行语句则不处理
        if (path.node.callee.name !== 'log') return
        // t.CallExpression 和 t.MemberExpression分别代表生成对于type的节点,path.replaceWith表示要去替换节点,这里我们只改变CallExpression第一个参数的值,第二个参数则用它自己原来的内容,即本来有的参数
        path.replaceWith(t.CallExpression(
            t.MemberExpression(t.identifier('console'), t.identifier('log')),
            path.node.arguments
        ))
    }
}

const result = babel.transform(code, {
    plugins: [{
        visitor: visitor
    }]
})

console.log(result.code)

讲解几个函数:
1.CallExpression 是执行函数时的钩子
2.通过path.node.callee.name 来获取当前函数是否是需要去处理的函数
3.t.MemberExpression 是获取某个对象的属性比如t.identifier('console'), t.identifier('log') 就是获取console.log
(我自己感觉,babel-types的api 还是要再看看,才能写好一个babel插件)

好文推荐

剖析Babel -- Babel 总览
http://www.alloyteam.com/2017/04/analysis-of-babel-babel-overview/

@AnnVoV AnnVoV added the Babel label Jul 28, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant