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

[Babel]Babel学习手记 #17

Open
cookiepool opened this issue Oct 15, 2019 · 0 comments
Open

[Babel]Babel学习手记 #17

cookiepool opened this issue Oct 15, 2019 · 0 comments

Comments

@cookiepool
Copy link
Owner

cookiepool commented Oct 15, 2019

一、Babel相关的概念

  • @babel/core: babel的核心,核心的api都在包含在这里。
  • @babel/cli: 命令行工具,通过命令对js文件进行转换的工具。
  • @babel/perset-env: 指定转换的工作环境。
  • @babel/polyfill: 相当于一个填充,因为babel本身只支持转换箭头函数、结构赋值这些语法糖类的语法,而一些新的API或者Promise函数等是无法转换的。@babel/polyfill就是解决这个问题的。
  • babel-loader: webpack的加载器,用于调用@babel/core的核心API来完成编译。

1、@babel/core与babel-core区别

@babel/core是babel 7过后的版本标识,babel-core是以前版本的标识。


2、.babelrc和babel.config.js

.babelrc和babel.config.js均是babel的配置文件,babel.config.js是bebel 7引入的新的方式。


3、@babel/polyfill和@babel/plugin-transform-runtime和@babel/runtime和@babel/runtime-corejs2(都是用来转换新Api的)

  • (1)@babel/polyfill:babel-polyfill则是通过改写全局prototype的方式实现,比较适合单独运行的项目。开启babel-polyfill的方式,可以直接在代码中require,或者在webpack的entry中添加,也可以在babel的env中设置useBuildins为true来开启,但是babel-polyfill会有近100K,打包后代码冗余量比较大,对于现代的浏览器,有些不需要polyfill,造成流量浪费污染了全局对象。
//使用方法一在entry中添加
module.exports = {
    entry: {
        main: ["@babel/polyfill", path.resolve(__dirname, "./src/index.js")]
    }
}
//使用方式二babel.config.js中设置
module.exports = {
    presets: [
        "@babel/preset-env", { "useBuiltIns": true }
    ]
}

  • (2)@babel/runtime和@babel/plugin-transform-runtime:babel-runtime和 babel-plugin-transform-runtime的区别是,相当一前者是手动挡而后者是自动挡,每当要转译一个api时都要手动加上require('babel-runtime'),而babel-plugin-transform-runtime会由工具自动添加,主要的功能是为api提供沙箱的垫片方案,不会污染全局的api,因此适合用在第三方的开发产品中。@babel/runtime和@babel/plugin-transform-runtime都要结合到使用

  • (3)@babel/runtime-corejs2:plugin-transform-runtime 可以设置成 false 或者 2,在babel.config.js的配置文件下有以下代码:
module.exports = {
    preset: ["@babel/preset-env"],
    plugins: [
        "@babel/plugin-transform-runtime", { corejs: 2 }
    ]
}

1、corejs 是一个给低版本的浏览器提供接口的库,如 Promise, map, set 等。在 babel 中你设置成 false 或者不设置,就是引入的是 corejs 中的库,而且在全局中引入,也就是说侵入了全局的变量。
2、如果你的全局有一个引入,不要让引入的库影响全局,那你就需要引把 corejs 设置成 2。
所以一旦你使用了2这个参数就必须引入@babel/runtime-corejs2
3、@babel/plugin-transform-runtime是必须装的,如果corejs设置为2的话安装@babel/runtime-corejs2来代替@babel/runtime,反正设置为false的话就需要@babel/runtime,根据你的设置来安装即可。
4、所以@babel/runtime和@babel/runtime-corejs2区别就是后面那个多了个corejs2的包。与@babel/runtime区别,官网是这样描述的Difference from @babel/runtime:This can be used instead of a polyfill for any non-instance methods. It will replace things like Promise or Symbol with the library functions in core-js


在babel 7下:

  • babel.config.js 是对整个项目(父子package) 都生效的配置,但要注意babel的执行工作目录。
  • .babelrc 是对 待编译文件 生效的配置,子package若想加载.babelrc是需要babel配置babelrcRoots才可以(父package自身的babelrc是默认可用的)。
  • 任何package中的babelrc寻找策略是: 只会向上寻找到本包的 package.json 那一级。
  • node_modules下面的模块一般都是编译好的,请剔除掉对他们的编译。如有需要,可以把个例加到 babelrcRoots 中。

