Babel中文:
Babel 中文网 · Babel - 下一代 JavaScript 语法的编译器

现代浏览器基本都支持了ES6及更新版本的代码语法,但是不保证所有人都在使用最新的浏览器,这个时候如果我们的项目完全使用最新的语法去编写,到了一些老版本的浏览器就会报错无法执行。

例如我们在src/index.js文件中写一些ES6的代码:

  1. const arr = [new Promise(() => {}), new Promise(() => {})];
  2. arr.map(item => {
  3. console.log(item);
  4. });

然后运行npm run build进行打包,打包完成后,我们找到dist/main.js在这个文件的最后几行会看到这样的代码:

  1. eval("const arr = [new Promise(() => {}), new Promise(() => {})];\n\narr.map(item => {\n console.log(item);\n})//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvaW5kZXguanMuanMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9jaGFwdGVyMDEvLi9zcmMvaW5kZXguanM/YjYzNSJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCBhcnIgPSBbbmV3IFByb21pc2UoKCkgPT4ge30pLCBuZXcgUHJvbWlzZSgoKSA9PiB7fSldO1xuXG5hcnIubWFwKGl0ZW0gPT4ge1xuICAgIGNvbnNvbGUubG9nKGl0ZW0pO1xufSkiXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./src/index.js\n");

这时候会发现**src/index.js**文件中写的代码未发生改变的被打包了出来,这样的问题就会导致在低版本浏览器中是无法运行代码的(比如**ie5**)。

babel-loader & @babel/preset-env

这个时候我们就可以利用Babel将我们写的ES6语法转换为老版本浏览器可以认识的语法。

安装:

  1. $ npm install babel-loader @babel/preset-env --save-dev

:::info

  • babel-loader:使用babel-loader处理js文件,实际上babel-loader是在让webpackbabel通信的桥梁,babel-loader本身并不会将ES6语法转换成ES5的语法。
  • @babel/preset-env:将ES6语法转换为ES5的语法,它包含了所有ES6转为ES5的语法规则。 :::

配置:

  1. // ...
  2. moudle.exports = {
  3. // ...
  4. module:{
  5. rules:[
  6. // ...
  7. {
  8. test: /\.m?js$/,
  9. // 排除 node_modules 目录下的代码
  10. exclude: /node_modules/,
  11. use: {
  12. loader: "babel-loader",
  13. options: {
  14. presets: ['@babel/preset-env']
  15. }
  16. }
  17. }
  18. ]
  19. }
  20. // ...
  21. }

等以后安装好且配置完毕后,再运行npm run build,就能看到部分代码转为为ES5的语法了。

  1. eval("var arr = [new Promise(function () {}), new Promise(function () {})];\narr.map(function (item) {\n console.log(item);\n});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9jaGFwdGVyMDEvLi9zcmMvaW5kZXguanM/YjYzNSJdLCJuYW1lcyI6WyJhcnIiLCJQcm9taXNlIiwibWFwIiwiaXRlbSIsImNvbnNvbGUiLCJsb2ciXSwibWFwcGluZ3MiOiJBQUFBLElBQU1BLEdBQUcsR0FBRyxDQUFDLElBQUlDLE9BQUosQ0FBWSxZQUFNLENBQUUsQ0FBcEIsQ0FBRCxFQUF3QixJQUFJQSxPQUFKLENBQVksWUFBTSxDQUFFLENBQXBCLENBQXhCLENBQVo7QUFFQUQsR0FBRyxDQUFDRSxHQUFKLENBQVEsVUFBQUMsSUFBSSxFQUFJO0FBQ1pDLEVBQUFBLE9BQU8sQ0FBQ0MsR0FBUixDQUFZRixJQUFaO0FBQ0gsQ0FGRCIsInNvdXJjZXNDb250ZW50IjpbImNvbnN0IGFyciA9IFtuZXcgUHJvbWlzZSgoKSA9PiB7fSksIG5ldyBQcm9taXNlKCgpID0+IHt9KV07XG5cbmFyci5tYXAoaXRlbSA9PiB7XG4gICAgY29uc29sZS5sb2coaXRlbSk7XG59KSJdLCJmaWxlIjoiLi9zcmMvaW5kZXguanMuanMiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./src/index.js\n");

可以明显的看到let被转换为var()=>{}被转换为function(){}

@babel/polyfill

