babel基础配置

babel标签(空格分隔): babelbabelBabel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。Babel 会在正在被转录的文件的当前目录中查找一个 .babelrc 文件。 如果不存在,它会遍历目录树,直到找到一个 .babelrc 文件,或一个 package.json 文件中有 “babel”: {}babel6将babel全家桶拆分成了许多不同的模块(rc是run command的缩写)依赖babel-loader:使用es6或加载模块时,对es6代码进行预处理,转为es5语法。babel-core:允许我们去调用babel的api,可以将js代码分析成ast(抽象语法树),方便各个插件分析语法进行相应的处理.babel-preset-env:推荐preset,比如es2015,es2016,es2017,latest,env(包含前面全部)babel-polyfill:它效仿一个完整的ES2015+环境,使得我们能够使用新的内置对象比如 Promise,比如 Array.prototype.includes 和生成器函数(提供给你使用 regenerator 插件)。为了达到这一点, polyfill 添加到了全局范围,就像原生类型比如 String 一样。babel-runtime babel-plugin-transform-runtime:这个插件能自动为项目引入polyfill和helperspresetsbabel5会默认转译ES6和jsx语法,babel6转译的语法都要在perset中配置,preset简单说就是一系列plugin包的使用。预设就是一系列插件的集合,把之前的参数保存为一个预设,下次就能直接使用。基础配置如下:(设置转码规则和插件){ “presets”: [], “plugins”: []}presets字段设定转码规则,官方提供以下的规则集,按需安装。可以看到提案在进入stage3阶段时就已经在一些环境被实现,在stage2阶段有babel的实现。# ES2015转码规则babel-preset-es2015# react转码规则babel-preset-react# ES7不同阶段语法提案的转码规则(共有4个阶段)babel-preset-stage-0babel-preset-stage-1: draft - 必须包含2个实验性的具体实现,其中一个可以是用转译器实现的,例如Babel。babel-preset-stage-2: candidate - 至少要有2个符合规范的具体实现。babel-preset-stage-3配置:{ “presets”: [ “es2015”, “stage-2” ], “plugins”: [] }babel-preset-env此段内容来自于babel到底该如何配置?上面这些preset官方现在都已经不推荐了,官方唯一推荐preset:babel-preset-env这款preset能灵活决定加载哪些插件和polyfill// cnpm install -D babel-preset -env{ “presets”: [ [“env”, { “targets”: { //指定要转译到哪个环境 //浏览器环境 “browsers”: [“last 2 versions”, “safari >= 7”], //node环境 “node”: “6.10”, //“current” 使用当前版本的node }, //是否将ES6的模块化语法转译成其他类型 //参数:“amd” | “umd” | “systemjs” | “commonjs” | false,默认为’commonjs’ “modules”: ‘commonjs’, //是否进行debug操作,会在控制台打印出所有插件中的log,已经插件的版本 “debug”: false, //强制开启某些模块,默认为[] “include”: [“transform-es2015-arrow-functions”], //禁用某些模块,默认为[] “exclude”: [“transform-es2015-for-of”], //是否自动引入polyfill,开启此选项必须保证已经安装了babel-polyfill //参数:Boolean,默认为false. “useBuiltIns”: false }] ]}{ “presets”: [ [“env”, { “modules”: false, “targets”: { “browsers”: ["> 1%", “last 2 versions”, “not ie <= 8”] } }], “stage-2” ], “plugins”: [ “transform-vue-jsx”, “transform-runtime”, “syntax-dynamic-import”, “transform-es2015-modules-commonjs” ]}plugins此段内容来自于babel到底该如何配置?babel中的插件,通过配置不同的插件才能告诉babel,我们的代码中有哪些是需要转译的。插件官网{ “plugins”: [ [“transform-es2015-arrow-functions”, { “spec”: true }] ]}transform-runtime,这个插件能自动为项目引入polyfill和helperspolyfill作用是用已经存在的语法和api实现一些浏览器还没有实现的api,对浏览器的一些缺陷做一些修补。例如Array新增了includes方法,但是低版本的浏览器上没有,就得做兼容处理transform-runtime这个插件依赖于babel-runtimebabel-runtime由三个部分组成:core-jscore-js极其强悍,通过ES3实现了大部分的ES5、6、7的polyfill。regeneratorregenerator来自facebook的一个库,用于实现 generator functions。helpersbabel的一些工具函数,这个helpers和使用babel-external-helpers生成的helpers是同一个东西配置transform-runtime{ “plugins”: [ [“transform-runtime”, { “helpers”: false, //自动引入helpers “polyfill”: false, //自动引入polyfill(core-js提供的polyfill) “regenerator”: true, //自动引入regenerator }] ]}比较transform-runtime与babel-polyfill引入垫片的差异:使用runtime是按需引入,需要用到哪些polyfill,runtime就自动帮你引入哪些,不需要再手动一个个的去配置plugins,只是引入的polyfill不是全局性的,有些局限性。而且runtime引入的polyfill不会改写一些实例方法,比如Object和Array原型链上的方法,像前面提到的Array.protype.includes。babel-polyfill就能解决runtime的那些问题,它的垫片是全局的,而且全能,基本上ES6中要用到的polyfill在babel-polyfill中都有,它提供了一个完整的ES6+的环境。babel官方建议只要不在意babel-polyfill的体积,最好进行全局引入,因为这是最稳妥的方式。一般的建议是开发一些框架或者库的时候使用不会污染全局作用域的babel-runtime,而开发web应用的时候可以全局引入babel-polyfill避免一些不必要的错误,而且大型web应用中全局引入babel-polyfill可能还会减少你打包后的文件体积(相比起各个模块引入重复的polyfill来说)。结合ESLint许多工具需要Babel进行前置转码,如ESLint和Mocha在项目根目录下,新建一个配置文件.eslint,在其中加入parser字段。{ “parser”: “babel-eslint”, “rules”: { … }}在package.json之中,加入相应的scripts脚本"scripts": { “lint”: “eslint –ext .js,.vue src”,}, ...