默认情况下.babelrc不作用于子包,那么在babel.config.js下加入一下babelrcRoots来指定即可。

    module.exports = {
      babelrcRoots: ['.', './frontend', './backend'] // 允许这两个子 package 加载 babelrc 相对配置
    }

一文读懂 babel7 的配置文件加载逻辑
对babel-transform-runtime,babel-polyfill的一些理解
babel7中 corejs 和 corejs2 的区别
babel preset env配置
babel学习笔记

二、Babel配置入门指南

首先需要本机安装node.js,使用npm包管理工具来初始化目录,本次操作学习都是在Babel 7上进行的,相较于Babel 6还是有一定区别,单独使用Babel 7需要CLI来完成,所以先安装脚手架和核心@babel/core。

npm install @babel/cli @babel/core

安装完成后,我们就可以使用cli提供的命令来转换我们的JS代码了,比如建立一个test.js的JS文件,里面包含ES6的内容:

let a = [1, 2, 3];

let b = () => {
	console.log('这是箭头函数!');
}

let c = [...a];

然后来一波命令:

npx babel test.js --watch --out-file test-transform.js
  • 注意:npx是npm在5.2.0后附赠的东西,使用这个我们可以避免全局安装这些命令工具,具体大家可以百度。
  • 我的test.js是直接建在跟package.json同级目录下的。
    之后我们可以发现输出的test-transform.js没改变,没有转化,这是因为babel是基于插件来实现的,没配置插件肯定什么也不会干,所以需要配置,这里就不介绍单独手动引入插件的方式了。
// 转换前
let a = [1, 2, 3];

let b = () => {
	console.log('这是箭头函数!');
}

let c = [...a];

// 转换后
let a = [1, 2, 3];

let b = () => {
	console.log('这是箭头函数!');
}

let c = [...a];

不想每次需要什么插件都去手动引入插件,所以我们就需要祭出@babel/preset-env,先安装这个工具。

npm intasll @babel/preset-env

然后在根目录建一个babel.config.js来配置babel,在文件中加入以下内容:

module.exports = {
	presets: [
		["@babel/preset-env"]
	]
}

之后我们再运行一次上面的转换命令,可以得到以下代码:

"use strict";

var a = [1, 2, 3];

var b = function b() {
  console.log('这是箭头函数!');
};

var c = [].concat(a);

这样ES6新的语法糖就转换成ES5了。
当然,babel.config.js里面还可以指定目标,当满足什么样的条件才去转换语法,不指定targets的情况下,默认是把所有的ES6+都转换成ES5,比如下面的示例:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8"
    }]
  ]
}

这种只有当在大于ie 8以上的浏览器不支持的语法才会转换。

@babel/polyfill

babel只能转换一般的语法糖,不能转换新的API,所以就只能祭出polyfill来弥补。首先安装下polyfill,然后引入就可以了。

npm install @babel/polyfill

然后在JS文件里面引入

import "@babel/polyfill";

对于使用webpack的同学,可以直接在main入口直接引入,打包过后直接可以使用,如下:

entry: {
  main: ["@babel/polyfill", path.resolve(__dirname, "../main.js")]
}
  • 注意:这个地方我只是举个栗子,实际上你这样单独操作出来的JS再直接引入某个HTML文件里面其实是没有用的,要结合到打包工具一起使用才行,比如Webpack。

@babel/preset-env 加强

上面的引入方法是完全引入,导致包非常大,我们可以按需引入,这里又要配置@babel/preset-env,修改babel.config.js代码如下:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
      "useBulitIns": "entry"
    }]
  ]
}

然后运行一下会看到这些代码:

"use strict";

require("core-js/modules/es6.array.copy-within");

require("core-js/modules/es6.array.every");

require("core-js/modules/es6.array.fill");
..........
require("core-js/modules/web.timers");

require("core-js/modules/web.immediate");