根据上面的dist/main.js的代码可以看到虽然部分代码被转换为了ES5的写法,但是像new Promisemap这样的方法依然没有进行编译,它们在低版本浏览器依然是不存在的。这时就需要不仅使用@babel/preset-env来做语法转换,还需要使用@babel/polyfil来帮我们做代码填充。 :::info @babel/polyfill相当于给代码打一个补丁,当我们使用PromiseMap这样方法的时候就可以使用这个补丁去实现Promise等方法。
老版本的浏览器没有新语法的API,所以我们只能手写实现。但是网上搜到的写法不够权威标准,而且每当一个方法不存在的时候都需要我们手写那就造成很大的开发量而且麻烦,所以我们就希望有一个库既能实现缺失的方法又比较权威标准,而这个就是corejs库。
corejs库里面就有Promise这样的方法实现,我们只要使用了它就可以放心的编写ES6+的代码了,但是corejs无法转换generator语法,这个时候🈶又可以使用regenerator这个库来弥补corejs的缺陷。
当我们把corejsregenerator结合之后,我们所有的新语法就就都能支持了,然而这就是@babel/polyfill,它就是进行了汇总。 :::

安装:

  1. $ npm install @babel/polyfill --save-dev

然后在我们需要运行的ES6的代码文件中的最上面引入@babel/polyfill来帮我们填充代码:

  1. import "@babel/polyfill";
  2. const arr = [new Promise(() => {}), new Promise(() => {})];
  3. arr.map(item => {
  4. console.log(item);
  5. })

运行之后我们会发现打包后的main.js文件竟有952KiB???
WX20221127-143133.png

这是因为我们引入@babel/polyfil的时候,它把所有的语法全部填充到了代码中,所以我们需要配置Babel来实现自动按需引入。

  1. // ...
  2. modules.export = {
  3. // ...
  4. module: {
  5. rules: [
  6. // ...
  7. {
  8. test: /\.m?js$/,
  9. // 排除 node_modules 下的代码
  10. exclude: /node_modules/,
  11. use: {
  12. loader: "babel-loader",
  13. options: {
  14. // presets[ [需要使用 presets 的名字, {具体配置}] ]
  15. presets: [
  16. [
  17. '@babel/preset-env',
  18. {
  19. // 当使用@babel/polyfill填充代码时,按需引入
  20. useBuiltIns: "usage",
  21. // 声明corejs版本(不然无法编译)
  22. corejs: "3"
  23. }
  24. ]
  25. ]
  26. }
  27. }
  28. }]
  29. },
  30. // ...
  31. }

另外还需要删除src/index.js中的@babel/polyfil完整引入(否则按需引入就配置了个寂寞)。

  1. const arr = [new Promise(() => {}), new Promise(() => {})];
  2. arr.map(item => {
  3. console.log(item);
  4. })

最后运行nm run build进行打包,bundle.js成功了缩减到了146KiB
WX20221127-143951.png

当我们想开发一个类库或者UI组件库的时候,@babel/polyfill就不适用了,因为这是@babel/polyfill会污染全局的变量名。
例如@babel/polyfill内实现Promise是全局挂载到window对象上的,window.Promise = function(){...},假如我们用的其他代码库也挂载了一个widnow.Promise属性,这样就会产生冲突!!!
可以使用@babel/plugin-transform-runtime来解决这个问题。
@babel/plugin-transform-runtime · Babel 中文网
因为它会有效的避免全局污染的问题,因为它是以闭包的方式引入。

单独配置 Babel

我们还可以将Babel的配置单独文件进行管理配置(和webpack.config.js一样,不能进行随意命名)。

  1. module.exports = {
  2. // 把 babel-loader 中的配置拿到这里
  3. presets: [
  4. [
  5. '@babel/preset-env', {
  6. corejs: "3", // 声明corejs版本
  7. useBuiltIns: "usage" // 当使用@babel/polyfill填充代码时,按需填充。
  8. }
  9. ]
  10. ]
  11. }
  1. // ...
  2. modules.export = {
  3. // ...
  4. module: {
  5. rules: [
  6. // ...
  7. {
  8. test: /\.m?js$/,
  9. // 排除 node_modules 下的代码
  10. exclude: /node_modules/,
  11. use: {
  12. loader: "babel-loader"
  13. }
  14. }]
  15. },
  16. // ...
  17. }