January 25, 2019 · 1 min · jiezi

ES6 系列之我们来聊聊装饰器

Decorator装饰器主要用于:装饰类装饰方法或属性装饰类@annotationclass MyClass { }function annotation(target) { target.annotated = true;}装饰方法或属性class MyClass { @readonly method() { }}function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}Babel安装编译我们可以在 Babel 官网的 Try it out,查看 Babel 编译后的代码。不过我们也可以选择本地编译:npm initnpm install –save-dev @babel/core @babel/clinpm install –save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties新建 .babelrc 文件{ “plugins”: [ ["@babel/plugin-proposal-decorators", { “legacy”: true }], ["@babel/plugin-proposal-class-properties", {“loose”: true}] ]}再编译指定的文件babel decorator.js –out-file decorator-compiled.js装饰类的编译编译前:@annotationclass MyClass { }function annotation(target) { target.annotated = true;}编译后:var _class;let MyClass = annotation(_class = class MyClass {}) || _class;function annotation(target) { target.annotated = true;}我们可以看到对于类的装饰,其原理就是:@decoratorclass A {}// 等同于class A {}A = decorator(A) || A;装饰方法的编译编译前:class MyClass { @unenumerable @readonly method() { }}function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}function unenumerable(target, name, descriptor) { descriptor.enumerable = false; return descriptor;}编译后:var _class;function _applyDecoratedDescriptor(target, property, decorators, descriptor, context ) { /** * 第一部分 * 拷贝属性 / var desc = {}; Object“ke” + “ys”.forEach(function(key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if (“value” in desc || desc.initializer) { desc.writable = true; } /* * 第二部分 * 应用多个 decorators / desc = decorators .slice() .reverse() .reduce(function(desc, decorator) { return decorator(target, property, desc) || desc; }, desc); /* * 第三部分 * 设置要 decorators 的属性 / if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object[“define” + “Property”](target, property, desc); desc = null; } return desc;}let MyClass = ((_class = class MyClass { method() {}}),_applyDecoratedDescriptor( _class.prototype, “method”, [readonly], Object.getOwnPropertyDescriptor(_class.prototype, “method”), _class.prototype),_class);function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}装饰方法的编译源码解析我们可以看到 Babel 构建了一个 _applyDecoratedDescriptor 函数,用于给方法装饰。Object.getOwnPropertyDescriptor()在传入参数的时候,我们使用了一个 Object.getOwnPropertyDescriptor() 方法,我们来看下这个方法:Object.getOwnPropertyDescriptor() 方法返回指定对象上的一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)顺便注意这是一个 ES5 的方法。举个例子:const foo = { value: 1 };const bar = Object.getOwnPropertyDescriptor(foo, “value”);// bar {// value: 1,// writable: true// enumerable: true,// configurable: true,// }const foo = { get value() { return 1; } };const bar = Object.getOwnPropertyDescriptor(foo, “value”);// bar {// get: /the getter function/,// set: undefined// enumerable: true,// configurable: true,// }第一部分源码解析在 _applyDecoratedDescriptor 函数内部,我们首先将 Object.getOwnPropertyDescriptor() 返回的属性描述符对象做了一份拷贝:// 拷贝一份 descriptorvar desc = {};Object“ke” + “ys”.forEach(function(key) { desc[key] = descriptor[key];});desc.enumerable = !!desc.enumerable;desc.configurable = !!desc.configurable;// 如果没有 value 属性或者没有 initializer 属性,表明是 getter 和 setterif (“value” in desc || desc.initializer) { desc.writable = true;}那么 initializer 属性是什么呢?Object.getOwnPropertyDescriptor() 返回的对象并不具有这个属性呀,确实,这是 Babel 的 Class 为了与 decorator 配合而产生的一个属性,比如说对于下面这种代码:class MyClass { @readonly born = Date.now();}function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}var foo = new MyClass();console.log(foo.born);Babel 就会编译为:// …(_descriptor = _applyDecoratedDescriptor(_class.prototype, “born”, [readonly], { configurable: true, enumerable: true, writable: true, initializer: function() { return Date.now(); }}))// …此时传入 _applyDecoratedDescriptor 函数的 descriptor 就具有 initializer 属性。第二部分源码解析接下是应用多个 decorators:/* * 第二部分 * @type {[type]} /desc = decorators .slice() .reverse() .reduce(function(desc, decorator) { return decorator(target, property, desc) || desc; }, desc);对于一个方法应用了多个 decorator,比如:class MyClass { @unenumerable @readonly method() { }}Babel 会编译为:_applyDecoratedDescriptor( _class.prototype, “method”, [unenumerable, readonly], Object.getOwnPropertyDescriptor(_class.prototype, “method”), _class.prototype)在第二部分的源码中,执行了 reverse() 和 reduce() 操作,由此我们也可以发现,如果同一个方法有多个装饰器,会由内向外执行。第三部分源码解析/* * 第三部分 * 设置要 decorators 的属性 /if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined;}if (desc.initializer === void 0) { Object[“define” + “Property”](target, property, desc); desc = null;}return desc;如果 desc 有 initializer 属性,意味着当装饰的是类的属性时,会将 value 的值设置为:desc.initializer.call(context)而 context 的值为 _class.prototype,之所以要 call(context),这也很好理解,因为有可能class MyClass { @readonly value = this.getNum() + 1; getNum() { return 1; }}最后无论是装饰方法还是属性,都会执行:Object[“define” + “Property”](target, property, desc);由此可见,装饰方法本质上还是使用 Object.defineProperty() 来实现的。应用1.log为一个方法添加 log 函数,检查输入的参数:class Math { @log add(a, b) { return a + b; }}function log(target, name, descriptor) { var oldValue = descriptor.value; descriptor.value = function(…args) { console.log(Calling ${name} with, args); return oldValue.apply(this, args); }; return descriptor;}const math = new Math();// Calling add with [2, 4]math.add(2, 4);再完善点:let log = (type) => { return (target, name, descriptor) => { const method = descriptor.value; descriptor.value = (…args) => { console.info((${type}) 正在执行: ${name}(${args}) = ?); let ret; try { ret = method.apply(target, args); console.info((${type}) 成功 : ${name}(${args}) =&gt; ${ret}); } catch (error) { console.error((${type}) 失败: ${name}(${args}) =&gt; ${error}); } return ret; } }};2.autobindclass Person { @autobind getPerson() { return this; }}let person = new Person();let { getPerson } = person;getPerson() === person;// true我们很容易想到的一个场景是 React 绑定事件的时候:class Toggle extends React.Component { @autobind handleClick() { console.log(this) } render() { return ( <button onClick={this.handleClick}> button </button> ); }}我们来写这样一个 autobind 函数:const { defineProperty, getPrototypeOf} = Object;function bind(fn, context) { if (fn.bind) { return fn.bind(context); } else { return function autobind() { return fn.apply(context, arguments); }; }}function createDefaultSetter(key) { return function set(newValue) { Object.defineProperty(this, key, { configurable: true, writable: true, enumerable: true, value: newValue }); return newValue; };}function autobind(target, key, { value: fn, configurable, enumerable }) { if (typeof fn !== ‘function’) { throw new SyntaxError(@autobind can only be used on functions, not: ${fn}); } const { constructor } = target; return { configurable, enumerable, get() { /* * 使用这种方式相当于替换了这个函数,所以当比如 * Class.prototype.hasOwnProperty(key) 的时候,为了正确返回 * 所以这里做了 this 的判断 */ if (this === target) { return fn; } const boundFn = bind(fn, this); defineProperty(this, key, { configurable: true, writable: true, enumerable: false, value: boundFn }); return boundFn; }, set: createDefaultSetter(key) };}3.debounce有的时候,我们需要对执行的方法进行防抖处理:class Toggle extends React.Component { @debounce(500, true) handleClick() { console.log(’toggle’) } render() { return ( <button onClick={this.handleClick}> button </button> ); }}我们来实现一下:function _debounce(func, wait, immediate) { var timeout; return function () { var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(function(){ timeout = null; }, wait) if (callNow) func.apply(context, args) } else { timeout = setTimeout(function(){ func.apply(context, args) }, wait); } }}function debounce(wait, immediate) { return function handleDescriptor(target, key, descriptor) { const callback = descriptor.value; if (typeof callback !== ‘function’) { throw new SyntaxError(‘Only functions can be debounced’); } var fn = _debounce(callback, wait, immediate) return { …descriptor, value() { fn() } }; }}4.time用于统计方法执行的时间:function time(prefix) { let count = 0; return function handleDescriptor(target, key, descriptor) { const fn = descriptor.value; if (prefix == null) { prefix = ${target.constructor.name}.${key}; } if (typeof fn !== ‘function’) { throw new SyntaxError(@time can only be used on functions, not: ${fn}); } return { …descriptor, value() { const label = ${prefix}-${count}; count++; console.time(label); try { return fn.apply(this, arguments); } finally { console.timeEnd(label); } } } }}5.mixin用于将对象的方法混入 Class 中:const SingerMixin = { sing(sound) { alert(sound); }};const FlyMixin = { // All types of property descriptors are supported get speed() {}, fly() {}, land() {}};@mixin(SingerMixin, FlyMixin)class Bird { singMatingCall() { this.sing(’tweet tweet’); }}var bird = new Bird();bird.singMatingCall();// alerts “tweet tweet"mixin 的一个简单实现如下:function mixin(…mixins) { return target => { if (!mixins.length) { throw new SyntaxError(@mixin() class ${target.name} requires at least one mixin as an argument); } for (let i = 0, l = mixins.length; i < l; i++) { const descs = Object.getOwnPropertyDescriptors(mixins[i]); const keys = Object.getOwnPropertyNames(descs); for (let j = 0, k = keys.length; j < k; j++) { const key = keys[j]; if (!target.prototype.hasOwnProperty(key)) { Object.defineProperty(target.prototype, key, descs[key]); } } } };}6.redux实际开发中,React 与 Redux 库结合使用时,常常需要写成下面这样。class MyReactComponent extends React.Component {}export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);有了装饰器,就可以改写上面的代码。@connect(mapStateToProps, mapDispatchToProps)export default class MyReactComponent extends React.Component {};相对来说,后一种写法看上去更容易理解。7.注意以上我们都是用于修饰类方法,我们获取值的方式为:const method = descriptor.value;但是如果我们修饰的是类的实例属性,因为 Babel 的缘故,通过 value 属性并不能获取值,我们可以写成:const value = descriptor.initializer && descriptor.initializer();参考ECMAScript 6 入门core-decoratorsES7 Decorator 装饰者模式JS 装饰器(Decorator)场景实战ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。 ...

November 15, 2018 · 6 min · jiezi