require("core-js/modules/web.dom.iterable");

require("regenerator-runtime/runtime");

var a = [1, 2, 3];

var b = function b() {
  console.log('这是箭头函数!');
};

var c = [].concat(a);
b();
var d = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("ok");
  }, 2000);
});
d.then(function (res) {
  console.log(res);
});

它会一股脑的把所有的包全部引进来,这样嘿不科学,所以usage参数可以做到按需引入,它只会引入相关的包,没使用的ES6+API不会引入相关的包,修改babel.config.js代码:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
      "useBuiltIns": "usage"
    }]
  ]
}

转换过后的代码如下:

"use strict";

require("core-js/modules/es6.promise");

require("core-js/modules/es6.object.to-string");

var a = [1, 2, 3];

var b = function b() {
  console.log('这是箭头函数!');
};

var c = [].concat(a);
b();
var d = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("ok");
  }, 2000);
});
d.then(function (res) {
  console.log(res);
});

这样,我们可以看到,我的代码使用了promise,它就只引入promise相关的代码。

  • 注意:这儿使用usage参数后,JS文件里面就不要单独写import "@babel/polyfill"这句话了,多此一举。

到这儿,问题来了,在使用usage参数后使用babel的cli命令工具(也就是这个:npx babel test.js --watch --out-file test-done.js)会给一段提示,告诉我们要指定core-js@2或者core-js@3,从babel 7.4过后官方就建议这么做了,就是让我们放弃@babel/polyfill,我先卸载掉@babel/polyfill试一试,发现还是能转换,不科学,然后查资料和看preset-env文件夹下才发现,人家自带了polyfill,所以这儿如果有了@babel/preset-env不需要单独安装@babel/polyfill了,前提应该是使用了@babel/preset-env配置了babel才行(像前面的完全引入polyfill还是需要单独安装@babel/polyfill),(注意:这儿我试了使用webpack结合babel-loader在不单独安装@babel/polyfill或者core-js这些打包会出问题,"useBuiltIns": "usage"时提示找不到包,使用entry参数打包不报错,直接不会导入包,所以环境不一样不能一概而论)。 前面有个要我们指定corejs的提示,也只需要在babel.config.js里面指定一下就可以了,这儿我的@babel/perset-env版本是7.6.3,不知道其它版本有没有集成polyfill,babel.config.js代码如下:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
      "useBuiltIns": "usage",
      "corejs": 2
    }]
  ]
}

这儿我测试了下指定corejs为2和3时引入的代码不一样,暂时没了解到原因-_-。

@babel/runtime、 @babel/plugin-transform-runtime

接下来走一波不污染全局的配置方式,前面的配置都是要污染全局,还是不怎么科学,关于这种方式的优点和缺点大家上网查一下就可以了,接下来先安装好包:

npm install @babel/plugin-transform-runtime -D

上面这个安装到dev依赖就可以了,不用于生产环境

npm install @babel/runtime

接下来我们配置一下babel.config.js,如下:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
    }]
  ],
  plugins: [
    ["@babel/plugin-transform-runtime"]
  ]
}

然后输出一下

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var a = [1, 2, 3];

var b = function b() {
  console.log('这是箭头函数!');
};

var c = [].concat(a);
b();
var d = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("ok");
  }, 2000);
});
d.then(function (res) {
  console.log(res);
});

var MM = function MM() {  // 这个是我转换前用class定义的类 class MM {constructor(){}} 这种
  (0, _classCallCheck2["default"])(this, MM);
};

发现输出的代码里面没得polyfill了。如果需要polyfill的话就要单独安装以下内容:

npm install @babel/runtime-corejs2

这里安装这个包之前把我@babel/runtime给卸载了,安装完成后再重新配置一下babel.config.js,如下:

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": "ie >= 8",
    }]
  ],
  plugins: [
    ["@babel/plugin-transform-runtime", {
      "corejs": 2
    }]
  ]
}

然后运行一下,看结果。相比前面的@babel/runtime,这里的polyfill回来了,多了一句这个

var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));

参考资料

