AnnVoV / blog

24 stars 2 forks source link

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

Open AnnVoV opened 6 years ago

AnnVoV commented 6 years ago

参考资料: 1.babel到底将代码转换成什么鸟样? https://github.com/lcxfs1991/blog/issues/9 2.babel 在线编译器 https://www.babeljs.cn/repl 3.你真的会用babel 吗? https://github.com/sunyongjian/blog/issues/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 配置

如果是以命令行方式使用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/