babel-runtime VS babel-polyfill
结合Babel 7.4.0 谈一下Babel-runtime 和 Babel-polyfill

三、结合到webpack来使用

结合webpack的话,还需要以下几个东西:

npm install webpack -D
npm install webpack-cli -D
npm install babel-loader -D

babel-loader是加载器,要结合webpack来使用的话,这个必须要,同时,在项目根目录建立一个webpack.config.js配置文件(这里webpack的知识就不介绍了,详情参考官网)。

还是借助@babel/preset-env

首先我还是先测试了参数为entry的情况,配置文件内容如下(注意:这儿我还安装了@babel/polyfill):

// index.js,是我要转换的文件
import '@babel/polyfill';

let a = `hello, can you hear me!`;

let func = () => {
    console.log('这是箭头函数');
}

let b = [1, 2, 3];
let c = [...b];

let array = [1];
 
 
console.log(array);

let promise_t = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(233);
    }, 2000)
});

promise_t.then((res) => {
    console.log(res);
})

class Test {
    constructor(name) {
        this.name = name;
    }

    print() {
        console.log(this.name);
    }
}

let t_t = new Test('xiaohong');
t_t.print();

然后是webpack.config.js文件

const path = require('path');

module.exports = {
    // 指定模式(这个指定可以用来区分开发模式和生产模式,可选值有:'devvlopment','production','none')
    mode: 'none',
    // 入口
    entry: {
        main: path.resolve(__dirname, './src/index.js')
    },

    // 出口
    output: {
        // 出口文件
        path: path.resolve(__dirname, './dist'),
        // 文件名
        filename: './[name].js',
    },

    // 加载器
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'babel-loader'
                    }
                ]
            }
        ]
    }
}

之后再是babel.config.js文件

module.exports = {
    presets: [
        ['@babel/preset-env', {
            'useBuiltIns': 'entry',
        }]
    ]
}

这儿我发现,即使我的babel.config.js不配置 'useBuiltIns': 'entry',这句,打包出来的main.js虽然内容有差别,但是还是能运行成功,IE11亲测可用上面js的promise语法。暂时我还没把useBuiltIns这个属性搞的特别清楚,只是知道在使用usage属性值时可以按需加载polyfill。
接下来使用'useBuiltIns': 'usage'来试一试,我发现在保持其他文件不变的情况下,(这儿有个问题是,使用usage时在index.js中还是要写这句import '@babel/polyfill',不然找不到包,这与我上面单独使用babel来转换时说不在单独引入polyfill有点出入,还没怎么明白,后期我会补充)。然后我比较了两种方式生成的main.js的代码,使用entry属性值时,main.js代码八千多行,大小大概250kb,使用usage参数时,代码一千多行,大小大概37kb,所以按需引入效果还是明显。而且亲测在IE11上两种方式生成的代码均可正常运行。

使用core-js代替@babel/polyfill

官方网站上面说从babel 7.4.0后开始放弃babel/polyfill,转而使用core-js。这儿我们又来试一试core-js
首先卸载@babel/polyfill,然后执行以下命令安装core-js@2:

npm install core-js@2 -S

这儿还有个core-js@3的版本,暂时没有用过,不知道区别。安装完成后修改一下babel.config.js

module.exports = {
    presets: [
        ['@babel/preset-env', {
            'useBuiltIns': 'usage',
            'corejs': 2
        }]
    ]
}

然后你的index.js的上面去掉import '@babel/polyfill',这时执行过后的main.js能完美运行在IE11上,而且这儿也不需要手动引入core-js@2,使用usage属性值时自动按需引入,比较稳。如果我不写'corejs': 2这个参数,转换也可以成功,但是会报warning,叫我去安装core-js并制定版本,问题不大,还是写上,毕竟官方要求。
然后如果我直接把useBuiltIns的参数值改为entry,发现class这些es6语法会转换,但是Promise的polyfill没有引入,看来没usage参数好使,需要手动引入才行,我在index.js手动引入import 'core-js',
然后执行webpack但是却报错,提示Module not found: Error: Can't resolve 'regenerator-runtime/runtime',这个就尴尬了,还要安装regenerator-runtime/runtime,但是使用usage却没有出现这个问题,暂时无解,这儿我单独安装一次:

npm install regenerator-runtime -D

这儿我单独安装一次regenerator-runtime就不会报错了,暂时没找到原因,试着在安装了regenerator-runtime的情况下使用usage参数值,同时去除index.js里面的import 'core-js'这句话,不然会报警告。内容像这样:When setting useBuiltIns: 'usage', polyfills are automatically imported when needed.
Please remove the import '@babel/polyfill' call or use useBuiltIns: 'entry' instead.
。我试了下,使用webpack打包没得问题,可以运行。这儿建议把regenerator-runtime安装一次,避免不必要的麻烦。

  • 这里我总结下吧(这儿总结的地方我都没有显示的指定corejs参数,也就是上面代码的‘corejs’: 2这句话)
    1、不安装core-js和regenerator-runtime,不论你的@babel/preset-env的useBuiltIns设置为哪个参数值都不能打包成功,usage参数值提示找不到core-js的包,entry直接不会引入相关的东西,因为包都没有,会提示你去下载corejs。false效果跟entry一样。
    2、只安装了core-js(这儿安装的core-js@2版本),usage参数可以打包成功,但是提示指定core-js版本,这个正常,因为我没写那句'corejs: 2'了,测试在IE11上正常运行。entry参数需要手动在index.js中引入core-js,打包提示 Can't resolve 'regenerator-runtime/runtime',打包失败,false参数打包成功,但是文件巨大,代码九千多行-_-,IE11运行成功。
    3、安装core-js和regenerator-runtime,usage参数可以打包成功,但是提示指定core-js版本,这个正常,因为我没写那句'corejs: 2'了,测试在IE11上正常运行。entry参数可以打包成功,但是提示指定core-js版本,这个正常,因为我没写那句'corejs: 2'了,测试在IE11上正常运行,打包过后文件巨大,我安装了regenerator-runtime,没有在index.js里面手动引入,只引入了core-js,这次打包没报上面找不到包的错误。false参数可以打包成功,但是提示指定core-js版本,这个正常,因为我没写那句'corejs: 2'了,测试在IE11上正常运行,打包过后文件巨大。

所以我得出一个配置结果
在babel.config.js里面这样写:

module.exports = {
   presets: [
       ['@babel/preset-env', {
           'useBuiltIns': 'usage',
           'corejs': 2
       }]
   ]
}

package.json里面包含这些包:

{
  "name": "webpack-babel",
  "version": "1.0.0",
  "description": "practice",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.config.js"
  },
  "author": "lee",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.7.0",
    "@babel/preset-env": "^7.7.1",
    "babel-loader": "^8.0.6",
    "regenerator-runtime": "^0.13.3",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.9"
  },
  "dependencies": {
    "core-js": "^2.6.10"
  }
}

core-js和regenerator-runtime(这个为了避免entry参数时包错找不到包)都安装起。这样就可以了。

使用@babel/plugin-transform-runtime和@babel/runtime-corejs2来实现API的polyfill

前面使用了@babel/preset-env的useBuiltIns来实现API填充,这儿试一试另一种方案,首先卸载掉core-js和regenerator-runtime,然后安装一下两个包

npm install @babel/plugin-transform-runtime -D
npm install @babel/runtime-corejs2 -S

安装完成后配置一下babel.config.js

module.exports = {
    presets: [
        ['@babel/preset-env']
    ],
    plugins: [
        ['@babel/plugin-transform-runtime', {
            'corejs': 2
        }]
    ]
}

注意:这儿我是安装的@babel/runtime-corejs2,所以“'corejs': 2”这个参数不能少,否者会报 Can't resolve '@babel/runtime/helpers/createClass'这种错误,上面这种方式不会污染全局,适合第三方开发。

四、总结

暂时babel就总结到这儿了,顺便熟悉了一下webpack,如有不准确的地方还请各位多多指正,文章里面还有几个没理解清楚的地方,后期再琢磨琢磨!

@cookiepool cookiepool changed the title [Babel]关于babel需要了解的 [Babel]Babel学习手记 Nov 6, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant